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 /nslcd | |
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.
Diffstat (limited to 'nslcd')
-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; +} |