ref: a5ac48e0d64d20fc0acef12f8639a5613a76c573
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);
}