pypopper/pypopper.py

178 lines
5.1 KiB
Python

"""pypopper: a file-based pop3 server
Useage:
python pypopper.py <port> <path_to_message_file>
"""
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
print('Final: {}'.format(data))
self.conn.sendall(data.encode('UTF-8'))
def recvall(self, END=END):
data = []
while True:
chunk = self.conn.recv(4096).decode('UTF-8')
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)
split_lines = data.splitlines()
self.top = '\r\n'.join(split_lines[0:5])
self.bot = '\r\n'.join(split_lines[6:])
print('Top:\n{}'.format(self.top))
print('Bot:\n{}'.format(self.bot))
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 = '5'
# lines = int(lines)
# text = msg.top + "\r\n\r\n" + "\r\n".join(msg.bot[:lines])
text = msg.top + "\r\n\r\n"
print('Text:\n {}'.format(text))
return "+OK top of message follows\r\n%s\r\n." % text
def handleRetr(data, msg):
log.info("message sent")
text = msg.top + "\r\n\r\n" + msg.bot
print('Text:\n {}'.format(text))
print('msg size: {}, msg data: \n{}'.format(len(text), text))
return "+OK %i octets\r\n%s\r\n\r\n." % (len(text), text)
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:
print(filename)
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]
print('command: {}'.format(command))
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 as 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: [<host>:]<port> <path_to_message_file>")
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):
print(host, port, filename)
serve(host, port, filename)
else:
print("File not found:", filename)