From 985ba08eccf5127729d7b0904cca31ed3ffd575a Mon Sep 17 00:00:00 2001 From: phoebos Date: Thu, 6 Jan 2022 22:00:42 +0000 Subject: [PATCH] add basic gemini support note: does not verify certificates at all --- hurl.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/hurl.c b/hurl.c index 5641198..92cf2b0 100644 --- a/hurl.c +++ b/hurl.c @@ -692,6 +692,149 @@ err: return ret; } + +int +gemini_request(void) +{ + struct tls *t; + char buf[READ_BUF_SIZ], *p; + const char *errstr, *path; + size_t len = 0; + ssize_t r; + int fd = -1, ret = 1, geminiok = 0; + + if (pledge("stdio dns inet rpath unveil", NULL) == -1) + err(1, "pledge"); + + 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; + } + tls_config_insecure_noverifycert(tls_config); + tls_config_insecure_noverifyname(tls_config); + + 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"); + + r = snprintf(buf, sizeof(buf), "%s%s%s%s%s\r\n", + u.proto, + u.host, + u.path[0] ? u.path : "/", + u.query[0] ? "?" : "", u.query); + + 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; + } + + for (len = 0; len < sizeof(buf);) { + /* NOTE: buffer size is -1 to NUL terminate the buffer for a + string comparison. */ + r = tls_read(t, &buf[len], sizeof(buf) - len - 1); + 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; + } + buf[len] = '\0'; + + if (!strncmp(buf, "20", sizeof("20") - 1)) + geminiok = 1; + + if (!(p = strstr(buf, "\r\n"))) { + fprintf(stderr, "no gemini header found or header too big\n"); + goto err; + } + *p = '\0'; /* NUL terminate header part */ + //cs = parse_content_length(buf, &expectedlen); + p += strlen("\r\n"); + //bodylen = len - (p - buf); /* (partial) body after header */ + + if (geminiok) { + int n = len - (p - buf); + r = fwrite(p, 1, n, stdout); + if (ferror(stdout)) { + fprintf(stderr, "fwrite: stdout: %s\n", strerror(errno)); + goto err; + } + } else { + /* if not 20 print header */ + fputs(buf, stderr); + fputs("\r\n", stderr); + /* NOTE: we are nice and keep reading (not closing) until the server is done. */ + } + + 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; + + if (geminiok) { + 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, "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 geminiok ? ret : 2; +} + void usage(void) { @@ -783,6 +926,20 @@ main(int argc, char **argv) if (!u.port[0]) memcpy(u.port, "70", 3); statuscode = gophers_request(); + } else if (!strcmp(u.proto, "gemini://")) { + 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, "1965", 5); + statuscode = gemini_request(); } else { if (u.proto[0]) errx(1, "unsupported protocol specified: %s", u.proto); -- 2.34.1