#!/usr/bin/env python
# GPG keys manager
# Author: Christopher R. Gabriel <cgabriel@gnu.org>
# Licensed under the terms of the GNU GPL
#
# TODO:
# preferences (keyserver, port, etc)
# UI improvements

from gnome.ui import *
from gtk import *
import GdkImlib
import string, urllib, re, os,sys
import smtplib

#config
version = "0.4"

## we're done here

cfgfile = os.environ['HOME'] + "/.nofgpgrc"
needconfig = 0
config = 0

def write_conf(cfgfile=cfgfile,keyserver="keyserver.linux.it",port="11371"):

    import ConfigParser
    cfp = ConfigParser.ConfigParser()
    cfp.add_section("NOFGPG")
    cfp.set("NOFGPG", "keyserver", keyserver)
    cfp.set("NOFGPG", "port", port)
    f = open(cfgfile, "w")
    cfp.write(f)
    f.close()
    
def read_conf(cfgfile=cfgfile):
    import ConfigParser
    cfp = ConfigParser.ConfigParser()
    cfp.read(cfgfile)
    config = {}
    config['keyserver'] = cfp.get("NOFGPG", "keyserver")
    config['port'] = cfp.get("NOFGPG", "port")
    return config

try:
    os.stat(cfgfile)
except OSError:
    needconfig =1
    write_conf()
    config = read_conf()
    


class Configger:
    
    def __init__(self):

        
        logo = GdkImlib.Image("/usr/share/pixmaps/gnome-settings.png")        
        self.win = GtkWindow()
        self.conf = GnomeDruid()
        self.win.add(self.conf)
        self.conf.show()
        self.conf.connect("cancel", self.write)

        page = GnomeDruidPageStart(title="NOFGPG Configuration",text="Welcome, here you can configure NOFGPG. \nPlease press the 'Next' button and Enjoy!",logo=logo)
        page.show()
        self.conf.append_page(page)

        label = GtkLabel("Keyserver (just type the host name, like 'pgp.mit.edu'")
        self.entry = GtkEntry()
 

        page = GnomeDruidPageStandard(title="Keyserver",logo=logo)
        vbox = page.__getattr__("vbox")
        vbox.pack_start(label)
        vbox.pack_start(self.entry)
        label.show()
        self.entry.show()
        page.show()
        self.conf.append_page(page)

	page = GnomeDruidPageFinish(title="And we're done!",text="Thanks, configuration complete.",logo=logo)

	page.finish()
        page.show()
        self.conf.append_page(page)
	  
        self.win.show()
    
    def write(self,cfp,keyserver="keyserver.linux.it",port="11371"):
        global config
        newkeyserver = self.entry.get_text()
        if(len(newkeyserver) <= 0):
            ks = keyserver
        else:
            ks = newkeyserver
            
        write_conf(keyserver=ks,port=port)
        self.win.destroy()
        config = read_conf()
        
    def cancel(self,cfp):
        self.write()
        self.win.destroy()

if(needconfig ==1):
    conf = Configger()
else:
    config = read_conf()
    
    
class Crypter:

    def __init__(self):
        self.win = GtkDialog()
        frame = GtkFrame("Crypt something")
        self.win.vbox.pack_start(frame)
        frame.show()
        bar = GtkOptionMenu()
        frame.add(bar)
        bar.show()
        menu = GtkMenu()
        
        menuitem = GtkMenuItem("cip")
        menu.append(menuitem)
        menuitem.show()
        menuitem = GtkMenuItem("ciop")
        menu.append(menuitem)
        menuitem.show()
        menu.show()
        bar.set_menu(menu)
        self.win.show()
    
class Importer:

    def __init__(self):
        self.file = None
        self.choices = {}
        self.win = GtkDialog()
        frame = GtkFrame("Import new key")
        self.win.vbox.pack_start(frame)
        frame.show()
        vbox = GtkVBox(spacing=3)
        frame.add(vbox)
        vbox.show()

        
        text = self.choices['text'] = GtkText()
        text.set_editable(TRUE)
        choice1 = GtkRadioButton(None, "Text")
        choice1.connect("toggled", self.set_sense, "text")
        vbox.pack_start(choice1)
        choice1.show()
       
        vbox.pack_start(text)
        text.show()

       
        file = self.choices['file'] = GtkButton("choose..")
        file.connect("clicked", self.select_file)
        file.set_sensitive(FALSE)
        
        choice2 = GtkRadioButton(choice1, "File")
        choice2.connect("toggled", self.set_sense, "file")
        vbox.pack_start(choice2)
        choice2.show()
        
        box = GtkHBox(spacing=3)
        vbox.pack_start(box)
        box.show()
        
        self.entry = GtkEntry()
        self.entry.set_sensitive(FALSE)
        box.pack_start(self.entry)
        self.entry.show()
        box.pack_start(file)
        file.show()

        button = GtkButton("close")
        button.connect("clicked", self.hide)
        self.win.action_area.pack_start(button)
        button.show()
                
        ib = GtkButton("import")
        ib.connect("clicked", self.do_import)
        self.win.action_area.pack_start(ib)
        ib.set_flags(CAN_DEFAULT)
        ib.grab_default()
        
        ib.show()


    def do_import(self,button):
        data = self.choices['text'].get_chars(0,-1)
        p = os.popen("gpg --import", 'w')
        p.write(data)
        res = p.close()
        self.hide(None)
        if(res == None):
            dlg = GnomeOkDialog("Key Imported!")
            dlg.set_modal(TRUE)
            dlg.run()
        else:
            dlg = GnomeErrorDialog("Errors importing the key!")
            dlg.set_modal(TRUE)
            dlg.run()

        
    def file_selection_ok(self, button, fs):
        self.choices['text'].delete_text(0,-1)
        self.entry.delete_text(0,-1)
        f = open(fs.get_filename(), "r")
        self.choices['text'].insert_defaults(f.read())
        self.entry.set_text(fs.get_filename())
        fs.hide()
        
    def select_file(self,button):
        win = GtkFileSelection("Choose the file containing the key")
        win.connect("delete_event", win.hide)
        win.ok_button.connect("clicked", self.file_selection_ok, win)
        win.cancel_button.connect("clicked", win.hide)
        win.show()
        
    def set_sense(self, button, arg):
        if(arg == "text"):
            self.choices['text'].set_sensitive(TRUE)
            self.choices['file'].set_sensitive(FALSE)
            self.entry.set_sensitive(FALSE)

        else:
            self.choices['text'].set_sensitive(FALSE)
            self.choices['file'].set_sensitive(TRUE)
            self.entry.set_sensitive(TRUE)
            
    def hide(self,foo):
        self.win.hide()
        
    def show(self):
        self.win.show()

def import_new(button):
    importer = Importer()
    importer.show()

def crypter_new(button):
    crypter = Crypter()
    
class SecKeys:

    def __init__(self):
        self.ui = GtkVBox(spacing=3)

        
        
        self.sb = GtkScrolledWindow()
       

        self.sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
        self.ui.pack_start(self.sb)
        self.vbox = GtkVBox(spacing=3)
        self.sb.add_with_viewport(self.vbox)
        self.clist =  GtkCList(4, ["Key Id", "Date", "Owner", "Email"])
        self.clist.set_column_width(0, 100)
        self.clist.set_column_width(1, 70)
        self.clist.set_column_width(2, 150)
        self.vbox.pack_start(self.clist)
        self.clist.show()
        self.vbox.show()
        self.found_keys = []
        self.sb.show()
        self.ui.show()

        
    def refresh(self,widget):
        self.clist.clear()
        self.display_keys()
        
    def display_keys(self):
        
        p = os.popen("gpg --list-secret-keys", 'r')
        data = string.strip(p.read())
        res = p.close()
        if(res == None):
            data = string.replace(data, "\r", "")
            data = string.split(data, "\n")
            pub = re.compile("^sec.*")
            for datakey in data:
                match = pub.search(datakey)
                if(match != None):
                    key = string.split(match.group())
                    key_data = []
                    key_data.append(key[1])
                    key_data.append(key[2])
                    key_data.append(string.join(key[3:-1]))
                    key_data.append(key[-1])
                    self.clist.append(key_data)
        else:
            dlg = GnomeErrorDialog("Error fetching secret key list")
            dlg.set_modal(TRUE)
            dlg.run()
            
class PubKeys:

    def __init__(self):
        self.ui = GtkVBox(spacing=3)
        
        self.sb = GtkScrolledWindow()
       
        self.s = None
              
        self.sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
        self.ui.pack_start(self.sb)
        self.vbox = GtkVBox(spacing=3)
        self.sb.add_with_viewport(self.vbox)
        self.clist =  GtkCList(4, ["Key Id", "Date", "Owner", "Email"])
        self.clist.connect("button_press_event", self.show_menu)
        self.clist.connect("select_row", self.select_item)
       
        
        self.clist.set_column_width(0, 100)
        self.clist.set_column_width(1, 70)
        self.clist.set_column_width(2, 150)
        self.vbox.pack_start(self.clist)
        self.clist.show()

        scroll = GtkScrolledWindow()
        scroll.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
        self.ui.pack_start(scroll)
        box = GtkVBox(spacing=3)
        box.show()
        scroll.show()
        scroll.add_with_viewport(box)
        
        self.clistsub = GtkCList(2, ["Owner", "Email"])
        box.pack_start(self.clistsub)
        self.clistsub.show()
        
        self.menu = GtkMenu()
        menuitem = GtkMenuItem("Delete this key")
        menuitem.connect("activate", self.delete_key)
        self.menu.append(menuitem)
        menuitem.show()

        menuitem = GtkMenuItem("Export this key")
        menuitem.connect("activate", self.export_key)
        self.menu.append(menuitem)
        menuitem.show()
        menuitem = GtkMenuItem("Send him my public key")
        menuitem.connect("activate", self.send_my_key)
        self.menu.append(menuitem)
        menuitem.show()
        self.vbox.show()
        self.found_keys = []
        self.sb.show()
        self.ui.show()

    def delete_key(self,button):
        if(self.s == None):
            dlg = GnomeErrorDialog("Please select the key to delete first!")
            dlg.set_modal(TRUE)
            dlg.run()
        elif (self.s == 0):
            dlg = GnomeErrorDialog("Do you really want to remove *YOUR* public key, dude?")
            dlg.set_modal(TRUE)
            dlg.run()
        else:
            keyid = string.split(self.clist.get_text(self.s,0), "/")[1]
            os.system("gpg --batch --yes --delete-key %s" % keyid)
            dlg = GnomeOkDialog("Key %s removed" % keyid)
            self.refresh(dlg)
            dlg.set_modal(TRUE)
            dlg.run()
            
    def export_key(self,button):
        if(self.s == None):
            dlg = GnomeErrorDialog("Please select the key to export first!")
            dlg.set_modal(TRUE)
            dlg.run()
        else:
            keyid = string.split(self.clist.get_text(self.s,0), "/")[1]
            keyname = string.replace(self.clist.get_text(self.s, 3), "<", "")
            keyname = string.replace(keyname, ">", "")
            
            export_file = os.environ['HOME'] + "/" + keyname + "-public-key"
            os.system("gpg --export -a %s -o %s" % (keyid,export_file))
            dlg = GnomeOkDialog("Key %s exported to %s" % (keyid,export_file))
            dlg.set_modal(TRUE)
            dlg.run()
            
    def send_my_key(self,button):
        key_from = string.replace(self.clist.get_text(0,3), "<", "")
        key_from = string.replace(key_from, ">", "")
        key_to =  string.replace(self.clist.get_text(self.s,3), "<", "")
        key_to = string.replace(key_to, ">", "")
        
        p = os.popen("gpg --export -a %s" %  self.clist.get_text(0,2))
        data = "Hi, this is my public key! \n\n" + string.strip(p.read())
        res = p.close()
        if(res == None):
            server = smtplib.SMTP('localhost')
            server.sendmail(key_from, key_to, data)
            server.quit()
            dlg = GnomeOkDialog("Public key sent to %s" % key_to)
            dlg.set_modal(TRUE)
            dlg.run()
        else:
            dlg = GnomeErrorDialog("Error fetching public key")
            dlg.set_modal(TRUE)
            dlg.run()
        
    def show_menu(self, _clist, event):
        if(event.button == 3):
            self.clist.select_row(self.clist.get_selection_info(event.x, event.y)[0],self.clist.get_selection_info(event.x, event.y)[1])
            self.menu.popup(None, None, None , event.button, event.time)

    def select_item(self, _clist, r, c, event):
        self.s = r
        self.show_subuids()

    def show_subuids(self):
        self.clistsub.clear()
        keyid = string.split(self.clist.get_text(self.s,0), "/")[1]
        
        p = os.popen("gpg --list-keys %s" % keyid, 'r')
        data = string.strip(p.read())
        res = p.close()
        if(res == None):
            data = string.replace(data, "\r", "")
            data = string.split(data, "\n")
            pub = re.compile("^uid.*")
            for datakey in data:
                match = pub.search(datakey)
                if(match != None):
                    key = string.split(match.group())
                    key_data = []
                    key_data.append(string.join(key[1:-1]))
                    key_data.append(key[-1])
                    self.clistsub.append(key_data)
        else:
            dlg = GnomeErrorDialog("Error fetching public key list")
            dlg.set_modal(TRUE)
            dlg.run()
            
    def refresh(self,widget):
        self.clist.clear()
        self.display_keys()
        
    def display_keys(self):
        
        p = os.popen("gpg --list-keys", 'r')
        data = string.strip(p.read())
        res = p.close()
        if(res == None):
            data = string.replace(data, "\r", "")
            data = string.split(data, "\n")
            pub = re.compile("^pub.*")
            for datakey in data:
                match = pub.search(datakey)
                if(match != None):
                    key = string.split(match.group())
                    key_data = []
                    key_data.append(key[1])
                    key_data.append(key[2])
                    key_data.append(string.join(key[3:-1]))
                    key_data.append(key[-1])
                    self.clist.append(key_data)
        else:
            dlg = GnomeErrorDialog("Error fetching public key list")
            dlg.set_modal(TRUE)
            dlg.run()
            
class Search:
    def __init__(self):
        global config

        self.path = "/pks/lookup?op=index&search=%s"
        self.opener = urllib.URLopener()
        self.s = None
        self.found_keys = []
        self.founded = 0
        
        self.ui = GtkVBox(spacing=3)

        self.hbox = GtkHBox(spacing=3)
        self.ui.pack_start(self.hbox, expand=FALSE, fill=FALSE)

        
        label = GtkLabel("Search for:")
        self.hbox.pack_start(label)
        label.show()
        self.entry = GtkEntry()
        self.entry.connect("activate", self.fetch)
        self.hbox.pack_start(self.entry)
        self.entry.show()
            
        self.search_b = GtkButton("Search!")
        self.search_b.connect("clicked", self.fetch)
        self.hbox.pack_start(self.search_b)
        self.search_b.show()
        self.import_key_b = GtkButton("Import!")
        self.hbox.pack_start(self.import_key_b)
        self.import_key_b.connect("clicked", self.import_selected_key)
        self.import_key_b.show()
        self.clear_b = GtkButton("Clear list")
        self.clear_b.connect("clicked", self.clear_list)
        self.hbox.pack_start(self.clear_b)
        self.clear_b.show()

        sb = GtkScrolledWindow()
        sb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
        self.ui.pack_start(sb)
	sb.show()
        box = GtkVBox(spacing=3)
        sb.add_with_viewport(box)
        box.show()
        self.clist =  GtkCList(4, ["Key Id", "Date", "Owner", "Email"])
        self.clist.connect("select_row", self.select_item)
        self.clist.connect("unselect_row", self.unselect_item)
        box.pack_start(self.clist)
        self.hbox.show()
        self.ui.show()
        
    def clear_list(self, widget):
        self.clist.clear()
        self.found_keys = []
        self.s = None
        self.founded = 0

    def display_keys(self, source):
        
        reg = re.compile("<.*?>")
        data = reg.sub("", source)
        reg = re.compile("&gt;")
        data = reg.sub(">", data)
        reg = re.compile("&lt;")
        data = reg.sub("<", data)
        reg = re.compile("&quot;")
        data = reg.sub("\"", data)
        reg = re.compile("&amp;")
        data = reg.sub("\&", data)
        data = string.replace(data, "\r", "")
        result = string.split(data, "\n")
        
        result = result[6:]
        pub = re.compile(".*pub.*")
        for datakey in result:
            match = pub.search(datakey)
            if(match != None):
                self.founded = self.founded + 1
                key = string.split(match.group())
                key_data = []
                self.clist.set_column_width(0, len(key[1] * 10))
                key_data.append(key[1])
                key_id = string.split(key[1], "/")
                
                self.found_keys.append((key_id[1:]))
                
                self.clist.set_column_width(1, len(key[2] * 10))
                key_data.append(key[2])
                self.clist.set_column_width(2, 100)
                key_data.append(string.join(key[3:-1]))
                self.clist.set_column_width(3, 100)
                key_data.append(key[-1])
                self.clist.append(key_data)
                self.clist.show()
        dlg = GnomeOkDialog("%d keys found on %s" % (self.founded,config['keyserver']))
        dlg.set_modal(TRUE)
        dlg.run()

    def import_selected_key(self,_button):
        if(self.s == None):
            dlg = GnomeErrorDialog("Please select the key to import first!")
            dlg.set_modal(TRUE)
            dlg.run()
        else:
            res = os.system("gpg --keyserver %s --recv-keys %s" % (config['keyserver'], str(self.found_keys[self.s])[2:-2]))
            if(res != 0):
                dlg = GnomeErrorDialog("Impossbile to fetch key %s" % str(self.found_keys[self.s])[2:-2])
                dlg.set_modal(TRUE)
                dlg.run()
            else:
                dlg = GnomeOkDialog("Key %s successfully imported" % str(self.found_keys[self.s])[2:-2])
                dlg.set_modal(TRUE)
                dlg.run()
                
    def select_item(self, _clist, r, c, event):
        self.s = r

    def unselect_item(self, _clist, r, c, event):
        self.s = None

    def fetch(self,widget):
        keys = self.entry.get_text()
        to_search = string.replace(keys, " ", "+")
        keyserver = ("http://%s:%s" % (config['keyserver'], config['port']))
        url = ((keyserver+self.path) % to_search)
        
        try:
            f = self.opener.open(url)
        except IOError:
            dlg = GnomeErrorDialog("Can't connect to keyserver %s port %s!" %(config['keyserver'],config['port']))
            dlg.set_modal(TRUE)
            dlg.run()
            return ""
        
        source = f.read()
        self.display_keys(source)


pubkeys = PubKeys()
seckeys = SecKeys()
notebook = 0


def destroy(*args):
    mainquit()
    
def about(button):
    GnomeAbout('NOFGPG', version,
		   'Distributed under the terms of the GPL',
		   ['Christopher R. Gabriel <cgabriel@cgabriel.org>'],
		   ('Simple GPG key manager')).show()
def refresh(button):
    if(notebook.get_current_page() == 0):
        pubkeys.refresh(button)
    if(notebook.get_current_page() == 1):
        seckeys.refresh(button)

    
def run_config(button):
    conf = Configger()

    
file_menu = [     UIINFO_ITEM_STOCK('Preferences', None, run_config, STOCK_MENU_PROP),
				  UIINFO_ITEM_STOCK('Quit', None, destroy, STOCK_MENU_QUIT)]

help_menu = [ UIINFO_ITEM_STOCK('Please help!', None, None, STOCK_MENU_BOOK_RED),
			  UIINFO_ITEM_STOCK('About...', None, about, STOCK_MENU_ABOUT)]

menus = [ UIINFO_SUBTREE('File', file_menu),UIINFO_SUBTREE('Help', help_menu) ]

toolbar = [ UIINFO_ITEM_STOCK('Import', 'Import a new key', import_new, STOCK_PIXMAP_CONVERT),
            UIINFO_ITEM_STOCK('Crypt', 'Crypt a file', crypter_new, STOCK_PIXMAP_CONVERT),
            UIINFO_ITEM_STOCK('List keys', 'List the keys', refresh,
                              STOCK_PIXMAP_REFRESH),
            UIINFO_ITEM_STOCK('Refresh list', 'Refresh the current key list', refresh,
                              STOCK_PIXMAP_REFRESH)
            ]



window = GnomeApp("NofGPG", "No One Fear GPG - NOFGPG")
window.connect("destroy", destroy)
window.connect("delete_event", destroy)


notebook = GtkNotebook()
notebook.set_tab_pos(POS_TOP)
window.set_contents(notebook)

label = GtkLabel("Public Keys")
notebook.append_page(pubkeys.ui, label)

label = GtkLabel("Secret Keys")
notebook.append_page(seckeys.ui, label)

label = GtkLabel("Search")
search = Search()
notebook.append_page(search.ui, label)


notebook.show()
window.create_menus(menus)
window.create_toolbar(toolbar)
window.show()


mainloop()
