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