569 lines · 11.2 KB · C
RawDownload
#include <u.h>#include <libc.h>#include <ctype.h> #include "git.h" #define Useragent	"useragent git/2.24.1"#define Contenthdr	"headers Content-Type: application/x-git-%s-pack-request"#define Accepthdr	"headers Accept: application/x-git-%s-pack-result" enum {	Nproto	= 16,	Nport	= 16,	Nhost	= 256,	Npath	= 128,	Nbranch	= 32,}; char *matchcap(char *s, char *cap, int full){	if(strncmp(s, cap, strlen(cap)) == 0)		if(!full || strlen(s) == strlen(cap))			return s + strlen(cap);	return nil;} voidparsecaps(char *caps, Conn *c){	char *p, *n, *s, *t; 	for(p = caps; p != nil; p = n){		n = strchr(p, ' ');		if(n != nil)			*n++ = 0;		if(matchcap(p, "report-status", 1) != nil)			c->report = 1;		if(matchcap(p, "multi_ack", 1) != nil)			c->multiack = 1;		else if(matchcap(p, "side-band", 1) != nil)			c->sideband = 1;		else if(matchcap(p, "side-band-64k", 1) != nil)			c->sideband64k = 1;		else if((s = matchcap(p, "symref=", 0)) != nil){			if((t = strchr(s, ':')) == nil)				continue;			*t++ = '\0';			snprint(c->symfrom, sizeof(c->symfrom), s);			snprint(c->symto, sizeof(c->symto), t);		}	}}  voidtracepkt(int v, char *pfx, char *b, int n){	char *f;	int o, i; 	if(chattygit < v)		return;	o = 0;	f = emalloc(n*4 + 1);	for(i = 0; i < n; i++){		if(isprint(b[i])){			f[o++] = b[i];			continue;		}		f[o++] = '\\';		switch(b[i]){		case '\\':	f[o++] = '\\';	break;		case '\n':	f[o++] = 'n';	break;		case '\r':	f[o++] = 'r';	break;		case '\v':	f[o++] = 'v';	break;		case '\0':	f[o++] = '0';	break;		default:			f[o++] = 'x';			f[o++] = "0123456789abcdef"[(b[i]>>4)&0xf];			f[o++] = "0123456789abcdef"[(b[i]>>0)&0xf];			break;		}	}	f[o] = '\0';	fprint(2, "%s %04x:\t%s\n", pfx, n, f);	free(f);} intreadpkt(Conn *c, char *buf, int nbuf){	char len[5];	char *e;	int n; 	if(readn(c->rfd, len, 4) != 4){		werrstr("pktline: short read from transport");		return -1;	}	len[4] = 0;	n = strtol(len, &e, 16);	if(n == 0){		dprint(1, "=r=> 0000\n");		return 0;	}	if(e != len + 4 || n <= 4)		sysfatal("pktline: bad length '%s'", len);	n  -= 4;	if(n >= nbuf)		sysfatal("pktline: undersize buffer");	if(readn(c->rfd, buf, n) != n)		return -1;	if(n > 4 && strncmp(buf, "ERR ", 4) == 0){		if((e = strrchr(buf, '\n')) != nil)			*e = '\0';		werrstr("%s", buf + 4);		return -1;	}	buf[n] = 0;	tracepkt(1, "=r=>", buf, n);	return n;} intwritepkt(Conn *c, char *buf, int nbuf){	char len[5];  	snprint(len, sizeof(len), "%04x", nbuf + 4);	if(write(c->wfd, len, 4) != 4)		return -1;	if(write(c->wfd, buf, nbuf) != nbuf)		return -1;	tracepkt(1, "<=w=", buf, nbuf);	return 0;} intfmtpkt(Conn *c, char *fmt, ...){	char pkt[Pktmax];	va_list ap;	int n; 	va_start(ap, fmt);	n = vsnprint(pkt, sizeof(pkt), fmt, ap);	n = writepkt(c, pkt, n);	va_end(ap);	return n;} intflushpkt(Conn *c){	dprint(1, "<=w= 0000\n");	return write(c->wfd, "0000", 4);} static voidgrab(char *dst, int n, char *p, char *e){	int l; 	l = e - p;	if(l >= n)		sysfatal("overlong component");	memcpy(dst, p, l);	dst[l] = 0;} static intparseuri(char *uri, char *proto, char *host, char *port, char *path){	char *s, *p, *q;	int hasport; 	print("uri: \"%s\"\n", uri); 	p = strstr(uri, "://");	if(p == nil)		snprint(proto, Nproto, "ssh");	else if(strncmp(uri, "git+", 4) == 0)		grab(proto, Nproto, uri + 4, p);	else		grab(proto, Nproto, uri, p);	*port = 0;	hasport = 1;	if(strcmp(proto, "git") == 0)		snprint(port, Nport, "9418");	else if(strncmp(proto, "https", 5) == 0)		snprint(port, Nport, "443");	else if(strncmp(proto, "http", 4) == 0)		snprint(port, Nport, "80");	else if(strncmp(proto, "hjgit", 5) == 0)		snprint(port, Nport, "17021");	else if(strncmp(proto, "gits", 5) == 0)		snprint(port, Nport, "9419");	else		hasport = 0;	s = (p != nil) ? p + 3 : uri;	p = nil;	if(!hasport){		p = strstr(s, ":");		if(p != nil)			p++;	}	if(p == nil)		p = strstr(s, "/");	if(p == nil || strlen(p) == 1){		werrstr("missing path");		return -1;	} 	q = memchr(s, ':', p - s);	if(q){		grab(host, Nhost, s, q);		grab(port, Nport, q + 1, p);	}else{		grab(host, Nhost, s, p);	} 	snprint(path, Npath, "%s", p);	return 0;} static intwebclone(Conn *c, char *url){	char buf[16];	int n, conn; 	if((c->cfd = open("/mnt/web/clone", ORDWR)) < 0)		goto err;	if((n = read(c->cfd, buf, sizeof(buf)-1)) == -1)		goto err;	buf[n] = 0;	conn = atoi(buf); 	/* github will behave differently based on useragent */	if(write(c->cfd, Useragent, sizeof(Useragent)) == -1)		return -1;	dprint(1, "open url %s\n", url);	if(fprint(c->cfd, "url %s", url) == -1)		goto err;	free(c->dir);	c->dir = smprint("/mnt/web/%d", conn);	return 0;err:	if(c->cfd != -1)		close(c->cfd);	return -1;} static intwebopen(Conn *c, char *file, int mode){	char path[128];	int fd; 	snprint(path, sizeof(path), "%s/%s", c->dir, file);	if((fd = open(path, mode)) == -1)		return -1;	return fd;} static intissmarthttp(Conn *c, char *direction){	char buf[Pktmax+1], svc[128];	int fd, n; 	if((fd = webopen(c, "contenttype", OREAD)) == -1)		return -1;	n = readn(fd, buf, sizeof(buf) - 1);	close(fd);	if(n == -1)		return -1;	buf[n] = '\0';	snprint(svc, sizeof(svc), "application/x-git-%s-pack-advertisement", direction);	if(strcmp(svc, buf) != 0){		werrstr("dumb http protocol not supported");		return -1;	} 	if((n = readpkt(c, buf, sizeof(buf))) == -1)		sysfatal("http read: %r");	buf[n] = 0;	snprint(svc, sizeof(svc), "# service=git-%s-pack\n", direction);	if(strncmp(svc, buf, n) != 0){		werrstr("invalid initial packet line");		return -1;	}	if(readpkt(c, buf, sizeof(buf)) != 0){		werrstr("protocol garble: expected flushpkt");		return -1;	}	return 0;} static intdialhttp(Conn *c, char *proto, char *host, char *port, char *path, char *direction){	char *geturl, *suff, *hsep, *psep, *isep; 	suff = "";	hsep = "";	psep = "";	isep = "";	if(port && strlen(port) != 0)		hsep = ":";	if(path && path[0] != '/')		psep = "/";	if(path && path[0] && path[strlen(path)-1] != '/')		isep = "/";	memset(c, 0, sizeof(*c));	geturl = smprint("%s://%s%s%s%s%s%s%sinfo/refs?service=git-%s-pack",		proto, host, hsep, port, psep, path, suff, isep, direction);	c->type = ConnHttp;	c->url = smprint("%s://%s%s%s%s%s%s%sgit-%s-pack",		proto, host, hsep, port, psep, path, suff, isep, direction);	c->cfd = webclone(c, geturl);	free(geturl);	if(c->cfd == -1)		return -1;	c->rfd = webopen(c, "body", OREAD);	c->wfd = -1;	if(c->rfd == -1)		return -1;	if(issmarthttp(c, direction) == -1)		return -1;	c->direction = estrdup(direction);	return 0;} static intdialssh(Conn *c, char *host, char *, char *path, char *direction){	int pid, pfd[2];	char cmd[64]; 	if(pipe(pfd) == -1)		sysfatal("unable to open pipe: %r");	pid = fork();	if(pid == -1)		sysfatal("unable to fork");	if(pid == 0){		dup(pfd[0], 0);		dup(pfd[0], 1);		close(pfd[0]);		close(pfd[1]);		snprint(cmd, sizeof(cmd), "git-%s-pack", direction);		dprint(1, "exec ssh '%s' '%s' %s\n", host, cmd, path);		execl("/bin/ssh", "ssh", host, cmd, path, nil);		sysfatal("exec: %r");	}	close(pfd[0]);	c->type = ConnSsh;	c->rfd = pfd[1];	c->wfd = dup(pfd[1], -1);	return 0;} static intgithandshake(Conn *c, char *host, char *path, char *direction){	char *p, *e, cmd[512]; 	p = cmd;	e = cmd + sizeof(cmd);	p = seprint(p, e - 1, "git-%s-pack %s", direction, path);	if(host != nil)		p = seprint(p + 1, e, "host=%s", host);	if(writepkt(c, cmd, p - cmd + 1) == -1){		fprint(2, "failed to write message\n");		closeconn(c);		return -1;	}	return 0;} static intdialhjgit(Conn *c, char *host, char *port, char *path, char *direction, int auth){	char *ds;	int pid, pfd[2]; 	if((ds = netmkaddr(host, "tcp", port)) == nil)		return -1;	if(pipe(pfd) == -1)		sysfatal("unable to open pipe: %r");	pid = fork();	if(pid == -1)		sysfatal("unable to fork");	if(pid == 0){		dup(pfd[0], 0);		dup(pfd[0], 1);		close(pfd[0]);		close(pfd[1]);		dprint(1, "exec tlsclient -a %s\n", ds);		if(auth)			execl("/bin/tlsclient", "tlsclient", "-a", ds, nil);		else			execl("/bin/tlsclient", "tlsclient", ds, nil);		sysfatal("exec: %r");	}	close(pfd[0]);	c->type = ConnGit9;	c->rfd = pfd[1];	c->wfd = dup(pfd[1], -1);	return githandshake(c, host, path, direction);} voidinitconn(Conn *c, int rd, int wr){	c->type = ConnGit;	c->rfd = rd;	c->wfd = wr;} static intdialgit(Conn *c, char *host, char *port, char *path, char *direction){	char *ds;	int fd; 	if((ds = netmkaddr(host, "tcp", port)) == nil)		return -1;	dprint(1, "dial %s git-%s-pack %s\n", ds, direction, path);	fd = dial(ds, nil, nil, nil);	if(fd == -1)		return -1;	c->type = ConnGit;	c->rfd = fd;	c->wfd = dup(fd, -1);	return githandshake(c, host, path, direction);} static intservelocal(Conn *c, char *path, char *direction){	int pid, pfd[2]; 	if(pipe(pfd) == -1)		sysfatal("unable to open pipe: %r");	pid = fork();	if(pid == -1)		sysfatal("unable to fork");	if(pid == 0){		dup(pfd[0], 0);		dup(pfd[0], 1);		close(pfd[0]);		close(pfd[1]);		execl("/bin/git/serve", "serve", "-w", nil);		sysfatal("exec: %r");	}	close(pfd[0]);	c->type = ConnGit;	c->rfd = pfd[1];	c->wfd = dup(pfd[1], -1);	return githandshake(c, nil, path, direction);} static intlocalrepo(char *uri, char *path, int npath){	int fd;	snprint(path, npath, "%s/.git/../", uri);	fd = open(path, OREAD);	if(fd < 0)		return -1;	if(fd2path(fd, path, npath) != 0){		close(fd);		return -1;	}	close(fd);	return 0;} intgitconnect(Conn *c, char *uri, char *direction){	char proto[Nproto], host[Nhost], port[Nport], path[Npath]; 	memset(c, 0, sizeof(Conn));	c->rfd = c->wfd = c->cfd = -1; 	if(localrepo(uri, path, sizeof(path)) == 0)		return servelocal(c, path, direction); 	if(parseuri(uri, proto, host, port, path) == -1){		werrstr("bad uri %s", uri);		return -1;	}	if(strcmp(proto, "ssh") == 0)		return dialssh(c, host, port, path, direction);	else if(strcmp(proto, "git") == 0)		return dialgit(c, host, port, path, direction);

Lines 1–500 of 569.