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