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