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);