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
main(int argc,char ** argv)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
boilerplate(struct svc_req * rqstp,SVCXPRT * transp)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
validstr(char * str,size_t size)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
validloginshell(char * pw_shell,char * arg,int privileged)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
unlimit(int lim)656 unlimit(int lim)
657 {
658 struct rlimit rlim;
659 rlim.rlim_cur = rlim.rlim_max = RLIM_INFINITY;
660 setrlimit(lim, &rlim);
661 }
662