xref: /netbsd-src/lib/libwrap/hosts_access.c (revision b1c86f5f087524e68db12794ee9c3e3da1ab17a0)
1 /*	$NetBSD: hosts_access.c,v 1.19 2008/12/18 20:16:52 christos Exp $	*/
2 
3  /*
4   * This module implements a simple access control language that is based on
5   * host (or domain) names, NIS (host) netgroup names, IP addresses (or
6   * network numbers) and daemon process names. When a match is found the
7   * search is terminated, and depending on whether PROCESS_OPTIONS is defined,
8   * a list of options is executed or an optional shell command is executed.
9   *
10   * Host and user names are looked up on demand, provided that suitable endpoint
11   * information is available as sockaddr_in structures or TLI netbufs. As a
12   * side effect, the pattern matching process may change the contents of
13   * request structure fields.
14   *
15   * Diagnostics are reported through syslog(3).
16   *
17   * Compile with -DNETGROUP if your library provides support for netgroups.
18   *
19   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
20   */
21 
22 #include <sys/cdefs.h>
23 #ifndef lint
24 #if 0
25 static char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22";
26 #else
27 __RCSID("$NetBSD: hosts_access.c,v 1.19 2008/12/18 20:16:52 christos Exp $");
28 #endif
29 #endif
30 
31 /* System libraries. */
32 
33 #include <sys/types.h>
34 #include <sys/param.h>
35 #ifdef INET6
36 #include <sys/socket.h>
37 #endif
38 #include <netinet/in.h>
39 #include <arpa/inet.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <syslog.h>
43 #include <ctype.h>
44 #include <errno.h>
45 #include <setjmp.h>
46 #include <string.h>
47 #include <netdb.h>
48 #ifdef  NETGROUP
49 #include <netgroup.h>
50 #include <rpcsvc/ypclnt.h>
51 #endif
52 
53 /* Local stuff. */
54 
55 #include "tcpd.h"
56 
57 /* Error handling. */
58 
59 extern jmp_buf tcpd_buf;
60 
61 /* Delimiters for lists of daemons or clients. */
62 
63 static char sep[] = ", \t\r\n";
64 
65 /* Constants to be used in assignments only, not in comparisons... */
66 
67 #define	YES		1
68 #define	NO		0
69 
70  /*
71   * These variables are globally visible so that they can be redirected in
72   * verification mode.
73   */
74 
75 char   *hosts_allow_table = HOSTS_ALLOW;
76 char   *hosts_deny_table = HOSTS_DENY;
77 int     hosts_access_verbose = 0;
78 
79  /*
80   * In a long-running process, we are not at liberty to just go away.
81   */
82 
83 int     resident = (-1);		/* -1, 0: unknown; +1: yes */
84 
85 /* Forward declarations. */
86 
87 static int table_match __P((char *, struct request_info *));
88 static int list_match __P((char *, struct request_info *,
89     int (*)(char *, struct request_info *)));
90 static int server_match __P((char *, struct request_info *));
91 static int client_match __P((char *, struct request_info *));
92 static int host_match __P((char *, struct host_info *));
93 static int hostfile_match __P((char *, struct host_info *));
94 static int rbl_match __P((char *, char *));
95 static int string_match __P((char *, char *));
96 static int masked_match __P((char *, char *, char *));
97 static int masked_match4 __P((char *, char *, char *));
98 #ifdef INET6
99 static int masked_match6 __P((char *, char *, char *));
100 #endif
101 
102 /* Size of logical line buffer. */
103 
104 #define	BUFLEN 2048
105 
106 /* hosts_access - host access control facility */
107 
108 int     hosts_access(request)
109 struct request_info *request;
110 {
111     int     verdict;
112 
113     /*
114      * If the (daemon, client) pair is matched by an entry in the file
115      * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
116      * client) pair is matched by an entry in the file /etc/hosts.deny,
117      * access is denied. Otherwise, access is granted. A non-existent
118      * access-control file is treated as an empty file.
119      *
120      * After a rule has been matched, the optional language extensions may
121      * decide to grant or refuse service anyway. Or, while a rule is being
122      * processed, a serious error is found, and it seems better to play safe
123      * and deny service. All this is done by jumping back into the
124      * hosts_access() routine, bypassing the regular return from the
125      * table_match() function calls below.
126      */
127 
128     if (resident <= 0)
129 	resident++;
130     verdict = setjmp(tcpd_buf);
131     if (verdict != 0)
132 	return (verdict == AC_PERMIT);
133     if (table_match(hosts_allow_table, request))
134 	return (YES);
135     if (table_match(hosts_deny_table, request))
136 	return (NO);
137     return (YES);
138 }
139 
140 /* table_match - match table entries with (daemon, client) pair */
141 
142 static int table_match(table, request)
143 char   *table;
144 struct request_info *request;
145 {
146     FILE   *fp;
147     char    sv_list[BUFLEN];		/* becomes list of daemons */
148     char   *cl_list;			/* becomes list of clients */
149     char   *sh_cmd = NULL;		/* becomes optional shell command */
150     int     match = NO;
151     struct tcpd_context saved_context;
152 
153     saved_context = tcpd_context;		/* stupid compilers */
154 
155     /*
156      * Between the fopen() and fclose() calls, avoid jumps that may cause
157      * file descriptor leaks.
158      */
159 
160     if ((fp = fopen(table, "r")) != 0) {
161 	tcpd_context.file = table;
162 	tcpd_context.line = 0;
163 	while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) {
164 	    if (sv_list[strlen(sv_list) - 1] != '\n') {
165 		tcpd_warn("missing newline or line too long");
166 		continue;
167 	    }
168 	    if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
169 		continue;
170 	    if ((cl_list = split_at(sv_list, ':')) == 0) {
171 		tcpd_warn("missing \":\" separator");
172 		continue;
173 	    }
174 	    sh_cmd = split_at(cl_list, ':');
175 	    match = list_match(sv_list, request, server_match)
176 		&& list_match(cl_list, request, client_match);
177 	}
178 	(void) fclose(fp);
179     } else if (errno != ENOENT) {
180 	tcpd_warn("cannot open %s: %m", table);
181     }
182     if (match) {
183 	if (hosts_access_verbose > 1)
184 	    syslog(LOG_DEBUG, "matched:  %s line %d",
185 		   tcpd_context.file, tcpd_context.line);
186 	if (sh_cmd) {
187 #ifdef PROCESS_OPTIONS
188 	    process_options(sh_cmd, request);
189 #else
190 	    char    cmd[BUFSIZ];
191 	    shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request));
192 #endif
193 	}
194     }
195     tcpd_context = saved_context;
196     return (match);
197 }
198 
199 /* list_match - match a request against a list of patterns with exceptions */
200 
201 static int list_match(list, request, match_fn)
202 char   *list;
203 struct request_info *request;
204 int   (*match_fn) __P((char *, struct request_info *));
205 {
206     char   *tok;
207     static char *last;
208     int l;
209 
210     /*
211      * Process tokens one at a time. We have exhausted all possible matches
212      * when we reach an "EXCEPT" token or the end of the list. If we do find
213      * a match, look for an "EXCEPT" list and recurse to determine whether
214      * the match is affected by any exceptions.
215      */
216 
217     for (tok = strtok_r(list, sep, &last); tok != 0;
218       tok = strtok_r(NULL, sep, &last)) {
219 	if (STR_EQ(tok, "EXCEPT"))		/* EXCEPT: give up */
220 	    return (NO);
221 	l = strlen(tok);
222 	if (*tok == '[' && tok[l - 1] == ']') {
223 	    tok[l - 1] = '\0';
224 	    tok++;
225 	}
226 	if (match_fn(tok, request)) {		/* YES: look for exceptions */
227 	    while ((tok = strtok_r(NULL, sep, &last)) && STR_NE(tok, "EXCEPT"))
228 		 /* VOID */ ;
229 	    return (tok == 0 || list_match(NULL, request, match_fn) == 0);
230 	}
231     }
232     return (NO);
233 }
234 
235 /* server_match - match server information */
236 
237 static int server_match(tok, request)
238 char   *tok;
239 struct request_info *request;
240 {
241     char   *host;
242 
243     if ((host = split_at(tok + 1, '@')) == 0) {	/* plain daemon */
244 	return (string_match(tok, eval_daemon(request)));
245     } else {					/* daemon@host */
246 	return (string_match(tok, eval_daemon(request))
247 		&& host_match(host, request->server));
248     }
249 }
250 
251 /* client_match - match client information */
252 
253 static int client_match(tok, request)
254 char   *tok;
255 struct request_info *request;
256 {
257     char   *host;
258 
259     if ((host = split_at(tok + 1, '@')) == 0) {	/* plain host */
260 	return (host_match(tok, request->client));
261     } else {					/* user@host */
262 	return (host_match(host, request->client)
263 		&& string_match(tok, eval_user(request)));
264     }
265 }
266 
267 /* host_match - match host name and/or address against pattern */
268 
269 static int host_match(tok, host)
270 char   *tok;
271 struct host_info *host;
272 {
273     char   *mask;
274 
275     /*
276      * This code looks a little hairy because we want to avoid unnecessary
277      * hostname lookups.
278      *
279      * The KNOWN pattern requires that both address AND name be known; some
280      * patterns are specific to host names or to host addresses; all other
281      * patterns are satisfied when either the address OR the name match.
282      */
283 
284     if (tok[0] == '@') {			/* netgroup: look it up */
285 #ifdef  NETGROUP
286 	static char *mydomain = 0;
287 	if (mydomain == 0)
288 	    yp_get_default_domain(&mydomain);
289 	return (innetgr(tok + 1, eval_hostname(host), NULL, mydomain));
290 #else
291 	tcpd_warn("netgroup support is disabled");	/* not tcpd_jump() */
292 	return (NO);
293 #endif
294     } else if (tok[0] == '/') {			/* /file hack */
295 	return (hostfile_match(tok, host));
296     } else if (STR_EQ(tok, "KNOWN")) {		/* check address and name */
297 	char   *name = eval_hostname(host);
298 	return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name));
299     } else if (STR_EQ(tok, "LOCAL")) {		/* local: no dots in name */
300 	char   *name = eval_hostname(host);
301 	return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name));
302     } else if (strncmp(tok, "{RBL}.", 6) == 0) { /* RBL lookup in domain */
303 	return rbl_match(tok+6, eval_hostaddr(host));
304     } else if ((mask = split_at(tok, '/')) != 0) {	/* net/mask */
305 	return (masked_match(tok, mask, eval_hostaddr(host)));
306     } else {					/* anything else */
307 	return (string_match(tok, eval_hostaddr(host))
308 	    || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host))));
309     }
310 }
311 
312 /* hostfile_match - look up host patterns from file */
313 
314 static int hostfile_match(path, host)
315 char   *path;
316 struct host_info *host;
317 {
318     char    tok[BUFSIZ];
319     int     match = NO;
320     FILE   *fp;
321 
322     if ((fp = fopen(path, "r")) != 0) {
323 	while (fscanf(fp, "%s", tok) == 1 && !(match = host_match(tok, host)))
324 	     /* void */ ;
325 	fclose(fp);
326     } else if (errno != ENOENT) {
327 	tcpd_warn("open %s: %m", path);
328     }
329     return (match);
330 }
331 
332 /* rbl_match() - match host by looking up in RBL domain */
333 
334 static int rbl_match(rbl_domain, rbl_hostaddr)
335 char   *rbl_domain;				/* RBL domain */
336 char   *rbl_hostaddr;				/* hostaddr */
337 {
338     char *rbl_name;
339     unsigned long host_address;
340     int ret = NO;
341     size_t len = strlen(rbl_domain) + (4 * 4) + 2;
342 
343     if (dot_quad_addr(rbl_hostaddr, &host_address) != 0) {
344 	tcpd_warn("unable to convert %s to address", rbl_hostaddr);
345 	return (NO);
346     }
347     host_address = ntohl(host_address);
348     /*  construct the rbl name to look up */
349     if ((rbl_name = malloc(len)) == NULL) {
350 	tcpd_jump("not enough memory to build RBL name for %s in %s", rbl_hostaddr, rbl_domain);
351 	/* NOTREACHED */
352     }
353     snprintf(rbl_name, len, "%u.%u.%u.%u.%s",
354 	    (unsigned int) ((host_address) & 0xff),
355 	    (unsigned int) ((host_address >> 8) & 0xff),
356 	    (unsigned int) ((host_address >> 16) & 0xff),
357 	    (unsigned int) ((host_address >> 24) & 0xff),
358 	    rbl_domain);
359     /* look it up */
360     if (gethostbyname(rbl_name) != NULL) {
361 	/* successful lookup - they're on the RBL list */
362 	ret = YES;
363     }
364     free(rbl_name);
365 
366     return ret;
367 }
368 
369 /* string_match - match string against pattern */
370 
371 static int string_match(tok, string)
372 char   *tok;
373 char   *string;
374 {
375     int     n;
376 
377     if (tok[0] == '.') {			/* suffix */
378 	n = strlen(string) - strlen(tok);
379 	return (n > 0 && STR_EQ(tok, string + n));
380     } else if (STR_EQ(tok, "ALL")) {		/* all: match any */
381 	return (YES);
382     } else if (STR_EQ(tok, "KNOWN")) {		/* not unknown */
383 	return (STR_NE(string, unknown));
384     } else if (tok[(n = strlen(tok)) - 1] == '.') {	/* prefix */
385 	return (STRN_EQ(tok, string, n));
386     } else {					/* exact match */
387 	return (STR_EQ(tok, string));
388     }
389 }
390 
391 /* masked_match - match address against netnumber/netmask */
392 
393 static int masked_match(net_tok, mask_tok, string)
394 char   *net_tok;
395 char   *mask_tok;
396 char   *string;
397 {
398 #ifndef INET6
399     return masked_match4(net_tok, mask_tok, string);
400 #else
401     /*
402      * masked_match4() is kept just for supporting shortened IPv4 address form.
403      * If we could get rid of shortened IPv4 form, we could just always use
404      * masked_match6().
405      */
406     if (dot_quad_addr(net_tok, NULL) != INADDR_NONE &&
407         dot_quad_addr(mask_tok, NULL) != INADDR_NONE &&
408         dot_quad_addr(string, NULL) != INADDR_NONE) {
409 	return masked_match4(net_tok, mask_tok, string);
410     } else
411 	return masked_match6(net_tok, mask_tok, string);
412 #endif
413 }
414 
415 static int masked_match4(net_tok, mask_tok, string)
416 char   *net_tok;
417 char   *mask_tok;
418 char   *string;
419 {
420     unsigned long net;
421     unsigned long mask;
422     unsigned long addr;
423 
424     /*
425      * Disallow forms other than dotted quad: the treatment that inet_addr()
426      * gives to forms with less than four components is inconsistent with the
427      * access control language. John P. Rouillard <rouilj@cs.umb.edu>.
428      */
429 
430     if (dot_quad_addr(string, &addr) != 0)
431 	return (NO);
432     if (dot_quad_addr(net_tok, &net) != 0 ||
433         dot_quad_addr(mask_tok, &mask) != 0) {
434 	tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok);
435 	return (NO);				/* not tcpd_jump() */
436     }
437 
438     if ((net & ~mask) != 0)
439 	tcpd_warn("host bits not all zero in %s/%s", net_tok, mask_tok);
440 
441     return ((addr & mask) == net);
442 }
443 
444 #ifdef INET6
445 static int masked_match6(net_tok, mask_tok, string)
446 char   *net_tok;
447 char   *mask_tok;
448 char   *string;
449 {
450     union {
451 	struct sockaddr sa;
452 	struct sockaddr_in sin;
453 	struct sockaddr_in6 sin6;
454     } net, mask, addr;
455     struct addrinfo hints, *res;
456     unsigned long masklen;
457     char *ep;
458     int i;
459     char *np, *mp, *ap;
460     int alen;
461 
462     memset(&hints, 0, sizeof(hints));
463     hints.ai_family = PF_UNSPEC;
464     hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
465     hints.ai_flags = AI_NUMERICHOST;
466     if (getaddrinfo(net_tok, "0", &hints, &res) == 0) {
467 	if (res->ai_addrlen > sizeof(net) || res->ai_next) {
468 	    freeaddrinfo(res);
469 	    return NO;
470 	}
471 	memcpy(&net, res->ai_addr, res->ai_addrlen);
472 	freeaddrinfo(res);
473     } else
474 	return NO;
475 
476     memset(&hints, 0, sizeof(hints));
477     hints.ai_family = net.sa.sa_family;
478     hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
479     hints.ai_flags = AI_NUMERICHOST;
480     ep = NULL;
481     if (getaddrinfo(mask_tok, "0", &hints, &res) == 0) {
482 	if (res->ai_family == AF_INET6 &&
483 	    ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id) {
484 	    freeaddrinfo(res);
485 	    return NO;
486 	}
487 	if (res->ai_addrlen > sizeof(mask) || res->ai_next) {
488 	    freeaddrinfo(res);
489 	    return NO;
490 	}
491 	memcpy(&mask, res->ai_addr, res->ai_addrlen);
492 	freeaddrinfo(res);
493     } else {
494 	ep = NULL;
495 	masklen = strtoul(mask_tok, &ep, 10);
496 	if (ep && !*ep) {
497 	    memset(&mask, 0, sizeof(mask));
498 	    mask.sa.sa_family = net.sa.sa_family;
499 	    mask.sa.sa_len = net.sa.sa_len;
500 	    switch (mask.sa.sa_family) {
501 	    case AF_INET:
502 		mp = (char *)&mask.sin.sin_addr;
503 		alen = sizeof(mask.sin.sin_addr);
504 		break;
505 	    case AF_INET6:
506 		mp = (char *)&mask.sin6.sin6_addr;
507 		alen = sizeof(mask.sin6.sin6_addr);
508 		break;
509 	    default:
510 		return NO;
511 	    }
512 	    if (masklen / 8 > alen)
513 		return NO;
514 	    memset(mp, 0xff, masklen / 8);
515 	    if (masklen % 8)
516 		mp[masklen / 8] = 0xff00 >> (masklen % 8);
517 	} else
518 	    return NO;
519     }
520 
521     memset(&hints, 0, sizeof(hints));
522     hints.ai_family = PF_UNSPEC;
523     hints.ai_socktype = SOCK_DGRAM;	/*dummy*/
524     hints.ai_flags = AI_NUMERICHOST;
525     if (getaddrinfo(string, "0", &hints, &res) == 0) {
526 	if (res->ai_addrlen > sizeof(addr) || res->ai_next) {
527 	    freeaddrinfo(res);
528 	    return NO;
529 	}
530 	/* special case - IPv4 mapped address */
531 	if (net.sa.sa_family == AF_INET && res->ai_family == AF_INET6 &&
532 	    IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr)) {
533 	    memset(&addr, 0, sizeof(addr));
534 	    addr.sa.sa_family = net.sa.sa_family;
535 	    addr.sa.sa_len = net.sa.sa_len;
536 	    memcpy(&addr.sin.sin_addr,
537 	        &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr.s6_addr[12],
538 		sizeof(addr.sin.sin_addr));
539 	} else
540 	    memcpy(&addr, res->ai_addr, res->ai_addrlen);
541 	freeaddrinfo(res);
542     } else
543 	return NO;
544 
545     if (net.sa.sa_family != mask.sa.sa_family ||
546         net.sa.sa_family != addr.sa.sa_family) {
547 	return NO;
548     }
549 
550     switch (net.sa.sa_family) {
551     case AF_INET:
552 	np = (char *)&net.sin.sin_addr;
553 	mp = (char *)&mask.sin.sin_addr;
554 	ap = (char *)&addr.sin.sin_addr;
555 	alen = sizeof(net.sin.sin_addr);
556 	break;
557     case AF_INET6:
558 	np = (char *)&net.sin6.sin6_addr;
559 	mp = (char *)&mask.sin6.sin6_addr;
560 	ap = (char *)&addr.sin6.sin6_addr;
561 	alen = sizeof(net.sin6.sin6_addr);
562 	break;
563     default:
564 	return NO;
565     }
566 
567     for (i = 0; i < alen; i++)
568 	if (np[i] & ~mp[i]) {
569 	    tcpd_warn("host bits not all zero in %s/%s", net_tok, mask_tok);
570 	    break;
571 	}
572 
573     for (i = 0; i < alen; i++)
574 	ap[i] &= mp[i];
575 
576     if (addr.sa.sa_family == AF_INET6 && addr.sin6.sin6_scope_id &&
577         addr.sin6.sin6_scope_id != net.sin6.sin6_scope_id)
578 	return NO;
579     return (memcmp(ap, np, alen) == 0);
580 }
581 #endif
582