diff --git a/src/chat_control.py b/src/chat_control.py index 03a40c1a4..a489d04c8 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2477,6 +2477,8 @@ class ChatControl(ChatControlBase): NS_ESESSION) and not gajim.capscache.is_supported( self.contact, 'notexistant'): self.begin_e2e_negotiation() + elif not self.session.accepted: + self.begin_archiving_negotiation() else: self.send_chatstate('active', self.contact) @@ -2710,7 +2712,7 @@ class ChatControl(ChatControlBase): else: self.begin_e2e_negotiation() - def begin_e2e_negotiation(self): + def begin_negotiation(self): self.no_autonegotiation = True if not self.session: @@ -2718,8 +2720,14 @@ class ChatControl(ChatControlBase): new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) self.set_session(new_sess) + def begin_e2e_negotiation(self): + self.begin_negotiation() self.session.negotiate_e2e(False) + def begin_archiving_negotiation(self): + self.begin_negotiation() + self.session.negotiate_archiving() + def got_connected(self): ChatControlBase.got_connected(self) # Refreshing contact diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py index 6af85bed1..0bd0d1dba 100644 --- a/src/common/message_archiving.py +++ b/src/common/message_archiving.py @@ -73,6 +73,47 @@ class ConnectionArchive: print iq_ self.connection.send(iq_) + def get_item_pref(self, jid): + jid = common.xmpp.JID(jid) + if unicode(jid) in self.items: + return self.items[jid] + + if jid.getStripped() in self.items: + return self.items[jid.getStripped()] + + if jid.getDomain() in self.items: + return self.items[jid.getDomain()] + + return self.default + + def logging_preference(self, jid, initiator_options=None): + otr = self.get_item_pref(jid)['otr'] + if initiator_options: + if ((initiator_options == ['mustnot'] and otr == 'forbid') or + (initiator_options == ['may'] and otr == 'require')): + return None + + if (initiator_options == ['mustnot'] or + (initiator_options[0] == 'mustnot' and + otr not in ('opppose', 'forbid')) or + (initiator_options == ['may', 'mustnot'] and + otr in ('require', 'prefer'))): + return 'mustnot' + + return 'may' + + if otr == 'require': + return ['mustnot'] + + if otr in ('prefer', 'approve'): + return ['mustnot', 'may'] + + if otr in ('concede', 'oppose'): + return ['may', 'mustnot'] + + # otr == 'forbid' + return ['may'] + def _ArchiveCB(self, con, iq_obj): print '_ArchiveCB', iq_obj.getType() if iq_obj.getType() == 'error': diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py index d5a5ec9e7..b825a931e 100644 --- a/src/common/stanza_session.py +++ b/src/common/stanza_session.py @@ -166,7 +166,114 @@ class StanzaSession(object): self.status = None -class EncryptedStanzaSession(StanzaSession): +class ArchivingStanzaSession(StanzaSession): + def __init__(self, conn, jid, thread_id, type_='chat'): + StanzaSession.__init__(self, conn, jid, thread_id, type_='chat') + self.accepted = False + + def archiving_logging_preference(self, initiator_options=None): + return self.conn.logging_preference(self.jid, initiator_options) + + def negotiate_archiving(self): + self.negotiated = {} + + request = xmpp.Message() + feature = request.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='form') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn', + typ='hidden')) + x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean', + required=True)) + + x.addChild(node=xmpp.DataField(name='logging', typ='list-single', + options=self.archiving_logging_preference(), required=True)) + + x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single', + options=['never'], required=True)) + x.addChild(node=xmpp.DataField(name='security', typ='list-single', + options=['none'], required=True)) + + feature.addChild(node=x) + + self.status = 'requested' + + self.send(request) + + def respond_archiving_bob(self, form): + field = form.getField('logging') + options = [x[1] for x in field.getOptions()] + values = field.getValues() + + logging = self.archiving_logging_preference(options) + self.negotiated['logging'] = logging + + response = xmpp.Message() + feature = response.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + x = xmpp.DataForm(typ='submit') + + x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn')) + x.addChild(node=xmpp.DataField(name='accept', value='true')) + + x.addChild(node=xmpp.DataField(name='logging', value=logging)) + + self.status = 'responded' + + feature.addChild(node=x) + + if not logging: + response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE) + + feature = xmpp.Node(xmpp.NS_FEATURE + ' feature') + + n = xmpp.Node('field') + n['var'] = 'logging' + feature.addChild(node=n) + + response.T.error.addChild(node=feature) + + self.send(response) + + def accept_archiving_bob(self, form): + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + print 'SESSION ACCEPTED', self.loggable + self.accepted = True + + def accept_archiving_alice(self, form): + negotiated = {} + ask_user = {} + not_acceptable = [] + + if form['logging'] not in self.archiving_logging_preference(): + raise + + self.negotiated['logging'] = form['logging'] + + accept = xmpp.Message() + feature = accept.NT.feature + feature.setNamespace(xmpp.NS_FEATURE) + + result = xmpp.DataForm(typ='result') + + result.addChild(node=xmpp.DataField(name='FORM_TYPE', + value='urn:xmpp:ssn')) + result.addChild(node=xmpp.DataField(name='accept', value='1')) + + feature.addChild(node=result) + + self.send(accept) + if self.negotiated['logging'] == 'mustnot': + self.loggable = False + print 'SESSION ACCEPTED', self.loggable + self.accepted = True + + +class EncryptedStanzaSession(ArchivingStanzaSession): ''' An encrypted stanza negotiation has several states. They arerepresented as the following values in the 'status' attribute of the session object: diff --git a/src/session.py b/src/session.py index 93fbf2375..5af7d4685 100644 --- a/src/session.py +++ b/src/session.py @@ -114,7 +114,6 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): log_type = 'chat_msg_recv' else: log_type = 'single_msg_recv' - if self.is_loggable() and msgtxt: try: msg_id = gajim.logger.write(log_type, full_jid_with_resource, @@ -386,40 +385,53 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): # encrypted session states. these are described in stanza_session.py try: - # bob responds if form.getType() == 'form' and 'security' in form.asDict(): - # we don't support 3-message negotiation as the responder - if 'dhkeys' in form.asDict(): - self.fail_bad_negotiation('3 message negotiation not supported ' - 'when responding', ('dhkeys',)) - return + security_options = [x[1] for x in form.getField('security').getOptions()] + if security_options == ['none']: + self.respond_archiving_bob(form) + else: + # bob responds - negotiated, not_acceptable, ask_user = self.verify_options_bob(form) + # we don't support 3-message negotiation as the responder + if 'dhkeys' in form.asDict(): + self.fail_bad_negotiation('3 message negotiation not supported ' + 'when responding', ('dhkeys',)) + return - if ask_user: - def accept_nondefault_options(is_checked): - self.dialog.destroy() - negotiated.update(ask_user) - self.respond_e2e_bob(form, negotiated, not_acceptable) + negotiated, not_acceptable, ask_user = self.verify_options_bob(form) - def reject_nondefault_options(): - self.dialog.destroy() - for key in ask_user.keys(): - not_acceptable.append(key) - self.respond_e2e_bob(form, negotiated, not_acceptable) + if ask_user: + def accept_nondefault_options(is_checked): + self.dialog.destroy() + negotiated.update(ask_user) + self.respond_e2e_bob(form, negotiated, not_acceptable) - self.dialog = dialogs.YesNoDialog(_('Confirm these session ' - 'options'), - _('''The remote client wants to negotiate an session with these features: + def reject_nondefault_options(): + self.dialog.destroy() + for key in ask_user.keys(): + not_acceptable.append(key) + self.respond_e2e_bob(form, negotiated, not_acceptable) + + self.dialog = dialogs.YesNoDialog(_('Confirm these session ' + 'options'), + _('''The remote client wants to negotiate an session with these features: %s Are these options acceptable?''') % (negotiation.describe_features( - ask_user)), - on_response_yes=accept_nondefault_options, - on_response_no=reject_nondefault_options) - else: - self.respond_e2e_bob(form, negotiated, not_acceptable) + ask_user)), + on_response_yes=accept_nondefault_options, + on_response_no=reject_nondefault_options) + else: + self.respond_e2e_bob(form, negotiated, not_acceptable) + + return + + elif self.status == 'requested' and form.getType() == 'submit': + try: + self.accept_archiving_alice(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) return @@ -455,6 +467,13 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): except exceptions.NegotiationError, details: self.fail_bad_negotiation(details) + return + elif self.status == 'responded' and form.getType() == 'result': + try: + self.accept_archiving_bob(form) + except exceptions.NegotiationError, details: + self.fail_bad_negotiation(details) + return elif self.status == 'responded-e2e' and form.getType() == 'result': try: