]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Rework mod_authorize_resume to use state, group and profile async lookups
authorNick Porter <nick@portercomputing.co.uk>
Tue, 28 Mar 2023 13:51:27 +0000 (14:51 +0100)
committerNick Porter <nick@portercomputing.co.uk>
Tue, 4 Apr 2023 07:30:14 +0000 (08:30 +0100)
src/modules/rlm_ldap/rlm_ldap.c

index 3514a47950b57cb05a5bc2394b6fb73eb4b3fdbe..49e9a41bdbfda99f75fb2b30ad91bcc31970b5d3 100644 (file)
@@ -1312,182 +1312,217 @@ static unlang_action_t mod_authorize_start(UNUSED rlm_rcode_t *p_result, UNUSED
                                        &autz_ctx->query);
 }
 
+#define REPEAT_MOD_AUTHORIZE_RESUME \
+       if (unlang_function_repeat_set(request, mod_authorize_resume) < 0) { \
+               rcode = RLM_MODULE_FAIL; \
+               goto finish; \
+       }
+
+/** Resume function called after each potential yield in LDAP authorization
+ *
+ * Some operations may or may not yeild.  E.g. if group membership is
+ * read from an attribute returned with the user object and is already
+ * in the correct form, that will not yeild.
+ * Hence, each state may fall through to the next.
+ *
+ * @param p_result     Result of current authorization.
+ * @param priority     Unused.
+ * @param request      Current request.
+ * @param uctx         Current authrorization context.
+ * @return One of the RLM_MODULE_* values.
+ */
 static unlang_action_t mod_authorize_resume(rlm_rcode_t *p_result, UNUSED int *priority, request_t *request, void *uctx)
 {
        ldap_autz_ctx_t         *autz_ctx = talloc_get_type_abort(uctx, ldap_autz_ctx_t);
        rlm_ldap_t const        *inst = talloc_get_type_abort_const(autz_ctx->inst, rlm_ldap_t);
-       LDAPMessage             *entry;
-       int                     i, ldap_errno;
+       ldap_autz_mod_env_t     *mod_env = talloc_get_type_abort(autz_ctx->mod_env, ldap_autz_mod_env_t);
+       int                     ldap_errno;
        rlm_rcode_t             rcode = RLM_MODULE_OK;
-       struct berval           **values;
        LDAP                    *handle = fr_ldap_handle_thread_local();
 
-       /*
-        *      If a user entry has been found the current rcode will be OK
-        */
-       if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
-
-       entry = ldap_first_entry(handle, autz_ctx->query->result);
-       if (!entry) {
-               ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
-               REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
+       switch (autz_ctx->status) {
+       case LDAP_AUTZ_FIND:
+               /*
+                *      If a user entry has been found the current rcode will be OK
+                */
+               if (*p_result != RLM_MODULE_OK) return UNLANG_ACTION_CALCULATE_RESULT;
 
-               goto finish;
-       }
+               autz_ctx->entry = ldap_first_entry(handle, autz_ctx->query->result);
+               if (!autz_ctx->entry) {
+                       ldap_get_option(handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
+                       REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
 
-       /*
-        *      Check for access.
-        */
-       if (inst->userobj_access_attr) {
-               rcode = rlm_ldap_check_access(inst, request, entry);
-               if (rcode != RLM_MODULE_OK) {
                        goto finish;
                }
-       }
 
-       /*
-        *      Check if we need to cache group memberships
-        */
-       if (inst->cacheable_group_dn || inst->cacheable_group_name) {
-               if (inst->userobj_membership_attr) {
-                       rlm_ldap_cacheable_userobj(&rcode, inst, request, autz_ctx->ttrunk, entry, inst->userobj_membership_attr);
+               /*
+                *      Check for access.
+                */
+               if (inst->userobj_access_attr) {
+                       rcode = rlm_ldap_check_access(inst, request, autz_ctx->entry);
                        if (rcode != RLM_MODULE_OK) {
                                goto finish;
                        }
                }
 
-               rlm_ldap_cacheable_groupobj(&rcode, inst, request, autz_ctx->ttrunk);
-               if (rcode != RLM_MODULE_OK) {
-                       goto finish;
+               /*
+                *      Check if we need to cache group memberships
+                */
+               if ((inst->cacheable_group_dn || inst->cacheable_group_name) && (inst->userobj_membership_attr)) {
+                       REPEAT_MOD_AUTHORIZE_RESUME;
+                       if (rlm_ldap_cacheable_userobj(&rcode, request, autz_ctx,
+                                                      inst->userobj_membership_attr) == UNLANG_ACTION_PUSHED_CHILD) {
+                               autz_ctx->status = LDAP_AUTZ_GROUP;
+                               return UNLANG_ACTION_PUSHED_CHILD;
+                       }
+                       if (rcode != RLM_MODULE_OK) goto finish;
                }
-       }
+               FALL_THROUGH;
+
+       case LDAP_AUTZ_GROUP:
+               if (inst->cacheable_group_dn || inst->cacheable_group_name) {
+                       REPEAT_MOD_AUTHORIZE_RESUME;
+                       if (rlm_ldap_cacheable_groupobj(&rcode, request, autz_ctx) == UNLANG_ACTION_PUSHED_CHILD) {
+                               autz_ctx->status = LDAP_AUTZ_POST_GROUP;
+                               return UNLANG_ACTION_PUSHED_CHILD;
+                       }
+                       if (rcode != RLM_MODULE_OK) goto finish;
+               }
+               FALL_THROUGH;
 
+       case LDAP_AUTZ_POST_GROUP:
 #ifdef WITH_EDIR
-       /*
-        *      We already have a Password.Cleartext.  Skip edir.
-        */
-       if (fr_pair_find_by_da(&request->control_pairs, NULL, attr_cleartext_password)) goto skip_edir;
-
-       /*
-        *      Retrieve Universal Password if we use eDirectory
-        */
-       if (inst->edir) {
-               fr_pair_t       *vp;
-               int             res = 0;
-               char            password[256];
-               size_t          pass_size = sizeof(password);
-               char const      *dn = rlm_find_user_dn_cached(request);;
-
                /*
-                *      Retrive universal password
+                *      We already have a Password.Cleartext.  Skip edir.
                 */
-               res = fr_ldap_edir_get_password(handle, dn, password, &pass_size);
-               if (res != 0) {
-                       REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, fr_ldap_edir_errstr(res));
-                       rcode = RLM_MODULE_FAIL;
-
-                       goto finish;
-               }
+               if (fr_pair_find_by_da(&request->control_pairs, NULL, attr_cleartext_password)) goto skip_edir;
 
                /*
-                *      Add Password.Cleartext attribute to the request
+                *      Retrieve Universal Password if we use eDirectory
                 */
-               MEM(pair_update_control(&vp, attr_cleartext_password) >= 0);
-               fr_pair_value_bstrndup(vp, password, pass_size, true);
-
-               if (RDEBUG_ENABLED3) {
-                       RDEBUG3("Added eDirectory password.  control.%pP", vp);
-               } else {
-                       RDEBUG2("Added eDirectory password");
-               }
+               if (inst->edir) {
+                       fr_pair_t       *vp;
+                       int             res = 0;
+                       char            password[256];
+                       size_t          pass_size = sizeof(password);
+                       char const      *dn = rlm_find_user_dn_cached(request);;
 
-               if (inst->edir_autz) {
-                       fr_ldap_thread_t *thread = talloc_get_type_abort(module_rlm_thread_by_data(inst)->data,
-                                                                        fr_ldap_thread_t);
-
-                       RDEBUG2("Binding as user for eDirectory authorization checks");
                        /*
-                        *      Bind as the user
+                        *      Retrive universal password
                         */
-                       if (fr_ldap_bind_auth_async(request, thread, dn, vp->vp_strvalue) < 0) {
+                       res = fr_ldap_edir_get_password(handle, dn, password, &pass_size);
+                       if (res != 0) {
+                               REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, fr_ldap_edir_errstr(res));
                                rcode = RLM_MODULE_FAIL;
+
                                goto finish;
                        }
 
-                       rcode = unlang_interpret_synchronous(unlang_interpret_event_list(request), request);
+                       /*
+                        *      Add Password.Cleartext attribute to the request
+                        */
+                       MEM(pair_update_control(&vp, attr_cleartext_password) >= 0);
+                       fr_pair_value_bstrndup(vp, password, pass_size, true);
 
-                       if (rcode != RLM_MODULE_OK) goto finish;
+                       if (RDEBUG_ENABLED3) {
+                               RDEBUG3("Added eDirectory password.  control.%pP", vp);
+                       } else {
+                               RDEBUG2("Added eDirectory password");
+                       }
+
+                       if (inst->edir_autz) {
+                               fr_ldap_thread_t *thread = talloc_get_type_abort(module_rlm_thread_by_data(inst)->data,
+                                                                        fr_ldap_thread_t);
+
+                               RDEBUG2("Binding as user for eDirectory authorization checks");
+                               /*
+                                *      Bind as the user
+                                */
+                               if (fr_ldap_bind_auth_async(request, thread, dn, vp->vp_strvalue) < 0) {
+                                       rcode = RLM_MODULE_FAIL;
+                                       goto finish;
+                               }
+
+                               rcode = unlang_interpret_synchronous(unlang_interpret_event_list(request), request);
+
+                               if (rcode != RLM_MODULE_OK) goto finish;
+                       }
                }
-       }
+               FALL_THROUGH;
 
-skip_edir:
+       case LDAP_AUTZ_POST_EDIR:
+       skip_edir:
 #endif
+               /*
+                *      Apply ONE user profile, or a default user profile.
+                */
+               if (mod_env->default_profile.type == FR_TYPE_STRING) {
+                       unlang_action_t ret;
+
+                       REPEAT_MOD_AUTHORIZE_RESUME;
+                       ret = rlm_ldap_map_profile(request, autz_ctx, mod_env->default_profile.vb_strvalue,
+                                                  &autz_ctx->expanded);
+                       switch (ret) {
+                       case UNLANG_ACTION_FAIL:
+                               rcode = RLM_MODULE_FAIL;
+                               goto finish;
 
-       /*
-        *      Apply ONE user profile, or a default user profile.
-        */
-       if (inst->default_profile) {
-               char const      *profile;
-               char            profile_buff[1024];
-               rlm_rcode_t     ret;
+                       case UNLANG_ACTION_PUSHED_CHILD:
+                               autz_ctx->status = LDAP_AUTZ_POST_DEFAULT_PROFILE;
+                               return UNLANG_ACTION_PUSHED_CHILD;
 
-               if (tmpl_expand(&profile, profile_buff, sizeof(profile_buff),
-                               request, inst->default_profile, NULL, NULL) < 0) {
-                       REDEBUG("Failed creating default profile string");
+                       default:
+                               break;
+                       }
+               }
+               FALL_THROUGH;
 
-                       rcode = RLM_MODULE_INVALID;
-                       goto finish;
+       case LDAP_AUTZ_POST_DEFAULT_PROFILE:
+               /*
+                *      Apply a SET of user profiles.
+                */
+               if (inst->profile_attr) {
+                       autz_ctx->profile_values = ldap_get_values_len(handle, autz_ctx->entry, inst->profile_attr);
                }
+               FALL_THROUGH;
 
-               rlm_ldap_map_profile(&ret, inst, request, autz_ctx->ttrunk, profile, &autz_ctx->expanded);
-               switch (ret) {
-               case RLM_MODULE_INVALID:
-                       rcode = RLM_MODULE_INVALID;
-                       goto finish;
+       case LDAP_AUTZ_USER_PROFILE:
+               /*
+                *      After each profile has been applied, execution will restart here.
+                *      Start by clearing the previously used value.
+                */
+               TALLOC_FREE(autz_ctx->profile_value);
 
-               case RLM_MODULE_FAIL:
-                       rcode = RLM_MODULE_FAIL;
-                       goto finish;
+               if (autz_ctx->profile_values && autz_ctx->profile_values[autz_ctx->value_idx]) {
+                       unlang_action_t ret;
 
-               case RLM_MODULE_UPDATED:
-                       rcode = RLM_MODULE_UPDATED;
-                       FALL_THROUGH;
-               default:
-                       break;
-               }
-       }
+                       autz_ctx->profile_value = fr_ldap_berval_to_string(autz_ctx, autz_ctx->profile_values[autz_ctx->value_idx++]);
+                       REPEAT_MOD_AUTHORIZE_RESUME;
+                       ret = rlm_ldap_map_profile(request, autz_ctx, autz_ctx->profile_value, &autz_ctx->expanded);
+                       switch (ret) {
+                       case UNLANG_ACTION_FAIL:
+                               rcode = RLM_MODULE_FAIL;
+                               goto finish;
 
-       /*
-        *      Apply a SET of user profiles.
-        */
-       if (inst->profile_attr) {
-               values = ldap_get_values_len(handle, entry, inst->profile_attr);
-               if (values != NULL) {
-                       for (i = 0; values[i] != NULL; i++) {
-                               rlm_rcode_t ret;
-                               char *value;
-
-                               value = fr_ldap_berval_to_string(request, values[i]);
-                               rlm_ldap_map_profile(&ret, inst, request, autz_ctx->ttrunk, value, &autz_ctx->expanded);
-                               talloc_free(value);
-                               if (ret == RLM_MODULE_FAIL) {
-                                       ldap_value_free_len(values);
-                                       rcode = ret;
-                                       goto finish;
-                               }
+                       case UNLANG_ACTION_PUSHED_CHILD:
+                               autz_ctx->status = LDAP_AUTZ_USER_PROFILE;
+                               return UNLANG_ACTION_PUSHED_CHILD;
 
+                       default:
+                               break;
                        }
-                       ldap_value_free_len(values);
                }
-       }
-
-       if (!map_list_empty(&inst->user_map) || inst->valuepair_attr) {
-               RDEBUG2("Processing user attributes");
-               RINDENT();
-               if (fr_ldap_map_do(request, inst->valuepair_attr,
-                                  &autz_ctx->expanded, entry) > 0) rcode = RLM_MODULE_UPDATED;
-               REXDENT();
-               rlm_ldap_check_reply(&(module_ctx_t){.inst = autz_ctx->dlinst}, request, autz_ctx->ttrunk);
+               FALL_THROUGH;
+
+       case LDAP_AUTZ_MAP:
+               if (!map_list_empty(&inst->user_map) || inst->valuepair_attr) {
+                       RDEBUG2("Processing user attributes");
+                       RINDENT();
+                       if (fr_ldap_map_do(request, inst->valuepair_attr,
+                                          &autz_ctx->expanded, autz_ctx->entry) > 0) rcode = RLM_MODULE_UPDATED;
+                       REXDENT();
+                       rlm_ldap_check_reply(&(module_ctx_t){.inst = autz_ctx->dlinst}, request, autz_ctx->ttrunk);
+               }
        }
 
 finish: