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 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 /* Copyright (c) 1987, 1988 Microsoft Corporation */ 31 /* All Rights Reserved */ 32 33 #pragma ident "%Z%%M% %I% %E% SMI" 34 35 /* 36 * su [-] [name [arg ...]] change userid, `-' changes environment. 37 * If SULOG is defined, all attempts to su to another user are 38 * logged there. 39 * If CONSOLE is defined, all successful attempts to su to uid 0 40 * are also logged there. 41 * 42 * If su cannot create, open, or write entries into SULOG, 43 * (or on the CONSOLE, if defined), the entry will not 44 * be logged -- thus losing a record of the su's attempted 45 * during this period. 46 */ 47 48 #include <stdio.h> 49 #include <sys/types.h> 50 #include <sys/stat.h> 51 #include <sys/param.h> 52 #include <unistd.h> 53 #include <stdlib.h> 54 #include <crypt.h> 55 #include <pwd.h> 56 #include <shadow.h> 57 #include <time.h> 58 #include <signal.h> 59 #include <fcntl.h> 60 #include <string.h> 61 #include <locale.h> 62 #include <syslog.h> 63 #include <sys/utsname.h> 64 #include <grp.h> 65 #include <deflt.h> 66 #include <limits.h> 67 #include <errno.h> 68 #include <stdarg.h> 69 70 #ifdef DYNAMIC_SU 71 #include <security/pam_appl.h> 72 #endif 73 74 #define PATH "/usr/bin:" /* path for users other than root */ 75 #define SUPATH "/usr/sbin:/usr/bin" /* path for root */ 76 #define SUPRMT "PS1=# " /* primary prompt for root */ 77 #define ELIM 128 78 #define ROOT 0 79 #ifdef DYNAMIC_SU 80 #define EMBEDDED_NAME "embedded_su" 81 #endif /* DYNAMIC_SU */ 82 83 /* 84 * Intervals to sleep after failed su 85 */ 86 #ifndef SLEEPTIME 87 #define SLEEPTIME 4 88 #endif 89 90 #define DEFAULT_LOGIN "/etc/default/login" 91 #define DEFFILE "/etc/default/su" 92 93 94 char *Sulog, *Console; 95 char *Path, *Supath; 96 97 /* 98 * Locale variables to be propagated to "su -" environment 99 */ 100 static char *initvar; 101 static char *initenv[] = { 102 "TZ", "LANG", "LC_CTYPE", 103 "LC_NUMERIC", "LC_TIME", "LC_COLLATE", 104 "LC_MONETARY", "LC_MESSAGES", "LC_ALL", 0}; 105 static char mail[30] = { "MAIL=/var/mail/" }; 106 107 static void envalt(void); 108 static void log(char *where, char *towho, int how); 109 static void to(int sig); 110 111 enum messagemode { USAGE, ERR, WARN }; 112 static void message(enum messagemode mode, char *fmt, ...); 113 114 static char *alloc_vsprintf(const char *fmt, va_list ap1); 115 static char *tail(char *a); 116 117 /* 118 * Obsolescent Audit hooks for su command 119 */ 120 extern void audit_su_bad_authentication(void); 121 extern void audit_su_bad_username(void); 122 extern void audit_su_init_info(char *, char *); 123 extern void audit_su_reset_ai(void); 124 extern void audit_su_success(void); 125 extern void audit_su_unknown_failure(void); 126 127 #ifdef DYNAMIC_SU 128 static void validate(char *usernam); 129 static int legalenvvar(char *s); 130 static int su_conv(int, struct pam_message **, struct pam_response **, void *); 131 static int emb_su_conv(int, struct pam_message **, struct pam_response **, 132 void *); 133 static void freeresponse(int num_msg, struct pam_response **response); 134 static struct pam_conv pam_conv = {su_conv, NULL}; 135 static struct pam_conv emb_pam_conv = {emb_su_conv, NULL}; 136 static pam_handle_t *pamh; /* Authentication handle */ 137 static void quotemsg(char *fmt, ...); 138 static void readinitblock(void); 139 #endif /* DYNAMIC_SU */ 140 141 struct passwd pwd; 142 char pwdbuf[1024]; /* buffer for getpwnam_r() */ 143 char shell[] = "/usr/bin/sh"; /* default shell */ 144 char safe_shell[] = "/sbin/sh"; /* "fallback" shell */ 145 char su[PATH_MAX] = "su"; /* arg0 for exec of shprog */ 146 char homedir[PATH_MAX] = "HOME="; 147 char logname[20] = "LOGNAME="; 148 char *suprmt = SUPRMT; 149 char termtyp[PATH_MAX] = "TERM="; 150 char *term; 151 char shelltyp[PATH_MAX] = "SHELL="; 152 char *hz; 153 char tznam[PATH_MAX]; 154 char hzname[10] = "HZ="; 155 char path[PATH_MAX] = "PATH="; 156 char supath[PATH_MAX] = "PATH="; 157 char *envinit[ELIM]; 158 extern char **environ; 159 char *ttyn; 160 char *username; /* the invoker */ 161 static int dosyslog = 0; /* use syslog? */ 162 char *myname; 163 #ifdef DYNAMIC_SU 164 boolean_t embedded = B_FALSE; 165 #endif /* DYNAMIC_SU */ 166 167 int 168 main(int argc, char **argv) 169 { 170 #ifndef DYNAMIC_SU 171 struct spwd sp; 172 char spbuf[1024]; /* buffer for getspnam_r() */ 173 char *password; 174 #endif 175 char *nptr; 176 char *pshell; 177 int eflag = 0; 178 int envidx = 0; 179 uid_t uid; 180 gid_t gid; 181 char *dir, *shprog, *name; 182 char *ptr; 183 char *prog = argv[0]; 184 #ifdef DYNAMIC_SU 185 int sleeptime = SLEEPTIME; 186 char **pam_env = 0; 187 int flags = 0; 188 int retcode; 189 int idx = 0; 190 #endif /* DYNAMIC_SU */ 191 192 (void) setlocale(LC_ALL, ""); 193 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 194 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it wasn't */ 195 #endif 196 (void) textdomain(TEXT_DOMAIN); 197 198 myname = tail(argv[0]); 199 200 #if defined(DYNAMIC_SU) 201 if (strcmp(myname, EMBEDDED_NAME) == 0) { 202 embedded = B_TRUE; 203 setbuf(stdin, NULL); 204 setbuf(stdout, NULL); 205 readinitblock(); 206 } 207 #endif /* defined(DYNAMIC_SU) */ 208 209 if (argc > 1 && *argv[1] == '-') { 210 /* Explicitly check for just `-' (no trailing chars) */ 211 if (strlen(argv[1]) == 1) { 212 eflag++; /* set eflag if `-' is specified */ 213 argv++; 214 argc--; 215 } else { 216 message(USAGE, 217 gettext("Usage: %s [-] [ username [ arg ... ] ]"), 218 prog); 219 exit(1); 220 } 221 } 222 223 /* 224 * Determine specified userid, get their password file entry, 225 * and set variables to values in password file entry fields. 226 */ 227 if (argc > 1) { 228 /* 229 * Usernames can't start with a `-', so we check for that to 230 * catch bad usage (like "su - -c ls"). 231 */ 232 if (*argv[1] == '-') { 233 message(USAGE, 234 gettext("Usage: %s [-] [ username [ arg ... ] ]"), 235 prog); 236 exit(1); 237 } else 238 nptr = argv[1]; /* use valid command-line username */ 239 } else 240 nptr = "root"; /* use default "root" username */ 241 242 if (defopen(DEFFILE) == 0) { 243 244 if (Sulog = defread("SULOG=")) 245 Sulog = strdup(Sulog); 246 if (Console = defread("CONSOLE=")) 247 Console = strdup(Console); 248 if (Path = defread("PATH=")) 249 Path = strdup(Path); 250 if (Supath = defread("SUPATH=")) 251 Supath = strdup(Supath); 252 if ((ptr = defread("SYSLOG=")) != NULL) 253 dosyslog = strcmp(ptr, "YES") == 0; 254 255 (void) defopen(NULL); 256 } 257 (void) strlcat(path, (Path) ? Path : PATH, sizeof (path)); 258 (void) strlcat(supath, (Supath) ? Supath : SUPATH, sizeof (supath)); 259 260 if ((ttyn = ttyname(0)) == NULL) 261 if ((ttyn = ttyname(1)) == NULL) 262 if ((ttyn = ttyname(2)) == NULL) 263 ttyn = "/dev/???"; 264 if ((username = cuserid(NULL)) == NULL) 265 username = "(null)"; 266 267 /* 268 * if Sulog defined, create SULOG, if it does not exist, with 269 * mode read/write user. Change owner and group to root 270 */ 271 if (Sulog != NULL) { 272 (void) close(open(Sulog, O_WRONLY | O_APPEND | O_CREAT, 273 (S_IRUSR|S_IWUSR))); 274 (void) chown(Sulog, (uid_t)ROOT, (gid_t)ROOT); 275 } 276 277 #ifdef DYNAMIC_SU 278 if (pam_start(embedded ? EMBEDDED_NAME : "su", nptr, 279 embedded ? &emb_pam_conv : &pam_conv, &pamh) != PAM_SUCCESS) 280 exit(1); 281 if (pam_set_item(pamh, PAM_TTY, ttyn) != PAM_SUCCESS) 282 exit(1); 283 #endif /* DYNAMIC_SU */ 284 285 /* 286 * Save away the following for writing user-level audit records: 287 * username desired 288 * current tty 289 * whether or not username desired is expired 290 * auditinfo structure of current process 291 * uid's and gid's of current process 292 */ 293 audit_su_init_info(nptr, ttyn); 294 openlog("su", LOG_CONS, LOG_AUTH); 295 296 #ifdef DYNAMIC_SU 297 298 /* 299 * Ignore SIGQUIT and SIGINT 300 */ 301 (void) signal(SIGQUIT, SIG_IGN); 302 (void) signal(SIGINT, SIG_IGN); 303 304 /* call pam_authenticate() to authenticate the user through PAM */ 305 if (getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) 306 retcode = PAM_USER_UNKNOWN; 307 else if ((flags = (getuid() != (uid_t)ROOT)) != 0) { 308 retcode = pam_authenticate(pamh, 0); 309 } else /* root user does not need to authenticate */ 310 retcode = PAM_SUCCESS; 311 312 if (retcode != PAM_SUCCESS) { 313 /* 314 * Use the same value of sleeptime that login(1) uses. 315 * This is obtained by reading the file /etc/default/login 316 * using the def*() functions 317 */ 318 if (defopen(DEFAULT_LOGIN) == 0) { 319 if ((ptr = defread("SLEEPTIME=")) != NULL) 320 sleeptime = atoi(ptr); 321 (void) defopen((char *)NULL); 322 323 if (sleeptime < 0 || sleeptime > 5) 324 sleeptime = SLEEPTIME; 325 } 326 327 /* 328 * 1st step: log the error. 329 * 2nd step: sleep. 330 * 3rd step: print out message to user. 331 */ 332 switch (retcode) { 333 case PAM_USER_UNKNOWN: 334 audit_su_bad_username(); 335 closelog(); 336 (void) sleep(sleeptime); 337 message(ERR, gettext("Unknown id: %s"), nptr); 338 break; 339 340 case PAM_AUTH_ERR: 341 if (Sulog != NULL) 342 log(Sulog, nptr, 0); /* log entry */ 343 audit_su_bad_authentication(); 344 if (dosyslog) 345 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 346 pwd.pw_name, username, ttyn); 347 closelog(); 348 (void) sleep(sleeptime); 349 message(ERR, gettext("Sorry")); 350 break; 351 352 case PAM_CONV_ERR: 353 default: 354 audit_su_unknown_failure(); 355 if (dosyslog) 356 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 357 pwd.pw_name, username, ttyn); 358 closelog(); 359 (void) sleep(sleeptime); 360 message(ERR, gettext("Sorry")); 361 break; 362 } 363 364 (void) signal(SIGQUIT, SIG_DFL); 365 (void) signal(SIGINT, SIG_DFL); 366 exit(1); 367 } 368 if (flags) 369 validate(username); 370 if (pam_setcred(pamh, PAM_REINITIALIZE_CRED) != PAM_SUCCESS) { 371 message(ERR, gettext("unable to set credentials")); 372 exit(2); 373 } 374 audit_su_success(); 375 if (dosyslog) 376 syslog(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 377 "'su %s' succeeded for %s on %s", 378 pwd.pw_name, username, ttyn); 379 closelog(); 380 (void) signal(SIGQUIT, SIG_DFL); 381 (void) signal(SIGINT, SIG_DFL); 382 #else /* STATIC !PAM */ 383 if ((getpwnam_r(nptr, &pwd, pwdbuf, sizeof (pwdbuf)) == NULL) || 384 (getspnam_r(nptr, &sp, spbuf, sizeof (spbuf)) == NULL)) { 385 message(ERR, gettext("Unknown id: %s"), nptr); 386 audit_su_bad_username(); 387 closelog(); 388 exit(1); 389 } 390 391 /* 392 * Prompt for password if invoking user is not root or 393 * if specified(new) user requires a password 394 */ 395 if (sp.sp_pwdp[0] == '\0' || getuid() == (uid_t)ROOT) 396 goto ok; 397 password = getpass(gettext("Password:")); 398 399 if ((strcmp(sp.sp_pwdp, crypt(password, sp.sp_pwdp)) != 0)) { 400 /* clear password file entry */ 401 (void) memset((void *)spbuf, 0, sizeof (spbuf)); 402 if (Sulog != NULL) 403 log(Sulog, nptr, 0); /* log entry */ 404 message(ERR, gettext("Sorry")); 405 audit_su_bad_authentication(); 406 if (dosyslog) 407 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 408 pwd.pw_name, username, ttyn); 409 closelog(); 410 exit(2); 411 } 412 /* clear password file entry */ 413 (void) memset((void *)spbuf, 0, sizeof (spbuf)); 414 ok: 415 audit_su_reset_ai(); 416 audit_su_success(); 417 if (dosyslog) 418 syslog(getuid() == 0 ? LOG_INFO : LOG_NOTICE, 419 "'su %s' succeeded for %s on %s", 420 pwd.pw_name, username, ttyn); 421 #endif /* STATIC !PAM */ 422 423 uid = pwd.pw_uid; 424 gid = pwd.pw_gid; 425 dir = strdup(pwd.pw_dir); 426 shprog = strdup(pwd.pw_shell); 427 name = strdup(pwd.pw_name); 428 429 if (Sulog != NULL) 430 log(Sulog, nptr, 1); /* log entry */ 431 432 /* set user and group ids to specified user */ 433 434 /* set the real (and effective) GID */ 435 if (setgid(gid) == -1) { 436 message(ERR, gettext("Invalid GID")); 437 exit(2); 438 } 439 /* Initialize the supplementary group access list. */ 440 if (!nptr) 441 exit(2); 442 if (initgroups(nptr, gid) == -1) { 443 exit(2); 444 } 445 /* set the real (and effective) UID */ 446 if (setuid(uid) == -1) { 447 message(ERR, gettext("Invalid UID")); 448 exit(2); 449 } 450 451 /* 452 * If new user's shell field is neither NULL nor equal to /usr/bin/sh, 453 * set: 454 * 455 * pshell = their shell 456 * su = [-]last component of shell's pathname 457 * 458 * Otherwise, set the shell to /usr/bin/sh and set argv[0] to '[-]su'. 459 */ 460 if (shprog[0] != '\0' && strcmp(shell, shprog) != 0) { 461 char *p; 462 463 pshell = shprog; 464 (void) strcpy(su, eflag ? "-" : ""); 465 466 if ((p = strrchr(pshell, '/')) != NULL) 467 (void) strlcat(su, p + 1, sizeof (su)); 468 else 469 (void) strlcat(su, pshell, sizeof (su)); 470 } else { 471 pshell = shell; 472 (void) strcpy(su, eflag ? "-su" : "su"); 473 } 474 475 /* 476 * set environment variables for new user; 477 * arg0 for exec of shprog must now contain `-' 478 * so that environment of new user is given 479 */ 480 if (eflag) { 481 int j; 482 char *var; 483 484 if (strlen(dir) == 0) { 485 (void) strcpy(dir, "/"); 486 message(WARN, gettext("No directory! Using home=/")); 487 } 488 (void) strlcat(homedir, dir, sizeof (homedir)); 489 (void) strlcat(logname, name, sizeof (logname)); 490 if (hz = getenv("HZ")) 491 (void) strlcat(hzname, hz, sizeof (hzname)); 492 493 (void) strlcat(shelltyp, pshell, sizeof (shelltyp)); 494 495 if (chdir(dir) < 0) { 496 message(ERR, gettext("No directory!")); 497 exit(1); 498 } 499 envinit[envidx = 0] = homedir; 500 envinit[++envidx] = ((uid == (uid_t)ROOT) ? supath : path); 501 envinit[++envidx] = logname; 502 envinit[++envidx] = hzname; 503 if ((term = getenv("TERM")) != NULL) { 504 (void) strlcat(termtyp, term, sizeof (termtyp)); 505 envinit[++envidx] = termtyp; 506 } 507 envinit[++envidx] = shelltyp; 508 509 (void) strlcat(mail, name, sizeof (mail)); 510 envinit[++envidx] = mail; 511 512 /* 513 * Fetch the relevant locale/TZ environment variables from 514 * the inherited environment. 515 * 516 * We have a priority here for setting TZ. If TZ is set in 517 * in the inherited environment, that value remains top 518 * priority. If the file /etc/default/login has TIMEZONE set, 519 * that has second highest priority. 520 */ 521 tznam[0] = '\0'; 522 for (j = 0; initenv[j] != 0; j++) { 523 if (initvar = getenv(initenv[j])) { 524 525 /* 526 * Skip over values beginning with '/' for 527 * security. 528 */ 529 if (initvar[0] == '/') continue; 530 531 if (strcmp(initenv[j], "TZ") == 0) { 532 (void) strcpy(tznam, "TZ="); 533 (void) strlcat(tznam, initvar, 534 sizeof (tznam)); 535 536 } else { 537 var = (char *) 538 malloc(strlen(initenv[j]) 539 + strlen(initvar) 540 + 2); 541 (void) strcpy(var, initenv[j]); 542 (void) strcat(var, "="); 543 (void) strcat(var, initvar); 544 envinit[++envidx] = var; 545 } 546 } 547 } 548 549 /* 550 * Check if TZ was found. If not then try to read it from 551 * /etc/default/login. 552 */ 553 if (tznam[0] == '\0') { 554 if (defopen(DEFAULT_LOGIN) == 0) { 555 if (initvar = defread("TIMEZONE=")) { 556 (void) strcpy(tznam, "TZ="); 557 (void) strlcat(tznam, initvar, 558 sizeof (tznam)); 559 } 560 (void) defopen(NULL); 561 } 562 } 563 564 if (tznam[0] != '\0') 565 envinit[++envidx] = tznam; 566 567 #ifdef DYNAMIC_SU 568 /* 569 * set the PAM environment variables - 570 * check for legal environment variables 571 */ 572 if ((pam_env = pam_getenvlist(pamh)) != 0) { 573 while (pam_env[idx] != 0) { 574 if (envidx + 2 < ELIM && 575 legalenvvar(pam_env[idx])) { 576 envinit[++envidx] = pam_env[idx]; 577 } 578 idx++; 579 } 580 } 581 #endif 582 envinit[++envidx] = NULL; 583 environ = envinit; 584 } else { 585 char **pp = environ, **qq, *p; 586 587 while ((p = *pp) != NULL) { 588 if (*p == 'L' && p[1] == 'D' && p[2] == '_') { 589 for (qq = pp; (*qq = qq[1]) != NULL; qq++) 590 ; 591 /* pp is not advanced */ 592 } else { 593 pp++; 594 } 595 } 596 } 597 598 #ifdef DYNAMIC_SU 599 if (pamh) 600 (void) pam_end(pamh, PAM_SUCCESS); 601 #endif 602 603 /* 604 * if new user is root: 605 * if CONSOLE defined, log entry there; 606 * if eflag not set, change environment to that of root. 607 */ 608 if (uid == (uid_t)ROOT) { 609 if (Console != NULL) 610 if (strcmp(ttyn, Console) != 0) { 611 (void) signal(SIGALRM, to); 612 (void) alarm(30); 613 log(Console, nptr, 1); 614 (void) alarm(0); 615 } 616 if (!eflag) 617 envalt(); 618 } 619 620 /* 621 * Default for SIGCPU and SIGXFSZ. Shells inherit 622 * signal disposition from parent. And the 623 * shells should have default dispositions for these 624 * signals. 625 */ 626 (void) signal(SIGXCPU, SIG_DFL); 627 (void) signal(SIGXFSZ, SIG_DFL); 628 629 #ifdef DYNAMIC_SU 630 if (embedded) { 631 (void) puts("SUCCESS"); 632 /* 633 * After this point, we're no longer talking the 634 * embedded_su protocol, so turn it off. 635 */ 636 embedded = B_FALSE; 637 } 638 #endif /* DYNAMIC_SU */ 639 640 /* 641 * if additional arguments, exec shell program with array 642 * of pointers to arguments: 643 * -> if shell = default, then su = [-]su 644 * -> if shell != default, then su = [-]last component of 645 * shell's pathname 646 * 647 * if no additional arguments, exec shell with arg0 of su 648 * where: 649 * -> if shell = default, then su = [-]su 650 * -> if shell != default, then su = [-]last component of 651 * shell's pathname 652 */ 653 if (argc > 2) { 654 argv[1] = su; 655 (void) execv(pshell, &argv[1]); 656 } else 657 (void) execl(pshell, su, 0); 658 659 660 /* 661 * Try to clean up after an administrator who has made a mistake 662 * configuring root's shell; if root's shell is other than /sbin/sh, 663 * try exec'ing /sbin/sh instead. 664 */ 665 if ((uid == (uid_t)ROOT) && (strcmp(name, "root") == 0) && 666 (strcmp(safe_shell, pshell) != 0)) { 667 message(WARN, 668 gettext("No shell %s. Trying fallback shell %s."), 669 pshell, safe_shell); 670 671 if (eflag) { 672 (void) strcpy(su, "-sh"); 673 (void) strlcpy(shelltyp + strlen("SHELL="), 674 safe_shell, sizeof (shelltyp) - strlen("SHELL=")); 675 } else { 676 (void) strcpy(su, "sh"); 677 } 678 679 if (argc > 2) { 680 argv[1] = su; 681 (void) execv(safe_shell, &argv[1]); 682 } else { 683 (void) execl(safe_shell, su, 0); 684 } 685 message(ERR, gettext("Couldn't exec fallback shell %s: %s"), 686 safe_shell, strerror(errno)); 687 } else { 688 message(ERR, gettext("No shell")); 689 } 690 return (3); 691 } 692 693 /* 694 * Environment altering routine - 695 * This routine is called when a user is su'ing to root 696 * without specifying the - flag. 697 * The user's PATH and PS1 variables are reset 698 * to the correct value for root. 699 * All of the user's other environment variables retain 700 * their current values after the su (if they are exported). 701 */ 702 static void 703 envalt(void) 704 { 705 /* 706 * If user has PATH variable in their environment, change its value 707 * to /bin:/etc:/usr/bin ; 708 * if user does not have PATH variable, add it to the user's 709 * environment; 710 * if either of the above fail, an error message is printed. 711 */ 712 if (putenv(supath) != 0) { 713 message(ERR, 714 gettext("unable to obtain memory to expand environment")); 715 exit(4); 716 } 717 718 /* 719 * If user has PROMPT variable in their environment, change its value 720 * to # ; 721 * if user does not have PROMPT variable, add it to the user's 722 * environment; 723 * if either of the above fail, an error message is printed. 724 */ 725 if (putenv(suprmt) != 0) { 726 message(ERR, 727 gettext("unable to obtain memory to expand environment")); 728 exit(4); 729 } 730 } 731 732 /* 733 * Logging routine - 734 * where = SULOG or CONSOLE 735 * towho = specified user ( user being su'ed to ) 736 * how = 0 if su attempt failed; 1 if su attempt succeeded 737 */ 738 static void 739 log(char *where, char *towho, int how) 740 { 741 FILE *logf; 742 time_t now; 743 struct tm *tmp; 744 745 /* 746 * open SULOG or CONSOLE - if open fails, return 747 */ 748 if ((logf = fopen(where, "a")) == NULL) 749 return; 750 751 now = time(0); 752 tmp = localtime(&now); 753 754 /* 755 * write entry into SULOG or onto CONSOLE - if write fails, return 756 */ 757 (void) fprintf(logf, "SU %.2d/%.2d %.2d:%.2d %c %s %s-%s\n", 758 tmp->tm_mon + 1, tmp->tm_mday, tmp->tm_hour, tmp->tm_min, 759 how ? '+' : '-', ttyn + sizeof ("/dev/") - 1, username, towho); 760 761 (void) fclose(logf); /* close SULOG or CONSOLE */ 762 } 763 764 /*ARGSUSED*/ 765 static void 766 to(int sig) 767 {} 768 769 #ifdef DYNAMIC_SU 770 /* 771 * su_conv(): 772 * This is the conv (conversation) function called from 773 * a PAM authentication module to print error messages 774 * or garner information from the user. 775 */ 776 /*ARGSUSED*/ 777 static int 778 su_conv(int num_msg, struct pam_message **msg, struct pam_response **response, 779 void *appdata_ptr) 780 { 781 struct pam_message *m; 782 struct pam_response *r; 783 char *temp; 784 int k; 785 char respbuf[PAM_MAX_RESP_SIZE]; 786 787 if (num_msg <= 0) 788 return (PAM_CONV_ERR); 789 790 *response = (struct pam_response *)calloc(num_msg, 791 sizeof (struct pam_response)); 792 if (*response == NULL) 793 return (PAM_BUF_ERR); 794 795 k = num_msg; 796 m = *msg; 797 r = *response; 798 while (k--) { 799 800 switch (m->msg_style) { 801 802 case PAM_PROMPT_ECHO_OFF: 803 errno = 0; 804 temp = getpassphrase(m->msg); 805 if (errno == EINTR) 806 return (PAM_CONV_ERR); 807 if (temp != NULL) { 808 r->resp = strdup(temp); 809 if (r->resp == NULL) { 810 freeresponse(num_msg, response); 811 return (PAM_BUF_ERR); 812 } 813 } 814 break; 815 816 case PAM_PROMPT_ECHO_ON: 817 if (m->msg != NULL) { 818 (void) fputs(m->msg, stdout); 819 } 820 821 (void) fgets(respbuf, sizeof (respbuf), stdin); 822 temp = strchr(respbuf, '\n'); 823 if (temp != NULL) 824 *temp = '\0'; 825 826 r->resp = strdup(respbuf); 827 if (r->resp == NULL) { 828 freeresponse(num_msg, response); 829 return (PAM_BUF_ERR); 830 } 831 break; 832 833 case PAM_ERROR_MSG: 834 if (m->msg != NULL) { 835 (void) fputs(m->msg, stderr); 836 (void) fputs("\n", stderr); 837 } 838 break; 839 840 case PAM_TEXT_INFO: 841 if (m->msg != NULL) { 842 (void) fputs(m->msg, stdout); 843 (void) fputs("\n", stdout); 844 } 845 break; 846 847 default: 848 break; 849 } 850 m++; 851 r++; 852 } 853 return (PAM_SUCCESS); 854 } 855 856 /* 857 * emb_su_conv(): 858 * This is the conv (conversation) function called from 859 * a PAM authentication module to print error messages 860 * or garner information from the user. 861 * This version is used for embedded_su. 862 */ 863 /*ARGSUSED*/ 864 static int 865 emb_su_conv(int num_msg, struct pam_message **msg, 866 struct pam_response **response, void *appdata_ptr) 867 { 868 struct pam_message *m; 869 struct pam_response *r; 870 char *temp; 871 int k; 872 char respbuf[PAM_MAX_RESP_SIZE]; 873 874 if (num_msg <= 0) 875 return (PAM_CONV_ERR); 876 877 *response = (struct pam_response *)calloc(num_msg, 878 sizeof (struct pam_response)); 879 if (*response == NULL) 880 return (PAM_BUF_ERR); 881 882 /* First, send the prompts */ 883 (void) printf("CONV %d\n", num_msg); 884 k = num_msg; 885 m = *msg; 886 while (k--) { 887 switch (m->msg_style) { 888 889 case PAM_PROMPT_ECHO_OFF: 890 (void) puts("PAM_PROMPT_ECHO_OFF"); 891 goto msg_common; 892 893 case PAM_PROMPT_ECHO_ON: 894 (void) puts("PAM_PROMPT_ECHO_ON"); 895 goto msg_common; 896 897 case PAM_ERROR_MSG: 898 (void) puts("PAM_ERROR_MSG"); 899 goto msg_common; 900 901 case PAM_TEXT_INFO: 902 (void) puts("PAM_TEXT_INFO"); 903 /* fall through to msg_common */ 904 msg_common: 905 if (m->msg == NULL) 906 quotemsg(NULL); 907 else 908 quotemsg("%s", m->msg); 909 break; 910 911 default: 912 break; 913 } 914 m++; 915 } 916 917 /* Next, collect the responses */ 918 k = num_msg; 919 m = *msg; 920 r = *response; 921 while (k--) { 922 923 switch (m->msg_style) { 924 925 case PAM_PROMPT_ECHO_OFF: 926 case PAM_PROMPT_ECHO_ON: 927 (void) fgets(respbuf, sizeof (respbuf), stdin); 928 929 temp = strchr(respbuf, '\n'); 930 if (temp != NULL) 931 *temp = '\0'; 932 933 r->resp = strdup(respbuf); 934 if (r->resp == NULL) { 935 freeresponse(num_msg, response); 936 return (PAM_BUF_ERR); 937 } 938 939 break; 940 941 case PAM_ERROR_MSG: 942 case PAM_TEXT_INFO: 943 break; 944 945 default: 946 break; 947 } 948 m++; 949 r++; 950 } 951 return (PAM_SUCCESS); 952 } 953 954 static void 955 freeresponse(int num_msg, struct pam_response **response) 956 { 957 struct pam_response *r; 958 int i; 959 960 /* free responses */ 961 r = *response; 962 for (i = 0; i < num_msg; i++, r++) { 963 if (r->resp != NULL) { 964 /* Zap it in case it's a password */ 965 (void) memset(r->resp, '\0', strlen(r->resp)); 966 free(r->resp); 967 } 968 } 969 free(*response); 970 *response = NULL; 971 } 972 973 /* 974 * Print a message, applying quoting for lines starting with '.'. 975 * 976 * I18n note: \n is "safe" in all locales, and all locales use 977 * a high-bit-set character to start multibyte sequences, so 978 * scanning for a \n followed by a '.' is safe. 979 */ 980 static void 981 quotemsg(char *fmt, ...) 982 { 983 if (fmt != NULL) { 984 char *msg; 985 char *p; 986 boolean_t bol; 987 va_list v; 988 989 va_start(v, fmt); 990 msg = alloc_vsprintf(fmt, v); 991 va_end(v); 992 993 bol = B_TRUE; 994 for (p = msg; *p != '\0'; p++) { 995 if (bol) { 996 if (*p == '.') 997 (void) putchar('.'); 998 bol = B_FALSE; 999 } 1000 (void) putchar(*p); 1001 if (*p == '\n') 1002 bol = B_TRUE; 1003 } 1004 (void) putchar('\n'); 1005 free(msg); 1006 } 1007 (void) putchar('.'); 1008 (void) putchar('\n'); 1009 } 1010 1011 /* 1012 * validate - Check that the account is valid for switching to. 1013 * 1014 * If the password has expired, we must refuse the 'su' attempt 1015 * regardless of whether the password is null or not. 1016 * 1017 * If the password is NULL but has NOT expired then we allow the 1018 * su attempt to succeed. 1019 */ 1020 static void 1021 validate(char *usernam) 1022 { 1023 int error = 0; 1024 1025 if ((error = pam_acct_mgmt(pamh, 0)) != 0) { 1026 if (Sulog != NULL) 1027 log(Sulog, pwd.pw_name, 0); /* log entry */ 1028 if (error == PAM_NEW_AUTHTOK_REQD) { 1029 message(ERR, gettext("Password for user " 1030 "'%s' has expired - use passwd(1) to update it"), 1031 pwd.pw_name); 1032 audit_su_bad_authentication(); 1033 if (dosyslog) 1034 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 1035 pwd.pw_name, usernam, ttyn); 1036 closelog(); 1037 exit(1); 1038 } else { 1039 message(ERR, gettext("Sorry")); 1040 audit_su_bad_authentication(); 1041 if (dosyslog) 1042 syslog(LOG_CRIT, "'su %s' failed for %s on %s", 1043 pwd.pw_name, usernam, ttyn); 1044 closelog(); 1045 exit(3); 1046 } 1047 } 1048 } 1049 1050 static char *illegal[] = { 1051 "SHELL=", 1052 "HOME=", 1053 "LOGNAME=", 1054 #ifndef NO_MAIL 1055 "MAIL=", 1056 #endif 1057 "CDPATH=", 1058 "IFS=", 1059 "PATH=", 1060 "TZ=", 1061 "HZ=", 1062 "TERM=", 1063 0 1064 }; 1065 1066 /* 1067 * legalenvvar - can PAM modules insert this environmental variable? 1068 */ 1069 1070 static int 1071 legalenvvar(char *s) 1072 { 1073 register char **p; 1074 1075 for (p = illegal; *p; p++) 1076 if (strncmp(s, *p, strlen(*p)) == 0) 1077 return (0); 1078 1079 if (s[0] == 'L' && s[1] == 'D' && s[2] == '_') 1080 return (0); 1081 1082 return (1); 1083 } 1084 1085 /* 1086 * The embedded_su protocol allows the client application to supply 1087 * an initialization block terminated by a line with just a "." on it. 1088 * 1089 * This initialization block is currently unused, reserved for future 1090 * expansion. Ignore it. This is made very slightly more complex by 1091 * the desire to cleanly ignore input lines of any length, while still 1092 * correctly detecting a line with just a "." on it. 1093 * 1094 * I18n note: It appears that none of the Solaris-supported locales 1095 * use 0x0a for any purpose other than newline, so looking for '\n' 1096 * seems safe. 1097 * All locales use high-bit-set leadin characters for their multi-byte 1098 * sequences, so a line consisting solely of ".\n" is what it appears 1099 * to be. 1100 */ 1101 static void 1102 readinitblock(void) 1103 { 1104 char buf[100]; 1105 boolean_t bol; 1106 1107 bol = B_TRUE; 1108 for (;;) { 1109 if (fgets(buf, sizeof (buf), stdin) == NULL) 1110 return; 1111 if (bol && strcmp(buf, ".\n") == 0) 1112 return; 1113 bol = (strchr(buf, '\n') != NULL); 1114 } 1115 } 1116 #endif /* DYNAMIC_SU */ 1117 1118 /* 1119 * Report an error, either a fatal one, a warning, or a usage message, 1120 * depending on the mode parameter. 1121 */ 1122 /*ARGSUSED*/ 1123 static void 1124 message(enum messagemode mode, char *fmt, ...) 1125 { 1126 char *s; 1127 va_list v; 1128 1129 va_start(v, fmt); 1130 s = alloc_vsprintf(fmt, v); 1131 va_end(v); 1132 1133 #ifdef DYNAMIC_SU 1134 if (embedded) { 1135 if (mode == WARN) { 1136 (void) printf("CONV 1\n"); 1137 (void) printf("PAM_ERROR_MSG\n"); 1138 } else { /* ERR, USAGE */ 1139 (void) printf("ERROR\n"); 1140 } 1141 if (mode == USAGE) { 1142 quotemsg("%s", s); 1143 } else { /* ERR, WARN */ 1144 quotemsg("%s: %s", myname, s); 1145 } 1146 } else { 1147 #endif /* DYNAMIC_SU */ 1148 if (mode == USAGE) { 1149 (void) fprintf(stderr, "%s\n", s); 1150 } else { /* ERR, WARN */ 1151 (void) fprintf(stderr, "%s: %s\n", myname, s); 1152 } 1153 #ifdef DYNAMIC_SU 1154 } 1155 #endif /* DYNAMIC_SU */ 1156 1157 free(s); 1158 } 1159 1160 /* 1161 * Return a pointer to the last path component of a. 1162 */ 1163 static char * 1164 tail(char *a) 1165 { 1166 char *p; 1167 1168 p = strrchr(a, '/'); 1169 if (p == NULL) 1170 p = a; 1171 else 1172 p++; /* step over the '/' */ 1173 1174 return (p); 1175 } 1176 1177 static char * 1178 alloc_vsprintf(const char *fmt, va_list ap1) 1179 { 1180 va_list ap2; 1181 int n; 1182 char buf[1]; 1183 char *s; 1184 1185 /* 1186 * We need to scan the argument list twice. Save off a copy 1187 * of the argument list pointer(s) for the second pass. Note that 1188 * we are responsible for va_end'ing our copy. 1189 */ 1190 va_copy(ap2, ap1); 1191 1192 /* 1193 * vsnprintf into a dummy to get a length. One might 1194 * think that passing 0 as the length to snprintf would 1195 * do what we want, but it's defined not to. 1196 * 1197 * Perhaps we should sprintf into a 100 character buffer 1198 * or something like that, to avoid two calls to snprintf 1199 * in most cases. 1200 */ 1201 n = vsnprintf(buf, sizeof (buf), fmt, ap2); 1202 va_end(ap2); 1203 1204 /* 1205 * Allocate an appropriately-sized buffer. 1206 */ 1207 s = malloc(n + 1); 1208 if (s == NULL) { 1209 perror("malloc"); 1210 exit(4); 1211 } 1212 1213 (void) vsnprintf(s, n+1, fmt, ap1); 1214 1215 return (s); 1216 } 1217