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 2004 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 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