wget: add support for https using "openssl s_client" as a helper
authorDenys Vlasenko <vda.linux@googlemail.com>
Sat, 22 Feb 2014 13:12:29 +0000 (14:12 +0100)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sat, 22 Feb 2014 13:12:29 +0000 (14:12 +0100)
www.kernel.org started redirecting http:// to https://
making https support mandatory for any auto build scripts.

function                                             old     new   delta
wget_main                                           2631    2971    +340
parse_url                                            409     471     +62
.rodata                                           115607  115626     +19
P_HTTPS                                                -       6      +6
P_HTTP                                                 -       5      +5
P_FTP                                                  -       4      +4
------------------------------------------------------------------------------
(add/remove: 3/0 grow/shrink: 3/0 up/down: 436/0)             Total: 436 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
networking/wget.c

index 7ca947aec1a4c5609c5f5b7f3fc91479038d8daf..dfea3d4d2e6be0717a50e9dc7c2690c80b177c5f 100644 (file)
@@ -47,10 +47,13 @@ struct host_info {
        char *allocated;
        const char *path;
        char       *user;
+       const char *protocol;
        char       *host;
        int         port;
-       smallint    is_ftp;
 };
+static const char P_FTP[] = "ftp";
+static const char P_HTTP[] = "http";
+static const char P_HTTPS[] = "https";
 
 
 /* Globals */
@@ -219,7 +222,7 @@ static FILE *open_socket(len_and_sockaddr *lsa)
        /* glibc 2.4 seems to try seeking on it - ??! */
        /* hopefully it understands what ESPIPE means... */
        fp = fdopen(fd, "r+");
-       if (fp == NULL)
+       if (!fp)
                bb_perror_msg_and_die(bb_msg_memory_exhausted);
 
        return fp;
@@ -274,23 +277,31 @@ static void parse_url(const char *src_url, struct host_info *h)
        free(h->allocated);
        h->allocated = url = xstrdup(src_url);
 
-       if (strncmp(url, "ftp://", 6) == 0) {
-               h->port = bb_lookup_port("ftp", "tcp", 21);
-               h->host = url + 6;
-               h->is_ftp = 1;
-       } else
-       if (strncmp(url, "http://", 7) == 0) {
-               h->host = url + 7;
+       h->protocol = P_FTP;
+       p = strstr(url, "://");
+       if (p) {
+               *p = '\0';
+               h->host = p + 3;
+               if (strcmp(url, P_FTP) == 0) {
+                       h->port = bb_lookup_port(P_FTP, "tcp", 21);
+               } else
+               if (strcmp(url, P_HTTPS) == 0) {
+                       h->port = bb_lookup_port(P_HTTPS, "tcp", 443);
+                       h->protocol = P_HTTPS;
+               } else
+               if (strcmp(url, P_HTTP) == 0) {
  http:
-               h->port = bb_lookup_port("http", "tcp", 80);
-               h->is_ftp = 0;
-       } else
-       if (!strstr(url, "//")) {
+                       h->port = bb_lookup_port(P_HTTP, "tcp", 80);
+                       h->protocol = P_HTTP;
+               } else {
+                       *p = ':';
+                       bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
+               }
+       } else {
                // GNU wget is user-friendly and falls back to http://
                h->host = url;
                goto http;
-       } else
-               bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
+       }
 
        // FYI:
        // "Real" wget 'http://busybox.net?var=a/b' sends this request:
@@ -472,6 +483,56 @@ static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_
        return sfp;
 }
 
+static int spawn_https_helper(const char *host, unsigned port)
+{
+       char *allocated = NULL;
+       int sp[2];
+       int pid;
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
+               /* Kernel can have AF_UNIX support disabled */
+               bb_perror_msg_and_die("socketpair");
+
+       if (!strchr(host, ':'))
+               host = allocated = xasprintf("%s:%u", host, port);
+
+       pid = BB_MMU ? xfork() : xvfork();
+       if (pid == 0) {
+               /* Child */
+               char *argv[6];
+
+               close(sp[0]);
+               xmove_fd(sp[1], 0);
+               xdup2(0, 1);
+               /*
+                * TODO: develop a tiny ssl/tls helper (using matrixssl?),
+                * try to exec it here before falling back to big fat openssl.
+                */
+               /*
+                * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
+                * It prints some debug stuff on stderr, don't know how to suppress it.
+                * Work around by dev-nulling stderr. We lose all error messages :(
+                */
+               xmove_fd(2, 3);
+               xopen("/dev/null", O_RDWR);
+               argv[0] = (char*)"openssl";
+               argv[1] = (char*)"s_client";
+               argv[2] = (char*)"-quiet";
+               argv[3] = (char*)"-connect";
+               argv[4] = (char*)host;
+               argv[5] = NULL;
+               BB_EXECVP(argv[0], argv);
+               xmove_fd(3, 2);
+               bb_perror_msg_and_die("can't execute '%s'", argv[0]);
+               /* notreached */
+       }
+
+       /* parent process */
+       free(allocated);
+       close(sp[1]);
+       return sp[0];
+}
+
 static void NOINLINE retrieve_file_data(FILE *dfp)
 {
 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
@@ -644,7 +705,8 @@ static void download_one_url(const char *url)
        /* Use the proxy if necessary */
        use_proxy = (strcmp(G.proxy_flag, "off") != 0);
        if (use_proxy) {
-               proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
+               proxy = getenv(target.protocol == P_FTP ? "ftp_proxy" : "http_proxy");
+//FIXME: what if protocol is https? Ok to use http_proxy?
                use_proxy = (proxy && proxy[0]);
                if (use_proxy)
                        parse_url(proxy, &server);
@@ -704,27 +766,31 @@ static void download_one_url(const char *url)
        /*G.content_len = 0; - redundant, got_clen = 0 is enough */
        G.got_clen = 0;
        G.chunked = 0;
-       if (use_proxy || !target.is_ftp) {
+       if (use_proxy || target.protocol != P_FTP) {
                /*
                 *  HTTP session
                 */
                char *str;
                int status;
 
-
-               /* Open socket to http server */
-               sfp = open_socket(lsa);
+               /* Open socket to http(s) server */
+               if (target.protocol == P_HTTPS) {
+                       int fd = spawn_https_helper(server.host, server.port);
+                       sfp = fdopen(fd, "r+");
+                       if (!sfp)
+                               bb_perror_msg_and_die(bb_msg_memory_exhausted);
+               } else
+                       sfp = open_socket(lsa);
 
                /* Send HTTP request */
                if (use_proxy) {
-                       fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
-                               target.is_ftp ? "f" : "ht", target.host,
+                       fprintf(sfp, "GET %s://%s/%s HTTP/1.1\r\n",
+                               target.protocol, target.host,
                                target.path);
                } else {
-                       if (option_mask32 & WGET_OPT_POST_DATA)
-                               fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
-                       else
-                               fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
+                       fprintf(sfp, "%s /%s HTTP/1.1\r\n",
+                               (option_mask32 & WGET_OPT_POST_DATA) ? "POST" : "GET",
+                               target.path);
                }
 
                fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",