shithub: tlsclient

ref: 396f8f369db25a0d6b0120ea08ecc84d089b8202
dir: /cpu.c/

View raw version
/*
 * cpu.c - Make a connection to a cpu server
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <gnutls/gnutls.h>

#include <u.h>
#include <args.h>
#include <libc.h>
#include <auth.h>
#include <authsrv.h>
#include <libsec.h>


#define MaxStr 128

static void	usage(void);
static int	readstr(int, char*, int);
static AuthInfo *p9any(int);
static int	getkey(Authkey*, char*, char*, char*, char*);
static int	p9authtls(int);

static char	*host;

char *argv0;

char *authserver;
char *secstore;
char *user, *pass;
char secstorebuf[65536];
char *geometry;

gnutls_session_t session;

void errstr(char *s){}

int
unix_dial(char *host, char *port)
{
	int fd;
	struct sockaddr_in server;
	struct hostent *he;
	struct in_addr **addr_list;

	he = gethostbyname(host);
	if(he == nil){
		sysfatal("could not resolve %s", host);
	}
	fd = socket(AF_INET, SOCK_STREAM, 0);
	addr_list = (struct in_addr **) he->h_addr_list;
	server.sin_addr.s_addr = inet_addr(inet_ntoa(*addr_list[0]));
	server.sin_family = AF_INET;
	server.sin_port = htons(atoi(port));
	if(connect(fd, (struct sockaddr*)&server, sizeof(server)) < 0)
		return -1;
	return fd;
}


char*
estrdup(char *s)
{
	s = strdup(s);
	if(s == nil)
		sysfatal("out of memory");
	return s;
}

typedef size_t (*iofunc)(int, void*, size_t);
size_t tls_send(int f, void *b, size_t n) { return gnutls_record_send(session, b, n); }
size_t tls_recv(int f, void *b, size_t n) { return gnutls_record_recv(session, b, n); }
size_t s_send(int f, void *b, size_t n) { return write(f, b, n); }
size_t s_recv(int f, void *b, size_t n) { return read(f, b, n); }

void
xfer(int from, int to, iofunc recvf, iofunc sendf)
{
	char buf[12*1024];
	size_t n;
	
	while((n = recvf(from, buf, sizeof buf)) > 0){
		if(sendf(to, buf, n) < 0)
			break;
	}

}

void
usage(void)
{
	fprint(2, "Usage: %s [-f] [ -u user ] [ -h host ] [ -a authserver ] -p port cmd...\n", argv0);
	exits("usage");
}

int
main(int argc, char **argv)
{
	int fd, res;
	char *cmd;
	char buf[1024];
	size_t n;
	char *port;
	int pin[2];
	int pout[2];
	int infd, outfd;
	pid_t execc, xferc;
	int consin, consout, consflag;

	execc = xferc = 0;
	consflag = 0;
	infd = 0;
	outfd = 1;
	user = getenv("USER");	
	host = getenv("CPU");
	authserver = getenv("AUTH");
	pass = getenv("PASS");
	port = nil;

	ARGBEGIN {
		case 'u': user = EARGF(usage()); break;
		case 'h': host = EARGF(usage()); break;
		case 'a': authserver = EARGF(usage()); break;
		case 'p': port = EARGF(usage()); break;
		case 'f': consflag++; break;
	} ARGEND

	if(user == nil || host == nil || authserver == nil || port == nil)
		usage();

	if(pass == nil)
		pass = getpass("password:");

	gnutls_global_init();
	res = gnutls_init(&session, GNUTLS_CLIENT);
	if(res != GNUTLS_E_SUCCESS){
		sysfatal("could not init session");
	}

	if(*argv){
		pipe(pin);
		pipe(pout);
		if(consflag){
			/*
			 * Unix has no /dev/cons, so there is no way to read
			 * and write from the terminal if stdin and stdout
			 * are dup'd over with the socket. This gives a bit
			 * of a back door in to the orginal stdin and stdout.
			 */
			consin = dup(0);
			consout = dup(1);
		}
		switch((execc = fork())){
		case -1:
			sysfatal("fork");
		case 0:
			close(pin[1]);
			close(pout[0]);
			dup2(pin[0], 0);
			dup2(pout[1], 1);
			execvp(argv[0], argv);
			sysfatal("exec");
		}
		close(pout[1]);
		close(pin[0]);
		infd = pout[0];
		outfd = pin[1];
		if(consflag){
			/*
			 * For the sake of portability,
			 * send the "cons" fds as the first
			 * to lines to the child process to avoid
			 * having to assume the next two fds
			 */
			n = sprint(buf, "%d%d", consin, consout);
			write(pin[1], buf, n);
		}
	}

	fd = unix_dial(host, port);
	if(fd < 0){
		sysfatal("Failed to connect to the client");
	}

	p9authtls(fd);

	switch((xferc = fork())){
	case -1:
		sysfatal("fork");
	case 0:
		xfer(infd, -1, s_recv, tls_send);
		break;
	default:
		xfer(-1, outfd, tls_recv, s_send);
		break;
	}
	
	if(xferc)
		kill(xferc, SIGTERM);
	if(execc)
		kill(execc, SIGTERM);
}

int
readstr(int fd, char *str, int len)
{
	int n;

	while(len) {
		n = read(fd, str, 1);
		if(n < 0) 
			return -1;
		if(*str == '\0')
			return 0;
		str++;
		len--;
	}
	return -1;
}


/*
 * p9any authentication followed by tls-psk encryption
 */
static int
p9authtls(int fd)
{
	AuthInfo *ai;
	gnutls_psk_client_credentials_t cred;
	gnutls_datum_t key;
	const char *error = NULL;
	int res;

	ai = p9any(fd);
	if(ai == nil)
		sysfatal("can't authenticate: %r");

	if(gnutls_psk_allocate_client_credentials(&cred) != 0)
		sysfatal("can't allocate client creds");

	key.size = ai->nsecret;
	key.data = ai->secret;

	if(gnutls_psk_set_client_credentials(cred, "p9secret", &key, GNUTLS_PSK_KEY_RAW) != 0)
		sysfatal("can't set creds");
	if(gnutls_credentials_set(session, GNUTLS_CRD_PSK, cred) != 0)
		sysfatal("can't set creds 2");
	res = gnutls_priority_set_direct(
		session,
		"NONE:+VERS-TLS1.2:+SIGN-ALL:+MAC-ALL:+CHACHA20-POLY1305:+PSK:+CTYPE-ALL",
		&error
	);
	if (res != GNUTLS_E_SUCCESS) {
		sysfatal("gnutls_priority_set_direct() failed: %s", error);
	}
	gnutls_transport_set_int(session, fd);
	do {
		res = gnutls_handshake(session);
	} while ( res != 0 && !gnutls_error_is_fatal(res) );

	if (gnutls_error_is_fatal(res)) {
		sysfatal("Fatal error during handshake");
	}

	return fd;
}

int
authdial(char *net, char *dom)
{
	return unix_dial(authserver, "567");
}

static int
getastickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
{
	int asfd, rv;
	char *dom;

	dom = tr->authdom;
	asfd = authdial(nil, dom);
	if(asfd < 0)
		return -1;
	if(y != nil){
		PAKpriv p;

		rv = -1;
		tr->type = AuthPAK;
		if(_asrequest(asfd, tr) != 0 || write(asfd, y, PAKYLEN) != PAKYLEN)
			goto Out;

		authpak_new(&p, key, (uchar*)tbuf, 1);
		if(write(asfd, tbuf, PAKYLEN) != PAKYLEN)
			goto Out;

		if(_asrdresp(asfd, tbuf, 2*PAKYLEN) != 2*PAKYLEN)
			goto Out;
	
		memmove(y, tbuf, PAKYLEN);
		if(authpak_finish(&p, key, (uchar*)tbuf+PAKYLEN))
			goto Out;
	}
	tr->type = AuthTreq;
	rv = _asgetticket(asfd, tr, tbuf, tbuflen);
Out:
	close(asfd);
	return rv;
}

static int
mkservertickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
{
	Ticket t;
	int ret;

	if(strcmp(tr->authid, tr->hostid) != 0)
		return -1;
	memset(&t, 0, sizeof(t));
	ret = 0;
	if(y != nil){
		PAKpriv p;

		t.form = 1;
		memmove(tbuf, y, PAKYLEN);
		authpak_new(&p, key, y, 0);
		authpak_finish(&p, key, (uchar*)tbuf);
	}
	memmove(t.chal, tr->chal, CHALLEN);
	strcpy(t.cuid, tr->uid);
	strcpy(t.suid, tr->uid);
	genrandom((uchar*)t.key, sizeof(t.key));
	t.num = AuthTc;
	ret += convT2M(&t, tbuf+ret, tbuflen-ret, key);
	t.num = AuthTs;
	ret += convT2M(&t, tbuf+ret, tbuflen-ret, key);
	memset(&t, 0, sizeof(t));

	return ret;
}

static int
gettickets(Authkey *key, Ticketreq *tr, uchar *y, char *tbuf, int tbuflen)
{
	int ret;
	ret = getastickets(key, tr, y, tbuf, tbuflen);
	if(ret > 0)
		return ret;
	return mkservertickets(key, tr, y, tbuf, tbuflen);
}

AuthInfo*
p9any(int fd)
{
	char buf[1024], buf2[1024], *bbuf, *p, *proto, *dom;
	uchar crand[2*NONCELEN], cchal[CHALLEN], y[PAKYLEN];
	char tbuf[2*MAXTICKETLEN+MAXAUTHENTLEN+PAKYLEN], trbuf[TICKREQLEN+PAKYLEN];
	Authkey authkey;
	Authenticator auth;
	int i, n, m, v2, dp9ik;
	Ticketreq tr;
	Ticket t;
	AuthInfo *ai;

	if(readstr(fd, buf, sizeof buf) < 0)
		sysfatal("cannot read p9any negotiation: %r");
	bbuf = buf;
	v2 = 0;
	if(strncmp(buf, "v.2 ", 4) == 0){
		v2 = 1;
		bbuf += 4;
	}
	dp9ik = 0;
	proto = nil;
	while(bbuf != nil){
		if((p = strchr(bbuf, ' ')))
			*p++ = 0;
		if((dom = strchr(bbuf, '@')) == nil)
			sysfatal("bad p9any domain");
		*dom++ = 0;
		if(strcmp(bbuf, "p9sk1") == 0 || strcmp(bbuf, "dp9ik") == 0){
			proto = bbuf;
			if(strcmp(proto, "dp9ik") == 0){
				dp9ik = 1;
				break;
			}
		}
		bbuf = p;
	}
	if(proto == nil)
		sysfatal("server did not offer p9sk1 or dp9ik");
	proto = estrdup(proto);
	sprint(buf2, "%s %s", proto, dom);
	if(write(fd, buf2, strlen(buf2)+1) != strlen(buf2)+1)
		sysfatal("cannot write user/domain choice in p9any");
	if(v2){
		if(readstr(fd, buf, sizeof buf) < 0)
			sysfatal("cannot read OK in p9any: %r");
		if(memcmp(buf, "OK\0", 3) != 0)
			sysfatal("did not get OK in p9any: got %s", buf);
	}
	genrandom(crand, 2*NONCELEN);
	genrandom(cchal, CHALLEN);
	if(write(fd, cchal, CHALLEN) != CHALLEN)
		sysfatal("cannot write p9sk1 challenge: %r");

	n = TICKREQLEN;
	if(dp9ik)
		n += PAKYLEN;

	if(readn(fd, trbuf, n) != n || convM2TR(trbuf, TICKREQLEN, &tr) <= 0)
		sysfatal("cannot read ticket request in p9sk1: %r");

again:
	if(!getkey(&authkey, user, tr.authdom, proto, pass))
		sysfatal("no password");

	strecpy(tr.hostid, tr.hostid+sizeof tr.hostid, user);
	strecpy(tr.uid, tr.uid+sizeof tr.uid, user);

	if(dp9ik){
		memmove(y, trbuf+TICKREQLEN, PAKYLEN);
		n = gettickets(&authkey, &tr, y, tbuf, sizeof(tbuf));
	} else {
		n = gettickets(&authkey, &tr, nil, tbuf, sizeof(tbuf));
	}
	if(n <= 0)
		sysfatal("cannot get auth tickets in p9sk1: %r");

	m = convM2T(tbuf, n, &t, &authkey);
	if(m <= 0 || t.num != AuthTc){
		print("?password mismatch with auth server\n");
		if(pass != nil && *pass)
			sysfatal("wrong password");
		goto again;
	}
	n -= m;
	memmove(tbuf, tbuf+m, n);

	if(dp9ik && write(fd, y, PAKYLEN) != PAKYLEN)
		sysfatal("cannot send authpak public key back: %r");

	auth.num = AuthAc;
	memmove(auth.rand, crand, NONCELEN);
	memmove(auth.chal, tr.chal, CHALLEN);
	m = convA2M(&auth, tbuf+n, sizeof(tbuf)-n, &t);
	n += m;

	if(write(fd, tbuf, n) != n)
		sysfatal("cannot send ticket and authenticator back: %r");

	if((n=read(fd, tbuf, m)) != m || memcmp(tbuf, "cpu:", 4) == 0){
		if(n <= 4)
			sysfatal("cannot read authenticator");

		/*
		 * didn't send back authenticator:
		 * sent back fatal error message.
		 */
		memmove(buf, tbuf, n);
		i = readn(fd, buf+n, sizeof buf-n-1);
		if(i > 0)
			n += i;
		buf[n] = 0;
		sysfatal("server says: %s", buf);
	}
	
	if(convM2A(tbuf, n, &auth, &t) <= 0
	|| auth.num != AuthAs || tsmemcmp(auth.chal, cchal, CHALLEN) != 0){
		print("?you and auth server agree about password.\n");
		print("?server is confused.\n");
		sysfatal("server lies");
	}
	memmove(crand+NONCELEN, auth.rand, NONCELEN);

	// print("i am %s there.\n", t.suid);

	ai = mallocz(sizeof(AuthInfo), 1);
	ai->suid = estrdup(t.suid);
	ai->cuid = estrdup(t.cuid);
	if(dp9ik){
		static char info[] = "Plan 9 session secret";
		ai->nsecret = 256;
		ai->secret = mallocz(ai->nsecret, 1);
		hkdf_x(	crand, 2*NONCELEN,
			(uchar*)info, sizeof(info)-1,
			(uchar*)t.key, NONCELEN,
			ai->secret, ai->nsecret,
			hmac_sha2_256, SHA2_256dlen);
	} else {
		ai->nsecret = 8;
		ai->secret = mallocz(ai->nsecret, 1);
		des56to64((uchar*)t.key, ai->secret);
	}

	memset(&t, 0, sizeof(t));
	memset(&auth, 0, sizeof(auth));
	memset(&authkey, 0, sizeof(authkey));
	memset(cchal, 0, sizeof(cchal));
	memset(crand, 0, sizeof(crand));
	free(proto);

	return ai;
}

static int
getkey(Authkey *key, char *user, char *dom, char *proto, char *pass)
{
	if(pass != nil && *pass)
		pass = estrdup(pass);
	else {
		sysfatal("getkey: no password");
	}
	if(pass != nil){
		memset(key, 0, sizeof(*key));
		passtokey(key, pass);
		if(strcmp(proto, "dp9ik") == 0) {
			authpak_hash(key, user);
		}
		return 1;
	}
	return 0;
}