# HG changeset patch # User Paul Boddie # Date 1414095857 -7200 # Node ID 1e301f44507caa7b390f5efdf623211a3e2eafce # Parent 8d4735b2bac7494e2f78b7d7cf3275b059e4d885 Changed handler return values to indicate the method for any generated content, thus permitting either replies (to the original senders) or deliveries (to the original recipients). Fixed the form of free/busy replies without any usable information. Added support for delivery via LMTP for messages that are forwarded to their original recipients. diff -r 8d4735b2bac7 -r 1e301f44507c imiptools/__init__.py --- a/imiptools/__init__.py Wed Oct 22 15:46:50 2014 +0200 +++ b/imiptools/__init__.py Thu Oct 23 22:24:17 2014 +0200 @@ -3,7 +3,7 @@ from email import message_from_file from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText -from smtplib import SMTP +from smtplib import LMTP, SMTP from imiptools.content import handle_itip_part import sys @@ -28,8 +28,11 @@ # Sending of outgoing messages. -def sendmail(sender, recipients, data): - smtp = SMTP("localhost") +def sendmail(sender, recipients, data, lmtp_socket=None): + if lmtp_socket: + smtp = LMTP(lmtp_socket) + else: + smtp = SMTP("localhost") smtp.sendmail(sender, recipients, data) smtp.quit() @@ -50,6 +53,7 @@ self.sender = sender or MESSAGE_SENDER self.subject = subject or MESSAGE_SUBJECT self.body_text = body_text or MESSAGE_TEXT + self.lmtp_socket = None def process(self, f, original_recipients, recipients): @@ -64,25 +68,55 @@ # Handle messages with iTIP parts. - all_parts = [] + all_responses = [] + handled = False for part in msg.walk(): if part.get_content_type() in itip_content_types and \ part.get_param("method"): - all_parts += handle_itip_part(part, original_recipients, self.handlers) + all_responses += handle_itip_part(part, original_recipients, self.handlers) + handled = True # Pack any returned parts into a single message. - if all_parts: - message = self.make_message(all_parts, senders) + if all_responses: + outgoing_parts = [] + forwarded_parts = [] + + for outgoing, part in all_responses: + if outgoing: + outgoing_parts.append(part) + else: + forwarded_parts.append(part) + + # Reply using any outgoing parts in a new message. + + if outgoing_parts: + message = self.make_message(outgoing_parts, senders) + if "-d" in sys.argv: + print message + else: + sendmail(self.sender, senders, message.as_string()) + + # Forward messages to their recipients using the existing message. + + if forwarded_parts: + msg.get_payload()[:0] = forwarded_parts + + if "-d" in sys.argv: + print msg + elif self.lmtp_socket: + sendmail(self.sender, original_recipients, msg.as_string(), self.lmtp_socket) + + # Unhandled messages are delivered as they are. + + if not handled: if "-d" in sys.argv: - print message - else: - sendmail(self.sender, senders, message.as_string()) - - # Forward messages to their recipients. + print msg + elif self.lmtp_socket: + sendmail(self.sender, original_recipients, msg.as_string(), self.lmtp_socket) def make_message(self, parts, recipients): @@ -112,6 +146,7 @@ original_recipients = [] recipients = [] senders = [] + lmtp = [] l = [] @@ -129,6 +164,11 @@ elif arg == "-s": l = senders + # Switch to getting the LMTP socket. + + elif arg == "-l": + l = lmtp + # Ignore debugging options. elif arg == "-d": @@ -137,6 +177,7 @@ l.append(arg) self.sender = senders and senders[0] or self.sender + self.lmtp_socket = lmtp and lmtp[0] or None self.process(stream, original_recipients, recipients) def __call__(self): @@ -156,6 +197,8 @@ except SystemExit, value: sys.exit(value) except Exception, exc: + if "-v" in args: + raise type, value, tb = sys.exc_info() print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno) sys.exit(EX_TEMPFAIL) diff -r 8d4735b2bac7 -r 1e301f44507c imiptools/content.py --- a/imiptools/content.py Wed Oct 22 15:46:50 2014 +0200 +++ b/imiptools/content.py Thu Oct 23 22:24:17 2014 +0200 @@ -142,7 +142,8 @@ """ Handle the given iTIP 'part' for the given 'recipients' using the given - 'handlers'. + 'handlers'. Return a list of responses, each response being a tuple of the + form (is-outgoing, message-part). """ method = part.get_param("method") @@ -158,17 +159,13 @@ if not itip: return [] - # Only handle calendar information. - - all_parts = [] - # Require consistency between declared and employed methods. if get_value(itip, "METHOD") == method: # Look for different kinds of sections. - all_objects = [] + all_results = [] for name, cls in handlers: for details in get_values(itip, name) or []: @@ -176,19 +173,18 @@ # Dispatch to a handler and obtain any response. handler = cls(details, recipients) - object = methods[method](handler)() + result = methods[method](handler)() # Concatenate responses for a single calendar object. - if object: - all_objects += object - - # Obtain a message part for the objects. + if result: + response_method, part = result + outgoing = method != response_method + all_results.append((outgoing, part)) - if all_objects: - all_parts.append(to_part(response_methods[method], all_objects)) + return all_results - return all_parts + return [] def parse_object(f, encoding, objtype): @@ -337,8 +333,4 @@ "REQUEST" : lambda handler: handler.request, } -response_methods = { - "REQUEST" : "REPLY", - } - # vim: tabstop=4 expandtab shiftwidth=4 diff -r 8d4735b2bac7 -r 1e301f44507c imiptools/handlers/person.py --- a/imiptools/handlers/person.py Wed Oct 22 15:46:50 2014 +0200 +++ b/imiptools/handlers/person.py Thu Oct 23 22:24:17 2014 +0200 @@ -4,7 +4,8 @@ Handlers for a person for whom scheduling is performed. """ -from imiptools.content import Handler +from email.mime.text import MIMEText +from imiptools.content import Handler, to_part from vCalendar import to_node class Event(Handler): @@ -71,6 +72,8 @@ # The message is now wrapped and passed on to the recipient. + return "REQUEST", MIMEText("A request has been queued.") + class Freebusy(Handler): "A free/busy handler." @@ -110,22 +113,22 @@ for attendee, attendee_attr in attendees.items(): freebusy = self.store.get_freebusy(attendee) - if freebusy: - record = [] - rwrite = record.append + record = [] + rwrite = record.append - rwrite(("ORGANIZER", organiser_attr, organiser)) - rwrite(("ATTENDEE", attendee_attr, attendee)) - rwrite(("UID", {}, self.uid)) + rwrite(("ORGANIZER", organiser_attr, organiser)) + rwrite(("ATTENDEE", attendee_attr, attendee)) + rwrite(("UID", {}, self.uid)) + if freebusy: for start, end, uid in freebusy: rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end])) - cwrite(("VFREEBUSY", {}, record)) + cwrite(("VFREEBUSY", {}, record)) # Return the reply. - return calendar + return "REPLY", to_part("REPLY", calendar) class Journal(Handler): diff -r 8d4735b2bac7 -r 1e301f44507c imiptools/handlers/resource.py --- a/imiptools/handlers/resource.py Wed Oct 22 15:46:50 2014 +0200 +++ b/imiptools/handlers/resource.py Thu Oct 23 22:24:17 2014 +0200 @@ -5,7 +5,7 @@ """ from datetime import date, datetime, timedelta -from imiptools.content import Handler, format_datetime +from imiptools.content import Handler, format_datetime, to_part from imiptools.period import insert_period, period_overlaps, remove_period from vCalendar import to_node from vRecurrence import get_parameters, get_rule @@ -152,7 +152,7 @@ {"VEVENT" : [(self.details, {})]} )) - return calendar + return "REPLY", to_part("REPLY", calendar) class Freebusy(Handler): @@ -190,22 +190,22 @@ for attendee, attendee_attr in attendees.items(): freebusy = self.store.get_freebusy(attendee) - if freebusy: - record = [] - rwrite = record.append + record = [] + rwrite = record.append - rwrite(("ORGANIZER", organiser_attr, organiser)) - rwrite(("ATTENDEE", attendee_attr, attendee)) - rwrite(("UID", {}, self.uid)) + rwrite(("ORGANIZER", organiser_attr, organiser)) + rwrite(("ATTENDEE", attendee_attr, attendee)) + rwrite(("UID", {}, self.uid)) + if freebusy: for start, end, uid in freebusy: rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, [start, end])) - cwrite(("VFREEBUSY", {}, record)) + cwrite(("VFREEBUSY", {}, record)) # Return the reply. - return calendar + return "REPLY", to_part("REPLY", calendar) class Journal(Handler):