summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonny Lamb <jonny.lamb@collabora.co.uk>2010-12-16 17:25:37 +0000
committerJonny Lamb <jonny.lamb@collabora.co.uk>2010-12-16 17:25:37 +0000
commit2c2a5e5f528e6814a2861f8fc0bba0a2436a7477 (patch)
treeaf387804143cf305798df3cce744e28fe0bcee67
parent964cfd93802ef6dd6aa0fa3e051da2173eb79ad0 (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.am1
-rw-r--r--butterfly/channel/sasl.py218
-rw-r--r--butterfly/connection.py50
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,