/*
pam.c - pam processing routines
Copyright (C) 2009 Howard Chu
Copyright (C) 2009-2017 Arthur de Jong
Copyright (C) 2015 Nokia Solutions and Networks
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"
static void search_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, "search_var_add(): malloc() failed to allocate memory");
return;
}
/* perform escaping of the value */
if (myldap_escape(value, escaped_value, sz))
{
log_log(LOG_ERR, "search_var_add(): escaped_value buffer too small");
free(escaped_value);
return;
}
/* add to dict */
dict_put(dict, name, escaped_value);
}
/* build a dictionary with variables that can be used in searches */
static DICT *search_vars_new(const char *dn, const char *username,
const char *service, const char *ruser,
const char *rhost, const char *tty)
{
char hostname[BUFLEN_HOSTNAME];
/* allocating this on the stack is OK because search_var_add()
will allocate new memory for the value */
const char *fqdn;
DICT *dict;
dict = dict_new();
if (dict == NULL)
{
log_log(LOG_CRIT, "search_vars_new(): dict_new() failed to allocate memory");
return NULL;
}
/* NOTE: any variables added here also need to be added to
cfg.c:check_search_variables() */
search_var_add(dict, "username", username);
search_var_add(dict, "service", service);
search_var_add(dict, "ruser", ruser);
search_var_add(dict, "rhost", rhost);
search_var_add(dict, "tty", tty);
if (gethostname(hostname, sizeof(hostname)) == 0)
search_var_add(dict, "hostname", hostname);
if ((fqdn = getfqdn()) != NULL)
search_var_add(dict, "fqdn", fqdn);
search_var_add(dict, "dn", dn);
search_var_add(dict, "uid", username);
return dict;
}
static void search_vars_free(DICT *dict)
{
int i;
const char **keys;
void *value;
/* go over all keys and free all the values
(they were allocated in search_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 */
dict_free(dict);
}
static const char *search_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_searches(MYLDAP_SESSION *session, const char *option,
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 %s \"%s\"", option, 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, "%s \"%s\" failed: %s",
option, filter, ldap_err2string(rc));
return rc;
}
/* try to get an entry */
entry = myldap_get_entry(search, &rc);
if (entry != NULL)
{
log_log(LOG_DEBUG, "%s found \"%s\"", option, myldap_get_dn(entry));
return LDAP_SUCCESS;
}
}
log_log(LOG_ERR, "%s \"%s\" found no matches", option, filter);
if (rc == LDAP_SUCCESS)
rc = LDAP_NO_SUCH_OBJECT;
return rc;
}
/* 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,
const char *username, const char *service,
const char *ruser, const char *rhost, const char *tty,
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;
DICT *dict;
char filter[BUFLEN_FILTER];
const char *res;
/* set up a new connection */
session = myldap_create_session();
if (session == NULL)
return LDAP_UNAVAILABLE;
/* perform a BIND operation with user credentials */
rc = myldap_bind(session, userdn, password, authzrc, &msg);
if (rc == LDAP_SUCCESS)
{
/* perform a search to trigger the BIND operation */
attrs[0] = "dn";
attrs[1] = NULL;
if (strcasecmp(nslcd_cfg->pam_authc_search, "BASE") == 0)
{
/* do a simple search to check userdn existence */
search = myldap_search(session, userdn, LDAP_SCOPE_BASE,
"(objectClass=*)", attrs, &rc);
if ((search == NULL) && (rc == LDAP_SUCCESS))
rc = LDAP_LOCAL_ERROR;
if (rc == LDAP_SUCCESS)
{
entry = myldap_get_entry(search, &rc);
if ((entry == NULL) && (rc == LDAP_SUCCESS))
rc = LDAP_NO_RESULTS_RETURNED;
}
}
else if (strcasecmp(nslcd_cfg->pam_authc_search, "NONE") != 0)
{
/* build the search filter */
dict = search_vars_new(userdn, username, service, ruser, rhost, tty);
if (dict == NULL)
{
myldap_session_close(session);
return LDAP_LOCAL_ERROR;
}
res = expr_parse(nslcd_cfg->pam_authc_search, filter, sizeof(filter),
search_var_get, (void *)dict);
if (res == NULL)
{
search_vars_free(dict);
myldap_session_close(session);
log_log(LOG_ERR, "invalid pam_authc_search \"%s\"",
nslcd_cfg->pam_authc_search);
return LDAP_LOCAL_ERROR;
}
/* perform a search for each search base */
rc = do_searches(session, "pam_authc_search", filter);
/* free search variables */
search_vars_free(dict);
}
}
/* log any authentication, search or authorsiation messages */
if (rc != LDAP_SUCCESS)
log_log(LOG_WARNING, "%s: %s", userdn, ldap_err2string(rc));
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 (STR_CMP(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, username, service, ruser, rhost, tty,
&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;
}
/* perform an authorisation search, returns an LDAP status code */
static int try_authz_search(MYLDAP_SESSION *session, const char *dn,
const char *username, const char *service,
const char *ruser, const char *rhost,
const char *tty)
{
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)
{
dict = search_vars_new(dn, username, service, ruser, rhost, tty);
if (dict == NULL)
return LDAP_LOCAL_ERROR;
}
/* build the search filter */
res = expr_parse(nslcd_cfg->pam_authz_searches[i],
filter, sizeof(filter),
search_var_get, (void *)dict);
if (res == NULL)
{
search_vars_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_searches(session, "pam_authz_search", filter);
if (rc != LDAP_SUCCESS)
break;
}
/* we went over all pam_authz_search entries */
if (dict != NULL)
search_vars_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_authz_search(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;
/* perform a BIND operation */
rc = myldap_bind(session, binddn, oldpassword, NULL, NULL);
if (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;
}