from jabber.objects.si_filetransfer import SI_FILETRANSFER_NS from jabber.idle_loop import IdleLoopTimer from PIL import Image #@UnresolvedImport class JabberException(Exception): pass show_types = (None, u"away", u"xa", u"dnd", u"chat") available_show_types = (None, u"chat", u'normal') status_states = enum(["Online", "Away", "Extended Away", "Do Not Disturb", "Free For Chat"]) features_supported = ['http://jabber.org/protocol/disco#info', BYTESTREAMS_NS, SI_NS, SI_FILETRANSFER_NS] #### pyxmpp overrides #### import pyxmpp.jabber.vcard as vcard vcard.VCard.components.update({"FN": (vcard.VCardString,"optional"), "N" : (vcard.VCardName, "optional")}) import pyxmpp.streambase as streambase
''' Information specific to creating accounts on the different protocols. ''' from util.primitives.mapping import ostorage as oS, Storage as S, dictadd, odict from util.primitives.structures import enum, oset as set from util.introspect import import_function from threading import RLock jcrypt_opts = enum('Use TLS if Possible', 'Require TLS', 'Force SSL', 'No Encryption') jabber_details = [ {'type':'enum', 'store':'encryption', 'elements':jcrypt_opts}, {'type':'bool', 'store':'ignore_ssl_warnings', 'label':'Ignore SSL Warnings'}, {'type':'bool', 'store':'allow_plaintext', 'label':'Allow Plaintext Login'}, ] jabber_defaults = {
class DigsbyProtocol(JabberClient, StateMixin): states = enum('Disconnected', 'Authenticating', 'Connected') #layering violations from threadstream.py do_ssl = False connect_killed = False def __init__(self, username, password, server, resource="Digsby"): host, port = server alphanum = string.letters + string.digits resource = resource + "." + "".join(random.choice(alphanum) for _x in xrange(6)) jid = JID(username, "digsby.org", resource) if isinstance(username, unicode): username = username.encode('utf8') if isinstance(password, unicode): password = sha256(password.encode('utf-8')).digest() jkwargs = dict(jid = jid, password = password, keepalive = 45) if host: jkwargs.update(server = host) if port: jkwargs.update(port = port) jkwargs.update(auth_methods = ("sasl:DIGSBY-SHA256-RSA-CERT-AES",)) JabberClient.__init__(self, **jkwargs) StateMixin.__init__(self) self.stream_class = ThreadStream @callsback def Connect(self, callback=None): self.change_state(self.Statuses.CONNECTING) self.connect_callback = callback def conn_attmpt_failed(): log.debug('conn_attmpt_failed') #nowhere else to go, + report conn fail self.set_offline(self.Reasons.CONN_FAIL) callback.error() self.connect_attempt_failed = conn_attmpt_failed try: self.connect() except Exception as _e: print_exc() self.connect_attempt_failed() else: self.connect_attempt_succeeded() # Go find the load balancer; if you cannot, call on_fail #self.get_login_servers(l.username) def connect_attempt_succeeded(self): log.debug('connect_attempt_succeeded') with self.lock: self.idle_loop = Delegate() self.idle_loop += self.idle self.idle_looper = jabber.IdleLoopTimer(1, self.idle_loop) def connect_attempt_failed(self): raise AssertionError('connection attempt cannot fail before it is attempted') def connected(self): with self.lock: if self.state == self.Statuses.CONNECTING: self.change_state(self.Statuses.AUTHENTICATING) def disconnected(self, want_try_again = False): log.debug('disconnected 1') with self.lock: log.debug('disconnected 2') if not want_try_again and not self.want_try_again: log.debug('disconnected 3') # assert False self.change_state(self.Statuses.OFFLINE) JabberClient.disconnected(self) def _get_stream(self): return getattr(self, '_stream', None) def _set_stream(self, stream): new = stream if new is None: old = self.stream if old is not None: netcall(old.close) for attr in ('process_stream_error', 'state_change', 'owner'): setattr(old, attr, Null) self._stream = new stream = property(_get_stream, _set_stream) # def auth_failed(self, reason=''): # if reason: # self._auth_error_msg = reason # self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) # self.Disconnect() def auth_failed(self, reason=''): if reason in ('bad-auth', 'not-authorized'): self._auth_error_msg = reason self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) elif reason: self.error_txt = reason self.fatal_error() @callsback def _reconnect(self, invisible = False, do_conn_fail = True, callback=None): log.info('jabber _reconnect!') getattr(getattr(self, 'idle_looper', None), 'stop', lambda: None)() #grab next set of connection opts opts = self._get_next_connection_options() if opts is None: return self.fatal_error() self.change_state(self.Statuses.CONNECTING) self.server, self.port = opts.pop('server') #collect $200 threaded(lambda: JabberProtocol.Connect(self, invisible = invisible, do_conn_fail = do_conn_fail, callback=callback))() def fatal_error(self): if getattr(self.stream, '_had_host_mismatch', False) and not self.offline_reason: log.error('there was a host mismatch, interpreting it as auth error...') self.setnotifyif('offline_reason', self.Reasons.BAD_PASSWORD) # technically it's a bad username, but auth error regardless reason = self.offline_reason or self.Reasons.CONN_LOST log.info('Out of connection options. Changing to %r', reason) self.set_offline(reason) return False def connect(self): """Connect to the server and set up the stream. Set `self.stream` and notify `self.state_changed` when connection succeeds. Additionally, initialize Disco items and info of the client. """ JabberClient.connect(self, register=False) def stop_idle_looper(self): idle_looper, self.idle_looper = getattr(self, 'idle_looper', None), None if idle_looper is not None: idle_looper.stop() del self.idle_looper def stop_timer_loops(self): self.stop_idle_looper() def Disconnect(self): netcall(self._Disconnect) def _Disconnect(self): log.debug('logging out %r', self.want_try_again) with self.lock: pres = Presence(stanza_type="unavailable", status='Logged Out') try: self.stream.send(pres) except AttributeError: pass self.connect_killed = True self.disconnect() try: self.stop_timer_loops() except AttributeError: print_exc() if getattr(self, 'idle_loop', None) is not None: del self.idle_loop[:] if self.interface_providers is not None: del self.interface_providers[:] self.interface_providers = None if self.stream is not None: self.stream = None log.debug('1logged out %r', self.want_try_again) self.offline_reason = None # self.disconnected() log.debug('1logged out %r', self.want_try_again) common.protocol.Disconnect(self) def session_started(self): ''' Called when the IM session is successfully started (after all the neccessery negotiations, authentication and authorizasion). ''' with self.lock: self.idle_looper.start() log.info('session started') s = self.stream newstate = self.Statuses.AUTHORIZED #when this is true, don't try to start a new connection on conn_lost self.have_connected = True # set up handlers for supported <iq/> queries s.set_iq_get_handler("query", "jabber:iq:version", self.get_version) # set up handlers for <presence/> stanzas # do(s.set_presence_handler(name, func) for name, func in [ # (None, self.buddies.update_presence), # ('unavailable', self.buddies.update_presence), # ('available', self.buddies.update_presence), # ('subscribe', self.subscription_requested), # ('subscribed', self.presence_control), # ('unsubscribe', self.presence_control), # ('unsubscribed', self.presence_control), # ]) self.session_started_notify(s) self.connect_callback.success() self.change_state(newstate) log.info('session started done') def session_started_notify(self, s): ''' whatever needs to be done after most setup and before the callback/state change happens. usually that will be plugins doing their own setup. allows notify to be overridden in subclass ''' hooks.notify('digsby.jabber.session_started', self, s) def authorized(self): log.info('authorized1') JabberClient.authorized(self) # required! log.info('authorized2') def get_version(self,iq): """Handler for jabber:iq:version queries. jabber:iq:version queries are not supported directly by PyXMPP, so the XML node is accessed directly through the libxml2 API. This should be used very carefully!""" iq = iq.make_result_response() q = iq.new_query("jabber:iq:version") q.newTextChild( q.ns(), "name", "Digsby Import Client" ) # q.newTextChild( q.ns(), "version", ('%s %s' % (sys.REVISION, sys.TAG)).strip()) # strip because sometimes TAG is '' if not self.hide_os: import platform platform_string = platform.platform() # for some reason, on my XP box, platform.platform() contains both # the platform AND release in platform.platform(). On Ubuntu, OS X, # and I believe older versions of Windows, this does not happen, # so we need to add the release in all other cases. if platform_string.find("XP") == -1: platform_string += " " + platform.release() q.newTextChild( q.ns(), "os", platform_string ) self.send(iq) return True # presense start def presence_push(self): pres = Presence() self.send_presence(pres) def presence_control(self,*_a, **_k): ''' Handle subscription control <presence/> stanzas -- acknowledge them. ''' return True # presense end @callsback def send_cb(self, query, timeout_duration=None, callback=None): ''' Given a callback object with callable attributes success, error, and timeout, sends out a query with response handlers set. ''' def free_stanza(st): stream = self.get_stream() with stream.lock: if stream.doc_out is not None: st.free() else: if st._error: st._error.xmlnode = None st.xmlnode = None def my_super_callback_success(stanza): stanza.handler_frees = True if not isinstance(callback.success, list): try: log.info("what is this?, callback was %r", funcinfo(callback.success)) except Exception: log.error('bad callback.success %r', callback.success) return free_stanza(stanza) if len(callback.success) == 0: return free_stanza(stanza) try: f = callback.success[0].cb _call_free = CallLater(lambda:free_stanza(stanza)) def my_hyper_callback_success(st): try: f(st) except Exception: log.error('error processing %r', f) print_exc() finally: _call_free() callback.success[0].cb = my_hyper_callback_success callany(callback.success, stanza) except Exception: print_exc() log.error('failed to set up success stanza.free for %r, %r', callback, stanza) def my_super_callback_error(stanza): stanza.handler_frees = True if not isinstance(callback.error, list): try: log.info("what is this?, callback was %r", funcinfo(callback.error)) except Exception: log.error('bad callback.error %r', callback.error) return free_stanza(stanza) if len(callback.error) == 0: return free_stanza(stanza) try: f = callback.error[0].cb _call_free = CallLater(lambda:free_stanza(stanza)) def my_hyper_callback_error(st): try: f(st) except Exception: log.error('error processing %r', f) print_exc() finally: _call_free() callback.error[0].cb = my_hyper_callback_error callany(callback.error, stanza) except Exception: print_exc() log.error('failed to set up error stanza.free for %r, %r', callback, stanza) def my_super_callback_timeout(*_a): ''' consume the arguments, they are from ExpiringDictionary and cause problems with the number of arguments taken by timeout function, which in this case is expected to be 0, since we can store the context in it's closure and hardly anything even uses timeouts. ''' return callback.timeout() s = self.get_stream() if s is not None: try: if timeout_duration is not None: self.stream.set_response_handlers(query, my_super_callback_success, my_super_callback_error, my_super_callback_timeout, timeout = timeout_duration) else: self.stream.set_response_handlers(query, my_super_callback_success, my_super_callback_error, my_super_callback_timeout) except Exception as _e: print_exc() log.critical("couln't set stream handlers") return callany(callback.error) try: self.stream.send(query) except Exception as _e: print_exc() log.critical("couln't send query") try: return callany(callback.error) except Exception: log.critical("couln't call callany(callback.error) %r", callback) def send_presence(self, pres): assert isinstance(pres, Presence) self.send(pres) def send_iq(self, iq): assert isinstance(iq, Iq) self.send(iq) def send(self, stanza): s = self.get_stream() if s is not None: try: self.stream.send(stanza) except Exception as _e: print_exc() log.critical("couln't send stanza") def idle(self): try: JabberClient.idle(self) if not self.stream.socket or self.stream.eof: raise AssertionError, "if the stream is dead or gone, we can't really send a keep-alive" except Exception: self.stop_timer_loops() else: self.cache.tick() def stream_error(self, err): if err.get_condition().name == "pwchanged": self.change_reason(self.Reasons.BAD_PASSWORD) self.profile.signoff(kicked=True) elif err.get_condition().name == "conflict": self.change_reason(self.Reasons.OTHER_USER) else: self.change_reason(self.Reasons.CONN_LOST) JabberClient.stream_error(self, err) class Statuses(jabber.protocol.Statuses): SYNC_PREFS = _('Synchronizing Preferences...') #LOAD_SKIN = _('Loading Skin...') AUTHORIZED = _('Synchronizing...') @callsback def get_blob_raw(self, elem_name, tstamp='0', callback=None): try: blob = blobs.name_to_obj[elem_name](tstamp) blob._data = None iq = blob.make_get(self) except Exception: blob = blobs.name_to_obj[elem_name]('0') blob._data = None iq = blob.make_get(self) self.send_cb(iq, callback = callback) @callsback def get_accounts(self, callback=None): iq = digsby.accounts.Accounts().make_get(self) self.send_cb(iq, callback = callback)