Commit Diff


commit - b8ec3578cd7c8dd902282fa1253fffc74fd615f0
commit + f0e62b859a56026db832e80814a11b27f6a7332f
blob - bc278afeb2580ae9e5bb6a01b29dcbdd99e5c878
blob + 3cff0bf9d3a78c2436a08b6ddcf8a9f4c936b008
--- Makefile.am
+++ Makefile.am
@@ -1,6 +1,6 @@
 SUBDIRS = test
 
-bin_PROGRAMS =		telescope
+bin_PROGRAMS =		telescope telescope-identity
 
 EXTRA_telescope_SOURCES = compat/ohash.h compat/queue.h compat/imsg.h contrib
 
@@ -58,6 +58,19 @@ telescope_SOURCES =	certs.c			\
 			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
@@ -47,25 +47,8 @@
 #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;
 
@@ -485,7 +468,7 @@ cert_open(const char *cert)
 }
 
 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;
@@ -520,7 +503,7 @@ rsa_key_create(FILE *f, const char *fname)
 }
 
 static EVP_PKEY *
-ec_key_create(FILE *f, const char *fname)
+ec_key_create(FILE *f)
 {
 	EC_KEY		*eckey = NULL;
 	EVP_PKEY	*pkey = NULL;
@@ -554,8 +537,7 @@ ec_key_create(FILE *f, const char *fname)
 }
 
 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;
@@ -564,20 +546,16 @@ cert_new(const char *common_name, const char *certpath
 	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;
 
@@ -602,9 +580,6 @@ cert_new(const char *common_name, const char *certpath
 	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;
 
@@ -621,10 +596,7 @@ cert_new(const char *common_name, const char *certpath
 		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
@@ -20,12 +20,32 @@
 
 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
@@ -66,7 +66,7 @@ char		known_hosts_file[PATH_MAX], known_hosts_tmp[PATH
 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];
@@ -373,6 +373,8 @@ fs_init(void)
 	    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
@@ -32,7 +32,7 @@ extern char	known_hosts_file[PATH_MAX], known_hosts_tm
 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
@@ -0,0 +1,521 @@
+/*
+ * 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;
+}
+