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 <string.h>
26 enum debug {
27 DEBUG_NONE,
28 DEBUG_CODE,
29 DEBUG_HEADER,
30 DEBUG_META,
31 DEBUG_ALL,
32 };
34 /* flags */
35 int debug;
36 int dont_verify_name;
37 int flag2;
38 int flag3;
39 int nop;
40 int redirects = 5;
41 int timer;
42 const char *cert;
43 const char *key;
44 const char *proxy_host;
45 const char *proxy_port;
46 const char *sni;
48 /* state */
49 struct tls_config *tls_conf;
51 static void
52 timeout(int signo)
53 {
54 dprintf(2, "%s: timer expired\n", getprogname());
55 exit(1);
56 }
58 static void
59 load_tls_conf(void)
60 {
61 if ((tls_conf = tls_config_new()) == NULL)
62 err(1, "tls_config_new");
64 tls_config_insecure_noverifycert(tls_conf);
65 if (dont_verify_name)
66 tls_config_insecure_noverifyname(tls_conf);
68 if (flag2 &&
69 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_2) == -1)
70 errx(1, "can't set TLSv1.2");
71 if (flag3 &&
72 tls_config_set_protocols(tls_conf, TLS_PROTOCOL_TLSv1_3) == -1)
73 errx(1, "can't set TLSv1.3");
75 if (cert != NULL &&
76 tls_config_set_keypair_file(tls_conf, cert, key) == -1)
77 errx(1, "can't load client certificate %s", cert);
78 }
80 static void
81 connectto(struct tls *ctx, const char *host, const char *port)
82 {
83 struct addrinfo hints, *res, *res0;
84 int error;
85 int saved_errno;
86 int s;
87 const char *cause = NULL;
88 const char *sname;
90 if (proxy_host != NULL) {
91 host = proxy_host;
92 port = proxy_port;
93 }
95 if ((sname = sni) == NULL)
96 sname = host;
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);
102 if (error)
103 errx(1, "%s", gai_strerror(error));
105 s = -1;
106 for (res = res0; res != NULL; res = res->ai_next) {
107 s = socket(res->ai_family, res->ai_socktype,
108 res->ai_protocol);
109 if (s == -1) {
110 cause = "socket";
111 continue;
114 if (connect(s, res->ai_addr, res->ai_addrlen) == -1) {
115 cause = "connect";
116 saved_errno = errno;
117 close(s);
118 errno = saved_errno;
119 s = -1;
120 continue;
123 break;
126 if (s == -1)
127 err(1, "%s: can't connect to %s:%s", cause,
128 host, port);
130 freeaddrinfo(res0);
132 if (tls_connect_socket(ctx, s, sname) == -1)
133 errx(1, "tls_connect_socket: %s", tls_error(ctx));
136 static void
137 doreq(struct tls *ctx, const char *buf)
139 size_t s;
140 ssize_t w;
142 s = strlen(buf);
143 while (s != 0) {
144 switch (w = tls_write(ctx, buf, s)) {
145 case 0:
146 case -1:
147 errx(1, "tls_write: %s", tls_error(ctx));
148 case TLS_WANT_POLLIN:
149 case TLS_WANT_POLLOUT:
150 continue;
153 s -= w;
154 buf += w;
158 static size_t
159 dorep(struct tls *ctx, void *buf, size_t len)
161 ssize_t w;
162 size_t tot = 0;
164 while (len != 0) {
165 switch (w = tls_read(ctx, buf, len)) {
166 case 0:
167 return tot;
168 case -1:
169 errx(1, "tls_write: %s", tls_error(ctx));
170 case TLS_WANT_POLLIN:
171 case TLS_WANT_POLLOUT:
172 continue;
175 len -= w;
176 buf += w;
177 tot += w;
180 return tot;
183 static int
184 get(const char *r)
186 struct tls *ctx;
187 struct iri iri;
188 int foundhdr = 0, code = -1, od;
189 char iribuf[GEMINI_URL_LEN];
190 char req[GEMINI_URL_LEN];
191 char buf[2048];
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);
206 if (nop)
207 errx(0, "IRI OK");
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));
215 host = iri.host;
216 port = "1965";
217 if (*iri.port != '\0')
218 port = iri.port;
220 connectto(ctx, host, port);
222 od = 0;
223 while (!od) {
224 switch (tls_handshake(ctx)) {
225 case 0:
226 od = 1;
227 break;
228 case -1:
229 errx(1, "handshake: %s", tls_error(ctx));
233 doreq(ctx, req);
235 for (;;) {
236 char *t;
237 size_t len;
239 len = dorep(ctx, buf, sizeof(buf));
240 if (len == 0)
241 goto close;
243 if (foundhdr) {
244 write(1, buf, len);
245 continue;
247 foundhdr = 1;
249 if (memmem(buf, len, "\r\n", 2) == NULL)
250 errx(1, "invalid reply: no \\r\\n");
251 if (!isdigit(buf[0]) || !isdigit(buf[1]) || buf[2] != ' ')
252 errx(1, "invalid reply: invalid response format");
254 code = (buf[0] - '0') * 10 + buf[1] - '0';
256 if (debug == DEBUG_CODE) {
257 printf("%d\n", code);
258 goto close;
261 if (debug == DEBUG_HEADER) {
262 t = memmem(buf, len, "\r\n", 2);
263 assert(t != NULL);
264 *t = '\0';
265 printf("%s\n", buf);
266 goto close;
269 if (debug == DEBUG_META) {
270 t = memmem(buf, len, "\r\n", 2);
271 assert(t != NULL);
272 *t = '\0';
273 printf("%s\n", buf+3);
274 goto close;
277 if (debug == DEBUG_ALL) {
278 write(1, buf, len);
279 continue;
282 /* skip the header */
283 t = memmem(buf, len, "\r\n", 2);
284 assert(t != NULL);
285 t += 2; /* skip \r\n */
286 len -= t - buf;
287 write(1, t, len);
290 close:
291 od = tls_close(ctx);
292 if (od == TLS_WANT_POLLIN || od == TLS_WANT_POLLOUT)
293 goto close;
295 tls_close(ctx);
296 tls_free(ctx);
297 return code;
300 static void __attribute__((noreturn))
301 usage(void)
303 fprintf(stderr, "version: " GG_STRING "\n");
304 fprintf(stderr, "usage: %s [-23Nn] [-C cert] [-d mode] [-H sni] "
305 "[-K key] [-P host[:port]]\n",
306 getprogname());
307 fprintf(stderr, " [-T seconds] gemini://...\n");
308 exit(1);
311 static int
312 parse_debug(const char *arg)
314 if (!strcmp(arg, "none"))
315 return DEBUG_NONE;
316 if (!strcmp(arg, "code"))
317 return DEBUG_CODE;
318 if (!strcmp(arg, "header"))
319 return DEBUG_HEADER;
320 if (!strcmp(arg, "meta"))
321 return DEBUG_META;
322 if (!strcmp(arg, "all"))
323 return DEBUG_ALL;
324 usage();
327 static void
328 parse_proxy(const char *arg)
330 char *at;
332 if ((proxy_host = strdup(arg)) == NULL)
333 err(1, "strdup");
335 proxy_port = "1965";
337 if ((at = strchr(proxy_host, ':')) == NULL)
338 return;
339 *at = '\0';
340 proxy_port = ++at;
342 if (strchr(proxy_port, ':') != NULL)
343 errx(1, "invalid port %s", proxy_port);
346 int
347 main(int argc, char **argv)
349 int ch, code;
350 const char *errstr;
352 while ((ch = getopt(argc, argv, "23C:d:H:K:NP:T:")) != -1) {
353 switch (ch) {
354 case '2':
355 flag2 = 1;
356 break;
357 case '3':
358 flag3 = 1;
359 break;
360 case 'C':
361 cert = optarg;
362 break;
363 case 'd':
364 debug = parse_debug(optarg);
365 break;
366 case 'H':
367 sni = optarg;
368 break;
369 case 'K':
370 key = optarg;
371 break;
372 case 'N':
373 dont_verify_name = 1;
374 break;
375 case 'n':
376 nop = 1;
377 break;
378 case 'P':
379 parse_proxy(optarg);
380 dont_verify_name = 1;
381 break;
382 case 'T':
383 timer = strtonum(optarg, 1, 1000, &errstr);
384 if (errstr != NULL)
385 errx(1, "timeout is %s: %s",
386 errstr, optarg);
387 signal(SIGALRM, timeout);
388 alarm(timer);
389 break;
390 default:
391 usage();
394 argc -= optind;
395 argv += optind;
397 if (flag2 + flag3 > 1) {
398 warnx("only -2 or -3 can be specified at the same time");
399 usage();
402 if ((cert != NULL && key == NULL) ||
403 (cert == NULL && key != NULL)) {
404 warnx("cert or key is missing");
405 usage();
408 if (argc != 1)
409 usage();
411 load_tls_conf();
413 signal(SIGPIPE, SIG_IGN);
415 #ifdef __OpenBSD__
416 if (pledge("stdio inet dns", NULL) == -1)
417 err(1, "pledge");
418 #endif
420 code = get(*argv);
422 return code < 20 || code >= 30;