1 #!/usr/bin/env python 2 3 from email import message_from_file 4 from imiptools.content import get_addresses, handle_itip_part 5 from imiptools.mail import Messenger 6 import sys 7 8 # Postfix exit codes. 9 10 EX_TEMPFAIL = 75 11 12 # Permitted iTIP content types. 13 14 itip_content_types = [ 15 "text/calendar", # from RFC 6047 16 "text/x-vcalendar", "application/ics", # other possibilities 17 ] 18 19 # Processing of incoming messages. 20 21 def get_all_values(msg, key): 22 l = [] 23 for v in msg.get_all(key) or []: 24 l += [s.strip() for s in v.split(",")] 25 return l 26 27 class Processor: 28 29 "The processing framework." 30 31 def __init__(self, handlers, messenger=None): 32 self.handlers = handlers 33 self.messenger = messenger or Messenger() 34 self.lmtp_socket = None 35 36 def process(self, f, original_recipients, recipients, outgoing_only): 37 38 """ 39 Process content from the stream 'f' accompanied by the given 40 'original_recipients' and 'recipients'. 41 """ 42 43 msg = message_from_file(f) 44 senders = get_addresses(msg.get_all("Reply-To") or msg.get_all("From")) 45 original_recipients = original_recipients or get_addresses(get_all_values(msg, "To")) 46 47 # Handle messages with iTIP parts. 48 49 all_responses = [] 50 handled = False 51 52 for part in msg.walk(): 53 if part.get_content_type() in itip_content_types and \ 54 part.get_param("method"): 55 56 all_responses += handle_itip_part(part, senders, original_recipients, self.handlers, self.messenger) 57 handled = True 58 59 # When processing outgoing messages, no replies or deliveries are 60 # performed. 61 62 if outgoing_only: 63 return 64 65 # Pack any returned parts into a single message. 66 67 if all_responses: 68 outgoing_parts = [] 69 forwarded_parts = [] 70 71 for outgoing, part in all_responses: 72 if outgoing: 73 outgoing_parts.append(part) 74 else: 75 forwarded_parts.append(part) 76 77 # Reply using any outgoing parts in a new message. 78 79 if outgoing_parts: 80 message = self.messenger.make_message(outgoing_parts, senders) 81 82 if "-d" in sys.argv: 83 print >>sys.stderr, "Outgoing parts..." 84 print message 85 else: 86 self.messenger.sendmail(senders, message.as_string()) 87 88 # Forward messages to their recipients using the existing message. 89 90 if forwarded_parts: 91 message = self.messenger.wrap_message(msg, forwarded_parts) 92 93 if "-d" in sys.argv: 94 print >>sys.stderr, "Forwarded parts..." 95 print message 96 elif self.lmtp_socket: 97 self.messenger.sendmail(original_recipients, message.as_string(), lmtp_socket=self.lmtp_socket) 98 99 # Unhandled messages are delivered as they are. 100 101 if not handled: 102 if "-d" in sys.argv: 103 print >>sys.stderr, "Unhandled parts..." 104 print msg 105 elif self.lmtp_socket: 106 self.messenger.sendmail(original_recipients, msg.as_string(), lmtp_socket=self.lmtp_socket) 107 108 def process_args(self, args, stream): 109 110 """ 111 Interpret the given program arguments 'args' and process input from the 112 given 'stream'. 113 """ 114 115 # Obtain the different kinds of recipients plus sender address. 116 117 original_recipients = [] 118 recipients = [] 119 senders = [] 120 lmtp = [] 121 outgoing_only = False 122 123 l = [] 124 125 for arg in args: 126 127 # Detect outgoing processing mode. 128 129 if arg == "-O": 130 outgoing_only = True 131 132 # Switch to collecting recipients. 133 134 if arg == "-o": 135 l = original_recipients 136 elif arg == "-r": 137 l = recipients 138 139 # Switch to collecting senders. 140 141 elif arg == "-s": 142 l = senders 143 144 # Switch to getting the LMTP socket. 145 146 elif arg == "-l": 147 l = lmtp 148 149 # Ignore debugging options. 150 151 elif arg == "-d": 152 pass 153 else: 154 l.append(arg) 155 156 self.messenger.sender = senders and senders[0] or self.messenger.sender 157 self.lmtp_socket = lmtp and lmtp[0] or None 158 self.process(stream, original_recipients, recipients, outgoing_only) 159 160 def __call__(self): 161 162 """ 163 Obtain arguments from the command line to initialise the processor 164 before invoking it. 165 """ 166 167 args = sys.argv[1:] 168 169 if "-d" in args: 170 self.process_args(args, sys.stdin) 171 else: 172 try: 173 self.process_args(args, sys.stdin) 174 except SystemExit, value: 175 sys.exit(value) 176 except Exception, exc: 177 if "-v" in args: 178 raise 179 type, value, tb = sys.exc_info() 180 print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno) 181 #import traceback 182 #traceback.print_exc(file=open("/tmp/mail.log", "a")) 183 sys.exit(EX_TEMPFAIL) 184 sys.exit(0) 185 186 # vim: tabstop=4 expandtab shiftwidth=4