1 #!/usr/bin/env python 2 3 from email import message_from_file 4 from email.mime.multipart import MIMEMultipart 5 from email.mime.text import MIMEText 6 from smtplib import SMTP 7 from imiptools.content import handle_itip_part 8 import sys 9 10 MESSAGE_SENDER = "resources+agent@example.com" 11 12 MESSAGE_SUBJECT = "Calendar system message" 13 14 MESSAGE_TEXT = """\ 15 This is a response to a calendar message sent by your calendar program. 16 """ 17 18 # Postfix exit codes. 19 20 EX_TEMPFAIL = 75 21 22 # Permitted iTIP content types. 23 24 itip_content_types = [ 25 "text/calendar", # from RFC 6047 26 "text/x-vcalendar", "application/ics", # other possibilities 27 ] 28 29 # Sending of outgoing messages. 30 31 def sendmail(sender, recipients, data): 32 smtp = SMTP("localhost") 33 smtp.sendmail(sender, recipients, data) 34 smtp.quit() 35 36 # Processing of incoming messages. 37 38 def get_all_values(msg, key): 39 l = [] 40 for v in msg.get_all(key) or []: 41 l += [s.strip() for s in v.split(",")] 42 return l 43 44 class Processor: 45 46 "The processing framework." 47 48 def __init__(self, handlers, sender=None, subject=None, body_text=None): 49 self.handlers = handlers 50 self.sender = sender or MESSAGE_SENDER 51 self.subject = subject or MESSAGE_SUBJECT 52 self.body_text = body_text or MESSAGE_TEXT 53 54 def process(self, f, original_recipients, recipients): 55 56 """ 57 Process content from the stream 'f' accompanied by the given 58 'original_recipients' and 'recipients'. 59 """ 60 61 msg = message_from_file(f) 62 senders = msg.get_all("Reply-To") or msg.get_all("From") 63 original_recipients = original_recipients or get_all_values(msg, "To") 64 65 # Handle messages with iTIP parts. 66 67 all_parts = [] 68 69 for part in msg.walk(): 70 if part.get_content_type() in itip_content_types and \ 71 part.get_param("method"): 72 73 all_parts += handle_itip_part(part, original_recipients, self.handlers) 74 75 # Pack any returned parts into a single message. 76 77 if all_parts: 78 message = self.make_message(all_parts, senders) 79 80 if "-d" in sys.argv: 81 print message 82 else: 83 sendmail(self.sender, senders, message.as_string()) 84 85 # Forward messages to their recipients. 86 87 def make_message(self, parts, recipients): 88 89 "Make a message from the given 'parts' for the given 'recipients'." 90 91 message = MIMEMultipart("alternative", _subparts=parts) 92 message.preamble = self.body_text 93 payload = message.get_payload() 94 payload.insert(0, MIMEText(self.body_text)) 95 96 message["From"] = self.sender 97 for recipient in recipients: 98 message["To"] = recipient 99 message["Subject"] = self.subject 100 101 return message 102 103 def process_args(self, args, stream): 104 105 """ 106 Interpret the given program arguments 'args' and process input from the 107 given 'stream'. 108 """ 109 110 # Obtain the different kinds of recipients plus sender address. 111 112 original_recipients = [] 113 recipients = [] 114 senders = [] 115 116 l = [] 117 118 for arg in args: 119 120 # Switch to collecting recipients. 121 122 if arg == "-o": 123 l = original_recipients 124 elif arg == "-r": 125 l = recipients 126 127 # Switch to collecting senders. 128 129 elif arg == "-s": 130 l = senders 131 132 # Ignore debugging options. 133 134 elif arg == "-d": 135 pass 136 else: 137 l.append(arg) 138 139 self.sender = senders and senders[0] or self.sender 140 self.process(stream, original_recipients, recipients) 141 142 def __call__(self): 143 144 """ 145 Obtain arguments from the command line to initialise the processor 146 before invoking it. 147 """ 148 149 args = sys.argv[1:] 150 151 if "-d" in args: 152 self.process_args(args, sys.stdin) 153 else: 154 try: 155 self.process_args(args, sys.stdin) 156 except SystemExit, value: 157 sys.exit(value) 158 except Exception, exc: 159 type, value, tb = sys.exc_info() 160 print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno) 161 sys.exit(EX_TEMPFAIL) 162 sys.exit(0) 163 164 # vim: tabstop=4 expandtab shiftwidth=4