From 329532b3ad43642d50815d089fa3e75746a694d8 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
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;i<MAX_SEARCHES_IN_SESSION;i++)
session->searches[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