122347Spst/* opiesu.c: main body of code for the su(1m) program 222347Spst 329964Sache%%% portions-copyright-cmetz-96 492906SmarkmPortions of this software are Copyright 1996-1999 by Craig Metz, All Rights 522347SpstReserved. The Inner Net License Version 2 applies to these portions of 622347Spstthe software. 722347SpstYou should have received a copy of the license with this software. If 822347Spstyou didn't get a copy, you may request one from <license@inner.net>. 922347Spst 1022347SpstPortions of this software are Copyright 1995 by Randall Atkinson and Dan 1122347SpstMcDonald, All Rights Reserved. All Rights under this copyright are assigned 1222347Spstto the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and 1322347SpstLicense Agreement applies to this software. 1422347Spst 1522347Spst History: 1622347Spst 1792906Smarkm Modified by cmetz for OPIE 2.4. Check euid on startup. Use 1892906Smarkm opiestrncpy(). 1959118Skris Modified by cmetz for OPIE 2.32. Set up TERM and PATH correctly. 2029964Sache Modified by cmetz for OPIE 2.31. Fix sulog(). Replaced Getlogin() with 2129964Sache currentuser. Fixed fencepost error in month printed by sulog(). 2222347Spst Modified by cmetz for OPIE 2.3. Limit the length of TERM on full login. 2322347Spst Use HAVE_SULOG instead of DOSULOG. 2422347Spst Modified by cmetz for OPIE 2.2. Don't try to clear non-blocking I/O. 2522347Spst Use opiereadpass(). Minor speedup. Removed termios manipulation 2622347Spst -- that's opiereadpass()'s job. Change opiereadpass() calls 2722347Spst to add echo arg. Removed useless strings (I don't think that 2822347Spst removing the ucb copyright one is a problem -- please let me 2922347Spst know if I'm wrong). Use FUNCTION declaration et al. Ifdef 3022347Spst around some headers. Make everything static. Removed 3122347Spst closelog() prototype. Use the same catchexit() trickery as 3222347Spst opielogin. 3322347Spst Modified at NRL for OPIE 2.2. Changed opiestrip_crlf to 3422347Spst opiestripcrlf. 3522347Spst Modified at NRL for OPIE 2.1. Added struct group declaration. 3622347Spst Added Solaris(+others?) sulog capability. Symbol changes 3722347Spst for autoconf. Removed des_crypt.h. File renamed to 3822347Spst opiesu.c. Symbol+misc changes for autoconf. Added bletch 3922347Spst for setpriority. 4022347Spst Modified at NRL for OPIE 2.02. Added SU_STAR_CHECK (turning a bug 4122347Spst into a feature ;). Fixed Solaris shadow password problem 4222347Spst introduced in OPIE 2.01 (the shadow password structure is 4322347Spst spwd, not spasswd). 4422347Spst Modified at NRL for OPIE 2.01. Changed password lookup handling 4522347Spst to use a static structure to avoid problems with drain- 4622347Spst bamaged shadow password packages. Always log failures. 4722347Spst Make sure to close syslog by function to avoid problems 4822347Spst with drain bamaged syslog implementations. Log a few 4922347Spst interesting errors. 5022347Spst Modified at NRL for OPIE 2.0. 5122347Spst Modified at Bellcore for the S/Key Version 1 software distribution. 5222347Spst Originally from BSD. 5322347Spst*/ 5422347Spst 5522347Spst/* 5622347Spst * Copyright (c) 1980 Regents of the University of California. 5722347Spst * All rights reserved. The Berkeley software License Agreement 5822347Spst * specifies the terms and conditions for redistribution. 5922347Spst */ 6022347Spst 6122347Spst#include "opie_cfg.h" 6222347Spst 6322347Spst#include <stdio.h> 6422347Spst#if HAVE_PWD_H 6522347Spst#include <pwd.h> 6622347Spst#endif /* HAVE_PWD_H */ 6722347Spst#include <grp.h> 6822347Spst#include <syslog.h> 6922347Spst#include <sys/types.h> 7022347Spst#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 7122347Spst#if TIME_WITH_SYS_TIME 7222347Spst# include <sys/time.h> 7322347Spst# include <time.h> 7422347Spst#else /* TIME_WITH_SYS_TIME */ 7522347Spst#if HAVE_SYS_TIME_H 7622347Spst#include <sys/time.h> 7722347Spst#else /* HAVE_SYS_TIME_H */ 7822347Spst#include <time.h> 7922347Spst#endif /* HAVE_SYS_TIME_H */ 8022347Spst#endif /* TIME_WITH_SYS_TIME */ 8122347Spst#include <sys/resource.h> 8222347Spst#else /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 8322347Spst#if TM_IN_SYS_TIME 8422347Spst#include <sys/time.h> 8522347Spst#else /* TM_IN_SYS_TIME */ 8622347Spst#include <time.h> 8722347Spst#endif /* TM_IN_SYS_TIME */ 8822347Spst#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 8922347Spst#if HAVE_STDLIB_H 9022347Spst#include <stdlib.h> 9122347Spst#endif /* HAVE_STDLIB_H */ 9222347Spst#if HAVE_UNISTD_H 9322347Spst#include <unistd.h> 9422347Spst#endif /* HAVE_UNISTD_H */ 9522347Spst#if HAVE_STRING_H 9622347Spst#include <string.h> 9722347Spst#endif /* HAVE_STRING_H */ 9822347Spst#include <errno.h> 9922347Spst 10022347Spst#include "opie.h" 10122347Spst 10222347Spststatic char userbuf[16] = "USER="; 10322347Spststatic char homebuf[128] = "HOME="; 10422347Spststatic char shellbuf[128] = "SHELL="; 10559118Skrisstatic char pathbuf[sizeof("PATH") + sizeof(DEFAULT_PATH) - 1] = "PATH="; 10622347Spststatic char termbuf[32] = "TERM="; 10722347Spststatic char *cleanenv[] = {userbuf, homebuf, shellbuf, pathbuf, 0, 0}; 10822347Spststatic char *user = "root"; 10922347Spststatic char *shell = "/bin/sh"; 11022347Spststatic int fulllogin; 11129964Sache#if 0 11222347Spststatic int fastlogin; 11329964Sache#else /* 0 */ 11429964Sachestatic int force = 0; 11529964Sache#endif /* 0 */ 11622347Spst 11729964Sachestatic char currentuser[65]; 11829964Sache 11922347Spstextern char **environ; 12022347Spststatic struct passwd thisuser, nouser; 12122347Spst 12222347Spst#if HAVE_SHADOW_H 12322347Spst#include <shadow.h> 12422347Spst#endif /* HAVE_SHADOW_H */ 12522347Spst 12622347Spst#if HAVE_CRYPT_H 12722347Spst#include <crypt.h> 12822347Spst#endif /* HAVE_CRYPT_H */ 12922347Spst 13022347Spststatic VOIDRET catchexit FUNCTION_NOARGS 13122347Spst{ 13222347Spst int i; 13322347Spst closelog(); 13422347Spst for (i = sysconf(_SC_OPEN_MAX); i > 2; i--) 13522347Spst close(i); 13622347Spst} 13722347Spst 13822347Spst/* We allow the malloc()s to potentially leak data out because we can 13922347Spstonly call this routine about four times in the lifetime of this process 14022347Spstand the kernel will free all heap memory when we exit or exec. */ 14122347Spststatic int lookupuser FUNCTION((name), char *name) 14222347Spst{ 14322347Spst struct passwd *pwd; 14422347Spst#if HAVE_SHADOW 14522347Spst struct spwd *spwd; 14622347Spst#endif /* HAVE_SHADOW */ 14722347Spst 14822347Spst memcpy(&thisuser, &nouser, sizeof(thisuser)); 14922347Spst 15022347Spst if (!(pwd = getpwnam(name))) 15122347Spst return -1; 15222347Spst 15322347Spst thisuser.pw_uid = pwd->pw_uid; 15422347Spst thisuser.pw_gid = pwd->pw_gid; 15522347Spst 15622347Spst if (!(thisuser.pw_name = malloc(strlen(pwd->pw_name) + 1))) 15722347Spst goto lookupuserbad; 15822347Spst strcpy(thisuser.pw_name, pwd->pw_name); 15922347Spst 16022347Spst if (!(thisuser.pw_dir = malloc(strlen(pwd->pw_dir) + 1))) 16122347Spst goto lookupuserbad; 16222347Spst strcpy(thisuser.pw_dir, pwd->pw_dir); 16322347Spst 16422347Spst if (!(thisuser.pw_shell = malloc(strlen(pwd->pw_shell) + 1))) 16522347Spst goto lookupuserbad; 16622347Spst strcpy(thisuser.pw_shell, pwd->pw_shell); 16722347Spst 16822347Spst#if HAVE_SHADOW 16922347Spst if (!(spwd = getspnam(name))) 17022347Spst goto lookupuserbad; 17122347Spst 17222347Spst pwd->pw_passwd = spwd->sp_pwdp; 17322347Spst 17422347Spst endspent(); 17522347Spst#endif /* HAVE_SHADOW */ 17622347Spst 17722347Spst if (!(thisuser.pw_passwd = malloc(strlen(pwd->pw_passwd) + 1))) 17822347Spst goto lookupuserbad; 17922347Spst strcpy(thisuser.pw_passwd, pwd->pw_passwd); 18022347Spst 18122347Spst endpwent(); 18222347Spst 18322347Spst#if SU_STAR_CHECK 18422347Spst return ((thisuser.pw_passwd[0] == '*') || (thisuser.pw_passwd[0] == '#')); 18522347Spst#else /* SU_STAR_CHECK */ 18622347Spst return 0; 18722347Spst#endif /* SU_STAR_CHECK */ 18822347Spst 18922347Spstlookupuserbad: 19022347Spst memcpy(&thisuser, &nouser, sizeof(thisuser)); 19122347Spst return -1; 19222347Spst} 19322347Spst 19422347Spststatic VOIDRET lsetenv FUNCTION((ename, eval, buf), char *ename AND char *eval AND char *buf) 19522347Spst{ 19622347Spst register char *cp, *dp; 19722347Spst register char **ep = environ; 19822347Spst 19922347Spst /* this assumes an environment variable "ename" already exists */ 20022347Spst while (dp = *ep++) { 20122347Spst for (cp = ename; *cp == *dp && *cp; cp++, dp++) 20222347Spst continue; 20322347Spst if (*cp == 0 && (*dp == '=' || *dp == 0)) { 20422347Spst strcat(buf, eval); 20522347Spst *--ep = buf; 20622347Spst return; 20722347Spst } 20822347Spst } 20922347Spst} 21022347Spst 21122347Spst#if HAVE_SULOG 21222347Spststatic int sulog FUNCTION((status, who), int status AND char *who) 21322347Spst{ 21422347Spst char *from; 21522347Spst char *ttynam; 21622347Spst struct tm *tm; 21722347Spst FILE *f; 21822347Spst time_t now; 21922347Spst 22022347Spst if (who) 22122347Spst from = who; 22222347Spst else 22329964Sache from = currentuser; 22422347Spst 22522347Spst if (!strncmp(ttynam = ttyname(2), "/dev/", 5)) 22622347Spst ttynam += 5; 22722347Spst 22822347Spst now = time(NULL); 22922347Spst tm = localtime(&now); 23022347Spst 23122347Spst if (!(f = fopen("/var/adm/sulog", "a"))) { 23222347Spst fprintf(stderr, "Can't update su log!\n"); 23322347Spst exit(1); 23422347Spst } 23522347Spst 23622347Spst fprintf(f, "SU %02d/%02d %02d:%02d %c %s %s-%s\n", 23729964Sache tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, 23829964Sache status ? '+' : '-', ttynam, from, user); 23922347Spst fclose(f); 24022347Spst} 24122347Spst#endif /* HAVE_SULOG */ 24222347Spst 24322347Spstint main FUNCTION((argc, argv), int argc AND char *argv[]) 24422347Spst{ 24529964Sache char *p; 24622347Spst struct opie opie; 24722347Spst int i; 24822347Spst char pbuf[256]; 24922347Spst char opieprompt[80]; 25022347Spst int console = 0; 25129964Sache char *argvbuf; 25222347Spst 25322347Spst for (i = sysconf(_SC_OPEN_MAX); i > 2; i--) 25422347Spst close(i); 25522347Spst 25629964Sache openlog("su", LOG_ODELAY, LOG_AUTH); 25729964Sache atexit(catchexit); 25822347Spst 25929964Sache { 26029964Sache int argvsize = 0; 26129964Sache for (i = 0; i < argc; argvsize += strlen(argv[i++])); 26229964Sache argvsize += argc; 26329964Sache if (!(argvbuf = malloc(argvsize))) { 26429964Sache syslog(LOG_ERR, "can't allocate memory to store command line"); 26529964Sache exit(1); 26629964Sache }; 26729964Sache for (i = 0, *argvbuf = 0; i < argc;) { 26829964Sache strcat(argvbuf, argv[i]); 26929964Sache if (++i < argc) 27029964Sache strcat(argvbuf, " "); 27129964Sache }; 27229964Sache }; 27329964Sache 27459118Skris strcat(pathbuf, DEFAULT_PATH); 27529964Sache 27622347Spstagain: 27722347Spst if (argc > 1 && strcmp(argv[1], "-f") == 0) { 27829964Sache#if 0 27922347Spst fastlogin++; 28029964Sache#else /* 0 */ 28129964Sache#if INSECURE_OVERRIDE 28229964Sache force = 1; 28329964Sache#else /* INSECURE_OVERRIDE */ 28429964Sache fprintf(stderr, "Sorry, but the -f option is not supported by this build of OPIE.\n"); 28529964Sache#endif /* INSECURE_OVERRIDE */ 28629964Sache#endif /* 0 */ 28722347Spst argc--, argv++; 28822347Spst goto again; 28922347Spst } 29022347Spst if (argc > 1 && strcmp(argv[1], "-c") == 0) { 29122347Spst console++; 29222347Spst argc--, argv++; 29322347Spst goto again; 29422347Spst } 29522347Spst if (argc > 1 && strcmp(argv[1], "-") == 0) { 29622347Spst fulllogin++; 29722347Spst argc--; 29822347Spst argv++; 29922347Spst goto again; 30022347Spst } 30122347Spst if (argc > 1 && argv[1][0] != '-') { 30222347Spst user = argv[1]; 30322347Spst argc--; 30422347Spst argv++; 30522347Spst } 30622347Spst 30792906Smarkm 30822347Spst { 30922347Spst struct passwd *pwd; 31029964Sache char *p = getlogin(); 31129964Sache char buf[32]; 31222347Spst 31322347Spst if ((pwd = getpwuid(getuid())) == NULL) { 31429964Sache syslog(LOG_CRIT, "'%s' failed for unknown uid %d on %s", argvbuf, getuid(), ttyname(2)); 31522347Spst#if HAVE_SULOG 31622347Spst sulog(0, "unknown"); 31722347Spst#endif /* HAVE_SULOG */ 31822347Spst exit(1); 31922347Spst } 32092906Smarkm opiestrncpy(buf, pwd->pw_name, sizeof(buf)); 32122347Spst 32229964Sache if (!p) 32329964Sache p = "unknown"; 32429964Sache 32592906Smarkm opiestrncpy(currentuser, p, 31); 32629964Sache 32729964Sache if (p && *p && strcmp(currentuser, buf)) { 32829964Sache strcat(currentuser, "("); 32929964Sache strcat(currentuser, buf); 33029964Sache strcat(currentuser, ")"); 33129964Sache }; 33229964Sache 33322347Spst if (lookupuser(user)) { 33429964Sache syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2)); 33522347Spst#if HAVE_SULOG 33622347Spst sulog(0, NULL); 33722347Spst#endif /* HAVE_SULOG */ 33822347Spst fprintf(stderr, "Unknown user: %s\n", user); 33922347Spst exit(1); 34022347Spst } 34122347Spst 34292906Smarkm if (geteuid()) { 34392906Smarkm syslog(LOG_CRIT, "'%s' failed for %s on %s: not running with superuser priveleges", argvbuf, currentuser, ttyname(2)); 34492906Smarkm#if HAVE_SULOG 34592906Smarkm sulog(0, NULL); 34692906Smarkm#endif /* HAVE_SULOG */ 34792906Smarkm fprintf(stderr, "You do not have permission to su %s\n", user); 34892906Smarkm exit(1); 34992906Smarkm }; 35092906Smarkm 35122347Spst/* Implement the BSD "wheel group" su restriction. */ 35222347Spst#if DOWHEEL 35322347Spst /* Only allow those in group zero to su to root? */ 35422347Spst if (thisuser.pw_uid == 0) { 35522347Spst struct group *gr; 35622347Spst if ((gr = getgrgid(0)) != NULL) { 35722347Spst for (i = 0; gr->gr_mem[i] != NULL; i++) 35822347Spst if (strcmp(buf, gr->gr_mem[i]) == 0) 35922347Spst goto userok; 36022347Spst fprintf(stderr, "You do not have permission to su %s\n", user); 36122347Spst exit(1); 36222347Spst } 36322347Spstuserok: 36422347Spst ; 36522347Spst#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 36622347Spst setpriority(PRIO_PROCESS, 0, -2); 36722347Spst#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 36822347Spst } 36922347Spst#endif /* DOWHEEL */ 37029964Sache }; 37122347Spst 37222347Spst if (!thisuser.pw_passwd[0] || getuid() == 0) 37322347Spst goto ok; 37422347Spst 37522347Spst if (console) { 37622347Spst if (!opiealways(thisuser.pw_dir)) { 37722347Spst fprintf(stderr, "That account requires OTP responses.\n"); 37822347Spst exit(1); 37922347Spst }; 38022347Spst /* Get user's secret password */ 38122347Spst fprintf(stderr, "Reminder - Only use this method from the console; NEVER from remote. If you\n"); 38222347Spst fprintf(stderr, "are using telnet, xterm, or a dial-in, type ^C now or exit with no password.\n"); 38322347Spst fprintf(stderr, "Then run su without the -c parameter.\n"); 38422347Spst if (opieinsecure()) { 38522347Spst fprintf(stderr, "Sorry, but you don't seem to be on the console or a secure terminal.\n"); 38629964Sache#if INSECURE_OVERRIDE 38729964Sache if (force) 38829964Sache fprintf(stderr, "Warning: Continuing could disclose your secret pass phrase to an attacker!\n"); 38929964Sache else 39029964Sache#endif /* INSECURE_OVERRIDE */ 39122347Spst exit(1); 39222347Spst }; 39322347Spst#if NEW_PROMPTS 39422347Spst printf("%s's system password: ", thisuser.pw_name); 39522347Spst if (!opiereadpass(pbuf, sizeof(pbuf), 0)) 39622347Spst goto error; 39722347Spst#endif /* NEW_PROMPTS */ 39822347Spst } else { 39922347Spst /* Attempt an OTP challenge */ 40022347Spst i = opiechallenge(&opie, user, opieprompt); 40122347Spst printf("%s\n", opieprompt); 40222347Spst#if NEW_PROMPTS 40322347Spst printf("%s's response: ", thisuser.pw_name); 40422347Spst if (!opiereadpass(pbuf, sizeof(pbuf), 1)) 40522347Spst goto error; 40622347Spst#else /* NEW_PROMPTS */ 40722347Spst printf("(OTP response required)\n"); 40822347Spst#endif /* NEW_PROMPTS */ 40922347Spst fflush(stdout); 41022347Spst }; 41122347Spst#if !NEW_PROMPTS 41222347Spst printf("%s's password: ", thisuser.pw_name); 41322347Spst if (!opiereadpass(pbuf, sizeof(pbuf), 0)) 41422347Spst goto error; 41522347Spst#endif /* !NEW_PROMPTS */ 41622347Spst 41722347Spst#if !NEW_PROMPTS 41822347Spst if (!pbuf[0] && !console) { 41922347Spst /* Null line entered; turn echoing back on and read again */ 42022347Spst printf(" (echo on)\n%s's password: ", thisuser.pw_name); 42122347Spst if (!opiereadpass(pbuf, sizeof(pbuf), 1)) 42222347Spst goto error; 42322347Spst } 42422347Spst#endif /* !NEW_PROMPTS */ 42522347Spst 42622347Spst if (console) { 42722347Spst /* Try regular password check, if allowed */ 42822347Spst if (!strcmp(crypt(pbuf, thisuser.pw_passwd), thisuser.pw_passwd)) 42922347Spst goto ok; 43022347Spst } else { 43122347Spst int i = opiegetsequence(&opie); 43222347Spst if (!opieverify(&opie, pbuf)) { 43322347Spst /* OPIE authentication succeeded */ 43422347Spst if (i < 5) 43522347Spst fprintf(stderr, "Warning: Change %s's OTP secret pass phrase NOW!\n", user); 43622347Spst else 43722347Spst if (i < 10) 43822347Spst fprintf(stderr, "Warning: Change %s's OTP secret pass phrase soon.\n", user); 43922347Spst goto ok; 44022347Spst }; 44122347Spst }; 44222347Spsterror: 44329964Sache if (!console) 44429964Sache opieverify(&opie, ""); 44522347Spst fprintf(stderr, "Sorry\n"); 44629964Sache syslog(LOG_CRIT, "'%s' failed for %s on %s", argvbuf, currentuser, ttyname(2)); 44722347Spst#if HAVE_SULOG 44822347Spst sulog(0, NULL); 44922347Spst#endif /* HAVE_SULOG */ 45022347Spst exit(2); 45122347Spst 45222347Spstok: 45329964Sache syslog(LOG_NOTICE, "'%s' by %s on %s", argvbuf, currentuser, ttyname(2)); 45422347Spst#if HAVE_SULOG 45522347Spst sulog(1, NULL); 45622347Spst#endif /* HAVE_SULOG */ 45722347Spst 45822347Spst if (setgid(thisuser.pw_gid) < 0) { 45922347Spst perror("su: setgid"); 46022347Spst exit(3); 46122347Spst } 46222347Spst if (initgroups(user, thisuser.pw_gid)) { 46322347Spst fprintf(stderr, "su: initgroups failed (errno=%d)\n", errno); 46422347Spst exit(4); 46522347Spst } 46622347Spst if (setuid(thisuser.pw_uid) < 0) { 46722347Spst perror("su: setuid"); 46822347Spst exit(5); 46922347Spst } 47022347Spst if (thisuser.pw_shell && *thisuser.pw_shell) 47122347Spst shell = thisuser.pw_shell; 47222347Spst if (fulllogin) { 47359118Skris if ((p = getenv("TERM")) && (strlen(termbuf) + strlen(p) - 1 < sizeof(termbuf))) { 47459118Skris strcat(termbuf, p); 47522347Spst cleanenv[4] = termbuf; 47622347Spst } 47722347Spst environ = cleanenv; 47822347Spst } 47922347Spst if (fulllogin || strcmp(user, "root") != 0) 48022347Spst lsetenv("USER", thisuser.pw_name, userbuf); 48122347Spst lsetenv("SHELL", shell, shellbuf); 48222347Spst lsetenv("HOME", thisuser.pw_dir, homebuf); 48322347Spst 48422347Spst#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H 48522347Spst setpriority(PRIO_PROCESS, 0, 0); 48622347Spst#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */ 48722347Spst 48829964Sache#if 0 48922347Spst if (fastlogin) { 49022347Spst *argv-- = "-f"; 49122347Spst *argv = "su"; 49222347Spst } else 49329964Sache#endif /* 0 */ 49422347Spst if (fulllogin) { 49522347Spst if (chdir(thisuser.pw_dir) < 0) { 49622347Spst fprintf(stderr, "No directory\n"); 49722347Spst exit(6); 49822347Spst } 49922347Spst *argv = "-su"; 50022347Spst } else { 50122347Spst *argv = "su"; 50222347Spst } 50322347Spst 50422347Spst catchexit(); 50522347Spst 50622347Spst#if DEBUG 50722347Spst syslog(LOG_DEBUG, "execing %s", shell); 50822347Spst#endif /* DEBUG */ 50922347Spst execv(shell, argv); 51022347Spst fprintf(stderr, "No shell\n"); 51122347Spst exit(7); 51222347Spst} 513