Update fakemail2
[thomas_code.git] / opstools / mail / fakemail2.py
blob79595b88ee8cd4a051df48c673bf8c2229d7d148
1 #!/usr/bin/env python
3 # fakemail2 - rewrite of Perl's fakemail in python
5 # Author: Thomas Guyot-Sionnest <tguyot@gmail.com>
7 # This file has been released to the public domain
10 from __future__ import print_function
11 import sys
12 import os
13 from optparse import OptionParser
14 #from tempfile import TemporaryFile
15 from smtpd import SMTPServer
16 from asyncore import loop
17 from time import ctime
19 VERSION = '1.1'
21 # Functions
22 class FakemailSMTPD(SMTPServer):
23 def __init__(self, localaddr, remoteaddr, saveobj, logobj):
24 self.capture = saveobj
25 self.message = logobj
26 SMTPServer.__init__(self, localaddr, remoteaddr)
28 def process_message(self, peer, mailfrom, rcptto, data):
29 self.message.log('Incoming mail')
30 for recp in rcptto:
31 self.message.log('Capturing mail to %s' % recp)
32 self.capture.save(mailfrom, recp, data)
33 self.message.log('Mail to %s saved' % recp)
34 self.message.log('Incoming mail dispatched')
36 class LogIt(object):
37 def __init__(self, logfile=None):
38 if logfile:
39 self.logfile = logfile + os.getpid()
40 # Test-open
41 with open(self.logfile, 'a'):
42 pass
43 else:
44 self.logfile = None
46 def log(self, message):
47 msg = ctime() + ': ' + str(message)
48 if self.logfile:
49 with open(self.logfile, 'a') as log:
50 log.write(msg)
51 else:
52 print(msg)
54 class SaveIt(object):
55 def __init__(self, savepath):
56 if not os.path.isdir(savepath):
57 raise Exception('Savepath is not a directory: %s' % savepath)
58 # TODO: Test-write, fail early on EACCESS
59 self.path = savepath
60 self.counts = {}
62 def save(self, sender, recipient, data):
63 msgfile = os.path.join(self.path, self.recp2file(recipient))
64 with open(msgfile, 'w') as outf:
65 outf.write('MAIL FROM: <%s>\nRCPT TO: <%s>\nDATA:\n%s\n' % (sender, recipient, data))
67 def recp2file(self, recipient):
68 for char in r'|<>&/\ ;!?':
69 recipient = recipient.replace(char, '')
70 self.counts[recipient] = self.counts.get(recipient, 0) + 1
71 return '.'.join((recipient, str(self.counts[recipient])))
73 def main():
74 process_opts = OptionParser(
75 usage='Usage: %prog -H <hostname> -p <port> -P <log_path> [-l <log_file>] [-b]',
76 version='%prog ' + VERSION
78 process_opts.add_option('-H', '--host', dest='host', help='Hostname/IP to listen on')
79 process_opts.add_option('-p', '--port', type='int', dest='port', help='Port to listen on')
80 process_opts.add_option('-P', '--path', dest='path', help='Directory to save emails into')
81 process_opts.add_option('-l', '--log', dest='log', help='Optionnal file to append messages to')
82 process_opts.add_option('-b', '--background', action='store_true', dest='background', help='Fork to background')
84 (options, args) = process_opts.parse_args()
85 if not options.host or not options.port or not options.path:
86 process_opts.error('You must supply a host, port and path')
87 if not os.path.isdir(options.path):
88 process_opts.error('Path \'%s\' is not a directory' % options.path)
89 if args:
90 process_opts.error('Unexpected extra argument: %s' % args[0])
92 # Fork to background if requested
93 if options.background:
94 try:
95 if os.fork() > 0:
96 os._exit(0)
97 except OSError as err:
98 print('Fork failed: %s (%d)' % (err.strerror, err.errno), file=sys.stderr)
99 return False
101 # See ya dad...
102 for fdno in range(0, 3):
103 try:
104 os.close(fdno)
105 except OSError:
106 pass
107 os.chdir(os.sep)
108 os.umask(0o022)
109 try:
110 os.setsid()
111 except Exception:
112 pass
114 message = LogIt(options.log)
115 capture = SaveIt(options.path)
117 # All set, lets go
118 message.log('Starting fakemail')
119 FakemailSMTPD((options.host, options.port), None, capture, message)
120 loop(timeout=1, use_poll=True)
121 message.log('Shutting down')
123 if __name__ == '__main__':
124 sys.exit(not main())