about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJosh Rickmar <jrick@devio.us>2012-09-18 18:17:14 -0400
committerJosh Rickmar <jrick@devio.us>2012-09-18 18:17:14 -0400
commit8421b1728ffe8da4b288d76d48d46d96fb4d0dfe (patch)
treec1eacb7cfb3e94dcd3ad7a15506a8a947c5f0eed
parentfe71f63d6a3a945021f0e5745fbf9240d0ea39ee (diff)
downloadxombrero-8421b1728ffe8da4b288d76d48d46d96fb4d0dfe.tar.gz
Add regex support to whitelists
This modifies the whitelist and https forcing code to internally use
unix extended regular expressions to match domains.  The old config
syntax converted to an appropiate regular expression.  Inputing of raw
regular expressions is possible by prepending the string "re:" in
front of a regular expression, for example:

js_wl = re:^(.*\.)*cyphertite\.com$

would be the same as

js_wl = .cyphertite.com
-rw-r--r--cookie.c6
-rw-r--r--hsts-preload81
-rw-r--r--settings.c30
-rw-r--r--whitelist.c218
-rw-r--r--xombrero.115
-rw-r--r--xombrero.c63
-rw-r--r--xombrero.css3
-rw-r--r--xombrero.h27
8 files changed, 224 insertions, 219 deletions
diff --git a/cookie.c b/cookie.c
index e7a9c6d..8de0fbc 100644
--- a/cookie.c
+++ b/cookie.c
@@ -122,7 +122,7 @@ soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
 void
 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
 {
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	SoupCookie		*c;
 	FILE			*r_cookie_f;
 	char			*public_suffix;
@@ -152,7 +152,7 @@ soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
 
 	if (public_suffix == NULL ||
 	    (enable_cookie_whitelist &&
-	    (d = wl_find(cookie->domain, &c_wl)) == NULL)) {
+	    (w = wl_find(cookie->domain, &c_wl)) == NULL)) {
 		blocked_cookies++;
 		DNPRINTF(XT_D_COOKIE,
 		    "soup_cookie_jar_add_cookie: reject %s\n",
@@ -188,7 +188,7 @@ soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
 	}
 
 	/* see if we are white listed for persistence */
-	if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
+	if ((w && w->handy) || (enable_cookie_whitelist == 0)) {
 		/* add to persistent jar */
 		c = soup_cookie_copy(cookie);
 		print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
diff --git a/hsts-preload b/hsts-preload
index cb2ecd9..20fe21a 100644
--- a/hsts-preload
+++ b/hsts-preload
@@ -1,6 +1,5 @@
 force_https = bitbucket.org
-force_https = bitrig.org
-force_https = www.bitrig.org
+force_https = re:^(www\.)?bitrig\.org$
 force_https = .conformal.com
 force_https = .conformalsys.org
 force_https = .cyphertite.com
@@ -18,22 +17,7 @@ force_https = twitter.com
 
 # sites from chromium's preloaded HSTS list
 # http://src.chromium.org/viewvc/chrome/trunk/src/net/base/transport_security_state_static.json?view=markup
-force_https = .health.google.com
-force_https = .checkout.google.com
-force_https = .chrome.google.com
-force_https = .docs.google.com
-force_https = .sites.google.com
-force_https = .spreadsheets.google.com
-force_https = .appengine.google.com
-force_https = .encrypted.google.com
-force_https = .accounts.google.com
-force_https = .profiles.google.com
-force_https = .mail.google.com
-force_https = .talkgadget.google.com
-force_https = .talk.google.com
-force_https = .hostedtalkgadget.google.com
-force_https = .plus.google.com
-force_https = .script.google.com
+force_https = re:^(.*\.)*(health|checkout|chrome|docs|sites|spreadsheets|appengine|encrypted|accounts|profiles|mail|talkgadget|talk|hostedtalkgadget|plus|script)\.google\.com$
 force_https = .market.android.com
 force_https = .ssl.google-analytics.com
 force_https = .googleplex.com
@@ -47,21 +31,15 @@ force_https = www.noisebridge.net
 force_https = .neg9.org
 force_https = .riseup.net
 force_https = factor.cc
-force_https = members.mayfirst.org
-force_https = support.mayfirst.org
-force_https = id.mayfirst.org
-force_https = lists.mayfirst.org
+force_https = re:^(members|support|id|lists)\.mayfirst\.org$
 force_https = aladdinschools.appspot.com
 force_https = .ottospora.nl
 force_https = www.paycheckrecords.com
-force_https = lastpass.com
-force_https = www.lastpass.com
+force_https = re:^(www\.)?lastpass\.com$
 force_https = .keyerror.com
-force_https = entropia.de
-force_https = www.entropia.de
+force_https = re:^(www\.)?entropia\.de$
 force_https = .romab.com
-force_https = logentries.com
-force_https = www.logentries.com
+force_https = re:^(www\.)?logentries\.com$
 force_https = .stripe.com
 force_https = .cloudsecurityalliance.org
 force_https = .login.sapo.pt
@@ -73,8 +51,7 @@ force_https = .cert.se
 force_https = .crypto.is
 force_https = .simon.butcher.name
 force_https = .linx.net
-force_https = dropcam.com
-force_https = www.dropcam.com
+force_https = re:^(www\.)?dropcam\.com$
 force_https = .ebanking.indovinabank.com.vn
 force_https = epoxate.com
 force_https = torproject.org
@@ -82,59 +59,41 @@ force_https = .blog.torproject.org
 force_https = .check.torproject.org
 force_https = .www.torproject.org
 force_https = .www.moneybookers.com
-force_https = ledgerscope.net
-force_https = www.ledgerscope.net
-force_https = kyps.net
-force_https = www.kyps.net
-force_https = .app.recurly.com
-force_https = .api.recurly.com
-force_https = greplin.com
-force_https = www.greplin.com
+force_https = re:^(www\.)?ledgerscope\.net$
+force_https = re:^(www\.)?kyps\.net$
+force_https = re:^(.*\.)*(app|api)\.recurly\.com$
+force_https = re:^(www\.)?greplin\.com$
 force_https = .luneta.nearbuysystems.com
 force_https = .ubertt.org
 force_https = .pixi.me
 force_https = .grepular.com
-force_https = mydigipass.com
-force_https = www.mydigipass.com
-force_https = developer.mydigipass.com
-force_https = www.developer.mydigipass.com
-force_https = sandbox.mydigipass.com
-force_https = www.sandbox.mydigipass.com
+force_https = re:^(www\.)?(developer\.|sandbox\.)?mydigipass\.com$
 force_https = .crypto.cat
 force_https = .bigshinylock.minazo.net
 force_https = .crate.io
 force_https = .braintreegateway.com
-force_https = braintreepayments.com
-force_https = www.braintreepayments.com
+force_https = re:^(www\.)?braintreepayments\.com$
 force_https = emailprivacytester.com
 force_https = .business.medbank.com.mt
 force_https = .arivo.com.br
 force_https = .www.apollo-auto.com
 force_https = .www.cueup.com
-force_https = jitsi.org
-force_https = www.jitsi.org
+force_https = re:^(www\.)?jitsi\.org$
 force_https = download.jitsi.org
 force_https = .sol.io
-force_https = irccloud.com
-force_https = www.irccloud.com
+force_https = re:^(www\.)?irccloud\.com$
 force_https = alpha.irccloud.com
 force_https = .passwd.io
 force_https = .browserid.org
 force_https = .login.persona.org
-force_https = neonisi.com
-force_https = www.neonisi.com
-force_https = shops.neonisi.com
+force_https = re:^(www\.|shops\.)?neonisi\.com$
 force_https = .piratenlogin.de
 force_https = .howrandom.org
 force_https = intercom.io
 force_https = .fatzebra.com.au
 force_https = .csawctf.poly.edu
-force_https = makeyourlaws.org
-force_https = www.makeyourlaws.org
+force_https = re:^(www\.)?makeyourlaws\.org$
 force_https = .iop.intuit.com
-force_https = api.intercom.io
-force_https = www.intercom.io
-force_https = gmail.com
-force_https = googlemail.com
-force_https = www.gmail.com
-force_https = www.googlemail.com
+force_https = re:^(api|www)\.intercom\.io$
+force_https = re:^(www\.)?gmail\.com$
+force_https = re:^(www\.)?googlemail\.com$
diff --git a/settings.c b/settings.c
index e895cc1..30b1a94 100644
--- a/settings.c
+++ b/settings.c
@@ -1812,7 +1812,11 @@ walk_ua(struct settings *s,
 int
 add_force_https(struct settings *s, char *value)
 {
-	wl_add(value, &force_https, XT_WL_PERSISTENT);
+	if (g_str_has_prefix(value, "re:")) {
+		value = &value[3];
+		wl_add(value, &force_https, XT_WL_PERSISTENT | XT_WL_REGEX);
+	} else
+		wl_add(value, &force_https, XT_WL_PERSISTENT);
 	return (0);
 }
 
@@ -3404,60 +3408,60 @@ void
 walk_cookie_wl(struct settings *s,
     void (*cb)(struct settings *, char *, void *), void *cb_args)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 
 	if (s == NULL || cb == NULL) {
 		show_oops(NULL, "walk_cookie_wl invalid parameters");
 		return;
 	}
 
-	RB_FOREACH_REVERSE(d, domain_list, &c_wl)
-		cb(s, d->d, cb_args);
+	TAILQ_FOREACH_REVERSE(w, &c_wl, wl_list, entry)
+		cb(s, w->pat, cb_args);
 }
 
 void
 walk_js_wl(struct settings *s,
     void (*cb)(struct settings *, char *, void *), void *cb_args)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 
 	if (s == NULL || cb == NULL) {
 		show_oops(NULL, "walk_js_wl invalid parameters");
 		return;
 	}
 
-	RB_FOREACH_REVERSE(d, domain_list, &js_wl)
-		cb(s, d->d, cb_args);
+	TAILQ_FOREACH_REVERSE(w, &js_wl, wl_list, entry)
+		cb(s, w->pat, cb_args);
 }
 
 void
 walk_pl_wl(struct settings *s,
     void (*cb)(struct settings *, char *, void *), void *cb_args)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 
 	if (s == NULL || cb == NULL) {
 		show_oops(NULL, "walk_pl_wl invalid parameters");
 		return;
 	}
 
-	RB_FOREACH_REVERSE(d, domain_list, &pl_wl)
-		cb(s, d->d, cb_args);
+	TAILQ_FOREACH_REVERSE(w, &pl_wl, wl_list, entry)
+		cb(s, w->pat, cb_args);
 }
 
 void
 walk_force_https(struct settings *s,
     void (*cb)(struct settings *, char *, void *), void *cb_args)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 
 	if (s == NULL || cb == NULL) {
 		show_oops(NULL, "walk_force_https invalid parameters");
 		return;
 	}
 
-	RB_FOREACH_REVERSE(d, domain_list, &force_https)
-		cb(s, d->d, cb_args);
+	TAILQ_FOREACH_REVERSE(w, &force_https, wl_list, entry)
+		cb(s, w->pat, cb_args);
 }
 
 int
diff --git a/whitelist.c b/whitelist.c
index a1204ed..47ca9d7 100644
--- a/whitelist.c
+++ b/whitelist.c
@@ -52,35 +52,22 @@ find_domain(const gchar *s, int flags)
 	return (ret);
 }
 
-struct domain *
-wl_find(const gchar *s, struct domain_list *wl)
+struct wl_entry *
+wl_find(const gchar *s, struct wl_list *wl)
 {
-	struct domain		*d = NULL, dfind;
-	int			i;
+	struct wl_entry		*w;
 
-	if (s == NULL || wl == NULL)
-		return (NULL);
-	if (strlen(s) < 2)
+	if (s == NULL || strlen(s) == 0 || wl == NULL)
 		return (NULL);
 
-	for (i = strlen(s) - 1; i >= 0; --i) {
-		if (i == 0 || (s[i] == '.')) {
-			dfind.d = (gchar *)&s[i];
-			d = RB_FIND(domain_list, wl, &dfind);
-			if (d)
-				goto done;
-			if (i == 0 && s[i] != '.') {
-				dfind.d = g_strdup_printf(".%s", s);
-				d = RB_FIND(domain_list, wl, &dfind);
-				g_free(dfind.d);
-				if (d)
-					goto done;
-			}
-		}
+	TAILQ_FOREACH(w, wl, entry) {
+		if (w->re == NULL)
+			continue;
+		if (!regexec(w->re, s, 0, 0, 0))
+			return (w);
 	}
 
-done:
-	return (d);
+	return (NULL);
 }
 
 int
@@ -92,7 +79,7 @@ wl_save(struct tab *t, struct karg *args, int list)
 	size_t			linelen;
 	const gchar		*uri;
 	struct karg		a;
-	struct domain		*d;
+	struct wl_entry		*w;
 	GSList			*cf;
 	SoupCookie		*ci, *c;
 
@@ -169,19 +156,19 @@ wl_save(struct tab *t, struct karg *args, int list)
 	a.i |= args->i;
 	switch (list) {
 	case XT_WL_JAVASCRIPT:
-		d = wl_find(dom, &js_wl);
-		if (!d) {
+		w = wl_find(dom, &js_wl);
+		if (w == NULL) {
 			settings_add("js_wl", dom);
-			d = wl_find(dom, &js_wl);
+			w = wl_find(dom, &js_wl);
 		}
 		toggle_js(t, &a);
 		break;
 
 	case XT_WL_COOKIE:
-		d = wl_find(dom, &c_wl);
-		if (!d) {
+		w = wl_find(dom, &c_wl);
+		if (w == NULL) {
 			settings_add("cookie_wl", dom);
-			d = wl_find(dom, &c_wl);
+			w = wl_find(dom, &c_wl);
 		}
 		toggle_cwl(t, &a);
 
@@ -199,26 +186,26 @@ wl_save(struct tab *t, struct karg *args, int list)
 		break;
 
 	case XT_WL_PLUGIN:
-		d = wl_find(dom, &pl_wl);
-		if (!d) {
+		w = wl_find(dom, &pl_wl);
+		if (w == NULL) {
 			settings_add("pl_wl", dom);
-			d = wl_find(dom, &pl_wl);
+			w = wl_find(dom, &pl_wl);
 		}
 		toggle_pl(t, &a);
 		break;
 	case XT_WL_HTTPS:
-		d = wl_find(dom, &force_https);
-		if (!d) {
+		w = wl_find(dom, &force_https);
+		if (w == NULL) {
 			settings_add("force_https", dom);
-			d = wl_find(dom, &force_https);
+			w = wl_find(dom, &force_https);
 		}
 		toggle_force_https(t, &a);
 		break;
 	default:
 		abort(); /* can't happen */
 	}
-	if (d)
-		d->handy = 1;
+	if (w != NULL)
+		w->handy = 1;
 
 done:
 	if (line)
@@ -234,9 +221,9 @@ done:
 }
 
 int
-wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
+wl_show(struct tab *t, struct karg *args, char *title, struct wl_list *wl)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 	char			*tmp, *body;
 
 	body = g_strdup("");
@@ -246,11 +233,11 @@ wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
 		tmp = body;
 		body = g_strdup_printf("%s<h2>Persistent</h2>", body);
 		g_free(tmp);
-		RB_FOREACH(d, domain_list, wl) {
-			if (d->handy == 0)
+		TAILQ_FOREACH(w, wl, entry) {
+			if (w->handy == 0)
 				continue;
 			tmp = body;
-			body = g_strdup_printf("%s%s<br/>", body, d->d);
+			body = g_strdup_printf("%s%s<br/>", body, w->pat);
 			g_free(tmp);
 		}
 	}
@@ -260,11 +247,11 @@ wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
 		tmp = body;
 		body = g_strdup_printf("%s<h2>Session</h2>", body);
 		g_free(tmp);
-		RB_FOREACH(d, domain_list, wl) {
-			if (d->handy == 1)
+		TAILQ_FOREACH(w, wl, entry) {
+			if (w->handy == 1)
 				continue;
 			tmp = body;
-			body = g_strdup_printf("%s%s<br/>", body, d->d);
+			body = g_strdup_printf("%s%s<br/>", body, w->pat);
 			g_free(tmp);
 		}
 	}
@@ -284,73 +271,106 @@ wl_show(struct tab *t, struct karg *args, char *title, struct domain_list *wl)
 }
 
 void
-wl_add(const char *str, struct domain_list *wl, int flags)
+wl_add(const char *str, struct wl_list *wl, int flags)
 {
-	struct domain		*d;
-	int			add_dot = 0;
-	char			*p;
+	struct wl_entry		*w;
+	int			add_dot = 0, chopped = 0;
+	const char		*s = str;
+	char			*escstr, *p, *pat;
+	char			**sv;
 
 	if (str == NULL || wl == NULL || strlen(str) < 2)
 		return;
 
 	DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
 
-	/* treat *.moo.com the same as .moo.com */
-	if (str[0] == '*' && str[1] == '.')
-		str = &str[1];
-	else if (str[0] != '.' && (flags & XT_WL_TOPLEVEL))
-		add_dot = 1;
-
 	/* slice off port number */
 	p = g_strrstr(str, ":");
 	if (p)
 		*p = '\0';
 
-	d = g_malloc(sizeof *d);
-	if (add_dot)
-		d->d = g_strdup_printf(".%s", str);
-	else
-		d->d = g_strdup(str);
-	d->handy = (flags & XT_WL_PERSISTENT) ? 1 : 0;
+	w = g_malloc(sizeof *w);
+	w->re = g_malloc(sizeof *w->re);
+	if (flags & XT_WL_REGEX) {
+		w->pat = g_strdup_printf("re:%s", str);
+		regcomp(w->re, str, REG_EXTENDED | REG_NOSUB);
+		DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", str);
+	} else {
+		/* treat *.moo.com the same as .moo.com */
+		if (s[0] == '*' && s[1] == '.')
+			s = &s[1];
+		else if (s[0] != '.' && (flags & XT_WL_TOPLEVEL))
+			add_dot = 1;
+
+		if (s[0] == '.') {
+			s = &s[1];
+			chopped = 1;
+		}
+		sv = g_strsplit(s, ".", 0);
+		escstr = g_strjoinv("\\.", sv);
+		g_strfreev(sv);
+
+		if (add_dot) {
+			w->pat = g_strdup_printf(".%s", str);
+			pat = g_strdup_printf("^(.*\\.)*%s$", escstr);
+			regcomp(w->re, pat, REG_EXTENDED | REG_NOSUB);
+		} else {
+			w->pat = g_strdup(str);
+			if (chopped)
+				pat = g_strdup_printf("^(.*\\.)*%s$", escstr);
+			else
+				pat = g_strdup_printf("^%s$", escstr);
+			regcomp(w->re, pat, REG_EXTENDED | REG_NOSUB);
+		}
+		DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", pat);
+		g_free(escstr);
+		g_free(pat);
+	}
 
-	if (RB_INSERT(domain_list, wl, d))
-		goto unwind;
+	w->handy = (flags & XT_WL_PERSISTENT) ? 1 : 0;
+
+	TAILQ_INSERT_HEAD(wl, w, entry);
 
-	DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
 	return;
-unwind:
-	if (d) {
-		if (d->d)
-			g_free(d->d);
-		g_free(d);
-	}
 }
 
 int
 add_cookie_wl(struct settings *s, char *entry)
 {
-	wl_add(entry, &c_wl, XT_WL_PERSISTENT);
+	if (g_str_has_prefix(entry, "re:")) {
+		entry = &entry[3];
+		wl_add(entry, &c_wl, XT_WL_PERSISTENT | XT_WL_REGEX);
+	} else
+		wl_add(entry, &c_wl, XT_WL_PERSISTENT);
 	return (0);
 }
 
 int
 add_js_wl(struct settings *s, char *entry)
 {
-	wl_add(entry, &js_wl, XT_WL_PERSISTENT);
+	if (g_str_has_prefix(entry, "re:")) {
+		entry = &entry[3];
+		wl_add(entry, &js_wl, XT_WL_PERSISTENT | XT_WL_REGEX);
+	} else
+		wl_add(entry, &js_wl, XT_WL_PERSISTENT);
 	return (0);
 }
 
 int
 add_pl_wl(struct settings *s, char *entry)
 {
-	wl_add(entry, &pl_wl, XT_WL_PERSISTENT);
+	if (g_str_has_prefix(entry, "re:")) {
+		entry = &entry[3];
+		wl_add(entry, &pl_wl, XT_WL_PERSISTENT | XT_WL_REGEX);
+	} else
+		wl_add(entry, &pl_wl, XT_WL_PERSISTENT);
 	return (0);
 }
 
 int
 toggle_cwl(struct tab *t, struct karg *args)
 {
-	struct domain		*d;
+	struct wl_entry		*w;
 	const gchar		*uri;
 	char			*dom = NULL;
 	int			es;
@@ -366,9 +386,9 @@ toggle_cwl(struct tab *t, struct karg *args)
 		show_oops(t, "Can't toggle domain in cookie white list");
 		goto done;
 	}
-	d = wl_find(dom, &c_wl);
+	w = wl_find(dom, &c_wl);
 
-	if (d == NULL)
+	if (w == NULL)
 		es = 0;
 	else
 		es = 1;
@@ -386,8 +406,11 @@ toggle_cwl(struct tab *t, struct karg *args)
 		wl_add(dom, &c_wl, args->i);
 	} else {
 		/* disable cookies for domain */
-		if (d)
-			RB_REMOVE(domain_list, &c_wl, d);
+		if (w != NULL) {
+			TAILQ_REMOVE(&c_wl, w, entry);
+			g_free(w->re);
+			g_free(w->pat);
+		}
 	}
 
 	if (args->i & XT_WL_RELOAD)
@@ -403,7 +426,7 @@ toggle_js(struct tab *t, struct karg *args)
 {
 	int			es;
 	const gchar		*uri;
-	struct domain		*d;
+	struct wl_entry		*w;
 	char			*dom = NULL;
 
 	if (args == NULL)
@@ -434,9 +457,12 @@ toggle_js(struct tab *t, struct karg *args)
 		args->i |= !XT_WL_PERSISTENT;
 		wl_add(dom, &js_wl, args->i);
 	} else {
-		d = wl_find(dom, &js_wl);
-		if (d)
-			RB_REMOVE(domain_list, &js_wl, d);
+		w = wl_find(dom, &js_wl);
+		if (w != NULL) {
+			TAILQ_REMOVE(&js_wl, w, entry);
+			g_free(w->re);
+			g_free(w->pat);
+		}
 		button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
 	}
 	g_object_set(G_OBJECT(t->settings),
@@ -458,7 +484,7 @@ toggle_pl(struct tab *t, struct karg *args)
 {
 	int			es;
 	const gchar		*uri;
-	struct domain		*d;
+	struct wl_entry		*w;
 	char			*dom = NULL;
 
 	if (args == NULL)
@@ -488,9 +514,12 @@ toggle_pl(struct tab *t, struct karg *args)
 		args->i |= !XT_WL_PERSISTENT;
 		wl_add(dom, &pl_wl, args->i);
 	} else {
-		d = wl_find(dom, &pl_wl);
-		if (d)
-			RB_REMOVE(domain_list, &pl_wl, d);
+		w = wl_find(dom, &pl_wl);
+		if (w != NULL) {
+			TAILQ_REMOVE(&pl_wl, w, entry);
+			g_free(w->re);
+			g_free(w->pat);
+		}
 	}
 	g_object_set(G_OBJECT(t->settings),
 	    "enable-plugins", es, (char *)NULL);
@@ -509,7 +538,7 @@ toggle_force_https(struct tab *t, struct karg *args)
 {
 	int			es;
 	const gchar		*uri;
-	struct domain		*d;
+	struct wl_entry		*w;
 	char			*dom = NULL;
 
 	if (args == NULL)
@@ -523,9 +552,9 @@ toggle_force_https(struct tab *t, struct karg *args)
 		show_oops(t, "Can't toggle domain in https force list");
 		goto done;
 	}
-	d = wl_find(dom, &force_https);
+	w = wl_find(dom, &force_https);
 
-	if (d == NULL)
+	if (w == NULL)
 		es = 0;
 	else
 		es = 1;
@@ -544,9 +573,12 @@ toggle_force_https(struct tab *t, struct karg *args)
 		args->i |= !XT_WL_PERSISTENT;
 		wl_add(dom, &force_https, args->i);
 	} else {
-		d = wl_find(dom, &force_https);
-		if (d)
-			RB_REMOVE(domain_list, &force_https, d);
+		w = wl_find(dom, &force_https);
+		if (w != NULL) {
+			TAILQ_REMOVE(&force_https, w, entry);
+			g_free(w->re);
+			g_free(w->pat);
+		}
 	}
 
 	if (args->i & XT_WL_RELOAD)
diff --git a/xombrero.1 b/xombrero.1
index 73021bf..81a5422 100644
--- a/xombrero.1
+++ b/xombrero.1
@@ -1110,10 +1110,25 @@ reject, reject all cookies.
 This is a cookie whitelist item.
 Use multiple times to add multiple entries.
 Valid entries are for example *.moo.com and the equivalent .moo.com.
+This matches both moo.com, as well as all subdomains.
 A fully qualified host is also valid and is for example www.moo.com or
 moo.com.
 Fully qualified hosts do not modify whitelist settings for any
 subdomains.
+.Pp
+Unix extended regular expressions may also be used to match any set of
+FQDNs.
+Regular expressions must begin with the prefix "re:".
+As an example, the regular expression equivalent to moo.com would be
+.Pa ^moo\.com$ ,
+and the equivalent to .moo.com would be
+.Pa ^(.*\.)*moo\.com$ .
+If using regular expressions for whitelist items, be careful to not
+accidentally match other domains;
+you will almost always want to add the ^ and $ characters to the
+beginning and end of the regex so that, for example,
+.Pa moo\.com
+would not match not-moo.com.
 .It Cm cookies_enabled
 Enable cookies.
 .It Cm ctrl_click_focus
diff --git a/xombrero.c b/xombrero.c
index e02f3ba..4d09a32 100644
--- a/xombrero.c
+++ b/xombrero.c
@@ -213,11 +213,11 @@ struct tab_list		tabs;
 struct history_list	hl;
 int			hl_purge_count = 0;
 struct session_list	sessions;
-struct domain_list	c_wl;
-struct domain_list	js_wl;
-struct domain_list	pl_wl;
-struct domain_list	force_https;
-struct domain_list	svil;
+struct wl_list		c_wl;
+struct wl_list		js_wl;
+struct wl_list		pl_wl;
+struct wl_list		force_https;
+struct wl_list		svil;
 struct strict_transport_tree	st_tree;
 struct undo_tailq	undos;
 struct keybinding_list	kbl;
@@ -686,13 +686,6 @@ history_rb_cmp(struct history *h1, struct history *h2)
 RB_GENERATE(history_list, history, entry, history_rb_cmp);
 
 int
-domain_rb_cmp(struct domain *d1, struct domain *d2)
-{
-	return (strcmp(d1->d, d2->d));
-}
-RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
-
-int
 download_rb_cmp(struct download *e1, struct download *e2)
 {
 	return (e1->id < e2->id ? -1 : e1->id > e2->id);
@@ -990,12 +983,12 @@ html_escape(const char *val)
 	return (s);
 }
 
-struct domain *
-wl_find_uri(const gchar *s, struct domain_list *wl)
+struct wl_entry *
+wl_find_uri(const gchar *s, struct wl_list *wl)
 {
 	int			i;
 	char			*ss;
-	struct domain		*r;
+	struct wl_entry		*w;
 
 	if (s == NULL || wl == NULL)
 		return (NULL);
@@ -1013,9 +1006,9 @@ wl_find_uri(const gchar *s, struct domain_list *wl)
 		if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
 			ss = g_strdup(s);
 			ss[i] = '\0';
-			r = wl_find(ss, wl);
+			w = wl_find(ss, wl);
 			g_free(ss);
-			return (r);
+			return (w);
 		}
 
 	return (NULL);
@@ -2161,7 +2154,7 @@ check_cert_changes(struct tab *t, const char *uri)
 {
 	SoupURI			*soupuri = NULL;
 	struct karg		args = {0};
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	const char		*errstr = NULL;
 	struct karg		*argsp;
 
@@ -2182,7 +2175,7 @@ check_cert_changes(struct tab *t, const char *uri)
 		if ((soupuri = soup_uri_new(uri)) == NULL ||
 		    soupuri->host == NULL)
 			break;
-		if ((d = wl_find(soupuri->host, &svil)) != NULL)
+		if ((w = wl_find(soupuri->host, &svil)) != NULL)
 			break;
 		t->xtp_meaning = XT_XTP_TAB_MEANING_SV;
 		argsp = g_malloc0(sizeof(struct karg));
@@ -3669,13 +3662,13 @@ activate_search_entry_cb(GtkWidget* entry, struct tab *t)
 void
 check_and_set_cookie(const gchar *uri, struct tab *t)
 {
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	int			es = 0;
 
 	if (uri == NULL || t == NULL)
 		return;
 
-	if ((d = wl_find_uri(uri, &c_wl)) == NULL)
+	if ((w = wl_find_uri(uri, &c_wl)) == NULL)
 		es = 0;
 	else
 		es = 1;
@@ -3691,13 +3684,13 @@ check_and_set_cookie(const gchar *uri, struct tab *t)
 void
 check_and_set_js(const gchar *uri, struct tab *t)
 {
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	int			es = 0;
 
 	if (uri == NULL || t == NULL)
 		return;
 
-	if ((d = wl_find_uri(uri, &js_wl)) == NULL)
+	if ((w = wl_find_uri(uri, &js_wl)) == NULL)
 		es = 0;
 	else
 		es = 1;
@@ -3718,13 +3711,13 @@ check_and_set_js(const gchar *uri, struct tab *t)
 void
 check_and_set_pl(const gchar *uri, struct tab *t)
 {
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	int			es = 0;
 
 	if (uri == NULL || t == NULL)
 		return;
 
-	if ((d = wl_find_uri(uri, &pl_wl)) == NULL)
+	if ((w = wl_find_uri(uri, &pl_wl)) == NULL)
 		es = 0;
 	else
 		es = 1;
@@ -4833,12 +4826,12 @@ corrupt_file:
 int
 force_https_check(const char *uri)
 {
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 
 	if (uri == NULL)
 		return (0);
 
-	if ((d = wl_find_uri(uri, &force_https)) == NULL)
+	if ((w = wl_find_uri(uri, &force_https)) == NULL)
 		return (0);
 	else
 		return (1);
@@ -5107,7 +5100,7 @@ WebKitWebView *
 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
 {
 	struct tab		*tt;
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 	const gchar		*uri;
 	WebKitWebView		*webview = NULL;
 	int			x = 1;
@@ -5120,7 +5113,7 @@ webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
 		webview = t->wv;
 	} else if (enable_scripts == 0 && enable_js_whitelist == 1) {
 		uri = webkit_web_view_get_uri(wv);
-		if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
+		if (uri && (w = wl_find_uri(uri, &js_wl)) == NULL)
 			return (NULL);
 
 		if (t->ctrl_click) {
@@ -5145,13 +5138,13 @@ gboolean
 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
 {
 	const gchar		*uri;
-	struct domain		*d = NULL;
+	struct wl_entry		*w = NULL;
 
 	DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
 
 	if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
 		uri = webkit_web_view_get_uri(wv);
-		if (uri && (d = wl_find_uri(uri, &js_wl)) == NULL)
+		if (uri && (w = wl_find_uri(uri, &js_wl)) == NULL)
 			return (FALSE);
 
 		delete_tab(t);
@@ -8632,13 +8625,9 @@ main(int argc, char **argv)
 	strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
 
 	RB_INIT(&hl);
-	RB_INIT(&js_wl);
-	RB_INIT(&pl_wl);
-	RB_INIT(&force_https);
 	RB_INIT(&downloads);
 	RB_INIT(&st_tree);
 	RB_INIT(&svl);
-	RB_INIT(&svil);
 	RB_INIT(&ua_list);
 
 	TAILQ_INIT(&sessions);
@@ -8652,6 +8641,10 @@ main(int argc, char **argv)
 	TAILQ_INIT(&shl);
 	TAILQ_INIT(&cul);
 	TAILQ_INIT(&srl);
+	TAILQ_INIT(&js_wl);
+	TAILQ_INIT(&pl_wl);
+	TAILQ_INIT(&force_https);
+	TAILQ_INIT(&svil);
 
 #ifndef XT_RESOURCE_LIMITS_DISABLE
 	struct rlimit		rlp;
diff --git a/xombrero.css b/xombrero.css
index c826134..88f8e75 100644
--- a/xombrero.css
+++ b/xombrero.css
@@ -37,7 +37,8 @@ GtkSpinner {
 	-GtkWidget-line-width: 0;
 }
 
-:insensitive {
+.button :insensitive,
+GtkMenu :insensitive {
 	background-color: rgba(0,0,0,0); /* transparent */
 }
 
diff --git a/xombrero.h b/xombrero.h
index 7e97d88..5ab0623 100644
--- a/xombrero.h
+++ b/xombrero.h
@@ -544,6 +544,7 @@ void			startpage_add(const char *, ...);
 #define XT_SAVE			(1<<10)
 #define XT_OPEN			(1<<11)
 #define XT_CACHE		(1<<12)
+#define XT_WL_REGEX		(1<<13)
 
 #define XT_WL_INVALID		(0)
 #define XT_WL_JAVASCRIPT	(1)
@@ -551,16 +552,16 @@ void			startpage_add(const char *, ...);
 #define XT_WL_PLUGIN		(3)
 #define XT_WL_HTTPS		(4)
 
-struct domain {
-	RB_ENTRY(domain)	entry;
-	gchar			*d;
+struct wl_entry {
+	regex_t			*re;
+	char			*pat;
 	int			handy; /* app use */
+	TAILQ_ENTRY(wl_entry)	entry;
 };
-RB_HEAD(domain_list, domain);
-RB_PROTOTYPE(domain_list, domain, entry, domain_rb_cmp);
+TAILQ_HEAD(wl_list, wl_entry);
 
 int			wl_show(struct tab *, struct karg *, char *,
-			    struct domain_list *);
+			    struct wl_list *);
 
 /* uri aliases */
 struct alias {
@@ -580,7 +581,7 @@ struct mime_type {
 };
 TAILQ_HEAD(mime_type_list, mime_type);
 
-struct domain *	wl_find(const gchar *, struct domain_list *);
+struct wl_entry * wl_find(const gchar *, struct wl_list *);
 int		wl_save(struct tab *, struct karg *, int);
 int		toggle_cwl(struct tab *, struct karg *);
 int		toggle_js(struct tab *, struct karg *);
@@ -843,7 +844,7 @@ void		focus_webview(struct tab *);
 int		is_g_object_setting(GObject *, char *);
 int		set_scrollbar_visibility(struct tab *, int);
 int		save_runtime_setting(const char *, const char *);
-void		wl_add(const char *, struct domain_list *, int);
+void		wl_add(const char *, struct wl_list *, int);
 
 #define		XT_DL_START	(0)
 #define		XT_DL_RESTART	(1)
@@ -959,11 +960,11 @@ extern int			hl_purge_count;
 extern struct download_list	downloads;
 extern struct tab_list		tabs;
 extern struct about_type	about_list[];
-extern struct domain_list	c_wl;
-extern struct domain_list	js_wl;
-extern struct domain_list	pl_wl;
-extern struct domain_list	force_https;
-extern struct domain_list	svil;
+extern struct wl_list		c_wl;
+extern struct wl_list		js_wl;
+extern struct wl_list		pl_wl;
+extern struct wl_list		force_https;
+extern struct wl_list		svil;
 extern struct strict_transport_tree	st_tree;
 extern struct alias_list	aliases;
 extern struct mime_type_list	mtl;