Blame SOURCES/0016-Backport-of-request-and-parse-password-policy-contro.patch

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