def serve(sockname, incoming): if os.path.exists(sockname): os.remove(sockname) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind(sockname) sock.listen(1) def listen(sock): while True: connection, client = sock.accept() def handle(connection, client): try: for octets in connection.makefile("rb"): try: text = octets.decode("ascii", "replace") text = text.strip("\n") if " " in text: instruction, data = text.split(" ", 1) args = common.b64unpickle(data) else: instruction, args = text, tuple() incoming.put((instruction,) + args) except Exception as err: debug("ERROR!", err.__class__.__name__, err) finally: connection.close() common.thread(handle, connection, client) common.thread(listen, sock)
def run(name, safe, irc): if name in self.events: for function in self.events[name]: if not function.saxo_synchronous: common.thread(safe, function, irc) else: safe(function, irc)
def start(base): # TODO: Check when two clients are running common.exit_cleanly() # http://stackoverflow.com/questions/11423225 # IGN rather than DFL, otherwise Popen.communicate can quit saxo signal.signal(signal.SIGPIPE, signal.SIG_IGN) opt = configparser.ConfigParser(interpolation=None) config = os.path.join(base, "config") if not os.path.isfile(config): error("missing config file in: `%s`" % config, E_NO_CONFIG) opt.read(config) # TODO: Defaulting? # TODO: Warn if the config file is widely readable? common.populate(saxo_path, base) sockname = os.path.join(base, "client.sock") serve(sockname, incoming) os.chmod(sockname, 0o600) # NOTE: If using os._exit, this doesn't work def remove_sock(sockname): if os.path.exists(sockname): os.remove(sockname) atexit.register(remove_sock, sockname) common.thread(scheduler.start, base, incoming) saxo = Saxo(base, opt) saxo.run()
def start(base): # TODO: Check when two clients are running common.exit_cleanly() # http://stackoverflow.com/questions/11423225 # IGN rather than DFL, otherwise Popen.communicate can quit saxo signal.signal(signal.SIGPIPE, signal.SIG_IGN) opt = configparser.ConfigParser(interpolation=None) config = os.path.join(base, "config") if not os.path.isfile(config): error("missing config file in: `%s`" % config, E_NO_CONFIG) opt.read(config) # TODO: Defaulting? # TODO: Warn if the config file is widely readable? sockname = os.path.join(base, "client.sock") serve(sockname, incoming) os.chmod(sockname, 0o600) # NOTE: If using os._exit, this doesn't work def remove_sock(sockname): if os.path.exists(sockname): os.remove(sockname) atexit.register(remove_sock, sockname) sched = scheduler.Scheduler(incoming) common.thread(sched.start, base) saxo = Saxo(base, opt) saxo.run()
def command(self, msg): cmd, arg = msg.cmd, msg.arg path = command_path(self.base, cmd) if path is None: return if random.random() < .001 and msg.nick.lower().strip("_-`") == "ramond": self.send("PRIVMSG", msg.sender, random.choice(["Mum's the word.", "Silence is golden.", "Don't flood the channel."])) return def command_process(env, cmd, path, arg): outs = process(env, cmd, path, arg) if outs: self.send("PRIVMSG", msg.sender, outs) env = self.environment_cache.copy() env["SAXO_NICK"] = msg.nick env["SAXO_SENDER"] = msg.sender if msg.sender in self.links: env["SAXO_URL"] = self.links[msg.sender] if msg.authorised(): env["SAXO_AUTHORISED"] = "1" env["SAXO_LF2STREAM"] = self.lf2stream common.thread(command_process, env, cmd, path, arg)
def serve(sockname, incoming): if os.path.exists(sockname): os.remove(sockname) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.bind(sockname) sock.listen(1) def listen(sock): while True: connection, client = sock.accept() def handle(connection, client): try: for octets in connection.makefile("rb"): try: text = octets.decode("ascii", "replace") text = text.strip("\n") if " " in text: instruction, data = text.split(" ", 1) args = common.b64unpickle(data) else: instruction, args = text, tuple() incoming.put((instruction, ) + args) except Exception as err: debug("ERROR!", err.__class__.__name__, err) finally: connection.close() common.thread(handle, connection, client) common.thread(listen, sock)
def start(base): # TODO: Check when two clients are running common.exit_cleanly() # http://stackoverflow.com/questions/11423225 signal.signal(signal.SIGPIPE, signal.SIG_DFL) plugins = os.path.join(base, "plugins") if not os.path.isdir(plugins): common.error("no plugins directory: `%s`" % plugins, E_NO_PLUGINS) # TODO: Check for broken symlinks common.populate(saxo_path, base) opt = configparser.ConfigParser(interpolation=None) config = os.path.join(base, "config") opt.read(config) # TODO: Defaulting? # TODO: Warn if the config file is widely readable? sockname = os.path.join(base, "client.sock") serve(sockname, incoming) os.chmod(sockname, 0o600) # NOTE: If using os._exit, this doesn't work def remove_sock(sockname): if os.path.exists(sockname): os.remove(sockname) atexit.register(remove_sock, sockname) common.thread(scheduler.start, base, incoming) saxo = Saxo(base, opt) saxo.run()
def connect(self): try: self.connect_sock() except Exception as err: raise SaxoConnectionError(str(err)) self.first = True # TODO: Reset other state? e.g. self.address receiving = (socket_receive, self.sock) self.receiving_thread = common.thread(*receiving) sending = (socket_send, self.sock, "flood" in self.opt["client"]) self.sending_thread = common.thread(*sending)
def scheduled_command(self, cmd, arg, sender=None): path = command_path(self.base, cmd) if path is None: return def command_process(env, cmd, path, arg): outs = process(env, cmd, path, arg) if outs and (sender is not None): self.send("PRIVMSG", sender, outs) env = self.environment_cache.copy() env["SAXO_SCHEDULED"] = "1" common.thread(command_process, env, cmd, path, arg)
def command(self, prefix, sender, identified, cmd, arg): if ("\x00" in cmd) or (os.sep in cmd) or ("." in cmd): return path = os.path.join(self.base, "commands", cmd) def process(env, path, arg): octets = arg.encode("utf-8", "replace") try: proc = subprocess.Popen([path, octets], env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE) except PermissionError: outs = "The command file does not have executable permissions" except FileNotFoundError: # Might have been removed just after running this thread return else: try: outs, errs = proc.communicate(octets + b"\n", timeout=12) except subprocess.TimeoutExpired: proc.kill() # TODO: Use actual prefix outs = "Sorry, %s took too long" % cmd else: outs = outs.decode("utf-8", "replace") if "\n" in outs: outs = outs.splitlines()[0] code = proc.returncode or 0 # Otherwise: TypeError: unorderable types: NoneType() > int() if (code > 0) and (not outs): # TODO: Use actual prefix outs = "Sorry, %s responded with an error" % cmd if outs: self.send("PRIVMSG", sender, outs) if os.path.isfile(path): env = self.environment_cache.copy() env["SAXO_NICK"] = prefix[0] env["SAXO_SENDER"] = sender if identified == True: env["SAXO_IDENTIFIED"] = "1" elif identified == False: env["SAXO_IDENTIFIED"] = "0" if sender in self.links: env["SAXO_URL"] = self.links[sender] common.thread(process, env, path, arg)
def connect(self): host = self.opt["server"]["host"] port = int(self.opt["server"]["port"]) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) if "ssl" in self.opt["server"]: import ssl debug("Warning: Using SSL, but not validating the cert!") self.sock = ssl.wrap_socket( self.sock, server_side=False, cert_reqs=ssl.CERT_NONE) # TODO: or CERT_REQUIRED debug("Connecting to %s:%s" % (host, port)) self.sock.connect((host, port)) self.first = True common.thread(socket_receive, self.sock) common.thread(socket_send, self.sock, "flood" in self.opt["client"])
def command(self, msg): cmd, arg = msg.cmd, msg.arg path = command_path(self.base, cmd) if path is None: return def command_process(env, cmd, path, arg): outs = process(env, cmd, path, arg) if outs: self.send("PRIVMSG", msg.sender, outs) env = self.environment_cache.copy() env["SAXO_NICK"] = msg.nick env["SAXO_SENDER"] = msg.sender if msg.sender in self.links: env["SAXO_URL"] = self.links[msg.sender] if msg.authorised(): env["SAXO_AUTHORISED"] = "1" common.thread(command_process, env, cmd, path, arg)
def listen(sock): while True: connection, client = sock.accept() def handle(connection, client): try: for octets in connection.makefile("rb"): try: text = octets.decode("ascii", "replace") text = text.strip("\n") if " " in text: instruction, data = text.split(" ", 1) args = common.b64unpickle(data) else: instruction, args = text, tuple() incoming.put((instruction,) + args) except Exception as err: debug("ERROR!", err.__class__.__name__, err) finally: connection.close() common.thread(handle, connection, client)
def listen(sock): while True: connection, client = sock.accept() def handle(connection, client): try: for octets in connection.makefile("rb"): try: text = octets.decode("ascii", "replace") text = text.strip("\n") if " " in text: instruction, data = text.split(" ", 1) args = common.b64unpickle(data) else: instruction, args = text, tuple() incoming.put((instruction, ) + args) except Exception as err: debug("ERROR!", err.__class__.__name__, err) finally: connection.close() common.thread(handle, connection, client)
def test(args): if args.directory is not None: common.error("Tests cannot be run in conjunction with a directory") import queue import shutil import socket import subprocess import tempfile # Save PEP 3122! if "." in __name__: from . import saxo else: import saxo saxo_script = sys.modules["__main__"].__file__ saxo_test_server = os.path.join(saxo.path, "test", "server.py") tmp = tempfile.mkdtemp() outgoing = queue.Queue() if not sys.executable: common.error("Couldn't find the python executable") if not os.path.isdir(tmp): common.error("There is no %s directory" % tmp) print("python executable:", sys.executable) print("saxo path:", saxo.path) print("saxo script:", saxo_script) print("saxo test server:", saxo_test_server) print() def run_server(): server = subprocess.Popen([sys.executable, "-u", saxo_test_server], stdout=subprocess.PIPE) for line in server.stdout: line = line.decode("utf-8", "replace") line = line.rstrip("\n") outgoing.put("S: " + line) outgoing.put("Server finished") def run_client(): saxo_test = os.path.join(tmp, "saxo-test") outgoing.put("Running in %s" % saxo_test) cmd = [sys.executable, saxo_script, "create", saxo_test] code = subprocess.call(cmd) if code: print("Error creating the client configuration") sys.exit(1) test_config = os.path.join(saxo.path, "test", "config") saxo_test_config = os.path.join(saxo_test, "config") with open(test_config) as f: with open(saxo_test_config, "w") as w: for line in f: line = line.replace("localhost", socket.gethostname()) w.write(line) # shutil.copy2(test_config, saxo_test_config) client = subprocess.Popen([sys.executable, "-u", saxo_script, "-f", "start", saxo_test], stdout=subprocess.PIPE) for line in client.stdout: line = line.decode("utf-8", "replace") line = line.rstrip("\n") outgoing.put("C: " + line) manifest01 = {"commands", "config", "database.sqlite3", "pid", "plugins"} manifest02 = manifest01 | {"client.sock"} if set(os.listdir(saxo_test)) == manifest01: shutil.rmtree(saxo_test) elif set(os.listdir(saxo_test)) == manifest02: outgoing.put("Warning: client.sock had not been removed") shutil.rmtree(saxo_test) else: outgoing.put("Refusing to delete the saxo test directory") outgoing.put("Data was found which does not match the manifest") outgoing.put(saxo_test) common.thread(run_server) common.thread(run_client) error = False completed = False client_buffer = [] while True: line = outgoing.get() if line.startswith("S: "): print(line) if line.startswith("S: ERROR"): error = True if line.startswith("S: Tests complete"): completed = True if not line.startswith("S: Test"): for c in client_buffer: print(c) del client_buffer[:] elif line.startswith("C: "): client_buffer.append(line) else: print(line) sys.stdout.flush() if line == "Server finished": break if not os.listdir(tmp): os.rmdir(tmp) else: print("Warning: Did not remove:", tmp) if completed and (not error): sys.exit(0) else: sys.exit(1)
def test(args): if args.directory is not None: common.error("Tests cannot be run in conjunction with a directory") import queue import shutil import socket import subprocess import tempfile # Save PEP 3122! if "." in __name__: from . import saxo else: import saxo saxo_script = sys.modules["__main__"].__file__ saxo_test_server = os.path.join(saxo.path, "test", "server.py") tmp = tempfile.mkdtemp() outgoing = queue.Queue() if not sys.executable: common.error("Couldn't find the python executable") if not os.path.isdir(tmp): common.error("There is no %s directory" % tmp) print("python executable:", sys.executable) print("saxo path:", saxo.path) print("saxo script:", saxo_script) print("saxo test server:", saxo_test_server) print() def run_server(): server = subprocess.Popen([sys.executable, "-u", saxo_test_server], stdout=subprocess.PIPE) for line in server.stdout: line = line.decode("utf-8", "replace") line = line.rstrip("\n") outgoing.put("S: " + line) outgoing.put("Server finished") def run_client(): saxo_test = os.path.join(tmp, "saxo-test") outgoing.put("Running in %s" % saxo_test) cmd = [sys.executable, saxo_script, "create", saxo_test] code = subprocess.call(cmd) if code: print("Error creating the client configuration") sys.exit(1) test_config = os.path.join(saxo.path, "test", "config") saxo_test_config = os.path.join(saxo_test, "config") with open(test_config) as f: with open(saxo_test_config, "w") as w: for line in f: line = line.replace("localhost", socket.gethostname()) w.write(line) # shutil.copy2(test_config, saxo_test_config) client = subprocess.Popen( [sys.executable, "-u", saxo_script, "-f", "start", saxo_test], stdout=subprocess.PIPE) for line in client.stdout: line = line.decode("utf-8", "replace") line = line.rstrip("\n") outgoing.put("C: " + line) manifest01 = { "commands", "config", "database.sqlite3", "pid", "plugins" } manifest02 = manifest01 | {"client.sock"} if set(os.listdir(saxo_test)) == manifest01: shutil.rmtree(saxo_test) elif set(os.listdir(saxo_test)) == manifest02: outgoing.put("Warning: client.sock had not been removed") shutil.rmtree(saxo_test) else: outgoing.put("Refusing to delete the saxo test directory") outgoing.put("Data was found which does not match the manifest") outgoing.put(saxo_test) common.thread(run_server) common.thread(run_client) error = False completed = False client_buffer = [] while True: line = outgoing.get() if line.startswith("S: "): print(line) if line.startswith("S: ERROR"): error = True if line.startswith("S: Tests complete"): completed = True if not line.startswith("S: Test"): for c in client_buffer: print(c) del client_buffer[:] elif line.startswith("C: "): client_buffer.append(line) else: print(line) sys.stdout.flush() if line == "Server finished": break if not os.listdir(tmp): os.rmdir(tmp) else: print("Warning: Did not remove:", tmp) if completed and (not error): sys.exit(0) else: sys.exit(1)