xref: /illumos-gate/usr/src/cmd/ypcmd/yppasswd/yppasswdd.c (revision eb00b1c8a31c2253a353644606388dff5b0e0275)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <signal.h>
33 #include <fcntl.h>
34 #include <ctype.h>
35 #include <string.h>
36 #include <syslog.h>
37 #include <crypt.h>
38 #include <errno.h>
39 #include <tiuser.h>
40 #include <netdir.h>
41 #include <pwd.h>
42 #include <shadow.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/wait.h>
46 #include <sys/resource.h>
47 #include <rpc/rpc.h>
48 #include <rpc/pmap_clnt.h>
49 #include <rpcsvc/yppasswd.h>
50 #include <netconfig.h>
51 #include <deflt.h>
52 
53 /* N2L includes */
54 #include <ndbm.h>
55 #include "shim.h"
56 #include "yptol.h"
57 
58 /* must match sizes in passwd */
59 #define	STRSIZE 100
60 
61 #define	DEFDIR "/etc/"
62 #define	MYPASSWD "passwd"
63 #define	MYSHADOW "shadow"
64 #define	DEFAULT_YPPASSWDD "/etc/default/yppasswdd"
65 #define	YPPASSWDD_STR "check_restricted_shell_name=1"
66 
67 /* The guts are in there */
68 extern void changepasswd(SVCXPRT *);
69 
70 static void	boilerplate(struct svc_req *rqstp, SVCXPRT *transp);
71 static void	unlimit(int lim);
72 bool_t		validloginshell(char *sh, char *arg, int);
73 int		validstr(char *str, size_t size);
74 
75 extern char  *getusershell(void);
76 extern void   setusershell(void);
77 extern void   endusershell(void);
78 
79 int  Argc;
80 char **Argv;
81 int  mflag;			/* do a make */
82 int Mstart;
83 int single = 0;
84 int nogecos = 0;
85 int noshell = 0;
86 int nopw = 0;
87 int useadjunct = 0;
88 int useshadow = 0;
89 
90 static char *defshell = "/bin/sh";
91 
92 /* These are the various reasons we might exit. */
93 enum exitstat {
94     Esuccess,
95     EminusDandfiles,
96     Emissingdir,
97     Emissingadjunct,
98     Eaccesspasswd,
99     Eaccessshadow,
100     Echdir,
101     Egetnetconfigent,
102     Et_open,
103     Enetdir_rsvdport,
104     Et_sync,
105     Et_info,
106     Esvc_create,
107     Esvc_reg,
108     Esvcrun_ret,
109     ElockFail,
110     EparseFail
111 };
112 
113 static char err_usage[] =
114 "Usage:\n"
115 "        rpc.yppasswdd [-D directory | passwd [passwd.adjunct]]\n"
116 "                      [-nopw] [-nogecos]\n"
117 "                      [-noshell] [-m arg1 arg2 ...]\n"
118 "where\n"
119 "        directory is the directory where the passwd, shadow and/or\n"
120 "        passwd.adjunct files are found (/etc by default)\n"
121 "        It should match the setting of PWDIR in /var/yp/Makefile\n\n"
122 "        Alternatively, the old 4.1.x syntax is supported where\n"
123 "        passwd is the path to the passwd file\n"
124 "        passwd.adjunct is the patch to the passwd.adjunct file\n"
125 "        NOTES:\n"
126 "         1. The -D option and the passwd/passwd.adjunct arguments are\n"
127 "            mutually exclusive\n"
128 "         2. The old syntax deprecated and will be removed in a future\n"
129 "            release\n"
130 "         3. A shadow file found in the same directory as the passwd\n"
131 "            will be assumed to contain the password information\n\n"
132 "        arguments after -m are passed to make(1S) after password changes\n"
133 "        -nopw passwords may not be changed remotely using passwd\n"
134 "        -nogecos full name may not be changed remotely using passwd or chfn\n"
135 "        -noshell shell may not be changed remotely using passwd or chsh\n";
136 
137 char passwd_file[FILENAME_MAX], shadow_file[FILENAME_MAX];
138 char lockfile[FILENAME_MAX], adjunct_file[FILENAME_MAX];
139 
140 int
141 main(int argc, char **argv)
142 {
143 	SVCXPRT *transp4, *transp6, *transpl;
144 	struct netconfig *nconf4, *nconf6, *nconfl;
145 	int i, tli4, tli6, stat;
146 	int errorflag;
147 	int dfexcl; /* -D or files, not both flag */
148 	enum exitstat exitstatus = Esuccess;
149 	int connmaxrec = RPC_MAXDATASIZE;
150 
151 	strcpy(passwd_file, DEFDIR MYPASSWD);
152 	strcpy(shadow_file, DEFDIR MYSHADOW);
153 	strcpy(lockfile, DEFDIR ".pwd.lock");
154 	strcpy(adjunct_file, DEFDIR "security/passwd.adjunct");
155 
156 	Argc = argc;
157 	Argv = argv;
158 
159 	for (i = 1, errorflag = 0, dfexcl = 0; i < argc; i++) {
160 		if (argv[i][0] == '-' && argv[i][1] == 'm') {
161 		    if (access("/usr/ccs/bin/make", X_OK) < 0)
162 			fprintf(stderr,
163 				"%s: /usr/ccs/bin/make is not available, "
164 				"ignoring -m option",
165 				argv[0]);
166 		    else {
167 			mflag++;
168 			Mstart = i;
169 			break;
170 		    }
171 		} else if (argv[i][0] == '-' && argv[i][1] == 'D') {
172 		    switch (dfexcl) {
173 		    case 0:
174 			if (++i < argc) {
175 			    strcpy(passwd_file, argv[i]);
176 			    strcpy(shadow_file, argv[i]);
177 			    strcpy(adjunct_file, argv[i]);
178 			    strcpy(lockfile, argv[i]);
179 			    if (argv[i][strlen(argv[i]) - 1] == '/') {
180 				strcat(passwd_file, MYPASSWD);
181 				strcat(shadow_file, MYSHADOW);
182 				strcat(lockfile, ".pwd.lock");
183 				strcat(adjunct_file, "security/passwd.adjunct");
184 			    } else {
185 				strcat(passwd_file, "/" MYPASSWD);
186 				strcat(shadow_file, "/" MYSHADOW);
187 				strcat(lockfile, "/.pwd.lock");
188 				strcat(adjunct_file,
189 					"/security/passwd.adjunct");
190 			    }
191 			    dfexcl++;
192 			} else {
193 			    fprintf(stderr,
194 				"rpc.yppasswdd: -D option requires a "
195 				"directory argument\n");
196 			    errorflag++;
197 			    exitstatus = Emissingdir;
198 			}
199 			break;
200 		    case 1:
201 			fprintf(stderr,
202 				"rpc.yppasswdd: cannot specify passwd/"
203 				"passwd.adjunct pathnames AND use -D\n");
204 			errorflag++;
205 			dfexcl++;
206 			exitstatus = EminusDandfiles;
207 			break;
208 		    default:
209 			break;
210 		    }
211 	/* -single: Allow user to change only one of password,  */
212 	/*		shell, or full name at a time.  (WHY?)	*/
213 	/*	else if (strcmp(argv[i], "-single") == 0)	*/
214 	/*	    single = 1;					*/
215 	/*	else if (strcmp(argv[i], "-nosingle") == 0)	*/
216 	/*	    single = 0;					*/
217 		} else if (strcmp(argv[i], "-nogecos") == 0)
218 		    nogecos = 1;
219 		else if (strcmp(argv[i], "-nopw") == 0)
220 		    nopw = 1;
221 		else if (strcmp(argv[i], "-noshell") == 0)
222 		    noshell = 1;
223 		else if (argv[i][0] != '-') {
224 			/*
225 			 * If we find a shadow file, we warn that we're
226 			 * using it in addition to warning that the user
227 			 * it using a deprecated syntax.
228 			 */
229 		    errorflag++;
230 		    switch (dfexcl) {
231 		    case 0:
232 			strcpy(passwd_file, argv[i]);
233 			memset(shadow_file, 0, sizeof (shadow_file));
234 			strncpy(shadow_file, argv[i],
235 				strrchr(argv[i], '/') - argv[i] + 1);
236 			strcat(shadow_file, MYSHADOW);
237 			fprintf(stderr,
238 				"rpc.yppasswdd: specifying the password file"
239 				" on the command line is \n"
240 				"               obsolete, "
241 				"consider using the -D option instead.\n");
242 			if (access(shadow_file, F_OK) == 0) {
243 			    fprintf(stderr,
244 				    "rpc.yppasswdd: found a shadow file in "
245 				    "the same directory as %s\n"
246 				    "               It will be used.\n",
247 				    passwd_file);
248 			}
249 			if (i + 1 < argc && argv[i+1][0] != '-') {
250 			    strcpy(adjunct_file, argv[++i]);
251 			    if (access(adjunct_file, F_OK) != 0) {
252 				fprintf(stderr,
253 					"rpc.yppasswdd: adjunct file %s "
254 					"not found\n",
255 					adjunct_file);
256 				exitstatus = Emissingadjunct;
257 			    }
258 			}
259 			dfexcl++;
260 			break;
261 		    case 1:
262 			fprintf(stderr,
263 				"rpc.yppasswdd: cannot specify passwd/"
264 				"passwd.adjunct pathnames AND use -D\n");
265 			dfexcl++;
266 			exitstatus = EminusDandfiles;
267 			break;
268 		    default:
269 			break;
270 		    }
271 		} else {
272 		    errorflag++;
273 		    fprintf(stderr,
274 			    "rpc.yppasswdd: unrecognized option %s ignored\n",
275 			    argv[i]);
276 		}
277 	}
278 
279 	if (errorflag)
280 		fprintf(stderr, err_usage);
281 
282 	if (exitstatus)
283 		exit(exitstatus);
284 
285 	if (access(passwd_file, W_OK) < 0) {
286 		fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
287 			passwd_file);
288 		exitstatus = Eaccesspasswd;
289 	}
290 	if (access(shadow_file, W_OK) == 0) {
291 		useshadow = 1;
292 	} else {
293 		/* We don't demand a shadow file unless we're looking at /etc */
294 		if (strcmp(DEFDIR MYSHADOW, shadow_file) == 0) {
295 		    fprintf(stderr, "rpc.yppasswdd: can't access %s\n",
296 				shadow_file);
297 		    exitstatus = Eaccessshadow;
298 		}
299 	}
300 	if (access(adjunct_file, W_OK) == 0) {
301 		/* using an adjunct file */
302 		useadjunct = 1;
303 	}
304 
305 	if (chdir("/var/yp") < 0) {
306 		fprintf(stderr, "rpc.yppasswdd: can't chdir to /var/yp\n");
307 		exitstatus = Echdir;
308 	}
309 
310 	if (exitstatus)
311 		exit(exitstatus);
312 
313 	if (errorflag)
314 		fprintf(stderr, "\nProceeding.\n");
315 
316 
317 	/*
318 	 * Initialize locking system.
319 	 * This is required for N2L version which accesses the DBM files.
320 	 * For the non N2L version this sets up some locking which, since non
321 	 * N2L mode does not access the DBM files, will be unused.
322 	 *
323 	 * This also sets up yptol_mode.
324 	 */
325 	if (!init_lock_system(TRUE)) {
326 		fprintf(stderr,
327 			"rpc.yppasswdd: Cant initialize locking system\n");
328 		exit(ElockFail);
329 	}
330 
331 #ifndef	DEBUG
332 	/* Close everything, but stdin/stdout/stderr */
333 	closefrom(3);
334 #endif
335 
336 	if (yptol_mode) {
337 		stat = parseConfig(NULL, NTOL_MAP_FILE);
338 		if (stat == 1) {
339 			fprintf(stderr, "yppasswdd : NIS to LDAP mapping"
340 							" inactive.\n");
341 		} else if (stat != 0) {
342 			fprintf(stderr, "yppasswdd : Aborting after NIS to LDAP"
343 							" mapping error.\n");
344 			exit(EparseFail);
345 		}
346 	}
347 
348 #ifndef	DEBUG
349 	/* Wack umask that we inherited from parent */
350 	umask(0);
351 
352 	/* Be a midwife to ourselves */
353 	if (fork())
354 		exit(Esuccess);
355 
356 	/* Disassociation is hard to do, la la la */
357 	setpgrp();
358 	setsid();
359 
360 	/* Ignore stuff */
361 	signal(SIGHUP, SIG_IGN);
362 	signal(SIGINT, SIG_IGN);
363 	signal(SIGWINCH, SIG_IGN);
364 	signal(SIGTSTP, SIG_IGN);
365 	signal(SIGTTIN, SIG_IGN);
366 	signal(SIGTTOU, SIG_IGN);
367 	signal(SIGCHLD, SIG_IGN);
368 
369 	/*
370 	 * Just in case that wasn't enough, let's fork
371 	 * again.  (per Stevens).
372 	 */
373 	if (fork())
374 		exit(Esuccess);
375 
376 	/*
377 	 * We need stdin, stdout, and stderr later when we
378 	 * fork a make(1).
379 	 */
380 	freopen("/dev/null", "r+", stdin);
381 	freopen("/dev/null", "r+", stdout);
382 	freopen("/dev/null", "r+", stderr);
383 #endif
384 
385 	openlog("yppasswdd", LOG_CONS | LOG_PID, LOG_AUTH);
386 	unlimit(RLIMIT_CPU);
387 	unlimit(RLIMIT_FSIZE);
388 
389 	/*
390 	 * Set non-blocking mode and maximum record size for
391 	 * connection oriented RPC transports.
392 	 */
393 	if (!rpc_control(RPC_SVC_CONNMAXREC_SET, &connmaxrec)) {
394 		syslog(LOG_INFO, "unable to set maximum RPC record size");
395 	}
396 
397 	nconf4 = getnetconfigent("udp");
398 	nconf6 = getnetconfigent("udp6");
399 	if (nconf4 == 0 && nconf6 == 0) {
400 		syslog(LOG_ERR, "udp/udp6 transport not supported\n");
401 		exit(Egetnetconfigent);
402 	}
403 
404 	tli4 = (nconf4 != 0) ? t_open(nconf4->nc_device, O_RDWR, NULL) : -1;
405 	tli6 = (nconf6 != 0) ? t_open(nconf6->nc_device, O_RDWR, NULL) : -1;
406 
407 	if (tli4 == -1 && tli6 == -1) {
408 		syslog(LOG_ERR, "can\'t open TLI endpoint(s)\n");
409 		exit(Et_open);
410 	}
411 
412 	if (tli4 != -1) {
413 		if (netdir_options(nconf4, ND_SET_RESERVEDPORT, tli4, NULL)) {
414 			syslog(LOG_ERR, "could not set reserved port: %s\n",
415 				netdir_sperror());
416 			exit(Enetdir_rsvdport);
417 		}
418 	}
419 	if (tli6 != -1) {
420 		if (netdir_options(nconf6, ND_SET_RESERVEDPORT, tli6, NULL)) {
421 			syslog(LOG_ERR, "could not set reserved port: %s\n",
422 				netdir_sperror());
423 			exit(Enetdir_rsvdport);
424 		}
425 	}
426 #ifdef	DEBUG
427 	{
428 		int i, tli[2];
429 		char *label[2] = {"udp", "udp6"};
430 		int state;
431 		struct t_info tinfo;
432 
433 		tli[0] = tli4;
434 		tli[1] = tli6;
435 
436 		for (i = 0; i < sizeof (tli)/sizeof (tli[0]); i++) {
437 			fprintf(stderr, "transport %s, fd = %d\n",
438 				tli[i], label[i]);
439 			if ((state = t_sync(tli[i])) < 0) {
440 				fprintf(stderr, "t_sync failed: %s\n",
441 					t_errlist[t_errno]);
442 				exit(Et_sync);
443 			}
444 			if (t_getinfo(tli[i], &tinfo) < 0) {
445 				fprintf(stderr, "t_getinfo failed: %s\n",
446 					t_errlist[t_errno]);
447 				exit(Et_info);
448 			}
449 
450 			switch (state) {
451 			case T_UNBND:
452 				fprintf(stderr, "TLI is unbound\n");
453 				break;
454 			case T_IDLE:
455 				fprintf(stderr, "TLI is idle\n");
456 				break;
457 			case T_INREL:
458 				fprintf(stderr,
459 					"other side wants to release\n");
460 				break;
461 			case T_INCON:
462 				fprintf(stderr, "T_INCON\n");
463 				break;
464 			case T_DATAXFER:
465 				fprintf(stderr, "T_DATAXFER\n");
466 				break;
467 			default:
468 				fprintf(stderr, "no state info, state = %d\n",
469 					state);
470 			}
471 		}
472 	}
473 #endif
474 	if (tli4 != -1) {
475 		rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
476 			nconf4);
477 		transp4 = svc_tli_create(tli4, nconf4, NULL, 0, 0);
478 	} else {
479 		transp4 = 0;
480 	}
481 	if (tli6 != -1) {
482 		rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
483 			nconf6);
484 		transp6 = svc_tli_create(tli6, nconf6, NULL, 0, 0);
485 	} else {
486 		transp6 = 0;
487 	}
488 	if (transp4 == 0 && transp6 == 0) {
489 		syslog(LOG_ERR, "yppasswdd: couldn't create an RPC server\n");
490 		exit(Esvc_create);
491 	}
492 	if (transp4 && !svc_reg(transp4, (ulong_t)YPPASSWDPROG,
493 			(ulong_t)YPPASSWDVERS, boilerplate, nconf4)) {
494 		syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
495 		exit(Esvc_reg);
496 	}
497 	if (transp6 && !svc_reg(transp6, (ulong_t)YPPASSWDPROG,
498 			(ulong_t)YPPASSWDVERS, boilerplate, nconf6)) {
499 		syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
500 		exit(Esvc_reg);
501 	}
502 
503 	/*
504 	 * Create a loopback RPC service for secure authentication of local
505 	 * principals -- we need this for accepting passwd updates from
506 	 * root on the master server.
507 	 */
508 	if ((nconfl = getnetconfigent("ticlts")) == NULL) {
509 	    syslog(LOG_ERR, "transport ticlts not supported\n");
510 	    exit(Egetnetconfigent);
511 	}
512 	rpcb_unset((ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS, nconfl);
513 	transpl = svc_tli_create(RPC_ANYFD, nconfl, NULL, 0, 0);
514 	if (transpl == NULL) {
515 	    syslog(LOG_ERR,
516 		"yppasswdd: couldn't create an loopback RPC server\n");
517 	    exit(Esvc_create);
518 	}
519 	if (!svc_reg(transpl, (ulong_t)YPPASSWDPROG, (ulong_t)YPPASSWDVERS,
520 			boilerplate, nconfl)) {
521 	    syslog(LOG_ERR, "yppasswdd: couldn't register yppasswdd\n");
522 	    exit(Esvc_reg);
523 	}
524 	__rpc_negotiate_uid(transpl->xp_fd);
525 	freenetconfigent(nconf4);
526 	freenetconfigent(nconf6);
527 	freenetconfigent(nconfl);
528 	svc_run();
529 	syslog(LOG_ERR, "yppasswdd: svc_run shouldn't have returned\n");
530 
531 	return (Esvcrun_ret);
532 	/* NOTREACHED */
533 }
534 
535 static void
536 boilerplate(struct svc_req *rqstp, SVCXPRT *transp)
537 {
538 	switch (rqstp->rq_proc) {
539 	case NULLPROC:
540 		if (!svc_sendreply(transp, xdr_void, (char *)0))
541 		    syslog(LOG_WARNING,
542 			"yppasswdd: couldn't reply to RPC call\n");
543 		break;
544 	case YPPASSWDPROC_UPDATE:
545 		if (yptol_mode)
546 			shim_changepasswd(transp);
547 		else
548 			changepasswd(transp);
549 		break;
550 	}
551 }
552 
553 int
554 validstr(char *str, size_t size)
555 {
556 	char c;
557 
558 	if (str == NULL || strlen(str) > size || strchr(str, ':'))
559 		return (0);
560 	while (c = *str++) {
561 		if (iscntrl(c))
562 		    return (0);
563 	}
564 	return (1);
565 }
566 
567 bool_t
568 validloginshell(char *pw_shell, char *arg, int privileged)
569 {
570 	static char newshell[STRSIZE];
571 	char *cp, *valid;
572 
573 	if (pw_shell == 0 || *pw_shell == '\0')
574 		pw_shell = defshell;
575 
576 	if ((defopen(DEFAULT_YPPASSWDD)) == 0) {
577 		if ((defread(YPPASSWDD_STR)) != NULL) {
578 			cp = strrchr(pw_shell, '/');
579 			if (cp)
580 				cp++;
581 			else
582 				cp = pw_shell;
583 
584 			if (*cp == 'r') {
585 				syslog(LOG_ERR,
586 					"yppasswdd: cannot change "
587 					"from restricted shell %s\n",
588 					pw_shell);
589 				return (0);
590 			}
591 		}
592 		(void) defopen((char *)NULL);
593 	}
594 
595 	for (valid = getusershell(); valid; valid = getusershell())
596 		if (strcmp(pw_shell, valid) == 0)
597 		    break;
598 
599 	if (valid == NULL && !privileged) {
600 		syslog(LOG_ERR, "yppasswdd: Current shell is not valid: %s\n",
601 			pw_shell);
602 		endusershell();
603 		return (0);
604 	}
605 
606 	if (arg != 0) {
607 		strncpy(newshell, arg, sizeof (newshell) - 1);
608 		newshell[sizeof (newshell) - 1] = 0;
609 	} else {
610 		endusershell();
611 		return (0);
612 	}
613 
614 	/*
615 	 * Allow user to give shell name w/o preceding pathname.
616 	 */
617 	setusershell();
618 	for (valid = getusershell(); valid; valid = getusershell()) {
619 		if (newshell[0] == '/') {
620 		    cp = valid;
621 		} else {
622 		    cp = strrchr(valid, '/');
623 		    if (cp == 0)
624 			cp = valid;
625 		    else
626 			cp++;
627 		}
628 		if (strcmp(newshell, cp) == 0)
629 		    break;
630 	}
631 
632 	if (valid == 0) {
633 		if (!privileged || newshell[0] != '/') {
634 			syslog(LOG_WARNING,
635 				"%s is unacceptable as a new shell.\n",
636 				newshell);
637 			endusershell();
638 			return (0);
639 		}
640 		valid = newshell;
641 	}
642 
643 	if (access(valid, X_OK) < 0) {
644 		syslog(LOG_WARNING, "%s is unavailable.\n", valid);
645 		endusershell();
646 		return (0);
647 	}
648 
649 	strncpy(newshell, valid, sizeof (newshell));
650 	pw_shell =  newshell;
651 	endusershell();
652 	return (1);
653 }
654 
655 static void
656 unlimit(int lim)
657 {
658 	struct rlimit rlim;
659 	rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
660 	setrlimit(lim, &rlim);
661 }
662