X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-fetchurl.c;h=1b337244421df9f0f74b7aa5a2d99186670ecd60;hb=62f6ff407bc4f2cf03d1fa7cf3dc9a3f4026624a;hp=15fc36ab77796639b033d5eae8edd9dc0ecf21b5;hpb=66bc25938679f1d6a1d1200f329093d82a5e99b4;p=zpackage diff --git a/zpm-fetchurl.c b/zpm-fetchurl.c index 15fc36a..1b33724 100644 --- a/zpm-fetchurl.c +++ b/zpm-fetchurl.c @@ -15,6 +15,7 @@ #include #include "tlse.h" +#define MARK fprintf(stderr, "%s %s:%d\n", __FILE__, __func__, __LINE__) struct tls_uri { char *scheme; @@ -30,21 +31,221 @@ void tls_free_uri(struct tls_uri *); int open_tcp_connection(char *host, int port); -int verify(struct TLSContext *context, struct TLSCertificate **chain, int len) { +/* if trustpolicy is 0, we just accept anything */ +int verify_trust(struct TLSContext *context, struct TLSCertificate **chain, int + len) { + /* suppress unused */ + if (context || chain || len) { + return 0; + } + + return 0; +} + +static char hexchars[] = "0123456789abcdefABCDEF"; + +static void hex(char *dst, uint8_t *src, size_t len) { + while (len--) { + dst[0] = hexchars[(src[0]>>4)&0xf]; + dst[1] = hexchars[src[0]&0xf]; + dst+=2; + src++; + } +} + +#if 0 +static void hexbin(uint8_t *dst, unsigned char *src, size_t len) { + size_t i; + int x; + + for (i=0; ierror == 0) { + loc = memchr(b->buffer, '\n', b->len); + if (loc) { + *size = loc - b->buffer + 1; + return b->buffer; + } else { + rv = read(fd, buf, sizeof buf); + if (rv == -1) { + return 0; + } + if (rv == 0) { + break; + } + tls_buffer_append(b, buf, rv); + } + } + if (rv == 0) { + *size = b->len; + return b->buffer; + } + return 0; +} + +/* + * We use a trust on first use policy. The trust DB is a simple + * file in /var/lib/zpm/known_hosts, or ~/.zpm/known_hosts, or ZPM_KNOWNHOSTS. + * if -k is given, no verification is done + */ +int verify_first(struct TLSContext *context, struct TLSCertificate **chain, int + certs) { + int err; + char *trustfile, *homedir = 0, *host, *fp; + unsigned char certhash[65]; + int trustdb; + struct tls_buffer tbuf; + + char *line = 0; + size_t len = 0; + + if (certs == 0 || chain == 0) { + return 1; + } + + err = tls_certificate_is_valid(chain[0]); + if (err) { + return err; + } + + if (context->sni) { + err = tls_certificate_valid_subject(chain[0], context->sni); + if (err) { + return err; + } + } + + hex(certhash, chain[0]->fp, 32); + certhash[64] = 0; + + trustfile = getenv("ZPM_KNOWNHOSTS"); + if (!trustfile) { + if (geteuid() == 0) { + trustfile = "/var/lib/zpm/known_hosts"; + } else { + /* we could do this with a series of + * openat() calls instead of building + * up a string + */ + trustfile = getenv("HOME"); + if (!trustfile) { + fprintf(stderr, "home = %s\n", trustfile); + return 1; + } + len = snprintf(homedir, 0, "%s/.zpm/known_hosts", trustfile); + homedir = malloc(len+1); + if (!homedir) { + return 1; + } + len = snprintf(homedir, len+1, "%s/.zpm/known_hosts", trustfile); + trustfile = homedir; + } + } + /* cert is valid on its face, so check against the trust db */ + trustdb = open(trustfile, O_RDWR|O_CREAT, 0600); + if (trustdb == -1) { + fprintf(stderr, "cannot open trustdb %s: %s\n", trustfile, strerror(errno)); + if (homedir) { + free(homedir); + } + return 1; + } + + if (homedir) { + free(homedir); + } + + len = 0; + tls_buffer_init(&tbuf, 128); + do { + char *off; + + tls_buffer_shift(&tbuf, len); + line = my_getline(&tbuf, trustdb, &len); + + if (!line || !len) { + break; + } + + fp = line; + while (isspace(*fp)) { + fp++; + } + if (*fp == '#') { + continue; + } + off = strchr(line, ':'); + if (!off) { + continue; + } + host = off + 1; + *off = 0; + if (line[len-1] == '\n') { + line[len-1] = 0; + } + + if (strlen(fp) != 64) { + continue; + } + + if (len && line[len-1] == '\n') { + line[len-1] = 0; + } + + if (strcmp(context->sni, host) != 0) { + continue; + } + + int match = (memcmp(certhash, fp, 64) == 0); + + close(trustdb); + tls_buffer_free(&tbuf); + return match ? no_error : bad_certificate; + } while (!tbuf.error); + + /* got here, so we should be at EOF, so add this host to trust db */ + lseek(trustdb, 0, SEEK_END); + + /* re-use the buffer so we only do one write */ + /* ignore errors, the cert is fine, we just can't update + * the trustdb if there's errors here + */ + tbuf.len = 0; + tls_buffer_append(&tbuf, certhash, 64); + tls_buffer_append_byte(&tbuf, ':'); + tls_buffer_append(&tbuf, context->sni, strlen(context->sni)); + tls_buffer_append_byte(&tbuf, '\n'); + write(trustdb, tbuf.buffer, tbuf.len); + close(trustdb); + tls_buffer_free(&tbuf); + + return no_error; +} + +int verify_roots(struct TLSContext *context, struct TLSCertificate **chain, int + len) { int i, err; if (chain) { for (i = 0; i < len; i++) { struct TLSCertificate *certificate = chain[i]; - // check validity date err = tls_certificate_is_valid(certificate); - if (err) + if (err) { return err; - // check certificate in certificate->bytes of length certificate->len - // the certificate is in ASN.1 DER format + } } } - // check if chain is valid + err = tls_certificate_chain_is_valid(chain, len); if (err) { return err; @@ -92,11 +293,9 @@ int month(char *m) { /* Wed, 06 Feb 2019 10:06:05 GMT */ time_t parse_date(char *d) { - //static const char format[] = "%a, %d %b %Y %H:%M:%S %Z"; // rfc 1123 struct tm tm = { 0 }; int rv; - //char *data = "Tue, 13 Dec 2011 16:08:21 GMT"; int h, m, s, dom, Y; char M[4]; rv = sscanf(d, "%*[a-zA-Z,] %d %s %d %d:%d:%d", &dom, M, &Y, &h, &m, &s); @@ -237,6 +436,16 @@ void append_header(struct tls_buffer *buf, char *header, char *val) { tls_buffer_append(buf, "\r\n", 2); } +void append_timeheader(struct tls_buffer *buf, char *header, time_t ts) { + char timestr[80]; + struct tm *tm; + + tm = gmtime(&ts); + + strftime(timestr, sizeof timestr, "%a, %d %b %Y %H:%M:%S GMT", tm); + append_header(buf, header, timestr); +} + static void pdots(int len, int ch, unsigned long was, unsigned long now, unsigned long total) { was = len * was / total; @@ -249,6 +458,56 @@ static void pdots(int len, int ch, unsigned long was, unsigned long now, } } +static void fake_header(struct io *io, int fd) { + struct stat st; + int code = 200, rv; + char *message, codestr[5], length[32]; + struct tls_buffer *hdr = &io->response; + + if (fd == -1) { + switch (errno) { + case EACCES: code = 403; break; + case ENOENT: code = 404; break; + default: code = 500; break; + } + } else { + rv = fstat(fd, &st); + if (rv == -1) { + code = 500; + } + } + + if (io->last_modified >= st.st_mtime) { + code = 304; + } + + switch (code) { + case 200: message = "OK"; break; + case 304: message = "Not Modified"; break; + case 403: message = "Forbidden"; break; + case 404: message = "Not Found"; break; + case 500: message = "Internal Server Error"; break; + default: break; + } + sprintf(codestr, "%0.3d ", code); + tls_buffer_append(hdr, "HTTP/1.1 ", 9); + tls_buffer_append(hdr, codestr, 4); + tls_buffer_append_str(hdr, message); + tls_buffer_append(hdr, "\r\n", 2); + + append_timeheader(hdr, "Date", time(NULL)); + append_header(hdr, "Server", "zpm-fetchurl/0.9"); + if (code < 400) { + append_timeheader(hdr, "Last-Modified", st.st_mtime); + sprintf(length, "%zu", st.st_size); + append_header(hdr, "Content-Length", length); + } + + append_header(hdr, "Connection", "close"); + append_header(hdr, "Content-Type", "application/octet-stream"); + tls_buffer_append(hdr, "\r\n", 2); +} + int main(int ac, char *av[]) { int sockfd, port = -1, rv; ssize_t ret; @@ -277,13 +536,16 @@ int main(int ac, char *av[]) { size_t header_len; char *url = 0; int redirs = 0, redirlimit = 50, printstatus = 0; + int verifypolicy = 1; ltc_mp = tfm_desc; - while ((option = getopt(ac, av, "o:rIfz:#R:S")) != -1) { + while ((option = getopt(ac, av, "o:rIfz:#R:SkK")) != -1) { switch (option) { case 'o': outfile = optarg; break; case 'S': printstatus = 1; head = 1; break; + case 'k': verifypolicy = 0; break; + case 'K': verifypolicy = 2; break; case 'I': head = 1; case 'r': raw = 1; break; case 'f': failsilent = 1; break; @@ -301,6 +563,7 @@ int main(int ac, char *av[]) { exit(EXIT_FAILURE); } + io.last_modified = 0; if (lmfile) { struct stat st; int rv; @@ -313,6 +576,7 @@ int main(int ac, char *av[]) { exit(EXIT_FAILURE); } ts = st.st_mtime; + io.last_modified = ts; mtime = gmtime(&ts); strftime(lmtime, sizeof lmtime, "%a, %d %b %Y %H:%M:%S GMT", mtime); } @@ -352,8 +616,7 @@ int main(int ac, char *av[]) { tls_buffer_append(&request, "GET ", 4); } tls_buffer_append(&request, uri.path, strlen(uri.path)); - tls_buffer_append(&request, " HTTP/1.1", 9); - tls_buffer_append(&request, "\r\n", 2); + tls_buffer_append(&request, " HTTP/1.1\r\n", 11); append_header(&request, "Host", host); append_header(&request, "Connection", "close"); @@ -361,25 +624,34 @@ int main(int ac, char *av[]) { append_header(&request, "If-Modified-Since", lmtime); } tls_buffer_append(&request, "\r\n", 2); - //fprintf(stderr, "msg =\n%.*s", (int)request.len, request.buffer); if (!strcmp(uri.scheme, "https")) { use_tls = 1; clientssl = tls_create_context(TLS_CLIENT, TLS_V12); - rv = tls_load_root_file(clientssl, "root.pem"); - if (rv == -1) { - fprintf(stderr, "Error loading root certs\n"); - return 1; - } - /* optionally, we can set a certificate validation * callback function if set_verify is not called, and * root ca is set, `tls_default_verify` will be used * (does exactly what `verify` does in this example) */ - tls_set_verify(clientssl, verify); + if (verifypolicy == 2) { + char *cert_path = 0; + cert_path = getenv("ZPM_CERTFILE"); + if (!cert_path) { + cert_path = "/var/lib/zpm/roots.pem"; + } + rv = tls_load_root_file(clientssl, cert_path); + if (rv == -1) { + fprintf(stderr, "Error loading root certs\n"); + return 1; + } + tls_set_verify(clientssl, verify_roots); + } else if (verifypolicy == 1) { + tls_set_verify(clientssl, verify_first); + } else { + tls_set_verify(clientssl, verify_trust); + } if (!clientssl) { fprintf(stderr, "Error initializing client context\n"); @@ -388,41 +660,49 @@ int main(int ac, char *av[]) { tls_sni_set(clientssl, uri.host); clientssl->sync = 1; io.tls = clientssl; - } - - sockfd = open_tcp_connection(host, port); - io.socket = sockfd; - - if (sockfd < 0) { - exit(EXIT_FAILURE); - } - - if (use_tls) { + sockfd = open_tcp_connection(host, port); + if (sockfd < 0) { + perror("can't open connection"); + exit(EXIT_FAILURE); + } tls_set_fd(clientssl, sockfd); - if ((rv = tls_connect(clientssl)) != 1) { fprintf(stderr, "Handshake Error %i\n", rv); - return -5; + return 1; } - ret = tls_write(clientssl, request.buffer, request.len); + } else if (!strcmp(uri.scheme, "http")) { + sockfd = open_tcp_connection(host, port); + if (sockfd < 0) { + perror("can't open connection"); + exit(EXIT_FAILURE); + } + ret = write(sockfd, request.buffer, request.len); + } else if (!strcmp(uri.scheme, "file")) { + sockfd = open(uri.path, O_RDONLY); + fake_header(&io, sockfd); + ret = 0; } else { - ret = write(io.socket, request.buffer, request.len); + fprintf(stderr, "scheme %s unknown\n", uri.scheme); + exit(EXIT_FAILURE); } - if (ret < 0) { - fprintf(stderr, "write error %zd\n", ret); - return -6; + if (ret == -1) { + fprintf(stderr, "unable to write http request: %s\n", strerror(errno)); + exit(EXIT_FAILURE); } + io.socket = sockfd; + do { - ret = fill_buffer(&io); - if (ret < 0) { - break; + if (io.response.len >= 4) { + eoh = strstr(io.response.buffer, "\r\n\r\n"); } - eoh = strstr(io.response.buffer, "\r\n\r\n"); - if (ret == 0) { - break; + if (!eoh) { + ret = fill_buffer(&io); + if (ret <= 0) { + break; + } } } while (!eoh); @@ -451,10 +731,6 @@ int main(int ac, char *av[]) { break; } - if (io.status_code != 200) { - break; - } - if (!raw) { tls_buffer_shift(&io.response, header_len); } @@ -486,6 +762,9 @@ int main(int ac, char *av[]) { } total += ret; } + if (head) { + break; + } ret = fill_buffer(&io); } while (ret > 0);