Attachment 'PyFetion.py'
Download 1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #Using GPL v2
4 #Author: [email protected]
5
6 import urllib2
7 import urllib
8 import cookielib
9 import sys,re
10 import binascii
11 import hashlib
12 import socket
13
14 from hashlib import md5
15 from hashlib import sha1
16 from uuid import uuid1
17
18
19 FetionVer = "2008"
20 #"SIPP" USED IN HTTP CONNECTION
21 FetionSIPP= "SIPP"
22 FetionNavURL = "nav.fetion.com.cn"
23 FetionConfigURL = "http://nav.fetion.com.cn/nav/getsystemconfig.aspx"
24
25 FetionConfigXML = """<config><user mobile-no="%s" /><client type="PC" version="3.2.0540" platform="W5.1" /><servers version="0" /><service-no version="0" /><parameters version="0" /><hints version="0" /><http-applications version="0" /><client-config version="0" /></config>"""
26
27 FetionLoginXML = """<args><device type="PC" version="0" client-version="3.2.0540" /><caps value="simple-im;im-session;temp-group;personal-group" /><events value="contact;permission;system-message;personal-group" /><user-info attributes="all" /><presence><basic value="400" desc="" /></presence></args>"""
28
29 debug = True
30
31 class PyFetionException(Exception):
32 """Base class for all exceptions raised by this module."""
33
34 class PyFetionInfoError(PyFetionException):
35 """Phone number or password incomplete"""
36
37 class PyFetionResponseException(PyFetionException):
38 """Base class for all exceptions that include SIPC/HTTP error code.
39 """
40 def __init__(self, code, msg):
41 self.PyFetion_code = code
42 self.PyFetion_error = msg
43 self.args = (code, msg)
44
45 class PyFetionAuthError(PyFetionResponseException):
46 """Authentication error.
47 Your password error, or your mobile NO. don't support fetion
48 """
49 class PyFetionRegisterError(PyFetionResponseException):
50 """RegisterError.
51 """
52 class PyFetionSendError(PyFetionResponseException):
53 """Send SMS error
54 """
55
56 class PyFetion():
57
58 __config_data = ""
59 __sipc_url = ""
60 __sipc_proxy = ""
61 __sid = ""
62
63 mobile_no = ""
64 passwd = ""
65 login_type = ""
66
67 def __init__(self,mobile_no,passwd,login_type="HTTP"):
68 if not passwd or len(mobile_no) != 11:
69 raise PyFetionInfoError(mobile_no,passwd)
70
71 self.mobile_no = mobile_no
72 self.passwd = passwd
73 self.login_type = login_type
74
75 self.__get_system_config()
76 self.__set_system_config()
77
78 def login(self):
79 (self.__ssic,self.__domain) = self.__get_uri()
80 try:
81 self.__register(self.__ssic,self.__domain)
82 except PyFetionRegisterError,e:
83 print "Register Failed!"
84 #这里使用一个status变量作为类的成员,每一种失败后都改变一下这个
85 pass
86 def get_offline_msg(self):
87 self.__SIPC.get("")
88
89 def add(self,who):
90 self.__SIPC.get("INFO","AddBuddy",who)
91 response = self.__SIPC.send()
92 code = self.__SIPC.get_code(response)
93 if code == 521:
94 d_print("Aleady added.")
95 elif code == 522:
96 d_print("Mobile NO. Don't Have Fetion")
97 self.__SIPC.get("INFO","AddMobileBuddy",who)
98 response = self.__SIPC.send()
99
100
101 def get_personal_info(self):
102 self.__SIPC.get("INFO","GetPersonalInfo")
103 self.__SIPC.send()
104
105 def get_info(self,who):
106 self.__SIPC.get("INFO","GetContactsInfo",who)
107 response = self.__SIPC.send()
108 return response
109
110
111 def get_contact_list(self):
112 self.__SIPC.get("INFO","GetContactList")
113 response = self.__SIPC.send()
114 return response
115
116 def get_uri(self,who):
117 if who == self.mobile_no:
118 who = self.__uri
119 if not who.startswith("sip"):
120 l = self.get_contact_list()
121 all = re.findall('uri="(.+?)" ',l)
122 #Get uri from contact list, compare one by one
123 #I can't get other more effect way
124 for uri in all:
125 ret = self.get_info(uri)
126 no = re.findall('mobile-no="(.+?)" ',ret)
127 if no:
128 if no[0] == who:
129 d_print(('who',),locals())
130 who = uri
131 break
132 return who
133
134 def send_msg(self,to,msg,flag="SENDMSG"):
135 self.__SIPC.get(flag,to,msg)
136 response = self.__SIPC.send()
137 code = self.__SIPC.get_code(response)
138 if code == 280:
139 d_print("Send sms/msg OK!")
140 else:
141 d_print(('code',),locals())
142
143 def send_sms(self,msg,to=None,long=False):
144 if not to:
145 to = self.__uri
146 else:
147 to = self.get_uri(to)
148 if long:
149 self.send_msg(to,msg,"SENDCatSMS")
150 else:
151 self.send_msg(to,msg,"SENDSMS")
152
153 def send_schedule_sms(self,msg,time,to=None):
154 if not to:
155 to = self.__uri
156 else:
157 to = self.get_uri(to)
158
159 self.__SIPC.get("SSSetScheduleSms",msg,time,to)
160 response = self.__SIPC.send()
161 code = self.__SIPC.get_code(response)
162 if code == 486:
163 d_print("Busy Here")
164 return None
165 if code == 200:
166 id = re.search('id="(\d+)"',response).group(1)
167 d_print(('id',),locals(),"schedule_sms id")
168 return id
169
170 def __register(self,ssic,domain):
171 self.__SIPC = SIPC(self.__sid,self.__domain,self.passwd,self.login_type,self.__http_tunnel,self.__ssic,self.__sipc_proxy)
172 response = ""
173 for step in range(1,3):
174 self.__SIPC.get("REG",step,response)
175 response = self.__SIPC.send()
176
177 code = self.__SIPC.get_code(response)
178 if code == 200:
179 d_print("register successful.")
180 else:
181 raise PyFetionRegisterError(code,response)
182
183 def __http_send(self,url,body="",exheaders="",login=False):
184 headers = {
185 'User-Agent':'IIC2.0/PC 3.2.0540',
186 }
187 headers.update(exheaders)
188 request = urllib2.Request(url,headers=headers,data=body)
189 try:
190 conn = urllib2.urlopen(request)
191 except urllib2.URLError, e:
192 code = e.code
193 msg = e.read()
194 if code == 401 or code == 404:
195 if login:
196 d_print(('code','text'),locals())
197 raise PyFetionAuthError(code,msg)
198 return -1
199
200 return conn
201
202
203 def __get_system_config(self):
204 global FetionConfigURL
205 global FetionConfigXML
206 url = FetionConfigURL
207 body = FetionConfigXML % self.mobile_no
208 d_print(('url','body'),locals())
209 self.__config_data = self.__http_send(url,body).read()
210
211
212 def __set_system_config(self):
213 sipc_url = re.search("<ssi-app-sign-in>(.*)</ssi-app-sign-in>",self.__config_data).group(1)
214 sipc_proxy = re.search("<sipc-proxy>(.*)</sipc-proxy>",self.__config_data).group(1)
215 http_tunnel = re.search("<http-tunnel>(.*)</http-tunnel>",self.__config_data).group(1)
216 d_print(('sipc_url','sipc_proxy','http_tunnel'),locals())
217 self.__sipc_url = sipc_url
218 self.__sipc_proxy = sipc_proxy
219 self.__http_tunnel= http_tunnel
220
221 def __get_uri(self):
222 url = self.__sipc_url+"?mobileno="+self.mobile_no+"&pwd="+self.passwd
223 d_print(('url',),locals())
224 try:
225 ret = self.__http_send(url,login=True)
226 except PyFetionAuthError,e:
227 d_print(('e',),locals())
228 print "Your password error, or your mobile NO. don't support fetion"
229 sys.exit(-1)
230
231 header = str(ret.info())
232 body = ret.read()
233 ssic = re.search("ssic=(.*);",header).group(1)
234 sid = re.search("sip:(.*)@",body).group(1)
235 uri = re.search('uri="(.*)" mobile-no',body).group(1)
236 status = re.search('user-status="(\d+)"',body).group(1)
237 domain = "fetion.com.cn"
238
239 d_print(('ssic','sid','uri','status','domain'),locals(),"Get SID OK")
240 self.__sid = sid
241 self.__uri = uri
242 return (ssic,domain)
243
244 class SIPC():
245
246 global FetionVer
247 global FetionSIPP
248 global FetionLoginXML
249
250 header = ""
251 body = ""
252 content = ""
253 code = ''
254 ver = "SIP-C/2.0"
255 ID = 1
256 sid = ""
257 domain = ""
258 passwd = ""
259 __http_tunnel = ""
260
261 def __init__(self,sid,domain,passwd,login_type,http_tunnel,ssic,sipc_proxy):
262 self.sid = sid
263 self.domain = domain
264 self.passwd = passwd
265 self.login_type = login_type
266 self.domain = domain
267 self.sid = sid
268 self.__seq = 1
269 self.__sipc_proxy = sipc_proxy
270 if self.login_type == "HTTP":
271 self.__http_tunnel = http_tunnel
272 self.__ssic = ssic
273 guid = str(uuid1())
274 self.__exheaders = {
275 'Cookie':'ssic=%s' % self.__ssic,
276 'Content-Type':'application/oct-stream',
277 'Pragma':'xz4BBcV%s' % guid,
278 }
279
280 def init(self,type):
281 self.content = '%s %s %s\r\n' % (type,self.domain,self.ver)
282 self.header = [('F',self.sid),
283 ('I',self.ID),
284 ('Q','1 %s' % type),
285 ]
286
287 def send(self):
288 content = self.content
289 d_print(('content',),locals())
290 if self.login_type == "HTTP":
291 #First time t SHOULD SET AS 'i'
292 #Otherwise 405 code get
293 if self.__seq == 1:
294 t = 'i'
295 else:
296 t = 's'
297 url = self.__http_tunnel+"?t=%s&i=%s" % (t,self.__seq)
298 response = self.__http_send(url,content,self.__exheaders).read()
299 self.__seq+=1
300 response = self.__sendSIPP()
301 #This line will enhance the probablity of success.
302 #Sometimes it will return FetionSIPP twice.
303 #Probably you need add more
304 if response == FetionSIPP:
305 response = self.__sendSIPP()
306 else:
307 if self.__seq == 1:
308 self.__tcp_init()
309 self.__tcp_send(content)
310 response = self.__tcp_recv()
311 d_print(('response',),locals())
312 self.__seq+=1
313
314 code = self.get_code(response)
315 d_print(('code',),locals())
316 return response
317
318
319
320 def get_code(self,response):
321 try:
322 self.code =int(re.search("%s (\d{3})" % self.ver,response).group(1))
323 self.msg =re.search("%s \d{3} (.*)\r" % self.ver,response).group(1)
324 d_print(('self.code','self.msg',),locals())
325 return self.code
326 except AttributeError,e:
327 return None
328
329 def get(self,cmd,arg,ret="",extra=""):
330 body = ret
331 if cmd == "REG":
332 body = FetionLoginXML
333 self.init('R')
334 if arg == 1:
335 pass
336 if arg == 2:
337 nonce = re.search('nonce="(.*)"',ret).group(1)
338 cnonce = self.__get_cnonce()
339 if FetionVer == "2008":
340 response=self.__get_response_sha1(nonce,cnonce)
341 elif FetionVer == "2006":
342 response=self.__get_response_md5(nonce,cnonce)
343 salt = self.__get_salt()
344 d_print(('nonce','cnonce','response','salt'),locals())
345 #If this step failed try to uncomment this lines
346 #del self.header[2]
347 #self.header.insert(2,('Q','2 R'))
348 if FetionVer == "2008":
349 self.header.insert(3,('A','Digest algorithm="SHA1-sess",response="%s",cnonce="%s",salt="%s"' % (response,cnonce,salt)))
350 elif FetionVer == "2006":
351 self.header.insert(3,('A','Digest response="%s",cnonce="%s"' % (response,cnonce)))
352 #If register successful 200 code get
353 if arg == 3:
354 return self.code
355
356 if cmd == "SENDMSG":
357 self.init('M')
358 self.header.insert(3,('T',arg))
359 self.header.insert(4,('C','text/plain'))
360 self.header.insert(5,('K','SaveHistory'))
361
362 if cmd == "SENDSMS":
363 self.init('M')
364 self.header.append(('T',arg))
365 self.header.append(('N','SendSMS'))
366
367 if cmd == "SENDCatSMS":
368 self.init('M')
369 self.header.append(('T',arg))
370 self.header.append(('N','SendCatSMS'))
371
372 if cmd == "SSSetScheduleSms":
373 self.init('S')
374 self.header.insert(3,('N',cmd))
375 body = '<args><schedule-sms send-time="%s"><message>%s</message><receivers><receiver uri="%s" /></receivers></schedule-sms></args>' % (ret,arg,extra)
376 if cmd == "INFO":
377 self.init('S')
378 self.header.insert(3,('N',arg))
379 if arg == "GetPersonalInfo":
380 body = '<args><personal attributes="all" /><services version="" attributes="all" /><config version="33" attributes="all" /><mobile-device attributes="all" /></args>'
381 elif arg == "GetContactList":
382 body = '<args><contacts attributes="all"><buddies attributes="all" /></contacts></args>'
383 elif arg == "GetContactsInfo":
384 body = '<args><contacts attributes="all"><contact uri="%s" /></contacts></args>' % ret
385 elif arg == "AddBuddy":
386 body = '<args><contacts><buddies><buddy uri="tel:%s" buddy-lists="1" desc="This message is send by PyFetion" expose-mobile-no="1" expose-name="1" /></buddies></contacts></args>' % ret
387 elif arg == "AddMobileBuddy":
388 body = '<args><contacts><mobile-buddies><mobile-buddy uri="tel:%s" buddy-lists="1" desc="THis message is send by PyFetion" invite="0" /></mobile-buddies></contacts></args>' % ret
389
390
391
392 #general SIPC info
393 self.header.append(('L',len(body)))
394 for k in self.header:
395 self.content = self.content + k[0] + ": " + str(k[1]) + "\r\n"
396 self.content+="\r\n"
397 self.content+= body
398 if self.login_type == "HTTP":
399 #IN TCP CONNECTION "SIPP" SHOULD NOT BEEN SEND
400 self.content+= FetionSIPP
401 return self.content
402
403
404 def __sendSIPP(self):
405 body = FetionSIPP
406 url = self.__http_tunnel+"?t=s&i=%s" % self.__seq
407 response = self.__http_send(url,body,self.__exheaders).read()
408 d_print(('response',),locals())
409 self.__seq+=1
410 return response
411
412 def __http_send(self,url,body="",exheaders="",login=False):
413 headers = {
414 'User-Agent':'IIC2.0/PC 3.2.0540',
415 }
416 headers.update(exheaders)
417 request = urllib2.Request(url,headers=headers,data=body)
418 try:
419 conn = urllib2.urlopen(request)
420 except urllib2.URLError, e:
421 code = e.code
422 msg = e.read()
423 d_print(('code','text'),locals())
424 if code == 401 or code == 404:
425 if login:
426 raise PyFetionAuthError(code,msg)
427 return -1
428
429 return conn
430
431
432 def __tcp_init(self):
433 try:
434 self.__sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
435 except socket.error,e:
436 s = None
437 print e.read()
438 #Should return -1 NOT just exit
439 sys.exit(-1)
440 (host,port) = tuple(self.__sipc_proxy.split(":"))
441 port = int(port)
442 try:
443 self.__sock.connect((host,port))
444 except socket.error,e:
445 self.__sock.close()
446 self.__sock = None
447 print e.read()
448 sys.exit(-1)
449
450
451 def __tcp_send(self,msg):
452 try:
453 self.__sock.send(msg)
454 except socket.error,e:
455 self.__sock.close()
456 print e.read()
457 sys.exit(-1)
458
459 def __tcp_recv(self):
460 try:
461 data = self.__sock.recv(4096)
462 except socket.error,e:
463 self.__sock.close()
464 print e.read()
465 sys.exit(-1)
466 return data
467
468
469
470 def __get_salt(self):
471 return self.__hash_passwd()[:8]
472
473 def __get_cnonce(self):
474 return md5(str(uuid1())).hexdigest().upper()
475
476 def __get_response_md5(self,nonce,cnonce):
477 #nonce = "3D8348924962579418512B8B3966294E"
478 #cnonce= "9E169DCA9CBD85F1D1A89A893E00917E"
479 key = md5("%s:%s:%s" % (self.sid,self.domain,self.passwd)).digest()
480 h1 = md5("%s:%s:%s" % (key,nonce,cnonce)).hexdigest().upper()
481 h2 = md5("REGISTER:%s" % self.sid).hexdigest().upper()
482 response = md5("%s:%s:%s" % (h1,nonce,h2)).hexdigest().upper()
483 #d_print(('nonce','cnonce','key','h1','h2','response'),locals())
484 return response
485
486 def __get_response_sha1(self,nonce,cnonce):
487 #nonce = "3D8348924962579418512B8B3966294E"
488 #cnonce= "9E169DCA9CBD85F1D1A89A893E00917E"
489 hash_passwd = self.__hash_passwd()
490 hash_passwd_str = binascii.unhexlify(hash_passwd[8:])
491 key = sha1("%s:%s:%s" % (self.sid,self.domain,hash_passwd_str)).digest()
492 h1 = md5("%s:%s:%s" % (key,nonce,cnonce)).hexdigest().upper()
493 h2 = md5("REGISTER:%s" % self.sid).hexdigest().upper()
494 response = md5("%s:%s:%s" % (h1,nonce,h2)).hexdigest().upper()
495 return response
496
497 def __hash_passwd(self):
498 #salt = '%s%s%s%s' % (chr(0x77), chr(0x7A), chr(0x6D), chr(0x03))
499 salt = 'wzm\x03'
500 src = salt+sha1(self.passwd).digest()
501 return "777A6D03"+sha1(src).hexdigest().upper()
502
503
504 def d_print(vars=(),namespace=[],msg=""):
505 """if only sigle variable use like this ('var',)"""
506 global debug
507 if vars and not namespace and not msg:
508 msg = vars
509 if debug and vars and namespace:
510 for var in vars:
511 if var in namespace:
512 print "[PyFetion]:\033[0;31;48m%s\033[0m" % var,
513 print namespace[var]
514 if debug and msg:
515 print "[PyFetion]:\033[0;31;48m%s\033[0m" % msg
516
517
518 def main(argv=None):
519 try:
520 phone = PyFetion("138888888","888888","TCP")
521 except PyFetionInfoError,e:
522 print "corrent your mobile NO. and password"
523 return -1
524 phone.login()
525 #phone.get_offline_msg()
526 #phone.add("138888888")
527 #phone.get_info()
528 #phone.get_contact_list()
529 #phone.send_sms("Hello, ",long=True)
530 s = "2008-12-31 02:39:00."
531 for i in range(100,500):
532 time = s + str(i)
533 phone.send_schedule_sms("请注意,这个是定时短信",time)
534 #time_format = "%Y-%m-%d %H:%M:%S"
535 #time.strftime(time_format,time.gmtime())
536
537 if __name__ == "__main__":
538 sys.exit(main())
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.