Add gopher TLS support - hurl - Gopher/HTTP/HTTPS file grabber
 (HTM) git clone git://git.codemadness.org/hurl
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 9546c0f17665658befbc25876245acaa9db4b08f
 (DIR) parent e5399e327f21a6033de0decb3c8588a17a63eea8
 (HTM) Author: Christoph Lohmann <20h@r-36.net>
       Date:   Sun,  7 Jun 2020 20:10:46 +0200
       
       Add gopher TLS support
       
       + some changes by me (Hiltjo):
       
       - Refactor gopher TLS support and for now use gophers:// instead of a fallback
         to plain-text. Refactored because:
       
         - Downgrade attacks are possible by spoofing the first byte.
         - Some gopher servers have different buffering of the data, which causes
           issues with the TLS handshake.  Regular plain-text gopher requests must work
           flawlessly without delay.
         - There could be log "spam" of TLS handshake for gophers that don't support TLS.
       
       Non-related fix:
       - Fix error handling of path truncation.
       
       Diffstat:
         M hurl.c                              |     155 ++++++++++++++++++++++++++-----
       
       1 file changed, 133 insertions(+), 22 deletions(-)
       ---
 (DIR) diff --git a/hurl.c b/hurl.c
       @@ -6,7 +6,6 @@
        #include <errno.h>
        #include <netdb.h>
        #include <locale.h>
       -#include <signal.h>
        #include <stdarg.h>
        #include <stdio.h>
        #include <stdint.h>
       @@ -55,13 +54,6 @@ static char *url;
        /* TLS config */
        static struct tls_config *tls_config;
        
       -void
       -sighandler(int signo)
       -{
       -        if (signo == SIGALRM)
       -                _exit(2);
       -}
       -
        int
        parseuri(const char *s, struct uri *u)
        {
       @@ -183,7 +175,7 @@ edial(const char *host, const char *port)
        int
        https_request(void)
        {
       -        struct tls *t = NULL;
       +        struct tls *t;
                char buf[READ_BUF_SIZ], *p;
                const char *errstr;
                size_t n, len;
       @@ -226,8 +218,10 @@ https_request(void)
                        stdport ? "" : ":",
                        stdport ? "" : u.port,
                        config_headers, config_headers[0] ? "\r\n" : "");
       -        if (r < 0 || (size_t)r >= sizeof(buf))
       -                errx(1, "not writing header because it is truncated");
       +        if (r < 0 || (size_t)r >= sizeof(buf)) {
       +                fprintf(stderr, "not writing header because it is truncated");
       +                goto err;
       +        }
        
                for (len = r, p = buf; len > 0; ) {
                        r = tls_write(t, p, len);
       @@ -352,8 +346,10 @@ http_request(void)
                        stdport ? "" : ":",
                        stdport ? "" : u.port,
                        config_headers, config_headers[0] ? "\r\n" : "");
       -        if (r < 0 || (size_t)r >= sizeof(buf))
       -                errx(1, "not writing header because it is truncated");
       +        if (r < 0 || (size_t)r >= sizeof(buf)) {
       +                fprintf(stderr, "not writing header because it is truncated");
       +                goto err;
       +        }
                if ((r = write(fd, buf, r)) == -1) {
                        fprintf(stderr, "write: %s\n", strerror(errno));
                        goto err;
       @@ -434,7 +430,8 @@ err:
        int
        gopher_request(void)
        {
       -        char buf[READ_BUF_SIZ];
       +        char buf[READ_BUF_SIZ], *p;
       +        const char *errstr;
                size_t len = 0;
                ssize_t r;
                int fd = -1, ret = 1;
       @@ -448,7 +445,12 @@ gopher_request(void)
                        err(1, "pledge");
        
                /* create and send path, skip type part */
       -        snprintf(buf, sizeof(buf), "%s\r\n", u.path + 2);
       +        r = snprintf(buf, sizeof(buf), "%s\r\n", u.path + 2);
       +        if (r < 0 || (size_t)r >= sizeof(buf)) {
       +                fprintf(stderr, "not writing header because it is truncated");
       +                goto err;
       +        }
       +
                if ((r = write(fd, buf, strlen(buf))) == -1) {
                        fprintf(stderr, "write: %s\n", strerror(errno));
                        goto err;
       @@ -456,9 +458,9 @@ gopher_request(void)
        
                while (1) {
                        r = read(fd, &buf, sizeof(buf));
       -                if (r == 0)
       +                if (r == 0) {
                                break;
       -                if (r == -1) {
       +                } else if (r == -1) {
                                fprintf(stderr, "read: %s\n", strerror(errno));
                                goto err;
                        }
       @@ -474,7 +476,7 @@ gopher_request(void)
                                break;
                }
                if (config_maxresponsesiz && len >= config_maxresponsesiz) {
       -                fprintf(stderr, "tls_read: response too big: %zu >= %zu\n",
       +                fprintf(stderr, "read: response too big: %zu >= %zu\n",
                                len, config_maxresponsesiz);
                        goto err;
                }
       @@ -486,6 +488,101 @@ err:
                return ret;
        }
        
       +int
       +gophers_request(void)
       +{
       +        struct tls *t;
       +        char buf[READ_BUF_SIZ], *p;
       +        const char *errstr;
       +        size_t len = 0;
       +        ssize_t r;
       +        int fd = -1, ret = 1;
       +
       +        if (pledge("stdio dns inet rpath unveil", NULL) == -1)
       +                err(1, "pledge");
       +
       +        if (unveil(TLS_CA_CERT_FILE, "r") == -1)
       +                err(1, "unveil: %s", TLS_CA_CERT_FILE);
       +        if (unveil(NULL, NULL) == -1)
       +                err(1, "unveil");
       +
       +        if (!(t = tls_client())) {
       +                errstr = tls_error(t);
       +                fprintf(stderr, "tls_client: %s\n", errstr ? errstr : "");
       +                goto err;
       +        }
       +        if (tls_configure(t, tls_config)) {
       +                errstr = tls_error(t);
       +                fprintf(stderr, "tls_configure: %s\n", errstr ? errstr : "");
       +                goto err;
       +        }
       +
       +        fd = edial(u.host, u.port);
       +        if (tls_connect_socket(t, fd, u.host) == -1)
       +                errx(1, "tls_connect: %s", tls_error(t));
       +
       +        if (pledge("stdio", NULL) == -1)
       +                err(1, "pledge");
       +
       +        /* create and send path, skip type part */
       +        r = snprintf(buf, sizeof(buf), "%s\r\n", u.path + 2);
       +        if (r < 0 || (size_t)r >= sizeof(buf)) {
       +                fprintf(stderr, "not writing header because it is truncated");
       +                goto err;
       +        }
       +
       +        for (len = r, p = buf; len > 0; ) {
       +                r = tls_write(t, p, len);
       +                if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
       +                        continue;
       +                } else if (r == -1) {
       +                        fprintf(stderr, "tls_write: %s\n", tls_error(t));
       +                        goto err;
       +                }
       +                p += r;
       +                len -= r;
       +        }
       +
       +        while (1) {
       +                r = tls_read(t, &buf, sizeof(buf));
       +                if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT) {
       +                        continue;
       +                } else if (r == 0) {
       +                        break;
       +                } else if (r == -1) {
       +                        errstr = tls_error(t);
       +                        fprintf(stderr, "tls_read: %s\n", errstr ? errstr : "");
       +                        goto err;
       +                }
       +                len += r;
       +
       +                r = fwrite(buf, 1, r, stdout);
       +                if (ferror(stdout)) {
       +                        fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno));
       +                        goto err;
       +                }
       +
       +                if (config_maxresponsesiz && len >= config_maxresponsesiz)
       +                        break;
       +        }
       +        if (config_maxresponsesiz && len >= config_maxresponsesiz) {
       +                fprintf(stderr, "read: response too big: %zu >= %zu\n",
       +                        len, config_maxresponsesiz);
       +                goto err;
       +        }
       +        ret = 0;
       +
       +err:
       +        if (t) {
       +                tls_close(t);
       +                tls_free(t);
       +        }
       +
       +        if (fd != -1)
       +                close(fd);
       +        return ret;
       +}
       +
        void
        usage(void)
        {
       @@ -533,10 +630,6 @@ main(int argc, char **argv)
                if (parseuri(url, &u) == -1)
                        errx(1, "invalid url: %s", url);
        
       -        signal(SIGALRM, sighandler);
       -        if (alarm(config_timeout) == -1)
       -                err(1, "alarm");
       -
                if (!strcmp(u.proto, "https")) {
                        if (tls_init())
                                errx(1, "tls_init failed");
       @@ -563,6 +656,24 @@ main(int argc, char **argv)
                                errx(1, "must specify type");
        
                        statuscode = gopher_request();
       +        } else if (!strcmp(u.proto, "gophers")) {
       +                if (tls_init())
       +                        errx(1, "tls_init failed");
       +                if (!(tls_config = tls_config_new()))
       +                        errx(1, "tls config failed");
       +                if (config_legacy) {
       +                        /* enable legacy cipher and negotiation. */
       +                        if (tls_config_set_ciphers(tls_config, "legacy"))
       +                                errx(1, "tls set ciphers failed: %s",
       +                                     tls_config_error(tls_config));
       +                }
       +                if (!u.port[0])
       +                        memcpy(u.port, "70", 3);
       +
       +                if (u.path[0] != '/' || u.path[1] == '\0')
       +                        errx(1, "must specify type");
       +
       +                statuscode = gophers_request();
                } else {
                        if (u.proto[0])
                                errx(1, "unsupported protocol specified: %s", u.proto);