aboutsummaryrefslogtreecommitdiff
path: root/usr.sbin/rpc.yppasswdd/yppasswdd_server.c
diff options
context:
space:
mode:
authorBill Paul <wpaul@FreeBSD.org>1996-02-12 15:09:01 +0000
committerBill Paul <wpaul@FreeBSD.org>1996-02-12 15:09:01 +0000
commit8256fad9b70db4901b52926f120a856cf75c71e8 (patch)
treeb703880bdbddd91df70e1980b86153e851a2dfc8 /usr.sbin/rpc.yppasswdd/yppasswdd_server.c
parentd565512ed50cfac90dc81990ece288013b474c7f (diff)
downloadsrc-8256fad9b70db4901b52926f120a856cf75c71e8.tar.gz
src-8256fad9b70db4901b52926f120a856cf75c71e8.zip
Import new rpc.yppasswdd. (Note: accompanying changes to passwd(1) and
chpass(1) are on the way too.) This version supports all the features of the old one and adds several new ones: - Supports real multi-domain operation (optional, can be turned on with a command-line flag). This means you can actually have several different domains all served from one NIS server and allow users in any of the supported domains to change their passwords. The old yppasswdd only allowed changing passwords in the domain that was set as the system default domain name on the NIS master server. The new one can change passwords in any domain by trying to match the user information passed to it against all the passwd maps it can find. This is something of a hack, but the yppasswd.x protocol definiton does not allow for a domain to be passwd as an argument to rpc.yppasswdd, so the server has no choice but to grope around for a likely match. Since this method can fail if the same user exists in two domains, this feature is off by default. If the feature is turned on and the server becomes confused by duplicate entries, it will abort the update. - Does not require NIS client services to be available. NIS servers do _NOT_ necessarily have to be configured as NIS clients in order to function: the ypserv, ypxfr and yppush programs I've written recently will operate fine even if the system domain name isn't set, ypbind isn't running and there are no magic '+' entries in any of the /etc files. Now rpc.yppasswdd is the same way. The old yppasswdd would not work like this because it depended on getpwent(3) and friends to look up users: this will obviously only work if the system where yppasswdd is running is configured as an NIS client. The new rpc.yppasswdd doesn't use getpwent(3) at all: instead it searches through the master.passwd map databases directly. This also makes it easier for it to handle multiple domains. - Allows the superuser on the NIS master server to change any user's password without requiring password authentication. rpc.yppasswdd creates a UNIX domain socket (/var/run/ypsock) which it monitors using the same svc_run() loop used to handle incoming RPC requests. It also clears all the permission bits for /var/run/ypsock; since this socket is owned by root, this prevents anyone except root from successfully connect()ing to it. (Using a UNIX domain socket also prevents IP spoofing attacks.) By building code into passwd(1) and chpass(1) to take advantage of this 'trusted' channel, the superuser can use them to send private requests to rpc.yppasswdd. - Allows the superuser on the NIS master to use chpass(1) to update _all_ of a user's master.passwd information. The UNIX domain access point accepts a full master.passwd style structure (along with a domain name and other information), which allows the superuser to update all of a user's master.passwd information in the NIS master.passwd maps. Normal users on NIS clients are still only allowed to change their full name and shell information with chpass. - Allows the superuser on the NIS master to _add_ records to the NIS master.passwd maps using chpass(1). This feature is also switchable with a command-line flag and is off by default.
Notes
Notes: svn path=/cvs2svn/branches/WPAUL/dist/; revision=14062
Diffstat (limited to 'usr.sbin/rpc.yppasswdd/yppasswdd_server.c')
-rw-r--r--usr.sbin/rpc.yppasswdd/yppasswdd_server.c649
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 000000000000..fa2551a5d332
--- /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;
+}