#!/usr/bin/python # schedulebe (c) robipolli@gmail.com # a postfix plugin for managing events in python # License: GPL2 # # # this software manage mail meeting invitation, # notifying to bedework # * meeting request to bedework users # * meeting replies # takes a mail from stdin # check if it's a meeting request/reply # get header from mail # use them to make a POST request to bedework RTSVC # import StringIO import vobject import email, httplib import re import pycurl import sys, getopt from xml.dom import minidom from xml.parsers.expat import ExpatError _debug = False #rtsvcUrl = "http://caladminurl" #rtsvcUrl = "http://pubcaladminurl" rtsvcUrl = None conf_request_enabled = True conf_mail_enabled = True # rtsvcUser = "user1" # rtsvcPass = "user2" class Meeting: def __init__(self): self.ics = None self.method = None self.organizer = None self.attendees = [] self.sender = None self.recipient = [] def setMail(self, mailMessage): self.walkMail(mailMessage) def getMethod(self): return self.ics.method.value def getOrganizer(self): # TODO check organizer ~= /mailto:/ o = cleanMailAddress(self.ics.vevent.organizer.value) return o def getAttendees(self): if len(self.attendees) == 0: for a in self.ics.vevent.__getattr__("attendee_list"): self.attendees.append(cleanMailAddress(a.value)) return self.attendees def validate(self): if conf_request_enabled and self.getMethod() == "REQUEST": #mail recipient must be internal and in attendees # in a meeting request, organizer is the mail sender if self.getOrganizer() != self.sender: if _debug: print "in %s: Organizer != sender : %s != %s" % (self.getMethod(), self.getOrganizer(), self.sender) return False elif self.getMethod() == "REPLY": #the sender mail should be one of the attendees if not self.sender in self.getAttendees(): if _debug: print "in %s: sender != attendees : %s not contains %s" % (self.getMethod(),self.getAttendees(), self.sender) return False else: print "Error validating meeting" return True # check that organizer is valid # if not __checkOrganizer(self.getOrganizer()): # return False def walkMail(self, mailMessage): """walk thru the mail looking for calendar attachment""" """parse attachment and set meeting.ics""" self.sender = cleanMailAddress(mailMessage['From']) for rcpt in mailMessage['To'].split(","): self.recipient.append(cleanMailAddress(rcpt)) if _debug: print "DEBUG: from: %s, rcpt: %s" % (mailMessage['From'], mailMessage['To']) mailWalker = mailMessage.walk() try: for i in mailWalker: if i.get_content_type() == "text/calendar": if self != None: icalendar = vobject.readOne(i.get_payload(decode=True)) self.ics = icalendar else: if _debug: print "Test case: %s" % i.get_payload(decode=True) except vobject.base.ParseError: print "Error while parsing calendar" raise #end class def __checkOrganizer(organizer): """check if organizer is an user of the platform""" return True def getMeetingInfo(meeting): """get meeting info """ """ TODO http://docs.python.org/library/email.html#module-email""" meeting.method = meeting.ics.method.value meeting.organizer = meeting return meeting.method.value def cleanMailAddress(mailaddress): """clean the mail address returning a string value (no unicode)""" s = re.compile('^.*<(.+)>.*',re.IGNORECASE).sub(r'\1', mailaddress) s = re.compile('mailto:',re.IGNORECASE).sub(r'', s) return str(s.lower()) def getRecipientsFromMail(mail): """get the TO Header from the email""" recipients = ["one@example.com","two@exmaple.com"] recipient = mail['To'] return recipients def sendRequestToBedework(meeting): """send a POST request to RTSVC url, setting """ """ Header: originator: me@gmail.com """ """ Header: recipient: one@example.com """ """ Header: recipient: two@example.com """ """ Header: Content-type: text/calendar """ """ .ics as POST body """ if not meeting.validate(): return False rtsvcHeader = [ 'Content-Type:text/calendar; charset=UTF-8' ] rtsvcHeader.append('originator: ' + meeting.sender) if conf_mail_enabled: rcptList = [] if meeting.getMethod() == "REQUEST": rcptList = set(meeting.getAttendees()).intersection(meeting.recipient) elif meeting.getMethod() == "REPLY": rcptList.append(meeting.getOrganizer()) rcptList = set(rcptList).intersection(meeting.recipient) for a in rcptList: rtsvcHeader.append('recipient: ' + a) c = pycurl.Curl() if _debug: print "DEBUG:" + meeting.ics.serialize() # c.setopt(c.VERBOSE, 1) for a in rtsvcHeader: print "DEBUG:" + a output = StringIO.StringIO() c.setopt(c.HTTPHEADER, rtsvcHeader) c.setopt(c.POSTFIELDS, meeting.ics.serialize()) c.setopt(c.URL, rtsvcUrl) c.setopt(c.HEADER, 1) c.setopt(c.POST, 1) c.setopt(pycurl.WRITEFUNCTION, output.write) res = c.perform() if _debug: # print output print """DEBUG request %d """ % c.getinfo(pycurl.HTTP_CODE) #response = output.read() response = output.getvalue() if _debug: print "DEBUG: response: [%s]" % response parseResponse(response) return True def parseRecipientResponse(response): """return True if the scheduling request to the recipient is successful attendee@mysite.edu 2.0;Success """ if response.localName != 'response': return False for walk in response.childNodes: if walk.localName == 'recipient': attendees = walk.getElementsByTagName('href') attendee = attendees[0].childNodes[0].data elif walk.localName == 'request-status': if _debug: print "REPORT: attendee: %s\t\tstatus: %s" % (attendee, walk.childNodes[0].data) v = {'2.0;Success' : True, '1.0;Deferred' : True, 'default' : False } return v.get(walk.childNodes[0].data, 'default') # false by default return False def parseResponse(response): """Parse the xml response of the RTSVC server The response is like: HttpHeaders ... attendee@mysite.edu 2.0;Success """ ret = False # strip http headers try: response = response[response.index('<'):] except ValueError: print "DEBUG: Can't find < in response" return False #support multiple xml documents in response for singleResponse in response.split('\n