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