#!/usr/bin/env python # -*- coding: utf-8 -*- ## ui.py ## ## Copyright 2008-2012 Kjell Braden ## ## 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 . ## import gobject import gtk from common import gajim from plugins.gui import GajimPluginConfigDialog import otrmodule try: import potr import potr.proto except ImportError: pass 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,)) 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')) 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('%s'%fpr) def forget_button_clicked_cb(self, button, *args): accounts = {} for acc in gajim.connections.iterkeys(): accounts[gajim.get_jid_from_account(acc)] = acc 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 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' \ '%s') their_fp_text = _('Purported fingerprint for %(jid)s:\n' \ '%(fp)s') another_q = _('You may want to authenticate your buddy as well by asking'\ 'your own question.') smp_query = _('%s is trying to authenticate you using a secret only known '\ 'to him/her and you.') smp_q_query = _('%s has chosen a question for you to answer to '\ 'authenticate yourself:') enter_secret = _('Please enter your secret below.') smp_init = _('You are trying to authenticate %s using a secret only known ' \ 'to him/her and yourself.') 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 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) 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