Blob


1 /*
2 * Copyright (c) 2021 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>
27 enum debug {
28 DEBUG_NONE,
29 DEBUG_CODE,
30 DEBUG_HEADER,
31 DEBUG_META,
32 DEBUG_ALL,
33 };
35 /* flags */
36 int debug;
37 int dont_verify_name;
38 int flag2;
39 int flag3;
40 int nop;
41 int redirects = 5;
42 int timer;
43 const char *cert;
44 const char *key;
45 const char *proxy_host;
46 const char *proxy_port;
47 const char *sni;
49 /* state */
50 struct tls_config *tls_conf;
52 static void
53 timeout(int signo)
54 {
55 dprintf(2, "%s: timer expired\n", getprogname());
56 exit(1);
57 }
59 static void
60 load_tls_conf(void)
61 {
62 if ((tls_conf = tls_config_new()) == NULL)
63 err(1, "tls_config_new");
65 tls_config_insecure_noverifycert(tls_conf);
66 if (dont_verify_name)
67 tls_config_insecure_noverifyname(tls_conf);
69 if (flag2 &&
70 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
71 errx(1, "can't set TLSv1.2");
72 if (flag3 &&
73 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
74 errx(1, "can't set TLSv1.3");
76 if (cert != NULL &&
77 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
78 errx(1, "can't load client certificate %s", cert);
79 }
81 static void
82 connectto(struct tls *ctx, const char *host, const char *port)
83 {
84 struct addrinfo hints, *res, *res0;
85 int error;
86 int saved_errno;
87 int s;
88 const char *cause = NULL;
89 const char *sname;
91 if (proxy_host != NULL) {
92 host = proxy_host;
93 port = proxy_port;
94 }
96 if ((sname = sni) == NULL)
97 sname = host;
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);
103 if (error)
104 errx(1, "%s", gai_strerror(error));
106 s = -1;
107 for (res = res0; res != NULL; res = res->ai_next) {
108 s = socket(res->ai_family, res->ai_socktype,
109 res->ai_protocol);
110 if (s == -1) {
111 cause = "socket";
112 continue;
115 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
116 cause = "connect";
117 saved_errno = errno;
118 close(s);
119 errno = saved_errno;
120 s = -1;
121 continue;
124 break;
127 if (s == -1)
128 err(1, "%s: can't connect to %s:%s", cause,
129 host, port);
131 freeaddrinfo(res0);
133 if (tls_connect_socket(ctx, s, sname) == -1)
134 errx(1, "tls_connect_socket: %s", tls_error(ctx));
137 static void
138 doreq(struct tls *ctx, const char *buf)
140 size_t s;
141 ssize_t w;
143 s = strlen(buf);
144 while (s != 0) {
145 switch (w = tls_write(ctx, buf, s)) {
146 case 0:
147 case -1:
148 errx(1, "tls_write: %s", tls_error(ctx));
149 case TLS_WANT_POLLIN:
150 case TLS_WANT_POLLOUT:
151 continue;
154 s -= w;
155 buf += w;
159 static size_t
160 dorep(struct tls *ctx, uint8_t *buf, size_t len)
162 ssize_t w;
163 size_t tot = 0;
165 while (len != 0) {
166 switch (w = tls_read(ctx, buf, len)) {
167 case 0:
168 return tot;
169 case -1:
170 errx(1, "tls_write: %s", tls_error(ctx));
171 case TLS_WANT_POLLIN:
172 case TLS_WANT_POLLOUT:
173 continue;
176 len -= w;
177 buf += w;
178 tot += w;
181 return tot;
184 static int
185 get(const char *r)
187 struct tls *ctx;
188 struct iri iri;
189 int foundhdr = 0, code = -1, od;
190 char iribuf[GEMINI_URL_LEN];
191 char req[GEMINI_URL_LEN];
192 uint8_t buf[2048];
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);
207 if (nop)
208 errx(0, "IRI OK");
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));
216 host = iri.host;
217 port = "1965";
218 if (*iri.port != '\0')
219 port = iri.port;
221 connectto(ctx, host, port);
223 od = 0;
224 while (!od) {
225 switch (tls_handshake(ctx)) {
226 case 0:
227 od = 1;
228 break;
229 case -1:
230 errx(1, "handshake: %s", tls_error(ctx));
234 doreq(ctx, req);
236 for (;;) {
237 uint8_t *t;
238 size_t len;
240 len = dorep(ctx, buf, sizeof(buf));
241 if (len == 0)
242 goto close;
244 if (foundhdr) {
245 write(1, buf, len);
246 continue;
248 foundhdr = 1;
250 if (memmem(buf, len, "\r\n", 2) == NULL)
251 errx(1, "invalid reply: no \\r\\n");
252 if (!isdigit((unsigned char)buf[0]) ||
253 !isdigit((unsigned char)buf[1]) ||
254 buf[2] != ' ')
255 errx(1, "invalid reply: invalid response format");
257 code = (buf[0] - '0') * 10 + buf[1] - '0';
259 if (debug == DEBUG_CODE) {
260 printf("%d\n", code);
261 goto close;
264 if (debug == DEBUG_HEADER) {
265 t = memmem(buf, len, "\r\n", 2);
266 assert(t != NULL);
267 *t = '\0';
268 printf("%s\n", buf);
269 goto close;
272 if (debug == DEBUG_META) {
273 t = memmem(buf, len, "\r\n", 2);
274 assert(t != NULL);
275 *t = '\0';
276 printf("%s\n", buf+3);
277 goto close;
280 if (debug == DEBUG_ALL) {
281 write(1, buf, len);
282 continue;
285 /* skip the header */
286 t = memmem(buf, len, "\r\n", 2);
287 assert(t != NULL);
288 t += 2; /* skip \r\n */
289 len -= t - buf;
290 write(1, t, len);
293 close:
294 od = tls_close(ctx);
295 if (od == TLS_WANT_POLLIN || od == TLS_WANT_POLLOUT)
296 goto close;
298 tls_close(ctx);
299 tls_free(ctx);
300 return code;
303 static void __attribute__((noreturn))
304 usage(void)
306 fprintf(stderr, "version: " GG_STRING "\n");
307 fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
308 "[-K key] [-P host[:port]]\n",
309 getprogname());
310 fprintf(stderr, " [-T seconds] gemini://...\n");
311 exit(1);
314 static int
315 parse_debug(const char *arg)
317 if (!strcmp(arg, "none"))
318 return DEBUG_NONE;
319 if (!strcmp(arg, "code"))
320 return DEBUG_CODE;
321 if (!strcmp(arg, "header"))
322 return DEBUG_HEADER;
323 if (!strcmp(arg, "meta"))
324 return DEBUG_META;
325 if (!strcmp(arg, "all"))
326 return DEBUG_ALL;
327 usage();
330 static void
331 parse_proxy(const char *arg)
333 char *at;
335 if ((proxy_host = strdup(arg)) == NULL)
336 err(1, "strdup");
338 proxy_port = "1965";
340 if ((at = strchr(proxy_host, ':')) == NULL)
341 return;
342 *at = '\0';
343 proxy_port = ++at;
345 if (strchr(proxy_port, ':') != NULL)
346 errx(1, "invalid port %s", proxy_port);
349 int
350 main(int argc, char **argv)
352 int ch, code;
353 const char *errstr;
355 setlocale(LC_CTYPE, "");
357 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
358 switch (ch) {
359 case '2':
360 flag2 = 1;
361 break;
362 case '3':
363 flag3 = 1;
364 break;
365 case 'C':
366 cert = optarg;
367 break;
368 case 'd':
369 debug = parse_debug(optarg);
370 break;
371 case 'H':
372 sni = optarg;
373 break;
374 case 'K':
375 key = optarg;
376 break;
377 case 'N':
378 dont_verify_name = 1;
379 break;
380 case 'n':
381 nop = 1;
382 break;
383 case 'P':
384 parse_proxy(optarg);
385 dont_verify_name = 1;
386 break;
387 case 'T':
388 timer = strtonum(optarg, 1, 1000, &errstr);
389 if (errstr != NULL)
390 errx(1, "timeout is %s: %s",
391 errstr, optarg);
392 signal(SIGALRM, timeout);
393 alarm(timer);
394 break;
395 default:
396 usage();
399 argc -= optind;
400 argv += optind;
402 if (flag2 + flag3 > 1) {
403 warnx("only -2 or -3 can be specified at the same time");
404 usage();
407 if ((cert != NULL && key == NULL) ||
408 (cert == NULL && key != NULL)) {
409 warnx("cert or key is missing");
410 usage();
413 if (argc != 1)
414 usage();
416 load_tls_conf();
418 signal(SIGPIPE, SIG_IGN);
420 #ifdef __OpenBSD__
421 if (pledge("stdio inet dns", NULL) == -1)
422 err(1, "pledge");
423 #endif
425 code = get(*argv);
427 return code < 20 || code >= 30;