#!/usr/bin/python # TODO # # More complete error handling for SMTP exceptions import re import sys import netrc import getpass import mailbox import smtplib import optparse import email.Parser fromaddr = 'tmz@pobox.com' smtphost = 'sasl.smtp.pobox.com' smtpport = '587' confirm_re = re.compile('^confirm [a-z0-9]{40}$') subject_re = re.compile('.*requires\n?[\s]+approval$', re.DOTALL) usage = '%prog [options] mailbox' parser = optparse.OptionParser(usage=usage) parser.add_option('-a', '--approve', dest='approve', action='store_true', default=False, help='Approve, rather than discard, message(s) [%default]') parser.add_option('-f', '--from', dest='fromaddr', default=fromaddr, metavar='address', help='From address to use as a last resort [%default]') parser.add_option('-H', '--smtp-host', dest='smtphost', default=smtphost, help='SMTP hostname [%default]', metavar='hostname') parser.add_option('-P', '--smtp-port', dest='smtpport', default=smtpport, help='SMTP port number [%default]', metavar='port') parser.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Enable more verbose output') opts, args = parser.parse_args() fromaddr = opts.fromaddr smtphost = opts.smtphost smtpport = opts.smtpport class LoginInfoError(StandardError): pass def get_login_info(host): try: net_rc = netrc.netrc() if host not in net_rc.hosts: raise LoginInfoError('No machine entry for %s in ~/.netrc' % host) return net_rc.authenticators(host) except Exception, error: raise LoginInfoError(error) # This comes from http://code.activestate.com/recipes/576553/ def mbox_generator(input, sep=''): """Yield each message found in 'input'.""" assert type(input) is file parsestr = email.Parser.Parser().parsestr data = [] while True: line = input.readline() if sep and line.rstrip() == sep: line = 'From nobody' if line[:5] == 'From ' or line == '': if data: yield parsestr(''.join(data)) data = [] elif line == '': raise StopIteration else: data.append(line) try: if args: if len(args) > 1: parser.error('Only one mailbox argument is supported') # python < 2.5 doesn't have mailbox.mbox try: mbox = mailbox.mbox(args[0]) except AttributeError: mbox = mbox_generator(open(args[0])) else: mbox = mbox_generator(sys.stdin, sep='~###~') except Exception, e: raise SystemExit(e) # FIXME this won't work with mbox_generator #if not mbox: # print 'No messages found' seen = [] confirmations = [] # FIXME this won't work with mbox_generator #print 'Processing %s messages...' % len(mbox) if opts.verbose: print 'Processing messages...' for msg in mbox: # No point looking any further if the msg subject isn't correct if 'subject' not in msg or not subject_re.match(msg['subject']): if opts.verbose: print 'No matching subject, skipping...' continue found = False for part in msg.walk(): if 'subject' not in part or 'from' not in part: if opts.verbose: print 'Message part lacks subject or from, skipping...' continue frm = listaddr = part['from'] subject = part['subject'] if confirm_re.match(subject): found = True if subject in seen: if opts.verbose: print 'Skipping duplicate message (%s)' % subject break reply = email.Message.Message() reply['To'] = frm #reply['To'] = 'tmz@pobox.com' if 'delivered-to' in msg and msg['delivered-to']: reply['From'] = msg['delivered-to'] else: reply['From'] = fromaddr reply['Subject'] = subject if opts.approve: list = msg['subject'].split()[0] try: password = get_login_info(listaddr)[2] except LoginInfoError, error: print 'Unable to find %s password: %s' % (list, error) try: password = getpass.getpass('%s password: ' % list) except Exception, error: raise SystemExit('Unable to get password: %s' % error) if not password: raise SystemExit('Failed to get %s password' % list) reply['Approve'] = password seen.append(subject) confirmations.append(reply) break if not found: print 'No confirmation part found in message (%s)' % msg['subject'] if confirmations: mode = opts.approve and 'approval' or 'discard' print 'Sending %s %s confirmation(s)... ' % (len(confirmations), mode) try: login, account, password = get_login_info(smtphost) except LoginInfoError, error: raise SystemExit(error) # FIXME Do proper smtp error handling try: smtp = smtplib.SMTP(smtphost, smtpport) smtp.starttls() smtp.ehlo('localhost.localdomain') smtp.login(login, password) failed = [] for msg in confirmations: errors = smtp.sendmail(msg['from'], [msg['to']], msg.as_string()) if not errors: print 'Sent %s' % msg['subject'] else: failed.append(errors) # quit() could throw an sslerror or another error we don't care about try: smtp.quit() except: pass except Exception, error: print 'Sending confirmations failed: %s' % error for errors in failed: for addr in errors: errno, errmsg = errors[addr] print 'Delivery to %s failed: %s (%s)' % (addr, errmsg, errno)