1 | import sys, os |
---|
2 | import httplib, urllib |
---|
3 | import random, binascii |
---|
4 | from urlparse import urlparse |
---|
5 | |
---|
6 | from punjab.httpb import HttpbParse |
---|
7 | |
---|
8 | from twisted.words.xish import domish |
---|
9 | from twisted.words.protocols.jabber import jid |
---|
10 | |
---|
11 | TLS_XMLNS = 'urn:ietf:params:xml:ns:xmpp-tls' |
---|
12 | SASL_XMLNS = 'urn:ietf:params:xml:ns:xmpp-sasl' |
---|
13 | BIND_XMLNS = 'urn:ietf:params:xml:ns:xmpp-bind' |
---|
14 | SESSION_XMLNS = 'urn:ietf:params:xml:ns:xmpp-session' |
---|
15 | |
---|
16 | |
---|
17 | class BOSHClient: |
---|
18 | def __init__(self, jabberid, password, bosh_service): |
---|
19 | self.rid = random.randint(0, 10000000) |
---|
20 | self.jabberid = jid.internJID(jabberid) |
---|
21 | self.password = password |
---|
22 | |
---|
23 | self.authid = None |
---|
24 | self.sid = None |
---|
25 | self.logged_in = False |
---|
26 | self.headers = {"Content-type": "text/xml", |
---|
27 | "Accept": "text/xml"} |
---|
28 | |
---|
29 | self.bosh_service = urlparse(bosh_service) |
---|
30 | |
---|
31 | def buildBody(self, child=None): |
---|
32 | """Build a BOSH body. |
---|
33 | """ |
---|
34 | |
---|
35 | body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) |
---|
36 | body['content'] = 'text/xml; charset=utf-8' |
---|
37 | self.rid = self.rid + 1 |
---|
38 | body['rid'] = str(self.rid) |
---|
39 | body['sid'] = str(self.sid) |
---|
40 | body['xml:lang'] = 'en' |
---|
41 | |
---|
42 | if child is not None: |
---|
43 | body.addChild(child) |
---|
44 | |
---|
45 | return body |
---|
46 | |
---|
47 | def sendBody(self, body): |
---|
48 | """Send the body. |
---|
49 | """ |
---|
50 | |
---|
51 | parser = HttpbParse(True) |
---|
52 | |
---|
53 | # start new session |
---|
54 | conn = httplib.HTTPConnection(self.bosh_service.netloc) |
---|
55 | conn.request("POST", self.bosh_service.path, |
---|
56 | body.toXml(), self.headers) |
---|
57 | |
---|
58 | response = conn.getresponse() |
---|
59 | data = '' |
---|
60 | if response.status == 200: |
---|
61 | data = response.read() |
---|
62 | conn.close() |
---|
63 | |
---|
64 | return parser.parse(data) |
---|
65 | |
---|
66 | def startSessionAndAuth(self, hold='1', wait='70'): |
---|
67 | # Create a session |
---|
68 | # create body |
---|
69 | body = domish.Element(("http://jabber.org/protocol/httpbind", "body")) |
---|
70 | |
---|
71 | body['content'] = 'text/xml; charset=utf-8' |
---|
72 | body['hold'] = hold |
---|
73 | body['rid'] = str(self.rid) |
---|
74 | body['to'] = self.jabberid.host |
---|
75 | body['wait'] = wait |
---|
76 | body['window'] = '5' |
---|
77 | body['xml:lang'] = 'en' |
---|
78 | |
---|
79 | |
---|
80 | retb, elems = self.sendBody(body) |
---|
81 | if type(retb) != str and retb.hasAttribute('authid') and \ |
---|
82 | retb.hasAttribute('sid'): |
---|
83 | self.authid = retb['authid'] |
---|
84 | self.sid = retb['sid'] |
---|
85 | |
---|
86 | # go ahead and auth |
---|
87 | auth = domish.Element((SASL_XMLNS, 'auth')) |
---|
88 | auth['mechanism'] = 'PLAIN' |
---|
89 | |
---|
90 | # TODO: add authzid |
---|
91 | if auth['mechanism'] == 'PLAIN': |
---|
92 | auth_str = "" |
---|
93 | auth_str += "\000" |
---|
94 | auth_str += self.jabberid.user.encode('utf-8') |
---|
95 | auth_str += "\000" |
---|
96 | try: |
---|
97 | auth_str += self.password.encode('utf-8').strip() |
---|
98 | except UnicodeDecodeError: |
---|
99 | auth_str += self.password.decode('latin1') \ |
---|
100 | .encode('utf-8').strip() |
---|
101 | |
---|
102 | auth.addContent(binascii.b2a_base64(auth_str)) |
---|
103 | |
---|
104 | retb, elems = self.sendBody(self.buildBody(auth)) |
---|
105 | if len(elems) == 0: |
---|
106 | # poll for data |
---|
107 | retb, elems = self.sendBody(self.buildBody()) |
---|
108 | |
---|
109 | if len(elems) > 0: |
---|
110 | if elems[0].name == 'success': |
---|
111 | retb, elems = self.sendBody(self.buildBody()) |
---|
112 | |
---|
113 | if elems[0].firstChildElement().name == 'bind': |
---|
114 | iq = domish.Element(('jabber:client', 'iq')) |
---|
115 | iq['type'] = 'set' |
---|
116 | iq.addUniqueId() |
---|
117 | iq.addElement('bind') |
---|
118 | iq.bind['xmlns'] = BIND_XMLNS |
---|
119 | if self.jabberid.resource: |
---|
120 | iq.bind.addElement('resource') |
---|
121 | iq.bind.resource.addContent( |
---|
122 | self.jabberid.resource) |
---|
123 | |
---|
124 | retb, elems = self.sendBody(self.buildBody(iq)) |
---|
125 | if type(retb) != str and retb.name == 'body': |
---|
126 | # send session |
---|
127 | iq = domish.Element(('jabber:client', 'iq')) |
---|
128 | iq['type'] = 'set' |
---|
129 | iq.addUniqueId() |
---|
130 | iq.addElement('session') |
---|
131 | iq.session['xmlns'] = SESSION_XMLNS |
---|
132 | |
---|
133 | retb, elems = self.sendBody(self.buildBody(iq)) |
---|
134 | |
---|
135 | # did not bind, TODO - add a retry? |
---|
136 | if type(retb) != str and retb.name == 'body': |
---|
137 | self.logged_in = True |
---|
138 | # bump up the rid, punjab already |
---|
139 | # received self.rid |
---|
140 | self.rid += 1 |
---|
141 | |
---|
142 | |
---|
143 | if __name__ == '__main__': |
---|
144 | USERNAME = sys.argv[1] |
---|
145 | PASSWORD = sys.argv[2] |
---|
146 | SERVICE = sys.argv[3] |
---|
147 | |
---|
148 | c = BOSHClient(USERNAME, PASSWORD, SERVICE) |
---|
149 | c.startSessionAndAuth() |
---|
150 | |
---|
151 | print c.logged_in |
---|
152 | |
---|