1""" 2 Slixmpp: The Slick XMPP Library 3 Copyright (C) 2011 Nathanael C. Fritz 4 This file is part of Slixmpp. 5 6 See the file LICENSE for copying permission. 7""" 8 9import uuid 10import logging 11import hashlib 12 13from slixmpp.jid import JID 14from slixmpp.exceptions import IqError, IqTimeout 15from slixmpp.stanza import Iq, StreamFeatures 16from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin 17from slixmpp.plugins import BasePlugin 18from slixmpp.plugins.xep_0078 import stanza 19 20 21log = logging.getLogger(__name__) 22 23 24class XEP_0078(BasePlugin): 25 26 """ 27 XEP-0078 NON-SASL Authentication 28 29 This XEP is OBSOLETE in favor of using SASL, so DO NOT use this plugin 30 unless you are forced to use an old XMPP server implementation. 31 """ 32 33 name = 'xep_0078' 34 description = 'XEP-0078: Non-SASL Authentication' 35 dependencies = set() 36 stanza = stanza 37 default_config = { 38 'order': 15 39 } 40 41 def plugin_init(self): 42 self.xmpp.register_feature('auth', 43 self._handle_auth, 44 restart=False, 45 order=self.order) 46 47 self.xmpp.add_event_handler('legacy_protocol', 48 self._handle_legacy_protocol) 49 50 register_stanza_plugin(Iq, stanza.IqAuth) 51 register_stanza_plugin(StreamFeatures, stanza.AuthFeature) 52 53 def plugin_end(self): 54 self.xmpp.del_event_handler('legacy_protocol', 55 self._handle_legacy_protocol) 56 self.xmpp.unregister_feature('auth', self.order) 57 58 def _handle_auth(self, features): 59 # If we can or have already authenticated with SASL, do nothing. 60 if 'mechanisms' in features['features']: 61 return False 62 return self.authenticate() 63 64 def _handle_legacy_protocol(self, event): 65 self.authenticate() 66 67 def authenticate(self): 68 if self.xmpp.authenticated: 69 return False 70 71 log.debug("Starting jabber:iq:auth Authentication") 72 73 # Step 1: Request the auth form 74 iq = self.xmpp.Iq() 75 iq['type'] = 'get' 76 iq['to'] = self.xmpp.requested_jid.host 77 iq['auth']['username'] = self.xmpp.requested_jid.user 78 79 try: 80 resp = iq.send() 81 except IqError as err: 82 log.info("Authentication failed: %s", err.iq['error']['condition']) 83 self.xmpp.event('failed_auth') 84 self.xmpp.disconnect() 85 return True 86 except IqTimeout: 87 log.info("Authentication failed: %s", 'timeout') 88 self.xmpp.event('failed_auth') 89 self.xmpp.disconnect() 90 return True 91 92 # Step 2: Fill out auth form for either password or digest auth 93 iq = self.xmpp.Iq() 94 iq['type'] = 'set' 95 iq['auth']['username'] = self.xmpp.requested_jid.user 96 97 # A resource is required, so create a random one if necessary 98 resource = self.xmpp.requested_jid.resource 99 if not resource: 100 resource = str(uuid.uuid4()) 101 102 iq['auth']['resource'] = resource 103 104 if 'digest' in resp['auth']['fields']: 105 log.debug('Authenticating via jabber:iq:auth Digest') 106 stream_id = bytes(self.xmpp.stream_id, encoding='utf-8') 107 password = bytes(self.xmpp.password, encoding='utf-8') 108 109 digest = hashlib.sha1(b'%s%s' % (stream_id, password)).hexdigest() 110 iq['auth']['digest'] = digest 111 else: 112 log.warning('Authenticating via jabber:iq:auth Plain.') 113 iq['auth']['password'] = self.xmpp.password 114 115 # Step 3: Send credentials 116 try: 117 result = iq.send() 118 except IqError as err: 119 log.info("Authentication failed") 120 self.xmpp.event("failed_auth") 121 self.xmpp.disconnect() 122 except IqTimeout: 123 log.info("Authentication failed") 124 self.xmpp.event("failed_auth") 125 self.xmpp.disconnect() 126 127 self.xmpp.features.add('auth') 128 129 self.xmpp.authenticated = True 130 131 self.xmpp.boundjid = JID(self.xmpp.requested_jid) 132 self.xmpp.boundjid.resource = resource 133 self.xmpp.event('session_bind', self.xmpp.boundjid) 134 135 log.debug("Established Session") 136 self.xmpp.sessionstarted = True 137 self.xmpp.event('session_start') 138 139 return True 140