Line data Source code
1 : /*
2 : SSSD
3 :
4 : Authors:
5 : Stephen Gallagher <sgallagh@redhat.com>
6 :
7 : Copyright (C) 2012 Red Hat
8 :
9 : This program is free software; you can redistribute it and/or modify
10 : it under the terms of the GNU General Public License as published by
11 : the Free Software Foundation; either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : This program is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU General Public License for more details.
18 :
19 : You should have received a copy of the GNU General Public License
20 : along with this program. If not, see <http://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include <security/pam_modules.h>
24 : #include <syslog.h>
25 :
26 : #include "src/util/util.h"
27 : #include "src/providers/data_provider.h"
28 : #include "src/providers/dp_backend.h"
29 : #include "src/providers/ad/ad_access.h"
30 : #include "providers/ad/ad_gpo.h"
31 : #include "src/providers/ad/ad_common.h"
32 : #include "src/providers/ldap/sdap_access.h"
33 :
34 : /*
35 : * More advanced format can be used to restrict the filter to a specific
36 : * domain or a specific forest. This format is KEYWORD:NAME:FILTER
37 : *
38 : * KEYWORD can be one of DOM or FOREST
39 : * KEYWORD can be missing
40 : * NAME is a label.
41 : * - if KEYWORD equals DOM or missing completely, the filter is applied
42 : * for users from domain named NAME only
43 : * - if KEYWORD equals FOREST, the filter is applied on users from
44 : * forest named NAME only
45 : * examples of valid filters are:
46 : * apply filter on domain called dom1 only:
47 : * dom1:(memberOf=cn=admins,ou=groups,dc=dom1,dc=com)
48 : * apply filter on domain called dom2 only:
49 : * DOM:dom2:(memberOf=cn=admins,ou=groups,dc=dom2,dc=com)
50 : * apply filter on forest called EXAMPLE.COM only:
51 : * FOREST:EXAMPLE.COM:(memberOf=cn=admins,ou=groups,dc=example,dc=com)
52 : *
53 : * If any of the extended formats are used, the filter MUST be enclosed
54 : * already.
55 : */
56 :
57 : /* From least specific */
58 : #define AD_FILTER_GENERIC 0x01
59 : #define AD_FILTER_FOREST 0x02
60 : #define AD_FILTER_DOMAIN 0x04
61 :
62 : #define KW_FOREST "FOREST"
63 : #define KW_DOMAIN "DOM"
64 :
65 : /* parse filter in the format domain_name:filter */
66 : static errno_t
67 16 : parse_sub_filter(TALLOC_CTX *mem_ctx, const char *full_filter,
68 : char **filter, char **sub_name, int *flags,
69 : const int flagconst)
70 : {
71 : char *specdelim;
72 :
73 16 : specdelim = strchr(full_filter, ':');
74 16 : if (specdelim == NULL) return EINVAL;
75 :
76 : /* Make sure the filter is already enclosed in brackets */
77 16 : if (*(specdelim+1) != '(') return EINVAL;
78 :
79 13 : *sub_name = talloc_strndup(mem_ctx, full_filter, specdelim - full_filter);
80 13 : *filter = talloc_strdup(mem_ctx, specdelim+1);
81 13 : if (*sub_name == NULL || *filter == NULL) return ENOMEM;
82 :
83 13 : *flags = flagconst;
84 13 : return EOK;
85 : }
86 :
87 : static inline errno_t
88 15 : parse_dom_filter(TALLOC_CTX *mem_ctx, const char *dom_filter,
89 : char **filter, char **domname, int *flags)
90 : {
91 15 : return parse_sub_filter(mem_ctx, dom_filter, filter, domname,
92 : flags, AD_FILTER_DOMAIN);
93 : }
94 :
95 : static inline errno_t
96 1 : parse_forest_filter(TALLOC_CTX *mem_ctx, const char *forest_filter,
97 : char **filter, char **forest_name, int *flags)
98 : {
99 1 : return parse_sub_filter(mem_ctx, forest_filter, filter, forest_name,
100 : flags, AD_FILTER_FOREST);
101 : }
102 :
103 :
104 : static errno_t
105 28 : parse_filter(TALLOC_CTX *mem_ctx, const char *full_filter,
106 : char **filter, char **spec, int *flags)
107 : {
108 : char *kwdelim, *specdelim;
109 :
110 28 : if (filter == NULL || spec == NULL || flags == NULL) return EINVAL;
111 :
112 28 : kwdelim = strchr(full_filter, ':');
113 28 : if (kwdelim != NULL) {
114 19 : specdelim = strchr(kwdelim+1, ':');
115 :
116 19 : if (specdelim == NULL) {
117 : /* There is a single keyword. Treat it as a domain name */
118 11 : return parse_dom_filter(mem_ctx, full_filter, filter, spec, flags);
119 8 : } else if (strncmp(full_filter, "DOM", kwdelim-full_filter) == 0) {
120 : /* The format must be DOM:domain_name:filter */
121 6 : if (specdelim && specdelim-kwdelim <= 1) {
122 : /* Check if there is some domain_name */
123 2 : return EINVAL;
124 : }
125 :
126 4 : return parse_dom_filter(mem_ctx, kwdelim + 1, filter, spec, flags);
127 2 : } else if (strncmp(full_filter, "FOREST", kwdelim-full_filter) == 0) {
128 : /* The format must be FOREST:forest_name:filter */
129 1 : if (specdelim && specdelim-kwdelim <= 1) {
130 : /* Check if there is some domain_name */
131 0 : return EINVAL;
132 : }
133 :
134 1 : return parse_forest_filter(mem_ctx, kwdelim + 1,
135 : filter, spec, flags);
136 : }
137 :
138 : /* Malformed option */
139 1 : DEBUG(SSSDBG_CRIT_FAILURE,
140 : "Keyword in filter [%s] did not match expected format\n",
141 : full_filter);
142 1 : return EINVAL;
143 : }
144 :
145 : /* No keyword. Easy. */
146 9 : *filter = talloc_strdup(mem_ctx, full_filter);
147 9 : if (*filter == NULL) return ENOMEM;
148 :
149 9 : *spec = NULL;
150 9 : *flags = AD_FILTER_GENERIC;
151 9 : return EOK;
152 : }
153 :
154 : static errno_t
155 11 : ad_parse_access_filter(TALLOC_CTX *mem_ctx,
156 : struct sss_domain_info *dom,
157 : const char *filter_list,
158 : char **_filter)
159 : {
160 : char **filters;
161 : int nfilters;
162 : errno_t ret;
163 : char *best_match;
164 : int best_flags;
165 : char *filter;
166 : char *spec;
167 : int flags;
168 : TALLOC_CTX *tmp_ctx;
169 11 : int i = 0;
170 :
171 11 : if (_filter == NULL) return EINVAL;
172 :
173 11 : tmp_ctx = talloc_new(mem_ctx);
174 11 : if (tmp_ctx == NULL) {
175 0 : ret = ENOMEM;
176 0 : goto done;
177 : }
178 :
179 11 : if (filter_list == NULL) {
180 1 : *_filter = NULL;
181 1 : ret = EOK;
182 1 : goto done;
183 : }
184 :
185 10 : ret = split_on_separator(tmp_ctx, filter_list, '?', true, true,
186 : &filters, &nfilters);
187 10 : if (ret != EOK) {
188 0 : DEBUG(SSSDBG_CRIT_FAILURE,
189 : "Cannot parse the list of ad_access_filters\n");
190 0 : goto done;
191 : }
192 :
193 10 : best_match = NULL;
194 10 : best_flags = 0;
195 27 : for (i=0; i < nfilters; i++) {
196 17 : ret = parse_filter(tmp_ctx, filters[i], &filter, &spec, &flags);
197 17 : if (ret != EOK) {
198 : /* Skip the faulty filter. At worst, the user won't be
199 : * allowed access */
200 0 : DEBUG(SSSDBG_MINOR_FAILURE, "Access filter [%s] could not be "
201 : "parsed, skipping\n", filters[i]);
202 0 : continue;
203 : }
204 :
205 17 : if (flags & AD_FILTER_DOMAIN && strcasecmp(spec, dom->name) != 0) {
206 : /* If the filter specifies a domain, it must match the
207 : * domain the user comes from
208 : */
209 4 : continue;
210 : }
211 :
212 13 : if (flags & AD_FILTER_FOREST && strcasecmp(spec, dom->forest) != 0) {
213 : /* If the filter specifies a forest, it must match the
214 : * forest the user comes from
215 : */
216 0 : continue;
217 : }
218 :
219 13 : if (flags > best_flags) {
220 10 : best_flags = flags;
221 10 : best_match = filter;
222 : }
223 : }
224 :
225 10 : ret = EOK;
226 : /* Make sure the result is enclosed in brackets */
227 10 : *_filter = sdap_get_access_filter(mem_ctx, best_match);
228 : done:
229 11 : talloc_free(tmp_ctx);
230 11 : return ret;
231 : }
232 :
233 : struct ad_access_state {
234 : struct tevent_context *ev;
235 : struct ad_access_ctx *ctx;
236 : struct pam_data *pd;
237 : struct be_ctx *be_ctx;
238 : struct sss_domain_info *domain;
239 :
240 : char *filter;
241 : struct sdap_id_conn_ctx **clist;
242 : int cindex;
243 : };
244 :
245 : static errno_t
246 : ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn);
247 : static void
248 : ad_sdap_access_done(struct tevent_req *req);
249 :
250 : static struct tevent_req *
251 0 : ad_access_send(TALLOC_CTX *mem_ctx,
252 : struct tevent_context *ev,
253 : struct be_ctx *be_ctx,
254 : struct sss_domain_info *domain,
255 : struct ad_access_ctx *ctx,
256 : struct pam_data *pd)
257 : {
258 : struct tevent_req *req;
259 : struct ad_access_state *state;
260 : errno_t ret;
261 :
262 0 : req = tevent_req_create(mem_ctx, &state, struct ad_access_state);
263 0 : if (req == NULL) {
264 0 : return NULL;
265 : }
266 :
267 0 : state->ev = ev;
268 0 : state->ctx = ctx;
269 0 : state->pd = pd;
270 0 : state->be_ctx = be_ctx;
271 0 : state->domain = domain;
272 :
273 0 : ret = ad_parse_access_filter(state, domain, ctx->sdap_access_ctx->filter,
274 0 : &state->filter);
275 0 : if (ret != EOK) {
276 0 : DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine the best filter\n");
277 0 : ret = ERR_ACCESS_DENIED;
278 0 : goto done;
279 : }
280 :
281 0 : state->clist = ad_gc_conn_list(state, ctx->ad_id_ctx, domain);
282 0 : if (state->clist == NULL) {
283 0 : ret = ENOMEM;
284 0 : goto done;
285 : }
286 :
287 0 : ret = ad_sdap_access_step(req, state->clist[state->cindex]);
288 0 : if (ret != EOK) {
289 0 : goto done;
290 : }
291 :
292 0 : ret = EOK;
293 : done:
294 0 : if (ret != EOK) {
295 0 : tevent_req_error(req, ret);
296 :
297 0 : tevent_req_post(req, ev);
298 : }
299 0 : return req;
300 : }
301 :
302 : static errno_t
303 0 : ad_sdap_access_step(struct tevent_req *req, struct sdap_id_conn_ctx *conn)
304 : {
305 : struct tevent_req *subreq;
306 : struct ad_access_state *state;
307 : struct sdap_access_ctx *req_ctx;
308 :
309 0 : state = tevent_req_data(req, struct ad_access_state);
310 :
311 0 : req_ctx = talloc(state, struct sdap_access_ctx);
312 0 : if (req_ctx == NULL) {
313 0 : return ENOMEM;
314 : }
315 0 : req_ctx->id_ctx = state->ctx->sdap_access_ctx->id_ctx;
316 0 : req_ctx->filter = state->filter;
317 0 : memcpy(&req_ctx->access_rule,
318 0 : state->ctx->sdap_access_ctx->access_rule,
319 : sizeof(int) * LDAP_ACCESS_LAST);
320 :
321 0 : subreq = sdap_access_send(state, state->ev, state->be_ctx,
322 : state->domain, req_ctx,
323 : conn, state->pd);
324 0 : if (req == NULL) {
325 0 : talloc_free(req_ctx);
326 0 : return ENOMEM;
327 : }
328 0 : tevent_req_set_callback(subreq, ad_sdap_access_done, req);
329 0 : return EOK;
330 : }
331 :
332 : static void
333 : ad_gpo_access_done(struct tevent_req *subreq);
334 :
335 : static void
336 0 : ad_sdap_access_done(struct tevent_req *subreq)
337 : {
338 : struct tevent_req *req;
339 : struct ad_access_state *state;
340 : errno_t ret;
341 :
342 0 : req = tevent_req_callback_data(subreq, struct tevent_req);
343 0 : state = tevent_req_data(req, struct ad_access_state);
344 :
345 0 : ret = sdap_access_recv(subreq);
346 0 : talloc_zfree(subreq);
347 :
348 0 : if (ret != EOK) {
349 0 : switch (ret) {
350 : case ERR_ACCOUNT_EXPIRED:
351 0 : tevent_req_error(req, ret);
352 0 : return;
353 :
354 : case ERR_ACCESS_DENIED:
355 : /* Retry on ACCESS_DENIED, too, to make sure that we don't
356 : * miss out any attributes not present in GC
357 : * FIXME - this is slow. We should retry only if GC failed
358 : * and LDAP succeeded after the first ACCESS_DENIED
359 : */
360 0 : break;
361 :
362 : default:
363 0 : break;
364 : }
365 :
366 : /* If possible, retry with LDAP */
367 0 : state->cindex++;
368 0 : if (state->clist[state->cindex] == NULL) {
369 0 : DEBUG(SSSDBG_OP_FAILURE,
370 : "Error retrieving access check result: %s\n",
371 : sss_strerror(ret));
372 0 : tevent_req_error(req, ret);
373 0 : return;
374 : }
375 :
376 0 : ret = ad_sdap_access_step(req, state->clist[state->cindex]);
377 0 : if (ret != EOK) {
378 0 : tevent_req_error(req, ret);
379 0 : return;
380 : }
381 :
382 : /* Another check in progress */
383 :
384 0 : return;
385 : }
386 :
387 0 : switch (state->ctx->gpo_access_control_mode) {
388 : case GPO_ACCESS_CONTROL_DISABLED:
389 : /* do not evaluate gpos; mark request done */
390 0 : tevent_req_done(req);
391 0 : return;
392 : case GPO_ACCESS_CONTROL_PERMISSIVE:
393 : case GPO_ACCESS_CONTROL_ENFORCING:
394 : /* continue on to evaluate gpos */
395 0 : break;
396 : default:
397 0 : tevent_req_error(req, EINVAL);
398 0 : return;
399 : }
400 :
401 0 : subreq = ad_gpo_access_send(state,
402 0 : state->be_ctx->ev,
403 : state->domain,
404 : state->ctx,
405 0 : state->pd->user,
406 0 : state->pd->service);
407 :
408 0 : if (!subreq) {
409 0 : tevent_req_error(req, ENOMEM);
410 0 : return;
411 : }
412 :
413 0 : tevent_req_set_callback(subreq, ad_gpo_access_done, req);
414 :
415 : }
416 :
417 : static void
418 0 : ad_gpo_access_done(struct tevent_req *subreq)
419 : {
420 : struct tevent_req *req;
421 : struct ad_access_state *state;
422 : errno_t ret;
423 : enum gpo_access_control_mode mode;
424 :
425 0 : req = tevent_req_callback_data(subreq, struct tevent_req);
426 0 : state = tevent_req_data(req, struct ad_access_state);
427 0 : mode = state->ctx->gpo_access_control_mode;
428 :
429 0 : ret = ad_gpo_access_recv(subreq);
430 0 : talloc_zfree(subreq);
431 :
432 0 : if (ret == EOK) {
433 0 : DEBUG(SSSDBG_TRACE_FUNC, "GPO-based access control successful.\n");
434 0 : tevent_req_done(req);
435 : } else {
436 0 : DEBUG(SSSDBG_OP_FAILURE, "GPO-based access control failed.\n");
437 0 : if (mode == GPO_ACCESS_CONTROL_ENFORCING) {
438 0 : tevent_req_error(req, ret);
439 : } else {
440 0 : DEBUG(SSSDBG_OP_FAILURE,
441 : "Ignoring error: [%d](%s); GPO-based access control failed, "
442 : "but GPO is not in enforcing mode.\n",
443 : ret, sss_strerror(ret));
444 0 : sss_log_ext(SSS_LOG_WARNING, LOG_AUTHPRIV, "Warning: user would "
445 : "have been denied GPO-based logon access if the "
446 : "ad_gpo_access_control option were set to enforcing mode.");
447 0 : tevent_req_done(req);
448 : }
449 : }
450 0 : }
451 :
452 : static errno_t
453 0 : ad_access_recv(struct tevent_req *req)
454 : {
455 0 : TEVENT_REQ_RETURN_ON_ERROR(req);
456 :
457 0 : return EOK;
458 : }
459 :
460 : static void
461 : ad_access_done(struct tevent_req *req);
462 :
463 : void
464 0 : ad_access_handler(struct be_req *breq)
465 : {
466 : struct tevent_req *req;
467 0 : struct be_ctx *be_ctx = be_req_get_be_ctx(breq);
468 0 : struct ad_access_ctx *access_ctx =
469 0 : talloc_get_type(be_ctx->bet_info[BET_ACCESS].pvt_bet_data,
470 : struct ad_access_ctx);
471 0 : struct pam_data *pd =
472 0 : talloc_get_type(be_req_get_data(breq), struct pam_data);
473 : struct sss_domain_info *domain;
474 :
475 : /* Handle subdomains */
476 0 : if (strcasecmp(pd->domain, be_ctx->domain->name) != 0) {
477 0 : domain = find_domain_by_name(be_ctx->domain, pd->domain, true);
478 0 : if (domain == NULL) {
479 0 : DEBUG(SSSDBG_OP_FAILURE, "find_domain_by_name failed.\n");
480 0 : be_req_terminate(breq, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL);
481 0 : return;
482 : }
483 : } else {
484 0 : domain = be_ctx->domain;
485 : }
486 :
487 : /* Verify access control: locked accounts, ldap policies, GPOs, etc */
488 0 : req = ad_access_send(breq, be_ctx->ev, be_ctx, domain,
489 : access_ctx, pd);
490 0 : if (!req) {
491 0 : be_req_terminate(breq, DP_ERR_FATAL, PAM_SYSTEM_ERR, NULL);
492 0 : return;
493 : }
494 0 : tevent_req_set_callback(req, ad_access_done, breq);
495 : }
496 :
497 : static void
498 0 : ad_access_done(struct tevent_req *req)
499 : {
500 : errno_t ret;
501 0 : struct be_req *breq =
502 0 : tevent_req_callback_data(req, struct be_req);
503 0 : struct pam_data *pd =
504 0 : talloc_get_type(be_req_get_data(breq), struct pam_data);
505 :
506 0 : ret = ad_access_recv(req);
507 0 : talloc_zfree(req);
508 0 : switch (ret) {
509 : case EOK:
510 0 : pd->pam_status = PAM_SUCCESS;
511 0 : be_req_terminate(breq, DP_ERR_OK, PAM_SUCCESS, NULL);
512 0 : return;
513 : case ERR_ACCESS_DENIED:
514 : /* We got the proper denial */
515 0 : pd->pam_status = PAM_PERM_DENIED;
516 0 : be_req_terminate(breq, DP_ERR_OK, PAM_PERM_DENIED, NULL);
517 0 : return;
518 : case ERR_ACCOUNT_EXPIRED:
519 0 : pd->pam_status = PAM_ACCT_EXPIRED;
520 0 : be_req_terminate(breq, DP_ERR_OK, PAM_ACCT_EXPIRED, NULL);
521 0 : return;
522 : default:
523 : /* Something went wrong */
524 0 : pd->pam_status = PAM_SYSTEM_ERR;
525 0 : be_req_terminate(breq, DP_ERR_FATAL,
526 : PAM_SYSTEM_ERR, sss_strerror(ret));
527 0 : return;
528 : }
529 : }
|