remove xmpppy from the repos

parent 6af387de
This diff is collapsed.
README
setup.py
Makefile
ChangeLog
xmpp/__init__.py
xmpp/auth.py
xmpp/browser.py
xmpp/client.py
xmpp/commands.py
xmpp/debug.py
xmpp/dispatcher.py
xmpp/features.py
xmpp/filetransfer.py
xmpp/jep0106.py
xmpp/protocol.py
xmpp/roster.py
xmpp/session.py
xmpp/simplexml.py
xmpp/transports.py
#MODULESDIR=/usr/lib/($PYTHONVERSION)/site-packages
install:
# Add here commands to install the package into debian/python-xmpp
[ -d $(MODULESDIR)/xmpp ] || mkdir $(MODULESDIR)/xmpp
install -m 0644 xmpp/*py $(MODULESDIR)/xmpp
Introduction to xmpppy.
http://xmpppy.sf.net/
This is my work to replace the jabberpy with the current and maintained
project.
Now the project nears feature freese of 0.2 branch. Almost all goals are
achieved already. Though the main goal was to write a documentation - at least
a line for every feature of library. Yesterday I have checked in last docstrings
for all yet undocumented modules and now I can say that this issue is resolved
(at least for 0.2 release level).
Documentation exists in three formats.
- The first is the examples that I wrote to show xmpppy in action. This is
two simple scripts - README.py and xsend.py.
- The second is the html pages where I try to describe the idea of library
and the ways the goals are achieved.
- Third is the docstrings. I am currently using epydoc but other tools
should work too (at least the pydoc works)
Installation
If you are using Debian (sarge or above) you can simply run
apt-get install python-xmpp
and you will get the current stable release of xmpppy installed. After installation
you can do 'import xmpp'. Though currently debian contains 0.1 release of xmpppy so
if you want to use 0.2 branch you should install it manually (python2.3 required).
Here you have several options:
- run 'python setup.py install' from xmpppy distribution as root.
All should work nice.
- if you don't like python installator - just copy xmpp directory into python's
site-packages directory (this is what setup.py does).
- If you have no intention to install library system-wide (or just have no
privileges to do it) you can copy xmpp directory just in your application's
directory. Example:
myxmpppytry/
xmpp/
...xmpppy modules
test.py
If you have any questions about xmpppy usage or you have find a bug or want
to share some ideas - you are welcome in xmpppy-devel maillist - see
http://lists.sourceforge.net/lists/listinfo/xmpppy-devel
for details of subscription.
2004.12.26 Alexey Nezhdanov
# $Id: __init__.py,v 1.9 2005/03/07 09:34:51 snakeru Exp $
"""
All features of xmpppy library contained within separate modules.
At present there are modules:
simplexml - XML handling routines
protocol - jabber-objects (I.e. JID and different stanzas and sub-stanzas) handling routines.
debug - Jacob Lundquist's debugging module. Very handy if you like colored debug.
auth - Non-SASL and SASL stuff. You will need it to auth as a client or transport.
transports - low level connection handling. TCP and TLS currently. HTTP support planned.
roster - simple roster for use in clients.
dispatcher - decision-making logic. Handles all hooks. The first who takes control over fresh stanzas.
features - different stuff that didn't worths separating into modules
browser - DISCO server framework. Allows to build dynamic disco tree.
filetransfer - Currently contains only IBB stuff. Can be used for bot-to-bot transfers.
Most of the classes that is defined in all these modules is an ancestors of
class PlugIn so they share a single set of methods allowing you to compile
a featured XMPP client. For every instance of PlugIn class the 'owner' is the class
in what the plug was plugged. While plugging in such instance usually sets some
methods of owner to it's own ones for easy access. All session specific info stored
either in instance of PlugIn or in owner's instance. This is considered unhandy
and there are plans to port 'Session' class from xmppd.py project for storing all
session-related info. Though if you are not accessing instances variables directly
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,commands
from client import *
from protocol import *
## auth.py
##
## Copyright (C) 2003-2005 Alexey "Snake" Nezhdanov
##
## 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, 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.
# $Id: auth.py,v 1.41 2008/09/13 21:45:21 normanr Exp $
"""
Provides library with all Non-SASL and SASL authentication mechanisms.
Can be used both for client and transport authentication.
"""
from protocol import *
from client import PlugIn
import sha,base64,random,dispatcher,re
import md5
def HH(some): return md5.new(some).hexdigest()
def H(some): return md5.new(some).digest()
def C(some): return ':'.join(some)
class NonSASL(PlugIn):
""" Implements old Non-SASL (JEP-0078) authentication used in jabberd1.4 and transport authentication."""
def __init__(self,user,password,resource):
""" Caches username, password and resource for auth. """
PlugIn.__init__(self)
self.DBG_LINE='gen_auth'
self.user=user
self.password=password
self.resource=resource
def plugin(self,owner):
""" Determine the best auth method (digest/0k/plain) and use it for auth.
Returns used method name on success. Used internally. """
if not self.resource: return self.authComponent(owner)
self.DEBUG('Querying server about possible auth methods','start')
resp=owner.Dispatcher.SendAndWaitForResponse(Iq('get',NS_AUTH,payload=[Node('username',payload=[self.user])]))
if not isResultNode(resp):
self.DEBUG('No result node arrived! Aborting...','error')
return
iq=Iq(typ='set',node=resp)
query=iq.getTag('query')
query.setTagData('username',self.user)
query.setTagData('resource',self.resource)
if query.getTag('digest'):
self.DEBUG("Performing digest authentication",'ok')
query.setTagData('digest',sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest())
if query.getTag('password'): query.delChild('password')
method='digest'
elif query.getTag('token'):
token=query.getTagData('token')
seq=query.getTagData('sequence')
self.DEBUG("Performing zero-k authentication",'ok')
hash = sha.new(sha.new(self.password).hexdigest()+token).hexdigest()
for foo in xrange(int(seq)): hash = sha.new(hash).hexdigest()
query.setTagData('hash',hash)
method='0k'
else:
self.DEBUG("Sequre methods unsupported, performing plain text authentication",'warn')
query.setTagData('password',self.password)
method='plain'
resp=owner.Dispatcher.SendAndWaitForResponse(iq)
if isResultNode(resp):
self.DEBUG('Sucessfully authenticated with remove host.','ok')
owner.User=self.user
owner.Resource=self.resource
owner._registered_name=owner.User+'@'+owner.Server+'/'+owner.Resource
return method
self.DEBUG('Authentication failed!','error')
def authComponent(self,owner):
""" Authenticate component. Send handshake stanza and wait for result. Returns "ok" on success. """
self.handshake=0
owner.send(Node(NS_COMPONENT_ACCEPT+' handshake',payload=[sha.new(owner.Dispatcher.Stream._document_attrs['id']+self.password).hexdigest()]))
owner.RegisterHandler('handshake',self.handshakeHandler,xmlns=NS_COMPONENT_ACCEPT)
while not self.handshake:
self.DEBUG("waiting on handshake",'notify')
owner.Process(1)
owner._registered_name=self.user
if self.handshake+1: return 'ok'
def handshakeHandler(self,disp,stanza):
""" Handler for registering in dispatcher for accepting transport authentication. """
if stanza.getName()=='handshake': self.handshake=1
else: self.handshake=-1
class SASL(PlugIn):
""" Implements SASL authentication. """
def __init__(self,username,password):
PlugIn.__init__(self)
self.username=username
self.password=password
def plugin(self,owner):
if not self._owner.Dispatcher.Stream._document_attrs.has_key('version'): self.startsasl='not-supported'
elif self._owner.Dispatcher.Stream.features:
try: self.FeaturesHandler(self._owner.Dispatcher,self._owner.Dispatcher.Stream.features)
except NodeProcessed: pass
else: self.startsasl=None
def auth(self):
""" Start authentication. Result can be obtained via "SASL.startsasl" attribute and will be
either "success" or "failure". Note that successfull auth will take at least
two Dispatcher.Process() calls. """
if self.startsasl: pass
elif 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)
def plugout(self):
""" Remove SASL handlers from owner's dispatcher. Used internally. """
if self._owner.__dict__.has_key('features'): self._owner.UnregisterHandler('features',self.FeaturesHandler,xmlns=NS_STREAMS)
if self._owner.__dict__.has_key('challenge'): self._owner.UnregisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
if self._owner.__dict__.has_key('failure'): self._owner.UnregisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
if self._owner.__dict__.has_key('success'): self._owner.UnregisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
def FeaturesHandler(self,conn,feats):
""" Used to determine if server supports SASL auth. Used internally. """
if not feats.getTag('mechanisms',namespace=NS_SASL):
self.startsasl='not-supported'
self.DEBUG('SASL not supported by server','error')
return
mecs=[]
for mec in feats.getTag('mechanisms',namespace=NS_SASL).getTags('mechanism'):
mecs.append(mec.getData())
self._owner.RegisterHandler('challenge',self.SASLHandler,xmlns=NS_SASL)
self._owner.RegisterHandler('failure',self.SASLHandler,xmlns=NS_SASL)
self._owner.RegisterHandler('success',self.SASLHandler,xmlns=NS_SASL)
if "ANONYMOUS" in mecs and self.username == None:
node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'ANONYMOUS'})
elif "DIGEST-MD5" in mecs:
node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'DIGEST-MD5'})
elif "PLAIN" in mecs:
sasl_data='%s\x00%s\x00%s'%(self.username+'@'+self._owner.Server,self.username,self.password)
node=Node('auth',attrs={'xmlns':NS_SASL,'mechanism':'PLAIN'},payload=[base64.encodestring(sasl_data).replace('\r','').replace('\n','')])
else:
self.startsasl='failure'
self.DEBUG('I can only use DIGEST-MD5 and PLAIN mecanisms.','error')
return
self.startsasl='in-process'
self._owner.send(node.__str__())
raise NodeProcessed
def SASLHandler(self,conn,challenge):
""" Perform next SASL auth step. Used internally. """
if challenge.getNamespace()<>NS_SASL: return
if challenge.getName()=='failure':
self.startsasl='failure'
try: reason=challenge.getChildren()[0]
except: reason=challenge
self.DEBUG('Failed SASL authentification: %s'%reason,'error')
raise NodeProcessed
elif challenge.getName()=='success':
self.startsasl='success'
self.DEBUG('Successfully authenticated with remote server.','ok')
handlers=self._owner.Dispatcher.dumpHandlers()
self._owner.Dispatcher.PlugOut()
dispatcher.Dispatcher().PlugIn(self._owner)
self._owner.Dispatcher.restoreHandlers(handlers)
self._owner.User=self.username
raise NodeProcessed
########################################3333
incoming_data=challenge.getData()
chal={}
data=base64.decodestring(incoming_data)
self.DEBUG('Got challenge:'+data,'ok')
for pair in re.findall('(\w+\s*=\s*(?:(?:"[^"]+")|(?:[^,]+)))',data):
key,value=[x.strip() for x in pair.split('=', 1)]
if value[:1]=='"' and value[-1:]=='"': value=value[1:-1]
chal[key]=value
if chal.has_key('qop') and 'auth' in [x.strip() for x in chal['qop'].split(',')]:
resp={}
resp['username']=self.username
resp['realm']=self._owner.Server
resp['nonce']=chal['nonce']
cnonce=''
for i in range(7):
cnonce+=hex(int(random.random()*65536*4096))[2:]
resp['cnonce']=cnonce
resp['nc']=('00000001')
resp['qop']='auth'
resp['digest-uri']='xmpp/'+self._owner.Server
A1=C([H(C([resp['username'],resp['realm'],self.password])),resp['nonce'],resp['cnonce']])
A2=C(['AUTHENTICATE',resp['digest-uri']])
response= HH(C([HH(A1),resp['nonce'],resp['nc'],resp['cnonce'],resp['qop'],HH(A2)]))
resp['response']=response
resp['charset']='utf-8'
sasl_data=''
for key in ['charset','username','realm','nonce','nc','cnonce','digest-uri','response','qop']:
if key in ['nc','qop','response','charset']: sasl_data+="%s=%s,"%(key,resp[key])
else: sasl_data+='%s="%s",'%(key,resp[key])
########################################3333
node=Node('response',attrs={'xmlns':NS_SASL},payload=[base64.encodestring(sasl_data[:-1]).replace('\r','').replace('\n','')])
self._owner.send(node.__str__())
elif chal.has_key('rspauth'): self._owner.send(Node('response',attrs={'xmlns':NS_SASL}).__str__())
else:
self.startsasl='failure'
self.DEBUG('Failed SASL authentification: unknown challenge','error')
raise NodeProcessed
class Bind(PlugIn):
""" Bind 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
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)
def plugout(self):
""" Remove Bind handler from owner's dispatcher. Used internally. """
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,resource=None):
""" Perform binding. Use provided resource name or random (if not provided). """
while self.bound is None and self._owner.Process(1): pass
if resource: resource=[Node('resource',payload=[resource])]
else: resource=[]
resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('bind',attrs={'xmlns':NS_BIND},payload=resource)]))
if isResultNode(resp):
self.bound.append(resp.getTag('bind').getTagData('jid'))
self.DEBUG('Successfully bound %s.'%self.bound[-1],'ok')
jid=JID(resp.getTag('bind').getTagData('jid'))
self._owner.User=jid.getNode()
self._owner.Resource=jid.getResource()
resp=self._owner.SendAndWaitForResponse(Protocol('iq',typ='set',payload=[Node('session',attrs={'xmlns':NS_SESSION})]))
if isResultNode(resp):
self.DEBUG('Successfully opened session.','ok')
self.session=1
return 'ok'
else:
self.DEBUG('Session open failed.','error')
self.session=0
elif resp: self.DEBUG('Binding failed: %s.'%resp.getTag('error'),'error')
else:
self.DEBUG('Binding failed: timeout expired.','error')
return ''
class ComponentBind(PlugIn):
""" ComponentBind some JID to the current connection to allow router know of our location."""
def __init__(self, sasl):
PlugIn.__init__(self)
self.DBG_LINE='bind'
self.bound=None
self.needsUnregister=None
self.sasl = sasl
def plugin(self,owner):
""" Start resource binding, if allowed at this time. Used internally. """
if not self.sasl:
self.bound=[]
return
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
if self.sasl:
xmlns = NS_COMPONENT_1
else:
xmlns = None
self.bindresponse = None
ttl = dispatcher.DefaultTimeout
self._owner.RegisterHandler('bind',self.BindHandler,xmlns=xmlns)
self._owner.send(Protocol('bind',attrs={'name':domain},xmlns=NS_COMPONENT_1))
while self.bindresponse is None and self._owner.Process(1) and ttl > 0: ttl-=1
self._owner.UnregisterHandler('bind',self.BindHandler,xmlns=xmlns)
resp=self.bindresponse
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 ''
def BindHandler(self,conn,bind):
self.bindresponse = bind
pass
## browser.py
##
## Copyright (C) 2004 Alexey "Snake" Nezhdanov
##
## 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, 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.
# $Id: browser.py,v 1.12 2007/05/13 17:55:14 normanr Exp $
"""Browser module provides DISCO server framework for your application.
This functionality can be used for very different purposes - from publishing
software version and supported features to building of "jabber site" that users
can navigate with their disco browsers and interact with active content.
Such functionality is achieved via registering "DISCO handlers" that are
automatically called when user requests some node of your disco tree.
"""
from dispatcher import *
from client import PlugIn
class Browser(PlugIn):
""" WARNING! This class is for components only. It will not work in client mode!
Standart xmpppy class that is ancestor of PlugIn and can be attached
to your application.
All processing will be performed in the handlers registered in the browser
instance. You can register any number of handlers ensuring that for each
node/jid combination only one (or none) handler registered.
You can register static information or the fully-blown function that will
calculate the answer dynamically.
Example of static info (see JEP-0030, examples 13-14):
# cl - your xmpppy connection instance.
b=xmpp.browser.Browser()
b.PlugIn(cl)
items=[]
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='books'
item['name']='Books by and about Shakespeare'
items.append(item)
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='clothing'
item['name']='Wear your literary taste with pride'
items.append(item)
item={}
item['jid']='catalog.shakespeare.lit'
item['node']='music'
item['name']='Music from the time of Shakespeare'
items.append(item)
info={'ids':[], 'features':[]}
b.setDiscoHandler({'items':items,'info':info})
items should be a list of item elements.
every item element can have any of these four keys: 'jid', 'node', 'name', 'action'
info should be a dicionary and must have keys 'ids' and 'features'.
Both of them should be lists:
ids is a list of dictionaries and features is a list of text strings.
Example (see JEP-0030, examples 1-2)
# cl - your xmpppy connection instance.
b=xmpp.browser.Browser()
b.PlugIn(cl)
items=[]
ids=[]
ids.append({'category':'conference','type':'text','name':'Play-Specific Chatrooms'})
ids.append({'category':'directory','type':'chatroom','name':'Play-Specific Chatrooms'})
features=[NS_DISCO_INFO,NS_DISCO_ITEMS,NS_MUC,NS_REGISTER,NS_SEARCH,NS_TIME,NS_VERSION]
info={'ids':ids,'features':features}
# info['xdata']=xmpp.protocol.DataForm() # JEP-0128
b.setDiscoHandler({'items':[],'info':info})
"""
def __init__(self):
"""Initialises internal variables. Used internally."""
PlugIn.__init__(self)
DBG_LINE='browser'
self._exported_methods=[]
self._handlers={'':{}}
def plugin(self, owner):
""" Registers it's own iq handlers in your application dispatcher instance.
Used internally."""
owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
owner.RegisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
def plugout(self):
""" Unregisters browser's iq handlers from your application dispatcher instance.
Used internally."""
self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_INFO)
self._owner.UnregisterHandler('iq',self._DiscoveryHandler,typ='get',ns=NS_DISCO_ITEMS)
def _traversePath(self,node,jid,set=0):
""" Returns dictionary and key or None,None
None - root node (w/o "node" attribute)
/a/b/c - node
/a/b/ - branch
Set returns '' or None as the key
get returns '' or None as the key or None as the dict.
Used internally."""
if self._handlers.has_key(jid): cur=self._handlers[jid]
elif set:
self._handlers[jid]={}
cur=self._handlers[jid]
else: cur=self._handlers['']
if node is None: node=[None]
else: node=node.replace('/',' /').split('/')
for i in node:
if i<>'' and cur.has_key(i): cur=cur[i]
elif set and i<>'': cur[i]={dict:cur,str:i}; cur=cur[i]
elif set or cur.has_key(''): return cur,''
else: return None,None
if cur.has_key(1) or set: return cur,1
raise "Corrupted data"
def setDiscoHandler(self,handler,node='',jid=''):
""" This is the main method that you will use in this class.
It is used to register supplied DISCO handler (or dictionary with static info)
as handler of some disco tree branch.
If you do not specify the node this handler will be used for all queried nodes.
If you do not specify the jid this handler will be used for all queried JIDs.
Usage:
cl.Browser.setDiscoHandler(someDict,node,jid)
or
cl.Browser.setDiscoHandler(someDISCOHandler,node,jid)
where
someDict={
'items':[
{'jid':'jid1','action':'action1','node':'node1','name':'name1'},
{'jid':'jid2','action':'action2','node':'node2','name':'name2'},
{'jid':'jid3','node':'node3','name':'name3'},
{'jid':'jid4','node':'node4'}
],
'info' :{
'ids':[
{'category':'category1','type':'type1','name':'name1'},
{'category':'category2','type':'type2','name':'name2'},
{'category':'category3','type':'type3','name':'name3'},
],
'features':['feature1','feature2','feature3','feature4'],
'xdata':DataForm
}
}
and/or
def someDISCOHandler(session,request,TYR):
# if TYR=='items': # returns items list of the same format as shown above
# elif TYR=='info': # returns info dictionary of the same format as shown above
# else: # this case is impossible for now.
"""