1 #!/usr/bin/env python 2 3 from email import message_from_file 4 from vCalendar import parse, iterwrite 5 import imip_store 6 import sys 7 8 try: 9 from cStringIO import StringIO 10 except ImportError: 11 from StringIO import StringIO 12 13 # Postfix exit codes. 14 15 EX_USAGE = 64 16 EX_DATAERR = 65 17 EX_NOINPUT = 66 18 EX_NOUSER = 67 19 EX_NOHOST = 68 20 EX_UNAVAILABLE = 69 21 EX_SOFTWARE = 70 22 EX_OSERR = 71 23 EX_OSFILE = 72 24 EX_CANTCREAT = 73 25 EX_IOERR = 74 26 EX_TEMPFAIL = 75 27 EX_PROTOCOL = 76 28 EX_NOPERM = 77 29 EX_CONFIG = 78 30 31 # Permitted iTIP content types. 32 33 itip_content_types = [ 34 "text/calendar", # from RFC 6047 35 "text/x-vcalendar", "application/ics", # other possibilities 36 ] 37 38 def process(f, args): 39 msg = message_from_file(f) 40 print >>open("/tmp/imip.txt", "a"), "----" 41 print >>open("/tmp/imip.txt", "a"), args 42 print >>open("/tmp/imip.txt", "a"), "----" 43 print >>open("/tmp/imip.txt", "a"), msg.as_string() 44 print >>open("/tmp/imip.txt", "a") 45 46 # Handle messages with iTIP parts. 47 48 for part in msg.walk(): 49 if part.get_content_type() in itip_content_types and \ 50 part.get_param("method"): 51 52 handle_itip_part(part) 53 54 def get_itip_elements(elements): 55 d = {} 56 for name, attr, value in elements: 57 if isinstance(value, list): 58 d[name] = attr, get_itip_elements(value) 59 else: 60 if not d.has_key(name): 61 d[name] = [] 62 d[name].append((attr, value)) 63 return d 64 65 def get_attr_value(d, name, single=True): 66 if d.has_key(name): 67 values = d[name] 68 if isinstance(values, tuple): 69 return values 70 elif single and len(values) == 1: 71 return values[0] 72 else: 73 return values 74 else: 75 return None 76 77 def get_value(d, name, single=True): 78 if d.has_key(name): 79 values = d[name] 80 if isinstance(values, tuple): 81 return values[1] 82 elif single and len(values) == 1: 83 return values[0][1] 84 else: 85 return map(lambda x: x[1], values) 86 else: 87 return None 88 89 def handle_itip_part(part): 90 method = part.get_param("method") 91 92 f = StringIO(part.get_payload(decode=True)) 93 doctype, attrs, elements = parse(f, encoding=part.get_content_charset()) 94 95 if doctype == "VCALENDAR": 96 itip = get_itip_elements(elements) 97 98 if get_value(itip, "METHOD") == method: 99 100 for name, cls in handlers: 101 details = get_value(itip, name) 102 103 if details: 104 print >>open("/tmp/imip.txt", "a"), details 105 handler = cls(details) 106 print >>open("/tmp/imip.txt", "a"), "Handling", method, "with", handler, "->", methods[method](handler) 107 methods[method](handler)() 108 109 class Handler: 110 def __init__(self, details): 111 self.details = details 112 self.uid = get_value(details, "UID") 113 self.sequence = get_value(details, "SEQUENCE") 114 self.store = imip_store.FileStore() 115 116 def get_attr_value(self, name, single=True): 117 return get_attr_value(self.details, name, single) 118 119 def get_value(self, name, single=True): 120 return get_value(self.details, name, single) 121 122 class Event(Handler): 123 124 "An event handler." 125 126 def add(self): 127 pass 128 129 def cancel(self): 130 pass 131 132 def counter(self): 133 pass 134 135 def declinecounter(self): 136 pass 137 138 def publish(self): 139 pass 140 141 def refresh(self): 142 pass 143 144 def reply(self): 145 146 "Since this handler does not send requests, it will not handle replies." 147 148 pass 149 150 def request(self): 151 pass 152 153 class Freebusy(Handler): 154 155 "A free/busy handler." 156 157 def publish(self): 158 pass 159 160 def reply(self): 161 162 "Since this handler does not send requests, it will not handle replies." 163 164 pass 165 166 def request(self): 167 168 """ 169 Respond to a request by sending a reply containing free/busy information 170 for each indicated attendee. 171 """ 172 173 attendees = self.get_attr_value("ATTENDEE", False) 174 organiser = self.get_attr_value("ORGANIZER") 175 176 if not attendees and not organiser: 177 return 178 179 organiser_attr, organiser = organiser 180 181 # Get the details for the attendee. 182 183 out = open("/tmp/imip.txt", "a") 184 try: 185 w = iterwrite(out) 186 187 calendar = [] 188 cwrite = calendar.append 189 cwrite(("METHOD", {}, "REPLY")) 190 cwrite(("VERSION", {}, "2.0")) 191 192 for attendee_attr, attendee in attendees: 193 freebusy = self.store.get_freebusy(attendee) 194 if freebusy: 195 record = [] 196 rwrite = record.append 197 rwrite(("ORGANIZER", organiser_attr, organiser)) 198 rwrite(("ATTENDEE", attendee_attr, attendee)) 199 rwrite(("UID", {}, self.uid)) 200 201 for start, end in freebusy: 202 rwrite(("FREEBUSY", {}, [start, end])) 203 204 cwrite(("VFREEBUSY", {}, record)) 205 206 # Send a reply with the information. 207 208 w.write("VCALENDAR", {}, calendar) 209 210 finally: 211 out.close() 212 213 class Journal(Handler): 214 215 "A journal entry handler." 216 217 def add(self): 218 pass 219 220 def cancel(self): 221 pass 222 223 def publish(self): 224 pass 225 226 class Todo(Handler): 227 228 "A to-do item handler." 229 230 def add(self): 231 pass 232 233 def cancel(self): 234 pass 235 236 def counter(self): 237 pass 238 239 def declinecounter(self): 240 pass 241 242 def publish(self): 243 pass 244 245 def refresh(self): 246 pass 247 248 def reply(self): 249 250 "Since this handler does not send requests, it will not handle replies." 251 252 pass 253 254 def request(self): 255 pass 256 257 # Handler registry. 258 259 handlers = [ 260 ("VFREEBUSY", Freebusy), 261 ("VEVENT", Event), 262 ("VTODO", Todo), 263 ("VJOURNAL", Journal), 264 ] 265 266 methods = { 267 "ADD" : lambda handler: handler.add, 268 "CANCEL" : lambda handler: handler.cancel, 269 "COUNTER" : lambda handler: handler.counter, 270 "DECLINECOUNTER" : lambda handler: handler.declinecounter, 271 "PUBLISH" : lambda handler: handler.publish, 272 "REFRESH" : lambda handler: handler.refresh, 273 "REPLY" : lambda handler: handler.reply, 274 "REQUEST" : lambda handler: handler.request, 275 } 276 277 if __name__ == "__main__": 278 process(sys.stdin, sys.argv[1:]) 279 280 # vim: tabstop=4 expandtab shiftwidth=4