"""pypopper: a file-based pop3 server Useage: python pypopper.py """ import logging import os import socket import sys import traceback logging.basicConfig(format="%(name)s %(levelname)s - %(message)s") log = logging.getLogger("pypopper") log.setLevel(logging.INFO) class ChatterboxConnection(object): END = "\r\n" def __init__(self, conn): self.conn = conn def __getattr__(self, name): return getattr(self.conn, name) def sendall(self, data, END=END): if len(data) < 50: log.debug("send: %r", data) else: log.debug("send: %r...", data[:50]) data += END self.conn.sendall(data) def recvall(self, END=END): data = [] while True: chunk = self.conn.recv(4096) if END in chunk: data.append(chunk[:chunk.index(END)]) break data.append(chunk) if len(data) > 1: pair = data[-2] + data[-1] if END in pair: data[-2] = pair[:pair.index(END)] data.pop() break log.debug("recv: %r", "".join(data)) return "".join(data) class Message(object): def __init__(self, filename): msg = open(filename, "r") try: self.data = data = msg.read() self.size = len(data) self.top, bot = data.split("\r\n\r\n", 1) self.bot = bot.split("\r\n") finally: msg.close() def handleUser(data, msg): return "+OK user accepted" def handlePass(data, msg): return "+OK pass accepted" def handleStat(data, msg): return "+OK 1 %i" % msg.size def handleList(data, msg): return "+OK 1 messages (%i octets)\r\n1 %i\r\n." % (msg.size, msg.size) def handleTop(data, msg): cmd, num, lines = data.split() assert num == "1", "unknown message number: %s" % num lines = int(lines) text = msg.top + "\r\n\r\n" + "\r\n".join(msg.bot[:lines]) return "+OK top of message follows\r\n%s\r\n." % text def handleRetr(data, msg): log.info("message sent") return "+OK %i octets\r\n%s\r\n." % (msg.size, msg.data) def handleDele(data, msg): return "+OK message 1 deleted" def handleNoop(data, msg): return "+OK" def handleQuit(data, msg): return "+OK pypopper POP3 server signing off" dispatch = dict( USER=handleUser, PASS=handlePass, STAT=handleStat, LIST=handleList, TOP=handleTop, RETR=handleRetr, DELE=handleDele, NOOP=handleNoop, QUIT=handleQuit, ) def serve(host, port, filename): assert os.path.exists(filename) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind((host, port)) try: if host: hostname = host else: hostname = "localhost" log.info("pypopper POP3 serving '%s' on %s:%s", filename, hostname, port) while True: sock.listen(1) conn, addr = sock.accept() log.debug('Connected by %s', addr) try: msg = Message(filename) conn = ChatterboxConnection(conn) conn.sendall("+OK pypopper file-based pop3 server ready") while True: data = conn.recvall() command = data.split(None, 1)[0] try: cmd = dispatch[command] except KeyError: conn.sendall("-ERR unknown command") else: conn.sendall(cmd(data, msg)) if cmd is handleQuit: break finally: conn.close() msg = None except (SystemExit, KeyboardInterrupt): log.info("pypopper stopped") except Exception, ex: log.critical("fatal error", exc_info=ex) finally: sock.shutdown(socket.SHUT_RDWR) sock.close() if __name__ == "__main__": if len(sys.argv) != 3: print "USAGE: [:] " else: _, port, filename = sys.argv if ":" in port: host = port[:port.index(":")] port = port[port.index(":") + 1:] else: host = "" try: port = int(port) except Exception: print "Unknown port:", port else: if os.path.exists(filename): serve(host, port, filename) else: print "File not found:", filename