commit ad3b5b58b098ef83ec0c06619a6cb3272a74bcf5 from: Tobias Heider date: Sun Apr 14 16:00:32 2024 UTC Rename got-http helper to got-fetch-http to match naming scheme. We aren't planning to support sending anyway. ok stsp@ commit - ced242c2ebcf8284b1cb695dc144461cace5313c commit + ad3b5b58b098ef83ec0c06619a6cb3272a74bcf5 blob - acb28a09f9c5661a38c60cdfb5ed247c163acfdf blob + c29d30abe80af6d2006b0da3cd3c63362ff110d7 --- lib/dial.c +++ lib/dial.c @@ -74,9 +74,9 @@ got_dial_apply_unveil(const char *proto) } if (strstr(proto, "http") != NULL) { - if (unveil(GOT_PATH_PROG_HTTP, "x") != 0) { + if (unveil(GOT_PATH_PROG_FETCH_HTTP, "x") != 0) { return got_error_from_errno2("unveil", - GOT_PATH_PROG_HTTP); + GOT_PATH_PROG_FETCH_HTTP); } } @@ -398,7 +398,7 @@ got_dial_http(pid_t *newpid, int *newfd, const char *h if (!port) port = tls ? "443" : "80"; - argv[i++] = GOT_PATH_PROG_HTTP; + argv[i++] = GOT_PATH_PROG_FETCH_HTTP; if (verbosity == -1) argv[i++] = "-q"; else if (verbosity > 0) @@ -427,7 +427,7 @@ got_dial_http(pid_t *newpid, int *newfd, const char *h err(1, "dup2"); if (dup2(pfd[0], 1) == -1) err(1, "dup2"); - if (execv(GOT_PATH_PROG_HTTP, (char *const *)argv) == -1) + if (execv(GOT_PATH_PROG_FETCH_HTTP, (char *const *)argv) == -1) err(1, "execv"); abort(); /* not reached */ } else { blob - b4ff21c12c7352bc4698af27b7a379b4fd469722 blob + 96ea6e4692bb24d2f128702524fea15db694ddb5 --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -48,7 +48,7 @@ #define GOT_PROG_FETCH_PACK got-fetch-pack #define GOT_PROG_INDEX_PACK got-index-pack #define GOT_PROG_SEND_PACK got-send-pack -#define GOT_PROG_HTTP got-http +#define GOT_PROG_FETCH_HTTP got-fetch-http #define GOT_STRINGIFY(x) #x #define GOT_STRINGVAL(x) GOT_STRINGIFY(x) @@ -78,8 +78,8 @@ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_SEND_PACK) #define GOT_PATH_PROG_INDEX_PACK \ GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_INDEX_PACK) -#define GOT_PATH_PROG_HTTP \ - GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_HTTP) +#define GOT_PATH_PROG_FETCH_HTTP \ + GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_HTTP) enum got_imsg_type { /* An error occured while processing a request. */ blob - 4d6cde2c55b52d0441ac5e008f491223a79be253 blob + 0ca99e2d0f60973cd9f9304f89eb72f756cc520b --- lib/privsep.c +++ lib/privsep.c @@ -3559,7 +3559,7 @@ got_privsep_unveil_exec_helpers(void) GOT_PATH_PROG_FETCH_PACK, GOT_PATH_PROG_INDEX_PACK, GOT_PATH_PROG_SEND_PACK, - /* GOT_PATH_PROG_HTTP explicitly excluded */ + /* GOT_PATH_PROG_FETCH_HTTP explicitly excluded */ }; size_t i; blob - e47459f4a5515f987ac01c85ab1e376a32f5382a blob + ccd1c17d48eda14edd65963bbe8d19f20dae0f1c --- libexec/Makefile +++ libexec/Makefile @@ -1,6 +1,6 @@ SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \ got-read-tag got-fetch-pack got-index-pack got-read-pack \ got-read-gitconfig got-read-gotconfig got-send-pack \ - got-read-patch got-http + got-read-patch got-fetch-http .include blob - /dev/null blob + 9af4776950d6855f9ffcf20c80933a62f0d8232f (mode 644) --- /dev/null +++ libexec/got-fetch-http/Makefile @@ -0,0 +1,18 @@ +.PATH:${.CURDIR}/../../lib + +.include "../../got-version.mk" + +PROG= got-fetch-http +SRCS= got-fetch-http.c hash.c error.c inflate.c pollfd.c + +CPPFLAGS= -I${.CURDIR}/../../include -I${.CURDIR}/../../lib + +.if defined(PROFILE) +LDADD= -lutil_p -lz_p -ltls_p +.else +LDADD= -lutil -lz -ltls +.endif + +DPADD= ${LIBZ} ${LIBUTIL} ${LIBTLS} + +.include blob - /dev/null blob + 4ef081a3cb94b0823e86ad6cb1142cabd0226c3b (mode 644) --- /dev/null +++ libexec/got-fetch-http/got-fetch-http.c @@ -0,0 +1,608 @@ +/* + * Copyright (c) 2022 Omar Polo + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "got_version.h" + +#define UPLOAD_PACK_ADV "application/x-git-upload-pack-advertisement" +#define UPLOAD_PACK_REQ "application/x-git-upload-pack-request" +#define UPLOAD_PACK_RES "application/x-git-upload-pack-result" + +#define HTTP_BUFSIZ 4096 +#define GOT_USERAGENT "got/" GOT_VERSION_STR +#define MINIMUM(a, b) ((a) < (b) ? (a) : (b)) +#define hasprfx(str, p) (strncasecmp(str, p, strlen(p)) == 0) + +#define DEBUG_HTTP 1 + +FILE *tmp; + +static int verbose; + +static long long +hexstrtonum(const char *str, long long min, long long max, const char **errstr) +{ + long long lval; + char *cp; + + errno = 0; + lval = strtoll(str, &cp, 16); + if (*str == '\0' || *cp != '\0') { + *errstr = "not a number"; + return 0; + } + if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) || + lval < min || lval > max) { + *errstr = "out of range"; + return 0; + } + + *errstr = NULL; + return lval; +} + +static int +stdio_tls_write(void *arg, const char *buf, int len) +{ + struct tls *ctx = arg; + ssize_t ret; + + do { + ret = tls_write(ctx, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + if (ret == -1) + warn("tls_write: %s", tls_error(ctx)); + + return ret; +} + +static int +stdio_tls_read(void *arg, char *buf, int len) +{ + struct tls *ctx = arg; + ssize_t ret; + + do { + ret = tls_read(ctx, buf, len); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + if (ret == -1) + warn("tls_read: %s", tls_error(ctx)); + + return ret; +} + +static int +stdio_tls_close(void *arg) +{ + struct tls *ctx = arg; + int ret; + + do { + ret = tls_close(ctx); + } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); + + return ret; +} + +static FILE * +dial(int https, const char *host, const char *port) +{ + FILE *fp; + struct tls *ctx; + struct tls_config *conf; + struct addrinfo hints, *res, *res0; + int r, error, saved_errno, fd = -1; + const char *cause = NULL; + + if (https) { + if ((conf = tls_config_new()) == NULL) + errx(1, "failed to create TLS configuration"); + if ((ctx = tls_client()) == NULL) + errx(1, "failed to create TLS client"); + if (tls_configure(ctx, conf) == -1) + errx(1, "TLS configuration failure: %s", + tls_error(ctx)); + tls_config_free(conf); + + if (tls_connect(ctx, host, port) == -1) { + warnx("connect to %s:%s: %s", host, port, + tls_error(ctx)); + tls_close(ctx); + return NULL; + } + do { + r = tls_handshake(ctx); + } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); + fp = funopen(ctx, stdio_tls_read, stdio_tls_write, NULL, + stdio_tls_close); + if (fp == NULL) { + warn("funopen"); + tls_free(ctx); + } + return fp; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + error = getaddrinfo(host, port, &hints, &res0); + if (error) { + warnx("%s", gai_strerror(error)); + return NULL; + } + + for (res = res0; res; res = res->ai_next) { + fd = socket(res->ai_family, res->ai_socktype, + res->ai_protocol); + if (fd == -1) { + cause = "socket"; + continue; + } + + if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) + break; + + cause = "connect"; + saved_errno = errno; + close(fd); + fd = -1; + errno = saved_errno; + } + freeaddrinfo(res0); + + if (fd == -1) { + warn("%s", cause); + return NULL; + } + + if ((fp = fdopen(fd, "r+")) == NULL) { + warn("fdopen"); + close(fd); + } + return fp; +} + +static FILE * +http_open(int https, const char *method, const char *host, const char *port, + const char *path, const char *path_sufx, const char *query, + const char *ctype) +{ + FILE *fp; + const char *chdr = NULL, *te = ""; + char *p, *req; + int r; + + if ((fp = dial(https, host, port)) == NULL) + return NULL; + + if (path_sufx != NULL && *path && path[strlen(path) - 1] == '/') + path_sufx++; /* skip the slash */ + + if (strcmp(method, "POST") == 0) + te = "\r\nTransfer-Encoding: chunked\r\n"; + + if (ctype) + chdr = "Content-Type: "; + + r = asprintf(&p, "%s/%s%s%s", path, path_sufx, + query ? "?" : "", query ? query : ""); + if (r == -1) + err(1, "asprintf"); + + r = asprintf(&req, "%s %s HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: close\r\n" + "User-agent: %s\r\n" + "%s%s%s\r\n", + method, p, host, GOT_USERAGENT, + chdr ? chdr : "", ctype ? ctype : "", te); + free(p); + if (r == -1) + err(1, "asprintf"); + + if (verbose > 0) + fprintf(stderr, "%s: %s", getprogname(), req); + + if (fwrite(req, 1, r, fp) != r) { + free(req); + fclose(fp); + return NULL; + } + free(req); + + return fp; +} + +static int +http_parse_reply(FILE *fp, int *chunked, const char *expected_ctype) +{ + char *cp, *line = NULL; + size_t linesize = 0; + ssize_t linelen; + + *chunked = 0; + + if ((linelen = getline(&line, &linesize, fp)) == -1) { + warn("%s: getline", __func__); + return -1; + } + + if ((cp = strchr(line, '\r')) == NULL) { + warnx("malformed HTTP response"); + goto err; + } + *cp = '\0'; + + if ((cp = strchr(line, ' ')) == NULL) { + warnx("malformed HTTP response"); + goto err; + } + cp++; + + if (strncmp(cp, "200 ", 4) != 0) { + warnx("malformed HTTP response"); + goto err; + } + + while ((linelen = getline(&line, &linesize, fp)) != -1) { + if (line[linelen-1] == '\n') + line[--linelen] = '\0'; + if (linelen > 0 && line[linelen-1] == '\r') + line[--linelen] = '\0'; + + if (*line == '\0') + break; + + if (hasprfx(line, "content-type:")) { + cp = strchr(line, ':') + 1; + cp += strspn(cp, " \t"); + cp[strcspn(cp, " \t")] = '\0'; + if (strcmp(cp, expected_ctype) != 0) { + warnx("server not using the \"smart\" " + "HTTP protocol."); + goto err; + } + } + + if (hasprfx(line, "transfer-encoding:")) { + cp = strchr(line, ':') + 1; + cp += strspn(cp, " \t"); + cp[strcspn(cp, " \t")] = '\0'; + if (strcmp(cp, "chunked") != 0) { + warnx("unknown transfer-encoding"); + goto err; + } + *chunked = 1; + } + } + + free(line); + return 0; + +err: + free(line); + return -1; +} + +static ssize_t +http_read(FILE *fp, int chunked, size_t *chunksz, void *buf, size_t bufsz) +{ + const char *errstr; + char *cp, *line = NULL; + size_t r, linesize = 0; + ssize_t ret = 0, linelen; + + if (!chunked) { + r = fread(buf, 1, bufsz, fp); + if (r == 0 && ferror(fp)) + return -1; +#if DEBUG_HTTP + fwrite(buf, 1, r, stderr); +#endif + return r; + } + + while (bufsz > 0) { + if (*chunksz == 0) { + again: + if ((linelen = getline(&line, &linesize, fp)) == -1) { + if (ferror(fp)) { + warn("%s: getline", __func__); + ret = -1; + } + break; + } + + if ((cp = strchr(line, '\r')) == NULL) { + warnx("invalid HTTP chunk: missing CR"); + ret = -1; + break; + } + *cp = '\0'; + + if (*line == '\0') + goto again; /* was the CRLF after the chunk */ + + *chunksz = hexstrtonum(line, 0, INT_MAX, &errstr); + if (errstr != NULL) { + warnx("invalid HTTP chunk: size is %s (%s)", + errstr, line); + ret = -1; + break; + } + + if (*chunksz == 0) + break; + } + + r = fread(buf, 1, MINIMUM(*chunksz, bufsz), fp); + if (r == 0) { + if (ferror(fp)) + ret = -1; + break; + } + +#if DEBUG_HTTP + if (tmp) + fwrite(buf, 1, r, tmp); + /* fwrite(buf, 1, r, stderr); */ +#endif + ret += r; + buf += r; + bufsz -= r; + *chunksz -= r; + } + + free(line); + return ret; +} + +static void +http_chunk(FILE *fp, const void *buf, size_t len) +{ + /* fprintf(stderr, "> %.*s", (int)len, (char *)buf); */ + + fprintf(fp, "%zx\r\n", len); + if (fwrite(buf, 1, len, fp) != len || + fwrite("\r\n", 1, 2, fp) != 2) + err(1, "%s fwrite", __func__); +} + +static int +get_refs(int https, const char *host, const char *port, const char *path) +{ + char buf[HTTP_BUFSIZ]; + const char *errstr, *sufx = "/info/refs"; + FILE *fp; + size_t skip, chunksz = 0; + ssize_t r; + int chunked; + + fp = http_open(https, "GET", host, port, path, sufx, + "service=git-upload-pack", NULL); + if (fp == NULL) + return -1; + + if (http_parse_reply(fp, &chunked, UPLOAD_PACK_ADV) == -1) { + fclose(fp); + return -1; + } + + /* skip first pack; why git over http is like this? */ + r = http_read(fp, chunked, &chunksz, buf, 4); + if (r <= 0) { + fclose(fp); + return -1; + } + buf[4] = '\0'; + skip = hexstrtonum(buf, 0, INT_MAX, &errstr); + if (errstr != NULL) { + warnx("pktlen is %s", errstr); + fclose(fp); + return -1; + } + + /* TODO: validate it's # service=git-upload-pack\n */ + while (skip > 0) { + r = http_read(fp, chunked, &chunksz, buf, + MINIMUM(skip, sizeof(buf))); + if (r <= 0) { + fclose(fp); + return -1; + } + + skip -= r; + } + + for (;;) { + r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); + if (r == -1) { + fclose(fp); + return -1; + } + + if (r == 0) + break; + + fwrite(buf, 1, r, stdout); + } + + fflush(stdout); + fclose(fp); + return 0; +} + +static int +upload_request(int https, const char *host, const char *port, const char *path, + FILE *in) +{ + const char *errstr; + char buf[HTTP_BUFSIZ]; + FILE *fp; + ssize_t r; + size_t chunksz = 0; + long long t; + int chunked; + + fp = http_open(https, "POST", host, port, path, "/git-upload-pack", + NULL, UPLOAD_PACK_REQ); + if (fp == NULL) + return -1; + + for (;;) { + r = fread(buf, 1, 4, in); + if (r != 4) + goto err; + + buf[4] = '\0'; + t = hexstrtonum(buf, 0, sizeof(buf), &errstr); + if (errstr != NULL) { + warnx("pktline len is %s", errstr); + goto err; + } + + /* no idea why 0000 is not enough. */ + if (t == 0) { + const char *x = "00000009done\n"; + http_chunk(fp, x, strlen(x)); + http_chunk(fp, NULL, 0); + break; + } + + if (t < 6) { + warnx("pktline len is too small"); + goto err; + } + + r = fread(buf + 4, 1, t - 4, in); + if (r != t - 4) + goto err; + + http_chunk(fp, buf, t); + } + + if (http_parse_reply(fp, &chunked, UPLOAD_PACK_RES) == -1) + goto err; + + for (;;) { + r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); + if (r == -1) { + fclose(fp); + return -1; + } + + if (r == 0) + break; + + fwrite(buf, 1, r, stdout); + } + + fclose(fp); + return 0; + +err: + fclose(fp); + return -1; +} + +static __dead void +usage(void) +{ + fprintf(stderr, "usage: %s [-qv] proto host port path\n", + getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct pollfd pfd; + const char *host, *port, *path; + int https = 0; + int ch; +#if 0 + static int attached; + while (!attached) + sleep(1); +#endif + +#if !DEBUG_HTTP || defined(PROFILE) + if (pledge("stdio inet dns", NULL) == -1) + err(1, "pledge"); +#endif + + while ((ch = getopt(argc, argv, "qv")) != -1) { + switch (ch) { + case 'q': + verbose = -1; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc != 4) + usage(); + + https = strcmp(argv[0], "https") == 0; + + host = argv[1]; + port = argv[2]; + path = argv[3]; + + if (get_refs(https, host, port, path) == -1) + errx(1, "failed to get refs"); + +#if DEBUG_HTTP + tmp = fopen("/tmp/pck", "w"); +#endif + + pfd.fd = 0; + pfd.events = POLLIN; + if (poll(&pfd, 1, INFTIM) == -1) + err(1, "poll"); + + if ((ch = fgetc(stdin)) == EOF) + return 0; + + ungetc(ch, stdin); + if (upload_request(https, host, port, path, stdin) == -1) { + fflush(tmp); + errx(1, "failed to upload request"); + } + + return 0; +} blob - ffeb9c647a7d2e1cd1f496e4f748206961a665e4 (mode 644) blob + /dev/null --- libexec/got-http/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -.PATH:${.CURDIR}/../../lib - -.include "../../got-version.mk" - -PROG= got-http -SRCS= got-http.c hash.c error.c inflate.c pollfd.c - -CPPFLAGS= -I${.CURDIR}/../../include -I${.CURDIR}/../../lib - -.if defined(PROFILE) -LDADD= -lutil_p -lz_p -ltls_p -.else -LDADD= -lutil -lz -ltls -.endif - -DPADD= ${LIBZ} ${LIBUTIL} ${LIBTLS} - -.include blob - 4ef081a3cb94b0823e86ad6cb1142cabd0226c3b (mode 644) blob + /dev/null --- libexec/got-http/got-http.c +++ /dev/null @@ -1,608 +0,0 @@ -/* - * Copyright (c) 2022 Omar Polo - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "got_version.h" - -#define UPLOAD_PACK_ADV "application/x-git-upload-pack-advertisement" -#define UPLOAD_PACK_REQ "application/x-git-upload-pack-request" -#define UPLOAD_PACK_RES "application/x-git-upload-pack-result" - -#define HTTP_BUFSIZ 4096 -#define GOT_USERAGENT "got/" GOT_VERSION_STR -#define MINIMUM(a, b) ((a) < (b) ? (a) : (b)) -#define hasprfx(str, p) (strncasecmp(str, p, strlen(p)) == 0) - -#define DEBUG_HTTP 1 - -FILE *tmp; - -static int verbose; - -static long long -hexstrtonum(const char *str, long long min, long long max, const char **errstr) -{ - long long lval; - char *cp; - - errno = 0; - lval = strtoll(str, &cp, 16); - if (*str == '\0' || *cp != '\0') { - *errstr = "not a number"; - return 0; - } - if ((errno == ERANGE && (lval == LONG_MAX || lval == LONG_MIN)) || - lval < min || lval > max) { - *errstr = "out of range"; - return 0; - } - - *errstr = NULL; - return lval; -} - -static int -stdio_tls_write(void *arg, const char *buf, int len) -{ - struct tls *ctx = arg; - ssize_t ret; - - do { - ret = tls_write(ctx, buf, len); - } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); - - if (ret == -1) - warn("tls_write: %s", tls_error(ctx)); - - return ret; -} - -static int -stdio_tls_read(void *arg, char *buf, int len) -{ - struct tls *ctx = arg; - ssize_t ret; - - do { - ret = tls_read(ctx, buf, len); - } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); - - if (ret == -1) - warn("tls_read: %s", tls_error(ctx)); - - return ret; -} - -static int -stdio_tls_close(void *arg) -{ - struct tls *ctx = arg; - int ret; - - do { - ret = tls_close(ctx); - } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); - - return ret; -} - -static FILE * -dial(int https, const char *host, const char *port) -{ - FILE *fp; - struct tls *ctx; - struct tls_config *conf; - struct addrinfo hints, *res, *res0; - int r, error, saved_errno, fd = -1; - const char *cause = NULL; - - if (https) { - if ((conf = tls_config_new()) == NULL) - errx(1, "failed to create TLS configuration"); - if ((ctx = tls_client()) == NULL) - errx(1, "failed to create TLS client"); - if (tls_configure(ctx, conf) == -1) - errx(1, "TLS configuration failure: %s", - tls_error(ctx)); - tls_config_free(conf); - - if (tls_connect(ctx, host, port) == -1) { - warnx("connect to %s:%s: %s", host, port, - tls_error(ctx)); - tls_close(ctx); - return NULL; - } - do { - r = tls_handshake(ctx); - } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); - fp = funopen(ctx, stdio_tls_read, stdio_tls_write, NULL, - stdio_tls_close); - if (fp == NULL) { - warn("funopen"); - tls_free(ctx); - } - return fp; - } - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - error = getaddrinfo(host, port, &hints, &res0); - if (error) { - warnx("%s", gai_strerror(error)); - return NULL; - } - - for (res = res0; res; res = res->ai_next) { - fd = socket(res->ai_family, res->ai_socktype, - res->ai_protocol); - if (fd == -1) { - cause = "socket"; - continue; - } - - if (connect(fd, res->ai_addr, res->ai_addrlen) == 0) - break; - - cause = "connect"; - saved_errno = errno; - close(fd); - fd = -1; - errno = saved_errno; - } - freeaddrinfo(res0); - - if (fd == -1) { - warn("%s", cause); - return NULL; - } - - if ((fp = fdopen(fd, "r+")) == NULL) { - warn("fdopen"); - close(fd); - } - return fp; -} - -static FILE * -http_open(int https, const char *method, const char *host, const char *port, - const char *path, const char *path_sufx, const char *query, - const char *ctype) -{ - FILE *fp; - const char *chdr = NULL, *te = ""; - char *p, *req; - int r; - - if ((fp = dial(https, host, port)) == NULL) - return NULL; - - if (path_sufx != NULL && *path && path[strlen(path) - 1] == '/') - path_sufx++; /* skip the slash */ - - if (strcmp(method, "POST") == 0) - te = "\r\nTransfer-Encoding: chunked\r\n"; - - if (ctype) - chdr = "Content-Type: "; - - r = asprintf(&p, "%s/%s%s%s", path, path_sufx, - query ? "?" : "", query ? query : ""); - if (r == -1) - err(1, "asprintf"); - - r = asprintf(&req, "%s %s HTTP/1.1\r\n" - "Host: %s\r\n" - "Connection: close\r\n" - "User-agent: %s\r\n" - "%s%s%s\r\n", - method, p, host, GOT_USERAGENT, - chdr ? chdr : "", ctype ? ctype : "", te); - free(p); - if (r == -1) - err(1, "asprintf"); - - if (verbose > 0) - fprintf(stderr, "%s: %s", getprogname(), req); - - if (fwrite(req, 1, r, fp) != r) { - free(req); - fclose(fp); - return NULL; - } - free(req); - - return fp; -} - -static int -http_parse_reply(FILE *fp, int *chunked, const char *expected_ctype) -{ - char *cp, *line = NULL; - size_t linesize = 0; - ssize_t linelen; - - *chunked = 0; - - if ((linelen = getline(&line, &linesize, fp)) == -1) { - warn("%s: getline", __func__); - return -1; - } - - if ((cp = strchr(line, '\r')) == NULL) { - warnx("malformed HTTP response"); - goto err; - } - *cp = '\0'; - - if ((cp = strchr(line, ' ')) == NULL) { - warnx("malformed HTTP response"); - goto err; - } - cp++; - - if (strncmp(cp, "200 ", 4) != 0) { - warnx("malformed HTTP response"); - goto err; - } - - while ((linelen = getline(&line, &linesize, fp)) != -1) { - if (line[linelen-1] == '\n') - line[--linelen] = '\0'; - if (linelen > 0 && line[linelen-1] == '\r') - line[--linelen] = '\0'; - - if (*line == '\0') - break; - - if (hasprfx(line, "content-type:")) { - cp = strchr(line, ':') + 1; - cp += strspn(cp, " \t"); - cp[strcspn(cp, " \t")] = '\0'; - if (strcmp(cp, expected_ctype) != 0) { - warnx("server not using the \"smart\" " - "HTTP protocol."); - goto err; - } - } - - if (hasprfx(line, "transfer-encoding:")) { - cp = strchr(line, ':') + 1; - cp += strspn(cp, " \t"); - cp[strcspn(cp, " \t")] = '\0'; - if (strcmp(cp, "chunked") != 0) { - warnx("unknown transfer-encoding"); - goto err; - } - *chunked = 1; - } - } - - free(line); - return 0; - -err: - free(line); - return -1; -} - -static ssize_t -http_read(FILE *fp, int chunked, size_t *chunksz, void *buf, size_t bufsz) -{ - const char *errstr; - char *cp, *line = NULL; - size_t r, linesize = 0; - ssize_t ret = 0, linelen; - - if (!chunked) { - r = fread(buf, 1, bufsz, fp); - if (r == 0 && ferror(fp)) - return -1; -#if DEBUG_HTTP - fwrite(buf, 1, r, stderr); -#endif - return r; - } - - while (bufsz > 0) { - if (*chunksz == 0) { - again: - if ((linelen = getline(&line, &linesize, fp)) == -1) { - if (ferror(fp)) { - warn("%s: getline", __func__); - ret = -1; - } - break; - } - - if ((cp = strchr(line, '\r')) == NULL) { - warnx("invalid HTTP chunk: missing CR"); - ret = -1; - break; - } - *cp = '\0'; - - if (*line == '\0') - goto again; /* was the CRLF after the chunk */ - - *chunksz = hexstrtonum(line, 0, INT_MAX, &errstr); - if (errstr != NULL) { - warnx("invalid HTTP chunk: size is %s (%s)", - errstr, line); - ret = -1; - break; - } - - if (*chunksz == 0) - break; - } - - r = fread(buf, 1, MINIMUM(*chunksz, bufsz), fp); - if (r == 0) { - if (ferror(fp)) - ret = -1; - break; - } - -#if DEBUG_HTTP - if (tmp) - fwrite(buf, 1, r, tmp); - /* fwrite(buf, 1, r, stderr); */ -#endif - ret += r; - buf += r; - bufsz -= r; - *chunksz -= r; - } - - free(line); - return ret; -} - -static void -http_chunk(FILE *fp, const void *buf, size_t len) -{ - /* fprintf(stderr, "> %.*s", (int)len, (char *)buf); */ - - fprintf(fp, "%zx\r\n", len); - if (fwrite(buf, 1, len, fp) != len || - fwrite("\r\n", 1, 2, fp) != 2) - err(1, "%s fwrite", __func__); -} - -static int -get_refs(int https, const char *host, const char *port, const char *path) -{ - char buf[HTTP_BUFSIZ]; - const char *errstr, *sufx = "/info/refs"; - FILE *fp; - size_t skip, chunksz = 0; - ssize_t r; - int chunked; - - fp = http_open(https, "GET", host, port, path, sufx, - "service=git-upload-pack", NULL); - if (fp == NULL) - return -1; - - if (http_parse_reply(fp, &chunked, UPLOAD_PACK_ADV) == -1) { - fclose(fp); - return -1; - } - - /* skip first pack; why git over http is like this? */ - r = http_read(fp, chunked, &chunksz, buf, 4); - if (r <= 0) { - fclose(fp); - return -1; - } - buf[4] = '\0'; - skip = hexstrtonum(buf, 0, INT_MAX, &errstr); - if (errstr != NULL) { - warnx("pktlen is %s", errstr); - fclose(fp); - return -1; - } - - /* TODO: validate it's # service=git-upload-pack\n */ - while (skip > 0) { - r = http_read(fp, chunked, &chunksz, buf, - MINIMUM(skip, sizeof(buf))); - if (r <= 0) { - fclose(fp); - return -1; - } - - skip -= r; - } - - for (;;) { - r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); - if (r == -1) { - fclose(fp); - return -1; - } - - if (r == 0) - break; - - fwrite(buf, 1, r, stdout); - } - - fflush(stdout); - fclose(fp); - return 0; -} - -static int -upload_request(int https, const char *host, const char *port, const char *path, - FILE *in) -{ - const char *errstr; - char buf[HTTP_BUFSIZ]; - FILE *fp; - ssize_t r; - size_t chunksz = 0; - long long t; - int chunked; - - fp = http_open(https, "POST", host, port, path, "/git-upload-pack", - NULL, UPLOAD_PACK_REQ); - if (fp == NULL) - return -1; - - for (;;) { - r = fread(buf, 1, 4, in); - if (r != 4) - goto err; - - buf[4] = '\0'; - t = hexstrtonum(buf, 0, sizeof(buf), &errstr); - if (errstr != NULL) { - warnx("pktline len is %s", errstr); - goto err; - } - - /* no idea why 0000 is not enough. */ - if (t == 0) { - const char *x = "00000009done\n"; - http_chunk(fp, x, strlen(x)); - http_chunk(fp, NULL, 0); - break; - } - - if (t < 6) { - warnx("pktline len is too small"); - goto err; - } - - r = fread(buf + 4, 1, t - 4, in); - if (r != t - 4) - goto err; - - http_chunk(fp, buf, t); - } - - if (http_parse_reply(fp, &chunked, UPLOAD_PACK_RES) == -1) - goto err; - - for (;;) { - r = http_read(fp, chunked, &chunksz, buf, sizeof(buf)); - if (r == -1) { - fclose(fp); - return -1; - } - - if (r == 0) - break; - - fwrite(buf, 1, r, stdout); - } - - fclose(fp); - return 0; - -err: - fclose(fp); - return -1; -} - -static __dead void -usage(void) -{ - fprintf(stderr, "usage: %s [-qv] proto host port path\n", - getprogname()); - exit(1); -} - -int -main(int argc, char **argv) -{ - struct pollfd pfd; - const char *host, *port, *path; - int https = 0; - int ch; -#if 0 - static int attached; - while (!attached) - sleep(1); -#endif - -#if !DEBUG_HTTP || defined(PROFILE) - if (pledge("stdio inet dns", NULL) == -1) - err(1, "pledge"); -#endif - - while ((ch = getopt(argc, argv, "qv")) != -1) { - switch (ch) { - case 'q': - verbose = -1; - break; - case 'v': - verbose++; - break; - default: - usage(); - } - } - argc -= optind; - argv += optind; - - if (argc != 4) - usage(); - - https = strcmp(argv[0], "https") == 0; - - host = argv[1]; - port = argv[2]; - path = argv[3]; - - if (get_refs(https, host, port, path) == -1) - errx(1, "failed to get refs"); - -#if DEBUG_HTTP - tmp = fopen("/tmp/pck", "w"); -#endif - - pfd.fd = 0; - pfd.events = POLLIN; - if (poll(&pfd, 1, INFTIM) == -1) - err(1, "poll"); - - if ((ch = fgetc(stdin)) == EOF) - return 0; - - ungetc(ch, stdin); - if (upload_request(https, host, port, path, stdin) == -1) { - fflush(tmp); - errx(1, "failed to upload request"); - } - - return 0; -}