/* usermod.c - routines for changing user information such as full name, login shell, etc Copyright (C) 2013-2017 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 "compat/shell.h" /* 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 NULL; } return entry; } static int is_valid_homedir(const char *homedir) { struct stat sb; /* should be absolute path */ if (homedir[0] != '/') return 0; /* get directory status */ if (stat(homedir, &sb)) { log_log(LOG_DEBUG, "cannot stat() %s: %s", homedir, strerror(errno)); return 0; } /* check if a directory */ if (!S_ISDIR(sb.st_mode)) { log_log(LOG_DEBUG, "%s: not a directory", homedir); return 0; } /* FIXME: check ownership */ return 1; } static int is_valid_shell(const char *shell) { int valid = 0; char *l; setusershell(); while ((l = getusershell()) != NULL) { if (strcmp(l, shell) == 0) { valid = 1; break; } } endusershell(); return valid; } static MYLDAP_SESSION *get_session(const char *binddn, const char *password, int *rcp) { MYLDAP_SESSION *session; /* set up a new connection */ session = myldap_create_session(); if (session == NULL) { *rcp = LDAP_UNAVAILABLE; return NULL; } /* check that we can bind */ *rcp = myldap_bind(session, binddn, password, NULL, NULL); if (*rcp != LDAP_SUCCESS) { myldap_session_close(session); return NULL; } return session; } #define ADD_MOD(attribute, value) \ if ((value != NULL) && (attribute[0] != '"')) \ { \ strvals[i * 2] = (char *)value; \ strvals[i * 2 + 1] = NULL; \ mods[i].mod_op = LDAP_MOD_REPLACE; \ mods[i].mod_type = (char *)attribute; \ mods[i].mod_values = strvals + (i * 2); \ modsp[i] = mods + i; \ i++; \ } static int change(MYLDAP_SESSION *session, const char *userdn, const char *homedir, const char *shell) { #define NUMARGS 2 char *strvals[(NUMARGS + 1) * 2]; LDAPMod mods[(NUMARGS + 1)], *modsp[(NUMARGS + 1)]; int i = 0; /* build the list of modifications */ ADD_MOD(attmap_passwd_homeDirectory, homedir); ADD_MOD(attmap_passwd_loginShell, shell); /* terminate the list of modifications */ modsp[i] = NULL; /* execute the update */ return myldap_modify(session, userdn, modsp); } int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid) { int32_t tmpint32; int rc = LDAP_SUCCESS; char username[BUFLEN_NAME]; int asroot, isroot; char password[BUFLEN_PASSWORD]; int32_t param; char buffer[4096]; size_t buflen = sizeof(buffer); size_t bufptr = 0; const char *value = NULL; const char *fullname = NULL, *roomnumber = NULL, *workphone = NULL; const char *homephone = NULL, *other = NULL, *homedir = NULL; const char *shell = NULL; const char *binddn = NULL; /* the user performing the modification */ MYLDAP_ENTRY *entry; MYLDAP_SESSION *newsession; char errmsg[BUFLEN_MESSAGE]; /* read request parameters */ READ_STRING(fp, username); READ_INT32(fp, asroot); READ_STRING(fp, password); /* read the usermod parameters */ while (1) { READ_INT32(fp, param); if (param == NSLCD_USERMOD_END) break; READ_BUF_STRING(fp, value); switch (param) { case NSLCD_USERMOD_FULLNAME: fullname = value; break; case NSLCD_USERMOD_ROOMNUMBER: roomnumber = value; break; case NSLCD_USERMOD_WORKPHONE: workphone = value; break; case NSLCD_USERMOD_HOMEPHONE: homephone = value; break; case NSLCD_USERMOD_OTHER: other = value; break; case NSLCD_USERMOD_HOMEDIR: homedir = value; break; case NSLCD_USERMOD_SHELL: shell = value; break; default: /* other parameters are silently ignored */ break; } } /* log call */ log_setrequest("usermod=\"%s\"", username); log_log(LOG_DEBUG, "nslcd_usermod(\"%s\",%s,\"%s\")", username, asroot ? "asroot" : "asuser", *password ? "***" : ""); if (fullname != NULL) log_log(LOG_DEBUG, "nslcd_usermod(fullname=\"%s\")", fullname); if (roomnumber != NULL) log_log(LOG_DEBUG, "nslcd_usermod(roomnumber=\"%s\")", roomnumber); if (workphone != NULL) log_log(LOG_DEBUG, "nslcd_usermod(workphone=\"%s\")", workphone); if (homephone != NULL) log_log(LOG_DEBUG, "nslcd_usermod(homephone=\"%s\")", homephone); if (other != NULL) log_log(LOG_DEBUG, "nslcd_usermod(other=\"%s\")", other); if (homedir != NULL) log_log(LOG_DEBUG, "nslcd_usermod(homedir=\"%s\")", homedir); if (shell != NULL) log_log(LOG_DEBUG, "nslcd_usermod(shell=\"%s\")", shell); /* write the response header */ WRITE_INT32(fp, NSLCD_VERSION); WRITE_INT32(fp, NSLCD_ACTION_USERMOD); /* validate request */ entry = validate_user(session, username, &rc); if (entry == NULL) { /* for user not found we just say no result, otherwise break the protocol */ if (rc == LDAP_NO_SUCH_OBJECT) { WRITE_INT32(fp, NSLCD_RESULT_END); } return -1; } /* check if it is a modification as root */ isroot = (calleruid == 0) && asroot; if (asroot) { if (nslcd_cfg->rootpwmoddn == NULL) { log_log(LOG_NOTICE, "rootpwmoddn not configured"); /* we break the protocol */ return -1; } binddn = nslcd_cfg->rootpwmoddn; /* check if rootpwmodpw should be used */ if ((*password == '\0') && isroot && (nslcd_cfg->rootpwmodpw != NULL)) { if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password)) { log_log(LOG_ERR, "nslcd_pam_pwmod(): rootpwmodpw will not fit in password"); return -1; } strcpy(password, nslcd_cfg->rootpwmodpw); } } else binddn = myldap_get_dn(entry); WRITE_INT32(fp, NSLCD_RESULT_BEGIN); /* home directory change requires either root or valid directory */ if ((homedir != NULL) && (!isroot) && !is_valid_homedir(homedir)) { log_log(LOG_NOTICE, "invalid directory: %s", homedir); WRITE_INT32(fp, NSLCD_USERMOD_HOMEDIR); WRITE_STRING(fp, "invalid directory"); homedir = NULL; } /* shell change requires either root or a valid shell */ if ((shell != NULL) && (!isroot) && !is_valid_shell(shell)) { log_log(LOG_NOTICE, "invalid shell: %s", shell); WRITE_INT32(fp, NSLCD_USERMOD_SHELL); WRITE_STRING(fp, "invalid shell"); shell = NULL; } /* perform requested changes */ newsession = get_session(binddn, password, &rc); if (newsession != NULL) { rc = change(newsession, myldap_get_dn(entry), homedir, shell); myldap_session_close(newsession); } /* return response to caller */ if (rc != LDAP_SUCCESS) { log_log(LOG_WARNING, "%s: modification failed: %s", myldap_get_dn(entry), ldap_err2string(rc)); mysnprintf(errmsg, sizeof(errmsg) - 1, "change failed: %s", ldap_err2string(rc)); WRITE_INT32(fp, NSLCD_USERMOD_RESULT); WRITE_STRING(fp, errmsg); WRITE_INT32(fp, NSLCD_USERMOD_END); WRITE_INT32(fp, NSLCD_RESULT_END); return 0; } log_log(LOG_NOTICE, "changed information for %s", myldap_get_dn(entry)); WRITE_INT32(fp, NSLCD_USERMOD_END); WRITE_INT32(fp, NSLCD_RESULT_END); return 0; }