Annotation of src/lib/libwrap/hosts_access.c, Revision 1.19.10.2
1.19.10.2! riz 1: /* $NetBSD: hosts_access.c,v 1.19.10.1 2012/04/23 16:48:53 riz Exp $ */
1.2 christos 2:
1.1 mrg 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.
1.8 simonb 9: *
1.1 mrg 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.
1.8 simonb 14: *
1.1 mrg 15: * Diagnostics are reported through syslog(3).
1.8 simonb 16: *
1.1 mrg 17: * Compile with -DNETGROUP if your library provides support for netgroups.
1.8 simonb 18: *
1.1 mrg 19: * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
20: */
21:
1.2 christos 22: #include <sys/cdefs.h>
1.1 mrg 23: #ifndef lint
1.2 christos 24: #if 0
1.9 itojun 25: static char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22";
1.2 christos 26: #else
1.19.10.2! riz 27: __RCSID("$NetBSD: hosts_access.c,v 1.19.10.1 2012/04/23 16:48:53 riz Exp $");
1.2 christos 28: #endif
1.1 mrg 29: #endif
30:
31: /* System libraries. */
32:
33: #include <sys/types.h>
34: #include <sys/param.h>
1.10 itojun 35: #ifdef INET6
36: #include <sys/socket.h>
37: #endif
1.1 mrg 38: #include <netinet/in.h>
39: #include <arpa/inet.h>
40: #include <stdio.h>
1.4 christos 41: #include <stdlib.h>
1.1 mrg 42: #include <syslog.h>
43: #include <ctype.h>
44: #include <errno.h>
45: #include <setjmp.h>
46: #include <string.h>
1.4 christos 47: #include <netdb.h>
1.3 christos 48: #ifdef NETGROUP
49: #include <netgroup.h>
50: #include <rpcsvc/ypclnt.h>
1.1 mrg 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:
1.19.10.2! riz 75: char *hosts_allow_table = HOSTS_ALLOW;
! 76: char *hosts_deny_table = HOSTS_DENY;
1.1 mrg 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:
1.19.10.2! riz 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 *));
1.10 itojun 98: #ifdef INET6
1.19.10.2! riz 99: static int masked_match6 __P((char *, char *, char *));
1.10 itojun 100: #endif
1.1 mrg 101:
102: /* Size of logical line buffer. */
103:
104: #define BUFLEN 2048
105:
106: /* hosts_access - host access control facility */
107:
1.19.10.2! riz 108: int hosts_access(request)
! 109: struct request_info *request;
1.1 mrg 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.
1.8 simonb 119: *
1.1 mrg 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++;
1.9 itojun 130: verdict = setjmp(tcpd_buf);
131: if (verdict != 0)
1.1 mrg 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:
1.19.10.2! riz 142: static int table_match(table, request)
! 143: char *table;
! 144: struct request_info *request;
1.1 mrg 145: {
146: FILE *fp;
147: char sv_list[BUFLEN]; /* becomes list of daemons */
148: char *cl_list; /* becomes list of clients */
1.2 christos 149: char *sh_cmd = NULL; /* becomes optional shell command */
1.1 mrg 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:
1.19.10.2! riz 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 *));
1.1 mrg 205: {
1.17 lukem 206: char *tok;
207: static char *last;
1.10 itojun 208: int l;
1.1 mrg 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:
1.16 itojun 217: for (tok = strtok_r(list, sep, &last); tok != 0;
218: tok = strtok_r(NULL, sep, &last)) {
1.1 mrg 219: if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */
220: return (NO);
1.10 itojun 221: l = strlen(tok);
222: if (*tok == '[' && tok[l - 1] == ']') {
223: tok[l - 1] = '\0';
224: tok++;
225: }
1.1 mrg 226: if (match_fn(tok, request)) { /* YES: look for exceptions */
1.16 itojun 227: while ((tok = strtok_r(NULL, sep, &last)) && STR_NE(tok, "EXCEPT"))
1.1 mrg 228: /* VOID */ ;
1.16 itojun 229: return (tok == 0 || list_match(NULL, request, match_fn) == 0);
1.1 mrg 230: }
231: }
232: return (NO);
233: }
234:
235: /* server_match - match server information */
236:
1.19.10.2! riz 237: static int server_match(tok, request)
! 238: char *tok;
! 239: struct request_info *request;
1.1 mrg 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:
1.19.10.2! riz 253: static int client_match(tok, request)
! 254: char *tok;
! 255: struct request_info *request;
1.1 mrg 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:
1.19.10.2! riz 269: static int host_match(tok, host)
! 270: char *tok;
! 271: struct host_info *host;
1.1 mrg 272: {
273: char *mask;
274:
275: /*
276: * This code looks a little hairy because we want to avoid unnecessary
277: * hostname lookups.
1.8 simonb 278: *
1.1 mrg 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);
1.16 itojun 289: return (innetgr(tok + 1, eval_hostname(host), NULL, mydomain));
1.1 mrg 290: #else
291: tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */
292: return (NO);
293: #endif
1.19 christos 294: } else if (tok[0] == '/') { /* /file hack */
295: return (hostfile_match(tok, host));
1.1 mrg 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));
1.4 christos 302: } else if (strncmp(tok, "{RBL}.", 6) == 0) { /* RBL lookup in domain */
303: return rbl_match(tok+6, eval_hostaddr(host));
1.1 mrg 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: }
1.4 christos 310: }
311:
1.19 christos 312: /* hostfile_match - look up host patterns from file */
313:
1.19.10.2! riz 314: static int hostfile_match(path, host)
! 315: char *path;
! 316: struct host_info *host;
1.19 christos 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:
1.4 christos 332: /* rbl_match() - match host by looking up in RBL domain */
333:
1.19.10.2! riz 334: static int rbl_match(rbl_domain, rbl_hostaddr)
! 335: char *rbl_domain; /* RBL domain */
! 336: char *rbl_hostaddr; /* hostaddr */
1.4 christos 337: {
338: char *rbl_name;
339: unsigned long host_address;
340: int ret = NO;
1.5 christos 341: size_t len = strlen(rbl_domain) + (4 * 4) + 2;
1.8 simonb 342:
1.6 christos 343: if (dot_quad_addr(rbl_hostaddr, &host_address) != 0) {
1.4 christos 344: tcpd_warn("unable to convert %s to address", rbl_hostaddr);
345: return (NO);
346: }
1.18 jdc 347: host_address = ntohl(host_address);
1.4 christos 348: /* construct the rbl name to look up */
1.5 christos 349: if ((rbl_name = malloc(len)) == NULL) {
1.4 christos 350: tcpd_jump("not enough memory to build RBL name for %s in %s", rbl_hostaddr, rbl_domain);
351: /* NOTREACHED */
352: }
1.5 christos 353: snprintf(rbl_name, len, "%u.%u.%u.%u.%s",
1.4 christos 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;
1.1 mrg 367: }
368:
369: /* string_match - match string against pattern */
370:
1.19.10.2! riz 371: static int string_match(tok, string)
! 372: char *tok;
! 373: char *string;
1.1 mrg 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:
1.19.10.2! riz 393: static int masked_match(net_tok, mask_tok, string)
! 394: char *net_tok;
! 395: char *mask_tok;
! 396: char *string;
1.1 mrg 397: {
1.10 itojun 398: #ifndef INET6
399: return masked_match4(net_tok, mask_tok, string);
400: #else
1.15 itojun 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: */
1.19.10.2! riz 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) {
1.10 itojun 409: return masked_match4(net_tok, mask_tok, string);
410: } else
411: return masked_match6(net_tok, mask_tok, string);
412: #endif
413: }
414:
1.19.10.2! riz 415: static int masked_match4(net_tok, mask_tok, string)
! 416: char *net_tok;
! 417: char *mask_tok;
! 418: char *string;
1.10 itojun 419: {
1.1 mrg 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:
1.6 christos 430: if (dot_quad_addr(string, &addr) != 0)
1.1 mrg 431: return (NO);
1.15 itojun 432: if (dot_quad_addr(net_tok, &net) != 0 ||
433: dot_quad_addr(mask_tok, &mask) != 0) {
1.1 mrg 434: tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok);
435: return (NO); /* not tcpd_jump() */
436: }
1.12 atatat 437:
438: if ((net & ~mask) != 0)
439: tcpd_warn("host bits not all zero in %s/%s", net_tok, mask_tok);
440:
1.1 mrg 441: return ((addr & mask) == net);
442: }
1.10 itojun 443:
444: #ifdef INET6
1.19.10.2! riz 445: static int masked_match6(net_tok, mask_tok, string)
! 446: char *net_tok;
! 447: char *mask_tok;
! 448: char *string;
1.10 itojun 449: {
1.15 itojun 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;
1.19.10.2! riz 458: int i;
1.15 itojun 459: char *np, *mp, *ap;
1.19.10.2! riz 460: int alen;
1.10 itojun 461:
1.15 itojun 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);
1.10 itojun 473: } else
474: return NO;
475:
1.15 itojun 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) {
1.10 itojun 497: memset(&mask, 0, sizeof(mask));
1.15 itojun 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;
1.10 itojun 511: }
1.15 itojun 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);
1.10 itojun 517: } else
1.15 itojun 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);
1.10 itojun 542: } else
1.15 itojun 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;
1.10 itojun 565: }
566:
1.15 itojun 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: }
1.12 atatat 572:
1.15 itojun 573: for (i = 0; i < alen; i++)
574: ap[i] &= mp[i];
1.12 atatat 575:
1.15 itojun 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);
1.10 itojun 580: }
581: #endif
CVSweb <webmaster@jp.NetBSD.org>