diff options
Diffstat (limited to 'usr.sbin/rpc.yppasswdd/yppasswdd_server.c')
-rw-r--r-- | usr.sbin/rpc.yppasswdd/yppasswdd_server.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/usr.sbin/rpc.yppasswdd/yppasswdd_server.c b/usr.sbin/rpc.yppasswdd/yppasswdd_server.c new file mode 100644 index 0000000..fa2551a --- /dev/null +++ b/usr.sbin/rpc.yppasswdd/yppasswdd_server.c @@ -0,0 +1,649 @@ +/* + * Copyright (c) 1995, 1996 + * Bill Paul <wpaul@ctr.columbia.edu>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Bill Paul. + * 4. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: yppasswdd_server.c,v 1.8 1996/02/09 04:38:19 wpaul Exp $ + */ + +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <limits.h> +#include <db.h> +#include <pwd.h> +#include <errno.h> +#include <signal.h> +#include <rpc/rpc.h> +#include <rpcsvc/yp.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/param.h> +struct dom_binding {}; +#include <rpcsvc/ypclnt.h> +#include "yppasswdd_extern.h" +#include "yppasswd.h" +#include "yppasswd_private.h" +#include "yppasswd_comm.h" + +#ifndef lint +static const char rcsid[] = "$Id: yppasswdd_server.c,v 1.8 1996/02/09 04:38:19 wpaul Exp $"; +#endif /* not lint */ + +char *tempname; + +void reaper(sig) + int sig; +{ + extern pid_t pid; + extern int pstat; + int st; + + if (sig > 0) { + if (sig == SIGCHLD) + while(wait3(&st, WNOHANG, NULL) > 0) ; + } else { + pid = waitpid(pid, &pstat, 0); + } + return; +} + +void install_reaper(on) + int on; +{ + if (on) { + signal(SIGCHLD, reaper); + } else { + signal(SIGCHLD, SIG_DFL); + } + return; +} + +static struct passwd yp_password; + +static void copy_yp_pass(p, x, m) +char *p; +int x, m; +{ + register char *t, *s = p; + static char *buf; + + yp_password.pw_fields = 0; + + buf = (char *)realloc(buf, m + 10); + bzero(buf, m + 10); + + /* Turn all colons into NULLs */ + while (strchr(s, ':')) { + s = (strchr(s, ':') + 1); + *(s - 1)= '\0'; + } + + t = buf; +#define EXPAND(e) e = t; while ((*t++ = *p++)); + EXPAND(yp_password.pw_name); + yp_password.pw_fields |= _PWF_NAME; + EXPAND(yp_password.pw_passwd); + yp_password.pw_fields |= _PWF_PASSWD; + yp_password.pw_uid = atoi(p); + p += (strlen(p) + 1); + yp_password.pw_fields |= _PWF_UID; + yp_password.pw_gid = atoi(p); + p += (strlen(p) + 1); + yp_password.pw_fields |= _PWF_GID; + if (x) { + EXPAND(yp_password.pw_class); + yp_password.pw_fields |= _PWF_CLASS; + yp_password.pw_change = atol(p); + p += (strlen(p) + 1); + yp_password.pw_fields |= _PWF_CHANGE; + yp_password.pw_expire = atol(p); + p += (strlen(p) + 1); + yp_password.pw_fields |= _PWF_EXPIRE; + } + EXPAND(yp_password.pw_gecos); + yp_password.pw_fields |= _PWF_GECOS; + EXPAND(yp_password.pw_dir); + yp_password.pw_fields |= _PWF_DIR; + EXPAND(yp_password.pw_shell); + yp_password.pw_fields |= _PWF_SHELL; + + return; +} + +static int validchars(arg) + char *arg; +{ + int i; + + for (i = 0; i < strlen(arg); i++) { + if (iscntrl(arg[i])) { + yp_error("string contains a control character"); + return(1); + } + if (arg[i] == ':') { + yp_error("string contains a colon"); + return(1); + } + /* Be evil: truncate strings with \n in them silently. */ + if (arg[i] == '\n') { + arg[i] = '\0'; + return(0); + } + } + return(0); +} + +static int validate_master(opw, npw) + struct passwd *opw; + struct x_master_passwd *npw; +{ + + if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') { + yp_error("client tried to modify an NIS entry"); + return(1); + } + + if (validchars(npw->pw_shell)) { + yp_error("specified shell contains invalid characters"); + return(1); + } + + if (validchars(npw->pw_gecos)) { + yp_error("specified gecos field contains invalid characters"); + return(1); + } + + if (validchars(npw->pw_passwd)) { + yp_error("specified password contains invalid characters"); + return(1); + } + return(0); +} + +static int validate(opw, npw) + struct passwd *opw; + struct x_passwd *npw; +{ + + if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') { + yp_error("client tried to modify an NIS entry"); + return(1); + } + + if (npw->pw_uid != opw->pw_uid) { + yp_error("UID mismatch: client says user %s has UID %d", + npw->pw_name, npw->pw_uid); + yp_error("database says user %s has UID %d", opw->pw_name, + opw->pw_uid); + return(1); + } + + if (npw->pw_gid != opw->pw_gid) { + yp_error("GID mismatch: client says user %s has GID %d", + npw->pw_name, npw->pw_gid); + yp_error("database says user %s has GID %d", opw->pw_name, + opw->pw_gid); + return(1); + } + + /* + * Don't allow the user to shoot himself in the foot, + * even on purpose. + */ + if (!ok_shell(npw->pw_shell)) { + yp_error("%s is not a valid shell", npw->pw_shell); + return(1); + } + + if (validchars(npw->pw_shell)) { + yp_error("specified shell contains invalid characters"); + return(1); + } + + if (validchars(npw->pw_gecos)) { + yp_error("specified gecos field contains invalid characters"); + return(1); + } + + if (validchars(npw->pw_passwd)) { + yp_error("specified password contains invalid characters"); + return(1); + } + return(0); +} + +/* + * Kludge alert: + * In order to have one rpc.yppasswdd support multiple domains, + * we have to cheat: we search each directory under /var/yp + * and try to match the user in each master.passwd.byname + * map that we find. If the user matches (username, uid and gid + * all agree), then we use that domain. If we match the user in + * more than one database, we must abort. + */ +static char *find_domain(pw) + struct x_passwd *pw; +{ + struct stat statbuf; + struct dirent *dirp; + DIR *dird; + char yp_mapdir[MAXPATHLEN + 2]; + char *domain = NULL; + char *tmp = NULL; + DBT key, data; + int hit = 0; + + yp_error("performing multidomain lookup"); + + if ((dird = opendir(yp_dir)) == NULL) { + yp_error("opendir(%s) failed: %s", yp_dir, strerror(errno)); + return(NULL); + } + + while ((dirp = readdir(dird)) != NULL) { + snprintf(yp_mapdir, sizeof(yp_mapdir), "%s/%s", + yp_dir, dirp->d_name); + if (stat(yp_mapdir, &statbuf) < 0) { + yp_error("stat(%s) failed: %s", yp_mapdir, + strerror(errno)); + closedir(dird); + return(NULL); + } + if (S_ISDIR(statbuf.st_mode)) { + tmp = (char *)dirp->d_name; + key.data = pw->pw_name; + key.size = strlen(pw->pw_name); + + if (yp_get_record(tmp,"master.passwd.byname", + &key, &data, 0) != YP_TRUE) { + continue; + } + *(char *)(data.data + data.size) = '\0'; + copy_yp_pass(data.data, 1, data.size); + if (yp_password.pw_uid == pw->pw_uid && + yp_password.pw_gid == pw->pw_gid) { + hit++; + domain = tmp; + } + } + } + + closedir(dird); + if (hit > 1) { + yp_error("found same user in two different domains"); + return(NULL); + } else + return(domain); +} + +int * +yppasswdproc_update_1_svc(yppasswd *argp, struct svc_req *rqstp) +{ + static int result; + struct sockaddr_in *rqhost; + DBT key, data; + int rval = 0; + int pfd, tfd; + int pid; + int passwd_changed = 0; + int shell_changed = 0; + int gecos_changed = 0; + char *oldshell = NULL; + char *oldgecos = NULL; + char *passfile_hold; + char passfile_buf[MAXPATHLEN + 2]; + char template[] = "/etc/yppwtmp.XXXXX"; + char *domain = yppasswd_domain; + + /* + * Normal user updates always use the 'default' master.passwd file. + */ + + passfile = passfile_default; + result = 1; + + rqhost = svc_getcaller(rqstp->rq_xprt); + + /* + * Step one: find the user. (It's kinda pointless to + * proceed if the user doesn't exist.) We look for the + * user in the master.passwd.byname database, _NOT_ by + * using getpwent() and friends! We can't use getpwent() + * since the NIS master server is not guaranteed to be + * configured as an NIS client. + */ + + if (multidomain) { + if ((domain = find_domain(&argp->newpw)) == NULL) { + yp_error("multidomain lookup failed - aborting update"); + return(&result); + } else + yp_error("updating user %s in domain %s", + argp->newpw.pw_name, domain); + } + + key.data = argp->newpw.pw_name; + key.size = strlen(argp->newpw.pw_name); + + if ((rval=yp_get_record(domain,"master.passwd.byname", + &key, &data, 0)) != YP_TRUE) { + if (rval == YP_NOKEY) { + yp_error("user %s not found in passwd database", + argp->newpw.pw_name); + } else { + yp_error("database access error: %s", + yperr_string(rval)); + } + return(&result); + } + + /* Nul terminate, please. */ + *(char *)(data.data + data.size) = '\0'; + + copy_yp_pass(data.data, 1, data.size); + + /* Step 2: check that the supplied oldpass is valid. */ + + if (strcmp(crypt(argp->oldpass, yp_password.pw_passwd), + yp_password.pw_passwd)) { + yp_error("rejected change attempt -- bad password"); + yp_error("client address: %s username: %s", + inet_ntoa(rqhost->sin_addr), + argp->newpw.pw_name); + return(&result); + } + + /* Step 3: validate the arguments passed to us by the client. */ + + if (validate(&yp_password, &argp->newpw)) { + yp_error("rejecting change attempt: bad arguments"); + yp_error("client address: %s username: %s", + inet_ntoa(rqhost->sin_addr), + argp->newpw.pw_name); + svcerr_decode(rqstp->rq_xprt); + return(&result); + } + + /* Step 4: update the user's passwd structure. */ + + if (!no_chsh && strcmp(argp->newpw.pw_shell, yp_password.pw_shell)) { + oldshell = yp_password.pw_shell; + yp_password.pw_shell = argp->newpw.pw_shell; + shell_changed++; + } + + + if (!no_chfn && strcmp(argp->newpw.pw_gecos, yp_password.pw_gecos)) { + oldgecos = yp_password.pw_gecos; + yp_password.pw_gecos = argp->newpw.pw_gecos; + gecos_changed++; + } + + if (strcmp(argp->newpw.pw_passwd, yp_password.pw_passwd)) { + yp_password.pw_passwd = argp->newpw.pw_passwd; + passwd_changed++; + } + + /* + * If the caller specified a domain other than our 'default' + * domain, change the path to master.passwd accordingly. + */ + + if (strcmp(domain, yppasswd_domain)) { + snprintf(passfile_buf, sizeof(passfile_buf), + "/var/yp/%s/master.passwd", domain); + passfile = (char *)&passfile_buf; + } + + /* Step 5: make a new password file with the updated info. */ + + if ((pfd = pw_lock()) < 0) { + return (&result); + } + if ((tfd = pw_tmp()) < 0) { + return (&result); + } + + if (pw_copy(pfd, tfd, &yp_password)) { + yp_error("failed to created updated password file -- \ +cleaning up and bailing out"); + unlink(tempname); + return(&result); + } + + passfile_hold = mktemp((char *)&template); + rename(passfile, passfile_hold); + if (strcmp(passfile, _PATH_MASTERPASSWD)) { + rename(tempname, passfile); + } else { + if (pw_mkdb() < 0) { + yp_error("pwd_mkdb failed"); + return(&result); + } + } + + switch((pid = fork())) { + case 0: + /* unlink(passfile_hold); */ + execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile, + yppasswd_domain, NULL); + yp_error("couldn't exec map update process: %s", + strerror(errno)); + unlink(passfile); + rename(passfile_hold, passfile); + exit(1); + break; + case -1: + yp_error("fork() failed: %s", strerror(errno)); + return(&result); + unlink(passfile); + rename(passfile_hold, passfile); + break; + default: + break; + } + + if (verbose) { + yp_error("update completed for user %s (uid %d):", + argp->newpw.pw_name, + argp->newpw.pw_uid); + + if (passwd_changed) + yp_error("password changed"); + + if (gecos_changed) + yp_error("gecos changed ('%s' -> '%s')", + oldgecos, argp->newpw.pw_gecos); + + if (shell_changed) + yp_error("shell changed ('%s' -> '%s')", + oldshell, argp->newpw.pw_shell); + } + + result = 0; + return (&result); + +} + +/* + * Note that this function performs a little less sanity checking + * than the last one. Since only the superuser is allowed to use it, + * it is assumed that the caller knows what he's doing. + */ +static int update_master(master_yppasswd *argp) +{ + int result; + int pfd, tfd; + int pid; + int rval = 0; + DBT key, data; + char *passfile_hold; + char passfile_buf[MAXPATHLEN + 2]; + char template[] = "/etc/yppwtmp.XXXXX"; + + result = 1; + passfile = passfile_default; + + key.data = argp->newpw.pw_name; + key.size = strlen(argp->newpw.pw_name); + + /* + * The superuser may add entries to the passwd maps if + * rpc.yppasswdd is started with the -a flag. Paranoia + * prevents me from allowing additions by default. + */ + if ((rval = yp_get_record(argp->domain, "master.passwd.byname", + &key, &data, 0)) != YP_TRUE) { + if (rval == YP_NOKEY) { + yp_error("user %s not found in passwd database", + argp->newpw.pw_name); + if (allow_additions) + yp_error("notice: adding user %s to \ +master.passwd database for domain %s", argp->newpw.pw_name, argp->domain); + else + yp_error("restart %s with the -a flag to \ +allow additions to be made to the password database", progname); + } else { + yp_error("database access error: %s", + yperr_string(rval)); + } + if (!allow_additions) + return(result); + } else { + + /* Nul terminate, please. */ + *(char *)(data.data + data.size) = '\0'; + + copy_yp_pass(data.data, 1, data.size); + } + + /* + * Perform a small bit of sanity checking. + */ + if (validate_master(rval == YP_TRUE ? &yp_password:NULL,&argp->newpw)){ + yp_error("rejecting update attempt for %s: bad arguments", + argp->newpw.pw_name); + return(result); + } + + /* + * If the caller specified a domain other than our 'default' + * domain, change the path to master.passwd accordingly. + */ + + if (strcmp(argp->domain, yppasswd_domain)) { + snprintf(passfile_buf, sizeof(passfile_buf), + "/var/yp/%s/master.passwd", argp->domain); + passfile = (char *)&passfile_buf; + } + + if ((pfd = pw_lock()) < 0) { + return (result); + } + if ((tfd = pw_tmp()) < 0) { + return (result); + } + + if (pw_copy(pfd, tfd, (struct passwd *)&argp->newpw)) { + yp_error("failed to created updated password file -- \ +cleaning up and bailing out"); + unlink(tempname); + return(result); + } + + passfile_hold = mktemp((char *)&template); + rename(passfile, passfile_hold); + if (strcmp(passfile, _PATH_MASTERPASSWD)) { + rename(tempname, passfile); + } else { + if (pw_mkdb() < 0) { + yp_error("pwd_mkdb failed"); + return(result); + } + } + + switch((pid = fork())) { + case 0: + close(yp_sock); + execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile, + argp->domain, NULL); + yp_error("couldn't exec map update process: %s", + strerror(errno)); + unlink(passfile); + rename(passfile_hold, passfile); + exit(1); + break; + case -1: + yp_error("fork() failed: %s", strerror(errno)); + unlink(passfile); + rename(passfile_hold, passfile); + return(result); + break; + default: + break; + } + + yp_error("performed update of user %s (uid %d) domain %s", + argp->newpw.pw_name, + argp->newpw.pw_uid, + argp->domain); + + result = 0; + return(result); +} + +/* + * Pseudo-dispatcher for private 'superuser-only' update handler. + */ +void do_master() +{ + struct master_yppasswd *pw; + int resp; + + if ((pw = getdat(yp_sock)) == NULL) { + return; + } + + yp_error("received update request from superuser on localhost"); + resp = update_master(pw); + sendresp(resp); + + /* Remember to free args. */ + xdr_free(xdr_master_yppasswd, (char *)pw); + + return; +} |