mirror of git://git.psyced.org/git/pypsyc
270 lines
10 KiB
Python
270 lines
10 KiB
Python
#!/usr/bin/env python
|
|
from pypsyc import parseUNL, UNL2Location, netLocation
|
|
|
|
from twisted.internet import reactor, defer
|
|
from pypsyc.net import PSYCClientConnector, PSYCActiveConnector
|
|
|
|
from pypsyc import dump_packet
|
|
|
|
|
|
class Center:
|
|
object_handlers = {}
|
|
remote_connections = {}
|
|
def msg(self, vars, mc, data):
|
|
raise NotImplementedError
|
|
|
|
def sendmsg(self, vars, mc, data, target = None):
|
|
raise NotImplementedError
|
|
|
|
def register_object(self, netname, object):
|
|
# multiple handlers are not possible
|
|
# we only want lower case netnames
|
|
self.object_handlers[netname.lower()] = object
|
|
|
|
def find_object(self, netname):
|
|
"""find a local object that corresponds to netname"""
|
|
# try "real/local" object"
|
|
# we only have lower case netnames
|
|
return self.object_handlers.get(netname.lower())
|
|
|
|
def find_remote(self, netname):
|
|
"""find a remote location where netname may be located"""
|
|
address = netLocation(netname)
|
|
if self.remote_connections.has_key(address):
|
|
return self.remote_connections[address]
|
|
return None
|
|
def register_remote(self, obj, where):
|
|
self.remote_connections[where] = obj
|
|
def unregister_remote(self, obj, where):
|
|
if self.remote_connections.get(where) == obj:
|
|
self.remote_connections.pop(where)
|
|
def connect(self, location):
|
|
raise NotImplementedError
|
|
def create_user(self, netname):
|
|
raise NotImplementedError
|
|
def create_place(self, netname):
|
|
raise NotImplementedError
|
|
|
|
|
|
class ClientCenter(Center):
|
|
# connection to the homeserver, default return for find_remote
|
|
default_connection = None
|
|
options = {}
|
|
def get_option(self, option):
|
|
return self.options.get(option)
|
|
def connect(self, location):
|
|
# already connected or connecting?
|
|
a = self.find_remote(location)
|
|
if a:
|
|
return a
|
|
a = PSYCClientConnector(self, location)
|
|
return a.get_queue()
|
|
def find_remote(self, netname):
|
|
"""clients will send most things via their homeserver
|
|
p2p connections are an exception to that, but they will be
|
|
created by user objects"""
|
|
return Center.find_remote(self, netname) or Center.find_remote(self, self.default_connection)
|
|
def msg(self, vars, mc, data):
|
|
if not mc:
|
|
#print "warning: minor bug in pypsyc: msg() without mc"
|
|
return
|
|
if self.get_option('debug'):
|
|
dump_packet(">>>", vars, mc, data)
|
|
source = vars.get('_context') or vars.get('_source')
|
|
if not source: return # ignore empty packets?
|
|
obj = self.find_object(source)
|
|
if obj:
|
|
obj.msg(vars, mc, data)
|
|
return
|
|
print 'unhandled packet from %s'%source
|
|
# do something about it... pop up a window, etc
|
|
u = parseUNL(source)
|
|
if u['resource'] and u['resource'].startswith('~'):
|
|
# create a user object
|
|
obj = self.create_user(source)
|
|
obj.msg(vars, mc, data)
|
|
elif u['resource'] and u['resource'].startswith('@'):
|
|
# create a place object
|
|
obj = self.create_place(source)
|
|
obj.msg(vars, mc, data)
|
|
else:
|
|
print 'no handler for %s object'%(source)
|
|
def sendmsg(self, vars, mc, data):
|
|
target = vars.get('_target')
|
|
if not target: return
|
|
obj = self.find_remote(target)
|
|
if obj:
|
|
obj.msg(vars, mc, data)
|
|
else:
|
|
raise
|
|
# this should not happen!
|
|
|
|
|
|
# TODO this does not belong here
|
|
import sha
|
|
class Authenticator:
|
|
def __init__(self, center, uni, password):
|
|
self.center = center
|
|
self.uni = uni
|
|
self.password = password
|
|
def startLink(self):
|
|
print 'start link'
|
|
self.sendmsg({'_target' : uni }, '_request_link', '')
|
|
def msg_query_password(self, vars, mc, data):
|
|
if vars['_nonce'] and self.center.get_option('auth_sha'):
|
|
digest = sha.sha(vars['_nonce'] + self.password).hexdigest()
|
|
self.center.sendmsg({ '_method' : 'sha1',
|
|
'_password' : digest },
|
|
'_set_password', '')
|
|
elif self.center.get_option('auth_plain'):
|
|
self.sendmsg({ '_password' : self.password },
|
|
'_set_password', '')
|
|
else:
|
|
print 'no authorization method available!'
|
|
return
|
|
|
|
|
|
class Client(ClientCenter):
|
|
default_uni = ''
|
|
nick = ''
|
|
online_callback = None
|
|
def __init__(self, config):
|
|
self.config = config
|
|
|
|
self.default_uni = config.get('main', 'uni')
|
|
|
|
u = parseUNL(self.default_uni)
|
|
self.nick = u['resource'][1:]
|
|
self.default_connection = netLocation(self.default_uni)
|
|
if self.config.has_section('library'):
|
|
for option in self.config.options('library'):
|
|
self.options[option] = self.config.getboolean('library', option)
|
|
def online(self):
|
|
self.connect(self.default_connection)
|
|
# experimental API using the cool Deferred's
|
|
self.online_callback = defer.Deferred()
|
|
return self.online_callback
|
|
def gotOnline(self):
|
|
if self.online_callback:
|
|
self.online_callback.callback(None)
|
|
print 'connected to host of default location'
|
|
def sendmsg(self, vars, mc, data):
|
|
if vars.get('_nick') == '':
|
|
vars['_nick'] = self.nick
|
|
# TODO: I am not sure, it this is correct
|
|
if vars.get('_source') == '':
|
|
vars['_source'] = self.default_uni
|
|
ClientCenter.sendmsg(self, vars, mc, data)
|
|
|
|
|
|
class ServerCenter(Center):
|
|
unl2uni = {}
|
|
remote_contexts = {}
|
|
def __init__(self, location):
|
|
self.location = location
|
|
def msg(self, vars, mc, data):
|
|
if not mc:
|
|
return # ignore empty packets
|
|
source = vars['_source']
|
|
# TODO: auth check 'global' or per-object
|
|
if vars.has_key('_identification'):
|
|
print 'Identification of %s is %s'%(source, vars['_identification'])
|
|
context = vars.get('_context')
|
|
if context and not self.is_local_object(context):
|
|
target = context
|
|
else:
|
|
target = vars['_target']
|
|
if self.unl2uni.has_key(source):
|
|
"""local client who is using us as a proxy"""
|
|
self.unl2uni[source].sendmsg(vars, mc, data)
|
|
elif target:
|
|
obj = self.find_object(target)
|
|
if obj:
|
|
return obj.msg(vars, mc, data)
|
|
# probably object has to be created
|
|
if self.is_local_object(target):
|
|
u = parseUNL(target)
|
|
if (u['resource'] and u['resource'].startswith('@')):
|
|
obj = self.create_place(target)
|
|
if obj:
|
|
return obj.msg(vars, mc, data)
|
|
else:
|
|
vars['_target'], vars['_source'] = vars['_source'], vars['_target']
|
|
self.sendmsg(vars, '_error_unknown_name_place',
|
|
'No such place: [_source]')
|
|
elif (u['resource'] and u['resource'].startswith('~')):
|
|
obj = self.create_user(target)
|
|
if obj:
|
|
return obj.msg(vars, mc, data)
|
|
else:
|
|
vars['_target'], vars['_source'] = vars['_source'], vars['_target']
|
|
self.sendmsg(vars, '_error_unknown_name_user',
|
|
'No such user: [_source]')
|
|
elif u['resource']:
|
|
vars['_target'] = source
|
|
vars['_source'] = target
|
|
self.sendmsg(vars, '_error_unknown_name',
|
|
'No such object: [_source]')
|
|
else: # rootmsg
|
|
pass
|
|
elif context is not None and self.remote_contexts.has_key(context):
|
|
self.remote_contexts[context].castmsg(vars, mc, data)
|
|
else: # nonlocal target
|
|
print 'rejected relay %s from %s to %s'%(mc, source, target)
|
|
return # skip it for now
|
|
self.sendmsg({ '_target' : source, '_source' : self.location,
|
|
'_destination' : target },
|
|
'_error_rejected_relay',
|
|
'You are not allowed to send messages to [_destination]')
|
|
else:
|
|
# no target???
|
|
print 'no target in packet???'
|
|
|
|
def sendmsg(self, vars, mc, data):
|
|
target = vars['_target']
|
|
if self.is_local_object(target):
|
|
self.msg(vars, mc, data)
|
|
else:
|
|
u = parseUNL(target)
|
|
target = (self.find_object(target) or
|
|
self.find_remote(target) or
|
|
self.find_remote(target))
|
|
if not target and u['scheme'] == 'psyc':
|
|
q = self.connect(u['root'])
|
|
q.msg(vars, mc, data)
|
|
elif target:
|
|
target.msg(vars, mc, data)
|
|
else:
|
|
raise 'can not find %s'%(target) # programming error?
|
|
def register_object(self, netname, object):
|
|
self.object_handlers[netname.lower()] = object
|
|
def link_unl(self, unl, uni):
|
|
self.unl2uni[unl] = uni
|
|
def unlink_unl(self, unl, uni):
|
|
if self.unl2uni.has_key(unl) and self.unl2uni[unl] == uni:
|
|
self.unl2uni.pop(unl)
|
|
def is_local_object(self, location):
|
|
return netLocation(location) == netLocation(self.location)
|
|
def connect(self, location):
|
|
a = PSYCActiveConnector(self, location, self.location)
|
|
self.remote_connections[location] = a.get_queue()
|
|
return a.get_queue()
|
|
def find_remote(self, netname):
|
|
address = netLocation(netname)
|
|
return self.remote_connections.get(address, None)
|
|
def join_context(self, context, obj):
|
|
if self.is_local_object(context):
|
|
print 'skipping join to local context'
|
|
if not self.remote_contexts.has_key(context):
|
|
self.remote_contexts[context] = self.create_context(context)
|
|
self.remote_contexts[context].join(obj)
|
|
def leave_context(self, context, obj):
|
|
if self.is_local_object(context):
|
|
print 'skipping part of local context'
|
|
if self.remote_contexts.has_key(context):
|
|
self.remote_contexts[context].leave(obj)
|
|
# TODO possibly clean_up that context
|
|
def create_context(self, netname):
|
|
"""this is how we create context slaves"""
|
|
raise NotImplementedError
|