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>
44 const char *proxy_host;
45 const char *proxy_port;
49 struct tls_config *tls_conf;
54 dprintf(2, "%s: timer expired\n", getprogname());
61 if ((tls_conf = tls_config_new()) == NULL)
62 err(1, "tls_config_new");
64 tls_config_insecure_noverifycert(tls_conf);
66 tls_config_insecure_noverifyname(tls_conf);
69 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
70 errx(1, "can't set TLSv1.2");
72 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
73 errx(1, "can't set TLSv1.3");
76 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
77 errx(1, "can't load client certificate %s", cert);
81 connectto(struct tls *ctx, const char *host, const char *port)
83 struct addrinfo hints, *res, *res0;
87 const char *cause = NULL;
90 if (proxy_host != NULL) {
95 if ((sname = sni) == NULL)
98 memset(&hints, 0, sizeof(hints));
99 hints.ai_family = AF_UNSPEC;
100 hints.ai_socktype = SOCK_STREAM;
101 error = getaddrinfo(host, port, &hints, &res0);
103 errx(1, "%s", gai_strerror(error));
106 for (res = res0; res != NULL; res = res->ai_next) {
107 s = socket(res->ai_family, res->ai_socktype,
114 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
127 err(1, "%s: can't connect to %s:%s", cause,
132 if (tls_connect_socket(ctx, s, sname) == -1)
133 errx(1, "tls_connect_socket: %s", tls_error(ctx));
137 doreq(struct tls *ctx, const char *buf)
144 switch (w = tls_write(ctx, buf, s)) {
147 errx(1, "tls_write: %s", tls_error(ctx));
148 case TLS_WANT_POLLIN:
149 case TLS_WANT_POLLOUT:
159 dorep(struct tls *ctx, void *buf, size_t len)
165 switch (w = tls_read(ctx, buf, len)) {
169 errx(1, "tls_write: %s", tls_error(ctx));
170 case TLS_WANT_POLLIN:
171 case TLS_WANT_POLLOUT:
188 int foundhdr = 0, code = -1, od;
189 char iribuf[GEMINI_URL_LEN];
190 char req[GEMINI_URL_LEN];
192 const char *parse_err, *host, *port;
194 if (strlcpy(iribuf, r, sizeof(iribuf)) >= sizeof(iribuf))
195 errx(1, "iri too long: %s", r);
197 if (strlcpy(req, r, sizeof(req)) >= sizeof(req))
198 errx(1, "iri too long: %s", r);
200 if (strlcat(req, "\r\n", sizeof(req)) >= sizeof(req))
201 errx(1, "iri too long: %s", r);
203 if (!parse_iri(iribuf, &iri, &parse_err))
204 errx(1, "invalid IRI: %s", parse_err);
209 if ((ctx = tls_client()) == NULL)
210 errx(1, "can't create tls context");
212 if (tls_configure(ctx, tls_conf) == -1)
213 errx(1, "tls_configure: %s", tls_error(ctx));
217 if (*iri.port != '\0')
220 connectto(ctx, host, port);
224 switch (tls_handshake(ctx)) {
229 errx(1, "handshake: %s", tls_error(ctx));
239 len = dorep(ctx, buf, sizeof(buf));
249 if (memmem(buf, len, "\r\n", 2) == NULL)
250 errx(1, "invalid reply: no \\r\\n");
251 if (!isdigit((unsigned char)buf[0]) ||
252 !isdigit((unsigned char)buf[1]) ||
254 errx(1, "invalid reply: invalid response format");
256 code = (buf[0] - '0') * 10 + buf[1] - '0';
258 if (debug == DEBUG_CODE) {
259 printf("%d\n", code);
263 if (debug == DEBUG_HEADER) {
264 t = memmem(buf, len, "\r\n", 2);
271 if (debug == DEBUG_META) {
272 t = memmem(buf, len, "\r\n", 2);
275 printf("%s\n", buf+3);
279 if (debug == DEBUG_ALL) {
284 /* skip the header */
285 t = memmem(buf, len, "\r\n", 2);
287 t += 2; /* skip \r\n */
294 if (od == TLS_WANT_POLLIN || od == TLS_WANT_POLLOUT)
302 static void __attribute__((noreturn))
305 fprintf(stderr, "version: " GG_STRING "\n");
306 fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
307 "[-K key] [-P host[:port]]\n",
309 fprintf(stderr, " [-T seconds] gemini://...\n");
314 parse_debug(const char *arg)
316 if (!strcmp(arg, "none"))
318 if (!strcmp(arg, "code"))
320 if (!strcmp(arg, "header"))
322 if (!strcmp(arg, "meta"))
324 if (!strcmp(arg, "all"))
330 parse_proxy(const char *arg)
334 if ((proxy_host = strdup(arg)) == NULL)
339 if ((at = strchr(proxy_host, ':')) == NULL)
344 if (strchr(proxy_port, ':') != NULL)
345 errx(1, "invalid port %s", proxy_port);
349 main(int argc, char **argv)
354 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
366 debug = parse_debug(optarg);
375 dont_verify_name = 1;
382 dont_verify_name = 1;
385 timer = strtonum(optarg, 1, 1000, &errstr);
387 errx(1, "timeout is %s: %s",
389 signal(SIGALRM, timeout);
399 if (flag2 + flag3 > 1) {
400 warnx("only -2 or -3 can be specified at the same time");
404 if ((cert != NULL && key == NULL) ||
405 (cert == NULL && key != NULL)) {
406 warnx("cert or key is missing");
415 signal(SIGPIPE, SIG_IGN);
418 if (pledge("stdio inet dns", NULL) == -1)
424 return code < 20 || code >= 30;