From: John Runyon Date: Wed, 20 Dec 2023 03:44:57 +0000 (-0700) Subject: start work on sockets module, not ready to use yet X-Git-Url: https://jfr.im/git/erebus.git/commitdiff_plain/aaa8eb8dc2f1e1237d44344488ec4cde9c0b0076 start work on sockets module, not ready to use yet --- diff --git a/config.py b/config.py index 5538dc7..9755f84 100644 --- a/config.py +++ b/config.py @@ -12,7 +12,7 @@ else: class Config(object): def __init__(self, filename, writeout=True): - self.__dict__['config'] = ConfigParser.RawConfigParser() + self.__dict__['config'] = ConfigParser.RawConfigParser(delimiters=('=',)) self.__dict__['filename'] = filename self.__dict__['writeout'] = writeout self.config.read(filename) diff --git a/modlib.py b/modlib.py index 91e0584..0627618 100644 --- a/modlib.py +++ b/modlib.py @@ -170,6 +170,37 @@ class modlib(object): return func return realhook + def bind(self, bindto): + """Used as a decorator on a class which implements getdata and parse methods. + See modules/sockets.py for an example. + Takes an arg like: + [unix:]/foo/bar + [udp|tcp:][ip:]port + """ + if len(bindto) == 0: + raise Exception('bindto must have a value') + if bindto[0] == '/': + return self._hooksocket(socket.AF_UNIX, socket.SOCK_STREAM, bindto) + if len(bindto) > 5 and bindto[0:5] == 'unix:': + return self._hooksocket(socket.AF_UNIX, socket.SOCK_STREAM, bindto[5:]) + af = socket.AF_INET + ty = socket.SOCK_STREAM + host = '0.0.0.0' + if len(bindto) > 4 and bindto[0:4] == 'udp:': + ty = socket.SOCK_DGRAM + bindto = bindto[4:] + if len(bindto) > 4 and bindto[0:4] == 'tcp:': + bindto = bindto[4:] + print(repr(bindto), ':' in bindto) + if ':' in bindto: + print(bindto) + pieces = bindto.rsplit(':', 1) + host = pieces[0] + bindto = pieces[1] + print(pieces,host,bindto) + port = int(bindto) + return self._hooksocket(af, ty, (host, port)) + def bind_tcp(self, host, port): return self._hooksocket(socket.AF_INET, socket.SOCK_STREAM, (host, port)) def bind_udp(self, host, port): @@ -196,7 +227,7 @@ class modlib(object): self.sockets.append((sock,obj)) sock.listen(5) self.parent.newfd(obj, sock.fileno()) - self.parent.log(repr(obj), '?', 'Socket ready to accept new connections') + self.parent.log(repr(obj), '?', 'Socket ready to accept new connections (%r, %r, %r, %r)' % (af, ty, address, cls)) def _destroy_socket(self, sock, obj): obj.close() diff --git a/modules/basic_socket.py b/modules/example_socket.py similarity index 100% rename from modules/basic_socket.py rename to modules/example_socket.py diff --git a/modules/sockets.py b/modules/sockets.py new file mode 100644 index 0000000..0e96e04 --- /dev/null +++ b/modules/sockets.py @@ -0,0 +1,84 @@ +# Erebus IRC bot - Author: Erebus Team +# vim: fileencoding=utf-8 +# This file is released into the public domain; see http://unlicense.org/ + +# Note: this module doesn't do any kind of authentication, so anyone who can connect to the bound port can spam you. + +# module info +modinfo = { + 'author': 'Erebus Team', + 'license': 'public domain', + 'compatible': [0], + 'depends': [], + 'softdeps': [], +} + +# preamble +import modlib +lib = modlib.modlib(__name__) +def modstart(parent, *args, **kwargs): + gotParent(parent) + return lib.modstart(parent, *args, **kwargs) +modstop = lib.modstop + +# module code + +def gotParent(parent): + for bindto, channel in parent.cfg.items('sockets'): + @lib.bind(bindto, data=channel) + class BasicServer(object): + def __init__(self, sock, data): + # The lambda bit is needed to make this copy the value-at-definition instead of using the closure value-at-runtime + self.chan = data + print(repr(self.chan)) + self.buffer = b'' + self.sock = sock + + def getdata(self): + recvd = self.sock.recv(8192) + if recvd == b"": # EOF + if len(self.buffer) != 0: + # Process what's left in the buffer. We'll get called again after. + remaining_buf = self.buffer.decode('utf-8', 'backslashreplace') + self.buffer = b"" + return [remaining_buf] + else: + # Nothing left in the buffer. Return None to signal the core to close this socket. + return None + self.buffer += recvd + lines = [] + + while b"\n" in self.buffer: + pieces = self.buffer.split(b"\n", 1) + s = pieces[0].decode('utf-8', 'backslashreplace').rstrip("\r") + lines.append(pieces[0].decode('utf-8', 'backslashreplace')) + self.buffer = pieces[1] + + return lines + + def parse(self, line): + bot = lib.parent.randbot() + maxlen = bot.maxmsglen() - len("PRIVMSG :") - len(self.chan) + while len(line) > maxlen: + cutat = line.rfind(' ', 0, maxlen) + if cutat == -1: + cutat = maxlen + bot.msg(self.chan, line[0:cutat]) + line = line[cutat:].strip() + bot.msg(self.chan, line) + + def send(self, line): + if lib.parent.parent.cfg.getboolean('debug', 'io'): + lib.parent.log(str(self), 'O', line) + self.sock.sendall(line.encode('utf-8', 'backslashreplace')+b"\r\n") + + def _getsockerr(self): + try: # SO_ERROR might not exist on all platforms + return self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) + except: + return None + + def __str__(self): + return '%s#%d' % (__name__, self.sock.fileno()) + def __repr__(self): + return '<%s #%d %s:%d>' % ((__name__, self.sock.fileno())+self.sock.getpeername())