gmid/gg.c
Omar Polo 800aa93c05 gg: warn when the TLS layer is not closed properly
various servers are not handling correctly the close notify so for
the moment don't turn this into an hard error but just warn.
Hopefully, given some time, most servers will be fixed.

while here, drop the gotos and just use a break to exit the main
loop.
2023-07-22 15:47:07 +00:00

434 lines
8.1 KiB
C

/*
* Copyright (c) 2021 Omar Polo <op@omarpolo.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "gmid.h"
#include <sys/socket.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <locale.h>
#include <string.h>
enum debug {
DEBUG_NONE,
DEBUG_CODE,
DEBUG_HEADER,
DEBUG_META,
DEBUG_ALL,
};
/* flags */
int debug;
int dont_verify_name;
int flag2;
int flag3;
int nop;
int redirects = 5;
int timer;
const char *cert;
const char *key;
const char *proxy_host;
const char *proxy_port;
const char *sni;
/* state */
struct tls_config *tls_conf;
static void
timeout(int signo)
{
dprintf(2, "%s: timer expired\n", getprogname());
exit(1);
}
static void
load_tls_conf(void)
{
if ((tls_conf = tls_config_new()) == NULL)
err(1, "tls_config_new");
tls_config_insecure_noverifycert(tls_conf);
if (dont_verify_name)
tls_config_insecure_noverifyname(tls_conf);
if (flag2 &&
tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
errx(1, "can't set TLSv1.2");
if (flag3 &&
tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
errx(1, "can't set TLSv1.3");
if (cert != NULL &&
tls_config_set_keypair_file(tls_conf, cert, key) == -1)
errx(1, "can't load client certificate %s", cert);
}
static void
connectto(struct tls *ctx, const char *host, const char *port)
{
struct addrinfo hints, *res, *res0;
int error;
int saved_errno;
int s;
const char *cause = NULL;
const char *sname;
if (proxy_host != NULL) {
host = proxy_host;
port = proxy_port;
}
if ((sname = sni) == NULL)
sname = host;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(host, port, &hints, &res0);
if (error)
errx(1, "%s", gai_strerror(error));
s = -1;
for (res = res0; res != NULL; res = res->ai_next) {
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s == -1) {
cause = "socket";
continue;
}
if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
cause = "connect";
saved_errno = errno;
close(s);
errno = saved_errno;
s = -1;
continue;
}
break;
}
if (s == -1)
err(1, "%s: can't connect to %s:%s", cause,
host, port);
freeaddrinfo(res0);
if (tls_connect_socket(ctx, s, sname) == -1)
errx(1, "tls_connect_socket: %s", tls_error(ctx));
}
static void
doreq(struct tls *ctx, const char *buf)
{
size_t s;
ssize_t w;
s = strlen(buf);
while (s != 0) {
switch (w = tls_write(ctx, buf, s)) {
case 0:
case -1:
errx(1, "tls_write: %s", tls_error(ctx));
case TLS_WANT_POLLIN:
case TLS_WANT_POLLOUT:
continue;
}
s -= w;
buf += w;
}
}
static size_t
dorep(struct tls *ctx, uint8_t *buf, size_t len)
{
ssize_t w;
size_t tot = 0;
while (len != 0) {
switch (w = tls_read(ctx, buf, len)) {
case 0:
return tot;
case -1:
errx(1, "tls_write: %s", tls_error(ctx));
case TLS_WANT_POLLIN:
case TLS_WANT_POLLOUT:
continue;
}
len -= w;
buf += w;
tot += w;
}
return tot;
}
static int
get(const char *r)
{
struct tls *ctx;
struct iri iri;
int foundhdr = 0, code = -1, od;
char iribuf[GEMINI_URL_LEN];
char req[GEMINI_URL_LEN];
uint8_t buf[2048];
const char *parse_err, *host, *port;
if (strlcpy(iribuf, r, sizeof(iribuf)) >= sizeof(iribuf))
errx(1, "iri too long: %s", r);
if (strlcpy(req, r, sizeof(req)) >= sizeof(req))
errx(1, "iri too long: %s", r);
if (strlcat(req, "\r\n", sizeof(req)) >= sizeof(req))
errx(1, "iri too long: %s", r);
if (!parse_iri(iribuf, &iri, &parse_err))
errx(1, "invalid IRI: %s", parse_err);
if (nop)
errx(0, "IRI OK");
if ((ctx = tls_client()) == NULL)
errx(1, "can't create tls context");
if (tls_configure(ctx, tls_conf) == -1)
errx(1, "tls_configure: %s", tls_error(ctx));
host = iri.host;
port = "1965";
if (*iri.port != '\0')
port = iri.port;
connectto(ctx, host, port);
od = 0;
while (!od) {
switch (tls_handshake(ctx)) {
case 0:
od = 1;
break;
case -1:
errx(1, "handshake: %s", tls_error(ctx));
}
}
doreq(ctx, req);
for (;;) {
uint8_t *t;
size_t len;
len = dorep(ctx, buf, sizeof(buf));
if (len == 0)
break;
if (foundhdr) {
write(1, buf, len);
continue;
}
foundhdr = 1;
if (memmem(buf, len, "\r\n", 2) == NULL)
errx(1, "invalid reply: no \\r\\n");
if (!isdigit((unsigned char)buf[0]) ||
!isdigit((unsigned char)buf[1]) ||
buf[2] != ' ')
errx(1, "invalid reply: invalid response format");
code = (buf[0] - '0') * 10 + buf[1] - '0';
if (debug == DEBUG_CODE) {
printf("%d\n", code);
break;
}
if (debug == DEBUG_HEADER) {
t = memmem(buf, len, "\r\n", 2);
assert(t != NULL);
*t = '\0';
printf("%s\n", buf);
break;
}
if (debug == DEBUG_META) {
t = memmem(buf, len, "\r\n", 2);
assert(t != NULL);
*t = '\0';
printf("%s\n", buf+3);
break;
}
if (debug == DEBUG_ALL) {
write(1, buf, len);
continue;
}
/* skip the header */
t = memmem(buf, len, "\r\n", 2);
assert(t != NULL);
t += 2; /* skip \r\n */
len -= t - buf;
write(1, t, len);
}
for (;;) {
switch (tls_close(ctx)) {
case TLS_WANT_POLLIN:
case TLS_WANT_POLLOUT:
continue;
case -1:
warnx("tls_close: %s", tls_error(ctx));
/* fallthrough */
default:
tls_free(ctx);
return code;
}
}
}
static void __attribute__((noreturn))
usage(void)
{
fprintf(stderr, "version: " GG_STRING "\n");
fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
"[-K key] [-P host[:port]]\n",
getprogname());
fprintf(stderr, " [-T seconds] gemini://...\n");
exit(1);
}
static int
parse_debug(const char *arg)
{
if (!strcmp(arg, "none"))
return DEBUG_NONE;
if (!strcmp(arg, "code"))
return DEBUG_CODE;
if (!strcmp(arg, "header"))
return DEBUG_HEADER;
if (!strcmp(arg, "meta"))
return DEBUG_META;
if (!strcmp(arg, "all"))
return DEBUG_ALL;
usage();
}
static void
parse_proxy(const char *arg)
{
char *at;
if ((proxy_host = strdup(arg)) == NULL)
err(1, "strdup");
proxy_port = "1965";
if ((at = strchr(proxy_host, ':')) == NULL)
return;
*at = '\0';
proxy_port = ++at;
if (strchr(proxy_port, ':') != NULL)
errx(1, "invalid port %s", proxy_port);
}
int
main(int argc, char **argv)
{
int ch, code;
const char *errstr;
setlocale(LC_CTYPE, "");
while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
switch (ch) {
case '2':
flag2 = 1;
break;
case '3':
flag3 = 1;
break;
case 'C':
cert = optarg;
break;
case 'd':
debug = parse_debug(optarg);
break;
case 'H':
sni = optarg;
break;
case 'K':
key = optarg;
break;
case 'N':
dont_verify_name = 1;
break;
case 'n':
nop = 1;
break;
case 'P':
parse_proxy(optarg);
dont_verify_name = 1;
break;
case 'T':
timer = strtonum(optarg, 1, 1000, &errstr);
if (errstr != NULL)
errx(1, "timeout is %s: %s",
errstr, optarg);
signal(SIGALRM, timeout);
alarm(timer);
break;
default:
usage();
}
}
argc -= optind;
argv += optind;
if (flag2 + flag3 > 1) {
warnx("only -2 or -3 can be specified at the same time");
usage();
}
if ((cert != NULL && key == NULL) ||
(cert == NULL && key != NULL)) {
warnx("cert or key is missing");
usage();
}
if (argc != 1)
usage();
load_tls_conf();
signal(SIGPIPE, SIG_IGN);
#ifdef __OpenBSD__
if (pledge("stdio inet dns", NULL) == -1)
err(1, "pledge");
#endif
code = get(*argv);
return code < 20 || code >= 30;
}