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.21 97/02/12 02:13:22"; |
---|
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 | verdict = setjmp(tcpd_buf); |
---|
115 | if (verdict != 0) |
---|
116 | return (verdict == AC_PERMIT); |
---|
117 | if (table_match(hosts_allow_table, request)) |
---|
118 | return (YES); |
---|
119 | if (table_match(hosts_deny_table, request)) |
---|
120 | return (NO); |
---|
121 | return (YES); |
---|
122 | } |
---|
123 | |
---|
124 | /* table_match - match table entries with (daemon, client) pair */ |
---|
125 | |
---|
126 | static int table_match(table, request) |
---|
127 | char *table; |
---|
128 | struct request_info *request; |
---|
129 | { |
---|
130 | FILE *fp; |
---|
131 | char sv_list[BUFLEN]; /* becomes list of daemons */ |
---|
132 | char *cl_list; /* becomes list of clients */ |
---|
133 | char *sh_cmd; /* becomes optional shell command */ |
---|
134 | int match = NO; |
---|
135 | struct tcpd_context saved_context; |
---|
136 | |
---|
137 | saved_context = tcpd_context; /* stupid compilers */ |
---|
138 | |
---|
139 | /* |
---|
140 | * Between the fopen() and fclose() calls, avoid jumps that may cause |
---|
141 | * file descriptor leaks. |
---|
142 | */ |
---|
143 | |
---|
144 | if ((fp = fopen(table, "r")) != 0) { |
---|
145 | tcpd_context.file = table; |
---|
146 | tcpd_context.line = 0; |
---|
147 | while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) { |
---|
148 | if (sv_list[strlen(sv_list) - 1] != '\n') { |
---|
149 | tcpd_warn("missing newline or line too long"); |
---|
150 | continue; |
---|
151 | } |
---|
152 | if (sv_list[0] == '#' || sv_list[strspn(sv_list, " \t\r\n")] == 0) |
---|
153 | continue; |
---|
154 | if ((cl_list = split_at(sv_list, ':')) == 0) { |
---|
155 | tcpd_warn("missing \":\" separator"); |
---|
156 | continue; |
---|
157 | } |
---|
158 | sh_cmd = split_at(cl_list, ':'); |
---|
159 | match = list_match(sv_list, request, server_match) |
---|
160 | && list_match(cl_list, request, client_match); |
---|
161 | } |
---|
162 | (void) fclose(fp); |
---|
163 | } else if (errno != ENOENT) { |
---|
164 | tcpd_warn("cannot open %s: %m", table); |
---|
165 | } |
---|
166 | if (match) { |
---|
167 | if (hosts_access_verbose > 1) |
---|
168 | syslog(LOG_DEBUG, "matched: %s line %d", |
---|
169 | tcpd_context.file, tcpd_context.line); |
---|
170 | if (sh_cmd) { |
---|
171 | #ifdef PROCESS_OPTIONS |
---|
172 | process_options(sh_cmd, request); |
---|
173 | #else |
---|
174 | char cmd[BUFSIZ]; |
---|
175 | shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request)); |
---|
176 | #endif |
---|
177 | } |
---|
178 | } |
---|
179 | tcpd_context = saved_context; |
---|
180 | return (match); |
---|
181 | } |
---|
182 | |
---|
183 | /* list_match - match a request against a list of patterns with exceptions */ |
---|
184 | |
---|
185 | static int list_match(list, request, match_fn) |
---|
186 | char *list; |
---|
187 | struct request_info *request; |
---|
188 | int (*match_fn) (); |
---|
189 | { |
---|
190 | char *tok; |
---|
191 | |
---|
192 | /* |
---|
193 | * Process tokens one at a time. We have exhausted all possible matches |
---|
194 | * when we reach an "EXCEPT" token or the end of the list. If we do find |
---|
195 | * a match, look for an "EXCEPT" list and recurse to determine whether |
---|
196 | * the match is affected by any exceptions. |
---|
197 | */ |
---|
198 | |
---|
199 | for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) { |
---|
200 | if (STR_EQ(tok, "EXCEPT")) /* EXCEPT: give up */ |
---|
201 | return (NO); |
---|
202 | if (match_fn(tok, request)) { /* YES: look for exceptions */ |
---|
203 | while ((tok = strtok((char *) 0, sep)) && STR_NE(tok, "EXCEPT")) |
---|
204 | /* VOID */ ; |
---|
205 | return (tok == 0 || list_match((char *) 0, request, match_fn) == 0); |
---|
206 | } |
---|
207 | } |
---|
208 | return (NO); |
---|
209 | } |
---|
210 | |
---|
211 | /* server_match - match server information */ |
---|
212 | |
---|
213 | static int server_match(tok, request) |
---|
214 | char *tok; |
---|
215 | struct request_info *request; |
---|
216 | { |
---|
217 | char *host; |
---|
218 | |
---|
219 | if ((host = split_at(tok + 1, '@')) == 0) { /* plain daemon */ |
---|
220 | return (string_match(tok, eval_daemon(request))); |
---|
221 | } else { /* daemon@host */ |
---|
222 | return (string_match(tok, eval_daemon(request)) |
---|
223 | && host_match(host, request->server)); |
---|
224 | } |
---|
225 | } |
---|
226 | |
---|
227 | /* client_match - match client information */ |
---|
228 | |
---|
229 | static int client_match(tok, request) |
---|
230 | char *tok; |
---|
231 | struct request_info *request; |
---|
232 | { |
---|
233 | char *host; |
---|
234 | |
---|
235 | if ((host = split_at(tok + 1, '@')) == 0) { /* plain host */ |
---|
236 | return (host_match(tok, request->client)); |
---|
237 | } else { /* user@host */ |
---|
238 | return (host_match(host, request->client) |
---|
239 | && string_match(tok, eval_user(request))); |
---|
240 | } |
---|
241 | } |
---|
242 | |
---|
243 | /* host_match - match host name and/or address against pattern */ |
---|
244 | |
---|
245 | static int host_match(tok, host) |
---|
246 | char *tok; |
---|
247 | struct host_info *host; |
---|
248 | { |
---|
249 | char *mask; |
---|
250 | |
---|
251 | /* |
---|
252 | * This code looks a little hairy because we want to avoid unnecessary |
---|
253 | * hostname lookups. |
---|
254 | * |
---|
255 | * The KNOWN pattern requires that both address AND name be known; some |
---|
256 | * patterns are specific to host names or to host addresses; all other |
---|
257 | * patterns are satisfied when either the address OR the name match. |
---|
258 | */ |
---|
259 | |
---|
260 | if (tok[0] == '@') { /* netgroup: look it up */ |
---|
261 | #ifdef NETGROUP |
---|
262 | static char *mydomain = 0; |
---|
263 | if (mydomain == 0) |
---|
264 | yp_get_default_domain(&mydomain); |
---|
265 | return (innetgr(tok + 1, eval_hostname(host), (char *) 0, mydomain)); |
---|
266 | #else |
---|
267 | tcpd_warn("netgroup support is disabled"); /* not tcpd_jump() */ |
---|
268 | return (NO); |
---|
269 | #endif |
---|
270 | } else if (STR_EQ(tok, "KNOWN")) { /* check address and name */ |
---|
271 | char *name = eval_hostname(host); |
---|
272 | return (STR_NE(eval_hostaddr(host), unknown) && HOSTNAME_KNOWN(name)); |
---|
273 | } else if (STR_EQ(tok, "LOCAL")) { /* local: no dots in name */ |
---|
274 | char *name = eval_hostname(host); |
---|
275 | return (strchr(name, '.') == 0 && HOSTNAME_KNOWN(name)); |
---|
276 | } else if ((mask = split_at(tok, '/')) != 0) { /* net/mask */ |
---|
277 | return (masked_match(tok, mask, eval_hostaddr(host))); |
---|
278 | } else { /* anything else */ |
---|
279 | return (string_match(tok, eval_hostaddr(host)) |
---|
280 | || (NOT_INADDR(tok) && string_match(tok, eval_hostname(host)))); |
---|
281 | } |
---|
282 | } |
---|
283 | |
---|
284 | /* string_match - match string against pattern */ |
---|
285 | |
---|
286 | static int string_match(tok, string) |
---|
287 | char *tok; |
---|
288 | char *string; |
---|
289 | { |
---|
290 | int n; |
---|
291 | |
---|
292 | if (tok[0] == '.') { /* suffix */ |
---|
293 | n = strlen(string) - strlen(tok); |
---|
294 | return (n > 0 && STR_EQ(tok, string + n)); |
---|
295 | } else if (STR_EQ(tok, "ALL")) { /* all: match any */ |
---|
296 | return (YES); |
---|
297 | } else if (STR_EQ(tok, "KNOWN")) { /* not unknown */ |
---|
298 | return (STR_NE(string, unknown)); |
---|
299 | } else if (tok[(n = strlen(tok)) - 1] == '.') { /* prefix */ |
---|
300 | return (STRN_EQ(tok, string, n)); |
---|
301 | } else { /* exact match */ |
---|
302 | return (STR_EQ(tok, string)); |
---|
303 | } |
---|
304 | } |
---|
305 | |
---|
306 | /* masked_match - match address against netnumber/netmask */ |
---|
307 | |
---|
308 | static int masked_match(net_tok, mask_tok, string) |
---|
309 | char *net_tok; |
---|
310 | char *mask_tok; |
---|
311 | char *string; |
---|
312 | { |
---|
313 | unsigned long net; |
---|
314 | unsigned long mask; |
---|
315 | unsigned long addr; |
---|
316 | |
---|
317 | /* |
---|
318 | * Disallow forms other than dotted quad: the treatment that inet_addr() |
---|
319 | * 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 | } |
---|