Blob


1 /*
2 * Copyright (c) 2021-2023 Omar Polo <op@omarpolo.com>
3 *
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.
7 *
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.
15 */
17 #include "gmid.h"
19 #include <sys/socket.h>
21 #include <assert.h>
22 #include <ctype.h>
23 #include <errno.h>
24 #include <locale.h>
25 #include <string.h>
26 #include <wchar.h>
28 enum debug {
29 DEBUG_NONE,
30 DEBUG_CODE,
31 DEBUG_HEADER,
32 DEBUG_META,
33 DEBUG_ALL,
34 };
36 /* flags */
37 int debug;
38 int dont_verify_name;
39 int flag2;
40 int flag3;
41 int nop;
42 int redirects = 5;
43 int timer;
44 const char *cert;
45 const char *key;
46 const char *proxy_host;
47 const char *proxy_port;
48 const char *sni;
50 /* state */
51 struct tls_config *tls_conf;
53 static void
54 timeout(int signo)
55 {
56 dprintf(2, "%s: timer expired\n", getprogname());
57 exit(1);
58 }
60 static void
61 load_tls_conf(void)
62 {
63 if ((tls_conf = tls_config_new()) == NULL)
64 err(1, "tls_config_new");
66 tls_config_insecure_noverifycert(tls_conf);
67 if (dont_verify_name)
68 tls_config_insecure_noverifyname(tls_conf);
70 if (flag2 &&
71 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
72 errx(1, "can't set TLSv1.2");
73 if (flag3 &&
74 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
75 errx(1, "can't set TLSv1.3");
77 if (cert != NULL &&
78 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
79 errx(1, "can't load client certificate %s", cert);
80 }
82 static void
83 connectto(struct tls *ctx, const char *host, const char *port)
84 {
85 struct addrinfo hints, *res, *res0;
86 int error;
87 int saved_errno;
88 int s;
89 const char *cause = NULL;
90 const char *sname;
92 if (proxy_host != NULL) {
93 host = proxy_host;
94 port = proxy_port;
95 }
97 if ((sname = sni) == NULL)
98 sname = host;
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);
104 if (error)
105 errx(1, "%s", gai_strerror(error));
107 s = -1;
108 for (res = res0; res != NULL; res = res->ai_next) {
109 s = socket(res->ai_family, res->ai_socktype,
110 res->ai_protocol);
111 if (s == -1) {
112 cause = "socket";
113 continue;
116 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
117 cause = "connect";
118 saved_errno = errno;
119 close(s);
120 errno = saved_errno;
121 s = -1;
122 continue;
125 break;
128 if (s == -1)
129 err(1, "%s: can't connect to %s:%s", cause,
130 host, port);
132 freeaddrinfo(res0);
134 if (tls_connect_socket(ctx, s, sname) == -1)
135 errx(1, "tls_connect_socket: %s", tls_error(ctx));
138 static void
139 doreq(struct tls *ctx, const char *buf)
141 size_t s;
142 ssize_t w;
144 s = strlen(buf);
145 while (s != 0) {
146 switch (w = tls_write(ctx, buf, s)) {
147 case 0:
148 case -1:
149 errx(1, "tls_write: %s", tls_error(ctx));
150 case TLS_WANT_POLLIN:
151 case TLS_WANT_POLLOUT:
152 continue;
155 s -= w;
156 buf += w;
160 static size_t
161 dorep(struct tls *ctx, uint8_t *buf, size_t len)
163 ssize_t w;
164 size_t tot = 0;
166 while (len != 0) {
167 switch (w = tls_read(ctx, buf, len)) {
168 case 0:
169 return tot;
170 case -1:
171 errx(1, "tls_write: %s", tls_error(ctx));
172 case TLS_WANT_POLLIN:
173 case TLS_WANT_POLLOUT:
174 continue;
177 len -= w;
178 buf += w;
179 tot += w;
182 return tot;
185 static void
186 safeprint(FILE *fp, const char *str)
188 int len;
189 wchar_t wc;
191 for (; *str != '\0'; str += len) {
192 if ((len = mbtowc(&wc, str, MB_CUR_MAX)) == -1) {
193 mbtowc(NULL, NULL, MB_CUR_MAX);
194 fputc('?', fp);
195 len = 1;
196 } else if (wcwidth(wc) == -1) {
197 fputc('?', fp);
198 } else if (wc != L'\n')
199 putwc(wc, fp);
202 fputc('\n', fp);
205 static int
206 get(const char *r)
208 struct tls *ctx;
209 struct iri iri;
210 int foundhdr = 0, code = -1, od;
211 char iribuf[GEMINI_URL_LEN];
212 char req[GEMINI_URL_LEN];
213 uint8_t buf[2048];
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);
228 if (nop)
229 errx(0, "IRI OK");
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));
237 host = iri.host;
238 port = "1965";
239 if (*iri.port != '\0')
240 port = iri.port;
242 connectto(ctx, host, port);
244 od = 0;
245 while (!od) {
246 switch (tls_handshake(ctx)) {
247 case 0:
248 od = 1;
249 break;
250 case -1:
251 errx(1, "handshake: %s", tls_error(ctx));
255 doreq(ctx, req);
257 for (;;) {
258 uint8_t *t;
259 size_t len;
261 len = dorep(ctx, buf, sizeof(buf));
262 if (len == 0)
263 break;
265 if (foundhdr) {
266 write(1, buf, len);
267 continue;
269 foundhdr = 1;
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]) ||
275 buf[2] != ' ')
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);
282 break;
285 if (debug == DEBUG_HEADER) {
286 t = memmem(buf, len, "\r\n", 2);
287 assert(t != NULL);
288 *t = '\0';
289 printf("%s\n", buf);
290 break;
293 if (debug == DEBUG_META) {
294 t = memmem(buf, len, "\r\n", 2);
295 assert(t != NULL);
296 *t = '\0';
297 printf("%s\n", buf+3);
298 break;
301 if (debug == DEBUG_ALL) {
302 write(1, buf, len);
303 continue;
306 /* skip the header */
307 t = memmem(buf, len, "\r\n", 2);
308 assert(t != NULL);
309 if (code < 20 || code >= 30) {
310 *t = '\0';
311 fprintf(stderr, "Server says: ");
312 safeprint(stderr, buf + 3); /* skip return code */
314 t += 2; /* skip \r\n */
315 len -= t - buf;
316 write(1, t, len);
319 for (;;) {
320 switch (tls_close(ctx)) {
321 case TLS_WANT_POLLIN:
322 case TLS_WANT_POLLOUT:
323 continue;
324 case -1:
325 warnx("tls_close: %s", tls_error(ctx));
326 /* fallthrough */
327 default:
328 tls_free(ctx);
329 return code;
334 static void __attribute__((noreturn))
335 usage(void)
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",
340 getprogname());
341 fprintf(stderr, " [-T seconds] gemini://...\n");
342 exit(1);
345 static int
346 parse_debug(const char *arg)
348 if (!strcmp(arg, "none"))
349 return DEBUG_NONE;
350 if (!strcmp(arg, "code"))
351 return DEBUG_CODE;
352 if (!strcmp(arg, "header"))
353 return DEBUG_HEADER;
354 if (!strcmp(arg, "meta"))
355 return DEBUG_META;
356 if (!strcmp(arg, "all"))
357 return DEBUG_ALL;
358 usage();
361 static void
362 parse_proxy(const char *arg)
364 char *at;
366 if ((proxy_host = strdup(arg)) == NULL)
367 err(1, "strdup");
369 proxy_port = "1965";
371 if ((at = strchr(proxy_host, ':')) == NULL)
372 return;
373 *at = '\0';
374 proxy_port = ++at;
376 if (strchr(proxy_port, ':') != NULL)
377 errx(1, "invalid port %s", proxy_port);
380 int
381 main(int argc, char **argv)
383 int ch, code;
384 const char *errstr;
386 setlocale(LC_CTYPE, "");
388 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
389 switch (ch) {
390 case '2':
391 flag2 = 1;
392 break;
393 case '3':
394 flag3 = 1;
395 break;
396 case 'C':
397 cert = optarg;
398 break;
399 case 'd':
400 debug = parse_debug(optarg);
401 break;
402 case 'H':
403 sni = optarg;
404 break;
405 case 'K':
406 key = optarg;
407 break;
408 case 'N':
409 dont_verify_name = 1;
410 break;
411 case 'n':
412 nop = 1;
413 break;
414 case 'P':
415 parse_proxy(optarg);
416 dont_verify_name = 1;
417 break;
418 case 'T':
419 timer = strtonum(optarg, 1, 1000, &errstr);
420 if (errstr != NULL)
421 errx(1, "timeout is %s: %s",
422 errstr, optarg);
423 signal(SIGALRM, timeout);
424 alarm(timer);
425 break;
426 default:
427 usage();
430 argc -= optind;
431 argv += optind;
433 if (flag2 + flag3 > 1) {
434 warnx("only -2 or -3 can be specified at the same time");
435 usage();
438 if ((cert != NULL && key == NULL) ||
439 (cert == NULL && key != NULL)) {
440 warnx("cert or key is missing");
441 usage();
444 if (argc != 1)
445 usage();
447 load_tls_conf();
449 signal(SIGPIPE, SIG_IGN);
451 #ifdef __OpenBSD__
452 if (pledge("stdio inet dns", NULL) == -1)
453 err(1, "pledge");
454 #endif
456 code = get(*argv);
457 if (code >= 20 && code < 30)
458 return 0;
459 return code;