1.1 --- a/imiptools/__init__.py Tue Oct 21 19:58:20 2014 +0200
1.2 +++ b/imiptools/__init__.py Tue Oct 21 23:39:55 2014 +0200
1.3 @@ -0,0 +1,165 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +from email import message_from_file
1.7 +from email.mime.multipart import MIMEMultipart
1.8 +from email.mime.text import MIMEText
1.9 +from smtplib import SMTP
1.10 +from imiptools.content import handle_itip_part
1.11 +import imip_resource
1.12 +import sys
1.13 +
1.14 +MESSAGE_SENDER = "resources+agent@example.com"
1.15 +
1.16 +MESSAGE_SUBJECT = "Calendar system message"
1.17 +
1.18 +MESSAGE_TEXT = """\
1.19 +This is a response to a calendar message sent by your calendar program.
1.20 +"""
1.21 +
1.22 +# Postfix exit codes.
1.23 +
1.24 +EX_TEMPFAIL = 75
1.25 +
1.26 +# Permitted iTIP content types.
1.27 +
1.28 +itip_content_types = [
1.29 + "text/calendar", # from RFC 6047
1.30 + "text/x-vcalendar", "application/ics", # other possibilities
1.31 + ]
1.32 +
1.33 +# Sending of outgoing messages.
1.34 +
1.35 +def sendmail(sender, recipients, data):
1.36 + smtp = SMTP("localhost")
1.37 + smtp.sendmail(sender, recipients, data)
1.38 + smtp.quit()
1.39 +
1.40 +# Processing of incoming messages.
1.41 +
1.42 +def get_all_values(msg, key):
1.43 + l = []
1.44 + for v in msg.get_all(key) or []:
1.45 + l += [s.strip() for s in v.split(",")]
1.46 + return l
1.47 +
1.48 +class Processor:
1.49 +
1.50 + "The processing framework."
1.51 +
1.52 + def __init__(self, handlers, sender=None, subject=None, body_text=None):
1.53 + self.handlers = handlers
1.54 + self.sender = sender or MESSAGE_SENDER
1.55 + self.subject = subject or MESSAGE_SUBJECT
1.56 + self.body_text = body_text or MESSAGE_TEXT
1.57 +
1.58 + def process(self, f, original_recipients, recipients):
1.59 +
1.60 + """
1.61 + Process content from the stream 'f' accompanied by the given
1.62 + 'original_recipients' and 'recipients'.
1.63 + """
1.64 +
1.65 + msg = message_from_file(f)
1.66 + senders = msg.get_all("Reply-To") or msg.get_all("From")
1.67 + original_recipients = original_recipients or get_all_values(msg, "To")
1.68 +
1.69 + # Handle messages with iTIP parts.
1.70 +
1.71 + all_parts = []
1.72 +
1.73 + for part in msg.walk():
1.74 + if part.get_content_type() in itip_content_types and \
1.75 + part.get_param("method"):
1.76 +
1.77 + # NOTE: Act on behalf of resources for now.
1.78 +
1.79 + all_parts += handle_itip_part(part, original_recipients, self.handlers)
1.80 +
1.81 + # Pack the parts into a single message.
1.82 +
1.83 + if all_parts:
1.84 + message = self.make_message(all_parts, senders)
1.85 +
1.86 + if "-d" in sys.argv:
1.87 + print message
1.88 + else:
1.89 + sendmail(self.sender, senders, message.as_string())
1.90 +
1.91 + def make_message(self, parts, recipients):
1.92 +
1.93 + "Make a message from the given 'parts' for the given 'recipients'."
1.94 +
1.95 + message = MIMEMultipart("alternative", _subparts=parts)
1.96 + message.preamble = self.body_text
1.97 + payload = message.get_payload()
1.98 + payload.insert(0, MIMEText(self.body_text))
1.99 +
1.100 + message["From"] = self.sender
1.101 + for recipient in recipients:
1.102 + message["To"] = recipient
1.103 + message["Subject"] = self.subject
1.104 +
1.105 + return message
1.106 +
1.107 + def process_args(self, args, stream):
1.108 +
1.109 + """
1.110 + Interpret the given program arguments 'args' and process input from the
1.111 + given 'stream'.
1.112 + """
1.113 +
1.114 + # Obtain the different kinds of recipients plus sender address.
1.115 +
1.116 + original_recipients = []
1.117 + recipients = []
1.118 + senders = []
1.119 +
1.120 + l = []
1.121 +
1.122 + for arg in args:
1.123 +
1.124 + # Switch to collecting recipients.
1.125 +
1.126 + if arg == "-o":
1.127 + l = original_recipients
1.128 + elif arg == "-r":
1.129 + l = recipients
1.130 +
1.131 + # Switch to collecting senders.
1.132 +
1.133 + elif arg == "-s":
1.134 + l = senders
1.135 +
1.136 + # Ignore debugging options.
1.137 +
1.138 + elif arg == "-d":
1.139 + pass
1.140 + else:
1.141 + l.append(arg)
1.142 +
1.143 + self.sender = senders and senders[0] or self.sender
1.144 + self.process(stream, original_recipients, recipients)
1.145 +
1.146 + def __call__(self):
1.147 +
1.148 + """
1.149 + Obtain arguments from the command line to initialise the processor
1.150 + before invoking it.
1.151 + """
1.152 +
1.153 + args = sys.argv[1:]
1.154 +
1.155 + if "-d" in args:
1.156 + self.process_args(args, sys.stdin)
1.157 + else:
1.158 + try:
1.159 + self.process_args(args, sys.stdin)
1.160 + except SystemExit, value:
1.161 + sys.exit(value)
1.162 + except Exception, exc:
1.163 + type, value, tb = sys.exc_info()
1.164 + print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno)
1.165 + sys.exit(EX_TEMPFAIL)
1.166 + sys.exit(0)
1.167 +
1.168 +# vim: tabstop=4 expandtab shiftwidth=4