def send_packet(packet, user, to_server): """Takes packet, user object and whether to send to server (as though from user) or vice versa. Simulates that kind of packet having been recived and passes it on as normal, ie. a packet still goes through the whole list of plugins. """ packets = handle_packet(packet, user, to_server) try: out_bytestr = "".join([pack(packet, to_server) for packet in packets]) except: # Undefined exception inherited from packet_decoder logging.exception( "Bad packet object while packing generated packet %s %s: %s", "from" if to_server else "to", user, packet ) raise # Will be caught as a failure of the plugin sending it. fd = user.srv_sock if to_server else user.user_sock write_buf = send_buffers.get(fd, "") write_buf += out_bytestr send_buffers[fd] = write_buf
def main(): global listener, ticks listener = socket() listener.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) listener.bind(LISTEN_ADDR) listener.listen(128) listener.setblocking(0) logging.basicConfig(filename=LOG_FILE, level=LOG_LEVEL, format=LOG_FORMAT) if DEBUG: debug_handler = logging.StreamHandler() # defaults to stderr debug_handler.setFormatter(logging.Formatter(LOG_FORMAT)) debug_handler.setLevel(logging.DEBUG) logging.root.addHandler(debug_handler) logging.info("Starting up") if PASSTHROUGH: passthrough_log = open(PASSTHROUGH_LOG_FILE, "w") import helpers # Hax before import does important hax helpers.active_users = active_users helpers.send_packet = send_packet from plugins import plugins as _plugins # Lazy import prevents circular references global plugins plugins = _plugins for plugin in plugins[:]: # Note that x[:] is a copy of x try: logging.debug("Loading plugin: %s", plugin) if hasattr(plugin, "on_start"): plugin.on_start() except: logging.exception("Error initialising plugin %s", plugin) plugins.remove(plugin) if not DEBUG: print "proxy: Daemonising..." daemonise() sys.stdout.close() sys.stderr.close() logging.debug("Started up") def add_tick(sig, frame): global ticks ticks += 1 signal.signal(signal.SIGALRM, add_tick) signal.setitimer(signal.ITIMER_REAL, TICK_INTERVAL, TICK_INTERVAL) try: while 1: while ticks: handle_tick() ticks -= 1 try: r, w, x = select(conn_map.keys() + [listener], send_buffers.keys(), []) except select_error, ex: ex_errno, ex_msg = ex.args if ex_errno == errno.EINTR: continue # This lets us handle any tick that may have been queued and retry raise dead = [] # Keeps track of fds in r, w that get dropped, so we know when not to bother. for fd in w: if fd in dead: logging.debug("fd already down - skipping") continue buf = send_buffers[fd] try: n = fd.send(buf[:MAX_SEND]) except socket_error, ex: if ex.errno == errno.EINTR: n = 0 elif ex.errno in (errno.ECONNRESET, errno.EPIPE, errno.ENETDOWN, errno.ENETUNREACH, errno.ENOBUFS): # These are all socket failure conditions, drop the connection user = user_map[fd] if ( ex.errno == errno.ECONNRESET and fd in usersocks ): # User CONNRESET is ok, means user closed program or lost conn. Server CONNRESET is NOT. logging.info("Connection from %s closed by connection reset", user) else: logging.warning( "Dropping connection for %s due to send error to %s", user, "user" if fd in user_socks else "server", exc_info=1, ) dead += [fd, conn_map[fd]] drop_connection(user) continue else: raise assert n <= len(buf) if n < len(buf): send_buffers[fd] = buf[n:] else: del send_buffers[fd] for fd in r: if fd in dead: logging.debug("fd already down - skipping") continue if fd is listener: new_connection() continue buf = read_buffers[fd] to_server = fd in user_socks user = user_map[fd] logging.debug("Buffer before read: length %d", len(buf)) try: read = fd.recv(MAX_RECV) except socket_error, ex: if ex.errno == errno.EINTR: continue if ex.errno in (errno.ECONNRESET, errno.ETIMEDOUT, errno.ENOBUFS, errno.ENOMEM): # These are all socket failure conditions, drop the connection logging.warning( "Dropping connection for %s due to recv error from %s", user, "user" if to_server else "server", exc_info=1, ) dead += [fd, conn_map[fd]] drop_connection(user) continue if not read: # Empty read means EOF if to_server: logging.info("Connection from %s closed", user) else: logging.info("Server connection for %s closed", user) dead += [fd, conn_map[fd]] drop_connection(user) continue # logging.debug("Read %s server for %s: %s", "to" if to_server else "from", user, repr(read)) buf += read logging.debug("Buffer after read: length %d", len(buf)) # Decode as many packets as we can while 1: if PASSTHROUGH: if not buf: break out_bytestr = buf logging.info("Passing through %s", repr(buf)) passthrough_log.write(buf) passthrough_log.flush() buf = "" else: try: packet, buf = unpack(buf, to_server) except: # Undefined exception inherited from packet_decoder logging.exception( "Bad packet %s %s:\n%s", "from" if to_server else "to", user, hexdump(buf) ) logging.warning( "Dropping connection for %s due to bad packet from %s", user, "user" if to_server else "server", ) dead += [fd, conn_map[fd]] drop_connection(user) break if packet is None: # Couldn't decode, need more read first - we're done here. break # logging.debug("%s server for %s: %s", "to" if to_server else "from", user, packet) packets = handle_packet(packet, user, to_server) packed = [] for packet in packets: try: packed.append(pack(packet, to_server)) except: # Undefined exception inherited from packet_decoder logging.warning( "Bad packet object while packing packet %s %s: %s", "from" if to_server else "to", user, packet, exc_info=1, ) out_bytestr = "".join(packed) # Append resulting bytestr to write buffer, to be sent later. send_fd = conn_map[fd] write_buf = send_buffers.get(send_fd, "") write_buf += out_bytestr send_buffers[send_fd] = write_buf if fd not in dead: logging.debug("Buffer after decode: length %d", len(buf)) read_buffers[fd] = buf