ref: cb2c30bfbf3f7d0ce1ecc28faf6891c98ab28273
dir: /conn.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <libsec.h> #include <ndb.h> #include "xml.h" #include "xmpp.h" enum { Ascramsha1 = 1<<0, Adigestmd5 = 1<<1, Aplain = 1<<2, }; static Xelem* expect(Biobuf *b, int flags, char *name) { Xelem *x; int err; x = xmlread(b, flags, &err); if(x != nil && strcmp(x->n, name) != 0){ werrstr("expected %q, got %q", name, x->n); xmlprint(x, 2); xmlfree(x); x = nil; } return x; } static int authscramsha1(int fd, Biobuf *b, char *user, char *passwd) { uchar cnonce[33], h[3][SHA1dlen], cp[SHA1dlen], svsig[SHA1dlen]; char cnonce64[45], *snonce64, *salt64, *salt, *s, *ni, *svfst; int i, j, numiter, pwdlen, slen; Xelem *x; svfst = nil; genrandom(cnonce, sizeof(cnonce)); if(enc64(cnonce64, sizeof(cnonce64), cnonce, sizeof(cnonce)) < 0) return -1; /* client first message */ s = smprint("n,,n=%s,r=%s", user, cnonce64); fprint(fd, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" " mechanism='SCRAM-SHA-1'>%.*[</auth>", (int)strlen(s), s); free(s); s = nil; /* server first message */ if((x = expect(b, 0, "challenge")) == nil || x->v == nil) goto error; slen = strlen(x->v); svfst = malloc(slen); if((slen = dec64((uchar*)svfst, slen, x->v, slen)) < 0) goto error; svfst[slen] = 0; s = strdup(svfst); if(s[0] != 'r' || s[1] != '=' || strncmp(cnonce64, s+2, strlen(cnonce64)) != 0) goto error; snonce64 = s + 2; salt64 = strstr(snonce64, ",s="); ni = salt64 != nil ? strstr(salt64+3, ",i=") : nil; if(!snonce64[0] || salt64 == nil || ni == nil) goto error; *salt64 = 0; salt64 += 3; *ni = 0; ni += 3; numiter = atoi(ni); if(!salt64[0] || numiter < 1) goto error; /* decode salt */ slen = strlen(salt64); salt = malloc(slen+4); if((slen = dec64((uchar*)salt, slen, salt64, slen)) < 0){ free(salt); goto error; } /* calc salted password in h[2] */ salt[slen+0] = 0; salt[slen+1] = 0; salt[slen+2] = 0; salt[slen+3] = 1; pwdlen = strlen(passwd); hmac_sha1((uchar*)salt, slen+4, (uchar*)passwd, pwdlen, h[0], nil); free(salt); memcpy(h[2], h[0], SHA1dlen); for(i = 1; i < numiter; i++){ hmac_sha1(h[0], SHA1dlen, (uchar*)passwd, pwdlen, h[1], nil); for(j = 0; j < SHA1dlen; j++) h[2][j] ^= h[1][j]; memcpy(h[0], h[1], SHA1dlen); } /* client (h[0]), server (h[1]) and stored (h[2]) keys */ hmac_sha1((uchar*)"Client Key", 10, h[2], SHA1dlen, h[0], nil); hmac_sha1((uchar*)"Server Key", 10, h[2], SHA1dlen, h[1], nil); sha1(h[0], SHA1dlen, h[2], nil); /* auth message */ snonce64 = strdup(snonce64); free(s); s = smprint("n=%s,r=%s,%s,c=biws,r=%s", user, cnonce64, svfst, snonce64); free(svfst); svfst = nil; xmlfree(x); /* client and server signatures */ hmac_sha1((uchar*)s, strlen(s), h[2], SHA1dlen, cp, nil); hmac_sha1((uchar*)s, strlen(s), h[1], SHA1dlen, svsig, nil); /* client proof */ for(i = 0; i < SHA1dlen; i++) cp[i] = h[0][i] ^ cp[i]; free(s); s = smprint("c=biws,r=%s,p=%.*[", snonce64, SHA1dlen, cp); fprint(fd, "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" "%.*[</response>", (int)strlen(s), s); free(s); s = nil; free(snonce64); if((x = expect(b, 0, "success")) == nil || x->v == nil) goto error; slen = strlen(x->v); s = malloc(slen); if((slen = dec64((uchar*)s, slen, x->v, slen)) < 0) goto error; s[slen] = 0; svfst = smprint("v=%.*[", SHA1dlen, (uchar*)svsig); if(strcmp(s, svfst) != 0){ werrstr("server signature doesn't match"); goto error; } xmlfree(x); free(s); free(svfst); return 0; error: werrstr("authscramsha1: %r"); free(s); free(svfst); xmlfree(x); return -1; } static DigestState* md5fmt(char *out, DigestState *st, char *fmt, ...) { va_list arg; char *s; va_start(arg, fmt); s = vsmprint(fmt, arg); st = md5((uchar*)s, strlen(s), (uchar*)out, st); free(s); return st; } static int authmd5(int fd, Biobuf *b, char *user, char *passwd) { Xelem *x; int chsz; char *ch, *realm, *nonce, *s; char ha1[MD5dlen], ha2[MD5dlen], res[MD5dlen], cnonce[MD5dlen]; DigestState *dgst; fprint(fd, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" " mechanism='DIGEST-MD5'/>"); if((x = expect(b, 0, "challenge")) == nil || x->v == nil) return -1; chsz = strlen(x->v)/4*3; ch = malloc(chsz + 1); chsz = dec64((uchar*)ch, chsz, x->v, strlen(x->v)); xmlfree(x); if(chsz < 0) return -1; realm = strstr(ch, "realm="); nonce = strstr(ch, "nonce="); if(realm != nil && (s = strchr(realm+7, '"')) != nil){ *s = 0; realm += 7; } if(nonce != nil && (s = strchr(nonce+7, '"')) != nil){ *s = 0; nonce += 7; }else if(nonce == nil){ werrstr("nil nonce"); free(ch); return -1; } genrandom((uchar*)cnonce, MD5dlen); if(realm == nil) realm = mydomain; /* ha1 = md5(md5(user:realm:passwd):nonce:cnonce:jid) */ md5fmt(ha1, nil, "%s:%s:%s", user, realm, passwd); dgst = md5((uchar*)ha1, MD5dlen, nil, nil); md5fmt(ha1, dgst, ":%s:%.*lH:%s", nonce, MD5dlen, cnonce, myjid); /* ha2 = md5(method:digesturi) */ md5fmt(ha2, nil, "AUTHENTICATE:xmpp/%s", mydomain); /* response = md5(ha1:nonce:nc:cnonce:qop:ha2) */ md5fmt(res, nil, "%.*lH:%s:00000001:%.*lH:auth:%.*lH", MD5dlen, ha1, nonce, MD5dlen, cnonce, MD5dlen, ha2); s = smprint("username=\"%s\",realm=\"%s\",nonce=\"%s\"," "cnonce=\"%.*lH\",nc=00000001,qop=auth,digest-uri=\"xmpp/%s\"," "response=%.*lH,charset=utf-8,authzid=\"%s\"", user, realm, nonce, MD5dlen, cnonce, mydomain, MD5dlen, res, myjid); fprint(fd, "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" "%.*[</response>", (int)strlen(s), s); free(s); free(ch); if((x = expect(b, 0, "challenge")) != nil){ xmlfree(x); fprint(fd, "<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>"); if((x = expect(b, 0, "success")) != nil){ xmlfree(x); return 0; } } return -1; } static int authplain(int fd, Biobuf *b, char *user, char *passwd) { int len; char *p; Xelem *x; p = smprint("%c%s%c%s", 0, user, 0, passwd); len = 1+strlen(user)+1+strlen(passwd); fprint(fd, "<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'" " mechanism='PLAIN'>" "%.*[</auth>", len, p); free(p); if((x = expect(b, 0, "success")) != nil){ xmlfree(x); return 0; } return -1; } static int streamstart(int fd) { return fprint(fd, "<?xml version='1.0'?>" "<stream:stream" " to='%Ӽ'" " xmlns='jabber:client'" " xmlns:stream='http://etherx.jabber.org/streams'" " version='1.0'>", mydomain); } static int login(int fd, Biobuf *b, char *user, char *passwd) { Xelem *x, *y; Thumbprint *th; TLSconn tls; int auth, r, oldfd, err; uchar hash[SHA1dlen]; x = nil; if((th = initThumbprints("/sys/lib/tls/xmpp", nil, "x509")) == nil) return -1; if(Binit(b, fd, OREAD) != 0) return -1; streamstart(fd); xmlfree(xmlread(b, Xmlstartonly, &err)); if(err != 0 || (x = expect(b, 0, "stream:features")) == nil) goto error; if(debug > 1) xmlprint(x, 2); /* require TLS */ if(xmlget(x->ch, "starttls") == nil){ werrstr("tls not supported"); goto error; } xmlfree(x); fprint(fd, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>"); if((x = expect(b, 0, "proceed")) == nil) goto error; xmlfree(x); x = nil; Bterm(b); memset(&tls, 0, sizeof(tls)); oldfd = fd; fd = tlsClient(fd, &tls); close(oldfd); if(th != nil){ if(tls.cert == nil || tls.certlen < 1){ werrstr("no server cert"); goto error; } sha1(tls.cert, tls.certlen, hash, nil); if(!okThumbprint(hash, SHA1dlen, th)){ werrstr("unknown thumbprint: %.*H", SHA1dlen, hash); goto error; } freeThumbprints(th); } free(tls.sessionID); free(tls.cert); if(Binit(b, fd, OREAD) != 0) return -1; if(fd < 0) goto error; streamstart(fd); xmlfree(xmlread(b, Xmlstartonly, &err)); if(err != 0 || (x = expect(b, 0, "stream:features")) == nil) goto error; if(debug > 1) xmlprint(x, 2); auth = 0; if((y = xmlget(x->ch, "mechanisms")) != nil){ if(debug > 0) fprint(2, "auth methods:"); for(y = y->ch; y != nil; y = y->next){ if(debug > 0) fprint(2, " %s", y->v); if(strcmp(y->v, "SCRAM-SHA-1") == 0) auth |= Ascramsha1; else if(strcmp(y->v, "DIGEST-MD5") == 0) auth |= Adigestmd5; else if(strcmp(y->v, "PLAIN") == 0 && plainallow) auth |= Aplain; } if(debug > 0) fprint(2, "\n"); } xmlfree(x); x = nil; if(auth & Ascramsha1) r = authscramsha1(fd, b, user, passwd); else if(auth & Adigestmd5) r = authmd5(fd, b, user, passwd); else if(auth & Aplain) r = authplain(fd, b, user, passwd); else{ werrstr("no supported auth methods"); goto error; } if(r != 0) goto error; streamstart(fd); xmlfree(xmlread(b, Xmlstartonly, &err)); if(err != 0) goto error; xmlfree(xmlread(b, Xmlstartonly, &err)); if(err != 0) goto error; xmlfree(xmlread(b, 0, &err)); if(err != 0) goto error; if(myresource == nil){ fprint(fd, "<iq type='set' id='xml sucks'>" "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>"); }else{ fprint(fd, "<iq type='set' id='xml sucks'>" "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>" "<resource>%Ӽ</resource>" "</bind></iq>", myresource); } xmlfree(xmlread(b, 0, &err)); if(err != 0) goto error; fprint(fd, "<iq type='set' id='xmpp sucks'>" "<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>"); xmlfree(xmlread(b, 0, &err)); if(err == 0) return fd; error: werrstr("login: %r"); xmlfree(x); Bterm(b); return -1; } int connect(Biobuf *b, char *user, char *passwd) { int fd, clfd; Ndbtuple *srv, *s, *targ, *port; char *p; p = smprint("_xmpp-client._tcp.%s", server); srv = dnsquery(nil, p, "srv"); free(p); for(s = srv, fd = -1; s != nil && fd < 0; s = s->entry){ if(strcmp(s->attr, "dom") != 0) continue; targ = ndbfindattr(s, s->line, "target"); port = ndbfindattr(s, s->line, "port"); if(targ == nil || port == nil) continue; fd = dial(netmkaddr(targ->val, "tcp", port->val), nil, nil, &clfd); } ndbfree(srv); if(fd < 0){ fd = dial(netmkaddr(server, "tcp", "5222"), nil, nil, &clfd); if(fd < 0) return -1; } write(clfd, "keepalive", 9); close(clfd); return login(fd, b, user, passwd); }