Changeset 9897
- Timestamp:
- 07/08/08 01:04:10 (5 months ago)
- Location:
- branches/bosh_support/src/common/xmpp
- Files:
-
- 7 modified
-
auth_nb.py (modified) (1 diff)
-
bosh.py (modified) (4 diffs)
-
client_nb.py (modified) (9 diffs)
-
client.py (modified) (3 diffs)
-
dispatcher_nb.py (modified) (12 diffs)
-
simplexml.py (modified) (3 diffs)
-
transports_nb.py (modified) (26 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/bosh_support/src/common/xmpp/auth_nb.py
r9877 r9897 174 174 log.info('Successfully authenticated with remote server.') 175 175 handlers=self._owner.Dispatcher.dumpHandlers() 176 print '6' * 79177 print handlers178 print '6' * 79179 176 self._owner.Dispatcher.PlugOut() 180 dispatcher_nb.Dispatcher().PlugIn(self._owner )177 dispatcher_nb.Dispatcher().PlugIn(self._owner, after_SASL=True) 181 178 self._owner.Dispatcher.restoreHandlers(handlers) 182 179 self._owner.User = self.username -
branches/bosh_support/src/common/xmpp/bosh.py
r9877 r9897 1 1 2 import protocol, simplexml,locale, random, dispatcher_nb2 import protocol, locale, random, dispatcher_nb 3 3 from client_nb import NBCommonClient 4 import transports_nb 4 5 import logging 6 from simplexml import Node 5 7 log = logging.getLogger('gajim.c.x.bosh') 6 8 … … 8 10 class BOSHClient(NBCommonClient): 9 11 ''' 10 Client class implementing BOSH. 12 Client class implementing BOSH. Extends common XMPP 11 13 ''' 12 def __init__(self, *args, **kw):14 def __init__(self, domain, idlequeue, caller=None): 13 15 '''Preceeds constructor of NBCommonClient and sets some of values that will 14 16 be used as attributes in <body> tag''' 15 self.Namespace = protocol.NS_HTTP_BIND16 # BOSH parameters should be given via Advanced Configuration Editor17 self.bosh_xml_lang = None18 self.bosh_hold = 119 self.bosh_wait=6020 self.bosh_rid=None21 17 self.bosh_sid=None 22 23 self.bosh_httpversion = 'HTTP/1.1'24 NBCommonClient.__init__(self, *args, **kw)25 26 27 def connect(self, *args, **kw):28 29 30 if locale.getdefaultlocale()[0]:31 self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0]32 18 33 19 # with 50-bit random initial rid, session would have to go up … … 37 23 r.seed() 38 24 self.bosh_rid = r.getrandbits(50) 39 40 proxy = kw['proxy'] 41 #self.bosh_protocol, self.bosh_host, self.bosh_uri = transports_nb.urisplit(proxy['host']) 42 self.bosh_port = proxy['port'] 25 self.bosh_sid = None 26 27 if locale.getdefaultlocale()[0]: 28 self.bosh_xml_lang = locale.getdefaultlocale()[0].split('_')[0] 29 else: 30 self.bosh_xml_lang = 'en' 31 32 self.http_version = 'HTTP/1.1' 33 self.bosh_to = domain 34 35 #self.Namespace = protocol.NS_HTTP_BIND 36 #self.defaultNamespace = self.Namespace 37 self.bosh_session_on = False 38 39 NBCommonClient.__init__(self, domain, idlequeue, caller) 40 41 42 43 def connect(self, on_connect, on_connect_failure, proxy, hostname=None, port=5222, 44 on_proxy_failure=None, secure=None): 45 ''' 46 Open XMPP connection (open XML streams in both directions). 47 :param hostname: hostname of XMPP server from SRV request 48 :param port: port number of XMPP server 49 :param on_connect: called after stream is successfully opened 50 :param on_connect_failure: called when error occures during connection 51 :param on_proxy_failure: called if error occurres during TCP connection to 52 proxy server or during connection to the proxy 53 :param proxy: dictionary with bosh-related paramters. It should contain at 54 least values for keys 'host' and 'port' - connection details for proxy 55 server and optionally keys 'user' and 'pass' as proxy credentials 56 :param secure: if 57 ''' 58 NBCommonClient.connect(self, on_connect, on_connect_failure, hostname, port, 59 on_proxy_failure, proxy, secure) 60 61 if hostname: 62 self.route_host = hostname 63 else: 64 self.route_host = self.Server 65 66 assert(proxy.has_key('type')) 67 assert(proxy['type']=='bosh') 68 43 69 self.bosh_wait = proxy['bosh_wait'] 44 70 self.bosh_hold = proxy['bosh_hold'] 45 self.bosh_to = proxy['to'] 46 #self.bosh_ack = proxy['bosh_ack'] 47 #self.bosh_secure = proxy['bosh_secure'] 48 NBCommonClient.connect(self, *args, **kw) 49 50 def send(self, stanza, now = False): 51 (id, stanza_to_send) = self.Dispatcher.assign_id(stanza) 52 53 self.Connection.send( 54 self.boshify_stanza(stanza_to_send), 55 now = now) 56 return id 57 58 def get_bodytag(self): 59 # this should be called not until after session creation response so sid has 60 # to be initialized. 61 assert(self.sid is not None) 62 self.rid = self.rid+1 63 return protocol.BOSHBody( 64 attrs={ 'rid': str(self.bosh_rid), 65 'sid': self.bosh_sid}) 66 67 68 def get_initial_bodytag(self): 69 return protocol.BOSHBody( 70 attrs={'content': 'text/xml; charset=utf-8', 71 'hold': str(self.bosh_hold), 72 'to': self.bosh_to, 73 'wait': str(self.bosh_wait), 74 'rid': str(self.bosh_rid), 75 'xmpp:version': '1.0', 76 'xmlns:xmpp': 'urn:xmpp:xbosh'} 77 ) 78 79 def get_closing_bodytag(self): 80 closing_bodytag = self.get_bodytag() 81 closing_bodytag.setAttr('type', 'terminate') 82 return closing_bodytag 83 84 85 def boshify_stanza(self, stanza): 86 ''' wraps stanza by body tag or modifies message entirely (in case of stream 87 opening and closing''' 88 log.info('boshify_staza - type is: %s' % type(stanza)) 89 if isinstance(stanza, simplexml.Node): 90 tag = self.get_bodytag() 91 return tag.setPayload(stanza) 92 else: 93 # only stream initialization and stream terminatoion are not Nodes 94 if stanza.startswith(dispatcher_nb.XML_DECLARATION): 95 # stream init 96 return self.get_initial_bodytag() 71 self.bosh_host = proxy['host'] 72 self.bosh_port = proxy['port'] 73 self.bosh_content = proxy['bosh_content'] 74 75 # _on_tcp_failure is callback for errors which occur during name resolving or 76 # TCP connecting. 77 self._on_tcp_failure = self.on_proxy_failure 78 79 80 81 # in BOSH, client connects to Connection Manager instead of directly to 82 # XMPP server ((hostname, port)). If HTTP Proxy is specified, client connects 83 # to HTTP proxy and Connection Manager is specified at URI and Host header 84 # in HTTP message 85 86 # tcp_host, tcp_port is hostname and port for socket connection - Connection 87 # Manager or HTTP proxy 88 if proxy.has_key('proxy_host') and proxy['proxy_host'] and \ 89 proxy.has_key('proxy_port') and proxy['proxy_port']: 90 91 tcp_host=proxy['proxy_host'] 92 tcp_port=proxy['proxy_port'] 93 94 # user and password for HTTP proxy 95 if proxy.has_key('user') and proxy['user'] and \ 96 proxy.has_key('pass') and proxy['pass']: 97 98 proxy_creds=(proxy['user'],proxy['pass']) 97 99 else: 98 # should be stream closing 99 assert(stanza == dispatcher_nb.STREAM_TERMINATOR) 100 return self.get_closing_bodytag() 101 102 100 proxy_creds=(None, None) 101 102 else: 103 tcp_host = transports_nb.urisplit(proxy['host'])[1] 104 tcp_port=proxy['port'] 105 106 if tcp_host is None: 107 self._on_connect_failure("Invalid BOSH URI") 108 return 109 110 self.socket = self.get_socket() 111 112 self._resolve_hostname( 113 hostname=tcp_host, 114 port=tcp_port, 115 on_success=self._try_next_ip, 116 on_failure=self._on_tcp_failure) 103 117 104 118 def _on_stream_start(self): 105 119 ''' 106 Called after XMPP stream is opened. In BOSH, TLS is negotiated elsewhere107 so success callback can be invoked.120 Called after XMPP stream is opened. In BOSH, TLS is negotiated on socket 121 connect so success callback can be invoked after TCP connect. 108 122 (authentication is started from auth() method) 109 123 ''' … … 111 125 if self.connected == 'tcp': 112 126 self._on_connect() 127 128 def get_socket(self): 129 tmp = transports_nb.NonBlockingHTTP( 130 raise_event=self.raise_event, 131 on_disconnect=self.on_http_disconnect, 132 http_uri = self.bosh_host, 133 http_port = self.bosh_port, 134 http_version = self.http_version 135 ) 136 tmp.PlugIn(self) 137 return tmp 138 139 def on_http_disconnect(self): 140 log.info('HTTP socket disconnected') 141 #import traceback 142 #traceback.print_stack() 143 if self.bosh_session_on: 144 self.socket.connect( 145 conn_5tuple=self.current_ip, 146 on_connect=self.on_http_reconnect, 147 on_connect_failure=self.on_disconnect) 148 else: 149 self.on_disconnect() 150 151 def on_http_reconnect(self): 152 self.socket._plug_idle() 153 log.info('Connected to BOSH CM again') 154 pass 155 156 157 def on_http_reconnect_fail(self): 158 log.error('Error when reconnecting to BOSH CM') 159 self.on_disconnect() 160 161 def send(self, stanza, now = False): 162 (id, stanza_to_send) = self.Dispatcher.assign_id(stanza) 163 164 self.socket.send( 165 self.boshify_stanza(stanza_to_send), 166 now = now) 167 return id 168 169 def get_rid(self): 170 # does this need a lock??" 171 self.bosh_rid = self.bosh_rid + 1 172 return str(self.bosh_rid) 173 174 def get_bodytag(self): 175 # this should be called not until after session creation response so sid has 176 # to be initialized. 177 assert(hasattr(self, 'bosh_sid')) 178 return protocol.BOSHBody( 179 attrs={ 'rid': self.get_rid(), 180 'sid': self.bosh_sid}) 181 182 def get_initial_bodytag(self, after_SASL=False): 183 tag = protocol.BOSHBody( 184 attrs={'content': self.bosh_content, 185 'hold': str(self.bosh_hold), 186 'route': '%s:%s' % (self.route_host, self.Port), 187 'to': self.bosh_to, 188 'wait': str(self.bosh_wait), 189 'rid': self.get_rid(), 190 'xml:lang': self.bosh_xml_lang, 191 'xmpp:version': '1.0', 192 'ver': '1.6', 193 'xmlns:xmpp': 'urn:xmpp:xbosh'}) 194 if after_SASL: 195 tag.delAttr('content') 196 tag.delAttr('hold') 197 tag.delAttr('route') 198 tag.delAttr('wait') 199 tag.delAttr('ver') 200 # xmpp:restart attribute is essential for stream restart request 201 tag.setAttr('xmpp:restart','true') 202 tag.setAttr('sid',self.bosh_sid) 203 204 return tag 205 206 207 def get_closing_bodytag(self): 208 closing_bodytag = self.get_bodytag() 209 closing_bodytag.setAttr('type', 'terminate') 210 return closing_bodytag 211 212 213 def boshify_stanza(self, stanza=None, body_attrs=None): 214 ''' wraps stanza by body tag with rid and sid ''' 215 #log.info('boshify_staza - type is: %s, stanza is %s' % (type(stanza), stanza)) 216 tag = self.get_bodytag() 217 tag.setPayload([stanza]) 218 return tag 219 220 221 def on_bodytag_attrs(self, body_attrs): 222 #log.info('on_bodytag_attrs: %s' % body_attrs) 223 if body_attrs.has_key('type'): 224 if body_attrs['type']=='terminated': 225 # BOSH session terminated 226 self.bosh_session_on = False 227 elif body_attrs['type']=='error': 228 # recoverable error 229 pass 230 if not self.bosh_sid: 231 # initial response - when bosh_sid is set 232 self.bosh_session_on = True 233 self.bosh_sid = body_attrs['sid'] 234 self.Dispatcher.Stream._document_attrs['id']=body_attrs['authid'] 235 -
branches/bosh_support/src/common/xmpp/client_nb.py
r9877 r9897 42 42 43 43 ''' 44 45 44 self.Namespace = protocol.NS_CLIENT 46 45 self.defaultNamespace = self.Namespace 46 47 47 self.idlequeue = idlequeue 48 self.defaultNamespace = self.Namespace49 48 self.disconnect_handlers = [] 50 49 … … 86 85 if self.__dict__.has_key('NonBlockingTLS'): 87 86 self.NonBlockingTLS.PlugOut() 88 if self.__dict__.has_key('NBHTTPP ROXYsocket'):87 if self.__dict__.has_key('NBHTTPProxySocket'): 89 88 self.NBHTTPPROXYsocket.PlugOut() 90 if self.__dict__.has_key('NBSOCKS5P ROXYsocket'):89 if self.__dict__.has_key('NBSOCKS5ProxySocket'): 91 90 self.NBSOCKS5PROXYsocket.PlugOut() 92 if self.__dict__.has_key('NonBlockingTcp'): 93 self.NonBlockingTcp.PlugOut() 91 if self.__dict__.has_key('NonBlockingTCP'): 92 self.NonBlockingTCP.PlugOut() 93 if self.__dict__.has_key('NonBlockingHTTP'): 94 self.NonBlockingHTTP.PlugOut() 94 95 95 96 … … 107 108 on_proxy_failure=None, proxy=None, secure=None): 108 109 ''' 109 Open XMPP connection (open streams in both directions).110 Open XMPP connection (open XML streams in both directions). 110 111 :param hostname: hostname of XMPP server from SRV request 111 112 :param port: port number of XMPP server … … 119 120 :param secure: 120 121 ''' 121 self.Port = port122 if hostname:123 xmpp_hostname = hostname124 else:125 xmpp_hostname = self.Server126 127 122 self.on_connect = on_connect 128 123 self.on_connect_failure=on_connect_failure … … 130 125 self._secure = secure 131 126 self.Connection = None 127 self.Port = port 128 129 130 131 132 133 def _resolve_hostname(self, hostname, port, on_success, on_failure): 134 ''' wrapper of getaddinfo call. FIXME: getaddinfo blocks''' 135 try: 136 self.ip_addresses = socket.getaddrinfo(hostname,port, 137 socket.AF_UNSPEC,socket.SOCK_STREAM) 138 except socket.gaierror, (errnum, errstr): 139 on_failure(err_message='Lookup failure for %s:%s - %s %s' % 140 (self.Server, self.Port, errnum, errstr)) 141 else: 142 on_success() 143 144 145 146 def _try_next_ip(self, err_message=None): 147 '''iterates over IP addresses from getaddinfo''' 148 if err_message: 149 log.debug('While looping over DNS A records: %s' % connect) 150 if self.ip_addresses == []: 151 self._on_tcp_failure(err_message='Run out of hosts for name %s:%s' % 152 (self.Server, self.Port)) 153 else: 154 self.current_ip = self.ip_addresses.pop(0) 155 self.socket.connect( 156 conn_5tuple=self.current_ip, 157 on_connect=lambda: self._xmpp_connect(socket_type='tcp'), 158 on_connect_failure=self._try_next_ip) 159 160 161 def incoming_stream_version(self): 162 ''' gets version of xml stream''' 163 if self.Dispatcher.Stream._document_attrs.has_key('version'): 164 return self.Dispatcher.Stream._document_attrs['version'] 165 else: 166 return None 167 168 def _xmpp_connect(self, socket_type): 169 self.connected = socket_type 170 self._xmpp_connect_machine() 171 172 173 def _xmpp_connect_machine(self, mode=None, data=None): 174 ''' 175 Finite automaton called after TCP connecting. Takes care of stream opening 176 and features tag handling. Calls _on_stream_start when stream is 177 started, and _on_connect_failure on failure. 178 ''' 179 #FIXME: use RegisterHandlerOnce instead of onreceive 180 log.info('========xmpp_connect_machine() >> mode: %s, data: %s' % (mode,str(data)[:20] )) 181 182 def on_next_receive(mode): 183 log.info('setting %s on next receive' % mode) 184 if mode is None: 185 self.onreceive(None) 186 else: 187 self.onreceive(lambda _data:self._xmpp_connect_machine(mode, _data)) 188 189 if not mode: 190 dispatcher_nb.Dispatcher().PlugIn(self) 191 on_next_receive('RECEIVE_DOCUMENT_ATTRIBUTES') 192 193 elif mode == 'FAILURE': 194 self._on_connect_failure(err_message='During XMPP connect: %s' % data) 195 196 elif mode == 'RECEIVE_DOCUMENT_ATTRIBUTES': 197 if data: 198 self.Dispatcher.ProcessNonBlocking(data) 199 if not hasattr(self, 'Dispatcher') or \ 200 self.Dispatcher.Stream._document_attrs is None: 201 self._xmpp_connect_machine( 202 mode='FAILURE', 203 data='Error on stream open') 204 if self.incoming_stream_version() == '1.0': 205 if not self.Dispatcher.Stream.features: 206 on_next_receive('RECEIVE_STREAM_FEATURES') 207 else: 208 log.info('got STREAM FEATURES in first read') 209 self._xmpp_connect_machine(mode='STREAM_STARTED') 210 211 else: 212 log.info('incoming stream version less than 1.0') 213 self._xmpp_connect_machine(mode='STREAM_STARTED') 214 215 elif mode == 'RECEIVE_STREAM_FEATURES': 216 if data: 217 # sometimes <features> are received together with document 218 # attributes and sometimes on next receive... 219 self.Dispatcher.ProcessNonBlocking(data) 220 if not self.Dispatcher.Stream.features: 221 self._xmpp_connect_machine( 222 mode='FAILURE', 223 data='Missing <features> in 1.0 stream') 224 else: 225 log.info('got STREAM FEATURES in second read') 226 self._xmpp_connect_machine(mode='STREAM_STARTED') 227 228 elif mode == 'STREAM_STARTED': 229 self._on_stream_start() 230 231 def _on_stream_start(self): 232 '''Called when stream is opened. To be overriden in derived classes.''' 233 234 def _on_connect_failure(self, retry=None, err_message=None): 235 self.connected = None 236 if err_message: 237 log.debug('While connecting: %s' % err_message) 238 if self.socket: 239 self.socket.disconnect() 240 self.on_connect_failure(retry) 241 242 def _on_connect(self): 243 self.onreceive(None) 244 self.on_connect(self, self.connected) 245 246 def raise_event(self, event_type, data): 247 log.info('raising event from transport: %s %s' % (event_type,data)) 248 if hasattr(self, 'Dispatcher'): 249 self.Dispatcher.Event('', event_type, data) 250 251 252 # moved from client.CommonClient: 253 def RegisterDisconnectHandler(self,handler): 254 """ Register handler that will be called on disconnect.""" 255 self.disconnect_handlers.append(handler) 256 257 def UnregisterDisconnectHandler(self,handler): 258 """ Unregister handler that is called on disconnect.""" 259 self.disconnect_handlers.remove(handler) 260 261 def DisconnectHandler(self): 262 """ Default disconnect handler. Just raises an IOError. 263 If you choosed to use this class in your production client, 264 override this method or at least unregister it. """ 265 raise IOError('Disconnected from server.') 266 267 def get_connect_type(self): 268 """ Returns connection state. F.e.: None / 'tls' / 'tcp+non_sasl' . """ 269 return self.connected 270 271 def get_peerhost(self): 272 ''' get the ip address of the account, from which is made connection 273 to the server , (e.g. me). 274 We will create listening socket on the same ip ''' 275 if hasattr(self, 'Connection'): 276 return self.Connection._sock.getsockname() 277 278 279 def auth(self, user, password, resource = '', sasl = 1, on_auth = None): 280 ''' Authenticate connnection and bind resource. If resource is not provided 281 random one or library name used. ''' 282 self._User, self._Password, self._Resource, self._sasl = user, password, resource, sasl 283 self.on_auth = on_auth 284 self._on_doc_attrs() 285 return 286 287 def _on_old_auth(self, res): 288 if res: 289 self.connected += '+old_auth' 290 self.on_auth(self, 'old_auth') 291 else: 292 self.on_auth(self, None) 293 294 def _on_doc_attrs(self): 295 if self._sasl: 296 auth_nb.SASL(self._User, self._Password, self._on_start_sasl).PlugIn(self) 297 if not self._sasl or self.SASL.startsasl == 'not-supported': 298 if not self._Resource: 299 self._Resource = 'xmpppy' 300 auth_nb.NonBlockingNonSASL(self._User, self._Password, self._Resource, self._on_old_auth).PlugIn(self) 301 return 302 self.onreceive(self._on_start_sasl) 303 self.SASL.auth() 304 return True 305 306 def _on_start_sasl(self, data=None): 307 if data: 308 self.Dispatcher.ProcessNonBlocking(data) 309 if not self.__dict__.has_key('SASL'): 310 # SASL is pluged out, possible disconnect 311 return 312 if self.SASL.startsasl == 'in-process': 313 return 314 self.onreceive(None) 315 if self.SASL.startsasl == 'failure': 316 # wrong user/pass, stop auth 317 self.connected = None 318 self._on_sasl_auth(None) 319 self.SASL.PlugOut() 320 elif self.SASL.startsasl == 'success': 321 auth_nb.NonBlockingBind().PlugIn(self) 322 self.onreceive(self._on_auth_bind) 323 return True 324 325 def _on_auth_bind(self, data): 326 if data: 327 self.Dispatcher.ProcessNonBlocking(data) 328
