## $Id: privacy.py,v 1.26 2001/10/10 12:09:09 kjetilja Exp $

import popen2
import re, os, string

from gnome.ui import *
from gtk import *

gpg_installed = (os.system('gpg --version >/dev/null') == 0)

re_pgp = re.compile('(^-----BEGIN PGP MESSAGE-----$.*?^-----END PGP MESSAGE-----$)', 
		    re.DOTALL|re.MULTILINE)
def is_encrypted(msg):
    return re_pgp.search(msg)

def split_encrypted(msg):
    return re_pgp.split(msg)

re_sign = re.compile('^-----BEGIN PGP SIGNED MESSAGE-----$.*?^-----END PGP SIGNATURE-----$', 
		     re.DOTALL|re.MULTILINE)

def is_signed(msg):
    return re_sign.search(msg)

re_isok = re.compile('(bad)|(failed)|(no valid)', re.IGNORECASE)

def is_ok(msg):
    return not re_isok.search(msg)

re_signok = re.compile('Good signature.*"(.*)"')
def signed_ok(msg):
    _from = re_signok.findall(msg)
    if len(_from):
	return _from[0]
    return None 

re_keys = re.compile('(pub|uid|sub)(.*\d|\s*) (.*) (<.*\@.*>)')


class Privacy:

    def __init__(self, appbar, parent):
	self.passphrase = None
	self.appbar = appbar
	self.callback = None
	self.need_sign, self.need_encrypt = (0,0)
	self.recipient = None
	self.parent = parent
        if not gpg_installed:
            self.not_installed()
            

    def not_installed(self):
        w = GnomeWarningDialog('GnuPG is not installed on your system!\nSigned or encrypted messages will not handled correctly by Pygmy unless you install GnuPG.')
        w.set_parent(self.parent)
        w.show()


    def handle_read(self, msg, callback):
        if is_encrypted(msg):
            if not gpg_installed:
                self.not_installed()
                return msg
	    passphrase = self.ask_passphrase(callback)
	    if passphrase:
		msg = self.decrypt(msg, passphrase)
        if is_signed(msg):
            if not gpg_installed:
                self.not_installed()
                return msg
            msg = self.decrypt(msg)
	return msg


    def handle_write(self, msg, callback):
	if self.need_sign:
            if not gpg_installed:
                self.not_installed()
                return msg
	    passphrase = self.ask_passphrase(callback)
	    if not passphrase:
		    return None
	    msg = self.encrypt_and_sign(msg, passphrase)
	elif self.need_encrypt:
            if not gpg_installed:
                self.not_installed()
                return msg
	    msg = self.encrypt_and_sign(msg)
	return msg

	    
    def decrypt(self, msg, pw=''):
        if not gpg_installed:
            self.not_installed()
            return msg
        import tempfile
        fname = tempfile.mktemp()
	cmd = 'gpg --armor --batch --decrypt --passphrase-fd=0 --output %s' % fname
	r,w,e = popen2.popen3(cmd, 8192)
	ret = w.write(pw + '\n' + msg)
	w.close()
	error = e.read()
	if not is_ok(error):
	    self.passphrase = None
	    self.appbar.set_status('Decryption failed!')
	    self.error_dialog(error)
	    return msg
	_from = signed_ok(error)
	if _from:
	    self.appbar.set_status('Signature by %s checked OK!' % _from)
	else:
	    self.appbar.set_status(error[string.rfind(error, 'gpg:'):-1])
        try:
            buf = open(fname).read()
            os.unlink(fname)
        except:
            self.appbar.set_status('Error reading result of decrypt or signature check!')
            buf = ''
        #buf = re_sign.sub(buf, msg)
	return buf


    def ask_passphrase(self, callback):
	# Check if we know the passphrase already
	if self.passphrase:
	    return self.passphrase

	self.callback = callback
	self.parent.request_password('GnuPG password:', self.passphrase_callback)
			     
	return None 

	
    def passphrase_callback(self, passphrase=None, b=None):
	# Kick the module which requested the passphrase
	self.passphrase = passphrase
	if passphrase:
	    self.callback()
	    self.callback = None


    def error_dialog(self, msg):
	w = GnomeErrorDialog(msg)
	w.set_parent(self.parent)
	w.show()


    def dialog(self, fld, default=None):
        if not gpg_installed:
            self.not_installed()
            return msg
	self.fld = fld
        self.prefs = self.fld.prefs
        self.win = GnomeDialog(':Pygmy - Security',
                               STOCK_BUTTON_OK, STOCK_BUTTON_CANCEL)
        self.win.set_parent(self.parent)
        self.vbox = self.win.vbox
        self.win.connect('clicked', self.handle_callbacks)
	self.win.connect('delete_event', self.destroy)
	self.win.connect('destroy', self.destroy)
        if self.init_contents(default) == 0:
            self.error_dialog("Found no keys!")
            self.destroy()
            return
        else:
            self.win.show()


    def destroy(self, a=None, b=None):
	self.win.destroy()


    def handle_callbacks(self, button, no):
        if no == 0:
	    self.handle_settings()
        elif no == 1:
            self.destroy()
        else:
            print "security -- got unknown button callback event"


    def handle_settings(self):
	self.need_sign = self.sign_button.get_active()
	self.need_encrypt = self.encrypt_button.get_active()
	if self.need_encrypt:
	    self.recipient = self.keys_combo.entry.get_text()
	else:
	    self.recipient = None
        self.win.destroy()


    def init_contents(self, default):
        t = GtkTable(2,3,0)
        t.show()
        self.sign_button = GtkCheckButton("Sign")
	self.sign_button.set_active(self.need_sign)
	self.sign_button.show()
        self.encrypt_button = GtkCheckButton("Encrypt")
	self.encrypt_button.set_active(self.need_encrypt)
	self.encrypt_button.show()

	# Fetch all keys in key-ring
	f = os.popen('gpg --list-keys')
	msg = f.read()
        f.close()
	keys = []
	info = re_keys.findall(msg)
        if info == []:
            return 0
	for entry in info:
	    str = entry[2]+' '+entry[3]
	    keys.append(str)
	    if not self.recipient and (string.find(entry[3], default) != -1):
		self.recipient = str 
	self.keys_combo = GtkCombo()
        self.keys_combo.entry.set_editable(FALSE)
        self.keys_combo.set_popdown_strings(keys)
	if self.recipient:
	    self.keys_combo.entry.set_text(self.recipient)
        self.keys_combo.show()
        self.keys_combo.set_usize(250, 0)

        t.attach(self.sign_button, 0, 1, 0, 1, yoptions=0, ypadding=3, xpadding=5)
        t.attach(self.encrypt_button, 1, 2, 0, 1, yoptions=0, ypadding=3, xpadding=5)
        t.attach(self.keys_combo, 0, 2, 1, 2, yoptions=0, ypadding=3, xpadding=5)
	self.vbox.pack_start(t)
        return 1

        
    def encrypt_and_sign(self, msg, pw=''):
	# Check if we have something to do at all?
	if not self.need_encrypt and not self.need_sign:
	    return
	    
	args = ['--armor', '--batch']
	if self.need_encrypt:
	    args.append('--encrypt')
	    args.append("--recipient '%s'" % self.recipient)
	if self.need_sign:
	    if self.need_encrypt:
		args.append('--sign')
	    else:
		args.append('--clearsign')

        import tempfile
        fname = tempfile.mktemp()
        tmpfile = open(fname, 'w')
        tmpfile.write(msg)
        tmpfile.close()
	cmd = 'gpg %s --passphrase-fd=0 %s' % (string.join(args), fname)
	r,w,e = popen2.popen3(cmd, 8192)
        w.write(pw + '\n')
	w.close()
	error = e.read()
	if not is_ok(error):
	    self.passphrase = None
	    self.appbar.set_status('Encryption failed!')
	    self.error_dialog(error)
	    return None
	msg = open(fname+'.asc').read()
        os.unlink(fname)
        os.unlink(fname+'.asc')

	# Reset settings
	self.need_encrypt, self.need_sign, self.recipient = (0, 0, None)

	return msg
