hosts_access.c revision 44743
1214077Sgibbs /*
2214077Sgibbs  * This module implements a simple access control language that is based on
3214077Sgibbs  * host (or domain) names, NIS (host) netgroup names, IP addresses (or
4214077Sgibbs  * network numbers) and daemon process names. When a match is found the
5214077Sgibbs  * search is terminated, and depending on whether PROCESS_OPTIONS is defined,
6214077Sgibbs  * a list of options is executed or an optional shell command is executed.
7214077Sgibbs  *
8214077Sgibbs  * Host and user names are looked up on demand, provided that suitable endpoint
9214077Sgibbs  * information is available as sockaddr_in structures or TLI netbufs. As a
10214077Sgibbs  * side effect, the pattern matching process may change the contents of
11214077Sgibbs  * request structure fields.
12214077Sgibbs  *
13214077Sgibbs  * Diagnostics are reported through syslog(3).
14214077Sgibbs  *
15214077Sgibbs  * Compile with -DNETGROUP if your library provides support for netgroups.
16214077Sgibbs  *
17214077Sgibbs  * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
18214077Sgibbs  */
19214077Sgibbs
20214077Sgibbs#ifndef lint
21214077Sgibbsstatic char sccsid[] = "@(#) hosts_access.c 1.21 97/02/12 02:13:22";
22214077Sgibbs#endif
23214077Sgibbs
24214077Sgibbs/* System libraries. */
25214077Sgibbs
26214077Sgibbs#include <sys/types.h>
27214077Sgibbs#include <sys/param.h>
28214077Sgibbs#include <netinet/in.h>
29214077Sgibbs#include <arpa/inet.h>
30214077Sgibbs#include <stdio.h>
31214077Sgibbs#include <syslog.h>
32214077Sgibbs#include <ctype.h>
33214077Sgibbs#include <errno.h>
34214077Sgibbs#include <setjmp.h>
35214077Sgibbs#include <string.h>
36214077Sgibbs
37214077Sgibbsextern char *fgets();
38214077Sgibbsextern int errno;
39214077Sgibbs
40214077Sgibbs#ifndef	INADDR_NONE
41214077Sgibbs#define	INADDR_NONE	(-1)		/* XXX should be 0xffffffff */
42214077Sgibbs#endif
43214077Sgibbs
44214077Sgibbs/* Local stuff. */
45214077Sgibbs
46214077Sgibbs#include "tcpd.h"
47214077Sgibbs
48214077Sgibbs/* Error handling. */
49214077Sgibbs
50214077Sgibbsextern jmp_buf tcpd_buf;
51214077Sgibbs
52214077Sgibbs/* Delimiters for lists of daemons or clients. */
53214077Sgibbs
54214077Sgibbsstatic char sep[] = ", \t\r\n";
55214077Sgibbs
56214077Sgibbs/* Constants to be used in assignments only, not in comparisons... */
57214077Sgibbs
58214077Sgibbs#define	YES		1
59214077Sgibbs#define	NO		0
60214077Sgibbs
61214077Sgibbs /*
62214077Sgibbs  * These variables are globally visible so that they can be redirected in
63214077Sgibbs  * verification mode.
64214077Sgibbs  */
65214077Sgibbs
66214077Sgibbschar   *hosts_allow_table = HOSTS_ALLOW;
67214077Sgibbschar   *hosts_deny_table = HOSTS_DENY;
68214077Sgibbsint     hosts_access_verbose = 0;
69214077Sgibbs
70214077Sgibbs /*
71214077Sgibbs  * In a long-running process, we are not at liberty to just go away.
72214077Sgibbs  */
73214077Sgibbs
74214077Sgibbsint     resident = (-1);		/* -1, 0: unknown; +1: yes */
75214077Sgibbs
76214077Sgibbs/* Forward declarations. */
77214077Sgibbs
78214077Sgibbsstatic int table_match();
79214077Sgibbsstatic int list_match();
80214077Sgibbsstatic int server_match();
81214077Sgibbsstatic int client_match();
82214077Sgibbsstatic int host_match();
83214077Sgibbsstatic int string_match();
84214077Sgibbsstatic int masked_match();
85214077Sgibbs
86214077Sgibbs/* Size of logical line buffer. */
87214077Sgibbs
88214077Sgibbs#define	BUFLEN 2048
89214077Sgibbs
90214077Sgibbs/* hosts_access - host access control facility */
91214077Sgibbs
92214077Sgibbsint     hosts_access(request)
93214077Sgibbsstruct request_info *request;
94214077Sgibbs{
95214077Sgibbs    int     verdict;
96214077Sgibbs
97214077Sgibbs    /*
98214077Sgibbs     * If the (daemon, client) pair is matched by an entry in the file
99214077Sgibbs     * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
100214077Sgibbs     * client) pair is matched by an entry in the file /etc/hosts.deny,
101214077Sgibbs     * access is denied. Otherwise, access is granted. A non-existent
102214077Sgibbs     * access-control file is treated as an empty file.
103214077Sgibbs     *
104214077Sgibbs     * After a rule has been matched, the optional language extensions may
105214077Sgibbs     * decide to grant or refuse service anyway. Or, while a rule is being
106214077Sgibbs     * processed, a serious error is found, and it seems better to play safe
107214077Sgibbs     * and deny service. All this is done by jumping back into the
108214077Sgibbs     * hosts_access() routine, bypassing the regular return from the
109214077Sgibbs     * table_match() function calls below.
110214077Sgibbs     */
111214077Sgibbs
112214077Sgibbs    if (resident <= 0)
113214077Sgibbs	resident++;
114214077Sgibbs    verdict = setjmp(tcpd_buf);
115214077Sgibbs    if (verdict != 0)
116214077Sgibbs	return (verdict == AC_PERMIT);
117214077Sgibbs    if (table_match(hosts_allow_table, request))
118214077Sgibbs	return (YES);
119214077Sgibbs    if (table_match(hosts_deny_table, request))
120214077Sgibbs	return (NO);
121214077Sgibbs    return (YES);
122214077Sgibbs}
123214077Sgibbs
124214077Sgibbs/* table_match - match table entries with (daemon, client) pair */
125214077Sgibbs
126214077Sgibbsstatic int table_match(table, request)
127214077Sgibbschar   *table;
128214077Sgibbsstruct request_info *request;
129214077Sgibbs{
130214077Sgibbs    FILE   *fp;
131214077Sgibbs    char    sv_list[BUFLEN];		/* becomes list of daemons */
132214077Sgibbs    char   *cl_list;			/* becomes list of clients */
133214077Sgibbs    char   *sh_cmd;			/* becomes optional shell command */
134214077Sgibbs    int     match = NO;
135214077Sgibbs    struct tcpd_context saved_context;
136214077Sgibbs
137214077Sgibbs    saved_context = tcpd_context;		/* stupid compilers */
138214077Sgibbs
139214077Sgibbs    /*
140214077Sgibbs     * Between the fopen() and fclose() calls, avoid jumps that may cause
141214077Sgibbs     * file descriptor leaks.
142214077Sgibbs     */
143214077Sgibbs
144214077Sgibbs    if ((fp = fopen(table, "r")) != 0) {
145214077Sgibbs	tcpd_context.file = table;
146214077Sgibbs	tcpd_context.line = 0;
147214077Sgibbs	while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) {
148214077Sgibbs	    if (sv_list[strlen(sv_list) - 1] != '\n') {
149214077Sgibbs		tcpd_warn("missing newline or line too long");
150214077Sgibbs		continue;
151214077Sgibbs	    }
152214077Sgibbs	    if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0)
153214077Sgibbs		continue;
154214077Sgibbs	    if ((cl_list = split_at(sv_list, ':')) == 0) {
155214077Sgibbs		tcpd_warn("missing \":\" separator");
156214077Sgibbs		continue;
157214077Sgibbs	    }
158214077Sgibbs	    sh_cmd = split_at(cl_list, ':');
159214077Sgibbs	    match = list_match(sv_list, request, server_match)
160214077Sgibbs		&& list_match(cl_list, request, client_match);
161214077Sgibbs	}
162214077Sgibbs	(void) fclose(fp);
163214077Sgibbs    } else if (errno != ENOENT) {
164214077Sgibbs	tcpd_warn("cannot open %s: %m", table);
165214077Sgibbs    }
166214077Sgibbs    if (match) {
167214077Sgibbs	if (hosts_access_verbose > 1)
168214077Sgibbs	    syslog(LOG_DEBUG, "matched:  %s line %d",
169214077Sgibbs		   tcpd_context.file, tcpd_context.line);
170214077Sgibbs	if (sh_cmd) {
171214077Sgibbs#ifdef PROCESS_OPTIONS
172214077Sgibbs	    process_options(sh_cmd, request);
173214077Sgibbs#else
174214077Sgibbs	    char    cmd[BUFSIZ];
175214077Sgibbs	    shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request));
176214077Sgibbs#endif
177214077Sgibbs	}
178214077Sgibbs    }
179214077Sgibbs    tcpd_context = saved_context;
180214077Sgibbs    return (match);
181214077Sgibbs}
182214077Sgibbs
183214077Sgibbs/* list_match - match a request against a list of patterns with exceptions */
184214077Sgibbs
185214077Sgibbsstatic int list_match(list, request, match_fn)
186214077Sgibbschar   *list;
187214077Sgibbsstruct request_info *request;
188214077Sgibbsint   (*match_fn) ();
189214077Sgibbs{
190214077Sgibbs    char   *tok;
191214077Sgibbs
192214077Sgibbs    /*
193214077Sgibbs     * Process tokens one at a time. We have exhausted all possible matches
194214077Sgibbs     * when we reach an "EXCEPT" token or the end of the list. If we do find
195214077Sgibbs     * a match, look for an "EXCEPT" list and recurse to determine whether
196214077Sgibbs     * the match is affected by any exceptions.
197214077Sgibbs     */
198214077Sgibbs
199214077Sgibbs    for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
200214077Sgibbs	if (STR_EQ(tok, "EXCEPT"))		/* EXCEPT: give up */
201214077Sgibbs	    return (NO);
202214077Sgibbs	if (match_fn(tok, request)) {		/* YES: look for exceptions */
203214077Sgibbs	    while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT"))
204214077Sgibbs		 /* VOID */ ;
205214077Sgibbs	    return (tok == 0 || list_match((char *) 0, request, match_fn) == 0);
206214077Sgibbs	}
207214077Sgibbs    }
208214077Sgibbs    return (NO);
209214077Sgibbs}
210214077Sgibbs
211222975Sgibbs/* server_match - match server information */
212214077Sgibbs
213214077Sgibbsstatic int server_match(tok, request)
214214077Sgibbschar   *tok;
215214077Sgibbsstruct request_info *request;
216214077Sgibbs{
217214077Sgibbs    char   *host;
218222975Sgibbs
219222975Sgibbs    if ((host = split_at(tok + 1, '@')) == 0) {	/* plain daemon */
220222975Sgibbs	return (string_match(tok, eval_daemon(request)));
221222975Sgibbs    } else {					/* daemon@host */
222222975Sgibbs	return (string_match(tok, eval_daemon(request))
223214077Sgibbs		&& host_match(host, request->server));
224222975Sgibbs    }
225222975Sgibbs}
226222975Sgibbs
227214077Sgibbs/* client_match - match client information */
228222975Sgibbs
229222975Sgibbsstatic int client_match(tok, request)
230214077Sgibbschar   *tok;
231222975Sgibbsstruct request_info *request;
232222975Sgibbs{
233222975Sgibbs    char   *host;
234222975Sgibbs
235222975Sgibbs    if ((host = split_at(tok + 1, '@')) == 0) {	/* plain host */
236222975Sgibbs	return (host_match(tok, request->client));
237222975Sgibbs    } else {					/* user@host */
238222975Sgibbs	return (host_match(host, request->client)
239222975Sgibbs		&& string_match(tok, eval_user(request)));
240222975Sgibbs    }
241222975Sgibbs}
242222975Sgibbs
243222975Sgibbs/* host_match - match host name and/or address against pattern */
244214077Sgibbs
245222975Sgibbsstatic int host_match(tok, host)
246222975Sgibbschar   *tok;
247222975Sgibbsstruct host_info *host;
248222975Sgibbs{
249222975Sgibbs    char   *mask;
250222975Sgibbs
251222975Sgibbs    /*
252222975Sgibbs     * This code looks a little hairy because we want to avoid unnecessary
253222975Sgibbs     * hostname lookups.
254222975Sgibbs     *
255222975Sgibbs     * The KNOWN pattern requires that both address AND name be known; some
256222975Sgibbs     * patterns are specific to host names or to host addresses; all other
257214077Sgibbs     * patterns are satisfied when either the address OR the name match.
258222975Sgibbs     */
259214077Sgibbs
260222975Sgibbs    if (tok[0] == '@') {			/* netgroup: look it up */
261222975Sgibbs#ifdef  NETGROUP
262222975Sgibbs	static char *mydomain = 0;
263222975Sgibbs	if (mydomain == 0)
264222975Sgibbs	    yp_get_default_domain(&mydomain);
265222975Sgibbs	return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain));
266222975Sgibbs#else
267222975Sgibbs	tcpd_warn("netgroup support is disabled");	/* not tcpd_jump() */
268222975Sgibbs	return (NO);
269222975Sgibbs#endif
270222975Sgibbs    } else if (STR_EQ(tok, "KNOWN")) {		/* check address and name */
271222975Sgibbs	char   *name = eval_hostname(host);
272222975Sgibbs	return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name));
273222975Sgibbs    } else if (STR_EQ(tok, "LOCAL")) {		/* local: no dots in name */
274222975Sgibbs	char   *name = eval_hostname(host);
275222975Sgibbs	return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name));
276222975Sgibbs    } else if ((mask = split_at(tok, '/')) != 0) {	/* net/mask */
277222975Sgibbs	return (masked_match(tok, mask, eval_hostaddr(host)));
278222975Sgibbs    } else {					/* anything else */
279222975Sgibbs	return (string_match(tok, eval_hostaddr(host))
280222975Sgibbs	    || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host))));
281222975Sgibbs    }
282222975Sgibbs}
283222975Sgibbs
284214077Sgibbs/* string_match - match string against pattern */
285214077Sgibbs
286214077Sgibbsstatic int string_match(tok, string)
287214077Sgibbschar   *tok;
288214077Sgibbschar   *string;
289214077Sgibbs{
290214077Sgibbs    int     n;
291214077Sgibbs
292214077Sgibbs    if (tok[0] == '.') {			/* suffix */
293214077Sgibbs	n = strlen(string) - strlen(tok);
294214077Sgibbs	return (n > 0 && STR_EQ(tok, string + n));
295225704Sgibbs    } else if (STR_EQ(tok, "ALL")) {		/* all: match any */
296214077Sgibbs	return (YES);
297214077Sgibbs    } else if (STR_EQ(tok, "KNOWN")) {		/* not unknown */
298214077Sgibbs	return (STR_NE(string, unknown));
299214077Sgibbs    } else if (tok[(n = strlen(tok)) - 1] == '.') {	/* prefix */
300222975Sgibbs	return (STRN_EQ(tok, string, n));
301214077Sgibbs    } else {					/* exact match */
302214077Sgibbs	return (STR_EQ(tok, string));
303214077Sgibbs    }
304214077Sgibbs}
305214077Sgibbs
306214077Sgibbs/* masked_match - match address against netnumber/netmask */
307214077Sgibbs
308214077Sgibbsstatic int masked_match(net_tok, mask_tok, string)
309222975Sgibbschar   *net_tok;
310222975Sgibbschar   *mask_tok;
311214077Sgibbschar   *string;
312214077Sgibbs{
313214077Sgibbs    unsigned long net;
314214077Sgibbs    unsigned long mask;
315214077Sgibbs    unsigned long addr;
316214077Sgibbs
317214077Sgibbs    /*
318214077Sgibbs     * Disallow forms other than dotted quad: the treatment that inet_addr()
319214077Sgibbs     * gives to forms with less than four components is inconsistent with the
320     * access control language. John P. Rouillard <rouilj@cs.umb.edu>.
321     */
322
323    if ((addr = dot_quad_addr(string)) == INADDR_NONE)
324	return (NO);
325    if ((net = dot_quad_addr(net_tok)) == INADDR_NONE
326	|| (mask = dot_quad_addr(mask_tok)) == INADDR_NONE) {
327	tcpd_warn("bad net/mask expression: %s/%s", net_tok, mask_tok);
328	return (NO);				/* not tcpd_jump() */
329    }
330    return ((addr & mask) == net);
331}
332