diff options
author | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-30 21:09:44 +0100 |
---|---|---|
committer | Arthur de Jong <arthur@arthurdejong.org> | 2013-03-30 23:09:30 +0100 |
commit | f1895f98a36d0f29b88ed61edf2e978dc520caed (patch) | |
tree | e095385e128ab270a5a94be813fdff16f256a741 | |
parent | 8fb5eb16ccc7fe3d119d7365537ca7cd82d496b8 (diff) |
Handle user modification requests in nslcd
This is currently limited to supporting modification of the homeDirectory and
loginShell attributes.
Modifications as root currently use the rootpwmoddn and rootpwmodpw options.
-rw-r--r-- | nslcd/Makefile.am | 4 | ||||
-rw-r--r-- | nslcd/common.h | 1 | ||||
-rw-r--r-- | nslcd/nslcd.c | 1 | ||||
-rw-r--r-- | nslcd/usermod.c | 297 |
4 files changed, 301 insertions, 2 deletions
diff --git a/nslcd/Makefile.am b/nslcd/Makefile.am index d901d64..60560f8 100644 --- a/nslcd/Makefile.am +++ b/nslcd/Makefile.am @@ -31,8 +31,8 @@ nslcd_SOURCES = nslcd.c ../nslcd.h ../common/nslcd-prot.h \ cfg.c cfg.h \ attmap.c attmap.h \ nsswitch.c nscd.c \ - alias.c config.c ether.c group.c host.c netgroup.c network.c \ - passwd.c protocol.c rpc.c service.c shadow.c pam.c + config.c alias.c ether.c group.c host.c netgroup.c network.c \ + passwd.c protocol.c rpc.c service.c shadow.c pam.c usermod.c nslcd_LDADD = ../common/libtio.a ../common/libdict.a \ ../common/libexpr.a ../compat/libcompat.a \ @nslcd_LIBS@ @PTHREAD_LIBS@ diff --git a/nslcd/common.h b/nslcd/common.h index 2965de6..a6c2c4d 100644 --- a/nslcd/common.h +++ b/nslcd/common.h @@ -240,6 +240,7 @@ int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session); int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION *session); int nslcd_pam_sess_c(TFILE *fp, MYLDAP_SESSION *session); int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid); +int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid); /* macros for generating service handling code */ #define NSLCD_HANDLE(db, fn, action, readfn, mkfilter, writefn) \ diff --git a/nslcd/nslcd.c b/nslcd/nslcd.c index 5f70963..9e22682 100644 --- a/nslcd/nslcd.c +++ b/nslcd/nslcd.c @@ -408,6 +408,7 @@ static void handleconnection(int sock, MYLDAP_SESSION *session) case NSLCD_ACTION_PAM_SESS_O: (void)nslcd_pam_sess_o(fp, session); break; case NSLCD_ACTION_PAM_SESS_C: (void)nslcd_pam_sess_c(fp, session); break; case NSLCD_ACTION_PAM_PWMOD: (void)nslcd_pam_pwmod(fp, session, uid); break; + case NSLCD_ACTION_USERMOD: (void)nslcd_usermod(fp, session, uid); break; default: log_log(LOG_WARNING, "invalid request id: 0x%08x", (unsigned int)action); break; diff --git a/nslcd/usermod.c b/nslcd/usermod.c new file mode 100644 index 0000000..61f6bbe --- /dev/null +++ b/nslcd/usermod.c @@ -0,0 +1,297 @@ +/* + usermod.c - routines for changing user information such as full name, + login shell, etc + + Copyright (C) 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif /* HAVE_STDINT_H */ +#include <unistd.h> +#include <sys/stat.h> + +#include "common.h" +#include "log.h" +#include "myldap.h" +#include "cfg.h" +#include "attmap.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 *userdn, + const char *password, int *rcp) +{ + MYLDAP_SESSION *session; + char buffer[256]; + /* set up a new connection */ + session = myldap_create_session(); + if (session == NULL) + { + *rcp = LDAP_UNAVAILABLE; + return NULL; + } + /* set up credentials for the session */ + myldap_set_credentials(session, binddn, password); + /* perform search for own object (just to do any kind of search to set + up the connection with fail-over) */ + if ((lookup_dn2uid(session, userdn, rcp, buffer, sizeof(buffer)) == NULL) || + (*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[256]; + int asroot, isroot; + char password[64]; + 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[1024]; + /* 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, myldap_get_dn(entry), 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; +} |