Added OTR support.

Work done by Kjell Braden <fnord@pentabarf.de>.
Some fixes done by me.
This commit is contained in:
js 2008-05-09 12:35:25 +00:00
parent b04250cf9f
commit 435042968e
11 changed files with 1016 additions and 3 deletions

View File

@ -71,6 +71,50 @@
<signal name="activate" handler="_on_toggle_e2e_menuitem_activate"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="otr_submenu">
<property name="no_show_all">True</property>
<property name="label" translatable="yes">Off-the-Record Encryption</property>
<property name="use_underline">True</property>
<child>
<widget class="GtkMenu" id="otr_submenu_menu">
<child>
<widget class="GtkMenuItem" id="otr_settings_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">OTR settings / fingerprint</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_otr_settings_menuitem_activate"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="smp_otr_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Authenticate contact</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_smp_otr_menuitem_activate"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="start_otr_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Start / Refresh OTR</property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_start_otr_menuitem_activate"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="end_otr_menuitem">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="label" translatable="yes">End OTR </property>
<property name="use_underline">True</property>
<signal name="activate" handler="_on_end_otr_menuitem_activate"/>
</widget>
</child>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>

View File

@ -0,0 +1,322 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.2 on Wed Mar 26 13:56:40 2008 -->
<glade-interface>
<widget class="GtkWindow" id="otr_settings_window">
<property name="resizable">False</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<child>
<widget class="GtkNotebook" id="notebook1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<widget class="GtkVBox" id="otr_fp_vbox">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<widget class="GtkLabel" id="our_fp_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Your fingerprint:
&lt;span weight="bold" face="monospace"&gt;01234567 89ABCDEF 01234567 89ABCDEF 01234567&lt;/span&gt;</property>
<property name="use_markup">True</property>
</widget>
</child>
<child>
<widget class="GtkLabel" id="their_fp_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Purported fingerprint for asdfasdf@xyzxyzxyz.de:
&lt;span weight="bold" face="monospace"&gt;01234567 89ABCDEF 01234567 89ABCDEF 01234567&lt;/span&gt;</property>
<property name="use_markup">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<widget class="GtkComboBox" id="verified_combobox">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="active">1</property>
<property name="items">I have NOT
I have</property>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="xalign">0.20000000298023224</property>
<property name="label" translatable="yes">verified that the purported fingerprint is in fact the correct fingerprint for that contact.</property>
<property name="wrap">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Authentication</property>
</widget>
<packing>
<property name="type">tab</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<widget class="GtkFrame" id="frame2">
<property name="visible">True</property>
<property name="label_xalign">0</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<child>
<widget class="GtkVBox" id="otr_settings_vbox">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="spacing">5</property>
<property name="homogeneous">True</property>
<child>
<widget class="GtkCheckButton" id="otr_policy_allow_v1_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">OTR version 1 allowed</property>
<property name="response_id">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
</child>
<child>
<widget class="GtkCheckButton" id="otr_policy_allow_v2_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">OTR version 2 allowed</property>
<property name="response_id">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="otr_policy_require_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Encryption required</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="otr_policy_send_tag_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Show others we understand OTR</property>
<property name="response_id">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="otr_policy_start_on_tag_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Automatically initiate encryption if partner understands OTR</property>
<property name="response_id">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="otr_policy_start_on_error_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Automatically start OTR when an OTR error occured</property>
<property name="response_id">0</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="position">5</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkCheckButton" id="otr_default_checkbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Use the default settings</property>
<property name="response_id">0</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="type">label_item</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">OTR Settings</property>
</widget>
<packing>
<property name="type">tab</property>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="settings_cancel_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="settings_ok_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="has_default">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-ok</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
<widget class="GtkWindow" id="otr_smp_window">
<property name="resizable">False</property>
<child>
<widget class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<child>
<widget class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="border_width">5</property>
<property name="spacing">5</property>
<child>
<widget class="GtkLabel" id="desc_label">
<property name="visible">True</property>
<property name="label" translatable="yes">label</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
</widget>
</child>
<child>
<widget class="GtkEntry" id="secret_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
<child>
<widget class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<property name="border_width">5</property>
<child>
<widget class="GtkProgressBar" id="progressbar">
<property name="visible">True</property>
<property name="text" translatable="yes"></property>
</widget>
<packing>
<property name="expand">False</property>
</packing>
</child>
<child>
<widget class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="layout_style">GTK_BUTTONBOX_END</property>
<child>
<widget class="GtkButton" id="smp_cancel_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
</child>
<child>
<widget class="GtkButton" id="smp_ok_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="label" translatable="yes">gtk-ok</property>
<property name="use_stock">True</property>
<property name="response_id">0</property>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -1176,9 +1176,37 @@ class ChatControl(ChatControlBase):
self.check_for_possible_inactive_chatstate, None)
def update_ui(self):
if gajim.otr_module:
self.update_otr(True)
# The name banner is drawn here
ChatControlBase.update_ui(self)
def update_otr(self, print_status=False):
# retrieve the OTR context from the chat's contact data
ctx = gajim.otr_module.otrl_context_find(gajim.otr_userstates[self.account],
self.contact.get_full_jid().encode(),
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO, 1,
(gajim.otr_add_appdata, self.account))[0]
enc_status = False
otr_status_text = ""
if ctx.msgstate == gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED:
enc_status = True
if ctx.active_fingerprint.trust:
otr_status_text = u"authenticated secure OTR connection"
else:
otr_status_text = u'*unauthenticated* secure OTR connection'
elif ctx.msgstate == gajim.otr_module.OTRL_MSGSTATE_FINISHED:
enc_status = True
otr_status_text = u"finished OTR connection"
else:
# nothing to print
print_status = False
self._show_lock_image(enc_status, u'OTR', enc_status, True)
if print_status:
self.print_conversation_line(u" [OTR] %s"%otr_status_text, 'status',
'', None)
def _update_banner_state_image(self):
contact = gajim.contacts.get_contact_with_highest_priority(self.account,
self.contact.jid)
@ -1696,6 +1724,11 @@ class ChatControl(ChatControlBase):
history_menuitem = xml.get_widget('history_menuitem')
toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem')
toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem')
otr_submenu = xml.get_widget('otr_submenu')
otr_settings_menuitem = xml.get_widget('otr_settings_menuitem')
smp_otr_menuitem = xml.get_widget('smp_otr_menuitem')
start_otr_menuitem = xml.get_widget('start_otr_menuitem')
end_otr_menuitem = xml.get_widget('end_otr_menuitem')
add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem')
send_file_menuitem = xml.get_widget('send_file_menuitem')
information_menuitem = xml.get_widget('information_menuitem')
@ -1781,6 +1814,33 @@ class ChatControl(ChatControlBase):
id = convert_to_gc_menuitem.connect('activate',
self._on_convert_to_gc_menuitem_activate)
self.handlers[id] = convert_to_gc_menuitem
if gajim.otr_module:
otr_submenu.show()
id = otr_settings_menuitem.connect('activate',
self._on_otr_settings_menuitem_activate)
self.handlers[id] = otr_settings_menuitem
id = start_otr_menuitem.connect('activate',
self._on_start_otr_menuitem_activate)
self.handlers[id] = start_otr_menuitem
id = end_otr_menuitem.connect('activate',
self._on_end_otr_menuitem_activate)
self.handlers[id] = end_otr_menuitem
id = smp_otr_menuitem.connect('activate',
self._on_smp_otr_menuitem_activate)
self.handlers[id] = smp_otr_menuitem
ctx = gajim.otr_module.otrl_context_find(gajim.otr_userstates[self.account],
self.contact.get_full_jid().encode(),
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO, 1,
(gajim.otr_add_appdata, self.account))[0]
# can end only when PLAINTEXT
end_otr_menuitem.set_sensitive(ctx.msgstate !=
gajim.otr_module.OTRL_MSGSTATE_PLAINTEXT)
# can SMP only when ENCRYPTED
smp_otr_menuitem.set_sensitive(ctx.msgstate ==
gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED)
menu.connect('selection-done', self.destroy_menu, history_menuitem,
information_menuitem)
return menu
@ -2259,6 +2319,27 @@ class ChatControl(ChatControlBase):
# XXX decide whether to use 4 or 3 message negotiation
self.session.negotiate_e2e(False)
def _on_start_otr_menuitem_activate(self, widget):
# ?OTR? gets replaced with a better message internally in otrl_message_sending
MessageControl.send_message(self, u"?OTR?", type="chat")
def _on_end_otr_menuitem_activate(self, widget):
fjid = self.contact.get_full_jid()
gajim.otr_module.otrl_message_disconnect(gajim.otr_userstates[self.account],
(gajim.otr_ui_ops, {'account':self.account,'urgent':True}),
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO,
fjid.encode())
gajim.otr_ui_ops.gajim_log("Private conversation with %s lost."%fjid,
self.account, fjid.encode())
self.update_ui()
def _on_otr_settings_menuitem_activate(self, widget):
gajim.otr_windows.ContactOtrWindow(self.contact, self.account, self)
def _on_smp_otr_menuitem_activate(self, widget):
ctx = gajim.otr_module.otrl_context_find(gajim.otr_userstates[self.account],
self.contact.get_full_jid().encode(),
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO, 1,
(gajim.otr_add_appdata, self.account))[0]
ctx.app_data.show(False)
def got_connected(self):
ChatControlBase.got_connected(self)
# Refreshing contact

View File

@ -319,6 +319,7 @@ class Config:
'zeroconf_jabber_id': [ opt_str, '', '', True ],
'zeroconf_email': [ opt_str, '', '', True ],
'use_env_http_proxy' : [opt_bool, False],
'otr_flags': [opt_int, 59 ],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
@ -369,6 +370,7 @@ class Config:
'contacts': ({
'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
'otr_flags': [opt_int, -1 ],
}, {}),
'rooms': ({
'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],

View File

@ -1024,7 +1024,7 @@ class Connection(ConnectionHandlers):
def send_message(self, jid, msg, keyID, type='chat', subject='',
chatstate=None, msg_id=None, composing_xep=None, resource=None,
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None):
user_nick=None, xhtml=None, session=None, forward_from=None, form_node=None, original_message=None):
if not self.connection:
return 1
if msg and not xhtml and gajim.config.get('rst_formatting_outgoing_messages'):
@ -1116,13 +1116,16 @@ class Connection(ConnectionHandlers):
if session.enable_encryption:
msg_iq = session.encrypt_stanza(msg_iq)
self.connection.send(msg_iq)
if not forward_from and session.is_loggable():
if not forward_from and session and session.is_loggable():
no_log_for = gajim.config.get_per('accounts', self.name, 'no_log_for')\
.split()
ji = gajim.get_jid_without_resource(jid)
if self.name not in no_log_for and ji not in no_log_for:
log_msg = msg
if original_message != None:
log_msg = original_message
if subject:
log_msg = _('Subject: %s\n%s') % (subject, msg)
if log_msg:

View File

@ -1677,6 +1677,34 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.dispatch('ERROR', (_('Disk Write Error'), str(e)))
return
elif mtype == 'chat': # it's type 'chat'
if gajim.otr_module and isinstance(msgtxt, unicode):
otr_msg_tuple = gajim.otr_module.otrl_message_receiving(
gajim.otr_userstates[self.name],
(gajim.otr_ui_ops, {'account':self.name}),
gajim.get_jid_from_account(self.name).encode(),
gajim.OTR_PROTO, frm.encode(), msgtxt.encode(),
(gajim.otr_add_appdata, self.name))
msgtxt = unicode(otr_msg_tuple[1])
# OTR messages are unformatted, or rather contain the same
# text in <body> and <html>
msghtml = msgtxt
if gajim.otr_module.otrl_tlv_find(otr_msg_tuple[2],
gajim.otr_module.OTRL_TLV_DISCONNECTED) != None:
gajim.otr_ui_ops.gajim_log("%s has ended his/her private conversation"
" with you; you should do the same."%frm, self.name,
frm)
ctrl = gajim.interface.msg_win_mgr.get_control(frm, self.name)
if ctrl:
ctrl.update_ui()
ctx = gajim.otr_module.otrl_context_find(gajim.otr_userstates[self.name], frm.encode(),
gajim.get_jid_from_account(self.name).encode(), gajim.OTR_PROTO, 1,
(gajim.otr_add_appdata, self.name))[0]
tlvs = otr_msg_tuple[2]
ctx.app_data.handle_tlv(tlvs)
if not msg.getTag('body') and chatstate is None: #no <body>
return
if msg.getTag('body') and session.is_loggable() and msgtxt:

View File

@ -165,6 +165,10 @@ else:
if system('gpg -h >/dev/null 2>&1'):
HAVE_GPG = False
OTR_PROTO = "xmpp"
otr_userstates = {}
otr_policy = {}
gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI,
xmpp.NS_FILE, xmpp.NS_MUC, xmpp.NS_MUC_USER,

View File

@ -253,6 +253,172 @@ from common import helpers
from common import optparser
from common import dataforms
from common.xmpp import Message as XmppMessage
try:
import otr, otr_windows
gajim.otr_module = otr
gajim.otr_windows = otr_windows
except ImportError:
gajim.otr_module = None
gajim.otr_windows = None
def add_appdata(data=None, context=None):
account = data
context.app_data = otr_windows.ContactOtrSMPWindow(unicode(context.username),
account)
gajim.otr_add_appdata = add_appdata
def otr_dialog_destroy(widget, *args, **kwargs):
widget.destroy()
class OtrlMessageAppOps:
def gajim_log(self, msg, account, fjid, no_print=False):
if not isinstance(fjid, unicode):
fjid = unicode(fjid)
if not isinstance(account, unicode):
account = unicode(account)
resource=gajim.get_resource_from_jid(fjid)
tim = time.localtime()
if not no_print:
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(fjid), account)
if ctrl:
ctrl.print_conversation_line(u" [OTR] %s"%msg, 'status', '', None)
id = gajim.logger.write('chat_msg_recv', fjid, message=msg, tim=tim)
gajim.logger.set_read_messages([id])
def policy(self, opdata=None, context=None):
policy = gajim.config.get_per("contacts",
gajim.get_jid_without_resource(context.username), "otr_flags")
if policy <= 0:
policy = gajim.config.get_per("accounts", opdata['account'], "otr_flags")
return policy
def create_privkey(self, opdata="", accountname="", protocol=""):
dialog = gtk.Dialog(title=_("Generating..."), parent=gajim.interface.roster.window,
flags=gtk.DIALOG_MODAL, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
permlabel = gtk.Label("Generating a private key for %s..."%accountname)
permlabel.set_padding(20,20)
dialog.set_response_sensitive(gtk.RESPONSE_CLOSE, False)
dialog.connect("destroy", otr_dialog_destroy)
dialog.connect("response", otr_dialog_destroy)
dialog.vbox.pack_start(permlabel)
dialog.get_root_window().raise_()
dialog.show_all()
dialog.map()
for c in dialog.get_children():
c.show_now()
c.map()
while gtk.events_pending():
gtk.main_iteration(block=False)
otr.otrl_privkey_generate(gajim.otr_userstates[opdata['account']],
os.path.join(gajimpaths.root, "%s.key"%opdata['account']).encode(),
accountname, gajim.OTR_PROTO)
permlabel.set_text("Generating a private key for %s...\ndone."%accountname)
dialog.set_response_sensitive(gtk.RESPONSE_CLOSE, True)
def is_logged_in(self, opdata={}, accountname="", protocol="", recipient=""):
return gajim.contacts.get_contact_from_full_jid(opdata['account'], recipient).show \
in ['dnd', 'xa', 'chat', 'online', 'away', 'invisible']
def inject_message(self, opdata=None, accountname="", protocol="", recipient="",
message=""):
msg_type = otr.otrl_proto_message_type(message)
if 'kwargs' not in opdata or 'urgent' in opdata:
# don't use send_message here to have the message sent immediatly.
# this results in being able to disconnect from OTR sessions before
# quitting
stanza = XmppMessage(to=recipient, body=message, typ="chat")
gajim.connections[opdata['account']].connection.send(stanza, now=True)
return
if msg_type == otr.OTRL_MSGTYPE_QUERY:
# split away XHTML-contaminated explanatory message
message = unicode(message.splitlines()[0])
message += u"\n%s has requested an Off-the-Record private " \
"conversation. However, you do not have a plugin to " \
"support that.\nSee http://otr.cypherpunks.ca/ for more "\
"information."%gajim.get_jid_from_account(opdata['account'])
gajim.connections[opdata['account']].send_message(recipient, message,
**opdata['kwargs'])
def notify(sef, opdata=None, username="", **kwargs):
self.gajim_log("Notify: "+str(kwargs), opdata['account'], username)
def display_otr_message(self, opdata=None, username="", msg="", **kwargs):
self.gajim_log("OTR Message: "+msg, opdata['account'], username)
return 0
def update_context_list(self, **kwargs):
# FIXME stub FIXME #
pass
def protocol_name(self, opdata=None, protocol=""):
return "XMPP"
def new_fingerprint(self, opdata=None, username="", fingerprint="", **kwargs):
self.gajim_log("New fingerprint for %s: %s"%(username,
otr.otrl_privkey_hash_to_human(fingerprint)), opdata['account'], username)
def write_fingerprints(self, opdata=""):
otr.otrl_privkey_write_fingerprints(gajim.otr_userstates[opdata['account']],
os.path.join(gajimpaths.root, "%s.fpr"%opdata['account']).encode())
def gone_secure(self, opdata="", context=None):
trust = context.active_fingerprint.trust
if trust:
trust = "verified"
else:
trust = "unverified"
self.gajim_log("%s secured OTR connection started"%trust,
opdata['account'], context.username, no_print=True)
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(unicode(context.username)),
opdata['account'])
if ctrl:
ctrl.update_otr(True)
def gone_insecure(self, opdata="", context=None):
self.gajim_log("Private conversation with %s lost.", opdata['account'], context.username)
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(unicode(context.username)),
opdata['account'])
if ctrl:
ctrl.update_otr()
def still_secure(self, opdata=None, context=None, is_reply=0):
ctrl = gajim.interface.msg_win_mgr.get_control(
gajim.get_jid_without_resource(unicode(context.username)),
opdata['account'])
if ctrl:
ctrl.update_otr(True)
self.gajim_log("OTR connection was refreshed", opdata['account'],
context.username)
def log_message(self, opdata=None, message=""):
gajim.log.debug(message)
def max_message_size(self, **kwargs):
return 0
def account_name(self, opdata=None, account="",protocol=""):
return gajim.get_name_from_jid(opdata['account'], unicode(account))
gajim.otr_ui_ops = OtrlMessageAppOps()
if verbose: gajim.verbose = True
del verbose
@ -3160,6 +3326,25 @@ class Interface:
gajim.status_before_autoaway[a] = ''
gajim.transport_avatar[a] = {}
if gajim.otr_module:
gajim.otr_userstates[a] = otr.otrl_userstate_create()
try:
otr.otrl_privkey_read(gajim.otr_userstates[a],
os.path.join(gajimpaths.root, "%s.key"%a).encode())
except Exception, e:
if hasattr(e,"os_errno") and e.os_errno == 2:
print "didn't find otr keyfile "+ \
(os.path.join(gajimpaths.root, "%s.key"%a).encode())
pass
try:
otr.otrl_privkey_read_fingerprints(gajim.otr_userstates[a],
os.path.join(gajimpaths.root, "%s.fpr"%a).encode(), (add_appdata, a))
except Exception, e:
if hasattr(e,"os_errno") and e.os_errno == 2:
print "didn't find otr fingerprint file "+ \
(os.path.join(gajimpaths.root, "%s.fpr"%a).encode())
pass
if gajim.config.get('remote_control'):
try:
import remote_control

View File

@ -143,6 +143,7 @@ class MessageControl:
'''Send the given message to the active tab. Doesn't return None if error
'''
jid = self.contact.jid
original_message = message
if not self.session:
fjid = self.contact.get_full_jid()
@ -150,8 +151,36 @@ class MessageControl:
self.set_session(new_session)
if gajim.otr_module:
if type == 'chat' and isinstance(message, unicode):
d = {'kwargs':{'keyID':keyID, 'type':type,
'chatstate':chatstate, 'msg_id':msg_id,
'composing_xep':composing_xep, 'resource':self.resource,
'user_nick':user_nick, 'session':self.session,
'original_message':original_message}, 'account':self.account}
new_msg = gajim.otr_module.otrl_message_sending(
gajim.otr_userstates[self.account],
(gajim.otr_ui_ops, d),
gajim.get_jid_from_account(self.account).encode(), gajim.OTR_PROTO,
self.contact.get_full_jid().encode(), message.encode(), None)
context = gajim.otr_module.otrl_context_find(
gajim.otr_userstates[self.account],
self.contact.get_full_jid().encode(),
gajim.get_jid_from_account(self.account).encode(),
gajim.OTR_PROTO, 1)[0]
print repr(context.accountname), repr(context.username)
# we send all because inject_message can filter on HTML stuff then
gajim.otr_module.otrl_message_fragment_and_send(
(gajim.otr_ui_ops, d),
context, new_msg, gajim.otr_module.OTRL_FRAGMENT_SEND_ALL)
return
# Send and update history
return gajim.connections[self.account].send_message(jid, message, keyID,
type = type, chatstate = chatstate, msg_id = msg_id,
composing_xep = composing_xep, resource = self.resource,
user_nick = user_nick, session = self.session)
user_nick = user_nick, session = self.session, original_message = original_message)

301
src/otr_windows.py Normal file
View File

@ -0,0 +1,301 @@
#!/usr/bin/env python
## otr_windows.py
##
##
## Copyright (C) 2008 Kjell Braden <fnord@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 gtkgui_helpers
from common import gajim
our_fp_text = """Your fingerprint:
<span weight="bold" face="monospace">%s</span>"""
their_fp_text = """Purported fingerprint for %s:
<span weight="bold" face="monospace">%s</span>"""
class ContactOtrSMPWindow:
def gw(self, n):
""" shorthand for self.xml.get_widget(n)"""
return self.xml.get_widget(n)
def __init__(self, fjid, account):
self.fjid = fjid
self.account = account
self.xml = gtkgui_helpers.get_glade("contact_otr_window.glade")
self.window = self.xml.get_widget("otr_smp_window")
# the contact may be unknown to gajim if ContactOtrSMPWindow is created very early
self.contact = gajim.contacts.get_contact_from_full_jid(account, fjid)
if self.contact:
self.window.set_title("OTR settings for %s"%
self.contact.get_full_jid())
self.ctx = gajim.otr_module.otrl_context_find(
gajim.otr_userstates[self.account],
self.fjid.encode(), gajim.get_jid_from_account(self.account).encode(),
gajim.OTR_PROTO, 1, (gajim.otr_add_appdata, self.account))[0]
self.gw("smp_cancel_button").connect("clicked", self._on_destroy)
self.gw("smp_ok_button").connect("clicked", self._apply)
def show(self, response):
# re-initialize if contact was unknown when we initially initialized
if not self.contact:
self.__init__(self.fjid, self.account)
assert(self.contact) # the contact MUST be known when showing the dialog
self.smp_running = False
self.finished = False
self.response = response
if response:
self.gw("desc_label").set_markup("<b>%s is trying to authenticate you "
"using a secret only known to him/her and you.</b> Please enter your secret "
"below."% self.contact.get_full_jid())
else:
self.gw("desc_label").set_markup("<b>You are trying to authenticate %s "
"using a secret only known to him/her and yourself.</b> Please enter your "
"secret below."% self.contact.get_full_jid())
self.window.show_all()
def _abort(self, text=None):
self.smp_running = False
gajim.otr_module.otrl_message_abort_smp(gajim.otr_userstates[self.account],
(gajim.otr_ui_ops, {'account':self.account}), self.ctx)
if text:
gajim.otr_ui_ops.gajim_log(text, self.account, self.contact.get_full_jid())
def _finish(self, text):
self.smp_running = False
self.finished = True
self.gw("smp_cancel_button").set_sensitive(False)
self.gw("smp_ok_button").set_sensitive(True)
self.gw("progressbar").set_fraction(1)
gajim.otr_ui_ops.gajim_log(text, self.account, self.contact.get_full_jid())
self.gw("desc_label").set_markup(text)
ctrl = gajim.interface.msg_win_mgr.get_control(self.contact.jid, self.account)
if ctrl:
ctrl.update_ui()
gajim.otr_ui_ops.write_fingerprints({'account':self.account})
def handle_tlv(self, tlvs):
if not self.contact:
self.__init__(self.fjid, self.account)
if tlvs:
nextTLV = self.ctx.smstate.nextExpected;
tlv = gajim.otr_module.otrl_tlv_find(tlvs, gajim.otr_module.OTRL_TLV_SMP1)
if tlv:
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT1:
self._abort()
else:
self.show(True)
self.gw("progressbar").set_fraction(0.3)
tlv = gajim.otr_module.otrl_tlv_find(tlvs, gajim.otr_module.OTRL_TLV_SMP2)
if tlv:
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT2:
self._abort()
else:
self.ctx.smstate.nextExpected = gajim.otr_module.OTRL_SMP_EXPECT4;
self.gw("progressbar").set_fraction(0.6)
tlv = gajim.otr_module.otrl_tlv_find(tlvs, gajim.otr_module.OTRL_TLV_SMP3)
if tlv:
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT3:
self._abort()
else:
self.ctx.smstate.nextExpected = gajim.otr_module.OTRL_SMP_EXPECT1;
if self.ctx.active_fingerprint.trust:
self._finish("SMP verifying succeeded")
else:
self._finish("SMP verifying failed")
tlv = gajim.otr_module.otrl_tlv_find(tlvs, gajim.otr_module.OTRL_TLV_SMP4)
if tlv:
if nextTLV != gajim.otr_module.OTRL_SMP_EXPECT4:
self._abort()
else:
self.ctx.smstate.nextExpected = gajim.otr_module.OTRL_SMP_EXPECT1;
if self.ctx.active_fingerprint.trust:
self._finish("SMP verifying succeeded")
else:
self._finish("SMP verifying failed")
tlv = gajim.otr_module.otrl_tlv_find(tlvs, gajim.otr_module.OTRL_TLV_SMP_ABORT)
if tlv:
self._finish("SMP verifying aborted")
def _on_destroy(self, widget):
if self.smp_running:
self._abort("user aborted SMP authentication")
self.window.hide_all()
def _apply(self, widget):
if self.finished:
self.window.hide_all()
return
secret = self.gw("secret_entry").get_text()
if self.response:
gajim.otr_module.otrl_message_respond_smp(gajim.otr_userstates[self.account],
(gajim.otr_ui_ops, {'account':self.account}), self.ctx, secret)
else:
gajim.otr_module.otrl_message_initiate_smp(gajim.otr_userstates[self.account],
(gajim.otr_ui_ops, {'account':self.account}), self.ctx, secret)
self.gw("progressbar").set_fraction(0.3)
self.smp_running = True
widget.set_sensitive(False)
class ContactOtrWindow:
def gw(self, n):
""" shorthand for self.xml.get_widget(n)"""
return self.xml.get_widget(n)
def __init__(self, contact, account, ctrl=None):
self.contact = contact
self.account = account
self.ctrl = ctrl
self.ctx = gajim.otr_module.otrl_context_find(
gajim.otr_userstates[self.account],
self.contact.get_full_jid().encode(),
gajim.get_jid_from_account(self.account).encode(),
gajim.OTR_PROTO, 1, (gajim.otr_add_appdata, self.account))[0]
self.xml = gtkgui_helpers.get_glade("contact_otr_window.glade")
self.window = self.xml.get_widget("otr_settings_window")
self.gw("settings_cancel_button").connect("clicked", self._on_destroy)
self.gw("settings_ok_button").connect("clicked", self._apply)
self.gw("otr_default_checkbutton").connect("toggled",
self._otr_default_checkbutton_toggled)
self.window.set_title("OTR settings for %s"%
self.contact.get_full_jid())
# always set the label containing our fingerprint
self.gw("our_fp_label").set_markup(our_fp_text%
gajim.otr_module.otrl_privkey_fingerprint(
gajim.otr_userstates[self.account],
gajim.get_jid_from_account(self.account).encode(),
gajim.OTR_PROTO))
if self.ctx.msgstate != gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED:
# 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%(self.contact.get_full_jid(),
"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
self.gw("their_fp_label").set_markup(
their_fp_text%(self.contact.get_full_jid(),
gajim.otr_module.otrl_privkey_hash_to_human(
self.ctx.active_fingerprint.fingerprint)))
# set the trust combobox
if self.ctx.active_fingerprint.trust:
self.gw("verified_combobox").set_active(1)
else:
self.gw("verified_combobox").set_active(0)
otr_flags = gajim.config.get_per("contacts", self.contact.jid,
"otr_flags")
if otr_flags > 0:
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 = gajim.config.get_per("accounts", self.account,
"otr_flags")
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_v1_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_ALLOW_V1)
self.gw("otr_policy_allow_v2_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_ALLOW_V2)
self.gw("otr_policy_require_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_REQUIRE_ENCRYPTION)
self.gw("otr_policy_send_tag_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_SEND_WHITESPACE_TAG)
self.gw("otr_policy_start_on_tag_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_WHITESPACE_START_AKE)
self.gw("otr_policy_start_on_error_checkbutton").set_active(
otr_flags & gajim.otr_module.OTRL_POLICY_ERROR_START_AKE)
self.window.show_all()
def _on_destroy(self, widget):
self.window.destroy()
def _apply(self, widget):
# -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.active_fingerprint.trust:
gajim.otr_module.otrl_context_set_trust(
self.ctx.active_fingerprint, "verified")
gajim.otr_ui_ops.write_fingerprints({'account':self.account})
elif trust_state == 0:
gajim.otr_module.otrl_context_set_trust(
self.ctx.active_fingerprint, "")
gajim.otr_ui_ops.write_fingerprints({'account':self.account})
if not self.ctrl:
self.ctrl = gajim.interface.msg_win_mgr.get_control(
self.contact.jid, self.account)
if self.ctrl:
self.ctrl.update_ui()
if self.gw("otr_default_checkbutton").get_active():
# default is enabled, so remove any user-specific settings if available
gajim.config.set_per("contacts", self.contact.jid, "otr_flags", -1)
else:
# build the flags using the checkboxes
flags = 0
flags += self.gw("otr_policy_allow_v1_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_ALLOW_V1
flags += self.gw("otr_policy_allow_v2_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_ALLOW_V2
flags += self.gw("otr_policy_require_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_REQUIRE_ENCRYPTION
flags += self.gw("otr_policy_send_tag_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_SEND_WHITESPACE_TAG
flags += self.gw("otr_policy_start_on_tag_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_WHITESPACE_START_AKE
flags += self.gw("otr_policy_start_on_error_checkbutton").get_active() \
and gajim.otr_module.OTRL_POLICY_ERROR_START_AKE
gajim.config.add_per("contacts", self.contact.jid)
gajim.config.set_per("contacts", self.contact.jid, "otr_flags", flags)
self._on_destroy(widget)
def _otr_default_checkbutton_toggled(self, widget):
for w in self.gw("otr_settings_vbox").get_children():
w.set_sensitive(not widget.get_active())

View File

@ -1771,6 +1771,20 @@ class RosterWindow:
gajim.sleeper_state[account] = 'online'
elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa'):
gajim.sleeper_state[account] = 'off'
if gajim.otr_module:
# disconnect from ENCRYPTED OTR contexts when going
# offline/invisible
if status == 'offline' or status == 'invisible':
ctx = gajim.otr_userstates[account].context_root
while ctx is not None:
if ctx.msgstate == gajim.otr_module.OTRL_MSGSTATE_ENCRYPTED:
disconnected = True
gajim.otr_module.otrl_message_disconnect(gajim.otr_userstates[account],
(gajim.otr_ui_ops,
{'account':account,'urgent':True}), ctx.accountname,
ctx.protocol, ctx.username)
ctx = ctx.next
if to:
gajim.connections[account].send_custom_status(status, txt, to)
else: