2 * Copyright (c) 2021 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19#include <sys/socket.h>
45const char *proxy_host;
46const char *proxy_port;
50struct tls_config *tls_conf;
55 dprintf(2, "%s: timer expired\n", getprogname());
62 if ((tls_conf = tls_config_new()) == NULL)
63 err(1, "tls_config_new");
65 tls_config_insecure_noverifycert(tls_conf);
67 tls_config_insecure_noverifyname(tls_conf);
70 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
71 errx(1, "can't set TLSv1.2");
73 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
74 errx(1, "can't set TLSv1.3");
77 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
78 errx(1, "can't load client certificate %s", cert);
82connectto(struct tls *ctx, const char *host, const char *port)
84 struct addrinfo hints, *res, *res0;
88 const char *cause = NULL;
91 if (proxy_host != NULL) {
96 if ((sname = sni) == NULL)
99 memset(&hints, 0, sizeof(hints));
100 hints.ai_family = AF_UNSPEC;
101 hints.ai_socktype = SOCK_STREAM;
102 error = getaddrinfo(host, port, &hints, &res0);
104 errx(1, "%s", gai_strerror(error));
107 for (res = res0; res != NULL; res = res->ai_next) {
108 s = socket(res->ai_family, res->ai_socktype,
115 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
128 err(1, "%s: can't connect to %s:%s", cause,
133 if (tls_connect_socket(ctx, s, sname) == -1)
134 errx(1, "tls_connect_socket: %s", tls_error(ctx));
138doreq(struct tls *ctx, const char *buf)
145 switch (w = tls_write(ctx, buf, s)) {
148 errx(1, "tls_write: %s", tls_error(ctx));
149 case TLS_WANT_POLLIN:
150 case TLS_WANT_POLLOUT:
160dorep(struct tls *ctx, void *buf, size_t len)
166 switch (w = tls_read(ctx, buf, len)) {
170 errx(1, "tls_write: %s", tls_error(ctx));
171 case TLS_WANT_POLLIN:
172 case TLS_WANT_POLLOUT:
189 int foundhdr = 0, code = -1, od;
190 char iribuf[GEMINI_URL_LEN];
191 char req[GEMINI_URL_LEN];
193 const char *parse_err, *host, *port;
195 if (strlcpy(iribuf, r, sizeof(iribuf)) >= sizeof(iribuf))
196 errx(1, "iri too long: %s", r);
198 if (strlcpy(req, r, sizeof(req)) >= sizeof(req))
199 errx(1, "iri too long: %s", r);
201 if (strlcat(req, "\r\n", sizeof(req)) >= sizeof(req))
202 errx(1, "iri too long: %s", r);
204 if (!parse_iri(iribuf, &iri, &parse_err))
205 errx(1, "invalid IRI: %s", parse_err);
210 if ((ctx = tls_client()) == NULL)
211 errx(1, "can't create tls context");
213 if (tls_configure(ctx, tls_conf) == -1)
214 errx(1, "tls_configure: %s", tls_error(ctx));
218 if (*iri.port != '\0')
221 connectto(ctx, host, port);
225 switch (tls_handshake(ctx)) {
230 errx(1, "handshake: %s", tls_error(ctx));
243 len = dorep(ctx, buf, sizeof(buf));
253 if (memmem(buf, len, "\r\n", 2) == NULL)
254 errx(1, "invalid reply: no \\r\\n");
255 if (!isdigit(buf[0]) || !isdigit(buf[1]) || buf[2] != ' ')
256 errx(1, "invalid reply: invalid response format");
258 code = (buf[0] - '0') * 10 + buf[1] - '0';
260 if (debug == DEBUG_CODE) {
261 printf("%d\n", code);
265 if (debug == DEBUG_HEADER) {
266 t = memmem(buf, len, "\r\n", 2);
273 if (debug == DEBUG_META) {
274 t = memmem(buf, len, "\r\n", 2);
277 printf("%s\n", buf+3);
281 if (debug == DEBUG_ALL) {
286 /* skip the header */
287 t = memmem(buf, len, "\r\n", 2);
289 t += 2; /* skip \r\n */
296 if (od == TLS_WANT_POLLIN || od == TLS_WANT_POLLOUT)
304static void __attribute__((noreturn))
307 fprintf(stderr, "usage: %s [-23Nnv] [-C cert] [-d mode] [-H sni] "
308 "[-K key] [-P host[:port]]\n",
310 fprintf(stderr, " [-T seconds] gemini://...\n");
315parse_debug(const char *arg)
317 if (!strcmp(arg, "none"))
319 if (!strcmp(arg, "code"))
321 if (!strcmp(arg, "header"))
323 if (!strcmp(arg, "meta"))
325 if (!strcmp(arg, "all"))
331parse_proxy(const char *arg)
335 if ((proxy_host = strdup(arg)) == NULL)
340 if ((at = strchr(proxy_host, ':')) == NULL)
345 if (strchr(proxy_port, ':') != NULL)
346 errx(1, "invalid port %s", proxy_port);
350main(int argc, char **argv)
355 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:v")) != -1) {
367 debug = parse_debug(optarg);
376 dont_verify_name = 1;
383 dont_verify_name = 1;
386 timer = strtonum(optarg, 1, 1000, &errstr);
388 errx(1, "timeout is %s: %s",
390 signal(SIGALRM, timeout);
403 if (flag2 + flag3 > 1) {
404 warnx("only -2 or -3 can be specified at the same time");
408 if ((cert != NULL && key == NULL) ||
409 (cert == NULL && key != NULL)) {
410 warnx("cert or key is missing");
419 signal(SIGPIPE, SIG_IGN);
422 if (pledge("stdio inet dns", NULL) == -1)
428 return code < 20 || code >= 30;