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:
M | hurl.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);