upstream updates
This commit is contained in:
parent
5367d4a4fb
commit
65f67a7a04
|
@ -26,6 +26,6 @@ and use only methods for access all values you should not have any problems.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import simplexml,protocol,debug,auth,transports,roster,dispatcher,features,browser,filetransfer
|
import simplexml,protocol,debug,auth,transports,roster,dispatcher,features,browser,filetransfer,commands
|
||||||
from client import *
|
from client import *
|
||||||
from protocol import *
|
from protocol import *
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
|
|
||||||
# $Id: auth.py,v 1.33 2005/11/30 17:05:40 normanr Exp $
|
# $Id: auth.py,v 1.35 2006/01/18 19:26:43 normanr Exp $
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Provides library with all Non-SASL and SASL authentication mechanisms.
|
Provides library with all Non-SASL and SASL authentication mechanisms.
|
||||||
|
@ -259,3 +259,48 @@ class Bind(PlugIn):
|
||||||
else:
|
else:
|
||||||
self.DEBUG('Binding failed: timeout expired.','error')
|
self.DEBUG('Binding failed: timeout expired.','error')
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
class ComponentBind(PlugIn):
|
||||||
|
""" ComponentBind some JID to the current connection to allow router know of our location."""
|
||||||
|
def __init__(self):
|
||||||
|
PlugIn.__init__(self)
|
||||||
|
self.DBG_LINE='bind'
|
||||||
|
self.bound=None
|
||||||
|
self.needsUnregister=None
|
||||||
|
|
||||||
|
def plugin(self,owner):
|
||||||
|
""" Start resource binding, if allowed at this time. Used internally. """
|
||||||
|
if self._owner.Dispatcher.Stream.features:
|
||||||
|
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
|
||||||
|
except NodeProcessed: pass
|
||||||
|
else:
|
||||||
|
self._owner.RegisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||||
|
self.needsUnregister=1
|
||||||
|
|
||||||
|
def plugout(self):
|
||||||
|
""" Remove ComponentBind handler from owner's dispatcher. Used internally. """
|
||||||
|
if self.needsUnregister:
|
||||||
|
self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
|
||||||
|
|
||||||
|
def FeaturesHandler(self,conn,feats):
|
||||||
|
""" Determine if server supports resource binding and set some internal attributes accordingly. """
|
||||||
|
if not feats.getTag('bind',namespace=NS_BIND):
|
||||||
|
self.bound='failure'
|
||||||
|
self.DEBUG('Server does not requested binding.','error')
|
||||||
|
return
|
||||||
|
if feats.getTag('session',namespace=NS_SESSION): self.session=1
|
||||||
|
else: self.session=-1
|
||||||
|
self.bound=[]
|
||||||
|
|
||||||
|
def Bind(self,domain=None):
|
||||||
|
""" Perform binding. Use provided domain name (if not provided). """
|
||||||
|
while self.bound is None and self._owner.Process(1): pass
|
||||||
|
resp=self._owner.SendAndWaitForResponse(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
|
||||||
|
if resp and resp.getAttr('error'):
|
||||||
|
self.DEBUG('Binding failed: %s.'%resp.getAttr('error'),'error')
|
||||||
|
elif resp:
|
||||||
|
self.DEBUG('Successfully bound.','ok')
|
||||||
|
return 'ok'
|
||||||
|
else:
|
||||||
|
self.DEBUG('Binding failed: timeout expired.','error')
|
||||||
|
return ''
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
|
|
||||||
# $Id: client.py,v 1.35 2005/04/30 10:17:19 snakeru Exp $
|
# $Id: client.py,v 1.52 2006/01/02 19:40:55 normanr Exp $
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Provides PlugIn class functionality to develop extentions for xmpppy.
|
Provides PlugIn class functionality to develop extentions for xmpppy.
|
||||||
|
@ -31,6 +31,7 @@ Debug.Debug.colors['nodebuilder']=debug.color_brown
|
||||||
Debug.Debug.colors['client']=debug.color_cyan
|
Debug.Debug.colors['client']=debug.color_cyan
|
||||||
Debug.Debug.colors['component']=debug.color_cyan
|
Debug.Debug.colors['component']=debug.color_cyan
|
||||||
Debug.Debug.colors['dispatcher']=debug.color_green
|
Debug.Debug.colors['dispatcher']=debug.color_green
|
||||||
|
Debug.Debug.colors['browser']=debug.color_blue
|
||||||
Debug.Debug.colors['auth']=debug.color_yellow
|
Debug.Debug.colors['auth']=debug.color_yellow
|
||||||
Debug.Debug.colors['roster']=debug.color_magenta
|
Debug.Debug.colors['roster']=debug.color_magenta
|
||||||
Debug.Debug.colors['ibb']=debug.color_yellow
|
Debug.Debug.colors['ibb']=debug.color_yellow
|
||||||
|
@ -111,6 +112,7 @@ class CommonClient:
|
||||||
self._registered_name=None
|
self._registered_name=None
|
||||||
self.RegisterDisconnectHandler(self.DisconnectHandler)
|
self.RegisterDisconnectHandler(self.DisconnectHandler)
|
||||||
self.connected=''
|
self.connected=''
|
||||||
|
self._component=0
|
||||||
|
|
||||||
def RegisterDisconnectHandler(self,handler):
|
def RegisterDisconnectHandler(self,handler):
|
||||||
""" Register handler that will be called on disconnect."""
|
""" Register handler that will be called on disconnect."""
|
||||||
|
@ -147,6 +149,11 @@ class CommonClient:
|
||||||
""" Example of reconnection method. In fact, it can be used to batch connection and auth as well. """
|
""" Example of reconnection method. In fact, it can be used to batch connection and auth as well. """
|
||||||
handlerssave=self.Dispatcher.dumpHandlers()
|
handlerssave=self.Dispatcher.dumpHandlers()
|
||||||
self.Dispatcher.PlugOut()
|
self.Dispatcher.PlugOut()
|
||||||
|
if self.__dict__.has_key('NonSASL'): self.NonSASL.PlugOut()
|
||||||
|
if self.__dict__.has_key('SASL'): self.SASL.PlugOut()
|
||||||
|
if self.__dict__.has_key('TLS'): self.TLS.PlugOut()
|
||||||
|
if self.__dict__.has_key('HTTPPROXYsocket'): self.HTTPPROXYsocket.PlugOut()
|
||||||
|
if self.__dict__.has_key('TCPsocket'): self.TCPsocket.PlugOut()
|
||||||
if not self.connect(server=self._Server,proxy=self._Proxy): return
|
if not self.connect(server=self._Server,proxy=self._Proxy): return
|
||||||
if not self.auth(self._User,self._Password,self._Resource): return
|
if not self.auth(self._User,self._Password,self._Resource): return
|
||||||
self.Dispatcher.restoreHandlers(handlerssave)
|
self.Dispatcher.restoreHandlers(handlerssave)
|
||||||
|
@ -159,16 +166,20 @@ class CommonClient:
|
||||||
if hasattr(self, 'Connection'):
|
if hasattr(self, 'Connection'):
|
||||||
return self.Connection._sock.getsockname()
|
return self.Connection._sock.getsockname()
|
||||||
|
|
||||||
def connect(self,server=None,proxy=None, ssl=None):
|
def connect(self,server=None,proxy=None,ssl=None,use_srv=None):
|
||||||
""" Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream. """
|
""" Make a tcp/ip connection, protect it with tls/ssl if possible and start XMPP stream.
|
||||||
|
Returns None or 'tcp' or 'tls', depending on the result."""
|
||||||
if not server: server=(self.Server,self.Port)
|
if not server: server=(self.Server,self.Port)
|
||||||
if proxy: connected=transports.HTTPPROXYsocket(proxy,server).PlugIn(self)
|
if proxy: socket=transports.HTTPPROXYsocket(proxy,server,use_srv)
|
||||||
else: connected=transports.TCPsocket(server).PlugIn(self)
|
else: socket=transports.TCPsocket(server,use_srv)
|
||||||
if not connected: return
|
connected=socket.PlugIn(self)
|
||||||
|
if not connected:
|
||||||
|
socket.PlugOut()
|
||||||
|
return
|
||||||
self._Server,self._Proxy=server,proxy
|
self._Server,self._Proxy=server,proxy
|
||||||
self.connected='tcp'
|
self.connected='tcp'
|
||||||
if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
|
if (ssl is None and self.Connection.getPort() in (5223, 443)) or ssl:
|
||||||
try:
|
try: # FIXME. This should be done in transports.py
|
||||||
transports.TLS().PlugIn(self,now=1)
|
transports.TLS().PlugIn(self,now=1)
|
||||||
self.connected='ssl'
|
self.connected='ssl'
|
||||||
except socket.sslerror:
|
except socket.sslerror:
|
||||||
|
@ -182,16 +193,16 @@ class CommonClient:
|
||||||
|
|
||||||
class Client(CommonClient):
|
class Client(CommonClient):
|
||||||
""" Example client class, based on CommonClient. """
|
""" Example client class, based on CommonClient. """
|
||||||
def connect(self,server=None,proxy=None,secure=None):
|
def connect(self,server=None,proxy=None,secure=None,use_srv=True):
|
||||||
""" Connect to jabber server. If you want to specify different ip/port to connect to you can
|
""" Connect to jabber server. If you want to specify different ip/port to connect to you can
|
||||||
pass it as tuple as first parameter. If there is HTTP proxy between you and server -
|
pass it as tuple as first parameter. If there is HTTP proxy between you and server
|
||||||
specify it's address and credentials (if needed) in the second argument
|
specify it's address and credentials (if needed) in the second argument.
|
||||||
If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
|
If you want ssl/tls support to be discovered and enable automatically - leave third argument as None. (ssl will be autodetected only if port is 5223 or 443)
|
||||||
If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1
|
If you want to force SSL start (i.e. if port 5223 or 443 is remapped to some non-standard port) then set it to 1.
|
||||||
If you want to disable tls/ssl support completely, set it to 0
|
If you want to disable tls/ssl support completely, set it to 0.
|
||||||
Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
|
Example: connect(('192.168.5.5',5222),{'host':'proxy.my.net','port':8080,'user':'me','password':'secret'})
|
||||||
Returns '' (on no connection) or 'tcp' or 'tls', depending on the result."""
|
Returns '' or 'tcp' or 'tls', depending on the result."""
|
||||||
if not CommonClient.connect(self,server,proxy,secure) or secure<>None and not secure: return self.connected
|
if not CommonClient.connect(self,server,proxy,secure,use_srv) or secure<>None and not secure: return self.connected
|
||||||
transports.TLS().PlugIn(self)
|
transports.TLS().PlugIn(self)
|
||||||
if not self.Dispatcher.Stream._document_attrs.has_key('version') or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected
|
if not self.Dispatcher.Stream._document_attrs.has_key('version') or not self.Dispatcher.Stream._document_attrs['version']=='1.0': return self.connected
|
||||||
while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
|
while not self.Dispatcher.Stream.features and self.Process(): pass # If we get version 1.0 stream the features tag MUST BE presented
|
||||||
|
@ -247,19 +258,31 @@ class Client(CommonClient):
|
||||||
|
|
||||||
class Component(CommonClient):
|
class Component(CommonClient):
|
||||||
""" Component class. The only difference from CommonClient is ability to perform component authentication. """
|
""" Component class. The only difference from CommonClient is ability to perform component authentication. """
|
||||||
def __init__(self,server,port=5347,typ=None,debug=['always', 'nodebuilder']):
|
def __init__(self,server,port=5347,typ=None,debug=['always', 'nodebuilder'],domains=None,component=0):
|
||||||
""" Init function for Components.
|
""" Init function for Components.
|
||||||
As components use a different auth mechanism which includes the namespace of the component.
|
As components use a different auth mechanism which includes the namespace of the component.
|
||||||
Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
|
Jabberd1.4 and Ejabberd use the default namespace then for all client messages.
|
||||||
Jabberd2 uses jabber:client."""
|
Jabberd2 uses jabber:client.
|
||||||
|
'server' argument is a server name that you are connecting to (f.e. "localhost").
|
||||||
|
'port' can be specified if 'server' resolves to correct IP. If it is not then you'll need to specify IP
|
||||||
|
and port while calling "connect()"."""
|
||||||
CommonClient.__init__(self,server,port=port,debug=debug)
|
CommonClient.__init__(self,server,port=port,debug=debug)
|
||||||
self.typ=typ
|
self.typ=typ
|
||||||
|
self.component=component
|
||||||
|
if domains:
|
||||||
|
self.domains=domains
|
||||||
|
else:
|
||||||
|
self.domains=[server]
|
||||||
|
|
||||||
def connect(self,server=None,proxy=None):
|
def connect(self,server=None,proxy=None):
|
||||||
""" This will connect to the server, and if the features tag is found then set
|
""" This will connect to the server, and if the features tag is found then set
|
||||||
the namespace to be jabber:client as that is required for jabberd2"""
|
the namespace to be jabber:client as that is required for jabberd2.
|
||||||
|
'server' and 'proxy' arguments have the same meaning as in xmpp.Client.connect() """
|
||||||
|
if self.component:
|
||||||
|
self.Namespace=auth.NS_COMPONENT_1
|
||||||
|
self.Server=server[0]
|
||||||
CommonClient.connect(self,server=server,proxy=proxy)
|
CommonClient.connect(self,server=server,proxy=proxy)
|
||||||
if self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features != None:
|
if self.connected and (self.typ=='jabberd2' or not self.typ and self.Dispatcher.Stream.features != None):
|
||||||
self.defaultNamespace=auth.NS_CLIENT
|
self.defaultNamespace=auth.NS_CLIENT
|
||||||
self.Dispatcher.RegisterNamespace(self.defaultNamespace)
|
self.Dispatcher.RegisterNamespace(self.defaultNamespace)
|
||||||
self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq)
|
self.Dispatcher.RegisterProtocol('iq',dispatcher.Iq)
|
||||||
|
@ -267,7 +290,32 @@ class Component(CommonClient):
|
||||||
self.Dispatcher.RegisterProtocol('presence',dispatcher.Presence)
|
self.Dispatcher.RegisterProtocol('presence',dispatcher.Presence)
|
||||||
return self.connected
|
return self.connected
|
||||||
|
|
||||||
def auth(self,name,password,dup=None):
|
def auth(self,name,password,dup=None,sasl=0):
|
||||||
""" Authenticate component "name" with password "password"."""
|
""" Authenticate component "name" with password "password"."""
|
||||||
self._User,self._Password,self._Resource=name,password,''
|
self._User,self._Password,self._Resource=name,password,''
|
||||||
return auth.NonSASL(name,password,'').PlugIn(self)
|
try:
|
||||||
|
if self.component: sasl=1
|
||||||
|
if sasl: auth.SASL(name,password).PlugIn(self)
|
||||||
|
if not sasl or self.SASL.startsasl=='not-supported':
|
||||||
|
if auth.NonSASL(name,password,'').PlugIn(self):
|
||||||
|
self.connected+='+old_auth'
|
||||||
|
return 'old_auth'
|
||||||
|
return
|
||||||
|
self.SASL.auth()
|
||||||
|
while self.SASL.startsasl=='in-process' and self.Process(): pass
|
||||||
|
if self.SASL.startsasl=='success':
|
||||||
|
if self.component:
|
||||||
|
self._component=self.component
|
||||||
|
for domain in self.domains:
|
||||||
|
auth.ComponentBind().PlugIn(self)
|
||||||
|
while self.ComponentBind.bound is None: self.Process()
|
||||||
|
if (not self.ComponentBind.Bind(domain)):
|
||||||
|
self.ComponentBind.PlugOut()
|
||||||
|
return
|
||||||
|
self.ComponentBind.PlugOut()
|
||||||
|
self.connected+='+sasl'
|
||||||
|
return 'sasl'
|
||||||
|
else:
|
||||||
|
raise auth.NotAuthorized(self.SASL.startsasl)
|
||||||
|
except:
|
||||||
|
self.DEBUG(self.DBG,"Failed to authenticate %s"%name,'error')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
## $Id: commands.py,v 1.10 2005/10/07 23:17:09 normanr Exp $
|
## $Id: commands.py,v 1.11 2005/11/30 17:03:11 normanr Exp $
|
||||||
|
|
||||||
## Ad-Hoc Command manager
|
## Ad-Hoc Command manager
|
||||||
## Mike Albon (c) 5th January 2005
|
## Mike Albon (c) 5th January 2005
|
||||||
|
@ -125,7 +125,7 @@ class Commands(PlugIn):
|
||||||
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
conn.send(Error(request,ERR_ITEM_NOT_FOUND))
|
||||||
raise NodeProcessed
|
raise NodeProcessed
|
||||||
elif typ == 'info':
|
elif typ == 'info':
|
||||||
return {'ids':[],'features':[]}
|
return {'ids':[{'category':'automation','type':'command-list'}],'features':[]}
|
||||||
|
|
||||||
def addCommand(self,name,cmddisco,cmdexecute,jid=''):
|
def addCommand(self,name,cmddisco,cmdexecute,jid=''):
|
||||||
"""The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""
|
"""The method to call if adding a new command to the session, the requred parameters of cmddisco and cmdexecute are the methods to enable that command to be executed"""
|
||||||
|
@ -197,7 +197,7 @@ class Command_Handler_Prototype(PlugIn):
|
||||||
self.sessioncount = 0
|
self.sessioncount = 0
|
||||||
self.sessions = {}
|
self.sessions = {}
|
||||||
# Disco information for command list pre-formatted as a tuple
|
# Disco information for command list pre-formatted as a tuple
|
||||||
self.discoinfo = {'ids':[{'category':'automation','type':'command','name':self.description}],'features': self.discofeatures}
|
self.discoinfo = {'ids':[{'category':'automation','type':'command-node','name':self.description}],'features': self.discofeatures}
|
||||||
self._jid = jid
|
self._jid = jid
|
||||||
|
|
||||||
def plugin(self,owner):
|
def plugin(self,owner):
|
||||||
|
|
|
@ -40,16 +40,16 @@ in this code
|
||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import traceback
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
if os.environ.has_key('TERM'):
|
||||||
|
colors_enabled=True
|
||||||
colornames = ['none', 'black', 'red', 'green', 'brown', 'blue', 'magenta',
|
else:
|
||||||
'cyan', 'light_gray', 'dark_gray', 'bright_red', 'bright_green', 'yellow',
|
colors_enabled=False
|
||||||
'bright_blue', 'purple', 'bright_cyan', 'white']
|
|
||||||
|
|
||||||
color_none = chr(27) + "[0m"
|
color_none = chr(27) + "[0m"
|
||||||
color_black = chr(27) + "[30m"
|
color_black = chr(27) + "[30m"
|
||||||
|
@ -69,11 +69,7 @@ color_purple = chr(27) + "[35;1m"
|
||||||
color_bright_cyan = chr(27) + "[36;1m"
|
color_bright_cyan = chr(27) + "[36;1m"
|
||||||
color_white = chr(27) + "[37;1m"
|
color_white = chr(27) + "[37;1m"
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
for colorname in colornames:
|
|
||||||
name = 'color_' + colorname
|
|
||||||
mod = compile("%s = ''" % name, 'gajim', 'exec')
|
|
||||||
eval(mod)
|
|
||||||
"""
|
"""
|
||||||
Define your flags in yor modules like this:
|
Define your flags in yor modules like this:
|
||||||
|
|
||||||
|
@ -204,7 +200,7 @@ class Debug:
|
||||||
mod_name = ""
|
mod_name = ""
|
||||||
self.show('Debug created for %s%s' % (caller.f_code.co_filename,
|
self.show('Debug created for %s%s' % (caller.f_code.co_filename,
|
||||||
mod_name ))
|
mod_name ))
|
||||||
self.show(' flags defined: %s' % ' '.join( self.active ))
|
self.show(' flags defined: %s' % ','.join( self.active ))
|
||||||
|
|
||||||
if type(flag_show) in (type(''), type(None)):
|
if type(flag_show) in (type(''), type(None)):
|
||||||
self.flag_show = flag_show
|
self.flag_show = flag_show
|
||||||
|
@ -397,12 +393,19 @@ class Debug:
|
||||||
|
|
||||||
colors={}
|
colors={}
|
||||||
def Show(self, flag, msg, prefix=''):
|
def Show(self, flag, msg, prefix=''):
|
||||||
msg=msg.replace('\r','\\r').replace('\n','\\n')
|
msg=msg.replace('\r','\\r').replace('\n','\\n').replace('><','>\n <')
|
||||||
if self.colors.has_key(prefix): msg=self.colors[prefix]+msg+color_none
|
if not colors_enabled: pass
|
||||||
|
elif self.colors.has_key(prefix): msg=self.colors[prefix]+msg+color_none
|
||||||
else: msg=color_none+msg
|
else: msg=color_none+msg
|
||||||
if self.colors.has_key(flag): prefixcolor=self.colors[flag]
|
if not colors_enabled: prefixcolor=''
|
||||||
|
elif self.colors.has_key(flag): prefixcolor=self.colors[flag]
|
||||||
else: prefixcolor=color_none
|
else: prefixcolor=color_none
|
||||||
|
|
||||||
|
if prefix=='error':
|
||||||
|
_exception = sys.exc_info()
|
||||||
|
if _exception[0]:
|
||||||
|
msg=msg+'\n'+''.join(traceback.format_exception(_exception[0], _exception[1], _exception[2])).rstrip()
|
||||||
|
|
||||||
prefix= self.prefix+prefixcolor+(flag+' '*12)[:12]+' '+(prefix+' '*6)[:6]
|
prefix= self.prefix+prefixcolor+(flag+' '*12)[:12]+' '+(prefix+' '*6)[:6]
|
||||||
self.show(msg, flag, prefix)
|
self.show(msg, flag, prefix)
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
|
|
||||||
# $Id: dispatcher.py,v 1.35 2005/05/07 03:26:51 snakeru Exp $
|
# $Id: dispatcher.py,v 1.40 2006/01/18 19:26:43 normanr Exp $
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Main xmpppy mechanism. Provides library with methods to assign different handlers
|
Main xmpppy mechanism. Provides library with methods to assign different handlers
|
||||||
|
@ -115,7 +115,10 @@ class Dispatcher(PlugIn):
|
||||||
Returns:
|
Returns:
|
||||||
1) length of processed data if some data were processed;
|
1) length of processed data if some data were processed;
|
||||||
2) '0' string if no data were processed but link is alive;
|
2) '0' string if no data were processed but link is alive;
|
||||||
3) 0 (zero) if underlying connection is closed."""
|
3) 0 (zero) if underlying connection is closed.
|
||||||
|
Take note that in case of disconnection detect during Process() call
|
||||||
|
disconnect handlers are called automatically.
|
||||||
|
"""
|
||||||
for handler in self._cycleHandlers: handler(self)
|
for handler in self._cycleHandlers: handler(self)
|
||||||
if len(self._pendingExceptions) > 0:
|
if len(self._pendingExceptions) > 0:
|
||||||
_pendingException = self._pendingExceptions.pop()
|
_pendingException = self._pendingExceptions.pop()
|
||||||
|
@ -234,13 +237,30 @@ class Dispatcher(PlugIn):
|
||||||
3) data that comes along with event. Depends on event."""
|
3) data that comes along with event. Depends on event."""
|
||||||
if self._eventHandler: self._eventHandler(realm,event,data)
|
if self._eventHandler: self._eventHandler(realm,event,data)
|
||||||
|
|
||||||
def dispatch(self,stanza,session=None):
|
def dispatch(self,stanza,session=None,direct=0):
|
||||||
""" Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
|
""" Main procedure that performs XMPP stanza recognition and calling apppropriate handlers for it.
|
||||||
Called internally. """
|
Called internally. """
|
||||||
if not session: session=self
|
if not session: session=self
|
||||||
session.Stream._mini_dom=None
|
session.Stream._mini_dom=None
|
||||||
name=stanza.getName()
|
name=stanza.getName()
|
||||||
|
|
||||||
|
if not direct and self._owner._component:
|
||||||
|
if name == 'route':
|
||||||
|
if stanza.getAttr('error') == None:
|
||||||
|
if len(stanza.getChildren()) == 1:
|
||||||
|
stanza = stanza.getChildren()[0]
|
||||||
|
name=stanza.getName()
|
||||||
|
else:
|
||||||
|
for each in stanza.getChildren():
|
||||||
|
self.dispatch(each,session,direct=1)
|
||||||
|
return
|
||||||
|
elif name == 'presence':
|
||||||
|
return
|
||||||
|
elif name in ('features','bind'):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise UnsupportedStanzaType(name)
|
||||||
|
|
||||||
if name=='features': session.Stream.features=stanza
|
if name=='features': session.Stream.features=stanza
|
||||||
|
|
||||||
xmlns=stanza.getNamespace()
|
xmlns=stanza.getNamespace()
|
||||||
|
@ -251,7 +271,7 @@ class Dispatcher(PlugIn):
|
||||||
self.DEBUG("Unknown stanza: " + name,'warn')
|
self.DEBUG("Unknown stanza: " + name,'warn')
|
||||||
name='unknown'
|
name='unknown'
|
||||||
else:
|
else:
|
||||||
self.DEBUG("Got %s stanza"%name, 'ok')
|
self.DEBUG("Got %s/%s stanza"%(xmlns,name), 'ok')
|
||||||
|
|
||||||
if stanza.__class__.__name__=='Node': stanza=self.handlers[xmlns][name][type](node=stanza)
|
if stanza.__class__.__name__=='Node': stanza=self.handlers[xmlns][name][type](node=stanza)
|
||||||
|
|
||||||
|
@ -281,7 +301,6 @@ class Dispatcher(PlugIn):
|
||||||
try: cb(session,stanza,**args)
|
try: cb(session,stanza,**args)
|
||||||
except Exception, typ:
|
except Exception, typ:
|
||||||
if typ.__class__.__name__<>'NodeProcessed': raise
|
if typ.__class__.__name__<>'NodeProcessed': raise
|
||||||
|
|
||||||
else:
|
else:
|
||||||
session.DEBUG("Expected stanza arrived!",'ok')
|
session.DEBUG("Expected stanza arrived!",'ok')
|
||||||
session._expected[ID]=stanza
|
session._expected[ID]=stanza
|
||||||
|
@ -344,6 +363,16 @@ class Dispatcher(PlugIn):
|
||||||
stanza.setID(_ID)
|
stanza.setID(_ID)
|
||||||
else: _ID=stanza.getID()
|
else: _ID=stanza.getID()
|
||||||
if self._owner._registered_name and not stanza.getAttr('from'): stanza.setAttr('from',self._owner._registered_name)
|
if self._owner._registered_name and not stanza.getAttr('from'): stanza.setAttr('from',self._owner._registered_name)
|
||||||
|
if self._owner._component and stanza.getName()!='bind':
|
||||||
|
to=self._owner.Server
|
||||||
|
if stanza.getTo() and stanza.getTo().getDomain():
|
||||||
|
to=stanza.getTo().getDomain()
|
||||||
|
frm=stanza.getFrom()
|
||||||
|
if frm.getDomain():
|
||||||
|
frm=frm.getDomain()
|
||||||
|
route=Protocol('route',to=to,frm=frm,payload=[stanza])
|
||||||
|
stanza=route
|
||||||
|
stanza.setNamespace(self._owner.Namespace)
|
||||||
stanza.setParent(self._metastream)
|
stanza.setParent(self._metastream)
|
||||||
self._owner_send(stanza)
|
self._owner_send(stanza)
|
||||||
return _ID
|
return _ID
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
|
|
||||||
# $Id: features.py,v 1.20 2005/04/30 07:43:01 snakeru Exp $
|
# $Id: features.py,v 1.22 2005/09/30 20:13:04 mikealbon Exp $
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module contains variable stuff that is not worth splitting into separate modules.
|
This module contains variable stuff that is not worth splitting into separate modules.
|
||||||
|
@ -60,7 +60,7 @@ def discoverInfo(disp,jid,node=None):
|
||||||
""" Query remote object about info that it publishes. Returns identities and features lists."""
|
""" Query remote object about info that it publishes. Returns identities and features lists."""
|
||||||
""" According to JEP-0030:
|
""" According to JEP-0030:
|
||||||
query MAY have node attribute
|
query MAY have node attribute
|
||||||
identity: MUST HAVE category and type attributes and MAY HAVE name attribute.
|
identity: MUST HAVE category and name attributes and MAY HAVE type attribute.
|
||||||
feature: MUST HAVE var attribute"""
|
feature: MUST HAVE var attribute"""
|
||||||
identities , features = [] , []
|
identities , features = [] , []
|
||||||
for i in _discover(disp,NS_DISCO_INFO,jid,node):
|
for i in _discover(disp,NS_DISCO_INFO,jid,node):
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
## GNU General Public License for more details.
|
## GNU General Public License for more details.
|
||||||
|
|
||||||
# $Id: protocol.py,v 1.41 2005/05/02 08:36:41 snakeru Exp $
|
# $Id: protocol.py,v 1.52 2006/01/09 22:08:57 normanr Exp $
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Protocol module contains tools that is needed for processing of
|
Protocol module contains tools that is needed for processing of
|
||||||
|
@ -21,43 +21,66 @@ xmpp-related data structures.
|
||||||
|
|
||||||
from simplexml import Node,ustr
|
from simplexml import Node,ustr
|
||||||
import time
|
import time
|
||||||
|
NS_ACTIVITY ='http://jabber.org/protocol/activity' # JEP-0108
|
||||||
|
NS_ADDRESS ='http://jabber.org/protocol/address' # JEP-0033
|
||||||
NS_AGENTS ='jabber:iq:agents'
|
NS_AGENTS ='jabber:iq:agents'
|
||||||
NS_AMP ='http://jabber.org/protocol/amp'
|
NS_AMP ='http://jabber.org/protocol/amp'
|
||||||
|
NS_AMP_ERRORS =NS_AMP+'#errors'
|
||||||
NS_AUTH ='jabber:iq:auth'
|
NS_AUTH ='jabber:iq:auth'
|
||||||
NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
|
NS_BIND ='urn:ietf:params:xml:ns:xmpp-bind'
|
||||||
NS_BROWSE ='jabber:iq:browse'
|
NS_BROWSE ='jabber:iq:browse'
|
||||||
|
NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
|
||||||
|
NS_CAPS ='http://jabber.org/protocol/caps' # JEP-0115
|
||||||
|
NS_CHATSTATES ='http://jabber.org/protocol/chatstates' # JEP-0085
|
||||||
NS_CLIENT ='jabber:client'
|
NS_CLIENT ='jabber:client'
|
||||||
NS_COMMANDS ='http://jabber.org/protocol/commands'
|
NS_COMMANDS ='http://jabber.org/protocol/commands'
|
||||||
NS_COMPONENT_ACCEPT='jabber:component:accept'
|
NS_COMPONENT_ACCEPT='jabber:component:accept'
|
||||||
|
NS_COMPONENT_1 ='http://jabberd.jabberstudio.org/ns/component/1.0'
|
||||||
|
NS_COMPRESS ='http://jabber.org/protocol/compress' # JEP-0138
|
||||||
NS_CONFERENCE ='jabber:x:conference'
|
NS_CONFERENCE ='jabber:x:conference'
|
||||||
NS_DATA ='jabber:x:data' # JEP-0004
|
NS_DATA ='jabber:x:data' # JEP-0004
|
||||||
NS_DELAY ='jabber:x:delay'
|
NS_DELAY ='jabber:x:delay'
|
||||||
NS_DIALBACK ='jabber:server:dialback'
|
NS_DIALBACK ='jabber:server:dialback'
|
||||||
NS_DISCO_INFO ='http://jabber.org/protocol/disco#info'
|
NS_DISCO ='http://jabber.org/protocol/disco'
|
||||||
NS_DISCO_ITEMS ='http://jabber.org/protocol/disco#items'
|
NS_DISCO_INFO =NS_DISCO+'#info'
|
||||||
|
NS_DISCO_ITEMS =NS_DISCO+'#items'
|
||||||
|
NS_ENCRYPTED ='jabber:x:encrypted' # JEP-0027
|
||||||
|
NS_EVENT ='jabber:x:event' # JEP-0022
|
||||||
|
NS_FEATURE ='http://jabber.org/protocol/feature-neg'
|
||||||
|
NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
|
||||||
|
NS_GEOLOC ='http://jabber.org/protocol/geoloc' # JEP-0080
|
||||||
NS_GROUPCHAT ='gc-1.0'
|
NS_GROUPCHAT ='gc-1.0'
|
||||||
|
NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # JEP-0070
|
||||||
|
NS_HTTP_BIND ='http://jabber.org/protocol/httpbind' # JEP-0124
|
||||||
NS_IBB ='http://jabber.org/protocol/ibb'
|
NS_IBB ='http://jabber.org/protocol/ibb'
|
||||||
NS_INVISIBLE ='presence-invisible' # jabberd2
|
NS_INVISIBLE ='presence-invisible' # Jabberd2
|
||||||
NS_IQ ='iq' # jabberd2
|
NS_IQ ='iq' # Jabberd2
|
||||||
NS_LAST ='jabber:iq:last'
|
NS_LAST ='jabber:iq:last'
|
||||||
NS_MESSAGE ='message' # jabberd2
|
NS_MESSAGE ='message' # Jabberd2
|
||||||
|
NS_MOOD ='http://jabber.org/protocol/mood' # JEP-0107
|
||||||
NS_MUC ='http://jabber.org/protocol/muc'
|
NS_MUC ='http://jabber.org/protocol/muc'
|
||||||
NS_MUC_USER ='http://jabber.org/protocol/muc#user'
|
NS_MUC_USER =NS_MUC+'#user'
|
||||||
NS_MUC_ADMIN ='http://jabber.org/protocol/muc#admin'
|
NS_MUC_ADMIN =NS_MUC+'#admin'
|
||||||
NS_MUC_OWNER ='http://jabber.org/protocol/muc#owner'
|
NS_MUC_OWNER =NS_MUC+'#owner'
|
||||||
NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # JEP-0013
|
NS_OFFLINE ='http://www.jabber.org/jeps/jep-0030.html' # JEP-0013
|
||||||
NS_PRESENCE ='presence' # jabberd2
|
NS_PHYSLOC ='http://jabber.org/protocol/physloc' # JEP-0112
|
||||||
|
NS_PRESENCE ='presence' # Jabberd2
|
||||||
NS_PRIVACY ='jabber:iq:privacy'
|
NS_PRIVACY ='jabber:iq:privacy'
|
||||||
NS_PRIVATE ='jabber:iq:private'
|
NS_PRIVATE ='jabber:iq:private'
|
||||||
|
NS_PUBSUB ='http://jabber.org/protocol/pubsub' # JEP-0060
|
||||||
NS_REGISTER ='jabber:iq:register'
|
NS_REGISTER ='jabber:iq:register'
|
||||||
NS_ROSTER ='jabber:iq:roster'
|
NS_ROSTER ='jabber:iq:roster'
|
||||||
|
NS_ROSTERX ='http://jabber.org/protocol/rosterx' # JEP-0144
|
||||||
NS_RPC ='jabber:iq:rpc' # JEP-0009
|
NS_RPC ='jabber:iq:rpc' # JEP-0009
|
||||||
NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
|
NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
|
||||||
NS_SEARCH ='jabber:iq:search'
|
NS_SEARCH ='jabber:iq:search'
|
||||||
NS_SERVER ='jabber:server'
|
NS_SERVER ='jabber:server'
|
||||||
NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
|
NS_SESSION ='urn:ietf:params:xml:ns:xmpp-session'
|
||||||
|
NS_SI ='http://jabber.org/protocol/si' # JEP-0096
|
||||||
|
NS_SI_PUB ='http://jabber.org/protocol/sipub' # JEP-0137
|
||||||
|
NS_SIGNED ='jabber:x:signed' # JEP-0027
|
||||||
NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
|
NS_STANZAS ='urn:ietf:params:xml:ns:xmpp-stanzas'
|
||||||
|
NS_STREAM ='http://affinix.com/jabber/stream'
|
||||||
NS_STREAMS ='http://etherx.jabber.org/streams'
|
NS_STREAMS ='http://etherx.jabber.org/streams'
|
||||||
NS_TIME ='jabber:iq:time'
|
NS_TIME ='jabber:iq:time'
|
||||||
NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
|
NS_TLS ='urn:ietf:params:xml:ns:xmpp-tls'
|
||||||
|
@ -66,16 +89,11 @@ NS_VCARD ='vcard-temp'
|
||||||
NS_GMAILNOTIFY ='google:mail:notify'
|
NS_GMAILNOTIFY ='google:mail:notify'
|
||||||
NS_VCARD_UPDATE =NS_VCARD+':x:update'
|
NS_VCARD_UPDATE =NS_VCARD+':x:update'
|
||||||
NS_VERSION ='jabber:iq:version'
|
NS_VERSION ='jabber:iq:version'
|
||||||
NS_ENCRYPTED ='jabber:x:encrypted' # JEP-0027
|
NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # JEP-0130
|
||||||
|
NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # JEP-0071
|
||||||
|
NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # JEP-0141
|
||||||
|
NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # JEP-0122
|
||||||
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
|
NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams'
|
||||||
NS_SIGNED ='jabber:x:signed' # JEP-0027
|
|
||||||
NS_SI ='http://jabber.org/protocol/si' # JEP-0096
|
|
||||||
NS_FILE ='http://jabber.org/protocol/si/profile/file-transfer' # JEP-0096
|
|
||||||
NS_FEATURE ='http://jabber.org/protocol/feature-neg'
|
|
||||||
NS_BYTESTREAM ='http://jabber.org/protocol/bytestreams' # JEP-0065
|
|
||||||
NS_DISCO ='http://jabber.org/protocol/disco#info' # JEP-0095
|
|
||||||
NS_STREAM ='http://affinix.com/jabber/stream'
|
|
||||||
NS_HTTP_AUTH ='http://jabber.org/protocol/http-auth' # JEP-0070
|
|
||||||
|
|
||||||
xmpp_stream_error_conditions="""
|
xmpp_stream_error_conditions="""
|
||||||
bad-format -- -- -- The entity has sent XML that cannot be processed.
|
bad-format -- -- -- The entity has sent XML that cannot be processed.
|
||||||
|
@ -676,7 +694,7 @@ class DataForm(Node):
|
||||||
for field in self.getTags('field'):
|
for field in self.getTags('field'):
|
||||||
name=field.getAttr('var')
|
name=field.getAttr('var')
|
||||||
typ=field.getType()
|
typ=field.getType()
|
||||||
if type(typ) in [type(''),type(u'')] and typ[-6:]=='multi':
|
if type(typ) in [type(''),type(u'')] and typ[-6:]=='-multi':
|
||||||
val=[]
|
val=[]
|
||||||
for i in field.getTags('value'): val.append(i.getData())
|
for i in field.getTags('value'): val.append(i.getData())
|
||||||
else: val=field.getTagData('value')
|
else: val=field.getTagData('value')
|
||||||
|
|
|
@ -66,7 +66,7 @@ class error:
|
||||||
|
|
||||||
class TCPsocket(PlugIn):
|
class TCPsocket(PlugIn):
|
||||||
""" This class defines direct TCP connection method. """
|
""" This class defines direct TCP connection method. """
|
||||||
def __init__(self, server=None):
|
def __init__(self, server=None, use_srv=True):
|
||||||
""" Cache connection point 'server'. 'server' is the tuple of (host, port)
|
""" Cache connection point 'server'. 'server' is the tuple of (host, port)
|
||||||
absolutely the same as standard tcp socket uses. """
|
absolutely the same as standard tcp socket uses. """
|
||||||
PlugIn.__init__(self)
|
PlugIn.__init__(self)
|
||||||
|
@ -165,12 +165,12 @@ class HTTPPROXYsocket(TCPsocket):
|
||||||
""" HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
|
""" HTTP (CONNECT) proxy connection class. Uses TCPsocket as the base class
|
||||||
redefines only connect method. Allows to use HTTP proxies like squid with
|
redefines only connect method. Allows to use HTTP proxies like squid with
|
||||||
(optionally) simple authentication (using login and password). """
|
(optionally) simple authentication (using login and password). """
|
||||||
def __init__(self,proxy,server):
|
def __init__(self,proxy,server,use_srv=True):
|
||||||
""" Caches proxy and target addresses.
|
""" Caches proxy and target addresses.
|
||||||
'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
|
'proxy' argument is a dictionary with mandatory keys 'host' and 'port' (proxy address)
|
||||||
and optional keys 'user' and 'password' to use for authentication.
|
and optional keys 'user' and 'password' to use for authentication.
|
||||||
'server' argument is a tuple of host and port - just like TCPsocket uses. """
|
'server' argument is a tuple of host and port - just like TCPsocket uses. """
|
||||||
TCPsocket.__init__(self,server)
|
TCPsocket.__init__(self,server,use_srv)
|
||||||
self.DBG_LINE=DBG_CONNECT_PROXY
|
self.DBG_LINE=DBG_CONNECT_PROXY
|
||||||
self._proxy=proxy
|
self._proxy=proxy
|
||||||
|
|
||||||
|
@ -196,14 +196,23 @@ class HTTPPROXYsocket(TCPsocket):
|
||||||
connector.append('Proxy-Authorization: Basic '+credentials)
|
connector.append('Proxy-Authorization: Basic '+credentials)
|
||||||
connector.append('\r\n')
|
connector.append('\r\n')
|
||||||
self.send('\r\n'.join(connector))
|
self.send('\r\n'.join(connector))
|
||||||
reply = self.receive().replace('\r','')
|
try: reply = self.receive().replace('\r','')
|
||||||
|
except IOError:
|
||||||
|
self.DEBUG('Proxy suddenly disconnected','error')
|
||||||
|
self._owner.disconnected()
|
||||||
|
return
|
||||||
try: proto,code,desc=reply.split('\n')[0].split(' ',2)
|
try: proto,code,desc=reply.split('\n')[0].split(' ',2)
|
||||||
except: raise error('Invalid proxy reply')
|
except: raise error('Invalid proxy reply')
|
||||||
if code<>'200':
|
if code<>'200':
|
||||||
self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error')
|
self.DEBUG('Invalid proxy reply: %s %s %s'%(proto,code,desc),'error')
|
||||||
self._owner.disconnected()
|
self._owner.disconnected()
|
||||||
return
|
return
|
||||||
while reply.find('\n\n') == -1: reply += self.receive().replace('\r','')
|
while reply.find('\n\n') == -1:
|
||||||
|
try: reply += self.receive().replace('\r','')
|
||||||
|
except IOError:
|
||||||
|
self.DEBUG('Proxy suddenly disconnected','error')
|
||||||
|
self._owner.disconnected()
|
||||||
|
return
|
||||||
self.DEBUG("Authentification successfull. Jabber server contacted.",'ok')
|
self.DEBUG("Authentification successfull. Jabber server contacted.",'ok')
|
||||||
return 'ok'
|
return 'ok'
|
||||||
|
|
||||||
|
@ -247,8 +256,13 @@ class TLS(PlugIn):
|
||||||
self._owner.Connection.send('<starttls xmlns="%s"/>'%NS_TLS)
|
self._owner.Connection.send('<starttls xmlns="%s"/>'%NS_TLS)
|
||||||
raise NodeProcessed
|
raise NodeProcessed
|
||||||
|
|
||||||
|
def pending_data(self,timeout=0):
|
||||||
|
""" Returns true if there possible is a data ready to be read. """
|
||||||
|
return self._tcpsock._seen_data or select.select([self._tcpsock._sock],[],[],timeout)[0]
|
||||||
|
|
||||||
def _startSSL(self):
|
def _startSSL(self):
|
||||||
""" Immidiatedly switch socket to TLS mode. Used internally."""
|
""" Immidiatedly switch socket to TLS mode. Used internally."""
|
||||||
|
""" Here we should switch pending_data to hint mode."""
|
||||||
tcpsock=self._owner.Connection
|
tcpsock=self._owner.Connection
|
||||||
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None)
|
||||||
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
tcpsock._sslIssuer = tcpsock._sslObj.issuer()
|
||||||
|
|
Loading…
Reference in New Issue