From: Kirill A. Korinsky Subject: Re: unwind: support wildcard in blacklist To: Otto Moerbeek , OpenBSD tech , florian@openbsd.org Date: Sat, 06 Jul 2024 16:40:30 +0100 Folks, Here a reminder about this diff. I'm using it for about two weeks and it jsut works. The diff changes symantic of blacklist into: type list file [log] A file with domains to build the block or allow list. The block list allows querying all possible domains, and any matching domain returns a REFUSED response. The allow list allows querying only matching domains. With log blocked queries are logged. The list supports limited wildcard syntax: domains starting with . (dot) are treated as any subdomains on that zone. So, after that this two lines: google.com *.google.com blocks any requests to google.com and all its subdomains, or allows access only to google.com which depends on type of this list. The 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 STRING %token NUMBER -%type port dot prefopt log acceptbogus +%type port dot prefopt log acceptbogus list_allowed %type string authname %type 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 ee2a83cbe2a..17fc0d43976 100644 --- sbin/unwind/resolver.c +++ sbin/unwind/resolver.c @@ -682,7 +682,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 dbcec1bd693..7344c2f81ac 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 @@ -168,6 +178,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