summaryrefslogtreecommitdiff
path: root/nslcd/db_pam.c
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2014-10-04 16:13:03 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2014-10-04 16:13:03 -0400
commitc9618dfe442305531ee6cab9660333f4a697e094 (patch)
tree63da3cf1c107fdebd82987519b858f0d98c12d23 /nslcd/db_pam.c
parentbe4588009b7106859e1beae6038aaea8d7f85825 (diff)
foo
Diffstat (limited to 'nslcd/db_pam.c')
-rw-r--r--nslcd/db_pam.c847
1 files changed, 847 insertions, 0 deletions
diff --git a/nslcd/db_pam.c b/nslcd/db_pam.c
new file mode 100644
index 0000000..0eff71b
--- /dev/null
+++ b/nslcd/db_pam.c
@@ -0,0 +1,847 @@
+/*
+ pam.c - pam processing routines
+
+ Copyright (C) 2009 Howard Chu
+ Copyright (C) 2009-2014 Arthur de Jong
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif /* HAVE_STDINT_H */
+#include <unistd.h>
+#include <time.h>
+
+#include "common.h"
+#include "log.h"
+#include "myldap.h"
+#include "cfg.h"
+#include "attmap.h"
+#include "common/dict.h"
+#include "common/expr.h"
+
+/* set up a connection and try to bind with the specified DN and password,
+ returns an LDAP result code */
+static int try_bind(const char *userdn, const char *password,
+ int *authzrc, char *authzmsg, size_t authzmsgsz)
+{
+ MYLDAP_SESSION *session;
+ MYLDAP_SEARCH *search;
+ MYLDAP_ENTRY *entry;
+ static const char *attrs[2];
+ int rc;
+ const char *msg;
+ /* set up a new connection */
+ session = myldap_create_session();
+ if (session == NULL)
+ return LDAP_UNAVAILABLE;
+ /* set up credentials for the session */
+ if (myldap_set_credentials(session, userdn, password))
+ {
+ myldap_session_close(session);
+ return LDAP_LOCAL_ERROR;
+ }
+ /* perform search for own object (just to do any kind of search) */
+ attrs[0] = "dn";
+ attrs[1] = NULL;
+ search = myldap_search(session, userdn, LDAP_SCOPE_BASE,
+ "(objectClass=*)", attrs, &rc);
+ if ((search == NULL) || (rc != LDAP_SUCCESS))
+ {
+ if (rc == LDAP_SUCCESS)
+ rc = LDAP_LOCAL_ERROR;
+ log_log(LOG_WARNING, "%s: %s", userdn, ldap_err2string(rc));
+ }
+ else
+ {
+ entry = myldap_get_entry(search, &rc);
+ if ((entry == NULL) || (rc != LDAP_SUCCESS))
+ {
+ if (rc == LDAP_SUCCESS)
+ rc = LDAP_NO_RESULTS_RETURNED;
+ log_log(LOG_WARNING, "%s: %s", userdn, ldap_err2string(rc));
+ }
+ }
+ /* get any policy response from the bind */
+ myldap_get_policy_response(session, authzrc, &msg);
+ if ((msg != NULL) && (msg[0] != '\0'))
+ {
+ mysnprintf(authzmsg, authzmsgsz - 1, "%s", msg);
+ log_log(LOG_WARNING, "%s: %s", userdn, authzmsg);
+ }
+ /* close the session */
+ myldap_session_close(session);
+ /* return results */
+ return rc;
+}
+
+/* ensure that both userdn and username are filled in from the entry,
+ returns an LDAP result code */
+static MYLDAP_ENTRY *validate_user(MYLDAP_SESSION *session,
+ char *username, int *rcp)
+{
+ int rc;
+ MYLDAP_ENTRY *entry = NULL;
+ /* check username for validity */
+ if (!isvalidname(username))
+ {
+ log_log(LOG_WARNING, "request denied by validnames option");
+ *rcp = LDAP_NO_SUCH_OBJECT;
+ return NULL;
+ }
+ /* get the user entry based on the username */
+ entry = uid2entry(session, username, &rc);
+ if (entry == NULL)
+ {
+ if (rc == LDAP_SUCCESS)
+ rc = LDAP_NO_SUCH_OBJECT;
+ log_log(LOG_DEBUG, "\"%s\": user not found: %s", username, ldap_err2string(rc));
+ *rcp = rc;
+ }
+ return entry;
+}
+
+/* update the username value from the entry if needed */
+static void update_username(MYLDAP_ENTRY *entry, char *username,
+ size_t username_len)
+{
+ const char **values;
+ const char *value;
+ /* get the "real" username */
+ value = myldap_get_rdn_value(entry, attmap_passwd_uid);
+ if (value == NULL)
+ {
+ /* get the username from the uid attribute */
+ values = myldap_get_values(entry, attmap_passwd_uid);
+ if ((values == NULL) || (values[0] == NULL))
+ {
+ log_log(LOG_WARNING, "%s: %s: missing",
+ myldap_get_dn(entry), attmap_passwd_uid);
+ return;
+ }
+ value = values[0];
+ }
+ /* check the username */
+ if ((value == NULL) || !isvalidname(value) || strlen(value) >= username_len)
+ {
+ log_log(LOG_WARNING, "%s: %s: denied by validnames option",
+ myldap_get_dn(entry), attmap_passwd_uid);
+ return;
+ }
+ /* check if the username is different and update it if needed */
+ if (strcmp(username, value) != 0)
+ {
+ log_log(LOG_INFO, "username changed from \"%s\" to \"%s\"",
+ username, value);
+ strcpy(username, value);
+ }
+}
+
+static int check_shadow(MYLDAP_SESSION *session, const char *username,
+ char *authzmsg, size_t authzmsgsz,
+ int check_maxdays, int check_mindays)
+{
+ MYLDAP_ENTRY *entry = NULL;
+ long today, lastchangedate, mindays, maxdays, warndays, inactdays, expiredate;
+ unsigned long flag;
+ long daysleft, inactleft;
+ /* get the shadow entry */
+ entry = shadow_uid2entry(session, username, NULL);
+ if (entry == NULL)
+ return NSLCD_PAM_SUCCESS; /* no shadow entry found, nothing to check */
+ /* get today's date */
+ today = (long)(time(NULL) / (60 * 60 * 24));
+ /* get shadow information */
+ get_shadow_properties(entry, &lastchangedate, &mindays, &maxdays, &warndays,
+ &inactdays, &expiredate, &flag);
+ /* check account expiry date */
+ if ((expiredate != -1) && (today >= expiredate))
+ {
+ daysleft = today - expiredate;
+ mysnprintf(authzmsg, authzmsgsz - 1, "account expired %ld days ago",
+ daysleft);
+ log_log(LOG_WARNING, "%s: %s: %s",
+ myldap_get_dn(entry), attmap_shadow_shadowExpire, authzmsg);
+ return NSLCD_PAM_ACCT_EXPIRED;
+ }
+ /* password expiration isn't interesting at this point because the user
+ may not have authenticated with a password and if he did that would be
+ checked in the authc phase */
+ if (check_maxdays)
+ {
+ /* check lastchanged */
+ if (lastchangedate == 0)
+ {
+ mysnprintf(authzmsg, authzmsgsz - 1, "need a new password");
+ log_log(LOG_WARNING, "%s: %s: %s",
+ myldap_get_dn(entry), attmap_shadow_shadowLastChange, authzmsg);
+ return NSLCD_PAM_NEW_AUTHTOK_REQD;
+ }
+ else if (today < lastchangedate)
+ log_log(LOG_WARNING, "%s: %s: password changed in the future",
+ myldap_get_dn(entry), attmap_shadow_shadowLastChange);
+ else if (maxdays != -1)
+ {
+ /* check maxdays */
+ daysleft = lastchangedate + maxdays - today;
+ if (daysleft == 0)
+ mysnprintf(authzmsg, authzmsgsz - 1, "password will expire today");
+ else if (daysleft < 0)
+ mysnprintf(authzmsg, authzmsgsz - 1, "password expired %ld days ago",
+ -daysleft);
+ /* check inactdays */
+ if ((daysleft <= 0) && (inactdays != -1))
+ {
+ inactleft = lastchangedate + maxdays + inactdays - today;
+ if (inactleft == 0)
+ mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
+ ", account will be locked today");
+ else if (inactleft > 0)
+ mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
+ ", account will be locked in %ld days", inactleft);
+ else
+ {
+ mysnprintf(authzmsg + strlen(authzmsg), authzmsgsz - strlen(authzmsg) - 1,
+ ", account locked %ld days ago", -inactleft);
+ log_log(LOG_WARNING, "%s: %s: %s", myldap_get_dn(entry),
+ attmap_shadow_shadowInactive, authzmsg);
+ return NSLCD_PAM_AUTHTOK_EXPIRED;
+ }
+ }
+ if (daysleft <= 0)
+ {
+ /* log previously built message */
+ log_log(LOG_WARNING, "%s: %s: %s",
+ myldap_get_dn(entry), attmap_shadow_shadowMax, authzmsg);
+ return NSLCD_PAM_NEW_AUTHTOK_REQD;
+ }
+ /* check warndays */
+ if ((warndays > 0) && (daysleft <= warndays))
+ {
+ mysnprintf(authzmsg, authzmsgsz - 1,
+ "password will expire in %ld days", daysleft);
+ log_log(LOG_WARNING, "%s: %s: %s",
+ myldap_get_dn(entry), attmap_shadow_shadowWarning, authzmsg);
+ }
+ }
+ }
+ if (check_mindays)
+ {
+ daysleft = lastchangedate + mindays - today;
+ if ((mindays != -1) && (daysleft > 0))
+ {
+ mysnprintf(authzmsg, authzmsgsz - 1,
+ "password cannot be changed for another %ld days", daysleft);
+ log_log(LOG_WARNING, "%s: %s: %s",
+ myldap_get_dn(entry), attmap_shadow_shadowMin, authzmsg);
+ return NSLCD_PAM_AUTHTOK_ERR;
+ }
+ }
+ return NSLCD_PAM_SUCCESS;
+}
+
+/* check authentication credentials of the user */
+int nslcd_pam_authc(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
+{
+ int32_t tmpint32;
+ int rc;
+ char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ char password[BUFLEN_PASSWORD];
+ const char *userdn;
+ MYLDAP_ENTRY *entry;
+ int authzrc = NSLCD_PAM_SUCCESS;
+ char authzmsg[BUFLEN_MESSAGE];
+ authzmsg[0] = '\0';
+ /* read request parameters */
+ READ_STRING(fp, username);
+ READ_STRING(fp, service);
+ READ_STRING(fp, ruser);
+ READ_STRING(fp, rhost);
+ READ_STRING(fp, tty);
+ READ_STRING(fp, password);
+ /* log call */
+ log_setrequest("authc=\"%s\"", username);
+ log_log(LOG_DEBUG, "nslcd_pam_authc(\"%s\",\"%s\",\"%s\")",
+ username, service, *password ? "***" : "");
+ /* write the response header */
+ WRITE_INT32(fp, NSLCD_VERSION);
+ WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHC);
+ /* if the username is blank and rootpwmoddn is configured, try to
+ authenticate as administrator, otherwise validate request as usual */
+ if (*username == '\0')
+ {
+ if (nslcd_cfg->rootpwmoddn == NULL)
+ {
+ log_log(LOG_NOTICE, "rootpwmoddn not configured");
+ /* we break the protocol */
+ memset(password, 0, sizeof(password));
+ return -1;
+ }
+ userdn = nslcd_cfg->rootpwmoddn;
+ /* if the caller is root we will allow the use of the rootpwmodpw option */
+ if ((*password == '\0') && (calleruid == 0) && (nslcd_cfg->rootpwmodpw != NULL))
+ {
+ if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password))
+ {
+ log_log(LOG_ERR, "nslcd_pam_authc(): rootpwmodpw will not fit in password");
+ memset(password, 0, sizeof(password));
+ return -1;
+ }
+ strcpy(password, nslcd_cfg->rootpwmodpw);
+ }
+ }
+ else
+ {
+ /* try normal authentication, lookup the user entry */
+ entry = validate_user(session, username, &rc);
+ if (entry == NULL)
+ {
+ /* for user not found we just say no result */
+ if (rc == LDAP_NO_SUCH_OBJECT)
+ {
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ }
+ memset(password, 0, sizeof(password));
+ return -1;
+ }
+ userdn = myldap_get_dn(entry);
+ update_username(entry, username, sizeof(username));
+ }
+ /* try authentication */
+ rc = try_bind(userdn, password, &authzrc, authzmsg, sizeof(authzmsg));
+ if (rc == LDAP_SUCCESS)
+ log_log(LOG_DEBUG, "bind successful");
+ /* map result code */
+ switch (rc)
+ {
+ case LDAP_SUCCESS: rc = NSLCD_PAM_SUCCESS; break;
+ case LDAP_INVALID_CREDENTIALS: rc = NSLCD_PAM_AUTH_ERR; break;
+ default: rc = NSLCD_PAM_AUTH_ERR;
+ }
+ /* perform shadow attribute checks */
+ if ((*username != '\0') && (authzrc == NSLCD_PAM_SUCCESS))
+ authzrc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 1, 0);
+ /* write response */
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, rc);
+ WRITE_STRING(fp, username);
+ WRITE_INT32(fp, authzrc);
+ WRITE_STRING(fp, authzmsg);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ memset(password, 0, sizeof(password));
+ return 0;
+}
+
+static void autzsearch_var_add(DICT *dict, const char *name,
+ const char *value)
+{
+ size_t sz;
+ char *escaped_value;
+ /* allocate memory for escaped string */
+ sz = ((strlen(value) + 8) * 120) / 100;
+ escaped_value = (char *)malloc(sz);
+ if (escaped_value == NULL)
+ {
+ log_log(LOG_CRIT, "autzsearch_var_add(): malloc() failed to allocate memory");
+ return;
+ }
+ /* perform escaping of the value */
+ if (myldap_escape(value, escaped_value, sz))
+ {
+ log_log(LOG_ERR, "autzsearch_var_add(): escaped_value buffer too small");
+ free(escaped_value);
+ return;
+ }
+ /* add to dict */
+ dict_put(dict, name, escaped_value);
+}
+
+static void autzsearch_vars_free(DICT *dict)
+{
+ int i;
+ const char **keys;
+ void *value;
+ /* go over all keys and free all the values
+ (they were allocated in autzsearch_var_add) */
+ /* loop over dictionary contents */
+ keys = dict_keys(dict);
+ for (i = 0; keys[i] != NULL; i++)
+ {
+ value = dict_get(dict, keys[i]);
+ if (value)
+ free(value);
+ }
+ free(keys);
+ /* after this values from the dict should obviously no longer be used */
+}
+
+static const char *autzsearch_var_get(const char *name, void *expander_attr)
+{
+ DICT *dict = (DICT *)expander_attr;
+ return (const char *)dict_get(dict, name);
+ /* TODO: if not set use entry to get attribute name (entry can be an
+ element in the dict) */
+}
+
+/* search all search bases using the provided filter */
+static int do_autzsearches(MYLDAP_SESSION *session, const char *filter)
+{
+ int i;
+ int rc;
+ const char *base;
+ static const char *attrs[2];
+ MYLDAP_SEARCH *search;
+ MYLDAP_ENTRY *entry;
+ /* prepare the search */
+ attrs[0] = "dn";
+ attrs[1] = NULL;
+ /* perform a search for each search base */
+ log_log(LOG_DEBUG, "trying pam_authz_search \"%s\"", filter);
+ for (i = 0; (base = nslcd_cfg->bases[i]) != NULL; i++)
+ {
+ /* do the LDAP search */
+ search = myldap_search(session, base, LDAP_SCOPE_SUBTREE, filter, attrs, &rc);
+ if (search == NULL)
+ {
+ log_log(LOG_ERR, "pam_authz_search \"%s\" failed: %s",
+ filter, ldap_err2string(rc));
+ return rc;
+ }
+ /* try to get an entry */
+ entry = myldap_get_entry(search, &rc);
+ if (entry != NULL)
+ {
+ log_log(LOG_DEBUG, "pam_authz_search found \"%s\"", myldap_get_dn(entry));
+ return LDAP_SUCCESS;
+ }
+ }
+ log_log(LOG_ERR, "pam_authz_search \"%s\" found no matches", filter);
+ if (rc == LDAP_SUCCESS)
+ rc = LDAP_NO_SUCH_OBJECT;
+ return rc;
+}
+
+/* perform an authorisation search, returns an LDAP status code */
+static int try_autzsearch(MYLDAP_SESSION *session, const char *dn,
+ const char *username, const char *servicename,
+ const char *ruser, const char *rhost,
+ const char *tty)
+{
+ char hostname[BUFLEN_HOSTNAME];
+ const char *fqdn;
+ DICT *dict = NULL;
+ char filter[BUFLEN_FILTER];
+ int rc = LDAP_SUCCESS;
+ const char *res;
+ int i;
+ /* go over all pam_authz_search options */
+ for (i = 0; (i < NSS_LDAP_CONFIG_MAX_AUTHZ_SEARCHES) && (nslcd_cfg->pam_authz_searches[i] != NULL); i++)
+ {
+ if (dict == NULL)
+ {
+ /* build the dictionary with variables
+ NOTE: any variables added here also need to be added to
+ cfg.c:parse_pam_authz_search_statement() */
+ dict = dict_new();
+ autzsearch_var_add(dict, "username", username);
+ autzsearch_var_add(dict, "service", servicename);
+ autzsearch_var_add(dict, "ruser", ruser);
+ autzsearch_var_add(dict, "rhost", rhost);
+ autzsearch_var_add(dict, "tty", tty);
+ if (gethostname(hostname, sizeof(hostname)) == 0)
+ autzsearch_var_add(dict, "hostname", hostname);
+ if ((fqdn = getfqdn()) != NULL)
+ autzsearch_var_add(dict, "fqdn", fqdn);
+ autzsearch_var_add(dict, "dn", dn);
+ autzsearch_var_add(dict, "uid", username);
+ }
+ /* build the search filter */
+ res = expr_parse(nslcd_cfg->pam_authz_searches[i],
+ filter, sizeof(filter),
+ autzsearch_var_get, (void *)dict);
+ if (res == NULL)
+ {
+ autzsearch_vars_free(dict);
+ dict_free(dict);
+ log_log(LOG_ERR, "invalid pam_authz_search \"%s\"",
+ nslcd_cfg->pam_authz_searches[i]);
+ return LDAP_LOCAL_ERROR;
+ }
+ /* perform the actual searches on all bases */
+ rc = do_autzsearches(session, filter);
+ if (rc != LDAP_SUCCESS)
+ break;
+ }
+ /* we went over all pam_authz_search entries */
+ if (dict != NULL)
+ {
+ autzsearch_vars_free(dict);
+ dict_free(dict);
+ }
+ return rc;
+}
+
+/* check authorisation of the user */
+int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session)
+{
+ int32_t tmpint32;
+ int rc;
+ char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ MYLDAP_ENTRY *entry;
+ char authzmsg[BUFLEN_MESSAGE];
+ authzmsg[0] = '\0';
+ /* read request parameters */
+ READ_STRING(fp, username);
+ READ_STRING(fp, service);
+ READ_STRING(fp, ruser);
+ READ_STRING(fp, rhost);
+ READ_STRING(fp, tty);
+ /* log call */
+ log_setrequest("authz=\"%s\"", username);
+ log_log(LOG_DEBUG, "nslcd_pam_authz(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\")",
+ username, service, ruser, rhost, tty);
+ /* write the response header */
+ WRITE_INT32(fp, NSLCD_VERSION);
+ WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHZ);
+ /* validate request */
+ entry = validate_user(session, username, &rc);
+ if (entry == NULL)
+ {
+ /* for user not found we just say no result */
+ if (rc == LDAP_NO_SUCH_OBJECT)
+ {
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ }
+ return -1;
+ }
+ /* check authorisation search */
+ rc = try_autzsearch(session, myldap_get_dn(entry), username, service, ruser,
+ rhost, tty);
+ if (rc != LDAP_SUCCESS)
+ {
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
+ WRITE_STRING(fp, "LDAP authorisation check failed");
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ return 0;
+ }
+ /* perform shadow attribute checks */
+ rc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 0, 0);
+ /* write response */
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, rc);
+ WRITE_STRING(fp, authzmsg);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ return 0;
+}
+
+int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
+{
+ int32_t tmpint32;
+ char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ char sessionid[25];
+ static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "01234567890";
+ unsigned int i;
+ /* read request parameters */
+ READ_STRING(fp, username);
+ READ_STRING(fp, service);
+ READ_STRING(fp, ruser);
+ READ_STRING(fp, rhost);
+ READ_STRING(fp, tty);
+ /* generate pseudo-random session id */
+ for (i = 0; i < (sizeof(sessionid) - 1); i++)
+ sessionid[i] = alphabet[rand() % (sizeof(alphabet) - 1)];
+ sessionid[i] = '\0';
+ /* log call */
+ log_setrequest("sess_o=\"%s\"", username);
+ log_log(LOG_DEBUG, "nslcd_pam_sess_o(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\"): %s",
+ username, service, tty, rhost, ruser, sessionid);
+ /* write the response header */
+ WRITE_INT32(fp, NSLCD_VERSION);
+ WRITE_INT32(fp, NSLCD_ACTION_PAM_SESS_O);
+ /* write response */
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_STRING(fp, sessionid);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ return 0;
+}
+
+int nslcd_pam_sess_c(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
+{
+ int32_t tmpint32;
+ char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ char sessionid[64];
+ /* read request parameters */
+ READ_STRING(fp, username);
+ READ_STRING(fp, service);
+ READ_STRING(fp, ruser);
+ READ_STRING(fp, rhost);
+ READ_STRING(fp, tty);
+ READ_STRING(fp, sessionid);
+ /* log call */
+ log_setrequest("sess_c=\"%s\"", username);
+ log_log(LOG_DEBUG, "nslcd_pam_sess_c(\"%s\",\"%s\",%s)",
+ username, service, sessionid);
+ /* write the response header */
+ WRITE_INT32(fp, NSLCD_VERSION);
+ WRITE_INT32(fp, NSLCD_ACTION_PAM_SESS_C);
+ /* write response */
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ return 0;
+}
+
+extern const char *shadow_filter;
+
+/* try to update the shadowLastChange attribute of the entry if possible */
+static int update_lastchange(MYLDAP_SESSION *session, const char *userdn)
+{
+ MYLDAP_SEARCH *search;
+ MYLDAP_ENTRY *entry;
+ static const char *attrs[3];
+ const char *attr;
+ int rc;
+ const char **values;
+ LDAPMod mod, *mods[2];
+ char buffer[64], *strvals[2];
+ /* find the name of the attribute to use */
+ if ((attmap_shadow_shadowLastChange == NULL) || (attmap_shadow_shadowLastChange[0] == '\0'))
+ return LDAP_LOCAL_ERROR; /* attribute not mapped at all */
+ else if (strcmp(attmap_shadow_shadowLastChange, "\"${shadowLastChange:--1}\"") == 0)
+ attr = "shadowLastChange";
+ else if (attmap_shadow_shadowLastChange[0] == '\"')
+ return LDAP_LOCAL_ERROR; /* other expressions not supported for now */
+ else
+ attr = attmap_shadow_shadowLastChange;
+ /* set up the attributes we need */
+ attrs[0] = attmap_shadow_uid;
+ attrs[1] = attr;
+ attrs[2] = NULL;
+ /* find the entry to see if the attribute is present */
+ search = myldap_search(session, userdn, LDAP_SCOPE_BASE, shadow_filter, attrs, &rc);
+ if (search == NULL)
+ return rc;
+ entry = myldap_get_entry(search, &rc);
+ if (entry == NULL)
+ return rc;
+ values = myldap_get_values(entry, attr);
+ if ((values == NULL) || (values[0] == NULL) || (values[0][0] == '\0'))
+ return LDAP_NO_SUCH_ATTRIBUTE;
+ /* build the value for the new attribute */
+ if (strcasecmp(attr, "pwdLastSet") == 0)
+ {
+ /* for AD we use another timestamp */
+ if (mysnprintf(buffer, sizeof(buffer), "%ld000000000",
+ ((long int)time(NULL) / 100L + (134774L * 864L))))
+ return LDAP_LOCAL_ERROR;
+ }
+ else
+ {
+ /* time in days since Jan 1, 1970 */
+ if (mysnprintf(buffer, sizeof(buffer), "%ld",
+ ((long int)(time(NULL) / (long int)(60 * 60 * 24)))))
+ return LDAP_LOCAL_ERROR;
+ }
+ /* update the shadowLastChange attribute */
+ strvals[0] = buffer;
+ strvals[1] = NULL;
+ mod.mod_op = LDAP_MOD_REPLACE;
+ mod.mod_type = (char *)attr;
+ mod.mod_values = strvals;
+ mods[0] = &mod;
+ mods[1] = NULL;
+ rc = myldap_modify(session, userdn, mods);
+ if (rc != LDAP_SUCCESS)
+ log_log(LOG_WARNING, "%s: %s: modification failed: %s",
+ userdn, attr, ldap_err2string(rc));
+ else
+ log_log(LOG_DEBUG, "%s: %s: modification succeeded", userdn, attr);
+ return rc;
+}
+
+/* perform an LDAP password modification, returns an LDAP status code */
+static int try_pwmod(MYLDAP_SESSION *oldsession,
+ const char *binddn, const char *userdn,
+ const char *oldpassword, const char *newpassword,
+ char *authzmsg, size_t authzmsg_len)
+{
+ MYLDAP_SESSION *session;
+ char buffer[BUFLEN_MESSAGE];
+ int rc;
+ /* set up a new connection */
+ session = myldap_create_session();
+ if (session == NULL)
+ return LDAP_UNAVAILABLE;
+ /* set up credentials for the session */
+ if (myldap_set_credentials(session, binddn, oldpassword))
+ {
+ myldap_session_close(session);
+ return LDAP_LOCAL_ERROR;
+ }
+ /* perform search for own object (just to do any kind of search) */
+ if ((lookup_dn2uid(session, userdn, &rc, buffer, sizeof(buffer)) != NULL) &&
+ (rc == LDAP_SUCCESS))
+ {
+ /* if doing password modification as admin, don't pass old password along */
+ if ((nslcd_cfg->rootpwmoddn != NULL) &&
+ (strcmp(binddn, nslcd_cfg->rootpwmoddn) == 0))
+ oldpassword = NULL;
+ /* perform password modification */
+ rc = myldap_passwd(session, userdn, oldpassword, newpassword);
+ if (rc == LDAP_SUCCESS)
+ {
+ /* try to update the shadowLastChange attribute */
+ if (update_lastchange(session, userdn) != LDAP_SUCCESS)
+ /* retry with the normal session */
+ (void)update_lastchange(oldsession, userdn);
+ }
+ else
+ {
+ /* get a diagnostic or error message */
+ if ((myldap_error_message(session, rc, buffer, sizeof(buffer)) == LDAP_SUCCESS) &&
+ (buffer[0] != '\0'))
+ mysnprintf(authzmsg, authzmsg_len - 1, "password change failed: %s",
+ buffer);
+ }
+ }
+ /* close the session */
+ myldap_session_close(session);
+ /* return */
+ return rc;
+}
+
+int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
+{
+ int32_t tmpint32;
+ int rc;
+ char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ int asroot;
+ char oldpassword[BUFLEN_PASSWORD];
+ char newpassword[BUFLEN_PASSWORD];
+ const char *binddn = NULL; /* the user performing the modification */
+ MYLDAP_ENTRY *entry;
+ char authzmsg[BUFLEN_MESSAGE];
+ authzmsg[0] = '\0';
+ /* read request parameters */
+ READ_STRING(fp, username);
+ READ_STRING(fp, service);
+ READ_STRING(fp, ruser);
+ READ_STRING(fp, rhost);
+ READ_STRING(fp, tty);
+ READ_INT32(fp, asroot);
+ READ_STRING(fp, oldpassword);
+ READ_STRING(fp, newpassword);
+ /* log call */
+ log_setrequest("pwmod=\"%s\"", username);
+ log_log(LOG_DEBUG, "nslcd_pam_pwmod(\"%s\",%s,\"%s\",\"%s\",\"%s\")",
+ username, asroot ? "asroot" : "asuser", service,
+ *oldpassword ? "***" : "", *newpassword ? "***" : "");
+ /* write the response header */
+ WRITE_INT32(fp, NSLCD_VERSION);
+ WRITE_INT32(fp, NSLCD_ACTION_PAM_PWMOD);
+ /* validate request */
+ entry = validate_user(session, username, &rc);
+ if (entry == NULL)
+ {
+ /* for user not found we just say no result */
+ if (rc == LDAP_NO_SUCH_OBJECT)
+ {
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ }
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return -1;
+ }
+ /* check if pam_password_prohibit_message is set */
+ if (nslcd_cfg->pam_password_prohibit_message != NULL)
+ {
+ log_log(LOG_NOTICE, "password change prohibited");
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
+ WRITE_STRING(fp, nslcd_cfg->pam_password_prohibit_message);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return 0;
+ }
+ /* check if the the user passed the rootpwmoddn */
+ if (asroot)
+ {
+ binddn = nslcd_cfg->rootpwmoddn;
+ /* check if rootpwmodpw should be used */
+ if ((*oldpassword == '\0') && (calleruid == 0) &&
+ (nslcd_cfg->rootpwmodpw != NULL))
+ {
+ if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(oldpassword))
+ {
+ log_log(LOG_ERR, "nslcd_pam_pwmod(): rootpwmodpw will not fit in oldpassword");
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return -1;
+ }
+ strcpy(oldpassword, nslcd_cfg->rootpwmodpw);
+ }
+ }
+ else
+ {
+ binddn = myldap_get_dn(entry);
+ /* check whether shadow properties allow password change */
+ rc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 0, 1);
+ if (rc != NSLCD_PAM_SUCCESS)
+ {
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, rc);
+ WRITE_STRING(fp, authzmsg);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return 0;
+ }
+ }
+ /* perform password modification */
+ rc = try_pwmod(session, binddn, myldap_get_dn(entry), oldpassword, newpassword,
+ authzmsg, sizeof(authzmsg));
+ if (rc != LDAP_SUCCESS)
+ {
+ if (authzmsg[0] == '\0')
+ mysnprintf(authzmsg, sizeof(authzmsg) - 1, "password change failed: %s",
+ ldap_err2string(rc));
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
+ WRITE_STRING(fp, authzmsg);
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return 0;
+ }
+ /* write response */
+ log_log(LOG_NOTICE, "password changed for %s", myldap_get_dn(entry));
+ WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
+ WRITE_INT32(fp, NSLCD_PAM_SUCCESS);
+ WRITE_STRING(fp, "");
+ WRITE_INT32(fp, NSLCD_RESULT_END);
+ memset(oldpassword, 0, sizeof(oldpassword));
+ memset(newpassword, 0, sizeof(newpassword));
+ return 0;
+}