X-Git-Url: https://pd.if.org/git/?a=blobdiff_plain;f=zpm-fetchurl.c;fp=zpm-fetchurl.c;h=15fc36ab77796639b033d5eae8edd9dc0ecf21b5;hb=66bc25938679f1d6a1d1200f329093d82a5e99b4;hp=0000000000000000000000000000000000000000;hpb=a52ee0733f420ca20224049260d6fc5cf7d8f621;p=zpackage diff --git a/zpm-fetchurl.c b/zpm-fetchurl.c new file mode 100644 index 0000000..15fc36a --- /dev/null +++ b/zpm-fetchurl.c @@ -0,0 +1,513 @@ +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tlse.h" + +struct tls_uri { + char *scheme; + char *userinfo; + char *host; + char *port; + char *path; + char *query; + char *fragment; +}; +int tls_parse_uri(char *, struct tls_uri *); +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) { + 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) + 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; + } + + if (len > 0 && context->sni) { + err = tls_certificate_valid_subject(chain[0], context->sni); + if (err) { + return err; + } + } + + /* Perform certificate validation against ROOT CA */ + err = tls_certificate_chain_is_valid_root(context, chain, len); + if (err) { + return err; + } + + return no_error; +} + +struct io { + struct tls_buffer response; + struct TLSContext *tls; + int socket; + int status_code; + time_t last_modified; + time_t date; + size_t content_length; + char *redirect; +}; + +int month(char *m) { + char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec" }; + int i; + + for (i=0; i < 12; i++) { + if (!strncasecmp(m, months[i], 3)) { + return i+1; + } + } + return 0; +} + +/* 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); + if (rv == 6) { + tm.tm_year = Y - 1900; + tm.tm_hour = h; + tm.tm_min = m; + tm.tm_sec = s; + tm.tm_mon = month(M)-1; + tm.tm_mday = dom; + + return mktime(&tm); + } + return -1; +} + +char *find_header(struct io *io, char *header, size_t *len) { + char *eoh, *soh; + size_t hlen; + + *len = 0; + + hlen = strlen(header); + eoh = strstr(io->response.buffer, "\r\n\r\n"); + if (!eoh) { + return 0; + } + soh = io->response.buffer; + do { + soh = strstr(soh, "\r\n"); + if (soh == eoh) { + break; + } + soh += 2; + if (!memcmp(soh, header, hlen)) { + break; + } + } while (soh < eoh); + + if (soh >= eoh) { + return 0; + } + eoh = strstr(soh, "\r\n"); + soh += hlen; + while (soh < eoh && isspace(*soh)) { + soh++; + } + *len = (eoh - soh); + return soh; +} + +void parse_header(struct io *io) { + char *s = io->response.buffer; + int code = 0; + char *hval; + size_t hlen; + + while (!isspace(*s)) { + s++; + } + while (isspace(*s)) { + s++; + } + code = strtol(s, 0, 10); + io->status_code = code; + + hval = find_header(io, "Date:", &hlen); + if (hval) { + hval[hlen] = 0; + io->date = parse_date(hval); + hval[hlen] = '\r'; + } + hval = find_header(io, "Last-Modified:", &hlen); + if (hval) { + hval[hlen] = 0; + io->last_modified = parse_date(hval); + hval[hlen] = '\r'; + } + + hval = find_header(io, "Content-Length:", &hlen); + if (hval) { + hval[hlen] = 0; + io->content_length = strtoul(hval, 0, 10); + hval[hlen] = '\r'; + } + + switch (code) { + case 301: + case 302: + case 303: + case 307: + hval = find_header(io, "Location:", &hlen); + if (hval) { + io->redirect = strndup(hval, hlen); + } + break; + default: + break; + } + +} + +ssize_t fill_buffer(struct io *io) { + unsigned char buffer[4096]; + ssize_t ret; + + if (io->tls) { + ret = tls_read(io->tls, buffer, sizeof buffer); + } else { + ret = read(io->socket, buffer, sizeof buffer); + } + + if (ret > 0) { + tls_buffer_append(&io->response, buffer, ret); + } + + return ret; +} + +#if 0 +char *nextline(struct io *io) { + char *eol = 0;; + + eol = memchr(io->response.buffer, '\n', io.response.size); + while (eol == 0) { + fill_buffer(io); + eol = memchr(io->response.buffer, '\n', io.response.size); + } + if (eol) { + +} +#endif + +void append_header(struct tls_buffer *buf, char *header, char *val) { + tls_buffer_append(buf, header, strlen(header)); + tls_buffer_append(buf, ": ", 2); + tls_buffer_append(buf, val, strlen(val)); + tls_buffer_append(buf, "\r\n", 2); +} + +static void pdots(int len, int ch, unsigned long was, unsigned long now, + unsigned long total) { + was = len * was / total; + if (now > total) { + now = total; + } + now = len * now / total; + while (was++ < now) { + putc(ch,stderr); + } +} + +int main(int ac, char *av[]) { + int sockfd, port = -1, rv; + ssize_t ret; + int option; +#if 0 + char msg[] = "GET %s HTTP/1.1\r\nHost: %s:%i\r\nConnection: close\r\n\r\n"; + char msg2[] = "GET %s HTTP/1.1\r\nHost: %s:%i\r\nLast-Modified: %s\r\nConnection: close\r\n\r\n"; + char msg_buffer[1024]; +#endif + char *req_file = 0; + char *host = 0; + struct tls_uri uri; + char *outfile = 0; + int raw = 0, head = 0; + int out = 1; + int use_tls = 0; + struct io io = { {0}, 0, -1, 0, 0, 0, 0, 0 }; + struct TLSContext *clientssl = 0; + int failsilent = 0; + char *lmfile = 0; + int progressbar = 0; + struct tls_buffer request; + char lmtime[80]; + char *eoh = 0; + size_t total = 0; + size_t header_len; + char *url = 0; + int redirs = 0, redirlimit = 50, printstatus = 0; + + ltc_mp = tfm_desc; + + while ((option = getopt(ac, av, "o:rIfz:#R:S")) != -1) { + switch (option) { + case 'o': outfile = optarg; break; + case 'S': printstatus = 1; head = 1; break; + case 'I': head = 1; + case 'r': raw = 1; break; + case 'f': failsilent = 1; break; + case 'z': lmfile = optarg; break; + case 'R': redirlimit = strtol(optarg, 0, 10); break; + case '#': progressbar = 1; break; + default: + exit(EXIT_FAILURE); + break; + } + } + + if (ac < optind) { + fprintf(stderr, "Usage: %s uri\n", av[0]); + exit(EXIT_FAILURE); + } + + if (lmfile) { + struct stat st; + int rv; + struct tm *mtime; + time_t ts; + + rv = stat(lmfile, &st); + if (rv == -1) { + perror("stat failed:"); + exit(EXIT_FAILURE); + } + ts = st.st_mtime; + mtime = gmtime(&ts); + strftime(lmtime, sizeof lmtime, "%a, %d %b %Y %H:%M:%S GMT", mtime); + } + + url = strdup(av[optind]); + if (!url) { + exit(EXIT_FAILURE); + } + + if (outfile) { + out = open(outfile, O_WRONLY|O_CREAT, 0600); + if (out == -1) { + perror("can't open output file:"); + exit(EXIT_FAILURE); + } + } + + signal(SIGPIPE, SIG_IGN); + + tls_buffer_init(&io.response, 0); + tls_buffer_init(&request, 128); + + while (redirs++ <= redirlimit) { + tls_free_uri(&uri); + io.response.len = 0; + request.len = 0; + + tls_parse_uri(url, &uri); + host = uri.host; + port = atoi(uri.port); + req_file = uri.path; + + /* construct request */ + if (head) { + tls_buffer_append(&request, "HEAD ", 5); + } else { + 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); + + append_header(&request, "Host", host); + append_header(&request, "Connection", "close"); + if (lmfile) { + 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 (!clientssl) { + fprintf(stderr, "Error initializing client context\n"); + return -1; + } + 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) { + tls_set_fd(clientssl, sockfd); + + if ((rv = tls_connect(clientssl)) != 1) { + fprintf(stderr, "Handshake Error %i\n", rv); + return -5; + } + + ret = tls_write(clientssl, request.buffer, request.len); + } else { + ret = write(io.socket, request.buffer, request.len); + } + + if (ret < 0) { + fprintf(stderr, "write error %zd\n", ret); + return -6; + } + + do { + ret = fill_buffer(&io); + if (ret < 0) { + break; + } + eoh = strstr(io.response.buffer, "\r\n\r\n"); + if (ret == 0) { + break; + } + } while (!eoh); + + if (!eoh) { + /* never got (complet) header */ + fprintf(stderr, "incomplete response to %s\n", av[optind]); + exit(EXIT_FAILURE); + } + + header_len = (size_t)(eoh - io.response.buffer) + 4; + parse_header(&io); + + switch (io.status_code) { + case 301: + case 302: + case 303: + case 307: + free(url); + url = strdup(io.redirect); + continue; + break; + } + + if (printstatus) { + printf("%d\n", io.status_code); + break; + } + + if (io.status_code != 200) { + break; + } + + if (!raw) { + tls_buffer_shift(&io.response, header_len); + } + if (head) { + io.response.len -= 2; + } + + if (progressbar) { + if (io.content_length) { + fprintf(stderr, "(%lu) ", io.content_length); + } + } + + do { + write(out, io.response.buffer, io.response.len); + ret = io.response.len; + io.response.len = 0; + + if (progressbar) { + if (io.content_length) { + pdots(50, '.', total, total+ret, + io.content_length); + } else { + int old = total / 1000000; + int new = (total+ret)/1000000; + while (old < new) { + putc('.',stderr); + } + } + total += ret; + } + ret = fill_buffer(&io); + } while (ret > 0); + + if (ret < 0) { + fprintf(stderr, "%s read error %zd\n", uri.scheme, ret); + } + /* futimens(out, ...) */ + close(out); + tls_buffer_free(&io.response); + break; + } + + if (use_tls) { + tls_shutdown(clientssl); + tls_free(clientssl); + } + + close(sockfd); + if (progressbar && io.status_code == 200) { + fprintf(stderr, "(%lu)", total); + putc('\n',stderr); + } + + return io.status_code == 200 ? 0 : EXIT_FAILURE; +}