summaryrefslogtreecommitdiff
path: root/bouncer.py
diff options
context:
space:
mode:
Diffstat (limited to 'bouncer.py')
-rw-r--r--bouncer.py335
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)