[2271] | 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 | |
---|