"Fossies" - the Fresh Open Source Software Archive

Member "sssd-2.2.3/src/providers/ad/ad_access.c" (30 Nov 2019, 15947 Bytes) of package /linux/misc/sssd-2.2.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ad_access.c" see the Fossies "Dox" file reference documentation.

    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/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 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     specdelim = strchr(full_filter, ':');
   74     if (specdelim == NULL) return EINVAL;
   75 
   76     /* Make sure the filter is already enclosed in brackets */
   77     if (*(specdelim+1) != '(') return EINVAL;
   78 
   79     *sub_name = talloc_strndup(mem_ctx, full_filter, specdelim - full_filter);
   80     *filter = talloc_strdup(mem_ctx, specdelim+1);
   81     if (*sub_name == NULL || *filter == NULL) return ENOMEM;
   82 
   83     *flags = flagconst;
   84     return EOK;
   85 }
   86 
   87 static inline errno_t
   88 parse_dom_filter(TALLOC_CTX *mem_ctx, const char *dom_filter,
   89                  char **filter, char **domname, int *flags)
   90 {
   91     return parse_sub_filter(mem_ctx, dom_filter, filter, domname,
   92                             flags, AD_FILTER_DOMAIN);
   93 }
   94 
   95 static inline errno_t
   96 parse_forest_filter(TALLOC_CTX *mem_ctx, const char *forest_filter,
   97                     char **filter, char **forest_name, int *flags)
   98 {
   99     return parse_sub_filter(mem_ctx, forest_filter, filter, forest_name,
  100                             flags, AD_FILTER_FOREST);
  101 }
  102 
  103 
  104 static errno_t
  105 parse_filter(TALLOC_CTX *mem_ctx, const char *full_filter,
  106              char **filter, char **spec, int *flags)
  107 {
  108     char *kwdelim, *specdelim;
  109 
  110     if (filter == NULL || spec == NULL || flags == NULL) return EINVAL;
  111 
  112     kwdelim = strchr(full_filter, ':');
  113     if (kwdelim != NULL) {
  114         specdelim = strchr(kwdelim+1, ':');
  115 
  116         if (specdelim == NULL) {
  117             /* There is a single keyword. Treat it as a domain name */
  118             return parse_dom_filter(mem_ctx, full_filter, filter, spec, flags);
  119         } else if (strncmp(full_filter, "DOM", kwdelim-full_filter) == 0) {
  120             /* The format must be DOM:domain_name:filter */
  121             if (specdelim && specdelim-kwdelim <= 1) {
  122                 /* Check if there is some domain_name */
  123                 return EINVAL;
  124             }
  125 
  126             return parse_dom_filter(mem_ctx, kwdelim + 1, filter, spec, flags);
  127         } else if (strncmp(full_filter, "FOREST", kwdelim-full_filter) == 0) {
  128             /* The format must be FOREST:forest_name:filter */
  129             if (specdelim && specdelim-kwdelim <= 1) {
  130                 /* Check if there is some domain_name */
  131                 return EINVAL;
  132             }
  133 
  134             return parse_forest_filter(mem_ctx, kwdelim + 1,
  135                                        filter, spec, flags);
  136         }
  137 
  138         /* Malformed option */
  139         DEBUG(SSSDBG_CRIT_FAILURE,
  140               "Keyword in filter [%s] did not match expected format\n",
  141                full_filter);
  142         return EINVAL;
  143     }
  144 
  145     /* No keyword. Easy. */
  146     *filter = talloc_strdup(mem_ctx, full_filter);
  147     if (*filter == NULL) return ENOMEM;
  148 
  149     *spec = NULL;
  150     *flags = AD_FILTER_GENERIC;
  151     return EOK;
  152 }
  153 
  154 static errno_t
  155 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     int i = 0;
  170 
  171     if (_filter == NULL) return EINVAL;
  172 
  173     tmp_ctx = talloc_new(mem_ctx);
  174     if (tmp_ctx == NULL) {
  175         ret = ENOMEM;
  176         goto done;
  177     }
  178 
  179     if (filter_list == NULL) {
  180         *_filter = NULL;
  181         ret = EOK;
  182         goto done;
  183     }
  184 
  185     ret = split_on_separator(tmp_ctx, filter_list, '?', true, true,
  186                              &filters, &nfilters);
  187     if (ret != EOK) {
  188         DEBUG(SSSDBG_CRIT_FAILURE,
  189               "Cannot parse the list of ad_access_filters\n");
  190         goto done;
  191     }
  192 
  193     best_match = NULL;
  194     best_flags = 0;
  195     for (i=0; i < nfilters; i++) {
  196         ret = parse_filter(tmp_ctx, filters[i], &filter, &spec, &flags);
  197         if (ret != EOK) {
  198             /* Skip the faulty filter. At worst, the user won't be
  199              * allowed access */
  200             DEBUG(SSSDBG_MINOR_FAILURE, "Access filter [%s] could not be "
  201                   "parsed, skipping\n", filters[i]);
  202             continue;
  203         }
  204 
  205         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             continue;
  210         }
  211 
  212         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             continue;
  217         }
  218 
  219         if (flags > best_flags) {
  220             best_flags = flags;
  221             best_match = filter;
  222         }
  223     }
  224 
  225     ret = EOK;
  226     /* Make sure the result is enclosed in brackets */
  227     *_filter = sdap_get_access_filter(mem_ctx, best_match);
  228 done:
  229     talloc_free(tmp_ctx);
  230     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 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     req = tevent_req_create(mem_ctx, &state, struct ad_access_state);
  263     if (req == NULL) {
  264         return NULL;
  265     }
  266 
  267     state->ev = ev;
  268     state->ctx = ctx;
  269     state->pd = pd;
  270     state->be_ctx = be_ctx;
  271     state->domain = domain;
  272 
  273     ret = ad_parse_access_filter(state, domain, ctx->sdap_access_ctx->filter,
  274                                  &state->filter);
  275     if (ret != EOK) {
  276         DEBUG(SSSDBG_CRIT_FAILURE, "Could not determine the best filter\n");
  277         ret = ERR_ACCESS_DENIED;
  278         goto done;
  279     }
  280 
  281     state->clist = ad_gc_conn_list(state, ctx->ad_id_ctx, domain);
  282     if (state->clist == NULL) {
  283         ret = ENOMEM;
  284         goto done;
  285     }
  286 
  287     ret = ad_sdap_access_step(req, state->clist[state->cindex]);
  288     if (ret != EOK) {
  289         goto done;
  290     }
  291 
  292     ret = EOK;
  293 done:
  294     if (ret != EOK) {
  295         tevent_req_error(req, ret);
  296 
  297         tevent_req_post(req, ev);
  298     }
  299     return req;
  300 }
  301 
  302 static errno_t
  303 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     state = tevent_req_data(req, struct ad_access_state);
  310 
  311     req_ctx = talloc(state, struct sdap_access_ctx);
  312     if (req_ctx == NULL) {
  313         return ENOMEM;
  314     }
  315     req_ctx->id_ctx = state->ctx->sdap_access_ctx->id_ctx;
  316     req_ctx->filter = state->filter;
  317     memcpy(&req_ctx->access_rule,
  318            state->ctx->sdap_access_ctx->access_rule,
  319            sizeof(int) * LDAP_ACCESS_LAST);
  320 
  321     subreq = sdap_access_send(state, state->ev, state->be_ctx,
  322                               state->domain, req_ctx,
  323                               conn, state->pd);
  324     if (req == NULL) {
  325         talloc_free(req_ctx);
  326         return ENOMEM;
  327     }
  328     tevent_req_set_callback(subreq, ad_sdap_access_done, req);
  329     return EOK;
  330 }
  331 
  332 static void
  333 ad_gpo_access_done(struct tevent_req *subreq);
  334 
  335 static void
  336 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     req = tevent_req_callback_data(subreq, struct tevent_req);
  343     state = tevent_req_data(req, struct ad_access_state);
  344 
  345     ret = sdap_access_recv(subreq);
  346     talloc_zfree(subreq);
  347 
  348     if (ret != EOK) {
  349         switch (ret) {
  350         case ERR_ACCOUNT_EXPIRED:
  351             tevent_req_error(req, ret);
  352             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             break;
  361 
  362         default:
  363             break;
  364         }
  365 
  366         /* If possible, retry with LDAP */
  367         state->cindex++;
  368         if (state->clist[state->cindex] == NULL) {
  369             DEBUG(SSSDBG_OP_FAILURE,
  370                   "Error retrieving access check result: %s\n",
  371                   sss_strerror(ret));
  372             tevent_req_error(req, ret);
  373             return;
  374         }
  375 
  376         ret = ad_sdap_access_step(req, state->clist[state->cindex]);
  377         if (ret != EOK) {
  378             tevent_req_error(req, ret);
  379             return;
  380         }
  381 
  382         /* Another check in progress */
  383 
  384         return;
  385     }
  386 
  387     switch (state->ctx->gpo_access_control_mode) {
  388     case GPO_ACCESS_CONTROL_DISABLED:
  389         /* do not evaluate gpos; mark request done */
  390         tevent_req_done(req);
  391         return;
  392     case GPO_ACCESS_CONTROL_PERMISSIVE:
  393     case GPO_ACCESS_CONTROL_ENFORCING:
  394         /* continue on to evaluate gpos */
  395         break;
  396     default:
  397         tevent_req_error(req, EINVAL);
  398         return;
  399     }
  400 
  401     subreq = ad_gpo_access_send(state,
  402                                 state->be_ctx->ev,
  403                                 state->domain,
  404                                 state->ctx,
  405                                 state->pd->user,
  406                                 state->pd->service);
  407 
  408     if (!subreq) {
  409         tevent_req_error(req, ENOMEM);
  410         return;
  411     }
  412 
  413     tevent_req_set_callback(subreq, ad_gpo_access_done, req);
  414 
  415 }
  416 
  417 static void
  418 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     req = tevent_req_callback_data(subreq, struct tevent_req);
  426     state = tevent_req_data(req, struct ad_access_state);
  427     mode = state->ctx->gpo_access_control_mode;
  428 
  429     ret = ad_gpo_access_recv(subreq);
  430     talloc_zfree(subreq);
  431 
  432     if (ret == EOK) {
  433         DEBUG(SSSDBG_TRACE_FUNC, "GPO-based access control successful.\n");
  434         tevent_req_done(req);
  435     } else {
  436         DEBUG(SSSDBG_OP_FAILURE, "GPO-based access control failed.\n");
  437         if (mode == GPO_ACCESS_CONTROL_ENFORCING) {
  438             tevent_req_error(req, ret);
  439         } else {
  440             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             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             tevent_req_done(req);
  448         }
  449     }
  450 }
  451 
  452 static errno_t
  453 ad_access_recv(struct tevent_req *req)
  454 {
  455     TEVENT_REQ_RETURN_ON_ERROR(req);
  456 
  457     return EOK;
  458 }
  459 
  460 struct ad_pam_access_handler_state {
  461     struct pam_data *pd;
  462 };
  463 
  464 static void ad_pam_access_handler_done(struct tevent_req *subreq);
  465 
  466 struct tevent_req *
  467 ad_pam_access_handler_send(TALLOC_CTX *mem_ctx,
  468                            struct ad_access_ctx *access_ctx,
  469                            struct pam_data *pd,
  470                            struct dp_req_params *params)
  471 {
  472     struct ad_pam_access_handler_state *state;
  473     struct tevent_req *subreq;
  474     struct tevent_req *req;
  475 
  476     req = tevent_req_create(mem_ctx, &state,
  477                             struct ad_pam_access_handler_state);
  478     if (req == NULL) {
  479         DEBUG(SSSDBG_CRIT_FAILURE, "tevent_req_create() failed\n");
  480         return NULL;
  481     }
  482 
  483     state->pd = pd;
  484 
  485     subreq = ad_access_send(state, params->ev, params->be_ctx,
  486                             params->domain, access_ctx, pd);
  487     if (subreq == NULL) {
  488         pd->pam_status = PAM_SYSTEM_ERR;
  489         goto immediately;
  490     }
  491 
  492     tevent_req_set_callback(subreq, ad_pam_access_handler_done, req);
  493 
  494     return req;
  495 
  496 immediately:
  497     /* TODO For backward compatibility we always return EOK to DP now. */
  498     tevent_req_done(req);
  499     tevent_req_post(req, params->ev);
  500 
  501     return req;
  502 }
  503 
  504 static void ad_pam_access_handler_done(struct tevent_req *subreq)
  505 {
  506     struct ad_pam_access_handler_state *state;
  507     struct tevent_req *req;
  508     errno_t ret;
  509 
  510     req = tevent_req_callback_data(subreq, struct tevent_req);
  511     state = tevent_req_data(req, struct ad_pam_access_handler_state);
  512 
  513     ret = ad_access_recv(subreq);
  514     talloc_free(subreq);
  515     switch (ret) {
  516     case EOK:
  517         state->pd->pam_status = PAM_SUCCESS;
  518         break;
  519     case ERR_ACCESS_DENIED:
  520         state->pd->pam_status = PAM_PERM_DENIED;
  521         break;
  522     case ERR_ACCOUNT_EXPIRED:
  523         state->pd->pam_status = PAM_ACCT_EXPIRED;
  524         break;
  525     default:
  526         state->pd->pam_status = PAM_SYSTEM_ERR;
  527         break;
  528     }
  529 
  530     /* TODO For backward compatibility we always return EOK to DP now. */
  531     tevent_req_done(req);
  532 }
  533 
  534 errno_t
  535 ad_pam_access_handler_recv(TALLOC_CTX *mem_ctx,
  536                              struct tevent_req *req,
  537                              struct pam_data **_data)
  538 {
  539     struct ad_pam_access_handler_state *state = NULL;
  540 
  541     state = tevent_req_data(req, struct ad_pam_access_handler_state);
  542 
  543     TEVENT_REQ_RETURN_ON_ERROR(req);
  544 
  545     *_data = talloc_steal(mem_ctx, state->pd);
  546 
  547     return EOK;
  548 }