2 * Copyright (c) 2021-2023 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>
46 const char *proxy_host;
47 const char *proxy_port;
51 struct tls_config *tls_conf;
56 dprintf(2, "%s: timer expired\n", getprogname());
63 if ((tls_conf = tls_config_new()) == NULL)
64 err(1, "tls_config_new");
66 tls_config_insecure_noverifycert(tls_conf);
68 tls_config_insecure_noverifyname(tls_conf);
71 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
72 errx(1, "can't set TLSv1.2");
74 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
75 errx(1, "can't set TLSv1.3");
78 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
79 errx(1, "can't load client certificate %s", cert);
83 connectto(struct tls *ctx, const char *host, const char *port)
85 struct addrinfo hints, *res, *res0;
89 const char *cause = NULL;
92 if (proxy_host != NULL) {
97 if ((sname = sni) == NULL)
100 memset(&hints, 0, sizeof(hints));
101 hints.ai_family = AF_UNSPEC;
102 hints.ai_socktype = SOCK_STREAM;
103 error = getaddrinfo(host, port, &hints, &res0);
105 errx(1, "%s", gai_strerror(error));
108 for (res = res0; res != NULL; res = res->ai_next) {
109 s = socket(res->ai_family, res->ai_socktype,
116 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
129 err(1, "%s: can't connect to %s:%s", cause,
134 if (tls_connect_socket(ctx, s, sname) == -1)
135 errx(1, "tls_connect_socket: %s", tls_error(ctx));
139 doreq(struct tls *ctx, const char *buf)
146 switch (w = tls_write(ctx, buf, s)) {
149 errx(1, "tls_write: %s", tls_error(ctx));
150 case TLS_WANT_POLLIN:
151 case TLS_WANT_POLLOUT:
161 dorep(struct tls *ctx, uint8_t *buf, size_t len)
167 switch (w = tls_read(ctx, buf, len)) {
171 errx(1, "tls_write: %s", tls_error(ctx));
172 case TLS_WANT_POLLIN:
173 case TLS_WANT_POLLOUT:
186 safeprint(FILE *fp, const char *str)
191 for (; *str != '\0'; str += len) {
192 if ((len = mbtowc(&wc, str, MB_CUR_MAX)) == -1) {
193 mbtowc(NULL, NULL, MB_CUR_MAX);
196 } else if (wcwidth(wc) == -1) {
198 } else if (wc != L'\n')
210 int foundhdr = 0, code = -1, od;
211 char iribuf[GEMINI_URL_LEN];
212 char req[GEMINI_URL_LEN];
214 const char *parse_err, *host, *port;
216 if (strlcpy(iribuf, r, sizeof(iribuf)) >= sizeof(iribuf))
217 errx(1, "iri too long: %s", r);
219 if (strlcpy(req, r, sizeof(req)) >= sizeof(req))
220 errx(1, "iri too long: %s", r);
222 if (strlcat(req, "\r\n", sizeof(req)) >= sizeof(req))
223 errx(1, "iri too long: %s", r);
225 if (!parse_iri(iribuf, &iri, &parse_err))
226 errx(1, "invalid IRI: %s", parse_err);
231 if ((ctx = tls_client()) == NULL)
232 errx(1, "can't create tls context");
234 if (tls_configure(ctx, tls_conf) == -1)
235 errx(1, "tls_configure: %s", tls_error(ctx));
239 if (*iri.port != '\0')
242 connectto(ctx, host, port);
246 switch (tls_handshake(ctx)) {
251 errx(1, "handshake: %s", tls_error(ctx));
261 len = dorep(ctx, buf, sizeof(buf));
271 if (memmem(buf, len, "\r\n", 2) == NULL)
272 errx(1, "invalid reply: no \\r\\n");
273 if (!isdigit((unsigned char)buf[0]) ||
274 !isdigit((unsigned char)buf[1]) ||
276 errx(1, "invalid reply: invalid response format");
278 code = (buf[0] - '0') * 10 + buf[1] - '0';
280 if (debug == DEBUG_CODE) {
281 printf("%d\n", code);
285 if (debug == DEBUG_HEADER) {
286 t = memmem(buf, len, "\r\n", 2);
293 if (debug == DEBUG_META) {
294 t = memmem(buf, len, "\r\n", 2);
297 printf("%s\n", buf+3);
301 if (debug == DEBUG_ALL) {
306 /* skip the header */
307 t = memmem(buf, len, "\r\n", 2);
309 if (code < 20 || code >= 30) {
311 fprintf(stderr, "Server says: ");
312 safeprint(stderr, buf + 3); /* skip return code */
314 t += 2; /* skip \r\n */
320 switch (tls_close(ctx)) {
321 case TLS_WANT_POLLIN:
322 case TLS_WANT_POLLOUT:
325 warnx("tls_close: %s", tls_error(ctx));
334 static void __attribute__((noreturn))
337 fprintf(stderr, "version: " GG_STRING "\n");
338 fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
339 "[-K key] [-P host[:port]]\n",
341 fprintf(stderr, " [-T seconds] gemini://...\n");
346 parse_debug(const char *arg)
348 if (!strcmp(arg, "none"))
350 if (!strcmp(arg, "code"))
352 if (!strcmp(arg, "header"))
354 if (!strcmp(arg, "meta"))
356 if (!strcmp(arg, "all"))
362 parse_proxy(const char *arg)
366 if ((proxy_host = strdup(arg)) == NULL)
371 if ((at = strchr(proxy_host, ':')) == NULL)
376 if (strchr(proxy_port, ':') != NULL)
377 errx(1, "invalid port %s", proxy_port);
381 main(int argc, char **argv)
386 setlocale(LC_CTYPE, "");
388 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
400 debug = parse_debug(optarg);
409 dont_verify_name = 1;
416 dont_verify_name = 1;
419 timer = strtonum(optarg, 1, 1000, &errstr);
421 errx(1, "timeout is %s: %s",
423 signal(SIGALRM, timeout);
433 if (flag2 + flag3 > 1) {
434 warnx("only -2 or -3 can be specified at the same time");
438 if ((cert != NULL && key == NULL) ||
439 (cert == NULL && key != NULL)) {
440 warnx("cert or key is missing");
449 signal(SIGPIPE, SIG_IGN);
452 if (pledge("stdio inet dns", NULL) == -1)
457 if (code >= 20 && code < 30)