From 329532b3ad43642d50815d089fa3e75746a694d8 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 13 Aug 2019 21:51:44 +0200 Subject: [PATCH 16/23] Backport of request and parse password policy controls when doing user authentication in nslcd --- compat/ldap_compat.h | 14 +++ configure.ac | 2 + nslcd/myldap.c | 217 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 224 insertions(+), 9 deletions(-) diff --git a/compat/ldap_compat.h b/compat/ldap_compat.h index 039932c..bca8e35 100644 --- a/compat/ldap_compat.h +++ b/compat/ldap_compat.h @@ -70,4 +70,18 @@ int ldap_passwd_s(LDAP *ld,struct berval *user,struct berval *oldpw, #endif /* LDAP_OPT_ERROR_STRING */ #endif /* not LDAP_OPT_DIAGNOSTIC_MESSAGE */ +/* provide replacement oid definitions */ +#ifndef LDAP_CONTROL_PWEXPIRED +#define LDAP_CONTROL_PWEXPIRED "2.16.840.1.113730.3.4.4" +#endif /* LDAP_CONTROL_PWEXPIRED */ +#ifndef LDAP_CONTROL_PWEXPIRING +#define LDAP_CONTROL_PWEXPIRING "2.16.840.1.113730.3.4.5" +#endif /* LDAP_CONTROL_PWEXPIRING */ +#ifndef LDAP_CONTROL_PASSWORDPOLICYREQUEST +#define LDAP_CONTROL_PASSWORDPOLICYREQUEST "1.3.6.1.4.1.42.2.27.8.5.1" +#endif /* LDAP_CONTROL_PASSWORDPOLICYREQUEST */ +#ifndef LDAP_CONTROL_PASSWORDPOLICYRESPONSE +#define LDAP_CONTROL_PASSWORDPOLICYRESPONSE "1.3.6.1.4.1.42.2.27.8.5.1" +#endif /* LDAP_CONTROL_PASSWORDPOLICYRESPONSE */ + #endif /* COMPAT__LDAP_COMPAT_H */ diff --git a/configure.ac b/configure.ac index bdb2792..344fa55 100644 --- a/configure.ac +++ b/configure.ac @@ -697,6 +697,8 @@ then AC_CHECK_FUNCS(ldap_parse_result ldap_memfree ldap_controls_free ldap_control_free) AC_CHECK_FUNCS(ldap_explode_dn ldap_explode_rdn ldap_set_option ldap_get_option) AC_CHECK_FUNCS(ldap_abandon ldap_simple_bind_s ldap_unbind ldap_set_rebind_proc) + AC_CHECK_FUNCS(ldap_sasl_bind ldap_sasl_bind_s ldap_control_find) + AC_CHECK_FUNCS(ldap_parse_passwordpolicy_control ldap_passwordpolicy_err2txt) AC_CHECK_FUNCS(ldap_initialize ldap_search_ext ldap_start_tls_s) AC_CHECK_FUNCS(ldap_create_control ldap_extended_operation_s) AC_CHECK_FUNCS(ldap_domain2hostlist ldap_domain2dn) diff --git a/nslcd/myldap.c b/nslcd/myldap.c index 895b682..64b7f13 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -85,16 +85,20 @@ struct ldap_session { /* the connection */ LDAP *ld; - /* the username to bind with */ - char binddn[256]; - /* the password to bind with if any */ - char bindpw[128]; /* timestamp of last activity */ time_t lastactivity; /* index into ldc_uris: currently connected LDAP uri */ int current_uri; /* a list of searches registered with this session */ struct myldap_search *searches[MAX_SEARCHES_IN_SESSION]; + /* the username to bind with */ + char binddn[256]; + /* the password to bind with if any */ + char bindpw[128]; + /* the authentication result (NSLCD_PAM_* code) */ + int policy_response; + /* the authentication message */ + char policy_message[1024]; }; /* A search description set as returned by myldap_search(). */ @@ -307,12 +311,14 @@ static MYLDAP_SESSION *myldap_session_new(void) } /* initialize the session */ session->ld=NULL; - session->binddn[0]='\0'; - session->bindpw[0]='\0'; session->lastactivity=0; session->current_uri=0; for (i=0;isearches[i]=NULL; + session->binddn[0]='\0'; + session->bindpw[0]='\0'; + session->policy_response = NSLCD_PAM_SUCCESS; + session->policy_message[0] = '\0'; /* return the new session */ return session; } @@ -398,6 +404,195 @@ static int do_sasl_interact(LDAP UNUSED(*ld),unsigned UNUSED(flags),void *defaul } #endif /* HAVE_SASL_INTERACT_T */ +#if defined(HAVE_LDAP_SASL_BIND) && defined(LDAP_SASL_SIMPLE) +static void handle_ppasswd_controls(MYLDAP_SESSION *session, LDAP *ld, LDAPControl **ctrls) +{ + int i; + int rc; + /* clear policy response information in session */ + session->policy_response = NSLCD_PAM_SUCCESS; + strncpy(session->policy_message, "", sizeof(session->policy_message)); + for (i = 0; ctrls[i] != NULL; i++) + { + if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRED) == 0) + { + /* check for expired control: force the user to change their password */ + log_log(LOG_DEBUG, "got LDAP_CONTROL_PWEXPIRED (password expired, user should change)"); + if (session->policy_response == NSLCD_PAM_SUCCESS) + session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD; + } + else if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PWEXPIRING) == 0) + { + /* check for password expiration warning control: the password is about + to expire (returns the number of seconds remaining until the password + expires) */ + char seconds[32]; + long int sec; + mysnprintf(seconds, sizeof(seconds), "%.*s", (int)ctrls[i]->ldctl_value.bv_len, + ctrls[i]->ldctl_value.bv_val); + sec = atol(seconds); + log_log(LOG_DEBUG, "got LDAP_CONTROL_PWEXPIRING (password will expire in %ld seconds)", + sec); + /* return this warning to PAM */ + if (session->policy_response == NSLCD_PAM_SUCCESS) + { + session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "password will expire in %ld seconds", sec); + } + } + else if (strcmp(ctrls[i]->ldctl_oid, LDAP_CONTROL_PASSWORDPOLICYRESPONSE) == 0) + { + /* check for password policy control */ + int expire = 0, grace = 0; + LDAPPasswordPolicyError error = -1; + rc = ldap_parse_passwordpolicy_control(ld, ctrls[i], &expire, &grace, &error); + if (rc != LDAP_SUCCESS) + myldap_err(LOG_WARNING, ld, rc, "ldap_parse_passwordpolicy_control() failed (ignored)"); + else + { + /* log returned control information */ + if (error != PP_noError) + log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (%s)", + ldap_passwordpolicy_err2txt(error)); + if (expire >= 0) + log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (password will expire in %d seconds)", + expire); + if (grace >= 0) + log_log(LOG_DEBUG, "got LDAP_CONTROL_PASSWORDPOLICYRESPONSE (%d grace logins left)", + grace); + /* return this information to PAM */ + if ((error == PP_passwordExpired) && + ((session->policy_response == NSLCD_PAM_SUCCESS) || + (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD))) + { + session->policy_response = NSLCD_PAM_AUTHTOK_EXPIRED; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "%s", ldap_passwordpolicy_err2txt(error)); + } + else if ((error == PP_accountLocked) && + ((session->policy_response == NSLCD_PAM_SUCCESS) || + (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD))) + { + session->policy_response = NSLCD_PAM_ACCT_EXPIRED; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "%s", ldap_passwordpolicy_err2txt(error)); + } + else if ((error == PP_changeAfterReset) && + (session->policy_response == NSLCD_PAM_SUCCESS)) + { + session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "%s", ldap_passwordpolicy_err2txt(error)); + } + else if ((error != PP_noError) && + ((session->policy_response == NSLCD_PAM_SUCCESS) || + (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD))) + { + session->policy_response = NSLCD_PAM_PERM_DENIED; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "%s", ldap_passwordpolicy_err2txt(error)); + } + else if ((expire >= 0) && + ((session->policy_response == NSLCD_PAM_SUCCESS) || + (session->policy_response == NSLCD_PAM_NEW_AUTHTOK_REQD))) + { + session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "Password will expire in %d seconds", expire); + } + else if ((grace >= 0) && + (session->policy_response == NSLCD_PAM_SUCCESS)) + { + session->policy_response = NSLCD_PAM_NEW_AUTHTOK_REQD; + mysnprintf(session->policy_message, sizeof(session->policy_message), + "Password expired, %d grace logins left", grace); + } + } + } + /* ignore any other controls */ + } +} +static int do_ppolicy_bind(MYLDAP_SESSION *session, LDAP *ld, const char *uri) +{ + int rc, parserc; + struct berval cred; + LDAPControl passwd_policy_req; + LDAPControl *requestctrls[2]; + LDAPControl **responsectrls; + int msgid; + struct timeval timeout; + LDAPMessage *result; + /* build password policy request control */ + passwd_policy_req.ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST; + passwd_policy_req.ldctl_value.bv_val = NULL; /* none */ + passwd_policy_req.ldctl_value.bv_len = 0; + passwd_policy_req.ldctl_iscritical = 0; /* not critical */ + requestctrls[0] = &passwd_policy_req; + requestctrls[1] = NULL; + /* build password berval */ + cred.bv_val = (char *)session->bindpw; + cred.bv_len = (session->bindpw == NULL) ? 0 : strlen(session->bindpw); + /* do a SASL simple bind with the binddn and bindpw */ + log_log(LOG_DEBUG, "ldap_sasl_bind(\"%s\",%s) (uri=\"%s\")", session->binddn, + ((session->bindpw != NULL) && (session->bindpw[0] != '\0')) ? "\"***\"" : "\"\"", uri); + rc = ldap_sasl_bind(ld, session->binddn, LDAP_SASL_SIMPLE, &cred, requestctrls, NULL, &msgid); + if (rc != LDAP_SUCCESS) + return rc; + if (msgid == -1) + { + myldap_err(LOG_WARNING, ld, rc,"ldap_sasl_bind() failed (msgid=-1, uri=%s)", uri); + return LDAP_OPERATIONS_ERROR; + } + /* get the result from the bind operation */ + timeout.tv_sec = nslcd_cfg->ldc_bind_timelimit; + timeout.tv_usec = 0; + result = NULL; + rc = ldap_result(ld, msgid, LDAP_MSG_ALL, &timeout, &result); + if (rc == -1) /* some error */ + { + if (ldap_get_option(ld, LDAP_OPT_ERROR_NUMBER, &rc) != LDAP_SUCCESS) + rc = LDAP_UNAVAILABLE; + myldap_err(LOG_ERR, ld, rc, "ldap_result() failed"); + if (result != NULL) + ldap_msgfree(result); + return LDAP_LOCAL_ERROR; + } + if (rc == 0) /* the timeout expired */ + { + log_log(LOG_ERR, "ldap_result() timed out"); + if (result != NULL) + ldap_msgfree(result); + return LDAP_TIMEOUT; + } + /* parse the result from the bind operation (frees result, get controls) */ + responsectrls = NULL; + parserc = ldap_parse_result(ld, result, &rc, NULL, NULL, NULL, &responsectrls, 1); + if (parserc != LDAP_SUCCESS) + { + myldap_err(LOG_ERR, ld, parserc, "ldap_parse_result() failed"); + if (responsectrls != NULL) + ldap_controls_free(responsectrls); + return parserc; + } + if (rc != LDAP_SUCCESS) + { + myldap_err(LOG_ERR, ld, rc, "ldap_parse_result() failed"); + if (responsectrls != NULL) + ldap_controls_free(responsectrls); + return rc; + } + /* check the returned controls */ + if (responsectrls != NULL) + { + handle_ppasswd_controls(session, ld, responsectrls); + /* free controls */ + ldap_controls_free(responsectrls); + } + return LDAP_SUCCESS; +} +#endif /* no SASL, so no ppolicy */ + #define LDAP_SET_OPTION(ld,option,invalue) \ rc=ldap_set_option(ld,option,invalue); \ if (rc!=LDAP_SUCCESS) \ @@ -410,7 +605,7 @@ static int do_sasl_interact(LDAP UNUSED(*ld),unsigned UNUSED(flags),void *defaul The binddn and bindpw parameters may be used to override the authentication mechanism defined in the configuration. This returns an LDAP result code. */ -static int do_bind(LDAP *ld,const char *binddn,const char *bindpw,const char *uri) +static int do_bind(MYLDAP_SESSION *session, LDAP *ld,const char *binddn,const char *bindpw,const char *uri) { int rc; #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S @@ -435,10 +630,14 @@ static int do_bind(LDAP *ld,const char *binddn,const char *bindpw,const char *ur /* check if the binddn and bindpw are overwritten in the session */ if ((binddn!=NULL)&&(binddn[0]!='\0')) { +#if defined(HAVE_LDAP_SASL_BIND) && defined(LDAP_SASL_SIMPLE) + return do_ppolicy_bind(session, ld, uri); +#else /* no SASL, so no ppolicy */ /* do a simple bind */ log_log(LOG_DEBUG,"ldap_simple_bind_s(\"%s\",%s) (uri=\"%s\")",binddn, ((bindpw!=NULL)&&(bindpw[0]!='\0'))?"\"***\"":"\"\"",uri); return ldap_simple_bind_s(ld,binddn,bindpw); +#endif } /* perform SASL bind if requested and available on platform */ #ifdef HAVE_LDAP_SASL_INTERACTIVE_BIND_S @@ -504,7 +703,7 @@ static int do_rebind(LDAP *ld,LDAP_CONST char *url, { MYLDAP_SESSION *session=(MYLDAP_SESSION *)arg; log_log(LOG_DEBUG,"rebinding to %s",url); - return do_bind(ld,session->binddn,session->bindpw,url); + return do_bind(session,ld,session->binddn,session->bindpw,url); } #else /* not recent OpenLDAP */ static int do_rebind(LDAP *ld,char **dnp,char **passwdp,int *authmethodp, @@ -798,7 +997,7 @@ static int do_open(MYLDAP_SESSION *session) } /* bind to the server */ errno=0; - rc=do_bind(session->ld,session->binddn,session->bindpw, + rc=do_bind(session, session->ld,session->binddn,session->bindpw, nslcd_cfg->ldc_uris[session->current_uri].uri); if (rc!=LDAP_SUCCESS) { -- 2.20.1