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 const struct parser gemtext_parser, textplain_parser, textpatch_parser;
85 void load_page_from_str(struct tab *tab, const char *page) { return; }
86 void erase_buffer(struct buffer *buffer) { return; }
88 static void __dead
89 usage(void)
90 {
91 size_t i;
93 fprintf(stderr, "usage: %s command [args...]\n", getprogname());
94 fprintf(stderr, "Available subcommands are:");
95 for (i = 0; i < nitems(cmds); ++i)
96 fprintf(stderr, " %s", cmds[i].name);
97 fputs(".\n", stderr);
98 exit(1);
99 }
101 static void __dead
102 cmd_usage(const struct cmd *cmd)
104 fprintf(stderr, "usage: %s %s%s%s\n", getprogname(), cmd->name,
105 *cmd->usage ? " " : "", cmd->usage);
106 exit(1);
109 int
110 main(int argc, char **argv)
112 const struct cmd *cmd;
113 size_t i;
115 /*
116 * Can't use portably getopt() since there's no cross-platform
117 * way of resetting it.
118 */
120 if (argc == 0)
121 usage();
122 argc--, argv++;
124 if (argc == 0)
125 usage();
127 if (!strcmp(*argv, "--"))
128 argc--, argv++;
129 else if (**argv == '-')
130 usage();
132 if (argc == 0)
133 usage();
135 for (i = 0; i < nitems(cmds); ++i) {
136 cmd = &cmds[i];
138 if (strcmp(cmd->name, argv[0]) != 0)
139 continue;
141 fs_init();
142 if (certs_init(certs_file) == -1)
143 errx(1, "failed to initialize the cert store.");
144 return (cmd->fn(cmd, argc, argv));
147 warnx("unknown command: %s", argv[0]);
148 usage();
151 static int
152 cmd_generate(const struct cmd *cmd, int argc, char **argv)
154 const char *name;
155 char path[PATH_MAX];
156 int ch, r;
157 int ec = 1;
159 while ((ch = getopt(argc, argv, "t:")) != -1) {
160 switch (ch) {
161 case 't':
162 if (!strcasecmp(optarg, "ec")) {
163 ec = 1;
164 break;
166 if (!strcasecmp(optarg, "rsa")) {
167 ec = 0;
168 break;
170 errx(1, "Unknown key type requested: %s", optarg);
172 default:
173 cmd_usage(cmd);
176 argc -= optind;
177 argv += optind;
179 if (argc != 1)
180 cmd_usage(cmd);
182 name = *argv;
184 r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
185 if (r < 0 || (size_t)r >= sizeof(path))
186 errx(1, "path too long");
188 if (cert_new(name, path, ec) == -1)
189 errx(1, "failure generating the key");
191 return 0;
194 static int
195 cmd_remove(const struct cmd *cmd, int argc, char **argv)
197 const char *name;
198 char path[PATH_MAX];
199 int ch, r;
201 while ((ch = getopt(argc, argv, "")) != -1) {
202 switch (ch) {
203 default:
204 cmd_usage(cmd);
207 argc -= optind;
208 argv += optind;
210 if (argc != 1)
211 cmd_usage(cmd);
213 name = *argv;
215 r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
216 if (r < 0 || (size_t)r >= sizeof(path))
217 errx(1, "path too long");
219 if (unlink(path) == -1)
220 err(1, "unlink %s", path);
221 return 0;
224 static int
225 cmd_import(const struct cmd *cmd, int argc, char **argv)
227 struct tls_config *conf;
228 const char *key = NULL, *cert = NULL;
229 char path[PATH_MAX], sfn[PATH_MAX];
230 FILE *fp;
231 uint8_t *keym, *certm;
232 size_t keyl, certl;
233 int ch, r, fd;
234 int force = 0;
236 while ((ch = getopt(argc, argv, "C:K:f")) != -1) {
237 switch (ch) {
238 case 'C':
239 cert = optarg;
240 break;
241 case 'K':
242 key = optarg;
243 break;
244 case 'f':
245 force = 1;
246 break;
247 default:
248 cmd_usage(cmd);
251 argc -= optind;
252 argv += optind;
254 if (argc != 1)
255 cmd_usage(cmd);
257 if (key == NULL)
258 key = cert;
259 if (cert == NULL)
260 cmd_usage(cmd);
262 if ((keym = tls_load_file(key, &keyl, NULL)) == NULL)
263 err(1, "can't open %s", key);
264 if ((certm = tls_load_file(cert, &certl, NULL)) == NULL)
265 err(1, "can't open %s", cert);
267 if ((conf = tls_config_new()) == NULL)
268 err(1, "tls_config_new");
270 if (tls_config_set_keypair_mem(conf, certm, certl, keym, keyl) == -1)
271 errx(1, "failed to load the keypair: %s",
272 tls_config_error(conf));
274 tls_config_free(conf);
276 r = snprintf(path, sizeof(path), "%s/%s", cert_dir, *argv);
277 if (r < 0 || (size_t)r >= sizeof(path))
278 err(1, "identity name too long");
280 strlcpy(sfn, cert_dir_tmp, sizeof(sfn));
281 if ((fd = mkstemp(sfn)) == -1 ||
282 (fp = fdopen(fd, "w")) == NULL) {
283 if (fd != -1) {
284 warn("fdopen");
285 unlink(sfn);
286 close(fd);
287 } else
288 warn("mkstamp");
289 return 1;
292 if (fwrite(certm, 1, certl, fp) != certl) {
293 warn("fwrite");
294 unlink(sfn);
295 fclose(fp);
296 return 1;
298 if (strcmp(key, cert) != 0 &&
299 fwrite(keym, 1, keyl, fp) != keyl) {
300 warn("fwrite");
301 unlink(sfn);
302 fclose(fp);
303 return 1;
306 if (fflush(fp) == EOF) {
307 warn("fflush");
308 unlink(sfn);
309 fclose(fp);
310 return 1;
312 fclose(fp);
314 if (!force && access(path, F_OK) == 0) {
315 warnx("identity %s already exists", *argv);
316 unlink(sfn);
317 return 1;
320 if (rename(sfn, path) == -1) {
321 warn("can't rename");
322 unlink(sfn);
323 return 1;
326 return (0);
329 static int
330 cmd_export(const struct cmd *cmd, int argc, char **argv)
332 FILE *fp, *outfp;
333 const char *cert = NULL;
334 const char *identity = NULL;
335 char path[PATH_MAX];
336 char buf[BUFSIZ];
337 size_t l;
338 int ch, r;
340 while ((ch = getopt(argc, argv, "C:")) != -1) {
341 switch (ch) {
342 case 'C':
343 cert = optarg;
344 break;
345 default:
346 cmd_usage(cmd);
349 argc -= optind;
350 argv += optind;
351 if (argc != 1)
352 cmd_usage(cmd);
353 identity = argv[0];
355 if (cert == NULL)
356 cmd_usage(cmd);
358 r = snprintf(path, sizeof(path), "%s/%s", cert_dir, identity);
359 if (r < 0 || (size_t)r >= sizeof(path))
360 err(1, "path too long");
361 if ((fp = fopen(path, "r")) == NULL)
362 err(1, "can't open %s", path);
364 if ((outfp = fopen(cert, "w")) == NULL)
365 err(1, "can't open %s", cert);
367 for (;;) {
368 l = fread(buf, 1, sizeof(buf), fp);
369 if (l == 0)
370 break;
371 if (fwrite(buf, 1, l, outfp) != l)
372 err(1, "fwrite");
374 if (ferror(fp))
375 err(1, "fread");
377 return 0;
380 static int
381 cmd_list(const struct cmd *cmd, int argc, char **argv)
383 char **id;
384 int ch;
386 while ((ch = getopt(argc, argv, "")) != -1) {
387 switch (ch) {
388 default:
389 cmd_usage(cmd);
392 argc -= optind;
393 argv += optind;
394 if (argc != 0)
395 cmd_usage(cmd);
397 for (id = identities; *id; ++id)
398 puts(*id);
400 return (0);
403 static int
404 cmd_mappings(const struct cmd *cmd, int argc, char **argv)
406 struct ccert *c;
407 const char *id = NULL;
408 int ch, defport;
409 size_t i;
411 while ((ch = getopt(argc, argv, "")) != -1) {
412 switch (ch) {
413 default:
414 cmd_usage(cmd);
417 argc -= optind;
418 argv += optind;
419 if (argc == 1) {
420 if ((id = ccert(*argv)) == NULL)
421 errx(1, "unknown identity %s", *argv);
422 argc--, argv++;
424 if (argc != 0)
425 cmd_usage(cmd);
427 for (i = 0; i < cert_store.len; ++i) {
428 c = &cert_store.certs[i];
430 if (id && strcmp(id, c->cert) != 0)
431 continue;
433 defport = !strcmp(c->port, "1965");
435 printf("%s\t%s%s%s%s\n", c->cert, c->host,
436 defport ? "" : ":", defport ? "" : c->port,
437 c->path);
440 return (0);
443 static struct iri *
444 parseiri(char *spec)
446 static struct iri iri;
447 const char *errstr;
448 char *host, *port = NULL, *path = NULL;
450 memset(&iri, 0, sizeof(iri));
452 host = spec;
454 port = host + strcspn(host, ":/");
455 if (*port == ':') {
456 *port++ = '\0';
457 if ((path = strchr(port, '/')) != NULL)
458 *path++ = '\0';
459 } else if (*port == '/') {
460 *port++ = '\0';
461 path = port;
462 port = NULL;
463 } else
464 port = NULL;
466 strlcpy(iri.iri_host, host, sizeof(iri.iri_host));
467 strlcpy(iri.iri_portstr, port ? port : "1965", sizeof(iri.iri_portstr));
468 strlcpy(iri.iri_path, path ? path : "/", sizeof(iri.iri_path));
470 iri.iri_port = strtonum(iri.iri_portstr, 0, UINT16_MAX, &errstr);
471 if (errstr)
472 err(1, "port number is %s: %s", errstr, iri.iri_portstr);
474 return &iri;
477 static int
478 cmd_use(const struct cmd *cmd, int argc, char **argv)
480 char *cert, *spec;
481 int ch;
483 while ((ch = getopt(argc, argv, "")) != -1) {
484 switch (ch) {
485 default:
486 cmd_usage(cmd);
489 argc -= optind;
490 argv += optind;
491 if (argc != 2)
492 cmd_usage(cmd);
494 cert = argv[0];
495 spec = argv[1];
497 if (ccert(cert) == NULL)
498 err(1, "unknown identity %s", cert);
500 if (cert_save_for(cert, parseiri(spec), 1) == -1)
501 errx(1, "failed to save the certificate");
503 return 0;
506 static int
507 cmd_forget(const struct cmd *cmd, int argc, char **argv)
509 char *cert, *spec;
510 int ch;
512 while ((ch = getopt(argc, argv, "")) != -1) {
513 switch (ch) {
514 default:
515 cmd_usage(cmd);
518 argc -= optind;
519 argv += optind;
520 if (argc != 2)
521 cmd_usage(cmd);
523 cert = argv[0];
524 spec = argv[1];
526 if (ccert(cert) == NULL)
527 err(1, "unknown identity %s", cert);
529 if (cert_delete_for(cert, parseiri(spec), 1) == -1)
530 errx(1, "failed to save the certificate");
532 return 0;