Skip to content
Snippets Groups Projects
ui.py 22.2 KiB
Newer Older
#!/usr/bin/env python
# -*- coding: utf-8 -*-
##    ui.py
##
## Copyright 2008-2012 Kjell Braden <afflux@pentabarf.de>
##
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
##
import gobject
import gtk
from common import gajim
from plugins.gui import GajimPluginConfigDialog

except ImportError as e:
    print(e)


class OtrPluginConfigDialog(GajimPluginConfigDialog):
    def init(self):
        self.GTK_BUILDER_FILE_PATH = \
                self.plugin.local_file_path('config_dialog.ui')
        self.B = gtk.Builder()
        self.B.set_translation_domain('gajim_plugins')
        self.B.add_from_file(self.GTK_BUILDER_FILE_PATH)

        self.fpr_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
                gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING,
                gobject.TYPE_STRING, gobject.TYPE_STRING)

        self.otr_account_store = self.B.get_object('account_store')

        for account in sorted(gajim.contacts.get_accounts()):
            self.otr_account_store.append(row=(account,))

Kjell Braden's avatar
Kjell Braden committed
        self.fpr_view = self.B.get_object('fingerprint_view')
        self.fpr_view.set_model(self.fpr_model)
        self.fpr_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)

        if len(self.otr_account_store) > 0:
            self.B.get_object('account_combobox').set_active(0)

        self.child.pack_start(self.B.get_object('notebook1'))

        self.flags = dict()
        flagList = (
            ('ALLOW_V2', 'enable_check'),
            ('SEND_TAG', 'advertise_check'),
            ('WHITESPACE_START_AKE', 'autoinitiate_check'),
            ('REQUIRE_ENCRYPTION', 'require_check')
        )
        for flagName, checkBoxName in flagList:
            self.flags[flagName] = self.B.get_object(checkBoxName)

        self.B.connect_signals(self)

    def on_run(self):
        self.plugin.update_context_list()
        self.account_combobox_changed_cb(self.B.get_object('account_combobox'))
Kjell Braden's avatar
Kjell Braden committed
    def fpr_button_pressed_cb(self, tw, event):
        if event.button == 3:
            pthinfo = tw.get_path_at_pos(int(event.x), int(event.y))

            if pthinfo is None:
                # only show the popup when we right clicked on list content
                # ie. don't show it when we click at empty rows
                return False

            # if the row under the mouse is already selected, we keep the
            # selection, otherwise we only select the new item
            keep_selection = tw.get_selection().path_is_selected(pthinfo[0])

            pop = self.B.get_object('fprclipboard_menu')
            pop.popup(None, None, None, event.button, event.time)

            # keep_selection=True -> no further processing of click event
            # keep_selection=False-> further processing -> GTK usually selects
            #   the item below the cursor
            return keep_selection

    def clipboard_button_cb(self, menuitem):
        mod, paths = self.fpr_view.get_selection().get_selected_rows()

        fprs = []
        for path in paths:
            it = mod.get_iter(path)
            jid, fpr = mod.get(it, 0, 6)
            fprs.append('%s: %s' % (jid, potr.human_hash(fpr)))
        gtk.Clipboard().set_text('\n'.join(fprs))
        gtk.Clipboard(selection='PRIMARY').set_text('\n'.join(fprs))
        
    def flags_toggled_cb(self, button):
        if button == self.B.get_object('enable_check'):
            new_status = button.get_active()
            self.B.get_object('advertise_check').set_sensitive(new_status)
            self.B.get_object('autoinitiate_check').set_sensitive(new_status)
            self.B.get_object('require_check').set_sensitive(new_status)

            if new_status is False:
                self.B.get_object('advertise_check').set_active(False)
                self.B.get_object('autoinitiate_check').set_active(False)
                self.B.get_object('require_check').set_active(False)

        box = self.B.get_object('account_combobox')
        active = box.get_active()
        if active > -1:
            account = self.otr_account_store[active][0]

            flagValues = {}
            for key, box in self.flags.iteritems():
                flagValues[key] = box.get_active()
            self.plugin.set_flags(flagValues, account)

    def account_combobox_changed_cb(self, box, *args):
        fpr_label = self.B.get_object('fingerprint_label')
        regen_button = self.B.get_object('regenerate_button')

        active = box.get_active()
        fpr = '-------- -------- -------- -------- --------'
        try:
            if active > -1:
                regen_button.set_sensitive(True)
                account = self.otr_account_store[active][0]

                otr_flags = self.plugin.get_flags(account)
                for key, box in self.flags.iteritems():
                    box.set_active(otr_flags[key])

                fpr = str(self.plugin.us[account].getPrivkey(autogen=False))
                regen_button.set_label('Regenerate')
            else:
                regen_button.set_sensitive(False)
        except LookupError, e:
            # Account not found, no private key available - display the
            # empty one
            regen_button.set_label('Generate')
        finally:
            self.B.get_object('fingerprint_label').set_markup('<tt>%s</tt>'%fpr)

    def forget_button_clicked_cb(self, button, *args):
        accounts = {}
        for acc in gajim.connections.iterkeys():
            accounts[gajim.get_jid_from_account(acc)] = acc


Kjell Braden's avatar
Kjell Braden committed
        mod, paths = self.fpr_view.get_selection().get_selected_rows()

        for path in paths:
            it = mod.get_iter(path)
            user, human_fpr, a, fpr = mod.get(it, 0, 3, 4, 6)

            dlg = gtk.Dialog('Confirm removal of fingerprint', self,
                    gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                        (gtk.STOCK_YES, gtk.RESPONSE_YES,
                        gtk.STOCK_NO, gtk.RESPONSE_NO)
                    )
            l = gtk.Label()
            l.set_markup('Are you sure you want remove the following '
                    'fingerprint for the contact %s on the account %s?'
                    '\n\n%s' % (user, a, human_fpr))
            l.set_line_wrap(True)
            dlg.vbox.pack_start(l)
            dlg.show_all()

            if dlg.run() == gtk.RESPONSE_YES:
                ctx = self.plugin.us[accounts[a]].getContext(user)
                ctx.removeFingerprint(fpr)
            dlg.destroy()
            self.plugin.us[accounts[a]].saveTrusts()

        self.plugin.update_context_list()

    def verify_button_clicked_cb(self, button, *args):
        accounts = {}
        for acc in gajim.connections.iterkeys():
            accounts[gajim.get_jid_from_account(acc)] = acc

Kjell Braden's avatar
Kjell Braden committed
        mod, paths = self.fpr_view.get_selection().get_selected_rows()

        # open the window for the first selected row
        for path in paths[0:1]:
            it = mod.get_iter(path)
            fjid, fpr, a = mod.get(it, 0, 6, 4)

            ctx = self.plugin.us[accounts[a]].getContext(fjid)

            dlg = ContactOtrWindow(self.plugin, ctx, fpr=fpr, parent=self)
            dlg.run()
            dlg.destroy()
            break

    def regenerate_button_clicked_cb(self, button, *args):
        box = self.B.get_object('account_combobox')
        active = box.get_active()
        if active > -1:
            account = self.otr_account_store[active][0]
            button.set_sensitive(False)
            try:
                self.plugin.us[account].getPrivkey(autogen=False)
                self.plugin.us[account].dropPrivkey()
            except LookupError:
                pass
            self.plugin.us[account].getPrivkey(autogen=True)
            self.account_combobox_changed_cb(box, *args)
            button.set_sensitive(True)


import gtkgui_helpers
from common import gajim

our_fp_text = _('Your fingerprint:\n' \
    '<span weight="bold" face="monospace">%s</span>')
Dicson's avatar
Dicson committed
their_fp_text = _('Purported fingerprint for %(jid)s:\n' \
    '<span weight="bold" face="monospace">%(fp)s</span>')

another_q = _('You may want to authenticate your buddy as well by asking'\
        'your own question.')
smp_query = _('<b>%s is trying to authenticate you using a secret only known '\
        'to him/her and you.</b>')
smp_q_query = _('<b>%s has chosen a question for you to answer to '\
        'authenticate yourself:</b>')
enter_secret = _('Please enter your secret below.')

smp_init = _('<b>You are trying to authenticate %s using a secret only known ' \
        'to him/her and yourself.</b>')
choose_q = _('You can choose a question as a hint for your buddy below.')

class ContactOtrSmpWindow:
    def gw(self, n):
        return self.xml.get_object(n)

    def __init__(self, ctx):
        self.question = None
        self.smp_running = False
        self.ctx = ctx
        self.account = ctx.user.accountname

        self.plugin = ctx.user.plugin

        self.GTK_BUILDER_FILE_PATH = \
                self.plugin.local_file_path('contact_otr_window.ui')
        self.xml = gtk.Builder()
        self.xml.set_translation_domain('gajim_plugins')
        self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)

        self.window = self.gw('otr_smp_window')
        self.window.set_title(_('OTR settings for %s') % ctx.peer)

        # the lambda thing is an anonymous helper that just discards the
        # parameters and calls hide_on_delete on clicking the window's
        # close button
        self.window.connect('delete-event', lambda d,o:
                self.window.hide_on_delete())

        self.gw('smp_cancel_button').connect('clicked', self._on_destroy)
        self.gw('smp_ok_button').connect('clicked', self._apply)
        self.gw('qcheckbutton').connect('toggled', self._toggle)

        self.gw('qcheckbutton').set_no_show_all(False)
        self.gw('qentry').set_no_show_all(False)
        self.gw('desclabel2').set_no_show_all(False)

    def _toggle(self, w, *args):
        self.gw('qentry').set_sensitive(w.get_active())

    def show(self, response):
        self.smp_running = False
        self.finished = False

        self.gw('smp_cancel_button').set_sensitive(True)
        self.gw('smp_ok_button').set_sensitive(True)
        self.gw('progressbar').set_fraction(0)
        self.gw('secret_entry').set_text('')

        self.response = response
        self.window.show_all()
        if response:
            self.gw('qcheckbutton').set_sensitive(False)
            if self.question is None:
                self.gw('qcheckbutton').set_active(False)
                self.gw('qcheckbutton').hide()
                self.gw('qentry').hide()
                self.gw('desclabel2').hide()
                self.gw('qcheckbutton').set_sensitive(False)
                self.gw('desclabel1').set_markup((smp_query % self.ctx.peer)
                        + ' ' + enter_secret)
            else:
                self.gw('qcheckbutton').set_active(True)
                self.gw('qcheckbutton').show()
                self.gw('qentry').show()
                self.gw('qentry').set_sensitive(True)
                self.gw('qentry').set_editable(False)
                self.gw('desclabel2').show()
                self.gw('qentry').set_text(self.question)

                self.gw('desclabel1').set_markup(smp_q_query % self.ctx.peer)
                self.gw('desclabel2').set_markup(enter_secret)
        else:
            self.gw('qcheckbutton').show()
            self.gw('qcheckbutton').set_active(True)
            self.gw('qcheckbutton').set_mode(True)
            self.gw('qcheckbutton').set_sensitive(True)
            self.gw('qentry').set_sensitive(True)
            self.gw('qentry').show()
            self.gw('qentry').set_text("")

            self.gw('qentry').set_editable(True)
            self.gw('qentry').set_sensitive(True)

            self.gw('desclabel2').show()
            self.gw('desclabel1').set_markup((smp_init % self.ctx.peer) + ' '
                    + choose_q)
            self.gw('desclabel2').set_markup(enter_secret)

    def _abort(self, text=None, appdata=None):
        self.smp_running = False

        self.ctx.smpAbort(appdata=appdata)
        if text:
            self.plugin.gajim_log(text, self.account, self.ctx.peer)

    def _finish(self, text):
        self.smp_running = False
        self.finished = True

        self.gw('qcheckbutton').set_active(False)
        self.gw('qcheckbutton').hide()
        self.gw('qentry').hide()
        self.gw('desclabel2').hide()

        self.gw('qcheckbutton').set_sensitive(False)
        self.gw('smp_cancel_button').set_sensitive(False)
        self.gw('smp_ok_button').set_sensitive(True)
        self.gw('progressbar').set_fraction(1)
        self.plugin.gajim_log(text, self.account, self.ctx.peer)
        self.gw('desclabel1').set_markup(text)

        self.plugin.update_otr(self.ctx.peer, self.account, True)
        self.ctx.user.saveTrusts()
        self.plugin.update_context_list()

    def get_tlv(self, tlvs, check):
        for tlv in tlvs:
            if isinstance(tlv, check):
                return tlv
        return None

    def handle_tlv(self, tlvs):
        if tlvs:
            is1qtlv = self.get_tlv(tlvs, potr.proto.SMP1QTLV)
            # check for TLV_SMP_ABORT or state = CHEATED
            if self.smp_running and not self.ctx.smpIsValid():
                self._finish(_('SMP verifying aborted'))

            # check for TLV_SMP1
            elif self.get_tlv(tlvs, potr.proto.SMP1TLV):
                self.smp_running = True
                self.question = None
                self.show(True)
                self.gw('progressbar').set_fraction(0.3)

            # check for TLV_SMP1Q
            elif is1qtlv:
                self.smp_running = True
                self.question = is1qtlv.msg
                self.show(True)
                self.gw('progressbar').set_fraction(0.3)

            # check for TLV_SMP2
            elif self.get_tlv(tlvs, potr.proto.SMP2TLV):
                self.gw('progressbar').set_fraction(0.6)

            # check for TLV_SMP3
            elif self.get_tlv(tlvs, potr.proto.SMP3TLV):
                if self.ctx.smpIsSuccess():
                    text = _('SMP verifying succeeded')
                    if self.question is not None:
                        text += ' '+another_q
                    self._finish(text)
                else:
                    self._finish(_('SMP verifying failed'))

            # check for TLV_SMP4
            elif self.get_tlv(tlvs, potr.proto.SMP4TLV):
                if self.ctx.smpIsSuccess():
                    text = _('SMP verifying succeeded')
                    if self.question is not None:
                        text += ' '+another_q
                    self._finish(text)
                else:
                    self._finish(_('SMP verifying failed'))

    def _on_destroy(self, widget):
        if self.smp_running:
            self._abort(_('user aborted SMP authentication'))
        self.window.hide_all()

    def _apply(self, widget, appdata=None):
        if self.finished:
            self.window.hide_all()
            return
        secret = self.gw('secret_entry').get_text()
        if self.response:
            self.ctx.smpGotSecret(secret, appdata=appdata)
        else:
            if self.gw('qcheckbutton').get_active():
                qtext = self.gw('qentry').get_text()
                self.ctx.smpInit(secret, question=qtext, appdata=appdata)
            else:
                self.ctx.smpInit(secret, appdata=appdata)
            self.gw('progressbar').set_fraction(0.3)
        self.smp_running = True
        widget.set_sensitive(False)

class ContactOtrWindow(gtk.Dialog):
    def gw(self, n):
        return self.xml.get_object(n)

    def __init__(self, plugin, ctx, fpr=None, parent=None):
        fjid = ctx.peer
        gtk.Dialog.__init__(self, title=_('OTR settings for %s') % fjid,
                parent=parent,
                flags=gtk.DIALOG_DESTROY_WITH_PARENT,
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
                gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))

        self.ctx = ctx
        self.fjid = fjid
        self.jid = gajim.get_room_and_nick_from_fjid(self.fjid)[0]
        self.account = ctx.user.accountname
        self.fpr = fpr
        self.plugin = plugin

        if self.fpr is None:
            key = self.ctx.getCurrentKey()
            if key is not None:
                self.fpr = key.cfingerprint()

        self.GTK_BUILDER_FILE_PATH = \
                self.plugin.local_file_path('contact_otr_window.ui')
        self.xml = gtk.Builder()
        self.xml.set_translation_domain('gajim_plugins')
        self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH)
        self.notebook = self.gw('otr_settings_notebook')
        self.child.pack_start(self.notebook)

        self.connect('response', self.on_response)
        self.gw('otr_default_checkbutton').connect('toggled',
                self._otr_default_checkbutton_toggled)

        # always set the label containing our fingerprint
        self.gw('our_fp_label').set_markup(our_fp_text % ctx.user.getPrivkey())

        if self.fpr is None:
            # make the fingerprint widgets insensitive
            # when not encrypted
            for widget in self.gw('otr_fp_vbox').get_children():
                widget.set_sensitive(False)
            # show that the fingerprint is unknown
Dicson's avatar
Dicson committed
            self.gw('their_fp_label').set_markup(their_fp_text % {
                    'jid': self.fjid, 'fp': _('unknown')})
            self.gw('verified_combobox').set_active(-1)
        else:
            # make the fingerprint widgets sensitive when encrypted
            for widget in self.gw('otr_fp_vbox').get_children():
                widget.set_sensitive(True)
            # show their fingerprint
            fp = potr.human_hash(self.fpr)
Dicson's avatar
Dicson committed
            self.gw('their_fp_label').set_markup(their_fp_text % {
                    'jid': self.fjid, 'fp': fp})
            # set the trust combobox
            if ctx.getCurrentTrust():
                self.gw('verified_combobox').set_active(1)
            else:
                self.gw('verified_combobox').set_active(0)

        otr_flags = self.plugin.get_flags(self.account, self.jid, fallback=False)

        if otr_flags is not None:
            self.gw('otr_default_checkbutton').set_active(0)
            for w in self.gw('otr_settings_vbox').get_children():
                w.set_sensitive(True)
        else:
            # per-user settings not available,
            # using default settings
            otr_flags = self.plugin.get_flags(self.account)
            self.gw('otr_default_checkbutton').set_active(1)
            for w in self.gw('otr_settings_vbox').get_children():
                w.set_sensitive(False)

        self.gw('otr_policy_allow_v2_checkbutton').set_active(
                otr_flags['ALLOW_V2'])
        self.gw('otr_policy_require_checkbutton').set_active(
                otr_flags['REQUIRE_ENCRYPTION'])
        self.gw('otr_policy_send_tag_checkbutton').set_active(
                otr_flags['SEND_TAG'])
        self.gw('otr_policy_start_on_tag_checkbutton').set_active(
                otr_flags['WHITESPACE_START_AKE'])

        self.child.show_all()

    def on_response(self, dlg, response, *args):
        if response != gtk.RESPONSE_ACCEPT:
            return


        # -1 when nothing is selected
        # (ie. the connection is not encrypted)
        trust_state = self.gw('verified_combobox').get_active()
        if trust_state == 1 and not self.ctx.getTrust(self.fpr):
            self.ctx.setTrust(self.fpr, 'verified')
            self.ctx.user.saveTrusts()
            self.plugin.update_context_list()
        elif trust_state == 0:
            self.ctx.setTrust(self.fpr, '')
            self.ctx.user.saveTrusts()
            self.plugin.update_context_list()

        self.plugin.update_otr(self.ctx.peer, self.ctx.user.accountname, True)

        if self.gw('otr_default_checkbutton').get_active():
            # default is enabled, so remove any user-specific
            # settings if available
            self.plugin.set_flags(None, self.account, self.jid)
        else:
            # build the flags using the checkboxes
            flags = {}
            flags['ALLOW_V2'] = \
                    self.gw('otr_policy_allow_v2_checkbutton').get_active()
            flags['REQUIRE_ENCRYPTION'] = \
                    self.gw('otr_policy_require_checkbutton').get_active()
            flags['SEND_TAG'] = \
                    self.gw('otr_policy_send_tag_checkbutton').get_active()
            flags['WHITESPACE_START_AKE'] = \
                    self.gw('otr_policy_start_on_tag_checkbutton').get_active()

            self.plugin.set_flags(flags, self.account, self.jid)

    def _otr_default_checkbutton_toggled(self, widget):
        for w in self.gw('otr_settings_vbox').get_children():
            w.set_sensitive(not widget.get_active())

def get_otr_submenu(plugin, control):
    GTK_BUILDER_FILE_PATH = \
            plugin.local_file_path('contact_otr_window.ui')
    xml = gtk.Builder()
    xml.set_translation_domain('gajim_plugins')
    xml.add_from_file(GTK_BUILDER_FILE_PATH)

    otr_submenu = xml.get_object('otr_submenu')
    otr_settings_menuitem, smp_otr_menuitem, start_otr_menuitem, \
            end_otr_menuitem = otr_submenu.get_submenu().get_children()

    otr_submenu.set_sensitive(True)
    otr_settings_menuitem.connect('activate', plugin.menu_settings_cb, control)
    start_otr_menuitem.connect('activate', plugin.menu_start_cb, control)
    end_otr_menuitem.connect('activate', plugin.menu_end_cb, control)
    smp_otr_menuitem.connect('activate', plugin.menu_smp_cb, control)

    enc, _, fin = plugin.get_otr_status(control.account, control.contact)
    # can end only when not in PLAINTEXT
    end_otr_menuitem.set_sensitive(enc)
    # can SMP only when ENCRYPTED
    smp_otr_menuitem.set_sensitive(enc and not fin)
    return otr_submenu