From 849108b11f7d482b3417e48dd574a9ac0f8ffba8 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Fri, 13 Feb 2009 20:24:23 +0000 Subject: [PATCH] request connection password only when neede. No need to request it for GSSAPI or ANONYMOUS login. see #2465 --- src/common/connection.py | 14 ++++++++ src/common/xmpp/auth_nb.py | 74 +++++++++++++++++++++----------------- src/gajim.py | 24 +++++++++++++ src/roster_window.py | 42 ++++------------------ 4 files changed, 85 insertions(+), 69 deletions(-) diff --git a/src/common/connection.py b/src/common/connection.py index 1940de163..d09b95d1b 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -167,6 +167,7 @@ class Connection(ConnectionHandlers): # request vcard or os info... to a real JID but act as if it comes from # the fake jid self.groupchat_jids = {} # {ID : groupchat_jid} + self.pasword_callback = None self.on_connect_success = None self.on_connect_failure = None @@ -1815,6 +1816,19 @@ class Connection(ConnectionHandlers): q.setTagData('password',password) self.connection.send(iq) + def get_password(self, callback): + if self.password: + callback(self.password) + return + self.pasword_callback = callback + self.dispatch('PASSWORD_REQUIRED', None) + + def set_password(self, password): + self.password = password + if self.pasword_callback: + self.pasword_callback(password) + self.pasword_callback = None + def unregister_account(self, on_remove_success): # no need to write this as a class method and keep the value of # on_remove_success as a class property as pass it as an argument diff --git a/src/common/xmpp/auth_nb.py b/src/common/xmpp/auth_nb.py index ba895fa4f..1df5278d5 100644 --- a/src/common/xmpp/auth_nb.py +++ b/src/common/xmpp/auth_nb.py @@ -212,13 +212,8 @@ class SASL(PlugIn): self.mechanism = 'DIGEST-MD5' elif 'PLAIN' in self.mecs: self.mecs.remove('PLAIN') - sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ - self._owner.Server, self.username, self.password) - sasl_data = sasl_data.encode('utf-8').encode('base64').replace( - '\n','') - node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, - payload=[sasl_data]) self.mechanism = 'PLAIN' + self._owner._caller.get_password(self.set_password) else: self.startsasl = SASL_FAILURE log.error('I can only use DIGEST-MD5, GSSAPI and PLAIN mecanisms.') @@ -297,36 +292,21 @@ class SASL(PlugIn): if 'qop' in chal and ((isinstance(chal['qop'], str) and \ chal['qop'] =='auth') or (isinstance(chal['qop'], list) and 'auth' in \ chal['qop'])): - resp = {} - resp['username'] = self.username + self.resp = {} + self.resp['username'] = self.username if self.realm: - resp['realm'] = self.realm + self.resp['realm'] = self.realm else: - resp['realm'] = self._owner.Server - resp['nonce'] = chal['nonce'] - resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in + self.resp['realm'] = self._owner.Server + self.resp['nonce'] = chal['nonce'] + self.resp['cnonce'] = ''.join("%x" % randint(0, 2**28) for randint in itertools.repeat(random.randint, 7)) - resp['nc'] = ('00000001') - resp['qop'] = 'auth' - resp['digest-uri'] = 'xmpp/' + self._owner.Server - A1=C([H(C([resp['username'], resp['realm'], self.password])), - resp['nonce'], resp['cnonce']]) - A2=C(['AUTHENTICATE',resp['digest-uri']]) - response= HH(C([HH(A1), resp['nonce'], resp['nc'], resp['cnonce'], - resp['qop'], HH(A2)])) - resp['response'] = response - resp['charset'] = 'utf-8' - sasl_data = u'' - for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', - 'digest-uri', 'response', 'qop'): - if key in ('nc','qop','response','charset'): - sasl_data += u"%s=%s," % (key, resp[key]) - else: - sasl_data += u'%s="%s",' % (key, resp[key]) - sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( - '\r','').replace('\n','') - node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) - self._owner.send(str(node)) + self.resp['nc'] = ('00000001') + self.resp['qop'] = 'auth' + self.resp['digest-uri'] = 'xmpp/' + self._owner.Server + self.resp['charset'] = 'utf-8' + # Password is now required + self._owner._caller.get_password(self.set_password) elif 'rspauth' in chal: self._owner.send(str(Node('response', attrs={'xmlns':NS_SASL}))) else: @@ -335,6 +315,34 @@ class SASL(PlugIn): if self.on_sasl: self.on_sasl() raise NodeProcessed + + def set_password(self, password): + self.password = password + if self.mechanism == 'DIGEST-MD5': + A1 = C([H(C([self.resp['username'], self.resp['realm'], + self.password])), self.resp['nonce'], self.resp['cnonce']]) + A2 = C(['AUTHENTICATE', self.resp['digest-uri']]) + response= HH(C([HH(A1), self.resp['nonce'], self.resp['nc'], + self.resp['cnonce'], self.resp['qop'], HH(A2)])) + self.resp['response'] = response + sasl_data = u'' + for key in ('charset', 'username', 'realm', 'nonce', 'nc', 'cnonce', + 'digest-uri', 'response', 'qop'): + if key in ('nc','qop','response','charset'): + sasl_data += u"%s=%s," % (key, self.resp[key]) + else: + sasl_data += u'%s="%s",' % (key, self.resp[key]) + sasl_data = sasl_data[:-1].encode('utf-8').encode('base64').replace( + '\r', '').replace('\n', '') + node = Node('response', attrs={'xmlns':NS_SASL}, payload=[sasl_data]) + elif self.mechanism == 'PLAIN': + sasl_data = u'%s\x00%s\x00%s' % (self.username + '@' + \ + self._owner.Server, self.username, self.password) + sasl_data = sasl_data.encode('utf-8').encode('base64').replace( + '\n', '') + node = Node('auth', attrs={'xmlns': NS_SASL, 'mechanism': 'PLAIN'}, + payload=[sasl_data]) + self._owner.send(str(node)) class NonBlockingNonSASL(PlugIn): diff --git a/src/gajim.py b/src/gajim.py index d0ee392b1..cd9cd2c8b 100644 --- a/src/gajim.py +++ b/src/gajim.py @@ -265,6 +265,7 @@ from common import socks5 from common import helpers from common import optparser from common import dataforms +from common import passwords if verbose: gajim.verbose = True del verbose @@ -1522,6 +1523,28 @@ class Interface: self.gpg_passphrase[keyid] = request request.add_callback(account, callback) + def handle_event_password_required(self, account, array): + #('PASSWORD_REQUIRED', account, None) + text = _('Enter your password for account %s') % account + if passwords.USER_HAS_GNOMEKEYRING and \ + not passwords.USER_USES_GNOMEKEYRING: + text += '\n' + _('Gnome Keyring is installed but not \ + correctly started (environment variable probably not \ + correctly set)') + + def on_ok(passphrase, save): + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + passwords.save_password(account, passphrase) + gajim.connections[account].set_password(passphrase) + + def on_cancel(): + self.roster.set_state(account, 'offline') + self.roster.update_status_combobox() + + dialogs.PassphraseDialog(_('Password Required'), text, _('Save password'), + ok_handler=on_ok, cancel_handler=on_cancel) + def handle_event_roster_info(self, account, array): #('ROSTER_INFO', account, (jid, name, sub, ask, groups)) jid = array[0] @@ -2262,6 +2285,7 @@ class Interface: self.handle_event_unique_room_id_unsupported, 'UNIQUE_ROOM_ID_SUPPORTED': self.handle_event_unique_room_id_supported, 'GPG_PASSWORD_REQUIRED': self.handle_event_gpg_password_required, + 'PASSWORD_REQUIRED': self.handle_event_password_required, 'SSL_ERROR': self.handle_event_ssl_error, 'FINGERPRINT_ERROR': self.handle_event_fingerprint_error, 'PLAIN_CONNECTION': self.handle_event_plain_connection, diff --git a/src/roster_window.py b/src/roster_window.py index 0e819e3ba..b0fd75441 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -54,7 +54,6 @@ import features_window from common import gajim from common import helpers -from common import passwords from common.exceptions import GajimGeneralException from common import i18n from common import pep @@ -1909,13 +1908,16 @@ class RosterWindow: dialogs.InformationDialog(_('Authorization has been removed'), _('Now "%s" will always see you as offline.') %jid) - def set_connecting_state(self, account): + def set_state(self, account, state): child_iterA = self._get_account_iter(account, self.model) if child_iterA: self.model[child_iterA][0] = \ - gajim.interface.jabber_state_images['16']['connecting'] + gajim.interface.jabber_state_images['16'][state] if gajim.interface.systray_enabled: - gajim.interface.systray.change_status('connecting') + gajim.interface.systray.change_status(state) + + def set_connecting_state(self, account): + self.set_state(account, 'connecting') def send_status(self, account, status, txt, auto=False, to=None): child_iterA = self._get_account_iter(account, self.model) @@ -1927,38 +1929,6 @@ class RosterWindow: if gajim.connections[account].connected < 2: self.set_connecting_state(account) - if not gajim.connections[account].password: - text = _('Enter your password for account %s') % account - if passwords.USER_HAS_GNOMEKEYRING and \ - not passwords.USER_USES_GNOMEKEYRING: - text += '\n' + _('Gnome Keyring is installed but not \ - correctly started (environment variable probably not \ - correctly set)') - def on_ok(passphrase, save): - gajim.connections[account].password = passphrase - if save: - gajim.config.set_per('accounts', account, 'savepass', True) - passwords.save_password(account, passphrase) - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid and not gajim.connections[account].gpg: - dialogs.WarningDialog(_('GPG is not usable'), - _('You will be connected to %s without OpenPGP.') % \ - account) - self.send_status_continue(account, status, txt, auto, to) - - def on_cancel(): - if child_iterA: - self.model[child_iterA][0] = \ - gajim.interface.jabber_state_images['16']['offline'] - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status('offline') - self.update_status_combobox() - - dialogs.PassphraseDialog(_('Password Required'), text, - _('Save password'), ok_handler=on_ok, - cancel_handler=on_cancel) - return - keyid = gajim.config.get_per('accounts', account, 'keyid') if keyid and not gajim.connections[account].gpg: dialogs.WarningDialog(_('GPG is not usable'),