diff options
author | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2010-12-16 17:25:37 +0000 |
---|---|---|
committer | Jonny Lamb <jonny.lamb@collabora.co.uk> | 2010-12-16 17:25:37 +0000 |
commit | 2c2a5e5f528e6814a2861f8fc0bba0a2436a7477 (patch) | |
tree | af387804143cf305798df3cce744e28fe0bcee67 | |
parent | 964cfd93802ef6dd6aa0fa3e051da2173eb79ad0 (diff) |
butterfly: pop up a ServerAuthentication SASL channel when no password givensasl
Signed-off-by: Jonny Lamb <jonny.lamb@collabora.co.uk>
-rw-r--r-- | butterfly/channel/Makefile.am | 1 | ||||
-rw-r--r-- | butterfly/channel/sasl.py | 218 | ||||
-rw-r--r-- | butterfly/connection.py | 50 |
3 files changed, 262 insertions, 7 deletions
diff --git a/butterfly/channel/Makefile.am b/butterfly/channel/Makefile.am index 6ac1774..cfc8076 100644 --- a/butterfly/channel/Makefile.am +++ b/butterfly/channel/Makefile.am @@ -8,4 +8,5 @@ channel_PYTHON = \ __init__.py \ media.py \ muc.py \ + sasl.py \ text.py diff --git a/butterfly/channel/sasl.py b/butterfly/channel/sasl.py new file mode 100644 index 0000000..72ae18b --- /dev/null +++ b/butterfly/channel/sasl.py @@ -0,0 +1,218 @@ +# telepathy-butterfly - an MSN connection manager for Telepathy +# +# Copyright (C) 2010 Collabora Ltd. +# +# This program 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; either version 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +import logging +import weakref +import time +import tempfile +import os +import shutil + +import dbus +import gobject +import telepathy +import papyon +import papyon.event +import socket + +from butterfly.util.decorator import async + +__all__ = ['ButterflySASLChannel'] + +logger = logging.getLogger('Butterfly.SASLChannel') + +class ButterflySASLChannel(telepathy.server.ChannelTypeServerAuthentication, + telepathy.server.ChannelInterfaceSASLAuthentication): + + _statuses = { + telepathy.SASL_STATUS_NOT_STARTED: 'not started', + telepathy.SASL_STATUS_IN_PROGRESS: 'in progress', + telepathy.SASL_STATUS_SERVER_SUCCEEDED: 'server succeeded', + telepathy.SASL_STATUS_CLIENT_ACCEPTED: 'client accepted', + telepathy.SASL_STATUS_SUCCEEDED: 'succeeded', + telepathy.SASL_STATUS_SERVER_FAILED: 'server failed', + telepathy.SASL_STATUS_CLIENT_FAILED: 'client failed' + } + + def __init__(self, conn, manager, props, object_path=None): + telepathy.server.ChannelTypeServerAuthentication.__init__(self, conn, manager, props, + object_path=object_path) + telepathy.server.ChannelInterfaceSASLAuthentication.__init__(self) + + self._sasl_status = telepathy.SASL_STATUS_NOT_STARTED + self._sasl_error = '' + self._sasl_error_details = {} + + self._callback = None + self._args = None + self._password = None + + dbus_interface = telepathy.CHANNEL_TYPE_SERVER_AUTHENTICATION + self._implement_property_get(dbus_interface, { + 'AuthenticationMethod': lambda: self.authentication_method + }) + + dbus_interface = telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION + self._implement_property_get(dbus_interface, { + 'AvailableMechanisms': lambda: self.available_mechanisms, + 'HasInitialData': lambda: self.has_initial_data, + 'CanTryAgain': lambda: self.can_try_again, + 'SASLStatus': lambda: self.sasl_status, + 'SASLError': lambda: self.sasl_error, + 'SASLErrorDetails': lambda: self.sasl_error_details, + 'AuthorizationIdentity': lambda: self.authorization_identity, + 'DefaultUsername': lambda: self.default_username, + 'DefaultRealm': lambda: self.default_realm + }) + + self._add_immutables({ + 'AuthenticationMethod': telepathy.CHANNEL_TYPE_SERVER_AUTHENTICATION, + 'AvailableMechanisms': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + 'HasInitialData': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + 'CanTryAgain': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + 'AuthorizationIdentity': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + 'DefaultUsername': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + 'DefaultRealm': telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION, + }) + + @property + def authentication_method(self): + return dbus.String(telepathy.CHANNEL_INTERFACE_SASL_AUTHENTICATION) + + @property + def available_mechanisms(self): + return dbus.Array(['X-TELEPATHY-PASSWORD'], signature='s') + + @property + def has_initial_data(self): + return dbus.Boolean(True) + + @property + def can_try_again(self): + return dbus.Boolean(False) + + @property + def sasl_status(self): + return dbus.UInt32(self._sasl_status) + + @property + def sasl_error(self): + return dbus.String(self._sasl_error) + + @property + def sasl_error_details(self): + return dbus.Dictionary(self._sasl_error_details, signature='sv') + + @property + def authorization_identity(self): + return dbus.String(self._conn.self_handle.name) + + @property + def default_username(self): + return dbus.String(self._conn.self_handle.name) + + @property + def default_realm(self): + return dbus.String('') + + def change_status(self, new_status, new_sasl_error=''): + self._sasl_status = new_status + self._sasl_error = new_sasl_error + self.SASLStatusChanged(self.sasl_status, self.sasl_error, self.sasl_error_details) + logger.debug('status changed to "%s"' % self._statuses[new_status]) + + def finished(self, dbus_error=None, message=None): + if dbus_error is not None: + # an error + self._callback(None, dbus_error, message, *self._args) + else: + # no error! + self._callback(self._password, None, None, *self._args) + + def Close(self): + logger.debug('Closing') + + if self.sasl_status not in (telepathy.SASL_STATUS_SUCCEEDED, + telepathy.SASL_STATUS_SERVER_FAILED, + telepathy.SASL_STATUS_CLIENT_FAILED): + cancelled_str = telepathy.Cancelled._dbus_error_name + + self.change_status(telepathy.SASL_STATUS_CLIENT_FAILED, + cancelled_str) + + self.finished(cancelled_str, 'SASL channel was closed') + + # chain-up + telepathy.server.ChannelTypeServerAuthentication.Close(self) + + def StartMechanismWithData(self, mechanism, initial_data): + if mechanism not in self.available_mechanisms: + raise telepathy.NotImplemented('Mechanism %s not implemented' % \ + mechanism) + + if self.sasl_status != telepathy.SASL_STATUS_NOT_STARTED: + raise telepathy.NotAvailable('StartMechanismWithData cannot be ' + \ + 'called in status %u' % self.sasl_status) + + if len(initial_data) == 0: + raise telepathy.InvalidArgument('No initial data given') + + self.change_status(telepathy.SASL_STATUS_IN_PROGRESS) + + self._password = initial_data + + self.change_status(telepathy.SASL_STATUS_SERVER_SUCCEEDED) + + logger.debug('Got password, server succeded') + + def AcceptSASL(self): + if self.sasl_status != telepathy.SASL_STATUS_SERVER_SUCCEEDED: + raise telepathy.NotAvailable('AcceptSASL cannot be called in status %u' % \ + self.sasl_status) + + self.change_status(telepathy.SASL_STATUS_SUCCEEDED) + + self.finished() + + def AbortSASL(self, reason, debug_message): + if self.sasl_status in (telepathy.SASL_STATUS_SERVER_SUCCEEDED, + telepathy.SASL_STATUS_CLIENT_ACCEPTED): + raise telepathy.NotAvailable('AbortSASL cannot be called in status %u' % \ + self.sasl_status) + + + if self.sasl_status in (telepathy.SASL_STATUS_CLIENT_FAILED, + telepathy.SASL_STATUS_SERVER_FAILED): + return + + logger.debug('Aborting SASL because: %s' % debug_message) + + cancelled_str = telepathy.Cancelled._dbus_error_name + + self._sasl_error_details['debug-message'] = debug_message + self.change_status(telepathy.SASL_STATUS_CLIENT_FAILED, + cancelled_str) + + self.finished(cancelled_str, 'AbortSASL was called') + + def prompt(self, callback, *args): + self._callback = callback + self._args = args + + self._conn.add_channels([self]) + diff --git a/butterfly/connection.py b/butterfly/connection.py index d5f3c07..872a699 100644 --- a/butterfly/connection.py +++ b/butterfly/connection.py @@ -33,6 +33,7 @@ from butterfly.handle import ButterflyHandleFactory, network_to_extension from butterfly.contacts import ButterflyContacts from butterfly.channel_manager import ButterflyChannelManager from butterfly.mail_notification import ButterflyMailNotification +from butterfly.channel.sasl import ButterflySASLChannel __all__ = ['ButterflyConnection'] @@ -80,8 +81,12 @@ class ButterflyConnection(telepathy.server.Connection, self._manager = weakref.proxy(manager) self._new_client(use_http=self._try_http) - self._account = (parameters['account'].encode('utf-8'), - parameters['password'].encode('utf-8')) + + password = parameters.get('password', None) + if password is not None: + password = password.encode('utf-8') + self._account = (parameters['account'].encode('utf-8'), password) + self._channel_manager = ButterflyChannelManager(self, protocol) # Call parent initializers @@ -103,6 +108,8 @@ class ButterflyConnection(telepathy.server.Connection, self._initial_presence = papyon.Presence.INVISIBLE self._initial_personal_message = None + self._sasl_channel = None + logger.info("Connection to the account %s created" % account) except Exception, e: import traceback @@ -265,9 +272,34 @@ class ButterflyConnection(telepathy.server.Connection, return self.ensure_handle(handle_type, handle_name, contact=contact) def Connect(self): - if self._status == telepathy.CONNECTION_STATUS_DISCONNECTED: - logger.info("Connecting") - self.__disconnect_reason = telepathy.CONNECTION_STATUS_REASON_NONE_SPECIFIED + if self._status != telepathy.CONNECTION_STATUS_DISCONNECTED: + return + + logger.info("Connecting") + + self.StatusChanged(telepathy.CONNECTION_STATUS_CONNECTING, + telepathy.CONNECTION_STATUS_REASON_REQUESTED) + self.__disconnect_reason = telepathy.CONNECTION_STATUS_REASON_NONE_SPECIFIED + + def prompt_cb(password, error, message): + if error is not None: + logger.warning('SASL channel failed: %s: %s' % (error, message)) + + self.__disconnect_reason = telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED + self._disconnected() + else: + self._account = (self._account[0], str(password).encode('utf-8')) + self._msn_client.login(*self._account) + + if self._account[1] is None: + # pop up a SASL channel asking for the password + props = self._generate_props(telepathy.CHANNEL_TYPE_SERVER_AUTHENTICATION, + telepathy.server.NoneHandle(), False) + + self._sasl_channel = ButterflySASLChannel(self, None, props, 'PasswordChannel') + self._sasl_channel.prompt(prompt_cb) + else: + # we have the password already self._msn_client.login(*self._account) def Disconnect(self): @@ -282,6 +314,10 @@ class ButterflyConnection(telepathy.server.Connection, logger.info("Disconnected") self.StatusChanged(telepathy.CONNECTION_STATUS_DISCONNECTED, self.__disconnect_reason) + + if self._sasl_channel is not None: + self._sasl_channel.Close() + self._channel_manager.close() self._manager.disconnected(self) @@ -337,8 +373,8 @@ class ButterflyConnection(telepathy.server.Connection, # papyon.event.ClientEventInterface def on_client_state_changed(self, state): if state == papyon.event.ClientState.CONNECTING: - self.StatusChanged(telepathy.CONNECTION_STATUS_CONNECTING, - telepathy.CONNECTION_STATUS_REASON_REQUESTED) + # connection status is already CONNECTING + pass elif state == papyon.event.ClientState.SYNCHRONIZED: handle = self.ensure_handle(telepathy.HANDLE_TYPE_LIST, 'subscribe') props = self._generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, |