Commit Diff


commit - fef9de8ff7030ff5eecba8cbb6a6def8b5bce9c8
commit + 288fd238ab2466e7fc9d8d11e7dd92363326a66f
blob - f1d91bbbe7efedd85f36c3a3127776239806b287
blob + 82e0f91bff2cf8e134c77bcea58c00500c6e79e6
--- ChangeLog
+++ ChangeLog
@@ -1,5 +1,7 @@
 2021-04-25  Omar Polo  <op@omarpolo.com>
 
+	* telescope.c (handle_check_cert_user_choice): allow saving the new certificate after mismatch
+
 	* fs.c (load_certs): don't crash on invalid lines in known_hosts
 
 	* hash.c (telescope_lookup_tofu): save certificates per (host, port) tuple, not only per-host
blob - 3959ee825b9e66fc3f65c56fdece058323e8080d
blob + 2b96509fdd0337da41b701583d79bb641267d74e
--- fs.c
+++ fs.c
@@ -37,6 +37,7 @@ static void		 handle_get(struct imsg*, size_t);
 static void		 handle_quit(struct imsg*, size_t);
 static void		 handle_bookmark_page(struct imsg*, size_t);
 static void		 handle_save_cert(struct imsg*, size_t);
+static void		 handle_update_cert(struct imsg*, size_t);
 static void		 handle_session_start(struct imsg*, size_t);
 static void		 handle_session_tab(struct imsg*, size_t);
 static void		 handle_session_end(struct imsg*, size_t);
@@ -48,7 +49,7 @@ static struct imsgbuf		*ibuf;
 static FILE			*session;
 
 static char	bookmark_file[PATH_MAX];
-static char	known_hosts_file[PATH_MAX];
+static char	known_hosts_file[PATH_MAX], known_hosts_tmp[PATH_MAX];
 static char	session_file[PATH_MAX];
 
 static imsg_handlerfn *handlers[] = {
@@ -56,6 +57,7 @@ static imsg_handlerfn *handlers[] = {
 	[IMSG_QUIT] = handle_quit,
 	[IMSG_BOOKMARK_PAGE] = handle_bookmark_page,
 	[IMSG_SAVE_CERT] = handle_save_cert,
+	[IMSG_UPDATE_CERT] = handle_update_cert,
 	[IMSG_SESSION_START] = handle_session_start,
 	[IMSG_SESSION_TAB] = handle_session_tab,
 	[IMSG_SESSION_END] = handle_session_end,
@@ -178,6 +180,68 @@ handle_save_cert(struct imsg *imsg, size_t datalen)
 	res = 0;
 end:
 	imsg_compose(ibuf, IMSG_SAVE_CERT_OK, imsg->hdr.peerid, 0, -1,
+	    &res, sizeof(res));
+	imsg_flush(ibuf);
+}
+
+static void
+handle_update_cert(struct imsg *imsg, size_t datalen)
+{
+	FILE	*tmp, *f;
+	struct	 tofu_entry entry;
+	char	 sfn[PATH_MAX], *line = NULL, *t;
+	size_t	 l, linesize = 0;
+	ssize_t	 linelen;
+	int	 fd, e, res = 0;
+
+	if (datalen != sizeof(entry))
+		die();
+	memcpy(&entry, imsg->data, datalen);
+
+	strlcpy(sfn, known_hosts_tmp, sizeof(sfn));
+	if ((fd = mkstemp(sfn)) == -1 ||
+	    (tmp = fdopen(fd, "w")) == NULL) {
+		if (fd != -1) {
+			unlink(sfn);
+			close(fd);
+		}
+		res = 0;
+		goto end;
+	}
+
+	if ((f = fopen(known_hosts_file, "r")) == NULL) {
+		unlink(sfn);
+		fclose(tmp);
+                res = 0;
+		goto end;
+	}
+
+	l = strlen(entry.domain);
+	while ((linelen = getline(&line, &linesize, f)) != -1) {
+		if ((t = strstr(line, entry.domain)) != NULL &&
+		    (line[l] == ' ' || line[l] == '\t'))
+			continue;
+		/* line has a trailing \n */
+		fprintf(tmp, "%s", line);
+	}
+	fprintf(tmp, "%s %s %d\n", entry.domain, entry.hash, entry.verified);
+
+	free(line);
+	e = ferror(tmp);
+
+	fclose(tmp);
+	fclose(f);
+
+	if (e) {
+		unlink(sfn);
+		res = 0;
+		goto end;
+	}
+
+	res = rename(sfn, known_hosts_file) != -1;
+
+end:
+	imsg_compose(ibuf, IMSG_UPDATE_CERT_OK, imsg->hdr.peerid, 0, -1,
 	    &res, sizeof(res));
 	imsg_flush(ibuf);
 }
@@ -238,6 +302,10 @@ fs_init(void)
 	strlcpy(known_hosts_file, getenv("HOME"), sizeof(known_hosts_file));
 	strlcat(known_hosts_file, "/.telescope/known_hosts", sizeof(known_hosts_file));
 
+	strlcpy(known_hosts_tmp, getenv("HOME"), sizeof(known_hosts_tmp));
+	strlcat(known_hosts_tmp, "/.telescope/known_hosts.tmp.XXXXXXXXXX",
+	    sizeof(known_hosts_file));
+
 	strlcpy(session_file, getenv("HOME"), sizeof(session_file));
 	strlcat(session_file, "/.telescope/session", sizeof(session_file));
 
blob - 1dc54d50a6a4ae71a414eb217d9d8799d0b34fde
blob + 01b9fe34f05109df256fc7d27e1a1b48d74f00bb
--- telescope.c
+++ telescope.c
@@ -26,12 +26,14 @@ static struct tab	*tab_by_id(uint32_t);
 static void		 handle_imsg_err(struct imsg*, size_t);
 static void		 handle_imsg_check_cert(struct imsg*, size_t);
 static void		 handle_check_cert_user_choice(int, unsigned int);
+static void		 handle_maybe_save_new_cert(int, unsigned int);
 static void		 handle_imsg_got_code(struct imsg*, size_t);
 static void		 handle_imsg_got_meta(struct imsg*, size_t);
 static void		 handle_imsg_buf(struct imsg*, size_t);
 static void		 handle_imsg_eof(struct imsg*, size_t);
 static void		 handle_imsg_bookmark_ok(struct imsg*, size_t);
 static void		 handle_imsg_save_cert_ok(struct imsg*, size_t);
+static void		 handle_imsg_update_cert_ok(struct imsg *, size_t);
 static void		 handle_dispatch_imsg(int, short, void*);
 static void		 load_page_from_str(struct tab*, const char*);
 static void		 do_load_url(struct tab*, const char*);
@@ -45,6 +47,7 @@ static imsg_handlerfn *handlers[] = {
 	[IMSG_EOF] = handle_imsg_eof,
 	[IMSG_BOOKMARK_OK] = handle_imsg_bookmark_ok,
 	[IMSG_SAVE_CERT_OK] = handle_imsg_save_cert_ok,
+	[IMSG_UPDATE_CERT_OK] = handle_imsg_update_cert_ok,
 };
 
 static struct ohash	certs;
@@ -131,6 +134,8 @@ handle_imsg_check_cert(struct imsg *imsg, size_t datal
 	} else {
 		tab->trust = TS_UNTRUSTED;
 		load_page_from_str(tab, "# Certificate mismatch\n");
+		if ((tab->cert = strdup(hash)) == NULL)
+			die();
 		ui_yornp("Certificate mismatch.  Proceed?",
 		    handle_check_cert_user_choice, tab->id);
 	}
@@ -139,11 +144,55 @@ handle_imsg_check_cert(struct imsg *imsg, size_t datal
 static void
 handle_check_cert_user_choice(int accept, unsigned int tabid)
 {
+	struct tab *tab;
+
+	tab = tab_by_id(tabid);
+
 	imsg_compose(netibuf, IMSG_CERT_STATUS, tabid, 0, -1,
 	    &accept, sizeof(accept));
 	imsg_flush(netibuf);
+
+	if (accept)
+		ui_yornp("Save the new certificate?",
+		    handle_maybe_save_new_cert, tabid);
+	else {
+		free(tab->cert);
+		tab->cert = NULL;
+	}
 }
 
+static void
+handle_maybe_save_new_cert(int accept, unsigned int tabid)
+{
+	struct tab *tab;
+	struct tofu_entry *e;
+
+	tab = tab_by_id(tabid);
+
+	if (!accept)
+		goto end;
+
+	if ((e = calloc(1, sizeof(e))) == NULL)
+		die();
+
+	strlcpy(e->domain, tab->uri.host, sizeof(e->domain));
+	if (*tab->uri.port != '\0' && strcmp(tab->uri.port, "1965")) {
+		strlcat(e->domain, ":", sizeof(e->domain));
+		strlcat(e->domain, tab->uri.port, sizeof(e->domain));
+	}
+	strlcpy(e->hash, tab->cert, sizeof(e->hash));
+	imsg_compose(fsibuf, IMSG_UPDATE_CERT, 0, 0, -1, e, sizeof(*e));
+	imsg_flush(fsibuf);
+
+	tofu_update(&certs, e);
+
+	tab->trust = TS_TRUSTED;
+
+end:
+	free(tab->cert);
+	tab->cert = NULL;
+}
+
 static inline int
 normalize_code(int n)
 {
@@ -280,6 +329,18 @@ handle_imsg_save_cert_ok(struct imsg *imsg, size_t dat
 	if (res != 0)
 		ui_notify("Failed to save the cert for: %s",
 		    strerror(res));
+}
+
+static void
+handle_imsg_update_cert_ok(struct imsg *imsg, size_t datalen)
+{
+	int res;
+
+	if (datalen != sizeof(res))
+		die();
+	memcpy(&res, imsg->data, datalen);
+	if (!res)
+		ui_notify("Failed to update the certificate");
 }
 
 static void
blob - d972e44d1820f75c484a8f39a511b7e3e3997bf0
blob + 3a5b568ee0673fb93a01be0ae82ff2ec4f6b75af
--- telescope.h
+++ telescope.h
@@ -46,6 +46,8 @@ enum imsg_type {
 	IMSG_BOOKMARK_OK,
 	IMSG_SAVE_CERT,
 	IMSG_SAVE_CERT_OK,
+	IMSG_UPDATE_CERT,
+	IMSG_UPDATE_CERT_OK,
 
 	IMSG_SESSION_START,
 	IMSG_SESSION_TAB,
@@ -148,6 +150,7 @@ struct tab {
 	uint32_t		 id;
 	uint32_t		 flags;
 
+	char			*cert;
 	enum trust_state	 trust;
 	struct phos_uri		 uri;
 	struct histhead		 hist;
@@ -249,6 +252,7 @@ void		 textplain_initparser(struct parser*);
 void			 tofu_init(struct ohash*, unsigned int, ptrdiff_t);
 struct tofu_entry	*tofu_lookup(struct ohash*, const char*, const char*);
 void			 tofu_add(struct ohash*, struct tofu_entry*);
+void			 tofu_update(struct ohash*, struct tofu_entry*);
 
 /* ui.c */
 unsigned int	 tab_new_id(void);
blob - 1814b4ea5d3302c0cbd2f818d003bc8c72d64710
blob + b474a99d2639bb8390a3b0ec64e782783a93d344
--- tofu.c
+++ tofu.c
@@ -82,3 +82,17 @@ tofu_add(struct ohash *h, struct tofu_entry *e)
 	slot = ohash_qlookup(h, e->domain);
 	ohash_insert(h, slot, e);
 }
+
+void
+tofu_update(struct ohash *h, struct tofu_entry *e)
+{
+	struct tofu_entry *t;
+
+	if ((t = tofu_lookup(h, e->domain, NULL)) == NULL)
+		tofu_add(h, e);
+	else {
+		strlcpy(t->hash, e->hash, sizeof(t->hash));
+		t->verified = e->verified;
+		free(e);
+	}
+}