From 496d85ef847bbcf31d03caec84b108bd5c0cf898 Mon Sep 17 00:00:00 2001 From: kkalev Date: Fri, 28 Jan 2005 07:21:43 +0000 Subject: [PATCH] Add second patch from Novell for creating a postauth method in order to implement the Novell eDirectory account policy check --- INSTALL | 2 + doc/ldap_howto.txt | 8 + doc/rlm_ldap | 13 ++ raddb/radiusd.conf.in | 20 ++- src/modules/rlm_ldap/rlm_ldap.c | 330 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 365 insertions(+), 8 deletions(-) diff --git a/INSTALL b/INSTALL index 647c57e..8c59af0 100644 --- a/INSTALL +++ b/INSTALL @@ -47,6 +47,8 @@ following list is a selection from the available flags: --with-experimental-modules Use experimental and unstable modules. (default=no) --enable-developer Turns on super-duper-extra-compile-warnings when using gcc. + --with-edir Compile with support for Novell eDirectory + integration. To get the defaults that Cistron Radius used up to 1.5.4.3-beta18, use: diff --git a/doc/ldap_howto.txt b/doc/ldap_howto.txt index 9ea421f..f53804c 100644 --- a/doc/ldap_howto.txt +++ b/doc/ldap_howto.txt @@ -985,7 +985,15 @@ ldap { ldap_cache_size = 0 ldap_connections_number = 10 #password_header = {clear} +#While integrating FreeRADIUS with Novell eDirectory, set +#'password_attribute = nspmpassword' in order to use the universal password +#of the eDirectory users for RADIUS authentication. This will work only if +#FreeRADIUS is configured to build with --with-edir option. password_attribute = userPassword +#Comment out the following to disable the eDirectory account policy check and +#intruder detection. This will work only if FreeRADIUS is configured to build +#with --with-edir option. +#edir_account_policy_check=no groupname_attribute = radiusGroupName groupmembership_filter = (&(uid=%{Stripped-User-Name:-%{User-Name}}) (objectclass=radiusprofile)) diff --git a/doc/rlm_ldap b/doc/rlm_ldap index 79c9d0a..5c870a3 100644 --- a/doc/rlm_ldap +++ b/doc/rlm_ldap @@ -178,11 +178,24 @@ the rlm_ldap module: # password_attribute: Define the attribute which contains the user # password. +# While integrating FreeRADIUS with Novell eDirectory, set +# 'password_attribute = nspmpassword' in order to use the universal +# password of the eDirectory users for RADIUS authentication. This will +# work only if FreeRADIUS is configured to build with --with-edir option. # # default: NULL - don't add password # # password_attribute = "userPassword" +# edir_account_policy_check: Specifies if the module has to enforce +# Novell eDirectory account policy check and intruder detection for +# RADIUS users. This will work only if FreeRADIUS is configured to build +# with --with-edir option. +# +# default: yes - eDirectory account policy check enabled +# +# edir_account_policy_check = no + # groupname_attribute: The attribute containing group name in the LDAP # server. It is used to search group by name. # diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in index 5c97c9f..4715913 100644 --- a/raddb/radiusd.conf.in +++ b/raddb/radiusd.conf.in @@ -803,6 +803,13 @@ $INCLUDE ${confdir}/eap.conf # freeRADIUS is configured to build with --with-edir option. # # password_attribute = userPassword + # + # Un-comment the following to disable Novell eDirectory account + # policy check and intruder detection. This will work *only if* + # FreeRADIUS is configured to build with --with-edir option. + # + # edir_account_policy_check=no + # # groupname_attribute = cn # groupmembership_filter = "(|(&(objectClass=GroupOfNames)(member=%{Ldap-UserDn}))(&(objectClass=GroupOfUniqueNames)(uniquemember=%{Ldap-UserDn})))" # groupmembership_attribute = radiusGroupName @@ -1947,8 +1954,17 @@ post-auth { # sql # - # Access-Reject packets are sent through the REJECT sub-section - # of the post-auth section. + # Un-comment the following if you have set + # 'edir_account_policy_check = yes' in the ldap module sub-section of + # the 'modules' section. + # +# ldap + # + # Access-Reject packets are sent through the REJECT sub-section of the + # post-auth section. + # Uncomment the following and set the module name to the ldap instance + # name if you have set 'edir_account_policy_check = yes' in the ldap + # module sub-section of the 'modules' section. # # Post-Auth-Type REJECT { # insert-module-name-here diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index fbe2668..ff02c91 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -155,6 +155,9 @@ typedef struct { TLDAP_RADIUS *check_item_map; TLDAP_RADIUS *reply_item_map; LDAP_CONN *conns; +#ifdef NOVELL + LDAP_CONN *apc_conns; +#endif int ldap_debug; /* Debug flag for LDAP SDK */ char *xlat_name; /* name used to xlat */ char *tls_cacertfile; @@ -163,6 +166,9 @@ typedef struct { char *tls_keyfile; char *tls_randfile; char *tls_require_cert; +#ifdef NOVELL + int edir_account_policy_check; +#endif } ldap_instance; /* The default setting for TLS Certificate Verification */ @@ -206,6 +212,9 @@ static CONF_PARSER module_config[] = { {"compare_check_items", PW_TYPE_BOOLEAN, offsetof(ldap_instance,do_comp), NULL, "no"}, {"access_attr_used_for_allow", PW_TYPE_BOOLEAN, offsetof(ldap_instance,default_allow), NULL, "yes"}, {"do_xlat", PW_TYPE_BOOLEAN, offsetof(ldap_instance,do_xlat), NULL, "yes"}, +#ifdef NOVELL + {"edir_account_policy_check", PW_TYPE_BOOLEAN, offsetof(ldap_instance,edir_account_policy_check), NULL, "yes"}, +#endif {NULL, -1, 0, NULL, NULL} }; @@ -220,7 +229,7 @@ static void fieldcpy(char *, char **); static VALUE_PAIR *ldap_pairget(LDAP *, LDAPMessage *, TLDAP_RADIUS *,VALUE_PAIR **,char); static int ldap_groupcmp(void *, REQUEST *, VALUE_PAIR *, VALUE_PAIR *, VALUE_PAIR *, VALUE_PAIR **); static int ldap_xlat(void *,REQUEST *, char *, char *,int, RADIUS_ESCAPE_STRING); -static LDAP *ldap_connect(void *instance, const char *, const char *, int, int *); +static LDAP *ldap_connect(void *instance, const char *, const char *, int, int *, char **); static int read_mappings(ldap_instance* inst); static inline int ldap_get_conn(LDAP_CONN *conns,LDAP_CONN **ret,void *instance) @@ -343,6 +352,22 @@ ldap_instantiate(CONF_SECTION * conf, void **instance) DEBUG("rlm_ldap: Registering ldap_xlat with xlat_name %s",xlat_name); xlat_register(xlat_name,ldap_xlat,inst); +#ifdef NOVELL + /* + * (LDAP_Instance, V1) attribute-value pair in the config items list means + * that the 'authorize' method of the instance 'V1' of the LDAP module has + * processed this request. + */ + dict_addattr("LDAP-Instance", 0, PW_TYPE_STRING, -1, flags); + /* + * ('eDir-APC', '1') in config items list => Do not perform eDirectory account + * policy check (APC) + * ('eDir-APC', '2') in config items list => Perform eDirectory APC + * ('eDir-APC', '3') in config items list => eDirectory APC has been completed + */ + dict_addattr("eDir-APC", 0, PW_TYPE_INTEGER, -1, flags); +#endif + if (inst->num_conns <= 0){ radlog(L_ERR, "rlm_ldap: Invalid ldap connections number passed."); free(inst); @@ -362,6 +387,28 @@ ldap_instantiate(CONF_SECTION * conf, void **instance) pthread_mutex_init(&inst->conns[i].mutex, NULL); } +#ifdef NOVELL + /* + * 'inst->apc_conns' is a separate connection pool to be used for performing + * eDirectory account policy check in the 'postauth' method. This avoids + * changing the (RADIUS server) credentials associated with the 'inst->conns' + * connection pool. + */ + inst->apc_conns = (LDAP_CONN *)malloc(sizeof(LDAP_CONN)*inst->num_conns); + if (inst->apc_conns == NULL){ + radlog(L_ERR, "rlm_ldap: Could not allocate memory. Aborting."); + free(inst); + return -1; + } + for(i = 0; i < inst->num_conns; i++){ + inst->apc_conns[i].bound = 0; + inst->apc_conns[i].locked = 0; + inst->apc_conns[i].failed_conns = 0; + inst->apc_conns[i].ld = NULL; + pthread_mutex_init(&inst->apc_conns[i].mutex, NULL); + } +#endif + if (read_mappings(inst) != 0) { radlog(L_ERR, "rlm_ldap: Reading dictionary mappings from file %s failed", inst->dictionary_mapping); @@ -593,7 +640,7 @@ retry: ldap_unbind_s(conn->ld); } if ((conn->ld = ldap_connect(instance, inst->login, - inst->password, 0, &res)) == NULL) { + inst->password, 0, &res, NULL)) == NULL) { radlog(L_ERR, "rlm_ldap: (re)connection attempt failed"); if (search_retry == 0) conn->failed_conns++; @@ -1309,6 +1356,63 @@ ldap_authorize(void *instance, REQUEST * request) strncpy(passwd_item->strvalue,passwd_val,MAX_STRING_LEN - 1); passwd_item->length = (passwd_len > (MAX_STRING_LEN - 1)) ? (MAX_STRING_LEN - 1) : passwd_len; pairadd(&request->config_items,passwd_item); + +#ifdef NOVELL + { + DICT_ATTR *dattr; + VALUE_PAIR *vp_inst, *vp_apc; + int inst_attr, apc_attr; + + dattr = dict_attrbyname("LDAP-Instance"); + inst_attr = dattr->attr; + dattr = dict_attrbyname("eDir-APC"); + apc_attr = dattr->attr; + + vp_inst = pairfind(request->config_items, inst_attr); + if(vp_inst == NULL){ + /* + * The authorize method of no other LDAP module instance has + * processed this request. + */ + if ((vp_inst = paircreate(inst_attr, PW_TYPE_STRING)) == NULL){ + radlog(L_ERR, "rlm_ldap: Could not allocate memory. Aborting."); + ldap_msgfree(result); + ldap_release_conn(conn_id, inst->conns); + memset(universal_password, 0, universal_password_len); + free(universal_password); + return RLM_MODULE_FAIL; + } + strcpy(vp_inst->strvalue, inst->xlat_name); + vp_inst->length = strlen(vp_inst->strvalue); + pairadd(&request->config_items, vp_inst); + + /* + * Inform the authenticate / post-auth method about the presence + * of UP in the config items list and whether eDirectory account + * policy check is to be performed or not. + */ + if ((vp_apc = paircreate(apc_attr, PW_TYPE_INTEGER)) == NULL){ + radlog(L_ERR, "rlm_ldap: Could not allocate memory. Aborting."); + ldap_msgfree(result); + ldap_release_conn(conn_id, inst->conns); + memset(universal_password, 0, universal_password_len); + free(universal_password); + return RLM_MODULE_FAIL; + } + + if(!inst->edir_account_policy_check){ + /* Do nothing */ + strcpy(vp_apc->strvalue, "1"); + }else{ + /* Perform eDirectory account-policy check */ + strcpy(vp_apc->strvalue, "2"); + } + vp_apc->length = 1; + pairadd(&request->config_items, vp_apc); + } + } +#endif + DEBUG("rlm_ldap: Added the eDirectory password in check items"); } } @@ -1350,6 +1454,20 @@ ldap_authorize(void *instance, REQUEST * request) } if (inst->do_comp && paircmp(request,request->packet->vps,*check_pairs,reply_pairs) != 0){ +#ifdef NOVELL + /* Don't perform eDirectory APC if RADIUS authorize fails */ + int apc_attr; + VALUE_PAIR *vp_apc; + DICT_ATTR *dattr; + + dattr = dict_attrbyname("eDir-APC"); + apc_attr = dattr->attr; + + vp_apc = pairfind(request->config_items, apc_attr); + if(vp_apc) + vp_apc->strvalue[0] = '1'; +#endif + DEBUG("rlm_ldap: Pairs do not match. Rejecting user."); snprintf(module_fmsg,sizeof(module_fmsg),"rlm_ldap: Pairs do not match"); module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ); @@ -1391,7 +1509,7 @@ ldap_authenticate(void *instance, REQUEST * request) LDAP *ld_user; LDAPMessage *result, *msg; ldap_instance *inst = instance; - char *user_dn, *attrs[] = {"uid", NULL}; + char *user_dn, *attrs[] = {"uid", NULL}, *err = NULL; char filter[MAX_FILTER_STR_LEN]; char basedn[MAX_FILTER_STR_LEN]; int res; @@ -1496,8 +1614,35 @@ ldap_authenticate(void *instance, REQUEST * request) DEBUG("rlm_ldap: user DN: %s", user_dn); +#ifndef NOVELL + ld_user = ldap_connect(instance, user_dn, request->password->strvalue, + 1, &res, NULL); +#else + ld_user = ldap_connect(instance, user_dn, request->password->strvalue, - 1, &res); + 1, &res, &err); + + if(err != NULL){ + /* 'err' contains the LDAP connection error description */ + DEBUG("rlm_ldap: %s", err); + pairadd(&request->reply->vps, pairmake("Reply-Message", err, T_OP_EQ)); + ldap_memfree((void *)err); + } + + /* Don't perform eDirectory APC again after attempting to bind here. */ + { + int apc_attr; + DICT_ATTR *dattr; + VALUE_PAIR *vp_apc; + + dattr = dict_attrbyname("eDir-APC"); + apc_attr = dattr->attr; + vp_apc = pairfind(request->config_items, apc_attr); + if(vp_apc && vp_apc->strvalue[0] == '2') + vp_apc->strvalue[0] = '3'; + } +#endif + if (ld_user == NULL){ if (res == RLM_MODULE_REJECT){ inst->failed_conns = 0; @@ -1520,8 +1665,151 @@ ldap_authenticate(void *instance, REQUEST * request) return RLM_MODULE_OK; } +#ifdef NOVELL +/***************************************************************************** + * + * Function: rlm_ldap_postauth + * + * Purpose: Perform eDirectory account policy check and failed-login reporting + * to eDirectory. + * + *****************************************************************************/ +static int +ldap_postauth(void *instance, REQUEST * request) +{ + int res = RLM_MODULE_FAIL; + int inst_attr, apc_attr; + char password[UNIVERSAL_PASS_LEN]; + ldap_instance *inst = instance; + LDAP_CONN *conn; + VALUE_PAIR *vp_inst, *vp_apc; + DICT_ATTR *dattr; + + dattr = dict_attrbyname("LDAP-Instance"); + inst_attr = dattr->attr; + dattr = dict_attrbyname("eDir-APC"); + apc_attr = dattr->attr; + + vp_inst = pairfind(request->config_items, inst_attr); + + /* + * Check if the password in the config items list is the user's UP which has + * been read in the authorize method of this instance of the LDAP module. + */ + if((vp_inst == NULL) || strcmp(vp_inst->strvalue, inst->xlat_name)) + return RLM_MODULE_NOOP; + + vp_apc = pairfind(request->config_items, apc_attr); + + switch(vp_apc->strvalue[0]){ + case '1': + /* Account policy check not enabled */ + case '3': + /* Account policy check has been completed */ + res = RLM_MODULE_NOOP; + break; + case '2': + { + int err, conn_id = -1, i; + char *error_msg = NULL; + LDAP *ld; + VALUE_PAIR *vp_fdn, *vp_pwd; + DICT_ATTR *da; + ldap_instance *inst = instance; + LDAP_CONN *conn; + + if(request->reply->code == PW_AUTHENTICATION_REJECT){ + /* Bind to eDirectory as the RADIUS user with a wrong password. */ + vp_pwd = pairfind(request->config_items, PW_PASSWORD); + strcpy(password, vp_pwd->strvalue); + if(strlen(password) > 0){ + if(password[0] != 'a'){ + password[0] = 'a'; + }else{ + password[0] = 'b'; + } + }else{ + strcpy(password, "dummy_password"); + } + res = RLM_MODULE_REJECT; + }else{ + /* Bind to eDirectory as the RADIUS user using the user's UP */ + vp_pwd = pairfind(request->config_items, PW_PASSWORD); + if(vp_pwd == NULL){ + DEBUG("rlm_ldap: User's Universal Password not in config items list."); + return RLM_MODULE_FAIL; + } + strcpy(password, vp_pwd->strvalue); + } + + if ((da = dict_attrbyname("Ldap-UserDn")) == NULL) { + DEBUG("rlm_ldap: Attribute for user FDN not found in dictionary. Unable to proceed"); + return RLM_MODULE_FAIL; + } + + vp_fdn = pairfind(request->packet->vps, da->attr); + if(vp_fdn == NULL){ + DEBUG("rlm_ldap: User's FQDN not in config items list."); + return RLM_MODULE_FAIL; + } + + if ((conn_id = ldap_get_conn(inst->apc_conns, &conn, inst)) == -1){ + radlog(L_ERR, "rlm_ldap: All ldap connections are in use"); + return RLM_MODULE_FAIL; + } + + /* + * If there is an existing LDAP connection to the directory, bind over + * it. Otherwise, establish a new connection. + */ +postauth_reconnect: + if (!conn->bound || conn->ld == NULL) { + DEBUG2("rlm_ldap: attempting LDAP reconnection"); + if (conn->ld){ + DEBUG2("rlm_ldap: closing existing LDAP connection"); + ldap_unbind_s(conn->ld); + } + if ((conn->ld = ldap_connect(instance, (char *)vp_fdn->strvalue, password, 0, &res, &error_msg)) == NULL) { + radlog(L_ERR, "rlm_ldap: eDirectory account policy check failed."); + + if(error_msg != NULL){ + DEBUG("rlm_ldap: %s", error_msg); + pairadd(&request->reply->vps, pairmake("Reply-Message", error_msg, T_OP_EQ)); + ldap_memfree((void *)error_msg); + } + + vp_apc->strvalue[0] = '3'; + ldap_release_conn(conn_id, inst->apc_conns); + return RLM_MODULE_REJECT; + } + conn->bound = 1; + }else if((err = ldap_simple_bind_s(conn->ld, (char *)vp_fdn->strvalue, password)) != LDAP_SUCCESS){ + if(err == LDAP_SERVER_DOWN){ + conn->bound = 0; + goto postauth_reconnect; + } + DEBUG("rlm_ldap: eDirectory account policy check failed."); + ldap_get_option(conn->ld, LDAP_OPT_ERROR_STRING, &error_msg); + if(error_msg != NULL){ + DEBUG("rlm_ldap: %s", error_msg); + pairadd(&request->reply->vps, pairmake("Reply-Message", error_msg, T_OP_EQ)); + ldap_memfree((void *)error_msg); + } + vp_apc->strvalue[0] = '3'; + ldap_release_conn(conn_id, inst->apc_conns); + return RLM_MODULE_REJECT; + } + vp_apc->strvalue[0] = '3'; + ldap_release_conn(conn_id, inst->apc_conns); + return RLM_MODULE_OK; + } + } + return res; +} +#endif + static LDAP * -ldap_connect(void *instance, const char *dn, const char *password, int auth, int *result) +ldap_connect(void *instance, const char *dn, const char *password, int auth, int *result, char **err) { ldap_instance *inst = instance; LDAP *ld = NULL; @@ -1677,6 +1965,9 @@ ldap_connect(void *instance, const char *dn, const char *password, int auth, int msgid = ldap_bind(ld, dn, password,LDAP_AUTH_SIMPLE); if (msgid == -1) { ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ldap_errno); + if(err != NULL){ + ldap_get_option(ld, LDAP_OPT_ERROR_STRING, err); + } if (inst->is_url) { radlog(L_ERR, "rlm_ldap: %s bind to %s failed: %s", dn, inst->server, ldap_err2string(ldap_errno)); @@ -1696,6 +1987,9 @@ ldap_connect(void *instance, const char *dn, const char *password, int auth, int if (rc < 1) { DEBUG("rlm_ldap: ldap_result()"); ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &ldap_errno); + if(err != NULL){ + ldap_get_option(ld, LDAP_OPT_ERROR_STRING, err); + } if (inst->is_url) { radlog(L_ERR, "rlm_ldap: %s bind to %s failed: %s", dn, inst->server, (rc == 0) ? "timeout" : ldap_err2string(ldap_errno)); @@ -1724,6 +2018,9 @@ ldap_connect(void *instance, const char *dn, const char *password, int auth, int radlog(L_ERR, "rlm_ldap: LDAP login failed: check identity, password settings in ldap section of radiusd.conf"); *result = RLM_MODULE_FAIL; } + if(err != NULL){ + ldap_get_option(ld, LDAP_OPT_ERROR_STRING, err); + } break; default: @@ -1736,6 +2033,9 @@ ldap_connect(void *instance, const char *dn, const char *password, int auth, int ldap_err2string(ldap_errno)); } *result = RLM_MODULE_FAIL; + if(err != NULL){ + ldap_get_option(ld, LDAP_OPT_ERROR_STRING, err); + } } if (*result != RLM_MODULE_OK) { @@ -1797,6 +2097,20 @@ ldap_detach(void *instance) free(inst->conns); } +#ifdef NOVELL + if (inst->apc_conns){ + int i; + + for(i = 0; i < inst->num_conns; i++){ + if (inst->apc_conns[i].ld){ + ldap_unbind_s(inst->apc_conns[i].ld); + } + pthread_mutex_destroy(&inst->apc_conns[i].mutex); + } + free(inst->apc_conns); + } +#endif + pair = inst->check_item_map; while (pair != NULL) { @@ -1963,7 +2277,11 @@ module_t rlm_ldap = { NULL, /* checksimul */ NULL, /* pre-proxy */ NULL, /* post-proxy */ - NULL /* post-auth */ +#ifdef NOVELL + ldap_postauth /* post-auth */ +#else + NULL +#endif }, ldap_detach, /* detach */ NULL, /* destroy */ -- 2.1.4