/usr/share/sugar-presence-service/server_plugin.py is in sugar-presence-service-0.88 0.88.0-3.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | """XMPP server plugin for Presence Service"""
# Copyright (C) 2007, Red Hat, Inc.
# Copyright (C) 2007, 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# Standard library
import logging
from itertools import izip
import re
import gconf
# Other libraries
import dbus
from telepathy.client import (Connection, Channel)
from telepathy.interfaces import (CONN_INTERFACE, CHANNEL_INTERFACE_GROUP,
CHANNEL_TYPE_CONTACT_LIST)
from telepathy.constants import (HANDLE_TYPE_CONTACT, HANDLE_TYPE_GROUP,
CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED,
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES,
CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
# Presence Service local modules
import psutils
from telepathy_plugin import TelepathyPlugin
_logger = logging.getLogger('s-p-s.server_plugin')
_MUC_JID_RE = re.compile('.*@.*/.*')
hexdigits = '0123456789abcdefABCDEF'
class ServerPlugin(TelepathyPlugin):
"""Telepathy-python-based presence server interface
The ServerPlugin instance translates network events from
Telepathy Python into GObject events. It provides direct
python calls to perform the required network operations
to implement the PresenceService.
"""
_TP_CONN_MANAGER = 'gabble'
_PROTOCOL = 'jabber'
_OBJ_PATH_PREFIX = "/org/freedesktop/Telepathy/Connection/gabble/"
def __init__(self, registry, owner):
TelepathyPlugin.__init__(self, registry, owner)
self._friends_channel = None
def _ip4_address_changed_cb(self, ip4am, address, iface):
TelepathyPlugin._ip4_address_changed_cb(self, ip4am, address, iface)
if address:
_logger.debug("::: valid IP4 address, conn_status %s" %
self._conn_status)
# this is a no-op if starting would be inappropriate right now
if self._conn_status != CONNECTION_STATUS_CONNECTED:
self.emit('want-to-connect')
else:
_logger.debug("::: invalid IP4 address, will disconnect")
self._stop()
def _get_account_info(self):
"""Retrieve connection manager parameters for this account.
We first try to connect without the register flag. If the connection
fails because of an authentication error we'll try to register
the account.
"""
server = self._owner.get_server()
khash = psutils.pubkey_to_keyid(self._owner.props.key)
return {
'account': "%s@%s" % (khash, server),
'fallback-conference-server': "conference.%s" % server,
'password': self._owner.get_key_hash(),
'register': False,
'port': dbus.UInt32(5223),
'old-ssl': True,
'ignore-ssl-errors': True,
}
def suggest_room_for_activity(self, activity_id):
"""Suggest a room to use to share the given activity.
"""
# We shouldn't have to do this, but Gabble sometimes finds the IRC
# transport and goes "that has chatrooms, that'll do nicely". Work
# around it til Gabble gets better at finding the MUC service.
return '%s@%s' % (activity_id,
self._account['fallback-conference-server'])
def _find_existing_connection(self):
"""Try to find an existing Telepathy connection to this server
filters the set of connections from
telepathy.client.Connection.get_connections
to find a connection using our protocol with the
"self handle" of that connection being a handle
which matches our account (see _get_account_info)
returns connection or None
"""
our_name = self._account['account']
# Search existing connections, if any, that we might be able to use
connections = Connection.get_connections()
for item in connections:
if not item.object_path.startswith(self._OBJ_PATH_PREFIX):
continue
if item[CONN_INTERFACE].GetProtocol() != self._PROTOCOL:
continue
if item[CONN_INTERFACE].GetStatus() == CONNECTION_STATUS_CONNECTED:
test_handle = item[CONN_INTERFACE].RequestHandles(
HANDLE_TYPE_CONTACT, [our_name])[0]
if item[CONN_INTERFACE].GetSelfHandle() != test_handle:
continue
return item
return None
def _could_connect(self):
return bool(self._ip4am.props.address and
TelepathyPlugin._could_connect(self))
def _server_is_trusted(self, hostname):
"""Return True if the server with the given hostname is trusted to
verify public-key ownership correctly, and only allows users to
register JIDs whose username part is either a public key fingerprint,
or of the wrong form to be a public key fingerprint (to allow for
ejabberd's admin@example.com address).
If we trust the server, we can skip verifying the key ourselves,
which leads to simplifications. In the current implementation we
never verify that people actually own the key they claim to, so
we will always give contacts on untrusted servers a JID- rather than
key-based identity.
For the moment we assume that the test server, olpc.collabora.co.uk,
does this verification.
"""
# FIXME: just trusting the owner's server for now
server = self._owner.get_server()
return (server and len(server) and hostname == server)
def identify_contacts(self, tp_chan, handles, identifiers=None):
"""Work out the "best" unique identifier we can for the given handles,
in the context of the given channel (which may be None), using only
'fast' connection manager API (that does not involve network
round-trips).
For the XMPP server case, we proceed as follows:
* Find the owners of the given handles, if the channel has
channel-specific handles
* If the owner (globally-valid JID) is on a trusted server, return
'keyid/' plus the 'key fingerprint' (the user part of their JID,
currently implemented as the SHA-1 of the Base64 blob in
owner.key.pub)
* If the owner (globally-valid JID) cannot be found or is on an
untrusted server, return 'xmpp/' plus an escaped form of the JID
The idea is that we identify buddies by key-ID (i.e. by key, assuming
no collisions) if we can find it without making network round-trips,
but if that's not possible we just use their JIDs.
:Parameters:
`tp_chan` : telepathy.client.Channel or None
The channel in which the handles were found, or None if they
are known to be channel-specific handles
`handles` : iterable over (int or long)
The contacts' handles in that channel
:Returns:
A dict mapping the provided handles to the best available
unique identifier, which is a string that could be used as a
suffix to an object path
"""
# we need to be able to index into handles, so force them to
# be a sequence
if not isinstance(handles, (tuple, list)):
handles = tuple(handles)
owners = handles
if tp_chan is not None and CHANNEL_INTERFACE_GROUP in tp_chan:
group = tp_chan[CHANNEL_INTERFACE_GROUP]
if (group.GetGroupFlags() &
CHANNEL_GROUP_FLAG_CHANNEL_SPECIFIC_HANDLES):
identifiers = None
owners = group.GetHandleOwners(handles)
for i, owner in enumerate(owners):
if owner == 0:
owners[i] = handles[i]
else:
group = None
if identifiers is None:
identifiers = self._conn[CONN_INTERFACE].InspectHandles(
HANDLE_TYPE_CONTACT, owners)
ret = {}
for handle, jid in izip(handles, identifiers):
# special-case the Owner - we always know who we are
if (handle == self.self_handle or
(group is not None and handle == group.GetSelfHandle())):
ret[handle] = self._owner.props.objid
continue
if '/' in jid:
# the contact is unidentifiable (in an anonymous MUC) - create
# a temporary identity for them, based on their room-JID
ret[handle] = 'xmpp/' + psutils.escape_identifier(jid)
else:
user, host = jid.split('@', 1)
if (self._server_is_trusted(host) and len(user) == 40 and
user.strip(hexdigits) == ''):
# they're on a trusted server and their username looks
# like a key-ID
ret[handle] = 'keyid/' + user.lower()
else:
# untrusted server, or not the right format to be a
# key-ID - identify the contact by their JID
ret[handle] = 'xmpp/' + psutils.escape_identifier(jid)
return ret
def _connected_cb(self):
TelepathyPlugin._connected_cb(self)
# request Friends group channel
def friends_channel_requested_cb(friends_chan_path):
self._friends_channel = Channel(self._conn.service_name,
friends_chan_path)
def error_requesting_friends_channel(e):
_logger.debug('error requesting friends channel: %r' % e)
handles = self._conn[CONN_INTERFACE].RequestHandles(HANDLE_TYPE_GROUP,
["Friends"])
self._conn[CONN_INTERFACE].RequestChannel(CHANNEL_TYPE_CONTACT_LIST,
HANDLE_TYPE_GROUP, handles[0], True,
reply_handler=friends_channel_requested_cb,
error_handler=error_requesting_friends_channel)
def _filter_trusted_server(self, handles):
"""Filter a list of contact handles removing the one which aren't hosted
on a trusted server.
This function is used to only accept subscriptions coming from a
trusted server.
:Parameters:
`handles` : iterable over (int or long)
The contacts' handles to filter
:Returns: a list of handles
"""
result = []
if not handles:
return result
identifiers = self._conn[CONN_INTERFACE].InspectHandles(
HANDLE_TYPE_CONTACT, handles)
for handle, jid in izip(handles, identifiers):
host = jid.split('@', 1)[1]
if self._server_is_trusted(host):
result.append(handle)
return result
def _publish_members_changed_cb(self, message, added, removed,
local_pending, remote_pending,
actor, reason):
TelepathyPlugin._publish_members_changed_cb(self, message,
added, removed, local_pending, remote_pending, actor,
reason)
local_pending = self._filter_trusted_server(local_pending)
if local_pending:
# accept all requested subscriptions
self._publish_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
local_pending, '')
# subscribe to people who've subscribed to us, if necessary
if self._subscribe_channel is not None:
added = list(set(added) - self._subscribe_members
- self._subscribe_remote_pending)
added = self._filter_trusted_server(added)
if added:
self._subscribe_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
added, '')
def _handle_connection_status_change(self, status, reason):
"""Override TelepathyPlugin implementation to manage connection errors
due to authentication problem. If the connection fails because of an
authentication error that's probably because the account isn't
registered yet on the server. So we try to register it.
If it fails because any other reason we unset the register flag so futur
connection attempts won't try to register until we got a new
authentication error. This should properly handle the "XO having to use
different jabber servers" use case."""
if status == self._conn_status:
return
if status == CONNECTION_STATUS_DISCONNECTED:
if reason == CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED and \
not self._account['register']:
_logger.debug(
'Authentication failed. Trying to register the account')
self._account['register'] = True
self._stop()
self._init_connection()
return
else:
self._account['register'] = False
TelepathyPlugin._handle_connection_status_change(self, status, reason)
def _publish_channel_cb(self, channel):
TelepathyPlugin._publish_channel_cb(self, channel)
publish_handles, local_pending, remote_pending = \
self._publish_channel[CHANNEL_INTERFACE_GROUP].GetAllMembers()
local_pending = self._filter_trusted_server(local_pending)
if local_pending:
# accept pending subscriptions
# FIXME: do this async
self._publish_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
local_pending, '')
self._add_not_subscribed_to_subscribe_channel()
def _subscribe_channel_cb(self, channel):
TelepathyPlugin._subscribe_channel_cb(self, channel)
self._add_not_subscribed_to_subscribe_channel()
def _add_not_subscribed_to_subscribe_channel(self):
if self._publish_channel is None or self._subscribe_channel is None:
return
publish_handles, local_pending, remote_pending = \
self._publish_channel[CHANNEL_INTERFACE_GROUP].GetAllMembers()
# request subscriptions from people subscribed to us if we're
# not subscribed to them
not_subscribed = set(publish_handles)
not_subscribed -= self._subscribe_members
not_subscribed = self._filter_trusted_server(not_subscribed)
if not_subscribed:
self._subscribe_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
not_subscribed, '')
def sync_friends(self, keys):
if self._friends_channel is None or self._subscribe_channel is None:
# not ready yet
return
client = gconf.client_get_default()
server = client.get_string("/desktop/sugar/collaboration/jabber_server")
friends_handles = set()
friends = set()
for key in keys:
identity = psutils.pubkey_to_keyid(key)
# this assumes that all our friends are on the same server as us
jid = '%s@%s' % (identity, server)
friends.add(jid)
def error_syncing_friends(e):
_logger.debug('error syncing friends: %r' % e)
def friends_group_synced():
_logger.debug('friends group synced')
def friends_subscribed():
_logger.debug('friends subscribed')
def got_friends_handles(handles):
friends_handles.update(handles)
# subscribe friends
self._subscribe_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
friends_handles, "New friend",
reply_handler=friends_subscribed,
error_handler=error_syncing_friends)
# add friends to the "Friends" group
self._friends_channel[CHANNEL_INTERFACE_GROUP].AddMembers(
friends_handles, "New friend",
reply_handler=friends_group_synced,
error_handler=error_syncing_friends)
self._conn[CONN_INTERFACE].RequestHandles(
HANDLE_TYPE_CONTACT, friends,
reply_handler=got_friends_handles,
error_handler=error_syncing_friends)
def _handle_is_channel_specific(self, handle):
# FIXME: This is crack. Really. Please kids, dont't do this at home.
# As we don't have a proper TP API to test if a handle is channel
# specific or not we use this cracky heuristic:
# "Is the jid contain a '/' after the '@'?".
# This is horribly protocol specific but should, hopefully, do the
# job.
jid = self._conn.InspectHandles(1, [handle])[0]
if _MUC_JID_RE.match(jid) is None:
_logger.debug('%s (%d) is not channel specific' % (jid, handle))
return False
else:
_logger.debug('%s (%d) is channel specific' % (jid, handle))
return True
|