Download raw body.
unwind: support wildcard in blacklist
On Tue, 25 Jun 2024 13:28:03 +0100,
Stuart Henderson <stu@spacehopper.org> wrote:
>
> I agree. How about just ".google.com" to match in this fashion?
> Syntax like this is common in some MTAs, is fairly understandable,
> and doesn't get confused with DNS wildcards.
>
Indeed, it makes things cleaner.
Thanks for suggestion.
Updated diff:
diff --git sbin/unwind/frontend.c sbin/unwind/frontend.c
index ccbc977eb73..6aa68aa0235 100644
--- sbin/unwind/frontend.c
+++ sbin/unwind/frontend.c
@@ -115,9 +115,11 @@ struct pending_query {
TAILQ_HEAD(, pending_query) pending_queries;
-struct bl_node {
- RB_ENTRY(bl_node) entry;
+struct l_node {
+ RB_ENTRY(l_node) entry;
char *domain;
+ int len;
+ int wildcard;
};
__dead void frontend_shutdown(void);
@@ -148,9 +150,9 @@ struct pending_query *find_pending_query(uint64_t);
void parse_trust_anchor(struct trust_anchor_head *, int);
void send_trust_anchors(struct trust_anchor_head *);
void write_trust_anchors(struct trust_anchor_head *, int);
-void parse_blocklist(int);
-int bl_cmp(struct bl_node *, struct bl_node *);
-void free_bl(void);
+void parse_list(int);
+int l_cmp(struct l_node *, struct l_node *);
+void free_l(void);
int pending_query_cnt(void);
void check_available_af(void);
@@ -164,13 +166,25 @@ int ta_fd = -1;
static struct trust_anchor_head trust_anchors, new_trust_anchors;
-RB_HEAD(bl_tree, bl_node) bl_head = RB_INITIALIZER(&bl_head);
-RB_PROTOTYPE(bl_tree, bl_node, entry, bl_cmp)
-RB_GENERATE(bl_tree, bl_node, entry, bl_cmp)
+RB_HEAD(l_tree, l_node) l_head = RB_INITIALIZER(&l_head);
+RB_PROTOTYPE(l_tree, l_node, entry, l_cmp)
+RB_GENERATE(l_tree, l_node, entry, l_cmp)
struct dns64_prefix *dns64_prefixes;
int dns64_prefix_count;
+static void
+reverse(char* begin, char* end)
+{
+ char t;
+ while (begin < --end) {
+ t = *begin;
+ *begin = *end;
+ *end = t;
+ ++begin;
+ }
+}
+
void
frontend_sig_handler(int sig, short event, void *bula)
{
@@ -362,7 +376,7 @@ frontend_dispatch_main(int fd, short event, void *bula)
event_add(&iev_resolver->ev, NULL);
break;
case IMSG_RECONF_CONF:
- case IMSG_RECONF_BLOCKLIST_FILE:
+ case IMSG_RECONF_LIST_FILE:
case IMSG_RECONF_FORWARDER:
case IMSG_RECONF_DOT_FORWARDER:
case IMSG_RECONF_FORCE:
@@ -373,8 +387,8 @@ frontend_dispatch_main(int fd, short event, void *bula)
fatalx("%s: IMSG_RECONF_END without "
"IMSG_RECONF_CONF", __func__);
merge_config(frontend_conf, nconf);
- if (frontend_conf->blocklist_file == NULL)
- free_bl();
+ if (frontend_conf->list_file == NULL)
+ free_l();
nconf = NULL;
break;
case IMSG_UDP6SOCK:
@@ -454,11 +468,11 @@ frontend_dispatch_main(int fd, short event, void *bula)
if (!TAILQ_EMPTY(&trust_anchors))
send_trust_anchors(&trust_anchors);
break;
- case IMSG_BLFD:
+ case IMSG_LSFD:
if ((fd = imsg_get_fd(&imsg)) == -1)
fatalx("%s: expected to receive imsg block "
"list fd but didn't receive any", __func__);
- parse_blocklist(fd);
+ parse_list(fd);
break;
default:
log_debug("%s: error handling imsg %d", __func__,
@@ -732,8 +746,8 @@ void
handle_query(struct pending_query *pq)
{
struct query_imsg query_imsg;
- struct bl_node find;
- int rcode;
+ struct l_node find;
+ int rcode, matched;
char *str;
char dname[LDNS_MAX_DOMAINLEN + 1];
char qclass_buf[16];
@@ -790,9 +804,14 @@ handle_query(struct pending_query *pq)
log_debug("%s: %s %s %s ?", ip_port((struct sockaddr *)&pq->from),
dname, qclass_buf, qtype_buf);
+ find.len = strlen(dname);
+ find.wildcard = 0;
+ reverse(dname, dname + find.len);
find.domain = dname;
- if (RB_FIND(bl_tree, &bl_head, &find) != NULL) {
- if (frontend_conf->blocklist_log)
+ matched = (RB_FIND(l_tree, &l_head, &find) != NULL);
+ reverse(dname, dname + find.len);
+ if (matched != frontend_conf->list_allowed) {
+ if (frontend_conf->list_log)
log_info("blocking %s", dname);
error_answer(pq, LDNS_RCODE_REFUSED);
goto send_answer;
@@ -1520,39 +1539,45 @@ out:
}
void
-parse_blocklist(int fd)
+parse_list(int fd)
{
FILE *f;
- struct bl_node *bl_node;
+ struct l_node *l_node;
char *line = NULL;
size_t linesize = 0;
ssize_t linelen;
if((f = fdopen(fd, "r")) == NULL) {
- log_warn("cannot read block list");
+ log_warn("cannot read list file");
close(fd);
return;
}
- free_bl();
+ free_l();
while ((linelen = getline(&line, &linesize, f)) != -1) {
if (line[linelen - 1] == '\n') {
if (linelen >= 2 && line[linelen - 2] != '.')
line[linelen - 1] = '.';
else
- line[linelen - 1] = '\0';
+ line[linelen-- - 1] = '\0';
}
- bl_node = malloc(sizeof *bl_node);
- if (bl_node == NULL)
+ if (line[0] == '#')
+ continue;
+
+ l_node = malloc(sizeof *l_node);
+ if (l_node == NULL)
fatal("%s: malloc", __func__);
- if ((bl_node->domain = strdup(line)) == NULL)
+ if ((l_node->domain = strdup(line)) == NULL)
fatal("%s: strdup", __func__);
- if (RB_INSERT(bl_tree, &bl_head, bl_node) != NULL) {
- log_warnx("duplicate blocked domain \"%s\"", line);
- free(bl_node->domain);
- free(bl_node);
+ reverse(l_node->domain, l_node->domain + linelen);
+ l_node->len = linelen;
+ l_node->wildcard = line[0] == '.';
+ if (RB_INSERT(l_tree, &l_head, l_node) != NULL) {
+ log_warnx("duplicate list domain \"%s\"", line);
+ free(l_node->domain);
+ free(l_node);
}
}
free(line);
@@ -1562,17 +1587,22 @@ parse_blocklist(int fd)
}
int
-bl_cmp(struct bl_node *e1, struct bl_node *e2) {
- return (strcasecmp(e1->domain, e2->domain));
+l_cmp(struct l_node *e1, struct l_node *e2) {
+ if (e1->wildcard == e2->wildcard)
+ return (strcasecmp(e1->domain, e2->domain));
+ else if (e1->wildcard)
+ return (strncasecmp(e1->domain, e2->domain, e1->len));
+ else /* e2->wildcard */
+ return (strncasecmp(e1->domain, e2->domain, e2->len));
}
void
-free_bl(void)
+free_l(void)
{
- struct bl_node *n, *nxt;
+ struct l_node *n, *nxt;
- RB_FOREACH_SAFE(n, bl_tree, &bl_head, nxt) {
- RB_REMOVE(bl_tree, &bl_head, n);
+ RB_FOREACH_SAFE(n, l_tree, &l_head, nxt) {
+ RB_REMOVE(l_tree, &l_head, n);
free(n->domain);
free(n);
}
diff --git sbin/unwind/parse.y sbin/unwind/parse.y
index 79231e3d5be..271a325656d 100644
--- sbin/unwind/parse.y
+++ sbin/unwind/parse.y
@@ -102,11 +102,11 @@ typedef struct {
%token INCLUDE ERROR
%token FORWARDER DOT PORT ODOT_FORWARDER ODOT_AUTOCONF ODOT_DHCP
%token AUTHENTICATION NAME PREFERENCE RECURSOR AUTOCONF DHCP STUB
-%token BLOCK LIST LOG FORCE ACCEPT BOGUS
+%token ALLOW BLOCK LIST LOG FORCE ACCEPT BOGUS
%token <v.string> STRING
%token <v.number> NUMBER
-%type <v.number> port dot prefopt log acceptbogus
+%type <v.number> port dot prefopt log acceptbogus list_allowed
%type <v.string> string authname
%type <v.force> force_list
@@ -118,7 +118,7 @@ grammar : /* empty */
| grammar varset '\n'
| grammar uw_pref '\n'
| grammar uw_forwarder '\n'
- | grammar block_list '\n'
+ | grammar the_list '\n'
| grammar force '\n'
| grammar error '\n' { file->errors++; }
;
@@ -176,22 +176,28 @@ optnl : '\n' optnl /* zero or more newlines */
| /*empty*/
;
-block_list : BLOCK LIST STRING log {
- if (conf->blocklist_file != NULL) {
- yyerror("block list already "
- "configured");
+the_list : list_allowed LIST STRING log {
+ if (conf->list_file != NULL) {
+ yyerror("the %s list already "
+ "configured",
+ $1 ? "allow" : "block");
free($3);
YYERROR;
} else {
- conf->blocklist_file = strdup($3);
- if (conf->blocklist_file == NULL)
+ conf->list_file = strdup($3);
+ if (conf->list_file == NULL)
err(1, "strdup");
free($3);
- conf->blocklist_log = $4;
+ conf->list_log = $4;
+ conf->list_allowed = $1;
}
}
;
+list_allowed: BLOCK { $$ = 0; }
+ | ALLOW { $$ = 1; }
+ ;
+
uw_pref : PREFERENCE {
conf->res_pref.len = 0;
memset(conf->enabled_resolvers, 0,
@@ -417,6 +423,7 @@ lookup(char *s)
static const struct keywords keywords[] = {
{"DoT", DOT},
{"accept", ACCEPT},
+ {"allow", ALLOW},
{"authentication", AUTHENTICATION},
{"autoconf", AUTOCONF},
{"block", BLOCK},
diff --git sbin/unwind/printconf.c sbin/unwind/printconf.c
index c2ee982c442..58fbaf2132a 100644
--- sbin/unwind/printconf.c
+++ sbin/unwind/printconf.c
@@ -66,9 +66,11 @@ print_config(struct uw_conf *conf)
printf("}\n");
}
- if (conf->blocklist_file != NULL)
- printf("block list \"%s\"%s\n", conf->blocklist_file,
- conf->blocklist_log ? " log" : "");
+ if (conf->list_file != NULL)
+ printf("%s list \"%s\"%s\n",
+ conf->list_allowed ? "allow" : "block",
+ conf->list_file,
+ conf->list_log ? " log" : "");
for (j = 0; j < UW_RES_NONE; j++) {
struct force_tree_entry *e;
int empty = 1;
diff --git sbin/unwind/resolver.c sbin/unwind/resolver.c
index 5f025443caa..99d3d5e81a1 100644
--- sbin/unwind/resolver.c
+++ sbin/unwind/resolver.c
@@ -676,7 +676,7 @@ resolver_dispatch_main(int fd, short event, void *bula)
fatal("pledge");
break;
case IMSG_RECONF_CONF:
- case IMSG_RECONF_BLOCKLIST_FILE:
+ case IMSG_RECONF_LIST_FILE:
case IMSG_RECONF_FORWARDER:
case IMSG_RECONF_DOT_FORWARDER:
case IMSG_RECONF_FORCE:
diff --git sbin/unwind/unwind.c sbin/unwind/unwind.c
index 1b70b6e3e6c..df1311c8417 100644
--- sbin/unwind/unwind.c
+++ sbin/unwind/unwind.c
@@ -73,7 +73,7 @@ int main_reload(void);
int main_sendall(enum imsg_type, void *, uint16_t);
void open_ports(void);
void solicit_dns_proposals(void);
-void send_blocklist_fd(void);
+void send_list_fd(void);
struct uw_conf *main_conf;
static struct imsgev *iev_frontend;
@@ -290,8 +290,8 @@ main(int argc, char *argv[])
main_imsg_compose_frontend_fd(IMSG_ROUTESOCK, 0, frontend_routesock);
main_imsg_send_config(main_conf);
- if (main_conf->blocklist_file != NULL)
- send_blocklist_fd();
+ if (main_conf->list_file != NULL)
+ send_list_fd();
if (pledge("stdio rpath sendfd", NULL) == -1)
fatal("pledge");
@@ -577,8 +577,8 @@ main_reload(void)
merge_config(main_conf, xconf);
- if (main_conf->blocklist_file != NULL)
- send_blocklist_fd();
+ if (main_conf->list_file != NULL)
+ send_list_fd();
return (0);
}
@@ -593,10 +593,10 @@ main_imsg_send_config(struct uw_conf *xconf)
if (main_sendall(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1)
return (-1);
- if (xconf->blocklist_file != NULL) {
- if (main_sendall(IMSG_RECONF_BLOCKLIST_FILE,
- xconf->blocklist_file, strlen(xconf->blocklist_file) + 1)
- == -1)
+ if (xconf->list_file != NULL) {
+ if (main_sendall(IMSG_RECONF_LIST_FILE,
+ xconf->list_file, strlen(xconf->list_file) + 1)
+ == -1)
return (-1);
}
@@ -667,9 +667,10 @@ merge_config(struct uw_conf *conf, struct uw_conf *xconf)
memcpy(&conf->res_pref, &xconf->res_pref,
sizeof(conf->res_pref));
- free(conf->blocklist_file);
- conf->blocklist_file = xconf->blocklist_file;
- conf->blocklist_log = xconf->blocklist_log;
+ free(conf->list_file);
+ conf->list_file = xconf->list_file;
+ conf->list_log = xconf->list_log;
+ conf->list_allowed = xconf->list_allowed;
/* Add new forwarders. */
TAILQ_CONCAT(&conf->uw_forwarder_list, &xconf->uw_forwarder_list,
@@ -861,14 +862,14 @@ solicit_dns_proposals(void)
}
void
-send_blocklist_fd(void)
+send_list_fd(void)
{
- int bl_fd;
+ int fd;
- if ((bl_fd = open(main_conf->blocklist_file, O_RDONLY)) != -1)
- main_imsg_compose_frontend_fd(IMSG_BLFD, 0, bl_fd);
+ if ((fd = open(main_conf->list_file, O_RDONLY)) != -1)
+ main_imsg_compose_frontend_fd(IMSG_LSFD, 0, fd);
else
- log_warn("%s", main_conf->blocklist_file);
+ log_warn("%s", main_conf->list_file);
}
void
@@ -896,11 +897,10 @@ imsg_receive_config(struct imsg *imsg, struct uw_conf **xconf)
TAILQ_INIT(&nconf->uw_dot_forwarder_list);
RB_INIT(&nconf->force);
break;
- case IMSG_RECONF_BLOCKLIST_FILE:
+ case IMSG_RECONF_LIST_FILE:
if (((char *)imsg->data)[IMSG_DATA_SIZE(*imsg) - 1] != '\0')
- fatalx("Invalid blocklist file");
- if ((nconf->blocklist_file = strdup(imsg->data)) ==
- NULL)
+ fatalx("Invalid allowlist file");
+ if ((nconf->list_file = strdup(imsg->data)) == NULL)
fatal("%s: strdup", __func__);
break;
case IMSG_RECONF_FORWARDER:
diff --git sbin/unwind/unwind.conf.5 sbin/unwind/unwind.conf.5
index dd64e3e4dd2..eeced7f8cd7 100644
--- sbin/unwind/unwind.conf.5
+++ sbin/unwind/unwind.conf.5
@@ -63,15 +63,25 @@ forwarder { $fwd1 $fwd2 }
.Ed
.Sh GLOBAL CONFIGURATION
.Bl -tag -width Ds
-.It Ic block list Ar file Op Cm log
-A file containing domains to block, one per line.
-If a domain from this list is queried,
-.Nm unwind
-answers with a return code of
-.Dv REFUSED .
-With
+.It Ar type Ic list Ar file Op Cm log
+A
+.Ar file
+with domains to build the
+.Ic block
+or
+.Ic allow
+list. The
+.Ic block
+list allows querying all possible domains, and any matching
+domain returns a
+.Dv REFUSED
+response. The
+.Ic allow
+list allows querying only matching domains. With
.Cm log
-blocked queries are logged.
+blocked queries are logged. The list supports limited wildcard
+syntax: domains starting with . (dot) are treated as any subdomains
+on that zone.
.It Ic forwarder Brq Ar address Oo Ic port Ar number Oc Oo Oo Ic authentication name Ar name Oc Ic DoT Oc ...
A list of addresses of DNS name servers to forward queries to.
.Ic port
@@ -163,6 +173,20 @@ to use a specific resolver type:
.Bd -literal -offset indent
force autoconf { domain.local }
.Ed
+.Pp
+Allow requests for domains in openbsd.org zone and log each
+blocked request:
+.Bd -literal -offset indent
+allow list "/etc/allowlist" log
+.Ed
+.Pp
+Where a list of allowed doamins
+.Pa /etc/allowlist :
+.Bd -literal -offset indent
+openbsd.org
+.openbsd.org
+.Ed
+.Pp
.Sh SEE ALSO
.Xr rc.conf.local 8 ,
.Xr unwind 8 ,
diff --git sbin/unwind/unwind.h sbin/unwind/unwind.h
index f21baf72970..2847fdf389f 100644
--- sbin/unwind/unwind.h
+++ sbin/unwind/unwind.h
@@ -90,7 +90,7 @@ enum imsg_type {
IMSG_CTL_AUTOCONF,
IMSG_CTL_MEM,
IMSG_RECONF_CONF,
- IMSG_RECONF_BLOCKLIST_FILE,
+ IMSG_RECONF_LIST_FILE,
IMSG_RECONF_FORWARDER,
IMSG_RECONF_DOT_FORWARDER,
IMSG_RECONF_FORCE,
@@ -117,7 +117,7 @@ enum imsg_type {
IMSG_NEW_TAS_ABORT,
IMSG_NEW_TAS_DONE,
IMSG_NETWORK_CHANGED,
- IMSG_BLFD,
+ IMSG_LSFD,
IMSG_REPLACE_DNS,
IMSG_NEW_DNS64_PREFIXES_START,
IMSG_NEW_DNS64_PREFIX,
@@ -155,8 +155,9 @@ struct uw_conf {
struct force_tree force;
struct resolver_preference res_pref;
int enabled_resolvers[UW_RES_NONE];
- char *blocklist_file;
- int blocklist_log;
+ char *list_file;
+ int list_log;
+ int list_allowed;
};
struct query_imsg {
--
wbr, Kirill
unwind: support wildcard in blacklist