1 #!/usr/bin/env python 2 3 from email import message_from_file 4 from imiptools.content import 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): 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 = msg.get_all("Reply-To") or msg.get_all("From") 45 original_recipients = original_recipients or 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, original_recipients, self.handlers) 57 handled = True 58 59 # Pack any returned parts into a single message. 60 61 if all_responses: 62 outgoing_parts = [] 63 forwarded_parts = [] 64 65 for outgoing, part in all_responses: 66 if outgoing: 67 outgoing_parts.append(part) 68 else: 69 forwarded_parts.append(part) 70 71 # Reply using any outgoing parts in a new message. 72 73 if outgoing_parts: 74 message = self.messenger.make_message(outgoing_parts, senders) 75 76 if "-d" in sys.argv: 77 print message 78 else: 79 self.messenger.sendmail(senders, message.as_string()) 80 81 # Forward messages to their recipients using the existing message. 82 83 if forwarded_parts: 84 message = self.messenger.wrap_message(msg, forwarded_parts) 85 86 if "-d" in sys.argv: 87 print message 88 elif self.lmtp_socket: 89 self.messenger.sendmail(original_recipients, message.as_string(), lmtp_socket=self.lmtp_socket) 90 91 # Unhandled messages are delivered as they are. 92 93 if not handled: 94 if "-d" in sys.argv: 95 print msg 96 elif self.lmtp_socket: 97 self.messenger.sendmail(original_recipients, msg.as_string(), lmtp_socket=self.lmtp_socket) 98 99 def process_args(self, args, stream): 100 101 """ 102 Interpret the given program arguments 'args' and process input from the 103 given 'stream'. 104 """ 105 106 # Obtain the different kinds of recipients plus sender address. 107 108 original_recipients = [] 109 recipients = [] 110 senders = [] 111 lmtp = [] 112 113 l = [] 114 115 for arg in args: 116 117 # Switch to collecting recipients. 118 119 if arg == "-o": 120 l = original_recipients 121 elif arg == "-r": 122 l = recipients 123 124 # Switch to collecting senders. 125 126 elif arg == "-s": 127 l = senders 128 129 # Switch to getting the LMTP socket. 130 131 elif arg == "-l": 132 l = lmtp 133 134 # Ignore debugging options. 135 136 elif arg == "-d": 137 pass 138 else: 139 l.append(arg) 140 141 self.messenger.sender = senders and senders[0] or self.messenger.sender 142 self.lmtp_socket = lmtp and lmtp[0] or None 143 self.process(stream, original_recipients, recipients) 144 145 def __call__(self): 146 147 """ 148 Obtain arguments from the command line to initialise the processor 149 before invoking it. 150 """ 151 152 args = sys.argv[1:] 153 154 if "-d" in args: 155 self.process_args(args, sys.stdin) 156 else: 157 try: 158 self.process_args(args, sys.stdin) 159 except SystemExit, value: 160 sys.exit(value) 161 except Exception, exc: 162 if "-v" in args: 163 raise 164 type, value, tb = sys.exc_info() 165 print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno) 166 #import traceback 167 #traceback.print_exc(file=open("/tmp/mail.log", "a")) 168 sys.exit(EX_TEMPFAIL) 169 sys.exit(0) 170 171 # vim: tabstop=4 expandtab shiftwidth=4