xref: /netbsd-src/lib/libwrap/hosts_access.c (revision d0fed6c87ddc40a8bffa6f99e7433ddfc864dd83)
1  /*
2   * This module implements a simple access control language that is based on
3   * host (or domain) names, NIS (host) netgroup names, IP addresses (or
4   * network numbers) and daemon process names. When a match is found the
5   * search is terminated, and depending on whether PROCESS_OPTIONS is defined,
6   * a list of options is executed or an optional shell command is executed.
7   *
8   * Host and user names are looked up on demand, provided that suitable endpoint
9   * information is available as sockaddr_in structures or TLI netbufs. As a
10   * side effect, the pattern matching process may change the contents of
11   * request structure fields.
12   *
13   * Diagnostics are reported through syslog(3).
14   *
15   * Compile with -DNETGROUP if your library provides support for netgroups.
16   *
17   * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
18   */
19 
20 #ifndef lint
21 static char sccsid[] = "@(#) hosts_access.c 1.20 96/02/11 17:01:27";
22 #endif
23 
24 /* System libraries. */
25 
26 #include <sys/types.h>
27 #include <sys/param.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <stdio.h>
31 #include <syslog.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <setjmp.h>
35 #include <string.h>
36 
37 extern char *fgets();
38 extern int errno;
39 
40 #ifndef	INADDR_NONE
41 #define	INADDR_NONE	(-1)		/* XXX should be 0xffffffff */
42 #endif
43 
44 /* Local stuff. */
45 
46 #include "tcpd.h"
47 
48 /* Error handling. */
49 
50 extern jmp_buf tcpd_buf;
51 
52 /* Delimiters for lists of daemons or clients. */
53 
54 static char sep[] = ", \t\r\n";
55 
56 /* Constants to be used in assignments only, not in comparisons... */
57 
58 #define	YES		1
59 #define	NO		0
60 
61  /*
62   * These variables are globally visible so that they can be redirected in
63   * verification mode.
64   */
65 
66 char   *hosts_allow_table = HOSTS_ALLOW;
67 char   *hosts_deny_table = HOSTS_DENY;
68 int     hosts_access_verbose = 0;
69 
70  /*
71   * In a long-running process, we are not at liberty to just go away.
72   */
73 
74 int     resident = (-1);		/* -1, 0: unknown; +1: yes */
75 
76 /* Forward declarations. */
77 
78 static int table_match();
79 static int list_match();
80 static int server_match();
81 static int client_match();
82 static int host_match();
83 static int string_match();
84 static int masked_match();
85 
86 /* Size of logical line buffer. */
87 
88 #define	BUFLEN 2048
89 
90 /* hosts_access - host access control facility */
91 
92 int     hosts_access(request)
93 struct request_info *request;
94 {
95     int     verdict;
96 
97     /*
98      * If the (daemon, client) pair is matched by an entry in the file
99      * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
100      * client) pair is matched by an entry in the file /etc/hosts.deny,
101      * access is denied. Otherwise, access is granted. A non-existent
102      * access-control file is treated as an empty file.
103      *
104      * After a rule has been matched, the optional language extensions may
105      * decide to grant or refuse service anyway. Or, while a rule is being
106      * processed, a serious error is found, and it seems better to play safe
107      * and deny service. All this is done by jumping back into the
108      * hosts_access() routine, bypassing the regular return from the
109      * table_match() function calls below.
110      */
111 
112     if (resident <= 0)
113 	resident++;
114     if ((verdict = setjmp(tcpd_buf)) != 0)
115 	return (verdict == AC_PERMIT);
116     if (table_match(hosts_allow_table, request))
117 	return (YES);
118     if (table_match(hosts_deny_table, request))
119 	return (NO);
120     return (YES);
121 }
122 
123 /* table_match - match table entries with (daemon, client) pair */
124 
125 static int table_match(table, request)
126 char   *table;
127 struct request_info *request;
128 {
129     FILE   *fp;
130     char    sv_list[BUFLEN];		/* becomes list of daemons */
131     char   *cl_list;			/* becomes list of clients */
132     char   *sh_cmd;			/* becomes optional shell command */
133     int     match = NO;
134     struct tcpd_context saved_context;
135 
136     saved_context = tcpd_context;		/* stupid compilers */
137 
138     /*
139      * Between the fopen() and fclose() calls, avoid jumps that may cause
140      * file descriptor leaks.
141      */
142 
143     if ((fp = fopen(table, "r")) != 0) {
144 	tcpd_context.file = table;
145 	tcpd_context.line = 0;
146 	while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) {
147 	    if (sv_list[strlen(sv_list) - 1] != '\n') {
148 		tcpd_warn("missing newline or line too long");
149 		continue;
150 	    }
151 	    if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
152 		continue;
153 	    if ((cl_list = split_at(sv_list, ':')) == 0) {
154 		tcpd_warn("missing \":\" separator");
155 		continue;
156 	    }
157 	    sh_cmd = split_at(cl_list, ':');
158 	    match = list_match(sv_list, request, server_match)
159 		&& list_match(cl_list, request, client_match);
160 	}
161 	(void) fclose(fp);
162     } else if (errno != ENOENT) {
163 	tcpd_warn("cannot open %s: %m", table);
164     }
165     if (match) {
166 	if (hosts_access_verbose > 1)
167 	    syslog(LOG_DEBUG, "matched:  %s line %d",
168 		   tcpd_context.file, tcpd_context.line);
169 	if (sh_cmd) {
170 #ifdef PROCESS_OPTIONS
171 	    process_options(sh_cmd, request);
172 #else
173 	    char    cmd[BUFSIZ];
174 	    shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request));
175 #endif
176 	}
177     }
178     tcpd_context = saved_context;
179     return (match);
180 }
181 
182 /* list_match - match a request against a list of patterns with exceptions */
183 
184 static int list_match(list, request, match_fn)
185 char   *list;
186 struct request_info *request;
187 int   (*match_fn) ();
188 {
189     char   *tok;
190 
191     /*
192      * Process tokens one at a time. We have exhausted all possible matches
193      * when we reach an "EXCEPT" token or the end of the list. If we do find
194      * a match, look for an "EXCEPT" list and recurse to determine whether
195      * the match is affected by any exceptions.
196      */
197 
198     for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
199 	if (STR_EQ(tok, "EXCEPT"))		/* EXCEPT: give up */
200 	    return (NO);
201 	if (match_fn(tok, request)) {		/* YES: look for exceptions */
202 	    while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT"))
203 		 /* VOID */ ;
204 	    return (tok == 0 || list_match((char *) 0, request, match_fn) == 0);
205 	}
206     }
207     return (NO);
208 }
209 
210 /* server_match - match server information */
211 
212 static int server_match(tok, request)
213 char   *tok;
214 struct request_info *request;
215 {
216     char   *host;
217 
218     if ((host = split_at(tok + 1, '@')) == 0) {	/* plain daemon */
219 	return (string_match(tok, eval_daemon(request)));
220     } else {					/* daemon@host */
221 	return (string_match(tok, eval_daemon(request))
222 		&& host_match(host, request->server));
223     }
224 }
225 
226 /* client_match - match client information */
227 
228 static int client_match(tok, request)
229 char   *tok;
230 struct request_info *request;
231 {
232     char   *host;
233 
234     if ((host = split_at(tok + 1, '@')) == 0) {	/* plain host */
235 	return (host_match(tok, request->client));
236     } else {					/* user@host */
237 	return (host_match(host, request->client)
238 		&& string_match(tok, eval_user(request)));
239     }
240 }
241 
242 /* host_match - match host name and/or address against pattern */
243 
244 static int host_match(tok, host)
245 char   *tok;
246 struct host_info *host;
247 {
248     char   *mask;
249 
250     /*
251      * This code looks a little hairy because we want to avoid unnecessary
252      * hostname lookups.
253      *
254      * The KNOWN pattern requires that both address AND name be known; some
255      * patterns are specific to host names or to host addresses; all other
256      * patterns are satisfied when either the address OR the name match.
257      */
258 
259     if (tok[0] == '@') {			/* netgroup: look it up */
260 #ifdef  NETGROUP
261 	static char *mydomain = 0;
262 	if (mydomain == 0)
263 	    yp_get_default_domain(&mydomain);
264 	return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain));
265 #else
266 	tcpd_warn("netgroup support is disabled");	/* not tcpd_jump() */
267 	return (NO);
268 #endif
269     } else if (STR_EQ(tok, "KNOWN")) {		/* check address and name */
270 	char   *name = eval_hostname(host);
271 	return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name));
272     } else if (STR_EQ(tok, "LOCAL")) {		/* local: no dots in name */
273 	char   *name = eval_hostname(host);
274 	return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name));
275     } else if ((mask = split_at(tok, '/')) != 0) {	/* net/mask */
276 	return (masked_match(tok, mask, eval_hostaddr(host)));
277     } else {					/* anything else */
278 	return (string_match(tok, eval_hostaddr(host))
279 	    || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host))));
280     }
281 }
282 
283 /* string_match - match string against pattern */
284 
285 static int string_match(tok, string)
286 char   *tok;
287 char   *string;
288 {
289     int     n;
290 
291     if (tok[0] == '.') {			/* suffix */
292 	n = strlen(string) - strlen(tok);
293 	return (n > 0 && STR_EQ(tok, string + n));
294     } else if (STR_EQ(tok, "ALL")) {		/* all: match any */
295 	return (YES);
296     } else if (STR_EQ(tok, "KNOWN")) {		/* not unknown */
297 	return (STR_NE(string, unknown));
298     } else if (tok[(n = strlen(tok)) - 1] == '.') {	/* prefix */
299 	return (STRN_EQ(tok, string, n));
300     } else {					/* exact match */
301 	return (STR_EQ(tok, string));
302     }
303 }
304 
305 /* masked_match - match address against netnumber/netmask */
306 
307 static int masked_match(net_tok, mask_tok, string)
308 char   *net_tok;
309 char   *mask_tok;
310 char   *string;
311 {
312     unsigned long net;
313     unsigned long mask;
314     unsigned long addr;
315 
316     /*
317      * Disallow forms other than dotted quad: the treatment that inet_addr()
318      * gives to forms with less than four components is inconsistent with the
319      * access control language. John P. Rouillard <rouilj@cs.umb.edu>.
320      */
321 
322     if ((addr = dot_quad_addr(string)) == INADDR_NONE)
323 	return (NO);
324     if ((net = dot_quad_addr(net_tok)) == INADDR_NONE
325 	|| (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) {
326 	tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok);
327 	return (NO);				/* not tcpd_jump() */
328     }
329     return ((addr & mask) == net);
330 }
331