1
#!/usr/bin/env python2.5
2
# -*- coding: utf-8 -*-
3
""" Class for handling wap push messages and creating MMS messages
4
5
fMMS - MMS for fremantle
6
Copyright (C) 2010 Nick Leppänen Larsson <frals@frals.se>
7
8
@license: GNU GPLv2, see COPYING file.
9
"""
10
import os
11
import sys
12
import urllib2
13
import httplib
14
import time
15
import socket
16
import array
17
import subprocess
18
import gettext
19
20
import dbus
21
import pynotify
22
from gnome import gnomevfs
23
24
from mms import message
25
from mms.message import MMSMessage
26
from mms import mms_pdu
27
import controller as fMMSController
28
import contacts as ContactH
29
import connectors
30
31
import logging
32
log = logging.getLogger('fmms.%s' % __name__)
33
34
class PushHandler:
35
	def __init__(self):
36
		self.cont = fMMSController.fMMS_controller()
37
		self.config = self.cont.config
38
		self._mmsdir = self.config.get_mmsdir()
39
		self._pushdir = self.config.get_pushdir()
40
		self._apn = self.config.get_apn()
41
		self._apn_nicename = self.config.get_apn_nicename()
42
		self._incoming = self.config.get_imgdir() + "/LAST_INCOMING"
43
44
	def _incoming_sms_push(self, source, src_port, dst_port, wsp_header, wsp_payload):
45
		""" handle incoming push over sms """
46
		log.info("Incoming SMS Push!")
47
		args = (source, src_port, dst_port, wsp_header, wsp_payload)
48
		
49
		if not os.path.isdir(self.config.get_imgdir()):
50
			os.makedirs(self.config.get_imgdir())
51
		
52
		f = open(self._incoming, 'w')
53
		for arg in args:
54
		    f.write(str(arg))
55
		    f.write('\n')
56
		f.close()
57
58
		log.info("SRC: %s:%s", source, src_port)
59
		log.info("DST: %s", dst_port)
60
61
		binarydata = []
62
		# throw away the wsp_header!
63
		#for d in wsp_header:
64
		#	data.append(int(d))
65
		
66
		for d in wsp_payload:
67
			binarydata.append(int(d))
68
69
		log.info("decoding...")
70
		
71
		(data, sndr, url, trans_id) = self.cont.decode_mms_from_push(binarydata)
72
		
73
		ch = ContactH.ContactHandler()
74
		sndr = ch.get_displayname_from_number(sndr)
75
		
76
		log.info("saving...")
77
		# Controller should save it
78
		pushid = self.cont.save_push_message(data)
79
		try:
80
			log.info("fetching mms...")
81
			path = self._get_mms_message(url, trans_id)
82
		except:
83
			log.info("failed to fetch - notifying push...")
84
			# Send a notify we got the SMS Push and parsed it A_OKEY!
85
			msgstr = "%s (%s)" % (gettext.ldgettext('rtcom-messaging-ui', "messaging_ti_new_mms"), "Push")
86
			self.notify_mms(sndr, msgstr)
87
			log.info("notified...")
88
			raise
89
		log.info("decoding mms... path: %s", path)
90
		message = self.cont.decode_binary_mms(path)
91
		log.info("storing mms...")
92
		self.cont.store_mms_message(pushid, message, transactionId=trans_id)
93
		log.info("notifying mms...")
94
		self.notify_mms(sndr, gettext.ldgettext('rtcom-messaging-ui', "messaging_ti_new_mms"), trans_id)
95
		log.info("done, returning!")
96
		return 0
97
98
	# TODO: implement this
99
	def _incoming_ip_push(self, src_ip, dst_ip, src_port, dst_port, wsp_header, wsp_payload):
100
		""" handle incoming ip push """
101
		log.info("SRC: %s:%s", src_ip, src_port)
102
		log.info("DST: %s:%s", dst_ip, dst_port)
103
104
	def notify_mms(self, sender, msg, path=None):
105
		""" notifies the user with a org.freedesktop.Notifications.Notify, really fancy """
106
		pynotify.init("fMMS")
107
		note = pynotify.Notification(sender, msg, "fmms")
108
		note.set_urgency(pynotify.URGENCY_CRITICAL)
109
		note.set_hint("led-pattern", "PatternCommunicationEmail")
110
		if path:
111
			note.set_hint("dbus-callback-default", "se.frals.fmms /se/frals/fmms se.frals.fmms open_mms string:\"" + path + "\"")
112
		else:
113
			note.set_hint("dbus-callback-default", "se.frals.fmms /se/frals/fmms se.frals.fmms open_gui")
114
		# we have to fake being an email for vibra/sound... oh well!
115
		bus = dbus.SessionBus()
116
		proxy = bus.get_object('com.nokia.HildonSVNotificationDaemon', '/com/nokia/HildonSVNotificationDaemon')
117
		interface = dbus.Interface(proxy,dbus_interface='com.nokia.HildonSVNotificationDaemon')
118
		interface.PlayEvent({'time': 0, 'category': 'email-message'}, "fmms")
119
		note.show()
120
121
	def _get_mms_message(self, location, transaction, controller=0):
122
		# this method should be a critical section
123
		if controller != 0:
124
			self.cont = controller
125
		connector = connectors.MasterConnector(self.cont)
126
		connector.connect(location)
127
		
128
		try:
129
			dirname = self.__get_mms_message(location, transaction)
130
		except:
131
			log.exception("Something went wrong with getting the message... bailing out")
132
			connector.disconnect()
133
			raise
134
		
135
		# send acknowledge we got it ok
136
		try:
137
			socket.setdefaulttimeout(20)
138
			self._send_acknowledge(transaction)
139
			log.info("ack sent")
140
		except:
141
			log.exception("sending ack failed")
142
		
143
		connector.disconnect()
144
		
145
		return dirname
146
		
147
	def __get_mms_message(self, location, transaction):
148
		""" get the mms message from content-location """
149
		# thanks benaranguren on talk.maemo.org for patch including x-wap-profile header
150
		log.info("getting file: %s", location)
151
		try:
152
			(proxyurl, proxyport) = self.config.get_proxy_from_apn()
153
			
154
			try:
155
				socket.setdefaulttimeout(20)
156
				notifyresp = self._send_notify_resp(transaction)
157
				log.info("notifyresp sent")
158
			except:
159
				log.exception("notify sending failed")
160
			
161
			# TODO: configurable time-out?
162
			timeout = 30
163
			socket.setdefaulttimeout(timeout)
164
			
165
			if proxyurl == "" or proxyurl == None:
166
				log.info("connecting without proxy")
167
			else:
168
				proxyfull = "%s:%s" % (str(proxyurl), str(proxyport))
169
				log.info("connecting with proxy %s", proxyfull)
170
				proxy = urllib2.ProxyHandler({"http": proxyfull})
171
				opener = urllib2.build_opener(proxy)
172
				urllib2.install_opener(opener)
173
174
			headers = {'User-Agent' : self.config.get_useragent(), 'x-wap-profile' : 'http://mms.frals.se/n900.rdf'}
175
			log.info("trying url: %s", location)
176
			req = urllib2.Request(location, headers=headers)
177
			mmsdata = urllib2.urlopen(req)
178
			try:
179
				log.info("mmsc info: %s", mmsdata.info())
180
			except:
181
				pass
182
			
183
			mmsdataall = mmsdata.read()
184
			dirname = self.cont.save_binary_mms(mmsdataall, transaction)
185
			
186
			log.info("fetched %s and wrote to file", location)
187
			
188
189
		except Exception, e:
190
			log.exception("fatal: %s %s", type(e), e)
191
			bus = dbus.SystemBus()
192
			proxy = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
193
			interface = dbus.Interface(proxy, dbus_interface='org.freedesktop.Notifications')
194
			interface.SystemNoteInfoprint(gettext.ldgettext('modest', "mail_ni_ui_folder_get_msg_folder_error"))
195
			raise
196
		
197
		return dirname
198
199
	def _send_notify_resp(self, transid):
200
		mms = MMSMessage(True)
201
		mms.headers['Message-Type'] = "m-notifyresp-ind"
202
		mms.headers['Transaction-Id'] = transid
203
		mms.headers['MMS-Version'] = "1.3"
204
		mms.headers['Status'] = "Deferred"
205
		
206
		sender = MMSSender(customMMS=True)
207
		log.info("sending notify...")
208
		out = sender.sendMMS(mms)
209
		log.info("m-notifyresp-ind: %s", out)
210
		return out
211
		
212
	def _send_acknowledge(self, transid):
213
		mms = MMSMessage(True)
214
		mms.headers['Message-Type'] = "m-acknowledge-ind"
215
		mms.headers['Transaction-Id'] = transid
216
		mms.headers['MMS-Version'] = "1.3"
217
		
218
		ack = MMSSender(customMMS=True)
219
		log.info("sending ack...")
220
		out = ack.sendMMS(mms)
221
		log.info("m-acknowledge-ind: %s", out)
222
		return out
223
224
    	    
225
class MMSSender:
226
	""" class for sending an mms """
227
	
228
	def __init__(self, number=None, subject=None, msg=None, attachment=None, sender=None, customMMS=None, setupConn=False, controller=0):
229
		self.customMMS = customMMS
230
		if controller != 0:
231
			self.cont = controller
232
		else:
233
			self.cont = fMMSController.fMMS_controller()
234
		self.config = self.cont.config
235
		self.setupConn = setupConn
236
		if customMMS == None:
237
			self.number = number
238
			if msg == None:
239
				msg = ""
240
			self.message = msg
241
			if subject == '' or subject == None:
242
				subject = self.message[:15].replace('\n', '')
243
				if len(self.message) > 15:
244
					subject += "..."
245
				if len(subject) == 0:
246
					subject = "MMS"
247
			self.subject = subject
248
			self.attachment = attachment
249
			self._mms = None
250
			self._sender = sender
251
			self.createMMS()
252
			if self.setupConn == True:
253
				self.connector = connectors.MasterConnector(self.cont)
254
				try:
255
					self.connector.connect()
256
				except:
257
					raise
258
259
	def createMMS(self):
260
		slide = message.MMSMessagePage()
261
		if self.attachment != None:
262
			try:
263
				filetype = gnomevfs.get_mime_type(self.attachment)
264
			except:
265
				filetype = "unknown"
266
			if filetype.startswith("audio"):
267
				slide.addAudio(self.attachment)
268
			elif filetype.startswith("video"):
269
				slide.addVideo(self.attachment)
270
			else:
271
				slide.addImage(self.attachment)
272
		slide.addText(self.message)
273
274
		self._mms = message.MMSMessage()
275
		self._mms.headers['Subject'] = self.subject
276
		if "@" in self.number:
277
			self._mms.headers['To'] = str(self.number)
278
		elif ";" in self.number:
279
			fullnr = "";
280
			nr = self.number.split(";")
281
			for val in nr:
282
				fullnr = "%s%s" % (fullnr, (val.strip() + '/TYPE=PLMN;'))
283
			self._mms.headers['To'] = str(fullnr).rstrip(";")
284
		else:
285
			self._mms.headers['To'] = str(self.number) + '/TYPE=PLMN'
286
		if self._sender == '0':
287
			self._mms.headers['From'] = ''
288
		else:
289
			self._mms.headers['From'] = str(self._sender) + '/TYPE=PLMN'
290
		
291
		self._mms.addPage(slide)
292
	
293
	def sendMMS(self, customData=None):
294
		try:
295
			(status, reason, outparsed, parsed) = self._sendMMS(customData)
296
		except:
297
			log.exception("Failed to send message.")
298
			raise
299
		finally:
300
			if self.setupConn == True:
301
				try:
302
					self.connector.disconnect()
303
				except:
304
					log.exception("Failed to close connection.")
305
		
306
		return status, reason, outparsed, parsed
307
	
308
	def _sendMMS(self, customData=None):
309
		mmsid = None
310
		if customData != None:
311
			log.info("using custom mms")
312
			self._mms = customData
313
	
314
		mmsc = self.config.get_mmsc()
315
		
316
		(proxyurl, proxyport) = self.config.get_proxy_from_apn()
317
		mms = self._mms.encode()
318
		
319
		socket.setdefaulttimeout(120)
320
		
321
		headers = {'Content-Type':'application/vnd.wap.mms-message', 'User-Agent' : self.config.get_useragent(), 'x-wap-profile' : 'http://mms.frals.se/n900.rdf'}
322
		if proxyurl == "" or proxyurl == None:
323
			log.info("connecting without proxy")
324
			mmsc = mmsc.lower()
325
			mmsc = mmsc.replace("http://", "")
326
			mmsc = mmsc.rstrip('/')
327
			mmsc = mmsc.partition('/')
328
			mmschost = mmsc[0]
329
			path = "/" + str(mmsc[2])
330
			log.info("mmschost: %s path: %s pathlen: %s", mmschost, path, len(path))
331
			conn = httplib.HTTPConnection(mmschost)
332
			conn.request('POST', path , mms, headers)
333
		else:
334
			log.info("connecting via proxy %s:%s", proxyurl, str(proxyport))
335
			log.info("mmschost: %s", mmsc)
336
			conn = httplib.HTTPConnection(proxyurl + ":" + str(proxyport))
337
			conn.request('POST', mmsc, mms, headers)
338
339
		if customData == None:			
340
			cont = self.cont
341
			path = cont.save_binary_outgoing_mms(mms, self._mms.transactionID)
342
			message = cont.decode_binary_mms(path)
343
			mmsid = cont.store_outgoing_mms(message)	
344
			
345
		res = conn.getresponse()
346
		log.info("MMSC STATUS: %s %s", res.status, res.reason)
347
		out = res.read()
348
		parsed = False
349
		try:
350
			decoder = mms_pdu.MMSDecoder()
351
			data = array.array('B')
352
			for b in out:
353
				data.append(ord(b))
354
			outparsed = decoder.decodeResponseHeader(data)
355
			parsed = True
356
			
357
			if mmsid and "Response-Status" in outparsed:
358
				if outparsed['Response-Status'] == "Ok":
359
					pushid = cont.store_outgoing_push(outparsed)
360
					cont.link_push_mms(pushid, mmsid)
361
		except Exception, e:
362
			#print type(e), e
363
			outparsed = out
364
		log.info("MMSC RESPONDED: %s", outparsed)
365
		
366
		return res.status, res.reason, outparsed, parsed
367
368
369
if __name__ == '__main__':
370
	source = sys.argv[1]
371
	srcport = sys.argv[2]
372
	dstport = sys.argv[3]
373
	header = sys.argv[4]
374
	payload = sys.argv[5]
375
	push = PushHandler()
376
	print source, srcport, dstport, header, payload
377
	try:
378
		push._incoming_sms_push(source, srcport, dstport, eval(header), eval(payload))
379
	except:
380
		pass