[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[minion-cvs] Full SMTP/POP3 proxy for sending and receiving mixminio...
Update of /home/minion/cvsroot/src/minion/etc
In directory moria.mit.edu:/tmp/cvs-serv12817
Modified Files:
minionSMTP.py pop3d.py
Added Files:
IMAPproxy.py minionProxy.py mmUtils.py
Log Message:
Full SMTP/POP3 proxy for sending and receiving mixminion messages.
Documentation to follow soon.
--- NEW FILE: IMAPproxy.py ---
import pop3d
import mmUtils
import sys
import os
import pwd
import errno
import getopt
import time
import socket
import asyncore
import asynchat
import re
import copy
import md5
import base64
import getpass
import imaplib
import cPickle
import email
def md5hash(m):
md = md5.new()
md.update(m)
md_res = (base64.encodestring(md.digest()))
return md_res[:22]
class IMAPproxy(pop3d.POP3Server):
""" An IMAP to POP3 proxy that fetches anonymous mail
It then delivers it to the POP3 mail box.
"""
def __init__(self, localaddr, ser, passwd):
"Server and Mixminion must be specified"
self.__server = ser
print 'IMAP login on %s' % (ser)
self.__pass = passwd
pop3d.POP3Server.__init__(self,localaddr)
self.__cachedIDs = []
# API for "doing something useful with the message"
def get_pop3_messages(self, user, passd):
# Get the IMAP messages
try:
M = imaplib.IMAP4(self.__server)
M.login(user, passd)
M.select()
# Filter for anonymous messages
typ, data = M.search(None, '(HEADER "X-Anonymous" "yes")')
ms = []
for num in data[0].split():
typ, data = M.fetch(num, '(RFC822)')
ms = ms + [data[0][1]]
M.logout()
except M.error, inst:
print "IMAP exception:",inst
# TODO: Should really include an "error" message here.
return []
# Implement a list of previously seen messages.
# The list of messages confirmed by the mail client
if 'seen_files.dat' in os.listdir('.'):
print 'loading seen file'
seenlist = cPickle.load(file('seen_files.dat','r'))
else:
seenlist = []
# Filter out messages that are already seen.
ms = filter(lambda x:not md5hash(x) in seenlist,ms)
self.__cachedIDs = map(lambda x: md5hash(x), ms)
# Decodes the anonymous messages
# The decode routine does not like '\r\n' so
# I need to transform everything to '\n'
ms = map(lambda x:re.sub('\r\n','\n',x),ms)
# How to recognise a SURB:
surbPat = re.compile('(?:- )?(-----BEGIN TYPE III REPLY BLOCK-----)([^\-]*)(?:- )?(-----END TYPE III REPLY BLOCK-----)',re.S)
# The list of surbs cached by the client.
if 'surb_file.dat' in os.listdir('.'):
surb_file = cPickle.load(file('surb_file.dat','r'))
else:
surb_file = {}
ms2 = []
for m in ms:
msg = email.message_from_string(m)
# Decode the body of the message
bx = mmUtils.decode(msg.get_payload(),passd)
# By default allow no reply.
reply_addrs = '%s@nym.taz' % 'anonymous'
# Extract any SURBs and store them.
rs = surbPat.findall(bx)
rs = map(lambda (x,y,z): "%s%s%s" % (x,y,z),rs)
if len(rs) > 0:
bx = surbPat.sub('',bx)
surb_file[md5hash(rs[0])[:10]] = rs
reply_addrs = '%s@nym.taz' % md5hash(rs[0])[:10]
# Set the reply addresses with none@nym.taz or the SURB IDs.
del msg['Return-Path']
msg['Return-Path'] = reply_addrs
new_from = re.sub('([^<]*@[^>]*)',reply_addrs,msg['From'])
del msg['From']
msg['From'] = new_from
msg.set_payload(bx)
ms2 += [msg.as_string()]
# Add '\r\n' back at the end of each line!
m2 = map(lambda x:re.sub('\n','\r\n',x),ms2)
# Save the SURBs
cPickle.dump(surb_file,file('surb_file.dat','w'))
return m2
def set_pop3_messages(self, user, msgs):
# Loads the list of seen (by the client) messages
seenlist = []
if 'seen_files.dat' in os.listdir('.'):
seenlist = cPickle.load(file('seen_files.dat','r'))
else:
seenlist = []
# Stores the IDs of seen messages
for (i,(d,m)) in zip(range(len(msgs)),msgs):
if d:
seenlist += [self.__cachedIDs[i]]
cPickle.dump(seenlist,file('seen_files.dat','w'))
self.__cachedIDs =[]
return None # No errors
if __name__ == '__main__':
import __main__
proxy = IMAPproxy(('127.0.0.1', 20110),'imap.hermes.cam.ac.uk',getpass.getpass())
try:
asyncore.loop()
except KeyboardInterupt:
pass
--- NEW FILE: minionProxy.py ---
from IMAPproxy import *
from minionSMTP import *
import getpass
import asyncore
imap_address = 'imap.hermes.cam.ac.uk'
local_host = '127.0.0.1'
smtp_port = 20025
imap_port = 20110
if __name__ == '__main__':
import __main__
print 'Mixminion password:'
mm_Pass = getpass.getpass()
proxy1 = IMAPproxy((local_host, imap_port),imap_address,mm_Pass)
proxy2 = minionSMTP((local_host,smtp_port),mm_Pass)
try:
asyncore.loop()
except KeyboardInterupt:
pass
--- NEW FILE: mmUtils.py ---
# Implmentes the mixminion interface.
import os, sys
import re
# Give it a list of ommands and what should go in the std input
# it returns what appeared in the std output.
# PRIVATE: DO NOT CALL FROM OUTSIDE THIS MODULE!!!
def mm_command(cmd, in_str = None, show_stderr = 1):
c = cmd
if show_stderr == 1:
(sout,sin) = os.popen4(c)
else:
(sout,sin,serr) = os.popen3(c)
if in_str != None:
sout.write(in_str+'\n')
sout.close()
result = sin.read()
return result
# provides a single use reply block
# If an error occus it return an empty list '[]'
def getSURB(addrs,login,passwd):
rs = mm_command(['mixminion','generate-surb','--identity=%s'%login,'-t',addrs], passwd)
surbPat = re.compile('-----BEGIN TYPE III REPLY BLOCK-----[^\-]*-----END TYPE III REPLY BLOCK-----',re.S)
rs = surbPat.findall(rs)
return rs
# routine to decode a received mixminion message
# If there is an error the empty string is returned.
def decode(msg,passwd):
decPat = re.compile('-----BEGIN TYPE III ANONYMOUS MESSAGE-----\r?\nMessage-type: (plaintext|encrypted)(.*)-----END TYPE III ANONYMOUS MESSAGE-----\r?\n',re.S)
mtc = decPat.search(msg)
if mtc != None:
f = open('__tempMM','w')
f.write(mtc.group(0))
f.close()
rs = mm_command(['mixminion','decode','-i','__tempMM'], passwd, 0)
# os.remove('__tempMM')
rs.strip('\n')
return rs+'\n'
# Delete file!
# Simply sends a message
def send(msg,addrs,cmd):
f = open('__msgMM','w')
f.write(msg)
f.close()
rs = mm_command(['mixminion','send','-i','__msgMM','-t',addrs]+cmd, None)
os.remove('__msgMM')
return rs
# routine to send a message using mixminion.
def reply(msg,surb,cmd):
f = open('__msgMM','w')
f.write(msg)
f.close()
f = open('__surbMM','w')
f.write(surb)
f.close()
rs = mm_command(['mixminion','send','-i','__msgMM','-R','__surbMM']+cmd, None)
os.remove('__msgMM')
os.remove('__surbMM')
return rs
# Delete files !!
# Old debugging information
if __name__ == '__main__':
import getpass
sb = getSURB('gd216@cl.cam.ac.uk',getpass.getpass())
reply('Hello world\nThis is my message\n',sb[0])
# print rs
Index: minionSMTP.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/etc/minionSMTP.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -d -r1.2 -r1.3
--- minionSMTP.py 18 Jan 2004 08:16:21 -0000 1.2
+++ minionSMTP.py 5 Mar 2004 17:42:31 -0000 1.3
@@ -39,12 +39,17 @@
import email
import re
+import mmUtils
+import getpass
+import cPickle
+
program = sys.argv[0]
__version__ = 'Mixminion SMTP proxy - 0.0.1'
class minionSMTP(smtpd.SMTPServer):
- def __init__(self, localaddr):
+ def __init__(self, localaddr,passwd):
+ self.__passwd = passwd
smtpd.SMTPServer.__init__(self,localaddr,localaddr)
print '%s started at %s\n\tLocal addr: %s\n\t' % (
self.__class__.__name__, time.ctime(time.time()),
@@ -60,6 +65,8 @@
and nickname contained in the headers are extracted and sent along.
"""
+ # print peer,mailfrom,rcpttos,data
+
# Use the email package to extract headers and body.
msg = email.message_from_string(data)
@@ -69,20 +76,18 @@
else:
subject = ''
- # Extract "from" field nickname
+ # Extract "from" field nickname and return address
import re
+ nickname = ''
+ retaddrs = None
if 'from' in msg:
- m = re.search("(.*)<", msg['from'])
- try:
- nickname = m.group(1).strip();
- nickname = nickname.strip('\"')
- if nickname.find('@') != -1:
- nickname = ''
- except AttributeError:
- # The string must be empty
- nickname = ''
- else:
- nickname = ''
+ m = re.search('^([^@<]*)', msg['from'])
+ if m != None:
+ nickname = m.group(1).strip()
+
+ m = re.search('([^< ]*@[^> ]*)', msg['from'])
+ if m != None:
+ retaddrs = m.group(1).strip()
print "Started sending"
@@ -102,34 +107,63 @@
print "No body found - make sure you send some text/plain"
return "501 no text/plain body found"
+ if retaddrs != None:
+ surb = mmUtils.getSURB(retaddrs,nickname,self.__passwd)
+ body = body +'\n'+surb[0]
+
# Base mixminion command
- cmd = ['mixminion', 'send']
+ cmd = []
# Augment the command with a nickname
if nickname != '':
- cmd.append('--from=%s'%nickname)
+ cmd.append('--from=%s' % nickname)
if subject != '':
- cmd.append('--subject=%s'%subject)
+ cmd.append('--subject=%s' % subject)
for address in rcpttos:
- # For each address it sends the message using mixminion.
- cmdFull = cmd + ['-t', address]
- (sout,sin) = os.popen2(cmdFull)
- print cmdFull
- print body
- sout.write(body)
- sout.close()
+ taz = re.findall('([^@]*)@nym.taz',address)
- # Check that mixminion confirms sending otherwise returns
- # an 502 error code.
- result = sin.read()
- m = re.search("... message sent", result)
- if m != None:
- return "502 Mixminion did not confirm sending"
+ # Reply to anonymous sender case.
+ if len(taz) > 0:
+ surb_id = taz[0]
+ if surb_id == 'anonymous':
+ # TODO: send back an error message
+ print 'Cannot send to anonymous'
+ continue
+ surb_file = {}
+ if 'surb_file.dat' in os.listdir('.'):
+ surb_file = cPickle.load(file('surb_file.dat','r'))
+
+ if surb_file.has_key(surb_id):
+ surb_list = surb_file[surb_id]
+ if len(surb_list) == 0:
+ # Send back an error message
+ print 'No more SURBs available'
+ del surb_file[surb_id]
+ else:
+ result = mmUtils.reply(body,surb_list[0],cmd)
+ print result
+ m = re.search("sent", result)
+
+ if m == None:
+ return "502 Mixminion did not confirm sending"
+ else:
+ surb_file[surb_id] = surb_list[1:]
+ print "Done"
+
+ cPickle.dump(surb_file,file('surb_file.dat','w'))
+ else:
+ print 'No address known for: %s@nym.taz'%taz[0]
else:
- pass
- print "Done"
+ # For each address it sends the message using mixminion.
+ result = mmUtils.send(body,address,cmd)
+ m = re.search("sent", result)
+
+ if m == None:
+ return "502 Mixminion did not confirm sending"
+ else:
+ print "Done"
# raise UnimplementedError
if __name__ == '__main__':
@@ -167,7 +201,7 @@
except ValueError:
print 'Bad local port: %s' % localspec
- proxy = minionSMTP((localhost,localport))
+ proxy = minionSMTP((localhost,localport),getpass.getpass())
# proxy = smtpd.DebuggingServer(('127.0.0.1',20025),None)
try:
asyncore.loop()
Index: pop3d.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/etc/pop3d.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -d -r1.1 -r1.2
--- pop3d.py 28 Feb 2004 20:54:52 -0000 1.1
+++ pop3d.py 5 Mar 2004 17:42:31 -0000 1.2
@@ -23,6 +23,7 @@
# - Refactor UIDL + LIST
# - Refactor TOP + RETR
# - Unify the checks for command parameters
+# - Make a command line interface
import sys
import os
@@ -39,6 +40,7 @@
import md5
import base64
+
class Devnull:
def write(self, msg): pass
def flush(self): pass
@@ -335,7 +337,22 @@
return
if self.__state == self.TRAN:
- status = self.__server.set_pop3_messages(self.__username, self.__messages)
+ # We will do something more smart than just returning the
+ # remaining messages. We will return tuple (0|1,m) indicating
+ # that a message has been deleted or not.
+
+ # the remaining messages
+ m = map(lambda (x,y): y, self.__messages)
+
+ # The list we will return
+ ret = []
+ for (x,msg) in self.__oldmessages:
+ if msg in m:
+ ret += [(0,msg)]
+ else:
+ ret += [(1,msg)]
+
+ status = self.__server.set_pop3_messages(self.__username, ret)
if status == None:
self.push('+OK Bye')
else:
@@ -481,8 +498,8 @@
If an empty sequence in returned there are no messages.
"""
- # Sample implemtation always provides one message.
- return ['from: x@cl.cam.ac.uk\nTo: gd216@cam.ac.uk\nSubject: Hello\n\nWhat\ncan\nI\ndo\nfor\nyou']
+ return []
+
raise UnimplementedError
def set_pop3_messages(self, user, msgs):
@@ -492,15 +509,9 @@
the messages left, otherwise it should return an error string.
"""
+
# sample implementation simply prints messages
print 'New box', user, msgs
return None # No errors
-if __name__ == '__main__':
- import __main__
- proxy = POP3Server(('127.0.0.1', 20110))
- try:
- asyncore.loop()
- except KeyboardInterupt:
- pass