Name: Anonymous 2007-10-25 10:06
Hey guys, I am the OP from http://dis.4chan.org/read/prog/1193225538/. It's quite shit, but here is what I ended up writing:
# vsss
# (very simple smtp server)
# revision 0.1
import socket, threading, urllib, os, random, datetime
HOSTNAME = ''
VALID_RECIPIENTS = [
('dis.4chan.org', ['prog', 'VIP']),
('example.com', ['lol'])
]
MAIL_PATH = 'Z:\MAIL'
DEBUG = False
########################################
CRLF = "\r\n"
def gethostname():
try:
# this returns ip address of the requestor
# i do it this way to help NAT'd servers find their external address
ipuo = urllib.urlopen('http://zxcsh.com/ip/')
ip = ipuo.read()
except:
try:
# if that doesn't work, just get hostname in usual way
hn = socket.gethostname()
except:
# and if that fails too, just make one up
hn = 'vsss'
else:
try:
# hostname using reverse dns
hn = socket.gethostbyaddr(ip)[0]
except:
# or just the ip address if lookup fails
hn = '[' + ip + ']'
return hn
class SMTPServer(threading.Thread):
def __init__(self, sock, addr, port):
threading.Thread.__init__(self)
self.sock = sock
self.addr = addr
self.port = port
self.buffer = ''
self.running = True
self.remotehost = None
self.returnpath = None
self.recipients = []
self.debug('new connection')
def debug(self, message):
if DEBUG:
print self.addr+':'+str(self.port)+' '+message
def run(self):
# send greeting
self.sendline(220, HOSTNAME+' ESMTP')
# loop until quit
while self.running:
line = self.getline()
if line == None:
break;
command = line.partition(' ')
try:
getattr(self, 'cmd_'+command[0].lower())(command[2])
except AttributeError:
self.resp_badcmd()
# this checks if it was a clean exit or not
if not self.running:
self.sendline(221, '2.0.0 '+HOSTNAME+' Service closing transmission channel')
self.sock.close()
self.debug('connection closed')
def getline(self):
# keep filling buffer until it contains a crlf
while CRLF not in self.buffer:
recvbuf = self.sock.recv(4096)
if recvbuf == '':
return None
self.buffer += recvbuf
# then return everything up to the crlf, and keep the rest in the buffer
crlfpart = self.buffer.partition(CRLF)
self.buffer = crlfpart[2]
self.debug('<<< '+crlfpart[0])
return crlfpart[0]
def sendline(self, status, message):
if type(message) == str:
message = [message]
for line in message[:-1]:
self.sendline2(str(status)+'-'+line)
self.sendline2(str(status)+' '+message[-1])
def sendline2(self, output):
self.sock.send(output+CRLF)
self.debug('>>> '+output)
def resp_badcmd(self):
self.sendline(502, '5.5.1 Syntax error, command unrecognized')
def resp_ok(self):
self.sendline(250, '2.0.0 OK')
def resp_noremotehost(self):
self.sendline(503, '5.5.1 Commands out of sequence, require HELO or EHLO')
def resp_noreturnpath(self):
self.sendline(503, '5.5.1 Commands out of sequence, require MAIL FROM')
def resp_norecipients(self):
self.sendline(503, '5.5.1 Commands out of sequence, require RCPT TO')
def resp_badreturnpath(self):
self.sendline(501, '5.5.4 Return path is invalid')
def resp_badaddress(self):
self.sendline(501, '5.5.4 Recipient address is invalid')
def resp_baddomain(self):
self.sendline(550, '5.7.1 Unable to relay')
def resp_badrecipient(self):
self.sendline(550, '5.7.1 Recipient address does not exist')
def resp_dataerror(self):
self.sendline(451, '4.3.0 Unknown error while accepting mail, try later')
def cmd_helo(self, args):
self.sendline(250, HOSTNAME+' says hello')
self.remotehost = args.strip()
def cmd_ehlo(self, args):
self.sendline(250, [HOSTNAME+' says hello','8BITMIME','ENHANCEDSTATUSCODES'])
self.remotehost = args.strip()
def check_returnpath(self, returnpath):
# (TODO: read the relevant RFCs and do this properly)
if returnpath == '':
return True
atsplit = returnpath.split('@')
if len(atsplit) == 2 and len(atsplit[0]) > 0 and len(atsplit[1]) > 0:
return True
return False
def cmd_mail(self, args):
if args[:5].lower() != 'from:':
self.resp_badcmd()
else:
if self.remotehost == None:
self.resp_noremotehost()
else:
args = args[5:].strip()
if args == '':
self.resp_badreturnpath()
return
# ignore any other args
rp = args.partition(' ')[0]
if rp[0] == '<':
if rp[-1] == '>':
rp = rp[1:-1]
else:
self.resp_badreturnpath()
return
if self.check_returnpath(rp):
self.returnpath = rp
self.resp_ok()
else:
self.resp_badreturnpath()
def cmd_rcpt(self, args):
if args[:3].lower() != 'to:':
self.resp_badcmd()
else:
if self.remotehost == None:
self.resp_noremotehost()
elif self.returnpath == None:
self.resp_noreturnpath()
else:
args = args[3:].strip()
if args == '':
self.resp_badaddress()
return
ra = args.partition(' ')[0]
if ra[0] == '<':
if ra[-1] == '>':
ra = ra[1:-1]
else:
self.resp_badaddress()
return
atsplit = ra.split('@')
if not (len(atsplit) == 2 and len(atsplit[0])>0 and len(atsplit[1])>0):
self.resp_badaddress()
return
matchvr = None
for vr in VALID_RECIPIENTS:
if atsplit[1].lower() == vr[0].lower():
matchvr = vr
if matchvr == None:
self.resp_baddomain()
return
for recp in matchvr[1]:
if atsplit[0].lower() == recp:
self.recipients.append((matchvr[0],recp))
self.resp_ok()
return
self.resp_badrecipient()
def chdir_mkdir(self, dir):
try:
os.chdir(dir)
except:
os.mkdir(dir)
os.chdir(dir)
def random_string(self):
s = ''
for i in range(1,16):
s += chr(random.randint(96,122))
return s
def cmd_data(self, args):
if self.remotehost == None:
self.resp_noremotehost()
if self.returnpath == None:
self.resp_noreturnpath()
elif self.recipients == []:
self.resp_norecipients()
else:
self.sendline(354, 'Start mail input; end with <CRLF>.<CRLF>')
message = ''
line = self.getline()
while line != '.':
if line[:1] == '.':
line = line[1:]
message += line + CRLF
line = self.getline()
now = datetime.datetime.now()
message = 'Received: from '+self.remotehost+' (['+self.addr+']) by '+HOSTNAME+'; '+now.strftime('%a, %d %b %Y %H:%M:%S +0000') + CRLF + message
message = 'Return-path: <'+self.returnpath+'>' + CRLF + message
filename = now.strftime('%Y%m%d%H%M%S-')+self.random_string()
try:
for recp in self.recipients:
os.chdir(MAIL_PATH)
self.chdir_mkdir(recp[0])
self.chdir_mkdir(recp[1])
mailfile = open(filename+'.eml', 'wb')
mailfile.write(message)
mailfile.close()
print "wrote mail "+filename+" for "+str(recp)
except:
print "ERROR!"
self.resp_dataerror()
else:
self.resp_ok()
os.chdir(MAIL_PATH)
def cmd_rset(self, args):
self.returnpath = None
self.recipients = []
self.resp_ok()
def cmd_noop(self, args):
self.resp_ok()
def cmd_quit(self, args):
self.running = False
########################################
print 'vsss - very simple smtp server'
print
# generate hostname, if required
if HOSTNAME == '':
HOSTNAME = gethostname()
print 'hostname is '+HOSTNAME
# listen on port 25
srvsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
srvsock.bind(('', 25))
srvsock.listen(5)
# create thread for each connection
while True:
(clisock, addrport) = srvsock.accept()
srvthrd = SMTPServer(clisock, addrport[0], addrport[1])
srvthrd.start()
# this is not reached (yet)
srvsock.close()