diff options
Diffstat (limited to 'bouncer.py')
-rw-r--r-- | bouncer.py | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/bouncer.py b/bouncer.py new file mode 100644 index 0000000..be4231d --- /dev/null +++ b/bouncer.py @@ -0,0 +1,335 @@ +#!/usr/bin/python +import socket, ssl, os, re, time, sys, string +from threading import Thread +import Queue + +class Bouncer (Thread): + def __init__(self, addr, port, servers, ssl=False, certfile=None, keyfile=None, ignore=None): + self.__name__="Bouncer for pyIRC" + self.__version__="1.0.0alpha1" + self.__author__="Brian Sherson" + self.__date__="Apr 21, 2013" + #print "Initializing ListenThread..." + self.addr=addr + self.port=port + self.servers=servers + self.socket=s=socket.socket() + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.ssl=ssl + self.certfile=certfile + self.keyfile=keyfile + s.bind((self.addr,self.port)) + self.connections=[] + for server in servers.values(): server.modules.append(self) + self.ignore=ignore + self.whoexpected=[] + Thread.__init__ ( self ) + + def run(self): + self.socket.listen(5) + #print ((self,"Now listening on port "+str(self.port))) + while True: + try: + (connection,addr)=self.socket.accept() + if self.ssl: + connection=ssl.wrap_socket(connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) + try: + hostname, aliaslist, addresslist = socket.gethostbyaddr(addr[0]) + addr = (hostname, addr[1]) + except: + pass + #print ((self,"New client connecting from %s:%s"%addr)) + except socket.error: + print "Shutting down Listener" + self.socket.close() + #raise + sys.exit() + bouncer=BouncerConnection(self, connection, addr) + bouncer.daemon=True + #self.connections.append(bouncer) + bouncer.start() + #ccrecv.start() + time.sleep(0.5) + try: + self.socket.close() + except: pass + def onRecv(self, IRC, line, data): + if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): + if data: + (origin, ident, host, cmd, target, params, extinfo)=data + #print data + if cmd=="352": ### Who reply + if len(self.whoexpected) and self.whoexpected[0] in self.connections and self.whoexpected[0].IRC==IRC: + self.whoexpected[0].connection.send(line+"\n") + elif cmd=="315": ### End of Who reply + if len(self.whoexpected) and self.whoexpected[0] in self.connections and self.whoexpected[0].IRC==IRC: + self.whoexpected[0].connection.send(line+"\n") + del self.whoexpected[0] + else: + for bouncer in self.connections: + #print bouncer.IRC + #print IRC + #print line + if bouncer.IRC==IRC: bouncer.connection.send(line+"\n") + def onSend(self, IRC, line, data, origin): + #print "Bouncer onSend" + if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): + (cmd, target, params, extinfo)=data + if cmd in ("PRIVMSG", "NOTICE"): + for bouncer in self.connections: + if bouncer==origin: continue + #print bouncer.IRC + #print IRC + if bouncer.IRC==IRC: bouncer.connection.send(":%s!%s@%s %s\n" % (bouncer.IRC.identity.nick, bouncer.IRC.identity.idnt, bouncer.IRC.identity.host, line)) + elif cmd=="WHO": + #print origin, line + self.whoexpected.append(origin) + def stop(self): + self.socket.shutdown(0) + def onDisconnect(self, IRC): + for bouncer in self.connections: + if bouncer.IRC==IRC: + quitmsg="Bouncer has been disconnected from IRC" + try: + bouncer.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.nick, self.addr[0], quitmsg)) + bouncer.connection.close() + bouncer.connection.shutdown(0) + except: + pass + if bouncer in self.connections: + self.connections.remove(bouncer) + +class BouncerConnection (Thread): + def __init__(self, bouncerlistener, connection, addr): + #print "Initializing ListenThread..." + self.bouncerlistener=bouncerlistener + self.connection=connection + self.addr=addr + self.IRC=None + self.pwd=None + self.nick=None + self.idnt=None + self.realname=None + self.addr=addr + Thread.__init__ ( self ) + + def stop(self): + self.connection.shutdown(0) + + def run(self): + #print "Bouncer Connection Started" + r=self.connection.makefile("r") + w=self.connection.makefile("w") + k=0 + while self.pwd==None or self.nick==None or self.idnt==None: + line=r.readline().rstrip() + match=re.findall("^PASS :?(.*)$", line, re.I) + if match: + self.pwd=match[0] + match=re.findall("^NICK :?(.*)$", line, re.I) + if match: + self.nick=match[0] + match=re.findall("^USER\\s+(.+?)\\s+(.+?)\\s+(.+?):(.*)$", line, re.I) + if match: + self.idnt, a, b, self.realname=match[0] + if k>10: + self.connection.send("ERROR :Closing link: (%s@%s) [Access Denied]\n" % (self.nick, self.addr[0])) + self.connection.close() + sys.exit() + k+=1 + for idnt, pwd in self.bouncerlistener.servers.keys(): + if idnt.lower()==self.idnt.lower() and pwd==self.pwd: + self.IRC=self.bouncerlistener.servers[idnt, pwd] + if self.IRC==None or not self.IRC.registered: + self.connection.send("ERROR :Closing link: (%s@%s) [Access Denied]\n" % (self.nick, self.addr[0])) + self.connection.close() + sys.exit() + + for bouncer in self.bouncerlistener.connections: + try: + bouncer.connection.send(":%s!%s@%s NOTICE %s :*** Bouncer Connection to %s originated from %s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, bouncer.IRC.identity.nick, self.IRC, self.addr[0])) + except: + self.bouncerlistener.connections.remove(bouncer) + + self.connection.send(":%s 001 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.welcome)) + self.connection.send(":%s 002 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.hostinfo)) + self.connection.send(":%s 003 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servinfo)) + self.connection.send(":%s 004 %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.serv004)) + for params in self.IRC.serv005: + self.connection.send(":%s 005 %s %s :are supported by this server\n" % (self.IRC.serv, self.IRC.identity.nick, params)) + + self.connection.send(":%s 375 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdgreet)) + for motdline in self.IRC.motd: + self.connection.send(":%s 372 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, motdline)) + self.connection.send(":%s 376 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend)) + + self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) + if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: + self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.server, self.IRC.identity.nick, self.IRC.identity.snomask)) + + for channel in self.IRC.identity.channels: + self.connection.send(":%s!%s@%s JOIN :%s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, channel.name)) + self.connection.send(":%s 332 %s %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topic)) + self.connection.send(":%s 333 %s %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topicsetby, channel.topictime)) + secret="s" in channel.modes.keys() and channel.modes["s"] + private="p" in channel.modes.keys() and channel.modes["p"] + namesusers=[] + modes, symbols=self.IRC.supports["PREFIX"] + self.connection.send(":%s 353 %s %s %s :%s\n" % ( + self.IRC.serv, + self.IRC.identity.nick, + "@" if secret else ("*" if private else "="), + channel.name, + string.join([string.join([symbols[k] if modes[k] in channel.modes.keys() and user in channel.modes[modes[k]] else "" for k in xrange(len(modes))],"")+user.nick for user in channel.users])) + ) + + self.bouncerlistener.connections.append(self) + + quitmsg="Connection Closed" + readbuf="" + linebuf=[] + + while True: + while len(linebuf)==0: + timestamp=reduce(lambda x,y: x+":"+y,[str(t).rjust(2,"0") for t in time.localtime()[0:6]]) + try: + read=self.connection.recv(512) + except: + exc,excmsg,tb=sys.exc_info() + print >>sys.stderr, "%(timestamp)s *** %(exc)s: %(excmsg)s" % vars() + server, port=self.addr + #server=self.addr[0] + #port=self.port + print >>sys.stderr, "%(timestamp)s *** Connection from %(server)s:%(port)s terminated" % vars() + self.bouncerlistener.connections.remove(self) + sys.stderr.flush() + raise + if read=="": + server, port=self.addr + #port=self.port + print >>sys.stderr, "%(timestamp)s *** Connection from %(server)s:%(port)s terminated" % vars() + self.bouncerlistener.connections.remove(self) + sys.stderr.flush() + sys.exit() + readbuf+=read + lastlf=readbuf.rfind("\n") + if lastlf>=0: + linebuf.extend(string.split(readbuf[0:lastlf], "\n")) + readbuf=readbuf[lastlf+1:] + + line=string.rstrip(linebuf.pop(0)) + + match=re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) + if len(match)==0: continue + (cmd, target, params, extinfo)=match[0] + + if cmd.upper()=="QUIT": + quitmsg=extinfo + break + elif cmd.upper()=="PING": + try: + self.connection.send(":%s PONG %s :%s\n" % (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick)) + except: + sys.exit() + continue + elif cmd.upper() in ("PRIVMSG", "NOTICE"): + #print line + ctcp=re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",extinfo) + if ctcp: + (ctcptype,ext)=ctcp[0] + if ctcptype=="LAGCHECK": + try: + self.connection.send(":%s!%s@%s %s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) + except: + self.connection.remove(self) + sys.exit() + elif ctcptype=="ACTION": + self.IRC.raw(line, origin=self) + #for bouncer in self.bouncerlistener.connections: + # if bouncer!=self and bouncer.IRC==self.IRC: + # try: + # bouncer.connection.send(":%s!%s@%s %s\n"%(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) + # except: + # self.bouncerlistener.connections.remove(bouncer) + else: + self.IRC.raw(line, origin=self) + else: + self.IRC.raw(line, origin=self) + #for bouncer in self.bouncerlistener.connections: + # if bouncer!=self and bouncer.IRC==self.IRC: + # try: + # bouncer.connection.send(":%s!%s@%s %s\n"%(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) + # except: + # self.bouncerlistener.connections.remove(bouncer) + elif cmd.upper() == "MODE": + #print "ddd", target, params, self.IRC.supports["CHANTYPES"] + if target and target[0] in self.IRC.supports["CHANTYPES"]: + if params=="": + channel=self.IRC.channel(target) + modes=channel.modes.keys() + modestr="".join([mode for mode in modes if mode not in self.IRC.supports["CHANMODES"][0]+self.IRC.supports["PREFIX"][0] and channel.modes[mode]]) + params=" ".join([channel.modes[mode] for mode in modes if mode in self.IRC.supports["CHANMODES"][1]+self.IRC.supports["CHANMODES"][2] and channel.modes[mode]]) + self.connection.send(":%s 324 %s %s +%s %s\n" % (self.IRC.server, self.IRC.identity.nick, channel.name, modestr, params)) + elif re.match("^\\+?[%s]+$"%self.IRC.supports["CHANMODES"][0], params) and extinfo=="": + #print "ddd Mode List Request", params + channel=self.IRC.channel(target) + listnumerics=dict(b=(367, 368, "channel ban list"), e=(348, 349, "Channel Exception List"), I=(346, 347, "Channel Invite Exception List"), w=(910, 911, "Channel Access List"), g=(941, 940, "chanel spamfilter list"), X=(954, 953, "channel exemptchanops list")) + redundant=[] + for mode in params.lstrip("+"): + if mode in redundant or mode not in listnumerics.keys(): continue + i,e,l=listnumerics[mode] + if mode in channel.modes.keys(): + for (mask, setby, settime) in channel.modes[mode]: + self.connection.send(":%s %d %s %s %s %s %s\n" % (self.IRC.server, i, channel.context.identity.nick, channel.name, mask, setby, settime)) + self.connection.send(":%s %d %s %s :End of %s\n" % (self.IRC.server, e, channel.context.identity.nick, channel.name, l)) + redundant.append(mode) + else: + self.IRC.raw(line, origin=self) + elif params=="" and target.lower()==self.IRC.identity.lower(): + self.connection.send(":%s 221 %s +%s\n" % (self.IRC.server, self.IRC.identity.nick, channel.name, self.IRC.identity.modes)) + if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: + self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.server, self.IRC.identity.nick, channel.name, self.IRC.identity.snomask)) + else: + self.IRC.raw(line, origin=self) + else: + self.IRC.raw(line, origin=self) + + + + continue + #print line + match=re.findall("^quit(?:\\s:?(.*))?$", line, re.I) + #print match + if match: + quitmsg=match[0] + break + else: + #match=re.findall("^ping(?:\\s.*:?(.*))?$", line) + self.IRC.raw(line) + match=re.findall("^PRIVMSG (\\S+) :(.*)$", line, re.I) + #print match + if match: + (origin, ident, host, cmd, params)=(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, "PRIVMSG", "") + target, extinfo=match[0] + data=(origin, ident, host, cmd, target, params, extinfo) + modules=list(self.IRC.modules) + for channel in self.IRC.channels: + for module in channel.modules: + if module not in modules: modules.append(module) + for module in set(modules): + if module!=self.bouncerlistener: + #print ":%s!%s@%s %s"%(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line) + try: + module.process(self.IRC, ":%s!%s@%s %s"%(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line), data) + except: + pass + else: + for bouncer in self.bouncerlistener.connections: + if bouncer!=self and bouncer.IRC==self.IRC: + try: + bouncer.connection.send(":%s!%s@%s %s\n"%(self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) + except: + self.bouncerlistener.connections.remove(bouncer) + self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.nick, self.addr[0], quitmsg)) + self.connection.close() + self.bouncerlistener.connections.remove(self) |