2dfcbf1931
ftp://ftp.nrl.navy.mil/pub/security/opie/
449 lines
12 KiB
C
449 lines
12 KiB
C
/* opiesu.c: main body of code for the su(1m) program
|
|
|
|
%%% portions-copyright-cmetz
|
|
Portions of this software are Copyright 1996 by Craig Metz, All Rights
|
|
Reserved. The Inner Net License Version 2 applies to these portions of
|
|
the software.
|
|
You should have received a copy of the license with this software. If
|
|
you didn't get a copy, you may request one from <license@inner.net>.
|
|
|
|
Portions of this software are Copyright 1995 by Randall Atkinson and Dan
|
|
McDonald, All Rights Reserved. All Rights under this copyright are assigned
|
|
to the U.S. Naval Research Laboratory (NRL). The NRL Copyright Notice and
|
|
License Agreement applies to this software.
|
|
|
|
History:
|
|
|
|
Modified by cmetz for OPIE 2.3. Limit the length of TERM on full login.
|
|
Use HAVE_SULOG instead of DOSULOG.
|
|
Modified by cmetz for OPIE 2.2. Don't try to clear non-blocking I/O.
|
|
Use opiereadpass(). Minor speedup. Removed termios manipulation
|
|
-- that's opiereadpass()'s job. Change opiereadpass() calls
|
|
to add echo arg. Removed useless strings (I don't think that
|
|
removing the ucb copyright one is a problem -- please let me
|
|
know if I'm wrong). Use FUNCTION declaration et al. Ifdef
|
|
around some headers. Make everything static. Removed
|
|
closelog() prototype. Use the same catchexit() trickery as
|
|
opielogin.
|
|
Modified at NRL for OPIE 2.2. Changed opiestrip_crlf to
|
|
opiestripcrlf.
|
|
Modified at NRL for OPIE 2.1. Added struct group declaration.
|
|
Added Solaris(+others?) sulog capability. Symbol changes
|
|
for autoconf. Removed des_crypt.h. File renamed to
|
|
opiesu.c. Symbol+misc changes for autoconf. Added bletch
|
|
for setpriority.
|
|
Modified at NRL for OPIE 2.02. Added SU_STAR_CHECK (turning a bug
|
|
into a feature ;). Fixed Solaris shadow password problem
|
|
introduced in OPIE 2.01 (the shadow password structure is
|
|
spwd, not spasswd).
|
|
Modified at NRL for OPIE 2.01. Changed password lookup handling
|
|
to use a static structure to avoid problems with drain-
|
|
bamaged shadow password packages. Always log failures.
|
|
Make sure to close syslog by function to avoid problems
|
|
with drain bamaged syslog implementations. Log a few
|
|
interesting errors.
|
|
Modified at NRL for OPIE 2.0.
|
|
Modified at Bellcore for the S/Key Version 1 software distribution.
|
|
Originally from BSD.
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 1980 Regents of the University of California.
|
|
* All rights reserved. The Berkeley software License Agreement
|
|
* specifies the terms and conditions for redistribution.
|
|
*/
|
|
|
|
#include "opie_cfg.h"
|
|
|
|
#include <stdio.h>
|
|
#if HAVE_PWD_H
|
|
#include <pwd.h>
|
|
#endif /* HAVE_PWD_H */
|
|
#include <grp.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
|
|
#if TIME_WITH_SYS_TIME
|
|
# include <sys/time.h>
|
|
# include <time.h>
|
|
#else /* TIME_WITH_SYS_TIME */
|
|
#if HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#else /* HAVE_SYS_TIME_H */
|
|
#include <time.h>
|
|
#endif /* HAVE_SYS_TIME_H */
|
|
#endif /* TIME_WITH_SYS_TIME */
|
|
#include <sys/resource.h>
|
|
#else /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
|
|
#if TM_IN_SYS_TIME
|
|
#include <sys/time.h>
|
|
#else /* TM_IN_SYS_TIME */
|
|
#include <time.h>
|
|
#endif /* TM_IN_SYS_TIME */
|
|
#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
|
|
#if HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif /* HAVE_STDLIB_H */
|
|
#if HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif /* HAVE_UNISTD_H */
|
|
#if HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif /* HAVE_STRING_H */
|
|
#include <errno.h>
|
|
|
|
#include "opie.h"
|
|
|
|
static char userbuf[16] = "USER=";
|
|
static char homebuf[128] = "HOME=";
|
|
static char shellbuf[128] = "SHELL=";
|
|
static char pathbuf[128] = "PATH=";
|
|
static char termbuf[32] = "TERM=";
|
|
static char *cleanenv[] = {userbuf, homebuf, shellbuf, pathbuf, 0, 0};
|
|
static char *user = "root";
|
|
static char *shell = "/bin/sh";
|
|
static int fulllogin;
|
|
static int fastlogin;
|
|
|
|
extern char **environ;
|
|
static struct passwd thisuser, nouser;
|
|
|
|
#if HAVE_SHADOW_H
|
|
#include <shadow.h>
|
|
#endif /* HAVE_SHADOW_H */
|
|
|
|
#if HAVE_CRYPT_H
|
|
#include <crypt.h>
|
|
#endif /* HAVE_CRYPT_H */
|
|
|
|
static VOIDRET catchexit FUNCTION_NOARGS
|
|
{
|
|
int i;
|
|
closelog();
|
|
for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
|
|
close(i);
|
|
}
|
|
|
|
/* We allow the malloc()s to potentially leak data out because we can
|
|
only call this routine about four times in the lifetime of this process
|
|
and the kernel will free all heap memory when we exit or exec. */
|
|
static int lookupuser FUNCTION((name), char *name)
|
|
{
|
|
struct passwd *pwd;
|
|
#if HAVE_SHADOW
|
|
struct spwd *spwd;
|
|
#endif /* HAVE_SHADOW */
|
|
|
|
memcpy(&thisuser, &nouser, sizeof(thisuser));
|
|
|
|
if (!(pwd = getpwnam(name)))
|
|
return -1;
|
|
|
|
thisuser.pw_uid = pwd->pw_uid;
|
|
thisuser.pw_gid = pwd->pw_gid;
|
|
|
|
if (!(thisuser.pw_name = malloc(strlen(pwd->pw_name) + 1)))
|
|
goto lookupuserbad;
|
|
strcpy(thisuser.pw_name, pwd->pw_name);
|
|
|
|
if (!(thisuser.pw_dir = malloc(strlen(pwd->pw_dir) + 1)))
|
|
goto lookupuserbad;
|
|
strcpy(thisuser.pw_dir, pwd->pw_dir);
|
|
|
|
if (!(thisuser.pw_shell = malloc(strlen(pwd->pw_shell) + 1)))
|
|
goto lookupuserbad;
|
|
strcpy(thisuser.pw_shell, pwd->pw_shell);
|
|
|
|
#if HAVE_SHADOW
|
|
if (!(spwd = getspnam(name)))
|
|
goto lookupuserbad;
|
|
|
|
pwd->pw_passwd = spwd->sp_pwdp;
|
|
|
|
endspent();
|
|
#endif /* HAVE_SHADOW */
|
|
|
|
if (!(thisuser.pw_passwd = malloc(strlen(pwd->pw_passwd) + 1)))
|
|
goto lookupuserbad;
|
|
strcpy(thisuser.pw_passwd, pwd->pw_passwd);
|
|
|
|
endpwent();
|
|
|
|
#if SU_STAR_CHECK
|
|
return ((thisuser.pw_passwd[0] == '*') || (thisuser.pw_passwd[0] == '#'));
|
|
#else /* SU_STAR_CHECK */
|
|
return 0;
|
|
#endif /* SU_STAR_CHECK */
|
|
|
|
lookupuserbad:
|
|
memcpy(&thisuser, &nouser, sizeof(thisuser));
|
|
return -1;
|
|
}
|
|
|
|
static VOIDRET lsetenv FUNCTION((ename, eval, buf), char *ename AND char *eval AND char *buf)
|
|
{
|
|
register char *cp, *dp;
|
|
register char **ep = environ;
|
|
|
|
/* this assumes an environment variable "ename" already exists */
|
|
while (dp = *ep++) {
|
|
for (cp = ename; *cp == *dp && *cp; cp++, dp++)
|
|
continue;
|
|
if (*cp == 0 && (*dp == '=' || *dp == 0)) {
|
|
strcat(buf, eval);
|
|
*--ep = buf;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if HAVE_SULOG
|
|
static int sulog FUNCTION((status, who), int status AND char *who)
|
|
{
|
|
char *from;
|
|
char *ttynam;
|
|
struct tm *tm;
|
|
FILE *f;
|
|
time_t now;
|
|
|
|
if (who)
|
|
from = who;
|
|
else
|
|
from = Getlogin();
|
|
|
|
if (!strncmp(ttynam = ttyname(2), "/dev/", 5))
|
|
ttynam += 5;
|
|
|
|
now = time(NULL);
|
|
tm = localtime(&now);
|
|
|
|
if (!(f = fopen("/var/adm/sulog", "a"))) {
|
|
fprintf(stderr, "Can't update su log!\n");
|
|
exit(1);
|
|
}
|
|
|
|
fprintf(f, "SU %02d/%02d %02d:%02d %c %s %s-%s\n",
|
|
tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min,
|
|
result ? '+' : '-', ttynam, from, user);
|
|
fclose(f);
|
|
}
|
|
#endif /* HAVE_SULOG */
|
|
|
|
int main FUNCTION((argc, argv), int argc AND char *argv[])
|
|
{
|
|
char buf[1000], *p;
|
|
struct opie opie;
|
|
int i;
|
|
char pbuf[256];
|
|
char opieprompt[80];
|
|
int console = 0;
|
|
|
|
#define Getlogin() (((p = getlogin()) && *p) ? p : buf)
|
|
|
|
for (i = sysconf(_SC_OPEN_MAX); i > 2; i--)
|
|
close(i);
|
|
|
|
strcat(pathbuf, DEFAULT_PATH);
|
|
|
|
again:
|
|
if (argc > 1 && strcmp(argv[1], "-f") == 0) {
|
|
fastlogin++;
|
|
argc--, argv++;
|
|
goto again;
|
|
}
|
|
if (argc > 1 && strcmp(argv[1], "-c") == 0) {
|
|
console++;
|
|
argc--, argv++;
|
|
goto again;
|
|
}
|
|
if (argc > 1 && strcmp(argv[1], "-") == 0) {
|
|
fulllogin++;
|
|
argc--;
|
|
argv++;
|
|
goto again;
|
|
}
|
|
if (argc > 1 && argv[1][0] != '-') {
|
|
user = argv[1];
|
|
argc--;
|
|
argv++;
|
|
}
|
|
|
|
openlog("su", LOG_ODELAY, LOG_AUTH);
|
|
atexit(catchexit);
|
|
|
|
{
|
|
struct passwd *pwd;
|
|
|
|
if ((pwd = getpwuid(getuid())) == NULL) {
|
|
syslog(LOG_CRIT, "'%s' failed for unknown uid %d on %s", argv[0], getuid(), ttyname(2));
|
|
#if HAVE_SULOG
|
|
sulog(0, "unknown");
|
|
#endif /* HAVE_SULOG */
|
|
exit(1);
|
|
}
|
|
strcpy(buf, pwd->pw_name);
|
|
}
|
|
|
|
if (lookupuser(user)) {
|
|
syslog(LOG_CRIT, "'%s' failed for %s on %s", argv[0], Getlogin(), ttyname(2));
|
|
#if HAVE_SULOG
|
|
sulog(0, NULL);
|
|
#endif /* HAVE_SULOG */
|
|
fprintf(stderr, "Unknown user: %s\n", user);
|
|
exit(1);
|
|
}
|
|
|
|
/* Implement the BSD "wheel group" su restriction. */
|
|
#if DOWHEEL
|
|
/* Only allow those in group zero to su to root? */
|
|
if (thisuser.pw_uid == 0) {
|
|
struct group *gr;
|
|
if ((gr = getgrgid(0)) != NULL) {
|
|
for (i = 0; gr->gr_mem[i] != NULL; i++)
|
|
if (strcmp(buf, gr->gr_mem[i]) == 0)
|
|
goto userok;
|
|
fprintf(stderr, "You do not have permission to su %s\n", user);
|
|
exit(1);
|
|
}
|
|
userok:
|
|
;
|
|
#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
|
|
setpriority(PRIO_PROCESS, 0, -2);
|
|
#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
|
|
}
|
|
#endif /* DOWHEEL */
|
|
|
|
if (!thisuser.pw_passwd[0] || getuid() == 0)
|
|
goto ok;
|
|
|
|
if (console) {
|
|
if (!opiealways(thisuser.pw_dir)) {
|
|
fprintf(stderr, "That account requires OTP responses.\n");
|
|
exit(1);
|
|
};
|
|
/* Get user's secret password */
|
|
fprintf(stderr, "Reminder - Only use this method from the console; NEVER from remote. If you\n");
|
|
fprintf(stderr, "are using telnet, xterm, or a dial-in, type ^C now or exit with no password.\n");
|
|
fprintf(stderr, "Then run su without the -c parameter.\n");
|
|
if (opieinsecure()) {
|
|
fprintf(stderr, "Sorry, but you don't seem to be on the console or a secure terminal.\n");
|
|
exit(1);
|
|
};
|
|
#if NEW_PROMPTS
|
|
printf("%s's system password: ", thisuser.pw_name);
|
|
if (!opiereadpass(pbuf, sizeof(pbuf), 0))
|
|
goto error;
|
|
#endif /* NEW_PROMPTS */
|
|
} else {
|
|
/* Attempt an OTP challenge */
|
|
i = opiechallenge(&opie, user, opieprompt);
|
|
printf("%s\n", opieprompt);
|
|
#if NEW_PROMPTS
|
|
printf("%s's response: ", thisuser.pw_name);
|
|
if (!opiereadpass(pbuf, sizeof(pbuf), 1))
|
|
goto error;
|
|
#else /* NEW_PROMPTS */
|
|
printf("(OTP response required)\n");
|
|
#endif /* NEW_PROMPTS */
|
|
fflush(stdout);
|
|
};
|
|
#if !NEW_PROMPTS
|
|
printf("%s's password: ", thisuser.pw_name);
|
|
if (!opiereadpass(pbuf, sizeof(pbuf), 0))
|
|
goto error;
|
|
#endif /* !NEW_PROMPTS */
|
|
|
|
#if !NEW_PROMPTS
|
|
if (!pbuf[0] && !console) {
|
|
/* Null line entered; turn echoing back on and read again */
|
|
printf(" (echo on)\n%s's password: ", thisuser.pw_name);
|
|
if (!opiereadpass(pbuf, sizeof(pbuf), 1))
|
|
goto error;
|
|
}
|
|
#endif /* !NEW_PROMPTS */
|
|
|
|
if (console) {
|
|
/* Try regular password check, if allowed */
|
|
if (!strcmp(crypt(pbuf, thisuser.pw_passwd), thisuser.pw_passwd))
|
|
goto ok;
|
|
} else {
|
|
int i = opiegetsequence(&opie);
|
|
if (!opieverify(&opie, pbuf)) {
|
|
/* OPIE authentication succeeded */
|
|
if (i < 5)
|
|
fprintf(stderr, "Warning: Change %s's OTP secret pass phrase NOW!\n", user);
|
|
else
|
|
if (i < 10)
|
|
fprintf(stderr, "Warning: Change %s's OTP secret pass phrase soon.\n", user);
|
|
goto ok;
|
|
};
|
|
};
|
|
error:
|
|
opieverify(&opie, "");
|
|
fprintf(stderr, "Sorry\n");
|
|
syslog(LOG_CRIT, "'%s' failed for %s on %s", argv[0], Getlogin(), ttyname(2));
|
|
#if HAVE_SULOG
|
|
sulog(0, NULL);
|
|
#endif /* HAVE_SULOG */
|
|
exit(2);
|
|
|
|
ok:
|
|
syslog(LOG_NOTICE, "'%s' by %s on %s", argv[0], Getlogin(), ttyname(2));
|
|
#if HAVE_SULOG
|
|
sulog(1, NULL);
|
|
#endif /* HAVE_SULOG */
|
|
|
|
if (setgid(thisuser.pw_gid) < 0) {
|
|
perror("su: setgid");
|
|
exit(3);
|
|
}
|
|
if (initgroups(user, thisuser.pw_gid)) {
|
|
fprintf(stderr, "su: initgroups failed (errno=%d)\n", errno);
|
|
exit(4);
|
|
}
|
|
if (setuid(thisuser.pw_uid) < 0) {
|
|
perror("su: setuid");
|
|
exit(5);
|
|
}
|
|
if (thisuser.pw_shell && *thisuser.pw_shell)
|
|
shell = thisuser.pw_shell;
|
|
if (fulllogin) {
|
|
if (p = getenv("TERM")) {
|
|
strncpy(termbuf, p, sizeof(termbuf));
|
|
cleanenv[4] = termbuf;
|
|
}
|
|
environ = cleanenv;
|
|
}
|
|
if (fulllogin || strcmp(user, "root") != 0)
|
|
lsetenv("USER", thisuser.pw_name, userbuf);
|
|
lsetenv("SHELL", shell, shellbuf);
|
|
lsetenv("HOME", thisuser.pw_dir, homebuf);
|
|
|
|
#if HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H
|
|
setpriority(PRIO_PROCESS, 0, 0);
|
|
#endif /* HAVE_SETPRIORITY && HAVE_SYS_RESOURCE_H */
|
|
|
|
if (fastlogin) {
|
|
*argv-- = "-f";
|
|
*argv = "su";
|
|
} else
|
|
if (fulllogin) {
|
|
if (chdir(thisuser.pw_dir) < 0) {
|
|
fprintf(stderr, "No directory\n");
|
|
exit(6);
|
|
}
|
|
*argv = "-su";
|
|
} else {
|
|
*argv = "su";
|
|
}
|
|
|
|
catchexit();
|
|
|
|
#if DEBUG
|
|
syslog(LOG_DEBUG, "execing %s", shell);
|
|
#endif /* DEBUG */
|
|
execv(shell, argv);
|
|
fprintf(stderr, "No shell\n");
|
|
exit(7);
|
|
}
|