Blob


1 /*
2 * Copyright (c) 2024 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 "compat.h"
19 #include <limits.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <tls.h>
24 #include <unistd.h>
26 #include "certs.h"
27 #include "fs.h"
28 #include "parser.h"
29 #include "telescope.h"
31 #ifndef nitems
32 #define nitems(x) (sizeof(x) / sizeof((x)[0]))
33 #endif
35 struct cmd;
37 static int cmd_generate(const struct cmd *, int, char **);
38 static int cmd_remove(const struct cmd *, int, char **);
39 static int cmd_import(const struct cmd *, int, char **);
40 static int cmd_export(const struct cmd *, int, char **);
41 static int cmd_list(const struct cmd *, int, char **);
42 static int cmd_mappings(const struct cmd *, int, char **);
43 static int cmd_use(const struct cmd *, int, char **);
44 static int cmd_forget(const struct cmd *, int, char **);
46 struct cmd {
47 const char *name;
48 int (*fn)(const struct cmd *, int argc, char **argv);
49 const char *usage;
50 };
52 static const struct cmd cmds[] = {
53 { "generate", cmd_generate, "[-t type] name" },
54 { "remove", cmd_remove, "name" },
55 { "import", cmd_import, "-C cert [-K key] name" },
56 { "export", cmd_export, "-C cert name" },
57 { "list", cmd_list, "" },
58 { "mappings", cmd_mappings, "" },
59 { "use", cmd_use, "name host[:port][/path]" },
60 { "forget", cmd_forget, "name host[:port][/path]" },
61 };
63 /*
64 * Provide some symbols so that we can pull in some subsystems without
65 * their the dependencies.
66 */
68 const uint8_t *about_about;
69 size_t about_about_len;
70 const uint8_t *about_blank;
71 size_t about_blank_len;
72 const uint8_t *about_crash;
73 size_t about_crash_len;
74 const uint8_t *about_help;
75 size_t about_help_len;
76 const uint8_t *about_license;
77 size_t about_license_len;
78 const uint8_t *about_new;
79 size_t about_new_len;
80 const uint8_t *bookmarks;
81 size_t bookmarks_len;
83 void gemtext_initparser(struct parser *p) { return; }
84 void textpatch_initparser(struct parser *p) { return; }
85 void textplain_initparser(struct parser *p) { return; }
87 void load_page_from_str(struct tab *tab, const char *page) { return; }
88 void erase_buffer(struct buffer *buffer) { return; }
90 static void __dead
91 usage(void)
92 {
93 size_t i;
95 fprintf(stderr, "usage: %s command [args...]\n", getprogname());
96 fprintf(stderr, "Available subcommands are:");
97 for (i = 0; i < nitems(cmds); ++i)
98 fprintf(stderr, " %s", cmds[i].name);
99 fputs(".\n", stderr);
100 exit(1);
103 static void __dead
104 cmd_usage(const struct cmd *cmd)
106 fprintf(stderr, "usage: %s %s%s%s\n", getprogname(), cmd->name,
107 *cmd->usage ? " " : "", cmd->usage);
108 exit(1);
111 int
112 main(int argc, char **argv)
114 const struct cmd *cmd;
115 size_t i;
117 /*
118 * Can't use portably getopt() since there's no cross-platform
119 * way of resetting it.
120 */
122 if (argc == 0)
123 usage();
124 argc--, argv++;
126 if (argc == 0)
127 usage();
129 if (!strcmp(*argv, "--"))
130 argc--, argv++;
131 else if (**argv == '-')
132 usage();
134 if (argc == 0)
135 usage();
137 for (i = 0; i < nitems(cmds); ++i) {
138 cmd = &cmds[i];
140 if (strcmp(cmd->name, argv[0]) != 0)
141 continue;
143 fs_init();
144 if (certs_init(certs_file) == -1)
145 errx(1, "failed to initialize the cert store.");
146 return (cmd->fn(cmd, argc, argv));
149 warnx("unknown command: %s", argv[0]);
150 usage();
153 static int
154 cmd_generate(const struct cmd *cmd, int argc, char **argv)
156 const char *name;
157 char path[PATH_MAX];
158 int ch, r;
159 int ec = 1;
161 while ((ch = getopt(argc, argv, "t:")) != -1) {
162 switch (ch) {
163 case 't':
164 if (!strcasecmp(optarg, "ec")) {
165 ec = 1;
166 break;
168 if (!strcasecmp(optarg, "rsa")) {
169 ec = 0;
170 break;
172 errx(1, "Unknown key type requested: %s", optarg);
174 default:
175 cmd_usage(cmd);
178 argc -= optind;
179 argv += optind;
181 if (argc != 1)
182 cmd_usage(cmd);
184 name = *argv;
186 r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
187 if (r < 0 || (size_t)r >= sizeof(path))
188 errx(1, "path too long");
190 if (cert_new(name, path, ec) == -1)
191 errx(1, "failure generating the key");
193 return 0;
196 static int
197 cmd_remove(const struct cmd *cmd, int argc, char **argv)
199 const char *name;
200 char path[PATH_MAX];
201 int ch, r;
203 while ((ch = getopt(argc, argv, "")) != -1) {
204 switch (ch) {
205 default:
206 cmd_usage(cmd);
209 argc -= optind;
210 argv += optind;
212 if (argc != 1)
213 cmd_usage(cmd);
215 name = *argv;
217 r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
218 if (r < 0 || (size_t)r >= sizeof(path))
219 errx(1, "path too long");
221 if (unlink(path) == -1)
222 err(1, "unlink %s", path);
223 return 0;
226 static int
227 cmd_import(const struct cmd *cmd, int argc, char **argv)
229 struct tls_config *conf;
230 const char *key = NULL, *cert = NULL;
231 char path[PATH_MAX], sfn[PATH_MAX];
232 FILE *fp;
233 uint8_t *keym, *certm;
234 size_t keyl, certl;
235 int ch, r, fd;
236 int force = 0;
238 while ((ch = getopt(argc, argv, "C:K:f")) != -1) {
239 switch (ch) {
240 case 'C':
241 cert = optarg;
242 break;
243 case 'K':
244 key = optarg;
245 break;
246 case 'f':
247 force = 1;
248 break;
249 default:
250 cmd_usage(cmd);
253 argc -= optind;
254 argv += optind;
256 if (argc != 1)
257 cmd_usage(cmd);
259 if (key == NULL)
260 key = cert;
261 if (cert == NULL)
262 cmd_usage(cmd);
264 if ((keym = tls_load_file(key, &keyl, NULL)) == NULL)
265 err(1, "can't open %s", key);
266 if ((certm = tls_load_file(cert, &certl, NULL)) == NULL)
267 err(1, "can't open %s", cert);
269 if ((conf = tls_config_new()) == NULL)
270 err(1, "tls_config_new");
272 if (tls_config_set_keypair_mem(conf, certm, certl, keym, keyl) == -1)
273 errx(1, "failed to load the keypair: %s",
274 tls_config_error(conf));
276 tls_config_free(conf);
278 r = snprintf(path, sizeof(path), "%s/%s", cert_dir, *argv);
279 if (r < 0 || (size_t)r >= sizeof(path))
280 err(1, "identity name too long");
282 strlcpy(sfn, cert_dir_tmp, sizeof(sfn));
283 if ((fd = mkstemp(sfn)) == -1 ||
284 (fp = fdopen(fd, "w")) == NULL) {
285 if (fd != -1) {
286 warn("fdopen");
287 unlink(sfn);
288 close(fd);
289 } else
290 warn("mkstamp");
291 return 1;
294 if (fwrite(certm, 1, certl, fp) != certl) {
295 warn("fwrite");
296 unlink(sfn);
297 fclose(fp);
298 return 1;
300 if (strcmp(key, cert) != 0 &&
301 fwrite(keym, 1, keyl, fp) != keyl) {
302 warn("fwrite");
303 unlink(sfn);
304 fclose(fp);
305 return 1;
308 if (fflush(fp) == EOF) {
309 warn("fflush");
310 unlink(sfn);
311 fclose(fp);
312 return 1;
314 fclose(fp);
316 if (!force && access(path, F_OK) == 0) {
317 warnx("identity %s already exists", *argv);
318 unlink(sfn);
319 return 1;
322 if (rename(sfn, path) == -1) {
323 warn("can't rename");
324 unlink(sfn);
325 return 1;
328 return (0);
331 static int
332 cmd_export(const struct cmd *cmd, int argc, char **argv)
334 FILE *fp, *outfp;
335 const char *cert = NULL;
336 const char *identity = NULL;
337 char path[PATH_MAX];
338 char buf[BUFSIZ];
339 size_t l;
340 int ch, r;
342 while ((ch = getopt(argc, argv, "C:")) != -1) {
343 switch (ch) {
344 case 'C':
345 cert = optarg;
346 break;
347 default:
348 cmd_usage(cmd);
351 argc -= optind;
352 argv += optind;
353 if (argc != 1)
354 cmd_usage(cmd);
355 identity = argv[0];
357 if (cert == NULL)
358 cmd_usage(cmd);
360 r = snprintf(path, sizeof(path), "%s/%s", cert_dir, identity);
361 if (r < 0 || (size_t)r >= sizeof(path))
362 err(1, "path too long");
363 if ((fp = fopen(path, "r")) == NULL)
364 err(1, "can't open %s", path);
366 if ((outfp = fopen(cert, "w")) == NULL)
367 err(1, "can't open %s", cert);
369 for (;;) {
370 l = fread(buf, 1, sizeof(buf), fp);
371 if (l == 0)
372 break;
373 if (fwrite(buf, 1, l, outfp) != l)
374 err(1, "fwrite");
376 if (ferror(fp))
377 err(1, "fread");
379 return 0;
382 static int
383 cmd_list(const struct cmd *cmd, int argc, char **argv)
385 char **id;
386 int ch;
388 while ((ch = getopt(argc, argv, "")) != -1) {
389 switch (ch) {
390 default:
391 cmd_usage(cmd);
394 argc -= optind;
395 argv += optind;
396 if (argc != 0)
397 cmd_usage(cmd);
399 for (id = identities; *id; ++id)
400 puts(*id);
402 return (0);
405 static int
406 cmd_mappings(const struct cmd *cmd, int argc, char **argv)
408 struct ccert *c;
409 const char *id = NULL;
410 int ch, defport;
411 size_t i;
413 while ((ch = getopt(argc, argv, "")) != -1) {
414 switch (ch) {
415 default:
416 cmd_usage(cmd);
419 argc -= optind;
420 argv += optind;
421 if (argc == 1) {
422 if ((id = ccert(*argv)) == NULL)
423 errx(1, "unknown identity %s", *argv);
424 argc--, argv++;
426 if (argc != 0)
427 cmd_usage(cmd);
429 for (i = 0; i < cert_store.len; ++i) {
430 c = &cert_store.certs[i];
432 if (id && strcmp(id, c->cert) != 0)
433 continue;
435 defport = !strcmp(c->port, "1965");
437 printf("%s\t%s%s%s%s\n", c->cert, c->host,
438 defport ? "" : ":", defport ? "" : c->port,
439 c->path);
442 return (0);
445 static struct iri *
446 parseiri(char *spec)
448 static struct iri iri;
449 const char *errstr;
450 char *host, *port = NULL, *path = NULL;
452 memset(&iri, 0, sizeof(iri));
454 host = spec;
456 port = host + strcspn(host, ":/");
457 if (*port == ':') {
458 *port++ = '\0';
459 if ((path = strchr(port, '/')) != NULL)
460 *path++ = '\0';
461 } else if (*port == '/') {
462 *port++ = '\0';
463 path = port;
464 port = NULL;
465 } else
466 port = NULL;
468 strlcpy(iri.iri_host, host, sizeof(iri.iri_host));
469 strlcpy(iri.iri_portstr, port ? port : "1965", sizeof(iri.iri_portstr));
470 strlcpy(iri.iri_path, path ? path : "/", sizeof(iri.iri_path));
472 iri.iri_port = strtonum(iri.iri_portstr, 0, UINT16_MAX, &errstr);
473 if (errstr)
474 err(1, "port number is %s: %s", errstr, iri.iri_portstr);
476 return &iri;
479 static int
480 cmd_use(const struct cmd *cmd, int argc, char **argv)
482 char *cert, *spec;
483 int ch;
485 while ((ch = getopt(argc, argv, "")) != -1) {
486 switch (ch) {
487 default:
488 cmd_usage(cmd);
491 argc -= optind;
492 argv += optind;
493 if (argc != 2)
494 cmd_usage(cmd);
496 cert = argv[0];
497 spec = argv[1];
499 if (ccert(cert) == NULL)
500 err(1, "unknown identity %s", cert);
502 if (cert_save_for(cert, parseiri(spec), 1) == -1)
503 errx(1, "failed to save the certificate");
505 return 0;
508 static int
509 cmd_forget(const struct cmd *cmd, int argc, char **argv)
511 char *cert, *spec;
512 int ch;
514 while ((ch = getopt(argc, argv, "")) != -1) {
515 switch (ch) {
516 default:
517 cmd_usage(cmd);
520 argc -= optind;
521 argv += optind;
522 if (argc != 2)
523 cmd_usage(cmd);
525 cert = argv[0];
526 spec = argv[1];
528 if (ccert(cert) == NULL)
529 err(1, "unknown identity %s", cert);
531 if (cert_delete_for(cert, parseiri(spec), 1) == -1)
532 errx(1, "failed to save the certificate");
534 return 0;