#! /usr/bin/env python
# Copyright (c) 2003-2004 George Danezis; see LICENSE for copying info.

"""An SMTP mixminion proxy that anonymizes your email traffic

Syntax: minionSMTP [-hV] [localhost:localport]
-h, --help          - prints this help message
-V, --version       - prints the version
localhost:localport - changes the default hostname and port

By default it listens on the address 127.0.0.1 port 20025 for
incoming SMTP traffic. It then extracts the subject line, the
nickname contained in the "from" field, and the body of the message
and relays them anonymously through the mixminion network to all
receivers.

The nickname is the portion of the from field that does not contain
the email address e.g. for Red Monkey <red.monkey@jungle.za> the
nickname "Red Monkey" will be extracted. This can be set by most email
clients. Note that not all mixminion exit nodes support custom
nicknames.

MIME is supported but only the text/plain parts are relayed. The
others contain too much information to be safe.

You need to have installed mixminion and the program also needs 
to be on your path. See http://mixminion.net for more information.

Bugs and comments to "George.Danezis@cl.cam.ac.uk"
"""

import sys
import os
import time
import asyncore
import getopt

import smtpd
import email
import re

program = sys.argv[0]
__version__ = 'Mixminion SMTP proxy - 0.0.1'

class minionSMTP(smtpd.SMTPServer):

    def __init__(self, localaddr):
        smtpd.SMTPServer.__init__(self,localaddr,localaddr)
        print '%s started at %s\n\tLocal addr: %s\n\t' % (
            self.__class__.__name__, time.ctime(time.time()),
            localaddr)

    def process_message(self, peer, mailfrom, rcpttos, data):
        """ Provides the SMTP to mixminion forwarding mechanism.

        Mail is accepted through SMTP. The address of the receipients
        is extracted and anonymous messages are sent to all of them.

        Only the body of the message is forwarded, but also the subject
        and nickname contained in the headers are extracted and sent along. 
        """

        # Use the email package to extract headers and body.
        msg = email.message_from_string(data)

        # Extract the message subject
        if 'subject' in msg:
            subject = msg['subject']
        else:
            subject = ''

        # Extract "from" field nickname
	import re
        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 = ''

        print "Started sending"

        # Extract the body of the message
        body = None
        if msg.is_multipart():
            for msgx in msg.get_payload():
                # It should also contain the encoding, hmmm...
                # maybe I should check for it too.
                if msgx['content-type'].find('text/plain') != -1:
                   body = msgx.get_payload()
        else:
            body = msg.get_payload()

        # Check that a body was found
        if body == None:
            print "No body found - make sure you send some text/plain"
            return "501 no text/plain body found"

        # Base mixminion command
        cmd = 'mixminion send'
        
        # Augment the command with a nickname
        if nickname != '':
            cmd += ' --from=\"'+nickname+'\"'

        if subject != '':
            cmd += ' --subject=\"'+subject+'\"'

        for address in rcpttos:
            # For each address it sends the message using mixminion.
            cmd += ' -t '+ address
            (sout,sin) = os.popen2(cmd)
            print cmd
            print body
            sout.write(body)
            sout.close()

            # 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"
            else:
                pass
            print "Done"
        # raise UnimplementedError

if __name__ == '__main__':
    # Parse the command line arguments
    # -V, --version   - gives the version
    # -h, --help      - gives some help
    try:
        opts, args = getopt.getopt(
            sys.argv[1:], 'Vh',
            ['version', 'help'])
    except getopt.error, e:
        print e

    for opt, arg in opts:
        if opt in ('-h', '--help'):
            print __doc__
            sys.exit(0)
        elif opt in ('-V', '--version'):
            print >> sys.stderr, __version__
            sys.exit(0)
    
    # parse the rest of the arguments
    if len(args) < 1:
        localspec = '127.0.0.1:20025'
    else:
        localspec = args[0]

    # split into host/port pairs
    i = localspec.find(':')
    if i < 0:
        print 'Bad local spec: %s' % localspec
    localhost = localspec[:i]
    try:
        localport = int(localspec[i+1:])
    except ValueError:
        print 'Bad local port: %s' % localspec

    proxy = minionSMTP((localhost,localport))
    # proxy = smtpd.DebuggingServer(('127.0.0.1',20025),None)
    try:
        asyncore.loop()
    except KeyboardInterrupt:
        pass

@@ -3470,16 +3470,17 @@
   cmd = 
+%5B
 'mixmini
@@ -3485,14 +3485,18 @@
 nion
- 
+', '
 send'
+%5D
 %0A   
@@ -3593,33 +3593,36 @@
  cmd
- += ' 
+.append('
 --from=
-%5C%22'+
+%25s'%25
 nickname
 +'%5C%22
@@ -3617,21 +3617,17 @@
 nickname
-+'%5C%22'
+)
 %0A%0A      
@@ -3661,22 +3661,25 @@
      cmd
- += ' 
+.append('
 --subjec
@@ -3684,24 +3684,20 @@
 ect=
-%5C%22'+
+%25s'%25
 subject
-+'%5C%22'
+)
 %0A%0A  
@@ -3814,27 +3814,35 @@
  cmd
- += ' -t '+
+Full = cmd + %5B'-t',
  address
 %0A   
@@ -3837,16 +3837,17 @@
  address
+%5D
 %0A       
@@ -3877,16 +3877,20 @@
 pen2(cmd
+Full
 )%0A      
@@ -3904,16 +3904,20 @@
 rint cmd
+Full
 %0A       

Fix a security hole in minionSMTP.py.