]> pd.if.org Git - zpackage/blobdiff - zpm-fetchurl.c
commit files needed for zpm-fetchurl
[zpackage] / zpm-fetchurl.c
diff --git a/zpm-fetchurl.c b/zpm-fetchurl.c
new file mode 100644 (file)
index 0000000..15fc36a
--- /dev/null
@@ -0,0 +1,513 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#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;
+}