opiesu.c revision 59118
1/* opiesu.c: main body of code for the su(1m) program 2 3%%% portions-copyright-cmetz-96 4Portions of this software are Copyright 1996-1998 by Craig Metz, All Rights 5Reserved. The Inner Net License Version 2 applies to these portions of 6the software. 7You should have received a copy of the license with this software. If 8you didn't get a copy, you may request one from <license@inner.net>. 9 10Portions of this software are Copyright 1995 by Randall Atkinson and Dan 11McDonald, All Rights Reserved. All Rights under this copyright are assigned 12to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and 13License Agreement applies to this software. 14 15 History: 16 17 Modified by cmetz for OPIE 2.32. Set up TERM and PATH correctly. 18 Modified by cmetz for OPIE 2.31. Fix sulog(). Replaced Getlogin() with 19 currentuser. Fixed fencepost error in month printed by sulog(). 20 Modified by cmetz for OPIE 2.3. Limit the length of TERM on full login. 21 Use HAVE_SULOG instead of DOSULOG. 22 Modified by cmetz for OPIE 2.2. Don't try to clear non-blocking I/O. 23 Use opiereadpass(). Minor speedup. Removed termios manipulation 24 -- that's opiereadpass()'s job. Change opiereadpass() calls 25 to add echo arg. Removed useless strings (I don't think that 26 removing the ucb copyright one is a problem -- please let me 27 know if I'm wrong). Use FUNCTION declaration et al. Ifdef 28 around some headers. Make everything static. Removed 29 closelog() prototype. Use the same catchexit() trickery as 30 opielogin. 31 Modified at NRL for OPIE 2.2. Changed opiestrip_crlf to 32 opiestripcrlf. 33 Modified at NRL for OPIE 2.1. Added struct group declaration. 34 Added Solaris(+others?) sulog capability. Symbol changes 35 for autoconf. Removed des_crypt.h. File renamed to 36 opiesu.c. Symbol+misc changes for autoconf. Added bletch 37 for setpriority. 38 Modified at NRL for OPIE 2.02. Added SU_STAR_CHECK (turning a bug 39 into a feature ;). Fixed Solaris shadow password problem 40 introduced in OPIE 2.01 (the shadow password structure is 41 spwd, not spasswd). 42 Modified at NRL for OPIE 2.01. Changed password lookup handling 43 to use a static structure to avoid problems with drain- 44 bamaged shadow password packages. Always log failures. 45 Make sure to close syslog by function to avoid problems 46 with drain bamaged syslog implementations. Log a few 47 interesting errors. 48 Modified at NRL for OPIE 2.0. 49 Modified at Bellcore for the S/Key Version 1 software distribution. 50 Originally from BSD. 51*/ 52 53/* 54 * Copyright (c) 1980 Regents of the University of California. 55 * All rights reserved. The Berkeley software License Agreement 56 * specifies the terms and conditions for redistribution. 57 */ 58 59#include "opie_cfg.h" 60 61#include <stdio.h> 62#if HAVE_PWD_H 63#include <pwd.h> 64#endif /* HAVE_PWD_H */ 65#include <grp.h> 66#include <syslog.h> 67#include <sys/types.h> 68#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 69#if TIME_WITH_SYS_TIME 70# include <sys/time.h> 71# include <time.h> 72#else /* TIME_WITH_SYS_TIME */ 73#if HAVE_SYS_TIME_H 74#include <sys/time.h> 75#else /* HAVE_SYS_TIME_H */ 76#include <time.h> 77#endif /* HAVE_SYS_TIME_H */ 78#endif /* TIME_WITH_SYS_TIME */ 79#include <sys/resource.h> 80#else /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 81#if TM_IN_SYS_TIME 82#include <sys/time.h> 83#else /* TM_IN_SYS_TIME */ 84#include <time.h> 85#endif /* TM_IN_SYS_TIME */ 86#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 87#if HAVE_STDLIB_H 88#include <stdlib.h> 89#endif /* HAVE_STDLIB_H */ 90#if HAVE_UNISTD_H 91#include <unistd.h> 92#endif /* HAVE_UNISTD_H */ 93#if HAVE_STRING_H 94#include <string.h> 95#endif /* HAVE_STRING_H */ 96#include <errno.h> 97 98#include "opie.h" 99 100static char userbuf[16] = "USER="; 101static char homebuf[128] = "HOME="; 102static char shellbuf[128] = "SHELL="; 103static char pathbuf[sizeof("PATH") + sizeof(DEFAULT_PATH) - 1] = "PATH="; 104static char termbuf[32] = "TERM="; 105static char *cleanenv[] = {userbuf, homebuf, shellbuf, pathbuf, 0, 0}; 106static char *user = "root"; 107static char *shell = "/bin/sh"; 108static int fulllogin; 109#if 0 110static int fastlogin; 111#else /* 0 */ 112static int force = 0; 113#endif /* 0 */ 114 115static char currentuser[65]; 116 117extern char **environ; 118static struct passwd thisuser, nouser; 119 120#if HAVE_SHADOW_H 121#include <shadow.h> 122#endif /* HAVE_SHADOW_H */ 123 124#if HAVE_CRYPT_H 125#include <crypt.h> 126#endif /* HAVE_CRYPT_H */ 127 128static VOIDRET catchexit FUNCTION_NOARGS 129{ 130 int i; 131 closelog(); 132 for (i = sysconf(_SC_OPEN_MAX); i > 2; i--) 133 close(i); 134} 135 136/* We allow the malloc()s to potentially leak data out because we can 137only call this routine about four times in the lifetime of this process 138and the kernel will free all heap memory when we exit or exec. */ 139static int lookupuser FUNCTION((name), char *name) 140{ 141 struct passwd *pwd; 142#if HAVE_SHADOW 143 struct spwd *spwd; 144#endif /* HAVE_SHADOW */ 145 146 memcpy(&thisuser, &nouser, sizeof(thisuser)); 147 148 if (!(pwd = getpwnam(name))) 149 return -1; 150 151 thisuser.pw_uid = pwd->pw_uid; 152 thisuser.pw_gid = pwd->pw_gid; 153 154 if (!(thisuser.pw_name = malloc(strlen(pwd->pw_name) + 1))) 155 goto lookupuserbad; 156 strcpy(thisuser.pw_name, pwd->pw_name); 157 158 if (!(thisuser.pw_dir = malloc(strlen(pwd->pw_dir) + 1))) 159 goto lookupuserbad; 160 strcpy(thisuser.pw_dir, pwd->pw_dir); 161 162 if (!(thisuser.pw_shell = malloc(strlen(pwd->pw_shell) + 1))) 163 goto lookupuserbad; 164 strcpy(thisuser.pw_shell, pwd->pw_shell); 165 166#if HAVE_SHADOW 167 if (!(spwd = getspnam(name))) 168 goto lookupuserbad; 169 170 pwd->pw_passwd = spwd->sp_pwdp; 171 172 endspent(); 173#endif /* HAVE_SHADOW */ 174 175 if (!(thisuser.pw_passwd = malloc(strlen(pwd->pw_passwd) + 1))) 176 goto lookupuserbad; 177 strcpy(thisuser.pw_passwd, pwd->pw_passwd); 178 179 endpwent(); 180 181#if SU_STAR_CHECK 182 return ((thisuser.pw_passwd[0] == '*') || (thisuser.pw_passwd[0] == '#')); 183#else /* SU_STAR_CHECK */ 184 return 0; 185#endif /* SU_STAR_CHECK */ 186 187lookupuserbad: 188 memcpy(&thisuser, &nouser, sizeof(thisuser)); 189 return -1; 190} 191 192static VOIDRET lsetenv FUNCTION((ename, eval, buf), char *ename AND char *eval AND char *buf) 193{ 194 register char *cp, *dp; 195 register char **ep = environ; 196 197 /* this assumes an environment variable "ename" already exists */ 198 while (dp = *ep++) { 199 for (cp = ename; *cp == *dp && *cp; cp++, dp++) 200 continue; 201 if (*cp == 0 && (*dp == '=' || *dp == 0)) { 202 strcat(buf, eval); 203 *--ep = buf; 204 return; 205 } 206 } 207} 208 209#if HAVE_SULOG 210static int sulog FUNCTION((status, who), int status AND char *who) 211{ 212 char *from; 213 char *ttynam; 214 struct tm *tm; 215 FILE *f; 216 time_t now; 217 218 if (who) 219 from = who; 220 else 221 from = currentuser; 222 223 if (!strncmp(ttynam = ttyname(2), "/dev/", 5)) 224 ttynam += 5; 225 226 now = time(NULL); 227 tm = localtime(&now); 228 229 if (!(f = fopen("/var/adm/sulog", "a"))) { 230 fprintf(stderr, "Can't update su log!\n"); 231 exit(1); 232 } 233 234 fprintf(f, "SU %02d/%02d %02d:%02d %c %s %s-%s\n", 235 tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, 236 status ? '+' : '-', ttynam, from, user); 237 fclose(f); 238} 239#endif /* HAVE_SULOG */ 240 241int main FUNCTION((argc, argv), int argc AND char *argv[]) 242{ 243 char *p; 244 struct opie opie; 245 int i; 246 char pbuf[256]; 247 char opieprompt[80]; 248 int console = 0; 249 char *argvbuf; 250 251 for (i = sysconf(_SC_OPEN_MAX); i > 2; i--) 252 close(i); 253 254 openlog("su", LOG_ODELAY, LOG_AUTH); 255 atexit(catchexit); 256 257 { 258 int argvsize = 0; 259 for (i = 0; i < argc; argvsize += strlen(argv[i++])); 260 argvsize += argc; 261 if (!(argvbuf = malloc(argvsize))) { 262 syslog(LOG_ERR, "can't allocate memory to store command line"); 263 exit(1); 264 }; 265 for (i = 0, *argvbuf = 0; i < argc;) { 266 strcat(argvbuf, argv[i]); 267 if (++i < argc) 268 strcat(argvbuf, " "); 269 }; 270 }; 271 272 strcat(pathbuf, DEFAULT_PATH); 273 274again: 275 if (argc > 1 && strcmp(argv[1], "-f") == 0) { 276#if 0 277 fastlogin++; 278#else /* 0 */ 279#if INSECURE_OVERRIDE 280 force = 1; 281#else /* INSECURE_OVERRIDE */ 282 fprintf(stderr, "Sorry, but the -f option is not supported by this build of OPIE.\n"); 283#endif /* INSECURE_OVERRIDE */ 284#endif /* 0 */ 285 argc--, argv++; 286 goto again; 287 } 288 if (argc > 1 && strcmp(argv[1], "-c") == 0) { 289 console++; 290 argc--, argv++; 291 goto again; 292 } 293 if (argc > 1 && strcmp(argv[1], "-") == 0) { 294 fulllogin++; 295 argc--; 296 argv++; 297 goto again; 298 } 299 if (argc > 1 && argv[1][0] != '-') { 300 user = argv[1]; 301 argc--; 302 argv++; 303 } 304 305 { 306 struct passwd *pwd; 307 char *p = getlogin(); 308 char buf[32]; 309 310 if ((pwd = getpwuid(getuid())) == NULL) { 311 syslog(LOG_CRIT, "'%s' failed for unknown uid %d on %s", argvbuf, getuid(), ttyname(2)); 312#if HAVE_SULOG 313 sulog(0, "unknown"); 314#endif /* HAVE_SULOG */ 315 exit(1); 316 } 317 strncpy(buf, pwd->pw_name, sizeof(buf)-1); 318 buf[sizeof(buf)-1] = 0; 319 320 if (!p) 321 p = "unknown"; 322 323 strncpy(currentuser, p, 31); 324 currentuser[31] = 0; 325 326 if (p && *p && strcmp(currentuser, buf)) { 327 strcat(currentuser, "("); 328 strcat(currentuser, buf); 329 strcat(currentuser, ")"); 330 }; 331 332 if (lookupuser(user)) { 333 syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2)); 334#if HAVE_SULOG 335 sulog(0, NULL); 336#endif /* HAVE_SULOG */ 337 fprintf(stderr, "Unknown user: %s\n", user); 338 exit(1); 339 } 340 341/* Implement the BSD "wheel group" su restriction. */ 342#if DOWHEEL 343 /* Only allow those in group zero to su to root? */ 344 if (thisuser.pw_uid == 0) { 345 struct group *gr; 346 if ((gr = getgrgid(0)) != NULL) { 347 for (i = 0; gr->gr_mem[i] != NULL; i++) 348 if (strcmp(buf, gr->gr_mem[i]) == 0) 349 goto userok; 350 fprintf(stderr, "You do not have permission to su %s\n", user); 351 exit(1); 352 } 353userok: 354 ; 355#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 356 setpriority(PRIO_PROCESS, 0, -2); 357#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 358 } 359#endif /* DOWHEEL */ 360 }; 361 362 if (!thisuser.pw_passwd[0] || getuid() == 0) 363 goto ok; 364 365 if (console) { 366 if (!opiealways(thisuser.pw_dir)) { 367 fprintf(stderr, "That account requires OTP responses.\n"); 368 exit(1); 369 }; 370 /* Get user's secret password */ 371 fprintf(stderr, "Reminder - Only use this method from the console; NEVER from remote. If you\n"); 372 fprintf(stderr, "are using telnet, xterm, or a dial-in, type ^C now or exit with no password.\n"); 373 fprintf(stderr, "Then run su without the -c parameter.\n"); 374 if (opieinsecure()) { 375 fprintf(stderr, "Sorry, but you don't seem to be on the console or a secure terminal.\n"); 376#if INSECURE_OVERRIDE 377 if (force) 378 fprintf(stderr, "Warning: Continuing could disclose your secret pass phrase to an attacker!\n"); 379 else 380#endif /* INSECURE_OVERRIDE */ 381 exit(1); 382 }; 383#if NEW_PROMPTS 384 printf("%s's system password: ", thisuser.pw_name); 385 if (!opiereadpass(pbuf, sizeof(pbuf), 0)) 386 goto error; 387#endif /* NEW_PROMPTS */ 388 } else { 389 /* Attempt an OTP challenge */ 390 i = opiechallenge(&opie, user, opieprompt); 391 printf("%s\n", opieprompt); 392#if NEW_PROMPTS 393 printf("%s's response: ", thisuser.pw_name); 394 if (!opiereadpass(pbuf, sizeof(pbuf), 1)) 395 goto error; 396#else /* NEW_PROMPTS */ 397 printf("(OTP response required)\n"); 398#endif /* NEW_PROMPTS */ 399 fflush(stdout); 400 }; 401#if !NEW_PROMPTS 402 printf("%s's password: ", thisuser.pw_name); 403 if (!opiereadpass(pbuf, sizeof(pbuf), 0)) 404 goto error; 405#endif /* !NEW_PROMPTS */ 406 407#if !NEW_PROMPTS 408 if (!pbuf[0] && !console) { 409 /* Null line entered; turn echoing back on and read again */ 410 printf(" (echo on)\n%s's password: ", thisuser.pw_name); 411 if (!opiereadpass(pbuf, sizeof(pbuf), 1)) 412 goto error; 413 } 414#endif /* !NEW_PROMPTS */ 415 416 if (console) { 417 /* Try regular password check, if allowed */ 418 if (!strcmp(crypt(pbuf, thisuser.pw_passwd), thisuser.pw_passwd)) 419 goto ok; 420 } else { 421 int i = opiegetsequence(&opie); 422 if (!opieverify(&opie, pbuf)) { 423 /* OPIE authentication succeeded */ 424 if (i < 5) 425 fprintf(stderr, "Warning: Change %s's OTP secret pass phrase NOW!\n", user); 426 else 427 if (i < 10) 428 fprintf(stderr, "Warning: Change %s's OTP secret pass phrase soon.\n", user); 429 goto ok; 430 }; 431 }; 432error: 433 if (!console) 434 opieverify(&opie, ""); 435 fprintf(stderr, "Sorry\n"); 436 syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2)); 437#if HAVE_SULOG 438 sulog(0, NULL); 439#endif /* HAVE_SULOG */ 440 exit(2); 441 442ok: 443 syslog(LOG_NOTICE, "'%s' by %s on %s", argvbuf, currentuser, ttyname(2)); 444#if HAVE_SULOG 445 sulog(1, NULL); 446#endif /* HAVE_SULOG */ 447 448 if (setgid(thisuser.pw_gid) < 0) { 449 perror("su: setgid"); 450 exit(3); 451 } 452 if (initgroups(user, thisuser.pw_gid)) { 453 fprintf(stderr, "su: initgroups failed (errno=%d)\n", errno); 454 exit(4); 455 } 456 if (setuid(thisuser.pw_uid) < 0) { 457 perror("su: setuid"); 458 exit(5); 459 } 460 if (thisuser.pw_shell && *thisuser.pw_shell) 461 shell = thisuser.pw_shell; 462 if (fulllogin) { 463 if ((p = getenv("TERM")) && (strlen(termbuf) + strlen(p) - 1 < sizeof(termbuf))) { 464 strcat(termbuf, p); 465 cleanenv[4] = termbuf; 466 } 467 environ = cleanenv; 468 } 469 if (fulllogin || strcmp(user, "root") != 0) 470 lsetenv("USER", thisuser.pw_name, userbuf); 471 lsetenv("SHELL", shell, shellbuf); 472 lsetenv("HOME", thisuser.pw_dir, homebuf); 473 474#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 475 setpriority(PRIO_PROCESS, 0, 0); 476#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 477 478#if 0 479 if (fastlogin) { 480 *argv-- = "-f"; 481 *argv = "su"; 482 } else 483#endif /* 0 */ 484 if (fulllogin) { 485 if (chdir(thisuser.pw_dir) < 0) { 486 fprintf(stderr, "No directory\n"); 487 exit(6); 488 } 489 *argv = "-su"; 490 } else { 491 *argv = "su"; 492 } 493 494 catchexit(); 495 496#if DEBUG 497 syslog(LOG_DEBUG, "execing %s", shell); 498#endif /* DEBUG */ 499 execv(shell, argv); 500 fprintf(stderr, "No shell\n"); 501 exit(7); 502} 503