initial commit
[fmms] / src / wappushhandler.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """ Class for handling wap push messages and creating MMS messages
4
5 @author: Nick Leppänen Larsson <frals@frals.se>
6 @license: GNU GPL
7 """
8 import sys
9 import os
10 import dbus
11 import urllib2
12 import urllib
13 import httplib
14 import conic
15 import time
16 import socket
17 import array
18
19 from dbus.mainloop.glib import DBusGMainLoop
20
21 from mms import message
22 from mms.message import MMSMessage
23 from mms import mms_pdu
24 import fmms_config as fMMSconf
25 import controller as fMMSController
26
27 magic = 0xacdcacdc
28
29 _DBG = True
30
31 class PushHandler:
32         def __init__(self):
33                 self.cont = fMMSController.fMMS_controller()
34                 # TODO: get all this from controller instead of config
35                 self.config = fMMSconf.fMMS_config()
36                 self._mmsdir = self.config.get_mmsdir()
37                 self._pushdir = self.config.get_pushdir()
38                 self._apn = self.config.get_apn()
39                 self._apn_nicename = self.config.get_apn_nicename()
40                 self._incoming = '/home/user/.fmms/temp/LAST_INCOMING'
41                 
42                 if not os.path.isdir(self._mmsdir):
43                         print "creating dir", self._mmsdir
44                         os.makedirs(self._mmsdir)
45                 if not os.path.isdir(self._pushdir):
46                         print "creating dir", self._pushdir
47                         os.makedirs(self._pushdir)
48
49         """ handle incoming push over sms """
50         def _incoming_sms_push(self, source, src_port, dst_port, wsp_header, wsp_payload):
51                 dbus_loop = DBusGMainLoop()
52                 args = (source, src_port, dst_port, wsp_header, wsp_payload)
53                 
54                 # TODO: dont hardcode
55                 if not os.path.isdir('/home/user/.fmms/temp'):
56                         print "creating dir /home/user/.fmms/temp"
57                         os.makedirs("/home/user/.fmms/temp")
58                 
59                 f = open(self._incoming, 'w')
60                 for arg in args:
61                     f.write(str(arg))
62                     f.write('\n')
63                 f.close()
64
65                 if(_DBG):
66                         print "SRC: ", source, ":", src_port
67                         print "DST: ", dst_port
68                         #print "WSPHEADER: ", wsp_header
69                         #print "WSPPAYLOAD: ", wsp_payload
70
71                 binarydata = []
72                 # throw away the wsp_header!
73                 #for d in wsp_header:
74                 #       data.append(int(d))
75                 
76                 for d in wsp_payload:
77                         binarydata.append(int(d))
78
79                 print "decoding..."
80                 
81                 
82                 (data, sndr, url, trans_id) = self.cont.decode_mms_from_push(binarydata)
83                 
84                 print "saving..."
85                 # Controller should save it
86                 pushid = self.cont.save_push_message(data)
87                 print "notifying push..."
88                 # Send a notify we got the SMS Push and parsed it A_OKEY!
89                 self.notify_mms(dbus_loop, sndr, "SMS Push for MMS received")
90                 print "fetching mms..."
91                 path = self._get_mms_message(url, trans_id)
92                 print "decoding mms... path:", path
93                 message = self.cont.decode_binary_mms(path)
94                 print "storing mms..."
95                 mmsid = self.cont.store_mms_message(pushid, message)
96                 print "notifying mms..."
97                 self.notify_mms(dbus_loop, sndr, "New MMS", trans_id);
98                 return 0
99
100
101         """ handle incoming ip push """
102         # TODO: implement this
103         def _incoming_ip_push(self, src_ip, dst_ip, src_port, dst_port, wsp_header, wsp_payload):
104                 if(_DBG):
105                         print "SRC: " + src_ip + ":" + src_port + "\n"
106                         print "DST: " + dst_ip + ":" + dst_port + "\n"
107                         print "WSPHEADER: " + wsp_header + "\n"
108                         print "WSPPAYLOAD: " + wsp_payload + "\n"
109                         print
110
111
112         """ notifies the user with a org.freedesktop.Notifications.Notify, really fancy """
113         def notify_mms(self, dbus_loop, sender, message, path=None):
114                 bus = dbus.SystemBus()
115                 proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
116                 interface = dbus.Interface(proxy,dbus_interface='org.freedesktop.Notifications')
117                 choices = ['default', 'cancel']
118                 if path == None:
119                         interface.Notify('MMS', 0, '', message, sender, choices, {"category": "sms-message", "dialog-type": 4, "led-pattern": "PatternCommunicationEmail", "dbus-callback-default": "se.frals.fmms /se/frals/fmms se.frals.fmms open_gui"}, -1)
120                 else:
121                         # TODO: callback should open fMMS gui
122                         interface.Notify("MMS", 0, '', message, sender, choices, {"category": "email-message", "dialog-type": 4, "led-pattern": "PatternCommunicationEmail", "dbus-callback-default": "se.frals.fmms /se/frals/fmms se.frals.fmms open_mms string:\"" + path + "\""}, -1)
123
124
125         """ get the mms message from content-location """
126         """ thanks benaranguren on talk.maemo.org for patch including x-wap-profile header """
127         def _get_mms_message(self, location, transaction):
128                 print "getting file: ", location
129                 try:
130                         # TODO: remove hardcoded sleep
131                         con = ConnectToAPN(self._apn_nicename)
132                         #time.sleep(6)
133                         con.connect()
134                         
135                         try:
136                                 notifyresp = self._send_notify_resp(transaction)
137                                 print "notifyresp sent"
138                         except:
139                                 print "notify sending failed..."
140                         
141                         # TODO: configurable time-out?
142                         timeout = 60
143                         socket.setdefaulttimeout(timeout)
144                         (proxyurl, proxyport) = self.config.get_proxy_from_apn()
145                         
146                         if proxyurl == "" or proxyurl == None:
147                                 print "connecting without proxy"
148                         else:
149                                 proxyfull = str(proxyurl) + ":" + str(proxyport)
150                                 print "connecting with proxy", proxyfull        
151                                 proxy = urllib2.ProxyHandler({"http": proxyfull})
152                                 opener = urllib2.build_opener(proxy)
153                                 urllib2.install_opener(opener)
154                                 
155                         #headers = {'x-wap-profile': 'http://mms.frals.se/n900.rdf'}
156                         #User-Agent: NokiaN95/11.0.026; Series60/3.1 Profile/MIDP-2.0 Configuration/CLDC-1.1 
157                         headers = {'User-Agent' : 'NokiaN95/11.0.026; Series60/3.1 Profile/MIDP-2.0 Configuration/CLDC-1.1', 'x-wap-profile' : 'http://mms.frals.se/n900.rdf'}
158                         req = urllib2.Request(location, headers=headers)
159                         mmsdata = urllib2.urlopen(req)
160                         try:
161                                 print mmsdata.info()
162                         except:
163                                 pass
164                         
165                         mmsdataall = mmsdata.read()
166                         dirname = self.cont.save_binary_mms(mmsdataall, transaction)
167                         
168                         if(_DBG):
169                                 print "fetched ", location, " and wrote to file"
170                                 
171                         # send acknowledge we got it ok
172                         try:
173                                 ack = self._send_acknowledge(transaction)
174                                 print "ack sent"
175                         except:
176                                 print "sending ack failed"
177                         
178                         con.disconnect()                        
179                         
180                 except Exception, e:
181                         print e, e.args
182                         bus = dbus.SystemBus()
183                         proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
184                         interface = dbus.Interface(proxy,dbus_interface='org.freedesktop.Notifications')
185                         interface.SystemNoteInfoprint ("fMMS: Failed to download MMS message.")
186                         raise
187                         
188                 return dirname
189
190
191         def _send_notify_resp(self, transid):
192                 mms = MMSMessage(True)
193                 mms.headers['Message-Type'] = "m-notifyresp-ind"
194                 mms.headers['Transaction-Id'] = transid
195                 mms.headers['MMS-Version'] = "1.3"
196                 mms.headers['Status'] = "Deferred"
197                 
198                 print "setting up notify sender"
199                 sender = MMSSender(customMMS=True)
200                 print "sending notify..."
201                 out = sender.sendMMS(mms)
202                 print "m-notifyresp-ind:", out
203                 return out
204         
205         
206         def _send_acknowledge(self, transid):
207                 mms = MMSMessage(True)
208                 mms.headers['Message-Type'] = "m-acknowledge-ind"
209                 mms.headers['Transaction-Id'] = transid
210                 mms.headers['MMS-Version'] = "1.3"
211                 
212                 print "setting up ack sender"
213                 ack = MMSSender(customMMS=True)
214                 print "sending ack..."
215                 out = ack.sendMMS(mms)
216                 print "m-acknowledge-ind:", out
217                 return out
218
219        
220 class ConnectToAPN:
221         def __init__(self, apn):
222             self._apn = apn
223             self.connection = conic.Connection()
224             
225         def connection_cb(self, connection, event, magic):
226             print "connection_cb(%s, %s, %x)" % (connection, event, magic)
227
228         
229         def disconnect(self):
230                 connection = self.connection
231                 connection.disconnect_by_id(self._apn)
232         
233         def connect(self):
234                 global magic
235
236                 # Creates the connection object and attach the handler.
237                 connection = self.connection
238                 iaps = connection.get_all_iaps()
239                 iap = None
240                 for i in iaps:
241                         if i.get_name() == self._apn:
242                                 iap = i
243                 
244                 connection.disconnect()
245                 connection.connect("connection-event", self.connection_cb, magic)
246
247                 # The request_connection method should be called to initialize
248                 # some fields of the instance
249                 if not iap:
250                         assert(connection.request_connection(conic.CONNECT_FLAG_NONE))
251                 else:
252                 #print "Getting by iap", iap.get_id()
253                         assert(connection.request_connection_by_id(iap.get_id(), conic.CONNECT_FLAG_NONE))
254                         return False
255             
256 """ class for sending an mms """            
257 class MMSSender:
258         def __init__(self, number=None, subject=None, message=None, attachment=None, sender=None, customMMS=None):
259                 print "GOT SENDER:", sender
260                 print "customMMS:", customMMS
261                 self.customMMS = customMMS
262                 self.config = fMMSconf.fMMS_config()
263                 if customMMS == None:
264                         self.number = number
265                         self.subject = subject
266                         self.message = message
267                         self.attachment = attachment
268                         self._mms = None
269                         self._sender = sender
270                         self.createMMS()
271             
272         def createMMS(self):
273                 slide = message.MMSMessagePage()
274                 if self.attachment != None:
275                         slide.addImage(self.attachment)
276                 slide.addText(self.message)
277
278                 self._mms = message.MMSMessage()
279                 self._mms.headers['Subject'] = self.subject
280                 self._mms.headers['To'] = str(self.number) + '/TYPE=PLMN'
281                 self._mms.headers['From'] = str(self._sender) + '/TYPE=PLMN'
282                 self._mms.addPage(slide)
283         
284         def sendMMS(self, customData=None):
285                 mmsid = None
286                 if customData != None:
287                         print "using custom mms"
288                         self._mms = customData
289         
290                 mmsc = self.config.get_mmsc()
291                 
292                 (proxyurl, proxyport) = self.config.get_proxy_from_apn()
293                 mms = self._mms.encode()
294                 
295                 headers = {'Content-Type':'application/vnd.wap.mms-message', 'User-Agent' : 'NokiaN95/11.0.026; Series60/3.1 Profile/MIDP-2.0 Configuration/CLDC-1.1', 'x-wap-profile' : 'http://mms.frals.se/n900.rdf'}
296                 #headers = {'Content-Type':'application/vnd.wap.mms-message'}
297                 if proxyurl == "" or proxyurl == None:
298                         print "connecting without proxy"
299                         mmsc = mmsc.lower()
300                         mmsc = mmsc.replace("http://", "")
301                         mmsc = mmsc.rstrip('/')
302                         mmsc = mmsc.partition('/')
303                         mmschost = mmsc[0]
304                         path = "/" + str(mmsc[2])
305                         print "mmschost:", mmschost, "path:", path, "pathlen:", len(path)
306                         conn = httplib.HTTPConnection(mmschost)
307                         conn.request('POST', path , mms, headers)
308                 else:
309                         print "connecting via proxy " + proxyurl + ":" + str(proxyport)
310                         print "mmschost:", mmsc
311                         conn = httplib.HTTPConnection(proxyurl + ":" + str(proxyport))
312                         conn.request('POST', mmsc, mms, headers)
313
314                 if customData == None:                  
315                         cont = fMMSController.fMMS_controller()
316                         path = cont.save_binary_outgoing_mms(mms, self._mms.transactionID)
317                         message = cont.decode_binary_mms(path)
318                         mmsid = cont.store_outgoing_mms(message)        
319                         
320                 res = conn.getresponse()
321                 print "MMSC STATUS:", res.status, res.reason
322                 out = res.read()
323                 try:
324                         decoder = mms_pdu.MMSDecoder()
325                         data = array.array('B')
326                         for b in out:
327                                 data.append(ord(b))
328                         outparsed = decoder.decodeResponseHeader(data)
329                         
330                         if mmsid != None:
331                                 pushid = cont.store_outgoing_push(outparsed)
332                                 cont.link_push_mms(pushid, mmsid)
333                                 
334                 except Exception, e:
335                         print type(e), e
336                         outparsed = out
337                         
338                 print "MMSC RESPONDED:", outparsed
339                 return res.status, res.reason, outparsed