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