diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2014-10-04 16:13:03 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2014-10-04 16:13:03 -0400 |
commit | c9618dfe442305531ee6cab9660333f4a697e094 (patch) | |
tree | 63da3cf1c107fdebd82987519b858f0d98c12d23 /nslcd/db_pam.c | |
parent | be4588009b7106859e1beae6038aaea8d7f85825 (diff) |
foo
Diffstat (limited to 'nslcd/db_pam.c')
-rw-r--r-- | nslcd/db_pam.c | 847 |
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; +} |