/src/samba/lib/util/access.c
Line | Count | Source |
1 | | /* |
2 | | This module is an adaption of code from the tcpd-1.4 package written |
3 | | by Wietse Venema, Eindhoven University of Technology, The Netherlands. |
4 | | |
5 | | The code is used here with permission. |
6 | | |
7 | | The code has been considerably changed from the original. Bug reports |
8 | | should be sent to samba-technical@lists.samba.org |
9 | | |
10 | | Updated for IPv6 by Jeremy Allison (C) 2007. |
11 | | */ |
12 | | |
13 | | #include "replace.h" |
14 | | #include "system/locale.h" |
15 | | #include "lib/util/debug.h" |
16 | | #include "../lib/util/memcache.h" |
17 | | #include "lib/socket/interfaces.h" |
18 | | #include "lib/util/samba_util.h" |
19 | | #include "lib/util/util_net.h" |
20 | | #include "lib/util/samba_util.h" |
21 | | #include "lib/util/memory.h" |
22 | | #include "lib/util/access.h" |
23 | | #include "lib/util/unix_match.h" |
24 | | #include "lib/util/smb_strtox.h" |
25 | | |
26 | 0 | #define NAME_INDEX 0 |
27 | 0 | #define ADDR_INDEX 1 |
28 | | |
29 | | /* masked_match - match address against netnumber/netmask */ |
30 | | static bool masked_match(const char *tok, const char *slash, const char *s) |
31 | 0 | { |
32 | 0 | struct sockaddr_storage ss_mask; |
33 | 0 | struct sockaddr_storage ss_tok; |
34 | 0 | struct sockaddr_storage ss_host; |
35 | 0 | char *tok_copy = NULL; |
36 | |
|
37 | 0 | if (!interpret_string_addr(&ss_host, s, 0)) { |
38 | 0 | return false; |
39 | 0 | } |
40 | | |
41 | 0 | if (*tok == '[') { |
42 | | /* IPv6 address - remove braces. */ |
43 | 0 | tok_copy = smb_xstrdup(tok+1); |
44 | 0 | if (!tok_copy) { |
45 | 0 | return false; |
46 | 0 | } |
47 | | /* Remove the terminating ']' */ |
48 | 0 | tok_copy[PTR_DIFF(slash,tok)-1] = '\0'; |
49 | 0 | } else { |
50 | 0 | tok_copy = smb_xstrdup(tok); |
51 | 0 | if (!tok_copy) { |
52 | 0 | return false; |
53 | 0 | } |
54 | | /* Remove the terminating '/' */ |
55 | 0 | tok_copy[PTR_DIFF(slash,tok)] = '\0'; |
56 | 0 | } |
57 | | |
58 | 0 | if (!interpret_string_addr(&ss_tok, tok_copy, 0)) { |
59 | 0 | SAFE_FREE(tok_copy); |
60 | 0 | return false; |
61 | 0 | } |
62 | | |
63 | 0 | SAFE_FREE(tok_copy); |
64 | |
|
65 | 0 | if (strlen(slash + 1) > 2) { |
66 | 0 | if (!interpret_string_addr(&ss_mask, slash+1, 0)) { |
67 | 0 | return false; |
68 | 0 | } |
69 | 0 | } else { |
70 | 0 | int error = 0; |
71 | 0 | unsigned long val; |
72 | |
|
73 | 0 | val = smb_strtoul(slash+1, |
74 | 0 | NULL, |
75 | 0 | 0, |
76 | 0 | &error, |
77 | 0 | SMB_STR_FULL_STR_CONV); |
78 | 0 | if (error != 0) { |
79 | 0 | return false; |
80 | 0 | } |
81 | 0 | if (!make_netmask(&ss_mask, &ss_tok, val)) { |
82 | 0 | return false; |
83 | 0 | } |
84 | 0 | } |
85 | | |
86 | 0 | return same_net((struct sockaddr *)(void *)&ss_host, |
87 | 0 | (struct sockaddr *)(void *)&ss_tok, |
88 | 0 | (struct sockaddr *)(void *)&ss_mask); |
89 | 0 | } |
90 | | |
91 | | /* string_match - match string s against token tok */ |
92 | | static bool string_match(const char *tok,const char *s) |
93 | 0 | { |
94 | 0 | size_t tok_len; |
95 | 0 | size_t str_len; |
96 | 0 | const char *cut; |
97 | | |
98 | | /* Return true if a token has the magic value "ALL". Return |
99 | | * true if the token is "FAIL". If the token starts with a "." |
100 | | * (domain name), return true if it matches the last fields of |
101 | | * the string. If the token has the magic value "LOCAL", |
102 | | * return true if the string does not contain a "." |
103 | | * character. If the token ends on a "." (network number), |
104 | | * return true if it matches the first fields of the |
105 | | * string. If the token begins with a "@" (netgroup name), |
106 | | * return true if the string is a (host) member of the |
107 | | * netgroup. Return true if the token fully matches the |
108 | | * string. If the token is a netnumber/netmask pair, return |
109 | | * true if the address is a member of the specified subnet. |
110 | | */ |
111 | |
|
112 | 0 | if (tok[0] == '.') { /* domain: match last fields */ |
113 | 0 | if ((str_len = strlen(s)) > (tok_len = strlen(tok)) |
114 | 0 | && strequal_m(tok, s + str_len - tok_len)) { |
115 | 0 | return true; |
116 | 0 | } |
117 | 0 | } else if (tok[0] == '@') { /* netgroup: look it up */ |
118 | 0 | #if defined(HAVE_NETGROUP) && defined(HAVE_INNETGR) |
119 | 0 | DATA_BLOB tmp; |
120 | 0 | char *mydomain = NULL; |
121 | 0 | char *hostname = NULL; |
122 | 0 | bool netgroup_ok = false; |
123 | 0 | char nis_domain_buf[256]; |
124 | |
|
125 | 0 | if (memcache_lookup( |
126 | 0 | NULL, SINGLETON_CACHE, |
127 | 0 | data_blob_string_const_null("yp_default_domain"), |
128 | 0 | &tmp)) { |
129 | |
|
130 | 0 | SMB_ASSERT(tmp.length > 0); |
131 | 0 | mydomain = (tmp.data[0] == '\0') |
132 | 0 | ? NULL : (char *)tmp.data; |
133 | 0 | } else { |
134 | 0 | if (getdomainname(nis_domain_buf, |
135 | 0 | sizeof(nis_domain_buf)) == 0) { |
136 | 0 | mydomain = &nis_domain_buf[0]; |
137 | 0 | memcache_add(NULL, |
138 | 0 | SINGLETON_CACHE, |
139 | 0 | data_blob_string_const_null( |
140 | 0 | "yp_default_domain"), |
141 | 0 | data_blob_string_const_null( |
142 | 0 | mydomain)); |
143 | 0 | } else { |
144 | 0 | mydomain = NULL; |
145 | 0 | } |
146 | 0 | } |
147 | | |
148 | 0 | if (!mydomain) { |
149 | 0 | DEBUG(0,("Unable to get default yp domain. " |
150 | 0 | "Try without it.\n")); |
151 | 0 | } |
152 | 0 | if (!(hostname = smb_xstrdup(s))) { |
153 | 0 | DEBUG(1,("out of memory for strdup!\n")); |
154 | 0 | return false; |
155 | 0 | } |
156 | | |
157 | 0 | netgroup_ok = innetgr(tok + 1, hostname, (char *) 0, mydomain); |
158 | |
|
159 | 0 | DBG_INFO("%s %s of domain %s in netgroup %s\n", |
160 | 0 | netgroup_ok ? "Found" : "Could not find", |
161 | 0 | hostname, |
162 | 0 | mydomain?mydomain:"(ANY)", |
163 | 0 | tok+1); |
164 | |
|
165 | 0 | SAFE_FREE(hostname); |
166 | |
|
167 | 0 | if (netgroup_ok) |
168 | 0 | return true; |
169 | | #else |
170 | | DEBUG(0,("access: netgroup support is not configured\n")); |
171 | | return false; |
172 | | #endif |
173 | 0 | } else if (strequal_m(tok, "ALL")) { /* all: match any */ |
174 | 0 | return true; |
175 | 0 | } else if (strequal_m(tok, "FAIL")) { /* fail: match any */ |
176 | 0 | return true; |
177 | 0 | } else if (strequal_m(tok, "LOCAL")) { /* local: no dots */ |
178 | 0 | if (strchr_m(s, '.') == 0 && !strequal_m(s, "unknown")) { |
179 | 0 | return true; |
180 | 0 | } |
181 | 0 | } else if (strequal_m(tok, s)) { /* match host name or address */ |
182 | 0 | return true; |
183 | 0 | } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { /* network */ |
184 | 0 | if (strncmp(tok, s, tok_len) == 0) { |
185 | 0 | return true; |
186 | 0 | } |
187 | 0 | } else if ((cut = strchr_m(tok, '/')) != 0) { /* netnumber/netmask */ |
188 | 0 | if ((isdigit(s[0]) && strchr_m(tok, '.') != NULL) || |
189 | 0 | (tok[0] == '[' && cut > tok && cut[-1] == ']') || |
190 | 0 | ((isxdigit(s[0]) || s[0] == ':') && |
191 | 0 | strchr_m(tok, ':') != NULL)) { |
192 | | /* IPv4/netmask or |
193 | | * [IPv6:addr]/netmask or IPv6:addr/netmask */ |
194 | 0 | return masked_match(tok, cut, s); |
195 | 0 | } |
196 | 0 | } else if (strchr_m(tok, '*') != 0 || strchr_m(tok, '?')) { |
197 | 0 | return unix_wild_match(tok, s); |
198 | 0 | } |
199 | 0 | return false; |
200 | 0 | } |
201 | | |
202 | | /* client_match - match host name and address against token */ |
203 | | bool client_match(const char *tok, const void *item) |
204 | 0 | { |
205 | 0 | const char **client = discard_const_p(const char *, item); |
206 | 0 | const char *tok_addr = tok; |
207 | 0 | const char *cli_addr = client[ADDR_INDEX]; |
208 | | |
209 | | /* |
210 | | * tok and client[ADDR_INDEX] can be an IPv4 mapped to IPv6, |
211 | | * we try and match the IPv4 part of address only. |
212 | | * Bug #5311 and #7383. |
213 | | */ |
214 | |
|
215 | 0 | if (strncasecmp_m(tok_addr, "::ffff:", 7) == 0) { |
216 | 0 | tok_addr += 7; |
217 | 0 | } |
218 | |
|
219 | 0 | if (strncasecmp_m(cli_addr, "::ffff:", 7) == 0) { |
220 | 0 | cli_addr += 7; |
221 | 0 | } |
222 | | |
223 | | /* |
224 | | * Try to match the address first. If that fails, try to match the host |
225 | | * name if available. |
226 | | */ |
227 | |
|
228 | 0 | if (string_match(tok_addr, cli_addr)) { |
229 | 0 | return true; |
230 | 0 | } |
231 | | |
232 | 0 | if (client[NAME_INDEX][0] != 0) { |
233 | 0 | if (string_match(tok, client[NAME_INDEX])) { |
234 | 0 | return true; |
235 | 0 | } |
236 | 0 | } |
237 | | |
238 | 0 | return false; |
239 | 0 | } |
240 | | |
241 | | /* list_match - match an item against a list of tokens with exceptions */ |
242 | | bool list_match(const char **list,const void *item, |
243 | | bool (*match_fn)(const char *, const void *)) |
244 | 0 | { |
245 | 0 | bool match = false; |
246 | |
|
247 | 0 | if (!list) { |
248 | 0 | return false; |
249 | 0 | } |
250 | | |
251 | | /* |
252 | | * Process tokens one at a time. We have exhausted all possible matches |
253 | | * when we reach an "EXCEPT" token or the end of the list. If we do find |
254 | | * a match, look for an "EXCEPT" list and recurse to determine whether |
255 | | * the match is affected by any exceptions. |
256 | | */ |
257 | | |
258 | 0 | for (; *list ; list++) { |
259 | 0 | if (strequal_m(*list, "EXCEPT")) { |
260 | | /* EXCEPT: give up */ |
261 | 0 | break; |
262 | 0 | } |
263 | 0 | if ((match = (*match_fn) (*list, item))) { |
264 | | /* true or FAIL */ |
265 | 0 | break; |
266 | 0 | } |
267 | 0 | } |
268 | | /* Process exceptions to true or FAIL matches. */ |
269 | |
|
270 | 0 | if (match != false) { |
271 | 0 | while (*list && !strequal_m(*list, "EXCEPT")) { |
272 | 0 | list++; |
273 | 0 | } |
274 | |
|
275 | 0 | for (; *list; list++) { |
276 | 0 | if ((*match_fn) (*list, item)) { |
277 | | /* Exception Found */ |
278 | 0 | return false; |
279 | 0 | } |
280 | 0 | } |
281 | 0 | } |
282 | | |
283 | 0 | return match; |
284 | 0 | } |
285 | | |
286 | | /* return true if access should be allowed */ |
287 | | static bool allow_access_internal(const char **deny_list, |
288 | | const char **allow_list, |
289 | | const char *cname, |
290 | | const char *caddr) |
291 | 0 | { |
292 | 0 | const char *client[2]; |
293 | |
|
294 | 0 | client[NAME_INDEX] = cname; |
295 | 0 | client[ADDR_INDEX] = caddr; |
296 | | |
297 | | /* if it is loopback then always allow unless specifically denied */ |
298 | 0 | if (strcmp(caddr, "127.0.0.1") == 0 || strcmp(caddr, "::1") == 0) { |
299 | | /* |
300 | | * If 127.0.0.1 matches both allow and deny then allow. |
301 | | * Patch from Steve Langasek vorlon@netexpress.net. |
302 | | */ |
303 | 0 | if (deny_list && |
304 | 0 | list_match(deny_list,client,client_match) && |
305 | 0 | (!allow_list || |
306 | 0 | !list_match(allow_list,client, client_match))) { |
307 | 0 | return false; |
308 | 0 | } |
309 | 0 | return true; |
310 | 0 | } |
311 | | |
312 | | /* if there's no deny list and no allow list then allow access */ |
313 | 0 | if ((!deny_list || *deny_list == 0) && |
314 | 0 | (!allow_list || *allow_list == 0)) { |
315 | 0 | return true; |
316 | 0 | } |
317 | | |
318 | | /* if there is an allow list but no deny list then allow only hosts |
319 | | on the allow list */ |
320 | 0 | if (!deny_list || *deny_list == 0) { |
321 | 0 | return(list_match(allow_list,client,client_match)); |
322 | 0 | } |
323 | | |
324 | | /* if there's a deny list but no allow list then allow |
325 | | all hosts not on the deny list */ |
326 | 0 | if (!allow_list || *allow_list == 0) { |
327 | 0 | return(!list_match(deny_list,client,client_match)); |
328 | 0 | } |
329 | | |
330 | | /* if there are both types of list then allow all hosts on the |
331 | | allow list */ |
332 | 0 | if (list_match(allow_list,(const char *)client,client_match)) { |
333 | 0 | return true; |
334 | 0 | } |
335 | | |
336 | | /* if there are both types of list and it's not on the allow then |
337 | | allow it if its not on the deny */ |
338 | 0 | if (list_match(deny_list,(const char *)client,client_match)) { |
339 | 0 | return false; |
340 | 0 | } |
341 | | |
342 | 0 | return true; |
343 | 0 | } |
344 | | |
345 | | /* return true if access should be allowed - doesn't print log message */ |
346 | | bool allow_access_nolog(const char **deny_list, |
347 | | const char **allow_list, |
348 | | const char *cname, |
349 | | const char *caddr) |
350 | 0 | { |
351 | 0 | bool ret; |
352 | 0 | char *nc_cname = smb_xstrdup(cname); |
353 | 0 | char *nc_caddr = smb_xstrdup(caddr); |
354 | |
|
355 | 0 | ret = allow_access_internal(deny_list, allow_list, nc_cname, nc_caddr); |
356 | |
|
357 | 0 | SAFE_FREE(nc_cname); |
358 | 0 | SAFE_FREE(nc_caddr); |
359 | 0 | return ret; |
360 | 0 | } |
361 | | |
362 | | /* return true if access should be allowed - prints log message */ |
363 | | bool allow_access(const char **deny_list, |
364 | | const char **allow_list, |
365 | | const char *cname, |
366 | | const char *caddr) |
367 | 0 | { |
368 | 0 | bool ret; |
369 | |
|
370 | 0 | ret = allow_access_nolog(deny_list, allow_list, cname, caddr); |
371 | |
|
372 | 0 | DEBUG(ret ? 3 : 0, |
373 | 0 | ("%s connection from %s (%s)\n", |
374 | 0 | ret ? "Allowed" : "Denied", cname, caddr)); |
375 | |
|
376 | 0 | return ret; |
377 | 0 | } |