commit 5a945a4a1b45431a7683ba44fddec694f391c2f1
Author: dsorber <david.sorber@gmail.com>
Date:   Sun Aug 20 13:38:21 2017 -0400

    Very first version of pwmgr. It needs lots of work but does the basics.

diff --git a/software/pwmgr/pwmgr.py b/software/pwmgr/pwmgr.py
old mode 100644
new mode 100755
index d337133..a5466b7
--- a/software/pwmgr/pwmgr.py
+++ b/software/pwmgr/pwmgr.py
@@ -1,103 +1,122 @@
+#!/usr/bin/python3
+import os
+import shutil
+import subprocess
 import sys
 
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk, Pango
 
+MOUNT_PATH = '/mnt/pw_ramdisk'
+PWD_FILE_PATH = '/home/dsorber/Desktop/network'
+
+ENC_FILE_PATH = '/home/dsorber/Documents/chicken_soup.enc'
+SCRATCH_FILE_PATH = '/mnt/pw_ramdisk/chicken_soup.txt'
+ENC_TMP_FILE_PATH = '/home/dsorber/Documents/chicken_soup.enc.tmp'
+
+VERSION = '0.0.1'
+
 class SearchDialog(Gtk.Dialog):
 
     def __init__(self, parent):
-        Gtk.Dialog.__init__(self, "Search", parent,
+        Gtk.Dialog.__init__(self, 'Search', parent,
             Gtk.DialogFlags.MODAL, buttons=(
             Gtk.STOCK_FIND, Gtk.ResponseType.OK,
             Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL))
 
         box = self.get_content_area()
 
-        label = Gtk.Label("Insert text you want to search for:")
+        label = Gtk.Label('Insert text you want to search for:')
         box.add(label)
 
         self.entry = Gtk.Entry()
         box.add(self.entry)
 
         self.show_all()
+        
+def get_user_pw(parent, message, title=''):
+    """
+    Returns user input as a string or None
+    If user does not input text it returns None, NOT AN EMPTY STRING.
+    
+    https://stackoverflow.com/questions/13970445/python-gtk3-how-to-add-a-gtk-entry-to-a-gtk-messagedialog
+    """
+    dialogWindow = Gtk.MessageDialog(parent,
+                                     Gtk.DialogFlags.MODAL | 
+                                     Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                                     Gtk.MessageType.QUESTION,
+                                     Gtk.ButtonsType.OK_CANCEL,
+                                     message)
+
+    dialogWindow.set_title(title)
+
+    dialogBox = dialogWindow.get_content_area()
+    userEntry = Gtk.Entry()
+    userEntry.set_visibility(False)
+    userEntry.set_invisible_char('*')
+    userEntry.set_size_request(200, 0)
+    dialogBox.pack_end(userEntry, False, False, 0)
+
+    dialogWindow.show_all()
+    response = dialogWindow.run()
+    text = userEntry.get_text() 
+    dialogWindow.destroy()
+    if (response == Gtk.ResponseType.OK) and (text != ''):
+        return text
+    else:
+        return None
 
 class TextViewWindow(Gtk.Window):
 
     def __init__(self):
-        Gtk.Window.__init__(self, title="TextView Example")
+        Gtk.Window.__init__(self, title='pwmgr')
 
-        self.set_default_size(-1, 350)
+        self.set_default_size(800, 600)
 
         self.grid = Gtk.Grid()
         self.add(self.grid)
 
         self.create_textview()
         self.create_toolbar()
-        self.create_buttons()
+        self.create_bottom()
+        
+        self.mounted = False
+        self.pwd_file_fd = None
+        self.pwd = None
 
     def create_toolbar(self):
         toolbar = Gtk.Toolbar()
         self.grid.attach(toolbar, 0, 0, 3, 1)
 
-        button_bold = Gtk.ToolButton()
-        button_bold.set_icon_name("format-text-bold-symbolic")
-        toolbar.insert(button_bold, 0)
-
-        button_italic = Gtk.ToolButton()
-        button_italic.set_icon_name("format-text-italic-symbolic")
-        toolbar.insert(button_italic, 1)
-
-        button_underline = Gtk.ToolButton()
-        button_underline.set_icon_name("format-text-underline-symbolic")
-        toolbar.insert(button_underline, 2)
-
-        button_bold.connect("clicked", self.on_button_clicked, self.tag_bold)
-        button_italic.connect("clicked", self.on_button_clicked,
-            self.tag_italic)
-        button_underline.connect("clicked", self.on_button_clicked,
-            self.tag_underline)
+        button_open = Gtk.ToolButton()
+        button_open.set_icon_name('document-open')
+        toolbar.insert(button_open, 0)        
+        button_open.connect('clicked', self.open_pwd_file)
+        
+        button_save = Gtk.ToolButton()
+        button_save.set_icon_name('document-save')
+        toolbar.insert(button_save, 1)
+        button_save.connect('clicked', self.save_pwd_file)
+        
+        button_close = Gtk.ToolButton()
+        button_close.set_icon_name('window-close')
+        toolbar.insert(button_close, 2)
+        button_close.connect('clicked', self.close_pwd_file, None)
 
         toolbar.insert(Gtk.SeparatorToolItem(), 3)
 
-        radio_justifyleft = Gtk.RadioToolButton()
-        radio_justifyleft.set_icon_name("format-justify-left-symbolic")
-        toolbar.insert(radio_justifyleft, 4)
-
-        radio_justifycenter = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
-        radio_justifycenter.set_icon_name("format-justify-center-symbolic")
-        toolbar.insert(radio_justifycenter, 5)
-
-        radio_justifyright = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
-        radio_justifyright.set_icon_name("format-justify-right-symbolic")
-        toolbar.insert(radio_justifyright, 6)
-
-        radio_justifyfill = Gtk.RadioToolButton.new_from_widget(radio_justifyleft)
-        radio_justifyfill.set_icon_name("format-justify-fill-symbolic")
-        toolbar.insert(radio_justifyfill, 7)
-
-        radio_justifyleft.connect("toggled", self.on_justify_toggled,
-            Gtk.Justification.LEFT)
-        radio_justifycenter.connect("toggled", self.on_justify_toggled,
-            Gtk.Justification.CENTER)
-        radio_justifyright.connect("toggled", self.on_justify_toggled,
-            Gtk.Justification.RIGHT)
-        radio_justifyfill.connect("toggled", self.on_justify_toggled,
-            Gtk.Justification.FILL)
-
-        toolbar.insert(Gtk.SeparatorToolItem(), 8)
-
         button_clear = Gtk.ToolButton()
-        button_clear.set_icon_name("edit-clear-symbolic")
-        button_clear.connect("clicked", self.on_clear_clicked)
-        toolbar.insert(button_clear, 9)
+        button_clear.set_icon_name('edit-clear-symbolic')
+        button_clear.connect('clicked', self.on_clear_clicked)
+        toolbar.insert(button_clear, 4)
 
-        toolbar.insert(Gtk.SeparatorToolItem(), 10)
+        toolbar.insert(Gtk.SeparatorToolItem(), 5)
 
         button_search = Gtk.ToolButton()
-        button_search.set_icon_name("system-search-symbolic")
-        button_search.connect("clicked", self.on_search_clicked)
-        toolbar.insert(button_search, 11)
+        button_search.set_icon_name('system-search-symbolic')
+        button_search.connect('clicked', self.on_search_clicked)
+        toolbar.insert(button_search, 6)
 
     def create_textview(self):
         scrolledwindow = Gtk.ScrolledWindow()
@@ -106,10 +125,9 @@ class TextViewWindow(Gtk.Window):
         self.grid.attach(scrolledwindow, 0, 1, 3, 1)
 
         self.textview = Gtk.TextView()
+        self.textview.set_editable(False)
+        self.textview.set_cursor_visible(False)
         self.textbuffer = self.textview.get_buffer()
-        self.textbuffer.set_text("This is some text inside of a Gtk.TextView. "
-            + "Select text and click one of the buttons 'bold', 'italic', "
-            + "or 'underline' to modify the text accordingly.")
         scrolledwindow.add(self.textview)
 
         self.tag_bold = self.textbuffer.create_tag("bold",
@@ -118,41 +136,98 @@ class TextViewWindow(Gtk.Window):
             style=Pango.Style.ITALIC)
         self.tag_underline = self.textbuffer.create_tag("underline",
             underline=Pango.Underline.SINGLE)
-        self.tag_found = self.textbuffer.create_tag("found",
-            background="orange")
-
-    def create_buttons(self):
-        check_editable = Gtk.CheckButton("Editable")
-        check_editable.set_active(True)
-        check_editable.connect("toggled", self.on_editable_toggled)
-        self.grid.attach(check_editable, 0, 2, 1, 1)
-
-        check_cursor = Gtk.CheckButton("Cursor Visible")
-        check_cursor.set_active(True)
-        check_editable.connect("toggled", self.on_cursor_toggled)
-        self.grid.attach_next_to(check_cursor, check_editable,
-            Gtk.PositionType.RIGHT, 1, 1)
-
-        radio_wrapnone = Gtk.RadioButton.new_with_label_from_widget(None,
-            "No Wrapping")
-        self.grid.attach(radio_wrapnone, 0, 3, 1, 1)
-
-        radio_wrapchar = Gtk.RadioButton.new_with_label_from_widget(
-            radio_wrapnone, "Character Wrapping")
-        self.grid.attach_next_to(radio_wrapchar, radio_wrapnone,
-            Gtk.PositionType.RIGHT, 1, 1)
-
-        radio_wrapword = Gtk.RadioButton.new_with_label_from_widget(
-            radio_wrapnone, "Word Wrapping")
-        self.grid.attach_next_to(radio_wrapword, radio_wrapchar,
-            Gtk.PositionType.RIGHT, 1, 1)
-
-        radio_wrapnone.connect("toggled", self.on_wrap_toggled,
-            Gtk.WrapMode.NONE)
-        radio_wrapchar.connect("toggled", self.on_wrap_toggled,
-            Gtk.WrapMode.CHAR)
-        radio_wrapword.connect("toggled", self.on_wrap_toggled,
-            Gtk.WrapMode.WORD)
+        self.tag_found = self.textbuffer.create_tag('found', 
+                                                    background='orange')
+
+    def create_bottom(self):
+        
+        label = Gtk.Label()
+        label.set_text('version: {:s}'.format(VERSION))
+        label.set_justify(Gtk.Justification.RIGHT)
+        
+        self.grid.attach(label, 0, 2, 3, 1)
+
+    def open_pwd_file(self, widget):
+        """ 
+        Open handler
+        """
+        
+        # Get password
+        self.pwd = get_user_pw(self, 'Please enter your password', 'Password')
+        #~ print('User entered: {:s}'.format(self.pwd))
+        
+        if not self.mounted:
+            os.makedirs(MOUNT_PATH, 0o600, exist_ok=True)
+            
+            # Mount the tmpfs
+            mnt_cmd = 'mount -t tmpfs -o size=16m tmpfs {:s}'
+            mnt_cmd = mnt_cmd.format(MOUNT_PATH)
+            subprocess.call(mnt_cmd, shell=True, timeout=5)
+            self.mounted = True
+            
+            cmd = 'openssl enc -aes-256-cbc -d -in {:s} -out {:s} -pass pass:{:s}'
+            cmd = cmd.format(ENC_FILE_PATH, SCRATCH_FILE_PATH, self.pwd)
+            subprocess.call(cmd, shell=True, timeout=5)
+            
+            # Open and load file; make textview editable
+            with open(SCRATCH_FILE_PATH, 'r') as pwd_file_fd:
+                self.textbuffer.set_text(pwd_file_fd.read())
+            self.textview.set_editable(True)
+            self.textview.set_cursor_visible(True)
+        
+    def save_pwd_file(self, widget):
+        """
+        Save handler
+        """
+        
+        if self.mounted:
+            
+            with open(SCRATCH_FILE_PATH, 'w') as pwd_file_fd:
+                pwd_file_fd.write(
+                    self.textbuffer.get_text(self.textbuffer.get_start_iter(),
+                                             self.textbuffer.get_end_iter(), 
+                                             True))
+            print('Write complete')
+            
+            cmd = 'openssl enc -aes-256-cbc -salt -in {:s} -out {:s} -pass pass:{:s}'
+            cmd = cmd.format(SCRATCH_FILE_PATH, ENC_TMP_FILE_PATH, self.pwd)
+            subprocess.call(cmd, shell=True, timeout=5)
+            print('enc complete')
+            
+            # Clear the buffer and disable the textview
+            self.textbuffer.set_text('')
+            self.textview.set_editable(False)
+            self.textview.set_cursor_visible(False)
+            
+    def close_pwd_file(self, widget, other):
+        """
+        Close handler
+        """
+
+        rand_write_cmd = 'dd if=/dev/urandom of={:s} bs=16M count=1 status=none'
+        rand_write_cmd = rand_write_cmd.format(os.path.join(MOUNT_PATH, 'data'))
+        
+        zero_write_cmd = 'dd if=/dev/zero of={:s} bs=16M count=1 status=none'
+        zero_write_cmd = zero_write_cmd.format(os.path.join(MOUNT_PATH, 'data'))
+        
+        if self.mounted:
+            #~ shutil.rmtree(os.path.join(MOUNT_PATH, '*'), ignore_errors=True)
+            rm_cmd = 'rm -rf {:s}'.format(os.path.join(MOUNT_PATH, '*'))
+            subprocess.call(rm_cmd, shell=True, timeout=5)
+            
+            # Overwrite a few times
+            for ctr in range(10):
+                print(ctr)
+                subprocess.call(rand_write_cmd, shell=True, timeout=5)
+                subprocess.call(rm_cmd, shell=True, timeout=5)
+                subprocess.call(zero_write_cmd, shell=True, timeout=5)
+                subprocess.call(rm_cmd, shell=True, timeout=5)
+            
+            umount_cmd = 'umount {:s}'.format(MOUNT_PATH)
+            subprocess.call(umount_cmd, shell=True, timeout=5)
+        
+        Gtk.main_quit()
+        print('CLOSE')
 
     def on_button_clicked(self, widget, tag):
         bounds = self.textbuffer.get_selection_bounds()
@@ -165,18 +240,6 @@ class TextViewWindow(Gtk.Window):
         end = self.textbuffer.get_end_iter()
         self.textbuffer.remove_all_tags(start, end)
 
-    def on_editable_toggled(self, widget):
-        self.textview.set_editable(widget.get_active())
-
-    def on_cursor_toggled(self, widget):
-        self.textview.set_cursor_visible(widget.get_active())
-
-    def on_wrap_toggled(self, widget, mode):
-        self.textview.set_wrap_mode(mode)
-
-    def on_justify_toggled(self, widget, justification):
-        self.textview.set_justification(justification)
-
     def on_search_clicked(self, widget):
         dialog = SearchDialog(self)
         response = dialog.run()
@@ -200,9 +263,11 @@ class TextViewWindow(Gtk.Window):
             self.search_and_mark(text, match_end)
 
 def main():
-    
+    """
+    NOTE: this *must* be called using sudo or gksudo
+    """
     win = TextViewWindow()
-    win.connect("delete-event", Gtk.main_quit)
+    win.connect('delete-event', win.close_pwd_file)
     win.show_all()
     Gtk.main()
     
