ref: 96601790b2847b87bbab95382ed7010fa230cbbc
dir: /sys/src/cmd/git/proto.c/
#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, Nrepo = 64, Nbranch = 32, }; void tracepkt(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); } int readpkt(Conn *c, char *buf, int nbuf) { char len[5]; char *e; int n; if(readn(c->rfd, len, 4) != 4) sysfatal("pktline: short read from transport"); 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; buf[n] = 0; tracepkt(1, "=r=>", buf, n); return n; } int writepkt(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; } int fmtpkt(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; } int flushpkt(Conn *c) { dprint(1, "<=w= 0000\n"); return write(c->wfd, "0000", 4); } static void grab(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 int parseuri(char *uri, char *proto, char *host, char *port, char *path, char *repo) { char *s, *p, *q; int n, 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); if((q = strrchr(p, '/')) != nil) p = q + 1; if(strlen(p) == 0){ werrstr("missing repository in uri"); return -1; } n = strlen(p); if(hassuffix(p, ".git")) n -= 4; grab(repo, Nrepo, p, p + n); return 0; } static int webclone(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 int webopen(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 int issmarthttp(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 int dialhttp(Conn *c, char *host, char *port, char *path, char *direction) { char *geturl, *suff, *hsep, *psep; suff = ""; hsep = ""; psep = ""; if(port && strlen(port) != 0) hsep = ":"; if(path && path[0] != '/') psep = "/"; memset(c, 0, sizeof(*c)); geturl = smprint("https://%s%s%s%s%s%s/info/refs?service=git-%s-pack", host, hsep, port, psep, path, suff, direction); c->type = ConnHttp; c->url = smprint("https://%s%s%s%s%s%s/git-%s-pack", host, hsep, port, psep, path, suff, 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 int dialssh(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){ close(pfd[1]); dup(pfd[0], 0); dup(pfd[0], 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 int githandshake(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 int dialhjgit(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){ close(pfd[1]); dup(pfd[0], 0); dup(pfd[0], 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); } void initconn(Conn *c, int rd, int wr) { c->type = ConnGit; c->rfd = rd; c->wfd = wr; } static int dialgit(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 int servelocal(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){ close(pfd[1]); dup(pfd[0], 0); dup(pfd[0], 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 int localrepo(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; } int gitconnect(Conn *c, char *uri, char *direction) { char proto[Nproto], host[Nhost], port[Nport]; char repo[Nrepo], 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, repo) == -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); else if(strcmp(proto, "hjgit") == 0) return dialhjgit(c, host, port, path, direction, 1); else if(strcmp(proto, "gits") == 0) return dialhjgit(c, host, port, path, direction, 0); else if(strcmp(proto, "http") == 0 || strcmp(proto, "https") == 0) return dialhttp(c, host, port, path, direction); werrstr("unknown protocol %s", proto); return -1; } int writephase(Conn *c) { char hdr[128]; int n; dprint(1, "start write phase\n"); if(c->type != ConnHttp) return 0; if(c->wfd != -1) close(c->wfd); if(c->cfd != -1) close(c->cfd); if((c->cfd = webclone(c, c->url)) == -1) return -1; n = snprint(hdr, sizeof(hdr), Contenthdr, c->direction); if(write(c->cfd, hdr, n) == -1) return -1; n = snprint(hdr, sizeof(hdr), Accepthdr, c->direction); if(write(c->cfd, hdr, n) == -1) return -1; if((c->wfd = webopen(c, "postbody", OWRITE)) == -1) return -1; c->rfd = -1; return 0; } int readphase(Conn *c) { dprint(1, "start read phase\n"); if(c->type != ConnHttp) return 0; if(close(c->wfd) == -1) return -1; if((c->rfd = webopen(c, "body", OREAD)) == -1) return -1; c->wfd = -1; return 0; } void closeconn(Conn *c) { close(c->rfd); close(c->wfd); switch(c->type){ case ConnGit: break; case ConnGit9: case ConnSsh: free(wait()); break; case ConnHttp: close(c->cfd); break; } }