from twisted.internet import reactor, defer, error from twisted.internet.protocol import Protocol, ClientCreator from twisted.python import log import time # see http://wiki.anope.org/index.php/IRCd:Bahamut:Protocol __package__ = "FlaxServ" __version__ = "0.1" class ConnectionLost(Exception): pass class IRCConnection(Protocol): LOG_PROTOCOL = True def __init__(self, hostname): self.hostname = hostname def send(self, *args, **kw): if 'prefix' in kw: args = (":%s" % kw['prefix'],) + args if 'data' in kw: args += (':' + kw['data'],) msg = ' '.join(args) if self.LOG_PROTOCOL: log.msg("> %s" % msg) self.transport.write("%s\r\n" % msg) def handleMsg(self, args, prefix, data): if not args: return cmd = args.pop(0).upper() attr = 'irc_%s' % cmd method = getattr(self, attr, None) if method: method(args, prefix, data) return attr # irc command handlers def irc_PING(self, args, prefix, data): self.send('PONG', self.hostname, prefix=self.hostname, *(args + [data])) # called by twisted def connectionMade(self): log.msg("Connected to server") self.__buffer = "" def connectionLost(self, reason): if isinstance(reason, error.ConnectionDone): # connection closed cleanly. # XXX todo cleanup return #raise ConnectionLost, reason def dataReceived(self, data): data = data.replace('\r', '\n') data = data.replace('\n\n', '\n') self.__buffer += data msgs = self.__buffer.split('\n') self.__buffer = msgs.pop() for msg in msgs: if self.LOG_PROTOCOL: log.msg("< %s" % msg) if len(msg) == 0: continue prefix = data = None if msg[0] == ':': if not ' ' in msg: # malformed packet pass (prefix, msg) = msg[1:].split(' ', 1) if ' :' in msg: (msg, data) = msg.split(' :', 1) args = msg.split() self.handleMsg(args, prefix, data) class ServicesConnection(IRCConnection): def __init__(self, hostname, *services): self.loginDeferred = None self.services = map(lambda s: s(self), services) IRCConnection.__init__(self, hostname) def login(self, password): self.loginDeferred = defer.Deferred() self.send('PASS', password, data="TS") self.send('CAPAB', 'SSJOIN', 'NICKIP', 'TSMODE') # these are required; possibly support more later self.send('SERVER', self.hostname, '1', data="%s-%s" % (__package__, __version__)) self.send('SVINFO', '3', '1', '0', data="%d" % time.time()) for service in self.services: service.login() return self.loginDeferred def handleMsg(self, args, prefix, data): attr = IRCConnection.handleMsg(self, args, prefix, data) for service in self.services: method = getattr(service, attr, None) if method: method(args, prefix, data) return attr def registerNick(self, nick, mode, desc, hostname=None): if not hostname: hostname = self.hostname self.send('NICK', nick, '1', "%d" % time.time(), mode, 'services', hostname, self.hostname, '0', '0', data=desc) self.send('SQLINE', nick, data="Reserved for services") # irc actions def notice(self, frm, to, text): self.send('NOTICE', to, prefix=frm, data=text) def kill(self, user, reason=""): self.send('SVSKILL', user, prefix=self.hostname, data=reason) def changeNick(self, frm, to): self.send('SVSNICK', frm, to, prefix=self.hostname, data="%d" % time.time()) # irc command handlers def irc_PING(self, args, prefix, data): IRCConnection.irc_PING(self, args, prefix, data) if self.loginDeferred: # server will ping us after a successful login self.loginDeferred.callback(self) self.loginDeferred = None def irc_CAPAB(self, args, prefix, data): self.serverCapabilities = args log.msg("Server capabilities:", ', '.join(args)) # methods called by twisted def connectionMade(self): IRCConnection.connectionMade(self) self.serverCapabilities = [] self.serverName = None def connectionLost(self, reason): if self.loginDeferred: self.loginDeferred.errback(ConnectionLost(reason)) self.loginDeferred = None IRCConnection.connectionLost(self, reason) def connect(server, hostname, password, services=[], connhandler=ServicesConnection): # server is ('addr', port) d = defer.Deferred() ClientCreator(reactor, connhandler, hostname, *services ).connectTCP(*server ).addErrback(d.errback ).addCallback(lambda conn: conn.login(password).chainDeferred(d)) return d if __name__ == '__main__': import sys log.startLogging(sys.stdout) connect(('127.0.0.1', 6668), 'services.pentabarf.net', 'servpass').addCallback(lambda _: log.msg("connect callback fired") ).addErrback(log.err) reactor.run()