commit 5a39f5931f4b5be1d57d75c7f25cff8adae5f235 from: Omar Polo date: Mon Feb 05 18:16:35 2024 UTC load and optionally remember client certificates This adds use-certificate, a user function to start using a certificate or switch to a different one. It asks whether to persist the certificate, if not it will only be used for the current session. use-certificate is implicitly called when the server replies with a 6x status code. commit - 12472189518006a105ca309e5443be98a646b8a1 commit + 5a39f5931f4b5be1d57d75c7f25cff8adae5f235 blob - 00c6e94de447c08d07ccb19a922584839ab9e024 blob + cd2a4d10283bf4ad5deabc320552d4c15cff03a3 --- certs.c +++ certs.c @@ -49,18 +49,17 @@ /* client certificate */ struct ccert { - char *line; /* fields below points inside here */ char *host; char *port; char *path; char *cert; }; -static struct cert_store { +static struct cstore { struct ccert *certs; size_t len; size_t cap; -} cert_store; +} cert_store, temp_store; char **identities; static size_t id_len, id_cap; @@ -122,70 +121,76 @@ certs_cmp(const void *a, const void *b) } static int -certs_store_add(const char *l) +certs_store_add(struct cstore *cstore, const char *host, const char *port, + const char *path, const char *cert) { - size_t newcap; - void *t; - char *line, *host, *port, *path, *cert; + struct ccert *c; + void *t; + size_t newcap; - if ((line = strdup(l)) == NULL) - return (-1); + if (cstore->len == cstore->cap) { + newcap = cstore->cap + 8; + t = reallocarray(cstore->certs, newcap, + sizeof(*cstore->certs)); + if (t == NULL) + return (-1); + cstore->certs = t; + cstore->cap = newcap; + } - host = line; - while (isspace((unsigned char)*host)) - ++host; + c = &cstore->certs[cstore->len]; + if ((c->host = strdup(host)) == NULL || + (c->port = strdup(port)) == NULL || + (c->path = strdup(path)) == NULL || + (c->cert = strdup(cert)) == NULL) { + free(c->host); + free(c->port); + free(c->path); + free(c->cert); + memset(c, 0, sizeof(*c)); + } + cstore->len++; - if (*host == '#') { - free(line); + return (0); +} + +static int +certs_store_parse_line(struct cstore *cstore, char *line) +{ + char *host, *port, *path, *cert; + + while (isspace((unsigned char)*line)) + ++line; + if (*line == '#' || *line == '\0') return (0); - } + host = line; + port = host + strcspn(host, " \t"); if (*port == '\0') - goto err; + return (-1); *port++ = '\0'; while (isspace((unsigned char)*port)) ++port; path = port + strcspn(port, " \t"); if (*path == '\0') - goto err; + return (-1); *path++ = '\0'; while (isspace((unsigned char)*path)) ++path; cert = path + strcspn(path, " \t"); if (*cert == '\0') - goto err; + return (-1); *cert++ = '\0'; while (isspace((unsigned char)*cert)) ++cert; if (*cert == '\0') - goto err; + return (-1); - if (cert_store.len == cert_store.cap) { - newcap = cert_store.cap + 8; - t = reallocarray(cert_store.certs, newcap, - sizeof(*cert_store.certs)); - if (t == NULL) - goto err; - cert_store.certs = t; - cert_store.cap = newcap; - } - - cert_store.certs[cert_store.len].line = line; - cert_store.certs[cert_store.len].host = host; - cert_store.certs[cert_store.len].port = port; - cert_store.certs[cert_store.len].path = path; - cert_store.certs[cert_store.len].cert = cert; - cert_store.len++; - - return (0); - - err: - free(line); - return (-1); + return (certs_store_add(cstore, host, port, path, cert)); } int @@ -226,7 +231,7 @@ certs_init(const char *certfile) if (line[linelen - 1] == '\n') line[--linelen] = '\0'; - if (certs_store_add(line) == -1) { + if (certs_store_parse_line(&cert_store, line) == -1) { fclose(fp); free(line); return (-1); @@ -283,25 +288,121 @@ path_under(const char *cpath, const char *tpath) return (cpath[-1] == '/'); } -const char * -cert_for(struct iri *iri) +static struct ccert * +find_cert_for(struct cstore *cstore, struct iri *iri) { struct ccert *c; size_t i; - for (i = 0; i < cert_store.len; ++i) { - c = &cert_store.certs[i]; + for (i = 0; i < cstore->len; ++i) { + c = &cstore->certs[i]; if (!strcmp(c->host, iri->iri_host) && !strcmp(c->port, iri->iri_portstr) && path_under(c->path, iri->iri_path)) - return (c->cert); + return (c); } return (NULL); } +const char * +cert_for(struct iri *iri) +{ + struct ccert *c; + + if ((c = find_cert_for(&temp_store, iri)) != NULL) + return (c->cert); + if ((c = find_cert_for(&cert_store, iri)) != NULL) + return (c->cert); + return (NULL); +} + +static int +write_cert_file(void) +{ + struct ccert *c; + FILE *fp; + char sfn[PATH_MAX]; + size_t i; + int fd, r; + + strlcpy(sfn, certs_file_tmp, sizeof(sfn)); + if ((fd = mkstemp(sfn)) == -1 || + (fp = fdopen(fd, "w")) == NULL) { + if (fd != -1) { + unlink(sfn); + close(fd); + } + return (-1); + } + + for (i = 0; i < cert_store.len; ++i) { + c = &cert_store.certs[i]; + if (c->cert == NULL) + continue; + + r = fprintf(fp, "%s\t%s\t%s\t%s\n", c->host, c->port, + c->path, c->cert); + if (r < 0) { + fclose(fp); + unlink(sfn); + return (-1); + } + } + + if (ferror(fp)) { + fclose(fp); + unlink(sfn); + return (-1); + } + + if (fclose(fp) == EOF) { + unlink(sfn); + return (-1); + } + + if (rename(sfn, certs_file) == -1) { + unlink(sfn); + return (-1); + } + + return (0); +} + int +cert_save_for(const char *cert, struct iri *i, int persist) +{ + struct cstore *cstore; + struct ccert *c; + char *d; + + cstore = persist ? &cert_store : &temp_store; + + if ((c = find_cert_for(cstore, i)) != NULL) { + if ((d = strdup(cert)) == NULL) + return (-1); + + free(c->cert); + c->cert = d; + + return (0); + } + + if (certs_store_add(cstore, i->iri_host, i->iri_portstr, i->iri_path, + cert) == -1) + return (-1); + + qsort(cert_store.certs, cert_store.len, sizeof(*cert_store.certs), + certs_cmp); + + if (persist && write_cert_file() == -1) + return (-1); + + return (0); +} + +int cert_open(const char *cert) { char path[PATH_MAX]; blob - 02ed3f170e25607b1fa02036f749359c51db5996 blob + 3512f77a2d20b2adb66315efa49e2a2f15052ba3 --- certs.h +++ certs.h @@ -25,5 +25,6 @@ extern char **identities; int certs_init(const char *); const char *ccert(const char *); const char *cert_for(struct iri *); +int cert_save_for(const char *, struct iri *, int); int cert_open(const char *); int cert_new(const char *, const char *, const char *, int); blob - f0bf316ca2ca85c7efad6ecfa3d4bc3f73e22cb5 blob + 8a32941b4aad1fa405ae08f275800dd1c45b6693 --- cmd.c +++ cmd.c @@ -1093,3 +1093,14 @@ cmd_up(struct buffer *buffer) { load_url_in_tab(current_tab, "..", NULL, LU_MODE_NOCACHE); } + +void +cmd_use_certificate(struct buffer *buffer) +{ + GUARD_RECURSIVE_MINIBUFFER(); + + enter_minibuffer(sensible_self_insert, uc_select, exit_minibuffer, + NULL, compl_uc, NULL, 1); + strlcpy(ministate.prompt, "Select certificate: ", + sizeof(ministate.prompt)); +} blob - c215bb0e2afa6871f4f93d31fd8c9224af20a3a8 blob + 5f194e9f36baf000919951aaf3efe24470856f86 --- cmd.h +++ cmd.h @@ -82,4 +82,5 @@ CMD(cmd_toggle_downloads, "Toggle the downloads side w CMD(cmd_toggle_help, "Toggle side window with help."); CMD(cmd_toggle_pre_wrap, "Toggle the wrapping of preformatted blocks."); CMD(cmd_up, "Go up one level."); +CMD(cmd_use_certificate, "Use a certificate for the current page."); CMD(cmd_write_buffer, "Save the current page to the disk."); blob - f6243324b8c79bb4caa3af734dd71f9c490da953 blob + ded602c8ff80b3d4913b94345460da61c038babd --- compl.c +++ compl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Omar Polo + * Copyright (c) 2021, 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,6 +18,7 @@ #include +#include "certs.h" #include "compl.h" #include "hist.h" #include "telescope.h" @@ -158,3 +159,22 @@ compl_toc(void **data, void **ret, const char **descr) *line = TAILQ_NEXT(l, lines); return text; } + +/* + * Provide completions for use-certificate + */ +const char * +compl_uc(void **data, void **ret, const char **descr) +{ + const char ***state = (const char ***)data; + + /* first time: init the state */ + if (*state == NULL) + *state = (const char **)identities; + + if (**state == NULL) + return NULL; + + /* XXX filling descr too would be nice */ + return *((*state)++); +} blob - c2d3ddef9936e81d473da6e75de86d4af340c7e3 blob + 3fbb6ecf9ea517f229c1381d0b0ae97faf0c71c0 --- compl.h +++ compl.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Omar Polo + * Copyright (c) 2021, 2024 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -23,5 +23,6 @@ const char *compl_ts(void **, void **, const char **); const char *compl_ls(void **, void **, const char **); const char *compl_swiper(void **, void **, const char **); const char *compl_toc(void **, void **, const char **); +const char *compl_uc(void **, void **, const char **); #endif blob - e9606e181d1cb50517cabfd4d25d55fc9d872828 blob + b05beff923e24cff4ab21a19147baaa6fb5db07b --- minibuffer.c +++ minibuffer.c @@ -22,6 +22,7 @@ #include #include +#include "certs.h" #include "fs.h" #include "hist.h" #include "iri.h" @@ -426,6 +427,34 @@ toc_select(void) } static void +save_cert_for_site_cb(int r, struct tab *tab) +{ + cert_save_for(tab->client_cert, &tab->iri, r); +} + +void +uc_select(void) +{ + const char *name; + + name = minibuffer_compl_text(); + if (!strcmp(name, "Generate new certificate")) { + message("Not implemented yet!"); + return; + } + + if ((current_tab->client_cert = ccert(name)) == NULL) { + message("Certificate %s not found", name); + return; + } + + exit_minibuffer(); + + yornp("Always use this cert on this page?", save_cert_for_site_cb, + current_tab); +} + +static void yornp_self_insert(void) { if (thiskey.key != 'y' && thiskey.key != 'n') { blob - d993896dd79343bae46a2ba6dc48362c4b802c8a blob + 693f6b6e018dab5defaa9c193f993d8496332733 --- minibuffer.h +++ minibuffer.h @@ -84,6 +84,7 @@ void ts_select(void); void ls_select(void); void swiper_select(void); void toc_select(void); +void uc_select(void); void enter_minibuffer(void(*)(void), void(*)(void), void(*)(void), struct hist *, complfn *, void *, int); blob - c50a70155a36fb4151dc2ecd9f8e6e49345412d1 blob + 6e35e4e642563038362b5bad6eaf81a619958a40 --- telescope.c +++ telescope.c @@ -372,6 +372,8 @@ handle_request_response(struct tab *tab) LU_MODE_NOCACHE); } else { /* 4x, 5x & 6x */ load_page_from_str(tab, err_pages[tab->code]); + if (tab->code >= 60) + cmd_use_certificate(&tab->buffer); } }