about summary refs log tree commit diff stats
path: root/xxxterm.c
diff options
context:
space:
mode:
Diffstat (limited to 'xxxterm.c')
-rw-r--r--xxxterm.c294
1 files changed, 290 insertions, 4 deletions
diff --git a/xxxterm.c b/xxxterm.c
index 96d2e7d..41c6e18 100644
--- a/xxxterm.c
+++ b/xxxterm.c
@@ -237,6 +237,7 @@ struct session_list	sessions;
 struct domain_list	c_wl;
 struct domain_list	js_wl;
 struct domain_list	pl_wl;
+struct strict_transport_tree	st_tree;
 struct undo_tailq	undos;
 struct keybinding_list	kbl;
 struct sp_list		spl;
@@ -657,6 +658,7 @@ char			cache_dir[PATH_MAX];
 char			sessions_dir[PATH_MAX];
 char			temp_dir[PATH_MAX];
 char			cookie_file[PATH_MAX];
+char			*strict_transport_file = NULL;
 SoupSession		*session;
 SoupCookieJar		*s_cookiejar;
 SoupCookieJar		*p_cookiejar;
@@ -4096,8 +4098,254 @@ webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
 	update_statusbar_position(NULL, NULL);
 }
 
+int
+strict_transport_rb_cmp(struct strict_transport *a, struct strict_transport *b)
+{
+	char			*p1, *p2;
+	int			l1, l2;
+
+	/* compare strings from the end */
+	l1 = strlen(a->host);
+	l2 = strlen(b->host);
+
+	p1 = a->host + l1;
+	p2 = b->host + l2;
+	for (; *p1 == *p2 && p1 > a->host && p2 > b->host;
+	    p1--, p2--)
+		;
+
+	/*
+	 * Check if we need to do pattern expansion,
+	 * or if we're just keeping the tree in order
+	 */
+	if (a->flags & XT_STS_FLAGS_EXPAND &&
+	    b->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS) {
+		/* Check if we're matching the
+		 * 'host.xyz' part in '*.host.xyz'
+		 */
+		if (p2 == b->host && (p1 == a->host || *(p1-1) == '.')) {
+			return (0);
+		}
+	}
+
+	if (p1 == a->host && p2 == b->host)
+		return (0);
+	if (p1 == a->host)
+		return (1);
+	if (p2 == b->host)
+		return (-1);
+
+	if (*p1 < *p2)
+		return (-1);
+	if (*p1 > *p2)
+		return (1);
+
+	return (0);
+}
+RB_GENERATE(strict_transport_tree, strict_transport, entry,
+    strict_transport_rb_cmp);
+
+int
+strict_transport_add(const char *domain, time_t timeout, int subdomains)
+{
+	struct strict_transport	*d, find;
+	time_t			now;
+	FILE			*f;
+
+	if (enable_strict_transport == FALSE)
+		return (0);
+
+	DPRINTF("strict_transport_add(%s,%ld,%d)\n", domain, timeout,
+	    subdomains);
+
+	now = time(NULL);
+	if (timeout < now)
+		return (0);
+
+	find.host = (char *)domain;
+	find.flags = 0;
+	d = RB_FIND(strict_transport_tree, &st_tree, &find);
+
+	/* update flags */
+	if (d) {
+		/* check if update is needed */
+		if (d->timeout == timeout &&
+		    (d->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS) == subdomains)
+			return (0);
+
+		d->timeout = timeout;
+		if (subdomains)
+			d->flags |= XT_STS_FLAGS_INCLUDE_SUBDOMAINS;
+
+		/* We're still initializing */
+		if (strict_transport_file == NULL)
+			return (0);
+
+		if ((f = fopen(strict_transport_file, "w")) == NULL) {
+			show_oops(NULL,
+			    "can't open strict-transport rules file");
+			return (1);
+		}
+
+		fprintf(f, "# Generated file - do not update unless you know "
+		    "what you're doing\n");
+		RB_FOREACH(d, strict_transport_tree, &st_tree) {
+			if (d->timeout < now)
+				continue;
+			fprintf(f, "%s\t%d\t%d\n", d->host, d->timeout,
+			    d->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS);
+		}
+		fclose(f);
+	} else {
+		d = g_malloc(sizeof *d);
+		d->host= g_strdup(domain);
+		d->timeout = timeout;
+		if (subdomains)
+			d->flags = XT_STS_FLAGS_INCLUDE_SUBDOMAINS;
+		else
+			d->flags = 0;
+		RB_INSERT(strict_transport_tree, &st_tree, d);
+
+		/* We're still initializing */
+		if (strict_transport_file == NULL)
+			return (0);
+
+		if ((f = fopen(strict_transport_file, "a+")) == NULL) {
+			show_oops(NULL,
+			    "can't open strict-transport rules file");
+			return (1);
+		}
+
+		fseek(f, 0, SEEK_END);
+		fprintf(f,"%s\t%d\t%d\n", d->host, timeout, subdomains);
+		fclose(f);
+	}
+	return (0);
+}
+
+int
+strict_transport_check(const char *host)
+{
+	static struct strict_transport	*d = NULL;
+	struct strict_transport		find;
+
+	if (enable_strict_transport == FALSE)
+		return (0);
+
+	find.host = (char *)host;
+
+	/* match for domains that include subdomains */
+	find.flags = XT_STS_FLAGS_EXPAND;
+
+	/* First, check if we're already at the right node */
+	if (d != NULL && strict_transport_rb_cmp(&find, d) == 0) {
+		return (1);
+	}
+
+	d = RB_FIND(strict_transport_tree, &st_tree, &find);
+	if (d != NULL)
+		return (1);
+
+	return (0);
+}
+
+int
+strict_transport_init()
+{
+	char			file[PATH_MAX];
+	char			delim[3];
+	char			*rule;
+	char			*ptr;
+	FILE			*f;
+	size_t			len;
+	time_t			timeout, now;
+	int			subdomains;
+
+	snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_STS_FILE);
+	if ((f = fopen(file, "r")) == NULL) {
+		strict_transport_file = g_strdup(file);
+		return (0);
+	}
+
+	delim[0] = '\\';
+	delim[1] = '\\';
+	delim[2] = '#';
+	rule = NULL;
+	now = time(NULL);
+
+	for (;;) {
+		if ((rule = fparseln(f, &len, NULL, delim, 0)) == NULL) {
+			if (!feof(f) || ferror(f))
+				goto corrupt_file;
+			else
+				break;
+		}
+
+		/* get second entry */
+		if ((ptr = strpbrk(rule, " \t")) == NULL)
+			goto corrupt_file;
+
+		*ptr++ = '\0';
+		timeout = atoi(ptr);
+
+		/* get third entry */
+		if ((ptr = strpbrk(ptr, " \t")) == NULL)
+			goto corrupt_file;
+
+		ptr ++;
+		subdomains = atoi(ptr);
+
+		if (timeout > now)
+			strict_transport_add(rule, timeout, subdomains);
+		free(rule);
+	}
+
+	fclose(f);
+	strict_transport_file = g_strdup(file);
+	return (0);
+
+corrupt_file:
+	startpage_add("strict-transport rules file ('%s') is corrupt", file);
+	if (rule)
+		free(rule);
+	fclose(f);
+	return (1);
+}
+
 void
-session_rq_cb(SoupSession *s, SoupMessage *msg, SoupSocket *socket, gpointer data)
+strict_transport_security_cb(SoupMessage *msg, gpointer data)
+{
+	SoupURI		*uri;
+	const char	*sts;
+	char		*ptr;
+	int		timeout = 0;
+	int		subdomains = FALSE;
+
+	if (msg == NULL)
+		return;
+
+	sts = soup_message_headers_get_one(msg->response_headers,
+	    "Strict-Transport-Security");
+	uri = soup_message_get_uri(msg);
+
+	if (sts == NULL || uri == NULL)
+		return;
+
+	if ((ptr = strcasestr(sts, "max-age="))) {
+		ptr += strlen("max-age=");
+		timeout = atoi(ptr);
+	} else
+		return; /* malformed header - max-age must be included */
+
+	if ((ptr = strcasestr(sts, "includeSubDomains")))
+		subdomains = TRUE;
+
+	strict_transport_add(uri->host, timeout + time(NULL), subdomains);
+}
+
+void
+session_rq_cb(SoupSession *s, SoupMessage *msg, SoupSocket *socket,
+    gpointer data)
 {
 	SoupURI			*dest;
 	const char		*ref;
@@ -4122,17 +4370,27 @@ session_rq_cb(SoupSession *s, SoupMessage *msg, SoupSocket *socket, gpointer dat
 			dest = soup_message_get_uri(msg);
 
 			if (dest && !strstr(ref, dest->host)) {
-				soup_message_headers_remove(msg->request_headers, "Referer");
-				DNPRINTF(XT_D_NAV, "session_rq_cb: removing referer (not same domain)\n");
+				soup_message_headers_remove(msg->request_headers,
+				    "Referer");
+				DNPRINTF(XT_D_NAV, "session_rq_cb: removing "
+				    "referer (not same domain) (should be %s)\n",
+				    dest->host);
 			}
 			break;
 		case XT_REFERER_CUSTOM:
-			DNPRINTF(XT_D_NAV, "session_rq_cb: setting referer to %s\n", referer_custom);
+			DNPRINTF(XT_D_NAV, "session_rq_cb: setting referer "
+			    "to %s\n", referer_custom);
 			soup_message_headers_replace(msg->request_headers,
 			    "Referer", referer_custom);
 			break;
 		}
 	}
+
+	if (enable_strict_transport) {
+		soup_message_add_header_handler(msg, "finished",
+		    "Strict-Transport-Security",
+		    G_CALLBACK(strict_transport_security_cb), NULL);
+	}
 }
 
 int
@@ -4215,6 +4473,29 @@ webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
 	return (FALSE);
 }
 
+void
+webview_rrs_cb(WebKitWebView *wv, WebKitWebFrame *wf, WebKitWebResource *res,
+    WebKitNetworkRequest *request, WebKitNetworkResponse *response,
+    struct tab *t)
+{
+	SoupMessage		*msg;
+	SoupURI			*uri;
+
+	msg = webkit_network_request_get_message(request);
+	if (!msg) return;
+
+	uri = soup_message_get_uri(msg);
+	if (!uri) return;
+
+	if (strcmp(soup_uri_get_scheme(uri), SOUP_URI_SCHEME_HTTP) == 0) {
+		if (strict_transport_check(uri->host)) {
+			DNPRINTF(XT_D_NAV, "webview_rrs_cb: force https for %s\n",
+					uri->host);
+			soup_uri_set_scheme(uri, SOUP_URI_SCHEME_HTTPS);
+		}
+	}
+}
+
 WebKitWebView *
 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
 {
@@ -6726,6 +7007,7 @@ create_new_tab(char *title, struct undo *u, int focus, int position)
 	    "signal::mime-type-policy-decision-requested", G_CALLBACK(webview_mimetype_cb), t,
 	    "signal::navigation-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
 	    "signal::new-window-policy-decision-requested", G_CALLBACK(webview_npd_cb), t,
+	    "signal::resource-request-starting", G_CALLBACK(webview_rrs_cb), t,
 	    "signal::create-web-view", G_CALLBACK(webview_cwv_cb), t,
 	    "signal::close-web-view", G_CALLBACK(webview_closewv_cb), t,
 	    "signal::event", G_CALLBACK(webview_event_cb), t,
@@ -7345,6 +7627,7 @@ main(int argc, char **argv)
 	RB_INIT(&js_wl);
 	RB_INIT(&pl_wl);
 	RB_INIT(&downloads);
+	RB_INIT(&st_tree);
 
 	TAILQ_INIT(&sessions);
 	TAILQ_INIT(&tabs);
@@ -7626,6 +7909,9 @@ main(int argc, char **argv)
 	/* tld list */
 	tld_tree_init();
 
+	if (enable_strict_transport)
+		strict_transport_init();
+
 	/* uri completion */
 	completion_model = gtk_list_store_new(1, G_TYPE_STRING);