1.1 --- a/imip_agent.py Tue Oct 21 19:58:20 2014 +0200
1.2 +++ b/imip_agent.py Tue Oct 21 23:39:55 2014 +0200
1.3 @@ -1,136 +1,8 @@
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 +from imiptools import Processor
1.12 import imip_resource
1.13 -import sys
1.14 -
1.15 -MESSAGE_SENDER = "resources+agent@example.com"
1.16 -
1.17 -MESSAGE_SUBJECT = "Calendar system message"
1.18 -
1.19 -MESSAGE_TEXT = """\
1.20 -This is a response to a calendar message sent by your calendar program.
1.21 -"""
1.22 -
1.23 -# Postfix exit codes.
1.24 -
1.25 -EX_TEMPFAIL = 75
1.26 -
1.27 -# Permitted iTIP content types.
1.28 -
1.29 -itip_content_types = [
1.30 - "text/calendar", # from RFC 6047
1.31 - "text/x-vcalendar", "application/ics", # other possibilities
1.32 - ]
1.33 -
1.34 -# Sending of outgoing messages.
1.35 -
1.36 -def sendmail(sender, recipients, data):
1.37 - smtp = SMTP("localhost")
1.38 - smtp.sendmail(sender, recipients, data)
1.39 - smtp.quit()
1.40 -
1.41 -# Processing of incoming messages.
1.42 -
1.43 -def process(f, original_recipients, recipients, this_sender):
1.44 -
1.45 - """
1.46 - Process content from the stream 'f' accompanied by the given
1.47 - 'original_recipients' and 'recipients', together with details of
1.48 - 'this_sender' (the sender address on messages sent by this service).
1.49 - """
1.50 -
1.51 - msg = message_from_file(f)
1.52 - senders = msg.get_all("Reply-To") or msg.get_all("From")
1.53 - original_recipients = original_recipients or get_all_values(msg, "To")
1.54 -
1.55 - # Handle messages with iTIP parts.
1.56 -
1.57 - all_parts = []
1.58 -
1.59 - for part in msg.walk():
1.60 - if part.get_content_type() in itip_content_types and \
1.61 - part.get_param("method"):
1.62 -
1.63 - # NOTE: Act on behalf of resources for now.
1.64 -
1.65 - all_parts += handle_itip_part(part, original_recipients, imip_resource.handlers)
1.66 -
1.67 - # Pack the parts into a single message.
1.68
1.69 - if all_parts:
1.70 - text_part = MIMEText(MESSAGE_TEXT)
1.71 - all_parts.insert(0, text_part)
1.72 - message = MIMEMultipart("alternative", _subparts=all_parts)
1.73 - message.preamble = MESSAGE_TEXT
1.74 -
1.75 - message["From"] = this_sender
1.76 - for sender in senders:
1.77 - message["To"] = sender
1.78 - message["Subject"] = MESSAGE_SUBJECT
1.79 -
1.80 - if "-d" in sys.argv:
1.81 - print message
1.82 - else:
1.83 - sendmail(this_sender, senders, message.as_string())
1.84 -
1.85 -def get_all_values(msg, key):
1.86 - l = []
1.87 - for v in msg.get_all(key) or []:
1.88 - l += [s.strip() for s in v.split(",")]
1.89 - return l
1.90 -
1.91 -def main():
1.92 -
1.93 - "Interpret program arguments and process input."
1.94 -
1.95 - # Obtain the different kinds of recipients plus sender address.
1.96 -
1.97 - original_recipients = []
1.98 - recipients = []
1.99 - senders = []
1.100 -
1.101 - l = []
1.102 -
1.103 - for arg in sys.argv[1:]:
1.104 -
1.105 - # Switch to collecting recipients.
1.106 -
1.107 - if arg == "-o":
1.108 - l = original_recipients
1.109 - elif arg == "-r":
1.110 - l = recipients
1.111 -
1.112 - # Switch to collecting senders.
1.113 -
1.114 - elif arg == "-s":
1.115 - l = senders
1.116 -
1.117 - # Ignore debugging options.
1.118 -
1.119 - elif arg == "-d":
1.120 - pass
1.121 - else:
1.122 - l.append(arg)
1.123 -
1.124 - process(sys.stdin, original_recipients, recipients, senders and senders[0] or MESSAGE_SENDER)
1.125 -
1.126 -if __name__ == "__main__":
1.127 - if "-d" in sys.argv[1:]:
1.128 - main()
1.129 - else:
1.130 - try:
1.131 - main()
1.132 - except SystemExit, value:
1.133 - sys.exit(value)
1.134 - except Exception, exc:
1.135 - type, value, tb = sys.exc_info()
1.136 - print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno)
1.137 - sys.exit(EX_TEMPFAIL)
1.138 - sys.exit(0)
1.139 +Processor(imip_resource.handlers)()
1.140
1.141 # vim: tabstop=4 expandtab shiftwidth=4
2.1 --- a/imiptools/__init__.py Tue Oct 21 19:58:20 2014 +0200
2.2 +++ b/imiptools/__init__.py Tue Oct 21 23:39:55 2014 +0200
2.3 @@ -0,0 +1,165 @@
2.4 +#!/usr/bin/env python
2.5 +
2.6 +from email import message_from_file
2.7 +from email.mime.multipart import MIMEMultipart
2.8 +from email.mime.text import MIMEText
2.9 +from smtplib import SMTP
2.10 +from imiptools.content import handle_itip_part
2.11 +import imip_resource
2.12 +import sys
2.13 +
2.14 +MESSAGE_SENDER = "resources+agent@example.com"
2.15 +
2.16 +MESSAGE_SUBJECT = "Calendar system message"
2.17 +
2.18 +MESSAGE_TEXT = """\
2.19 +This is a response to a calendar message sent by your calendar program.
2.20 +"""
2.21 +
2.22 +# Postfix exit codes.
2.23 +
2.24 +EX_TEMPFAIL = 75
2.25 +
2.26 +# Permitted iTIP content types.
2.27 +
2.28 +itip_content_types = [
2.29 + "text/calendar", # from RFC 6047
2.30 + "text/x-vcalendar", "application/ics", # other possibilities
2.31 + ]
2.32 +
2.33 +# Sending of outgoing messages.
2.34 +
2.35 +def sendmail(sender, recipients, data):
2.36 + smtp = SMTP("localhost")
2.37 + smtp.sendmail(sender, recipients, data)
2.38 + smtp.quit()
2.39 +
2.40 +# Processing of incoming messages.
2.41 +
2.42 +def get_all_values(msg, key):
2.43 + l = []
2.44 + for v in msg.get_all(key) or []:
2.45 + l += [s.strip() for s in v.split(",")]
2.46 + return l
2.47 +
2.48 +class Processor:
2.49 +
2.50 + "The processing framework."
2.51 +
2.52 + def __init__(self, handlers, sender=None, subject=None, body_text=None):
2.53 + self.handlers = handlers
2.54 + self.sender = sender or MESSAGE_SENDER
2.55 + self.subject = subject or MESSAGE_SUBJECT
2.56 + self.body_text = body_text or MESSAGE_TEXT
2.57 +
2.58 + def process(self, f, original_recipients, recipients):
2.59 +
2.60 + """
2.61 + Process content from the stream 'f' accompanied by the given
2.62 + 'original_recipients' and 'recipients'.
2.63 + """
2.64 +
2.65 + msg = message_from_file(f)
2.66 + senders = msg.get_all("Reply-To") or msg.get_all("From")
2.67 + original_recipients = original_recipients or get_all_values(msg, "To")
2.68 +
2.69 + # Handle messages with iTIP parts.
2.70 +
2.71 + all_parts = []
2.72 +
2.73 + for part in msg.walk():
2.74 + if part.get_content_type() in itip_content_types and \
2.75 + part.get_param("method"):
2.76 +
2.77 + # NOTE: Act on behalf of resources for now.
2.78 +
2.79 + all_parts += handle_itip_part(part, original_recipients, self.handlers)
2.80 +
2.81 + # Pack the parts into a single message.
2.82 +
2.83 + if all_parts:
2.84 + message = self.make_message(all_parts, senders)
2.85 +
2.86 + if "-d" in sys.argv:
2.87 + print message
2.88 + else:
2.89 + sendmail(self.sender, senders, message.as_string())
2.90 +
2.91 + def make_message(self, parts, recipients):
2.92 +
2.93 + "Make a message from the given 'parts' for the given 'recipients'."
2.94 +
2.95 + message = MIMEMultipart("alternative", _subparts=parts)
2.96 + message.preamble = self.body_text
2.97 + payload = message.get_payload()
2.98 + payload.insert(0, MIMEText(self.body_text))
2.99 +
2.100 + message["From"] = self.sender
2.101 + for recipient in recipients:
2.102 + message["To"] = recipient
2.103 + message["Subject"] = self.subject
2.104 +
2.105 + return message
2.106 +
2.107 + def process_args(self, args, stream):
2.108 +
2.109 + """
2.110 + Interpret the given program arguments 'args' and process input from the
2.111 + given 'stream'.
2.112 + """
2.113 +
2.114 + # Obtain the different kinds of recipients plus sender address.
2.115 +
2.116 + original_recipients = []
2.117 + recipients = []
2.118 + senders = []
2.119 +
2.120 + l = []
2.121 +
2.122 + for arg in args:
2.123 +
2.124 + # Switch to collecting recipients.
2.125 +
2.126 + if arg == "-o":
2.127 + l = original_recipients
2.128 + elif arg == "-r":
2.129 + l = recipients
2.130 +
2.131 + # Switch to collecting senders.
2.132 +
2.133 + elif arg == "-s":
2.134 + l = senders
2.135 +
2.136 + # Ignore debugging options.
2.137 +
2.138 + elif arg == "-d":
2.139 + pass
2.140 + else:
2.141 + l.append(arg)
2.142 +
2.143 + self.sender = senders and senders[0] or self.sender
2.144 + self.process(stream, original_recipients, recipients)
2.145 +
2.146 + def __call__(self):
2.147 +
2.148 + """
2.149 + Obtain arguments from the command line to initialise the processor
2.150 + before invoking it.
2.151 + """
2.152 +
2.153 + args = sys.argv[1:]
2.154 +
2.155 + if "-d" in args:
2.156 + self.process_args(args, sys.stdin)
2.157 + else:
2.158 + try:
2.159 + self.process_args(args, sys.stdin)
2.160 + except SystemExit, value:
2.161 + sys.exit(value)
2.162 + except Exception, exc:
2.163 + type, value, tb = sys.exc_info()
2.164 + print >>sys.stderr, "Exception %s at %d" % (exc, tb.tb_lineno)
2.165 + sys.exit(EX_TEMPFAIL)
2.166 + sys.exit(0)
2.167 +
2.168 +# vim: tabstop=4 expandtab shiftwidth=4