449 lines · 9.9 KB · C
RawDownload
#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 intauthscramsha1(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 intauthmd5(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 intauthplain(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 intstreamstart(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 intlogin(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;} intconnect(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);}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t