hurl

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 9546c0f17665658befbc25876245acaa9db4b08f
parent e5399e327f21a6033de0decb3c8588a17a63eea8
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:
Mhurl.c | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
1 file changed, 133 insertions(+), 22 deletions(-)

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