commit - b8ec3578cd7c8dd902282fa1253fffc74fd615f0
commit + f0e62b859a56026db832e80814a11b27f6a7332f
blob - bc278afeb2580ae9e5bb6a01b29dcbdd99e5c878
blob + 3cff0bf9d3a78c2436a08b6ddcf8a9f4c936b008
--- Makefile.am
+++ Makefile.am
SUBDIRS = test
-bin_PROGRAMS = telescope
+bin_PROGRAMS = telescope telescope-identity
EXTRA_telescope_SOURCES = compat/ohash.h compat/queue.h compat/imsg.h contrib
utils.h \
wrap.c
+telescope_identity_SOURCES = \
+ certs.c \
+ certs.h \
+ fs.c \
+ fs.h \
+ hist.c \
+ hist.h \
+ identity.c \
+ parser.c \
+ parser.h \
+ utils.c \
+ utils.h
+
noinst_PROGRAMS = pagebundler
pagebundler_SOURCES = pagebundler.c
blob - abc87f62484296eec0be6a445199cd634defbe66
blob + 42bbc8f817d418c422346de15f12da1aae55e947
--- certs.c
+++ certs.c
#include "fs.h"
#include "iri.h"
-/* client certificate */
-struct ccert {
- char *host;
- char *port;
- char *path;
- char *cert;
-
-#define CERT_OK 0x00
-#define CERT_TEMP 0x01
-#define CERT_TEMP_DEL 0x02
- int flags;
-};
+struct cstore cert_store;
-static struct cstore {
- struct ccert *certs;
- size_t len;
- size_t cap;
-} cert_store;
-
char **identities;
static size_t id_len, id_cap;
}
static EVP_PKEY *
-rsa_key_create(FILE *f, const char *fname)
+rsa_key_create(FILE *f)
{
EVP_PKEY_CTX *ctx = NULL;
EVP_PKEY *pkey = NULL;
}
static EVP_PKEY *
-ec_key_create(FILE *f, const char *fname)
+ec_key_create(FILE *f)
{
EC_KEY *eckey = NULL;
EVP_PKEY *pkey = NULL;
}
int
-cert_new(const char *common_name, const char *certpath, const char *keypath,
- int eckey)
+cert_new(const char *common_name, const char *path, int eckey)
{
EVP_PKEY *pkey = NULL;
X509 *x509 = NULL;
int ret = -1;
const unsigned char *cn = (const unsigned char*)common_name;
- if ((fp = fopen(keypath, "w")) == NULL)
+ if ((fp = fopen(path, "wx")) == NULL)
goto done;
if (eckey)
- pkey = ec_key_create(fp, keypath);
+ pkey = ec_key_create(fp);
else
- pkey = rsa_key_create(fp, keypath);
+ pkey = rsa_key_create(fp);
if (pkey == NULL)
goto done;
- if (fflush(fp) == EOF || fclose(fp) == EOF)
- goto done;
- fp = NULL;
-
if ((x509 = X509_new()) == NULL)
goto done;
if (!X509_sign(x509, pkey, EVP_sha256()))
goto done;
- if ((fp = fopen(certpath, "w")) == NULL)
- goto done;
-
if (!PEM_write_X509(fp, x509))
goto done;
X509_NAME_free(name);
if (fp)
fclose(fp);
-
- if (ret == -1) {
- (void) unlink(certpath);
- (void) unlink(keypath);
- }
+ if (ret == -1)
+ (void) unlink(path);
return (ret);
}
blob - 04d0246480ec6f44be8df1d20dcf2aed524374ae
blob + 35fa735d83947d0d0a99ca4601f0e581f0a6fce6
--- certs.h
+++ certs.h
struct iri;
-extern char **identities;
+/* client certificate */
+struct ccert {
+ char *host;
+ char *port;
+ char *path;
+ char *cert;
+#define CERT_OK 0x00
+#define CERT_TEMP 0x01
+#define CERT_TEMP_DEL 0x02
+ int flags;
+};
+
+struct cstore {
+ struct ccert *certs;
+ size_t len;
+ size_t cap;
+};
+
+extern struct cstore cert_store;
+extern char **identities;
+
int certs_init(const char *);
const char *ccert(const char *);
const char *cert_for(struct iri *, int *);
int cert_save_for(const char *, struct iri *, int);
int cert_delete_for(const char *, struct iri *, int);
int cert_open(const char *);
-int cert_new(const char *, const char *, const char *, int);
+int cert_new(const char *, const char *, int);
blob - f6e777a09dba8c97f071b17670ccadebd40476c7
blob + 958c7ebd4c7fb2e1d37e1dee0a1ef0d7ed146d55
--- fs.c
+++ fs.c
char crashed_file[PATH_MAX];
char session_file[PATH_MAX], session_file_tmp[PATH_MAX];
char history_file[PATH_MAX], history_file_tmp[PATH_MAX];
-char cert_dir[PATH_MAX];
+char cert_dir[PATH_MAX], cert_dir_tmp[PATH_MAX];
char certs_file[PATH_MAX], certs_file_tmp[PATH_MAX];
char cwd[PATH_MAX];
sizeof(history_file_tmp));
join_path(cert_dir, data_path_base, "/certs/",
sizeof(cert_dir));
+ join_path(cert_dir_tmp, data_path_base, "/certs/id.XXXXXXXXXX",
+ sizeof(cert_dir_tmp));
join_path(certs_file, config_path_base, "/certs.conf",
sizeof(certs_file));
join_path(certs_file_tmp, config_path_base, "/certs.conf.XXXXXXXXXX",
blob - 00b1e9d9f7d432b1a5c0a6bb1ebae4c406eace56
blob + a99190534cfc48dbf9633337fbeb737afc1cd832
--- fs.h
+++ fs.h
extern char crashed_file[PATH_MAX];
extern char session_file[PATH_MAX], session_file_tmp[PATH_MAX];
extern char history_file[PATH_MAX], history_file_tmp[PATH_MAX];
-extern char cert_dir[PATH_MAX];
+extern char cert_dir[PATH_MAX], cert_dir_tmp[PATH_MAX];
extern char certs_file[PATH_MAX], certs_file_tmp[PATH_MAX];
extern char cwd[PATH_MAX];
blob - /dev/null
blob + c37f393e960ed08f9185909559aa8a0b289e46c5 (mode 644)
--- /dev/null
+++ identity.c
+/*
+ * Copyright (c) 2024 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "compat.h"
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tls.h>
+#include <unistd.h>
+
+#include "certs.h"
+#include "fs.h"
+#include "parser.h"
+#include "telescope.h"
+
+#ifndef nitems
+#define nitems(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+struct cmd;
+
+static int cmd_generate(const struct cmd *, int, char **);
+static int cmd_remove(const struct cmd *, int, char **);
+static int cmd_import(const struct cmd *, int, char **);
+static int cmd_export(const struct cmd *, int, char **);
+static int cmd_list(const struct cmd *, int, char **);
+static int cmd_mappings(const struct cmd *, int, char **);
+static int cmd_use(const struct cmd *, int, char **);
+static int cmd_forget(const struct cmd *, int, char **);
+
+struct cmd {
+ const char *name;
+ int (*fn)(const struct cmd *, int argc, char **argv);
+ const char *usage;
+};
+
+static const struct cmd cmds[] = {
+ { "generate", cmd_generate, "[-t type] name" },
+ { "remove", cmd_remove, "name" },
+ { "import", cmd_import, "-C cert [-K key] name" },
+ { "export", cmd_export, "-C cert name path" },
+ { "list", cmd_list, "" },
+ { "mappings", cmd_mappings, "" },
+ { "use", cmd_use, "name host[:port][/path]" },
+ { "forget", cmd_forget, "name host[:port][/path]" },
+};
+
+/*
+ * Provide some symbols so that we can pull in some subsystems without
+ * their the dependencies.
+ */
+
+const uint8_t *about_about;
+size_t about_about_len;
+const uint8_t *about_blank;
+size_t about_blank_len;
+const uint8_t *about_crash;
+size_t about_crash_len;
+const uint8_t *about_help;
+size_t about_help_len;
+const uint8_t *about_license;
+size_t about_license_len;
+const uint8_t *about_new;
+size_t about_new_len;
+const uint8_t *bookmarks;
+size_t bookmarks_len;
+
+void gemtext_initparser(struct parser *p) { return; }
+void textpatch_initparser(struct parser *p) { return; }
+void textplain_initparser(struct parser *p) { return; }
+
+void load_page_from_str(struct tab *tab, const char *page) { return; }
+void erase_buffer(struct buffer *buffer) { return; }
+
+static void __dead
+usage(void)
+{
+ size_t i;
+
+ fprintf(stderr, "usage: %s command [args...]\n", getprogname());
+ fprintf(stderr, "Available subcommands are:");
+ for (i = 0; i < nitems(cmds); ++i)
+ fprintf(stderr, " %s", cmds[i].name);
+ fputs(".\n", stderr);
+ exit(1);
+}
+
+static void __dead
+cmd_usage(const struct cmd *cmd)
+{
+ fprintf(stderr, "usage: %s %s%s%s\n", getprogname(), cmd->name,
+ *cmd->usage ? " " : "", cmd->usage);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const struct cmd *cmd;
+ int ch;
+ size_t i;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ optind = 0;
+ optreset = 1;
+
+ if (argc == 0)
+ usage();
+
+ for (i = 0; i < nitems(cmds); ++i) {
+ cmd = &cmds[i];
+
+ if (strcmp(cmd->name, argv[0]) != 0)
+ continue;
+
+ fs_init();
+ if (certs_init(certs_file) == -1)
+ errx(1, "failed to initialize the cert store.");
+ return (cmd->fn(cmd, argc, argv));
+ }
+
+ usage();
+}
+
+static int
+cmd_generate(const struct cmd *cmd, int argc, char **argv)
+{
+ const char *name;
+ char path[PATH_MAX];
+ int ch, r;
+ int ec = 1;
+
+ while ((ch = getopt(argc, argv, "t:")) != -1) {
+ switch (ch) {
+ case 't':
+ if (!strcasecmp(optarg, "ec")) {
+ ec = 1;
+ break;
+ }
+ if (!strcasecmp(optarg, "rsa")) {
+ ec = 0;
+ break;
+ }
+ errx(1, "Unknown key type requested: %s", optarg);
+
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ cmd_usage(cmd);
+
+ name = *argv;
+
+ r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
+ if (r < 0 || (size_t)r >= sizeof(path))
+ errx(1, "path too long");
+
+ if (cert_new(name, path, ec) == -1)
+ errx(1, "failure generating the key");
+
+ return 0;
+}
+
+static int
+cmd_remove(const struct cmd *cmd, int argc, char **argv)
+{
+ const char *name;
+ char path[PATH_MAX];
+ int ch, r;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ cmd_usage(cmd);
+
+ name = *argv;
+
+ r = snprintf(path, sizeof(path), "%s%s", cert_dir, name);
+ if (r < 0 || (size_t)r >= sizeof(path))
+ errx(1, "path too long");
+
+ if (unlink(path) == -1)
+ err(1, "unlink %s", path);
+ return 0;
+}
+
+static int
+cmd_import(const struct cmd *cmd, int argc, char **argv)
+{
+ struct tls_config *conf;
+ const char *key = NULL, *cert = NULL;
+ char path[PATH_MAX], sfn[PATH_MAX];
+ FILE *fp;
+ uint8_t *keym, *certm;
+ size_t keyl, certl;
+ int ch, r, fd;
+ int force = 0;
+
+ while ((ch = getopt(argc, argv, "C:K:f")) != -1) {
+ switch (ch) {
+ case 'C':
+ cert = optarg;
+ break;
+ case 'K':
+ key = optarg;
+ break;
+ case 'f':
+ force = 1;
+ break;
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc != 1)
+ cmd_usage(cmd);
+
+ if (key == NULL)
+ key = cert;
+ if (cert == NULL)
+ cmd_usage(cmd);
+
+ if ((keym = tls_load_file(key, &keyl, NULL)) == NULL)
+ err(1, "can't open %s", key);
+ if ((certm = tls_load_file(cert, &certl, NULL)) == NULL)
+ err(1, "can't open %s", cert);
+
+ if ((conf = tls_config_new()) == NULL)
+ err(1, "tls_config_new");
+
+ if (tls_config_set_keypair_mem(conf, certm, certl, keym, keyl) == -1)
+ errx(1, "failed to load the keypair: %s",
+ tls_config_error(conf));
+
+ tls_config_free(conf);
+
+ r = snprintf(path, sizeof(path), "%s/%s", cert_dir, *argv);
+ if (r < 0 || (size_t)r >= sizeof(path))
+ err(1, "identity name too long");
+
+ strlcpy(sfn, cert_dir_tmp, sizeof(sfn));
+ if ((fd = mkstemp(sfn)) == -1 ||
+ (fp = fdopen(fd, "w")) == NULL) {
+ if (fd != -1) {
+ warn("fdopen");
+ unlink(sfn);
+ close(fd);
+ } else
+ warn("mkstamp");
+ return 1;
+ }
+
+ if (fwrite(certm, 1, certl, fp) != certl) {
+ warn("fwrite");
+ unlink(sfn);
+ fclose(fp);
+ return 1;
+ }
+ if (strcmp(key, cert) != 0 &&
+ fwrite(keym, 1, keyl, fp) != keyl) {
+ warn("fwrite");
+ unlink(sfn);
+ fclose(fp);
+ return 1;
+ }
+
+ if (fflush(fp) == EOF) {
+ warn("fflush");
+ unlink(sfn);
+ fclose(fp);
+ return 1;
+ }
+ fclose(fp);
+
+ if (!force && access(path, F_OK) == 0) {
+ warnx("identity %s already exists", *argv);
+ unlink(sfn);
+ return 1;
+ }
+
+ if (rename(sfn, path) == -1) {
+ warn("can't rename");
+ unlink(sfn);
+ return 1;
+ }
+
+ return (0);
+}
+
+static int
+cmd_export(const struct cmd *cmd, int argc, char **argv)
+{
+ FILE *fp, *outfp;
+ const char *cert = NULL;
+ const char *identity = NULL;
+ char path[PATH_MAX];
+ char buf[BUFSIZ];
+ size_t l;
+ int ch, r;
+
+ while ((ch = getopt(argc, argv, "C:")) != -1) {
+ switch (ch) {
+ case 'C':
+ cert = optarg;
+ break;
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 1)
+ cmd_usage(cmd);
+ identity = argv[0];
+
+ if (cert == NULL)
+ cmd_usage(cmd);
+
+ r = snprintf(path, sizeof(path), "%s/%s", cert_dir, identity);
+ if (r < 0 || (size_t)r >= sizeof(path))
+ err(1, "path too long");
+ if ((fp = fopen(path, "r")) == NULL)
+ err(1, "can't open %s", path);
+
+ if ((outfp = fopen(cert, "w")) == NULL)
+ err(1, "can't open %s", cert);
+
+ for (;;) {
+ l = fread(buf, 1, sizeof(buf), fp);
+ if (l == 0)
+ break;
+ if (fwrite(buf, 1, l, outfp) != l)
+ err(1, "fwrite");
+ }
+ if (ferror(fp))
+ err(1, "fread");
+
+ return 0;
+}
+
+static int
+cmd_list(const struct cmd *cmd, int argc, char **argv)
+{
+ char **id;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ cmd_usage(cmd);
+
+ for (id = identities; *id; ++id)
+ puts(*id);
+
+ return (0);
+}
+
+static int
+cmd_mappings(const struct cmd *cmd, int argc, char **argv)
+{
+ struct ccert *ccert;
+ int ch, defport;
+ size_t i;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ cmd_usage(cmd);
+
+ for (i = 0; i < cert_store.len; ++i) {
+ ccert = &cert_store.certs[i];
+
+ defport = !strcmp(ccert->port, "1965");
+
+ printf("%s\t%s%s%s%s\n", ccert->host, ccert->host,
+ defport ? "" : ":", defport ? "" : ccert->port,
+ ccert->path);
+ }
+
+ return (0);
+}
+
+static struct iri *
+parseiri(char *spec)
+{
+ static struct iri iri;
+ const char *errstr;
+ char *host, *port = NULL, *path = NULL;
+
+ memset(&iri, 0, sizeof(iri));
+
+ host = spec;
+
+ port = host + strcspn(host, ":/");
+ if (*port == ':') {
+ *port++ = '\0';
+ if ((path = strchr(port, '/')) != NULL)
+ *path++ = '\0';
+ } else if (*port == '/') {
+ *port++ = '\0';
+ path = port;
+ port = NULL;
+ } else
+ port = NULL;
+
+ strlcpy(iri.iri_host, host, sizeof(iri.iri_host));
+ strlcpy(iri.iri_portstr, port ? port : "1965", sizeof(iri.iri_portstr));
+ strlcpy(iri.iri_path, path ? path : "/", sizeof(iri.iri_path));
+
+ iri.iri_port = strtonum(iri.iri_portstr, 0, UINT16_MAX, &errstr);
+ if (errstr)
+ err(1, "port number is %s: %s", errstr, iri.iri_portstr);
+
+ return &iri;
+}
+
+static int
+cmd_use(const struct cmd *cmd, int argc, char **argv)
+{
+ char *cert, *spec;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ cmd_usage(cmd);
+
+ cert = argv[0];
+ spec = argv[1];
+
+ if (ccert(cert) == NULL)
+ err(1, "unknown identity %s", cert);
+
+ if (cert_save_for(cert, parseiri(spec), 1) == -1)
+ errx(1, "failed to save the certificate");
+
+ return 0;
+}
+
+static int
+cmd_forget(const struct cmd *cmd, int argc, char **argv)
+{
+ char *cert, *spec;
+ int ch;
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ default:
+ cmd_usage(cmd);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 2)
+ cmd_usage(cmd);
+
+ cert = argv[0];
+ spec = argv[1];
+
+ if (ccert(cert) == NULL)
+ err(1, "unknown identity %s", cert);
+
+ if (cert_delete_for(cert, parseiri(spec), 1) == -1)
+ errx(1, "failed to save the certificate");
+
+ return 0;
+}
+