/* pam.c - pam processing routines Copyright (C) 2009 Howard Chu Copyright (C) 2009, 2010, 2011, 2012, 2013 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 #include #include #ifdef HAVE_STDINT_H #include #endif /* HAVE_STDINT_H */ #include #include #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 */ myldap_set_credentials(session, userdn, password); /* 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: lookup failed: %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: lookup failed: %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[256], service[64], ruser[256], rhost[HOST_NAME_MAX + 1], tty[64]; char password[64]; const char *userdn; MYLDAP_ENTRY *entry; int authzrc = NSLCD_PAM_SUCCESS; char authzmsg[1024]; 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 */ 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"); 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); } 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); 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_CRIT, "autzsearch_var_add(): myldap_escape() failed to fit in buffer"); 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[HOST_NAME_MAX + 1]; const char *fqdn; DICT *dict = NULL; char filter[4096]; 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[256], service[64], ruser[256], rhost[HOST_NAME_MAX + 1], tty[64]; MYLDAP_ENTRY *entry; char authzmsg[1024]; 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 *session) { int32_t tmpint32; char username[256], service[64], ruser[256], rhost[HOST_NAME_MAX + 1], tty[64]; char sessionid[25]; static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "01234567890"; 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 *session) { int32_t tmpint32; char username[256], service[64], ruser[256], rhost[HOST_NAME_MAX + 1], 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[1024]; int rc; /* set up a new connection */ session = myldap_create_session(); if (session == NULL) return LDAP_UNAVAILABLE; /* set up credentials for the session */ myldap_set_credentials(session, binddn, oldpassword); /* 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[256], service[64], ruser[256], rhost[HOST_NAME_MAX + 1], tty[64]; int asroot; char oldpassword[64]; char newpassword[64]; const char *binddn = NULL; /* the user performing the modification */ MYLDAP_ENTRY *entry; char authzmsg[1024]; 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); } 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); 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"); 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); 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); 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); return 0; }