1 #define _POSIX_C_SOURCE 200809L
5 #include <sys/socket.h>
6 #include <netinet/in.h>
18 #define MARK fprintf(stderr, "%s %s:%d\n", __FILE__, __func__, __LINE__)
29 int tls_parse_uri(char *, struct tls_uri *);
30 void tls_free_uri(struct tls_uri *);
32 int open_tcp_connection(char *host, int port);
34 /* if trustpolicy is 0, we just accept anything */
35 int verify_trust(struct TLSContext *context, struct TLSCertificate **chain, int
38 if (context || chain || len) {
45 static char hexchars[] = "0123456789abcdefABCDEF";
47 static void hex(char *dst, uint8_t *src, size_t len) {
49 dst[0] = hexchars[(src[0]>>4)&0xf];
50 dst[1] = hexchars[src[0]&0xf];
57 static void hexbin(uint8_t *dst, unsigned char *src, size_t len) {
61 for (i=0; i<len; i+=2) {
62 sscanf((const char *)src+i, "%02x", &x);
68 char *my_getline(struct tls_buffer *b, int fd, size_t *size) {
73 while (b->error == 0) {
74 loc = memchr(b->buffer, '\n', b->len);
76 *size = loc - b->buffer + 1;
79 rv = read(fd, buf, sizeof buf);
86 tls_buffer_append(b, buf, rv);
97 * We use a trust on first use policy. The trust DB is a simple
98 * file in /var/lib/zpm/known_hosts, or ~/.zpm/known_hosts, or ZPM_KNOWNHOSTS.
99 * if -k is given, no verification is done
101 int verify_first(struct TLSContext *context, struct TLSCertificate **chain, int
104 char *trustfile, *homedir = 0, *host, *fp;
105 unsigned char certhash[65];
107 struct tls_buffer tbuf;
112 if (certs == 0 || chain == 0) {
116 err = tls_certificate_is_valid(chain[0]);
122 err = tls_certificate_valid_subject(chain[0], context->sni);
128 hex(certhash, chain[0]->fp, 32);
131 trustfile = getenv("ZPM_KNOWNHOSTS");
133 if (geteuid() == 0) {
134 trustfile = "/var/lib/zpm/known_hosts";
136 /* we could do this with a series of
137 * openat() calls instead of building
140 trustfile = getenv("HOME");
142 fprintf(stderr, "home = %s\n", trustfile);
145 len = snprintf(homedir, 0, "%s/.zpm/known_hosts", trustfile);
146 homedir = malloc(len+1);
150 len = snprintf(homedir, len+1, "%s/.zpm/known_hosts", trustfile);
154 /* cert is valid on its face, so check against the trust db */
155 trustdb = open(trustfile, O_RDWR|O_CREAT, 0600);
157 fprintf(stderr, "cannot open trustdb %s: %s\n", trustfile, strerror(errno));
169 tls_buffer_init(&tbuf, 128);
173 tls_buffer_shift(&tbuf, len);
174 line = my_getline(&tbuf, trustdb, &len);
181 while (isspace(*fp)) {
187 off = strchr(line, ':');
193 if (line[len-1] == '\n') {
197 if (strlen(fp) != 64) {
201 if (len && line[len-1] == '\n') {
205 if (strcmp(context->sni, host) != 0) {
209 int match = (memcmp(certhash, fp, 64) == 0);
212 tls_buffer_free(&tbuf);
213 return match ? no_error : bad_certificate;
214 } while (!tbuf.error);
216 /* got here, so we should be at EOF, so add this host to trust db */
217 lseek(trustdb, 0, SEEK_END);
219 /* re-use the buffer so we only do one write */
220 /* ignore errors, the cert is fine, we just can't update
221 * the trustdb if there's errors here
224 tls_buffer_append(&tbuf, certhash, 64);
225 tls_buffer_append_byte(&tbuf, ':');
226 tls_buffer_append(&tbuf, context->sni, strlen(context->sni));
227 tls_buffer_append_byte(&tbuf, '\n');
228 write(trustdb, tbuf.buffer, tbuf.len);
230 tls_buffer_free(&tbuf);
235 int verify_roots(struct TLSContext *context, struct TLSCertificate **chain, int
240 for (i = 0; i < len; i++) {
241 struct TLSCertificate *certificate = chain[i];
242 err = tls_certificate_is_valid(certificate);
249 err = tls_certificate_chain_is_valid(chain, len);
254 if (len > 0 && context->sni) {
255 err = tls_certificate_valid_subject(chain[0], context->sni);
261 /* Perform certificate validation against ROOT CA */
262 err = tls_certificate_chain_is_valid_root(context, chain, len);
271 struct tls_buffer response;
272 struct TLSContext *tls;
275 time_t last_modified;
277 size_t content_length;
282 char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul",
283 "Aug", "Sep", "Oct", "Nov", "Dec" };
286 for (i=0; i < 12; i++) {
287 if (!strncasecmp(m, months[i], 3)) {
294 /* Wed, 06 Feb 2019 10:06:05 GMT */
295 time_t parse_date(char *d) {
296 struct tm tm = { 0 };
301 rv = sscanf(d, "%*[a-zA-Z,] %d %s %d %d:%d:%d", &dom, M, &Y, &h, &m, &s);
303 tm.tm_year = Y - 1900;
307 tm.tm_mon = month(M)-1;
315 char *find_header(struct io *io, char *header, size_t *len) {
321 hlen = strlen(header);
322 eoh = strstr(io->response.buffer, "\r\n\r\n");
326 soh = io->response.buffer;
328 soh = strstr(soh, "\r\n");
333 if (!memcmp(soh, header, hlen)) {
341 eoh = strstr(soh, "\r\n");
343 while (soh < eoh && isspace(*soh)) {
350 void parse_header(struct io *io) {
351 char *s = io->response.buffer;
356 while (!isspace(*s)) {
359 while (isspace(*s)) {
362 code = strtol(s, 0, 10);
363 io->status_code = code;
365 hval = find_header(io, "Date:", &hlen);
368 io->date = parse_date(hval);
371 hval = find_header(io, "Last-Modified:", &hlen);
374 io->last_modified = parse_date(hval);
378 hval = find_header(io, "Content-Length:", &hlen);
381 io->content_length = strtoul(hval, 0, 10);
390 hval = find_header(io, "Location:", &hlen);
392 io->redirect = strndup(hval, hlen);
401 ssize_t fill_buffer(struct io *io) {
402 unsigned char buffer[4096];
406 ret = tls_read(io->tls, buffer, sizeof buffer);
408 ret = read(io->socket, buffer, sizeof buffer);
412 tls_buffer_append(&io->response, buffer, ret);
419 char *nextline(struct io *io) {
422 eol = memchr(io->response.buffer, '\n', io.response.size);
425 eol = memchr(io->response.buffer, '\n', io.response.size);
432 void append_header(struct tls_buffer *buf, char *header, char *val) {
433 tls_buffer_append(buf, header, strlen(header));
434 tls_buffer_append(buf, ": ", 2);
435 tls_buffer_append(buf, val, strlen(val));
436 tls_buffer_append(buf, "\r\n", 2);
439 void append_timeheader(struct tls_buffer *buf, char *header, time_t ts) {
445 strftime(timestr, sizeof timestr, "%a, %d %b %Y %H:%M:%S GMT", tm);
446 append_header(buf, header, timestr);
449 static void pdots(int len, int ch, unsigned long was, unsigned long now,
450 unsigned long total) {
451 was = len * was / total;
455 now = len * now / total;
456 while (was++ < now) {
461 static void fake_header(struct io *io, int fd) {
464 char *message, codestr[5], length[32];
465 struct tls_buffer *hdr = &io->response;
469 case EACCES: code = 403; break;
470 case ENOENT: code = 404; break;
471 default: code = 500; break;
480 if (io->last_modified >= st.st_mtime) {
485 case 200: message = "OK"; break;
486 case 304: message = "Not Modified"; break;
487 case 403: message = "Forbidden"; break;
488 case 404: message = "Not Found"; break;
489 case 500: message = "Internal Server Error"; break;
492 sprintf(codestr, "%0.3d ", code);
493 tls_buffer_append(hdr, "HTTP/1.1 ", 9);
494 tls_buffer_append(hdr, codestr, 4);
495 tls_buffer_append_str(hdr, message);
496 tls_buffer_append(hdr, "\r\n", 2);
498 append_timeheader(hdr, "Date", time(NULL));
499 append_header(hdr, "Server", "zpm-fetchurl/0.9");
501 append_timeheader(hdr, "Last-Modified", st.st_mtime);
502 sprintf(length, "%zu", st.st_size);
503 append_header(hdr, "Content-Length", length);
506 append_header(hdr, "Connection", "close");
507 append_header(hdr, "Content-Type", "application/octet-stream");
508 tls_buffer_append(hdr, "\r\n", 2);
511 char *pathlast(char *path) {
519 while (*path == '/') {
526 while (*path && *path != '/') {
530 while (*path == '/') {
539 return strndup(last, len);
542 static time_t file_mtime(char *path) {
546 rv = stat(path, &st);
548 if (errno == ENOENT) {
551 perror("stat failed:");
558 int main(int ac, char *av[]) {
559 int sockfd, port = -1, rv;
563 char msg[] = "GET %s HTTP/1.1\r\nHost: %s:%i\r\nConnection: close\r\n\r\n";
564 char msg2[] = "GET %s HTTP/1.1\r\nHost: %s:%i\r\nLast-Modified: %s\r\nConnection: close\r\n\r\n";
565 char msg_buffer[1024];
569 struct tls_uri uri = { 0 };
571 int raw = 0, head = 0;
572 int out = 1; /* output file descriptor */
574 struct io io = { {0}, 0, -1, 0, 0, 0, 0, 0 };
575 struct TLSContext *clientssl = 0;
579 struct tls_buffer request;
585 int redirs = 0, redirlimit = 50, printstatus = 0;
586 int verifypolicy = 1, calcoutfile = 0, ifnewer = 0;
590 while ((option = getopt(ac, av, "o:OrIfz:np#R:SkK")) != -1) {
592 case 'o': outfile = optarg; break;
593 case 'O': calcoutfile = 1; break;
594 case 'S': printstatus = 1; head = 1; break;
595 case 'k': verifypolicy = 0; break;
596 case 'K': verifypolicy = 2; break;
598 case 'r': raw = 1; break;
599 case 'f': failsilent = 1; break;
600 case 'z': lmfile = optarg; break;
601 case 'n': ifnewer = 1; break;
602 case 'R': redirlimit = strtol(optarg, 0, 10); break;
604 case '#': progressbar = 1; break;
612 fprintf(stderr, "Usage: %s uri\n", av[0]);
616 url = strdup(av[optind]);
621 io.last_modified = 0;
623 if (calcoutfile && !outfile) {
624 tls_parse_uri(url, &uri);
625 outfile = pathlast(uri.path);
626 /* outfile leaks memory here, so if this
627 * were turned into a library function,
628 * we'd need to track it
631 fprintf(stderr, "unable to determine outfile\n");
636 if (ifnewer && outfile && !lmfile) {
644 ts = file_mtime(lmfile);
648 } else if (ts != 0) {
649 io.last_modified = ts;
651 strftime(lmtime, sizeof lmtime, "%a, %d %b %Y %H:%M:%S GMT", mtime);
657 signal(SIGPIPE, SIG_IGN);
659 tls_buffer_init(&io.response, 0);
660 tls_buffer_init(&request, 128);
662 while (redirs++ <= redirlimit) {
668 tls_parse_uri(url, &uri);
670 port = atoi(uri.port);
673 /* construct request */
675 tls_buffer_append(&request, "HEAD ", 5);
677 tls_buffer_append(&request, "GET ", 4);
679 tls_buffer_append(&request, uri.path, strlen(uri.path));
680 tls_buffer_append(&request, " HTTP/1.1\r\n", 11);
682 append_header(&request, "Host", host);
683 append_header(&request, "Connection", "close");
685 append_header(&request, "If-Modified-Since", lmtime);
687 tls_buffer_append(&request, "\r\n", 2);
689 if (!strcmp(uri.scheme, "https")) {
692 clientssl = tls_create_context(TLS_CLIENT, TLS_V12);
694 /* optionally, we can set a certificate validation
695 * callback function if set_verify is not called, and
696 * root ca is set, `tls_default_verify` will be used
697 * (does exactly what `verify` does in this example)
699 if (verifypolicy == 2) {
701 cert_path = getenv("ZPM_CERTFILE");
703 cert_path = "/var/lib/zpm/roots.pem";
705 rv = tls_load_root_file(clientssl, cert_path);
707 fprintf(stderr, "Error loading root certs\n");
710 tls_set_verify(clientssl, verify_roots);
711 } else if (verifypolicy == 1) {
712 tls_set_verify(clientssl, verify_first);
714 tls_set_verify(clientssl, verify_trust);
718 fprintf(stderr, "Error initializing client context\n");
721 tls_sni_set(clientssl, uri.host);
724 sockfd = open_tcp_connection(host, port);
726 perror("can't open connection");
729 tls_set_fd(clientssl, sockfd);
730 if ((rv = tls_connect(clientssl)) != 1) {
731 fprintf(stderr, "Handshake Error %i\n", rv);
734 ret = tls_write(clientssl, request.buffer, request.len);
735 } else if (!strcmp(uri.scheme, "http")) {
736 sockfd = open_tcp_connection(host, port);
738 perror("can't open connection");
741 ret = write(sockfd, request.buffer, request.len);
742 } else if (!strcmp(uri.scheme, "file")) {
743 sockfd = open(uri.path, O_RDONLY);
744 fake_header(&io, sockfd);
747 fprintf(stderr, "scheme %s unknown\n", uri.scheme);
752 fprintf(stderr, "unable to write http request: %s\n", strerror(errno));
759 if (io.response.len >= 4) {
760 eoh = strstr(io.response.buffer, "\r\n\r\n");
763 ret = fill_buffer(&io);
771 /* never got (complet) header */
772 fprintf(stderr, "incomplete response to %s\n", av[optind]);
776 header_len = (size_t)(eoh - io.response.buffer) + 4;
779 switch (io.status_code) {
787 url = strdup(io.redirect);
794 printf("%d\n", io.status_code);
799 tls_buffer_shift(&io.response, header_len);
802 io.response.len -= 2;
806 if (io.content_length) {
807 fprintf(stderr, "(%lu) ", io.content_length);
812 out = open(outfile, O_WRONLY|O_CREAT, 0600);
814 perror("can't open output file:");
820 write(out, io.response.buffer, io.response.len);
821 ret = io.response.len;
825 if (io.content_length) {
826 pdots(50, '.', total, total+ret,
829 int old = total / 1000000;
830 int new = (total+ret)/1000000;
840 ret = fill_buffer(&io);
844 fprintf(stderr, "%s read error %zd\n", uri.scheme, ret);
846 struct timespec ts[2];
847 ts[0].tv_sec = 0; ts[0].tv_nsec = UTIME_OMIT;
848 ts[1].tv_sec = io.last_modified;
853 tls_buffer_free(&io.response);
858 tls_shutdown(clientssl);
863 if (progressbar && io.status_code == 200) {
864 fprintf(stderr, "(%lu)", total);
868 return io.status_code < 400 ? 0 : EXIT_FAILURE;