yppasswdd_server.c revision 15687
1/*
2 * Copyright (c) 1995, 1996
3 *	Bill Paul <wpaul@ctr.columbia.edu>.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by Bill Paul.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 *    may be used to endorse or promote products derived from this software
18 *    without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED.  IN NO EVENT SHALL Bill Paul OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
31 *
32 *	$Id: yppasswdd_server.c,v 1.2 1996/02/24 22:10:42 wpaul Exp $
33 */
34
35#include <stdio.h>
36#include <string.h>
37#include <ctype.h>
38#include <stdlib.h>
39#include <unistd.h>
40#include <dirent.h>
41#include <sys/stat.h>
42#include <sys/socket.h>
43#include <netinet/in.h>
44#include <arpa/inet.h>
45#include <limits.h>
46#include <db.h>
47#include <pwd.h>
48#include <errno.h>
49#include <signal.h>
50#include <rpc/rpc.h>
51#include <rpcsvc/yp.h>
52#include <sys/types.h>
53#include <sys/wait.h>
54#include <sys/param.h>
55struct dom_binding {};
56#include <rpcsvc/ypclnt.h>
57#include "yppasswdd_extern.h"
58#include "yppasswd.h"
59#include "yppasswd_private.h"
60#include "yppasswd_comm.h"
61
62#ifndef lint
63static const char rcsid[] = "$Id: yppasswdd_server.c,v 1.2 1996/02/24 22:10:42 wpaul Exp $";
64#endif /* not lint */
65
66char *tempname;
67
68void reaper(sig)
69	int sig;
70{
71	extern pid_t pid;
72	extern int pstat;
73	int st;
74
75	if (sig > 0) {
76		if (sig == SIGCHLD)
77			while(wait3(&st, WNOHANG, NULL) > 0) ;
78	} else {
79		pid = waitpid(pid, &pstat, 0);
80	}
81	return;
82}
83
84void install_reaper(on)
85	int on;
86{
87	if (on) {
88		signal(SIGCHLD, reaper);
89	} else {
90		signal(SIGCHLD, SIG_DFL);
91	}
92	return;
93}
94
95static struct passwd yp_password;
96
97static void copy_yp_pass(p, x, m)
98char *p;
99int x, m;
100{
101	register char *t, *s = p;
102	static char *buf;
103
104	yp_password.pw_fields = 0;
105
106	buf = (char *)realloc(buf, m + 10);
107	bzero(buf, m + 10);
108
109	/* Turn all colons into NULLs */
110	while (strchr(s, ':')) {
111		s = (strchr(s, ':') + 1);
112		*(s - 1)= '\0';
113	}
114
115	t = buf;
116#define EXPAND(e)       e = t; while ((*t++ = *p++));
117        EXPAND(yp_password.pw_name);
118	yp_password.pw_fields |= _PWF_NAME;
119        EXPAND(yp_password.pw_passwd);
120	yp_password.pw_fields |= _PWF_PASSWD;
121	yp_password.pw_uid = atoi(p);
122        p += (strlen(p) + 1);
123	yp_password.pw_fields |= _PWF_UID;
124	yp_password.pw_gid = atoi(p);
125        p += (strlen(p) + 1);
126	yp_password.pw_fields |= _PWF_GID;
127	if (x) {
128		EXPAND(yp_password.pw_class);
129		yp_password.pw_fields |= _PWF_CLASS;
130		yp_password.pw_change = atol(p);
131		p += (strlen(p) + 1);
132		yp_password.pw_fields |= _PWF_CHANGE;
133		yp_password.pw_expire = atol(p);
134		p += (strlen(p) + 1);
135		yp_password.pw_fields |= _PWF_EXPIRE;
136	}
137        EXPAND(yp_password.pw_gecos);
138	yp_password.pw_fields |= _PWF_GECOS;
139        EXPAND(yp_password.pw_dir);
140	yp_password.pw_fields |= _PWF_DIR;
141        EXPAND(yp_password.pw_shell);
142	yp_password.pw_fields |= _PWF_SHELL;
143
144	return;
145}
146
147static int validchars(arg)
148	char *arg;
149{
150	int i;
151
152	for (i = 0; i < strlen(arg); i++) {
153		if (iscntrl(arg[i])) {
154			yp_error("string contains a control character");
155			return(1);
156		}
157		if (arg[i] == ':') {
158			yp_error("string contains a colon");
159			return(1);
160		}
161		/* Be evil: truncate strings with \n in them silently. */
162		if (arg[i] == '\n') {
163			arg[i] = '\0';
164			return(0);
165		}
166	}
167	return(0);
168}
169
170static int validate_master(opw, npw)
171	struct passwd *opw;
172	struct x_master_passwd *npw;
173{
174
175	if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') {
176		yp_error("client tried to modify an NIS entry");
177		return(1);
178	}
179
180	if (validchars(npw->pw_shell)) {
181		yp_error("specified shell contains invalid characters");
182		return(1);
183	}
184
185	if (validchars(npw->pw_gecos)) {
186		yp_error("specified gecos field contains invalid characters");
187		return(1);
188	}
189
190	if (validchars(npw->pw_passwd)) {
191		yp_error("specified password contains invalid characters");
192		return(1);
193	}
194	return(0);
195}
196
197static int validate(opw, npw)
198	struct passwd *opw;
199	struct x_passwd *npw;
200{
201
202	if (npw->pw_name[0] == '+' || npw->pw_name[0] == '-') {
203		yp_error("client tried to modify an NIS entry");
204		return(1);
205	}
206
207	if (npw->pw_uid != opw->pw_uid) {
208		yp_error("UID mismatch: client says user %s has UID %d",
209			 npw->pw_name, npw->pw_uid);
210		yp_error("database says user %s has UID %d", opw->pw_name,
211			 opw->pw_uid);
212		return(1);
213	}
214
215	if (npw->pw_gid != opw->pw_gid) {
216		yp_error("GID mismatch: client says user %s has GID %d",
217			 npw->pw_name, npw->pw_gid);
218		yp_error("database says user %s has GID %d", opw->pw_name,
219			 opw->pw_gid);
220		return(1);
221	}
222
223	/*
224	 * Don't allow the user to shoot himself in the foot,
225	 * even on purpose.
226	 */
227	if (!ok_shell(npw->pw_shell)) {
228		yp_error("%s is not a valid shell", npw->pw_shell);
229		return(1);
230	}
231
232	if (validchars(npw->pw_shell)) {
233		yp_error("specified shell contains invalid characters");
234		return(1);
235	}
236
237	if (validchars(npw->pw_gecos)) {
238		yp_error("specified gecos field contains invalid characters");
239		return(1);
240	}
241
242	if (validchars(npw->pw_passwd)) {
243		yp_error("specified password contains invalid characters");
244		return(1);
245	}
246	return(0);
247}
248
249/*
250 * Kludge alert:
251 * In order to have one rpc.yppasswdd support multiple domains,
252 * we have to cheat: we search each directory under /var/yp
253 * and try to match the user in each master.passwd.byname
254 * map that we find. If the user matches (username, uid and gid
255 * all agree), then we use that domain. If we match the user in
256 * more than one database, we must abort.
257 */
258static char *find_domain(pw)
259	struct x_passwd *pw;
260{
261	struct stat statbuf;
262	struct dirent *dirp;
263	DIR *dird;
264	char yp_mapdir[MAXPATHLEN + 2];
265	static char domain[YPMAXDOMAIN];
266	char *tmp = NULL;
267	DBT key, data;
268	int hit = 0;
269
270	yp_error("performing multidomain lookup");
271
272	if ((dird = opendir(yp_dir)) == NULL) {
273		yp_error("opendir(%s) failed: %s", yp_dir, strerror(errno));
274		return(NULL);
275	}
276
277	while ((dirp = readdir(dird)) != NULL) {
278		snprintf(yp_mapdir, sizeof(yp_mapdir), "%s/%s",
279							yp_dir, dirp->d_name);
280		if (stat(yp_mapdir, &statbuf) < 0) {
281			yp_error("stat(%s) failed: %s", yp_mapdir,
282							strerror(errno));
283			closedir(dird);
284			return(NULL);
285		}
286		if (S_ISDIR(statbuf.st_mode)) {
287			tmp = (char *)dirp->d_name;
288			key.data = pw->pw_name;
289			key.size = strlen(pw->pw_name);
290
291			if (yp_get_record(tmp,"master.passwd.byname",
292			  		&key, &data, 0) != YP_TRUE) {
293				continue;
294			}
295			*(char *)(data.data + data.size) = '\0';
296			copy_yp_pass(data.data, 1, data.size);
297			if (yp_password.pw_uid == pw->pw_uid &&
298			    yp_password.pw_gid == pw->pw_gid) {
299				hit++;
300				snprintf(domain, YPMAXDOMAIN, "%s", tmp);
301			}
302		}
303	}
304
305	closedir(dird);
306	if (hit > 1) {
307		yp_error("found same user in two different domains");
308		return(NULL);
309	} else
310		return(&domain);
311}
312
313int *
314yppasswdproc_update_1_svc(yppasswd *argp, struct svc_req *rqstp)
315{
316	static int  result;
317	struct sockaddr_in *rqhost;
318	DBT key, data;
319	int rval = 0;
320	int pfd, tfd;
321	int pid;
322	int passwd_changed = 0;
323	int shell_changed = 0;
324	int gecos_changed = 0;
325	char *oldshell = NULL;
326	char *oldgecos = NULL;
327	char *passfile_hold;
328	char passfile_buf[MAXPATHLEN + 2];
329	char template[] = "/etc/yppwtmp.XXXXX";
330	char *domain = yppasswd_domain;
331
332	/*
333	 * Normal user updates always use the 'default' master.passwd file.
334	 */
335
336	passfile = passfile_default;
337	result = 1;
338
339	rqhost = svc_getcaller(rqstp->rq_xprt);
340
341	if (yp_access(resvport ? "master.passwd.byname" : NULL, rqstp)) {
342		yp_error("rejected update request from unauthorized host");
343		svcerr_auth(rqstp->rq_xprt, AUTH_BADCRED);
344		return(&result);
345	}
346
347	/*
348	 * Step one: find the user. (It's kinda pointless to
349	 * proceed if the user doesn't exist.) We look for the
350	 * user in the master.passwd.byname database, _NOT_ by
351	 * using getpwent() and friends! We can't use getpwent()
352	 * since the NIS master server is not guaranteed to be
353	 * configured as an NIS client.
354	 */
355
356	if (multidomain) {
357		if ((domain = find_domain(&argp->newpw)) == NULL) {
358			yp_error("multidomain lookup failed - aborting update");
359			return(&result);
360		} else
361			yp_error("updating user %s in domain %s",
362					argp->newpw.pw_name, domain);
363	}
364
365	key.data = argp->newpw.pw_name;
366	key.size = strlen(argp->newpw.pw_name);
367
368	if ((rval=yp_get_record(domain,"master.passwd.byname",
369		  	&key, &data, 0)) != YP_TRUE) {
370		if (rval == YP_NOKEY) {
371			yp_error("user %s not found in passwd database",
372			 	argp->newpw.pw_name);
373		} else {
374			yp_error("database access error: %s",
375			 	yperr_string(rval));
376		}
377		return(&result);
378	}
379
380	/* Nul terminate, please. */
381	*(char *)(data.data + data.size) = '\0';
382
383	copy_yp_pass(data.data, 1, data.size);
384
385	/* Step 2: check that the supplied oldpass is valid. */
386
387	if (strcmp(crypt(argp->oldpass, yp_password.pw_passwd),
388					yp_password.pw_passwd)) {
389		yp_error("rejected change attempt -- bad password");
390		yp_error("client address: %s username: %s",
391			  inet_ntoa(rqhost->sin_addr),
392			  argp->newpw.pw_name);
393		return(&result);
394	}
395
396	/* Step 3: validate the arguments passed to us by the client. */
397
398	if (validate(&yp_password, &argp->newpw)) {
399		yp_error("rejecting change attempt: bad arguments");
400		yp_error("client address: %s username: %s",
401			 inet_ntoa(rqhost->sin_addr),
402			 argp->newpw.pw_name);
403		svcerr_decode(rqstp->rq_xprt);
404		return(&result);
405	}
406
407	/* Step 4: update the user's passwd structure. */
408
409	if (!no_chsh && strcmp(argp->newpw.pw_shell, yp_password.pw_shell)) {
410		oldshell = yp_password.pw_shell;
411		yp_password.pw_shell = argp->newpw.pw_shell;
412		shell_changed++;
413	}
414
415
416	if (!no_chfn && strcmp(argp->newpw.pw_gecos, yp_password.pw_gecos)) {
417		oldgecos = yp_password.pw_gecos;
418		yp_password.pw_gecos = argp->newpw.pw_gecos;
419		gecos_changed++;
420	}
421
422	if (strcmp(argp->newpw.pw_passwd, yp_password.pw_passwd)) {
423		yp_password.pw_passwd = argp->newpw.pw_passwd;
424		passwd_changed++;
425	}
426
427	/*
428	 * If the caller specified a domain other than our 'default'
429	 * domain, change the path to master.passwd accordingly.
430	 */
431
432	if (strcmp(domain, yppasswd_domain)) {
433		snprintf(passfile_buf, sizeof(passfile_buf),
434			"/var/yp/%s/master.passwd", domain);
435		passfile = (char *)&passfile_buf;
436	}
437
438	/* Step 5: make a new password file with the updated info. */
439
440	if ((pfd = pw_lock()) < 0) {
441		return (&result);
442	}
443	if ((tfd = pw_tmp()) < 0) {
444		return (&result);
445	}
446
447	if (pw_copy(pfd, tfd, &yp_password)) {
448		yp_error("failed to created updated password file -- \
449cleaning up and bailing out");
450		unlink(tempname);
451		return(&result);
452	}
453
454	passfile_hold = mktemp((char *)&template);
455	rename(passfile, passfile_hold);
456	if (strcmp(passfile, _PATH_MASTERPASSWD)) {
457		rename(tempname, passfile);
458	} else {
459		if (pw_mkdb() < 0) {
460			yp_error("pwd_mkdb failed");
461			return(&result);
462		}
463	}
464
465	switch((pid = fork())) {
466	case 0:
467		/* unlink(passfile_hold); */
468    		execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
469			yppasswd_domain, NULL);
470    		yp_error("couldn't exec map update process: %s",
471					strerror(errno));
472		unlink(passfile);
473		rename(passfile_hold, passfile);
474    		exit(1);
475		break;
476	case -1:
477		yp_error("fork() failed: %s", strerror(errno));
478		return(&result);
479		unlink(passfile);
480		rename(passfile_hold, passfile);
481		break;
482	default:
483		break;
484	}
485
486	if (verbose) {
487		yp_error("update completed for user %s (uid %d):",
488						argp->newpw.pw_name,
489						argp->newpw.pw_uid);
490
491		if (passwd_changed)
492			yp_error("password changed");
493
494		if (gecos_changed)
495			yp_error("gecos changed ('%s' -> '%s')",
496					oldgecos, argp->newpw.pw_gecos);
497
498		if (shell_changed)
499			yp_error("shell changed ('%s' -> '%s')",
500					oldshell, argp->newpw.pw_shell);
501	}
502
503	result = 0;
504	return (&result);
505
506}
507
508/*
509 * Note that this function performs a little less sanity checking
510 * than the last one. Since only the superuser is allowed to use it,
511 * it is assumed that the caller knows what he's doing.
512 */
513static int update_master(master_yppasswd *argp)
514{
515	int result;
516	int pfd, tfd;
517	int pid;
518	int rval = 0;
519	DBT key, data;
520	char *passfile_hold;
521	char passfile_buf[MAXPATHLEN + 2];
522	char template[] = "/etc/yppwtmp.XXXXX";
523
524	result = 1;
525	passfile = passfile_default;
526
527	key.data = argp->newpw.pw_name;
528	key.size = strlen(argp->newpw.pw_name);
529
530	/*
531	 * The superuser may add entries to the passwd maps if
532	 * rpc.yppasswdd is started with the -a flag. Paranoia
533	 * prevents me from allowing additions by default.
534	 */
535	if ((rval = yp_get_record(argp->domain, "master.passwd.byname",
536			  &key, &data, 0)) != YP_TRUE) {
537		if (rval == YP_NOKEY) {
538			yp_error("user %s not found in passwd database",
539				 argp->newpw.pw_name);
540			if (allow_additions)
541				yp_error("notice: adding user %s to \
542master.passwd database for domain %s", argp->newpw.pw_name, argp->domain);
543			else
544				yp_error("restart %s with the -a flag to \
545allow additions to be made to the password database", progname);
546		} else {
547			yp_error("database access error: %s",
548				 yperr_string(rval));
549		}
550		if (!allow_additions)
551			return(result);
552	} else {
553
554		/* Nul terminate, please. */
555		*(char *)(data.data + data.size) = '\0';
556
557		copy_yp_pass(data.data, 1, data.size);
558	}
559
560	/*
561	 * Perform a small bit of sanity checking.
562	 */
563	if (validate_master(rval == YP_TRUE ? &yp_password:NULL,&argp->newpw)){
564		yp_error("rejecting update attempt for %s: bad arguments",
565			 argp->newpw.pw_name);
566		return(result);
567	}
568
569	/*
570	 * If the caller specified a domain other than our 'default'
571	 * domain, change the path to master.passwd accordingly.
572	 */
573
574	if (strcmp(argp->domain, yppasswd_domain)) {
575		snprintf(passfile_buf, sizeof(passfile_buf),
576			"/var/yp/%s/master.passwd", argp->domain);
577		passfile = (char *)&passfile_buf;
578	}
579
580	if ((pfd = pw_lock()) < 0) {
581		return (result);
582	}
583	if ((tfd = pw_tmp()) < 0) {
584		return (result);
585	}
586
587	if (pw_copy(pfd, tfd, (struct passwd  *)&argp->newpw)) {
588		yp_error("failed to created updated password file -- \
589cleaning up and bailing out");
590		unlink(tempname);
591		return(result);
592	}
593
594	passfile_hold = mktemp((char *)&template);
595	rename(passfile, passfile_hold);
596	if (strcmp(passfile, _PATH_MASTERPASSWD)) {
597		rename(tempname, passfile);
598	} else {
599		if (pw_mkdb() < 0) {
600			yp_error("pwd_mkdb failed");
601			return(result);
602		}
603	}
604
605	switch((pid = fork())) {
606	case 0:
607		close(yp_sock);
608    		execlp(MAP_UPDATE_PATH, MAP_UPDATE, passfile,
609			argp->domain, NULL);
610    		yp_error("couldn't exec map update process: %s",
611					strerror(errno));
612		unlink(passfile);
613		rename(passfile_hold, passfile);
614    		exit(1);
615		break;
616	case -1:
617		yp_error("fork() failed: %s", strerror(errno));
618		unlink(passfile);
619		rename(passfile_hold, passfile);
620		return(result);
621		break;
622	default:
623		break;
624	}
625
626	yp_error("performed update of user %s (uid %d) domain %s",
627						argp->newpw.pw_name,
628						argp->newpw.pw_uid,
629						argp->domain);
630
631	result = 0;
632	return(result);
633}
634
635/*
636 * Pseudo-dispatcher for private 'superuser-only' update handler.
637 */
638void do_master()
639{
640	struct master_yppasswd *pw;
641
642	if ((pw = getdat(yp_sock)) == NULL) {
643		return;
644	}
645
646	yp_error("received update request from superuser on localhost");
647	sendresp(update_master(pw));
648
649	/* Remember to free args. */
650	xdr_free(xdr_master_yppasswd, (char *)pw);
651
652	return;
653}
654