#include <config.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#ifdef __linux__
# include <sys/vfs.h>
#endif
#if defined(__sun) && defined(__SVR4)
# include <sys/statvfs.h>
#endif
#ifndef __TANDEM
# include <sys/file.h>
#endif
#include <stdio.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#if TIME_WITH_SYS_TIME
# include <time.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include "sudo.h"
#define TS_CURRENT 0
#define TS_OLD 1
#define TS_MISSING 2
#define TS_NOFILE 3
#define TS_ERROR 4
#define TS_MAKE_DIRS 1
#define TS_REMOVE 2
static struct tty_info {
dev_t dev;
dev_t rdev;
ino_t ino;
struct timeval ctime;
} tty_info;
static void build_timestamp __P((char **, char **));
static int timestamp_status __P((char *, char *, char *, int));
static char *expand_prompt __P((char *, char *, char *));
static void lecture __P((int));
static void update_timestamp __P((char *, char *));
static int tty_is_devpts __P((const char *));
void
check_user(validated, mode)
int validated;
int mode;
{
char *timestampdir = NULL;
char *timestampfile = NULL;
char *prompt;
struct stat sb;
int status;
if (def_tty_tickets && user_ttypath && stat(user_ttypath, &sb) == 0) {
tty_info.dev = sb.st_dev;
tty_info.ino = sb.st_ino;
tty_info.rdev = sb.st_rdev;
if (tty_is_devpts(user_ttypath))
ctim_get(&sb, &tty_info.ctime);
}
if (ISSET(mode, MODE_INVALIDATE)) {
SET(validated, FLAG_CHECK_USER);
} else {
if (user_uid == 0 || (user_uid == runas_pw->pw_uid &&
(!runas_gr || user_in_group(sudo_user.pw, runas_gr->gr_name))) ||
user_is_exempt())
return;
}
build_timestamp(×tampdir, ×tampfile);
status = timestamp_status(timestampdir, timestampfile, user_name,
TS_MAKE_DIRS);
if (status != TS_CURRENT || ISSET(validated, FLAG_CHECK_USER)) {
if (ISSET(mode, MODE_NONINTERACTIVE))
errorx(1, "sorry, a password is required to run %s", getprogname());
if (ISSET(tgetpass_flags, TGP_ASKPASS)) {
if (user_askpass == NULL)
log_error(NO_MAIL,
"no askpass program specified, try setting SUDO_ASKPASS");
} else if (!ISSET(tgetpass_flags, TGP_STDIN)) {
if (!user_ttypath && !tty_present()) {
if (user_askpass && user_display && *user_display != '\0') {
SET(tgetpass_flags, TGP_ASKPASS);
} else if (!def_visiblepw) {
log_error(NO_MAIL,
"no tty present and no askpass program specified");
}
}
}
if (!ISSET(tgetpass_flags, TGP_ASKPASS))
lecture(status);
prompt = expand_prompt(user_prompt ? user_prompt : def_passprompt,
user_name, user_shost);
verify_user(auth_pw, prompt);
}
if (ISSET(validated, VALIDATE_OK) && !ISSET(mode, MODE_INVALIDATE) && status != TS_ERROR)
update_timestamp(timestampdir, timestampfile);
efree(timestampdir);
efree(timestampfile);
}
static void
lecture(status)
int status;
{
FILE *fp;
char buf[BUFSIZ];
ssize_t nread;
if (def_lecture == never ||
(def_lecture == once && status != TS_MISSING && status != TS_ERROR))
return;
if (def_lecture_file && (fp = fopen(def_lecture_file, "r")) != NULL) {
while ((nread = fread(buf, sizeof(char), sizeof(buf), fp)) != 0)
fwrite(buf, nread, 1, stderr);
fclose(fp);
} else {
(void) fputs("\n\
WARNING: Improper use of the sudo command could lead to data loss\n\
or the deletion of important system files. Please double-check your\n\
typing when using sudo. Type \"man sudo\" for more information.\n\
\n\
To proceed, enter your password, or type Ctrl-C to abort.\n\n",
stderr);
}
}
static void
update_timestamp(timestampdir, timestampfile)
char *timestampdir;
char *timestampfile;
{
if (def_tty_tickets && !user_ttypath)
return;
if (timestamp_uid != 0)
set_perms(PERM_TIMESTAMP);
if (timestampfile) {
int fd = open(timestampfile, O_WRONLY|O_CREAT, 0600);
if (fd == -1)
log_error(NO_EXIT|USE_ERRNO, "Can't open %s", timestampfile);
else {
lock_file(fd, SUDO_LOCK);
write(fd, &tty_info, sizeof(tty_info));
close(fd);
}
} else {
if (touch(-1, timestampdir, NULL) == -1) {
if (mkdir(timestampdir, 0700) == -1)
log_error(NO_EXIT|USE_ERRNO, "Can't mkdir %s", timestampdir);
}
}
if (timestamp_uid != 0)
set_perms(PERM_ROOT);
}
static char *
expand_prompt(old_prompt, user, host)
char *old_prompt;
char *user;
char *host;
{
size_t len, n;
int subst;
char *p, *np, *new_prompt, *endp;
subst = 0;
for (p = old_prompt, len = strlen(old_prompt); *p; p++) {
if (p[0] =='%') {
switch (p[1]) {
case 'h':
p++;
len += strlen(user_shost) - 2;
subst = 1;
break;
case 'H':
p++;
len += strlen(user_host) - 2;
subst = 1;
break;
case 'p':
p++;
if (def_rootpw)
len += 2;
else if (def_targetpw || def_runaspw)
len += strlen(runas_pw->pw_name) - 2;
else
len += strlen(user_name) - 2;
subst = 1;
break;
case 'u':
p++;
len += strlen(user_name) - 2;
subst = 1;
break;
case 'U':
p++;
len += strlen(runas_pw->pw_name) - 2;
subst = 1;
break;
case '%':
p++;
len--;
subst = 1;
break;
default:
break;
}
}
}
if (subst) {
new_prompt = (char *) emalloc(++len);
endp = new_prompt + len;
for (p = old_prompt, np = new_prompt; *p; p++) {
if (p[0] =='%') {
switch (p[1]) {
case 'h':
p++;
n = strlcpy(np, user_shost, np - endp);
if (n >= np - endp)
goto oflow;
np += n;
continue;
case 'H':
p++;
n = strlcpy(np, user_host, np - endp);
if (n >= np - endp)
goto oflow;
np += n;
continue;
case 'p':
p++;
if (def_rootpw)
n = strlcpy(np, "root", np - endp);
else if (def_targetpw || def_runaspw)
n = strlcpy(np, runas_pw->pw_name, np - endp);
else
n = strlcpy(np, user_name, np - endp);
if (n >= np - endp)
goto oflow;
np += n;
continue;
case 'u':
p++;
n = strlcpy(np, user_name, np - endp);
if (n >= np - endp)
goto oflow;
np += n;
continue;
case 'U':
p++;
n = strlcpy(np, runas_pw->pw_name, np - endp);
if (n >= np - endp)
goto oflow;
np += n;
continue;
case '%':
p++;
break;
default:
break;
}
}
*np++ = *p;
if (np >= endp)
goto oflow;
}
*np = '\0';
} else
new_prompt = old_prompt;
return(new_prompt);
oflow:
errorx(1, "internal error, expand_prompt() overflow");
}
int
user_is_exempt()
{
if (!def_exempt_group)
return(FALSE);
return(user_in_group(sudo_user.pw, def_exempt_group));
}
static void
build_timestamp(timestampdir, timestampfile)
char **timestampdir;
char **timestampfile;
{
char *dirparent;
int len;
dirparent = def_timestampdir;
len = easprintf(timestampdir, "%s/%s", dirparent, user_name);
if (len >= PATH_MAX)
log_error(0, "timestamp path too long: %s", *timestampdir);
if (def_tty_tickets) {
char *p;
if ((p = strrchr(user_tty, '/')))
p++;
else
p = user_tty;
if (def_targetpw)
len = easprintf(timestampfile, "%s/%s/%s:%s", dirparent, user_name,
p, runas_pw->pw_name);
else
len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name, p);
if (len >= PATH_MAX)
log_error(0, "timestamp path too long: %s", *timestampfile);
} else if (def_targetpw) {
len = easprintf(timestampfile, "%s/%s/%s", dirparent, user_name,
runas_pw->pw_name);
if (len >= PATH_MAX)
log_error(0, "timestamp path too long: %s", *timestampfile);
} else
*timestampfile = NULL;
}
static int
timestamp_status(timestampdir, timestampfile, user, flags)
char *timestampdir;
char *timestampfile;
char *user;
int flags;
{
struct stat sb;
struct timeval boottime, mtime;
time_t now;
char *dirparent = def_timestampdir;
int status = TS_ERROR;
if (timestamp_uid != 0)
set_perms(PERM_TIMESTAMP);
if (lstat(dirparent, &sb) == 0) {
if (!S_ISDIR(sb.st_mode))
log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
dirparent, (unsigned int) sb.st_mode);
else if (sb.st_uid != timestamp_uid)
log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
dirparent, (unsigned long) sb.st_uid,
(unsigned long) timestamp_uid);
else if ((sb.st_mode & 0000022))
log_error(NO_EXIT,
"%s writable by non-owner (0%o), should be mode 0700",
dirparent, (unsigned int) sb.st_mode);
else {
if ((sb.st_mode & 0000777) != 0700)
(void) chmod(dirparent, 0700);
status = TS_MISSING;
}
} else if (errno != ENOENT) {
log_error(NO_EXIT|USE_ERRNO, "can't stat %s", dirparent);
} else {
if (ISSET(flags, TS_MAKE_DIRS)) {
if (mkdir(dirparent, S_IRWXU))
log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s",
dirparent);
else
status = TS_MISSING;
}
}
if (status == TS_ERROR) {
if (timestamp_uid != 0)
set_perms(PERM_ROOT);
return(status);
}
status = TS_ERROR;
if (lstat(timestampdir, &sb) == 0) {
if (!S_ISDIR(sb.st_mode)) {
if (S_ISREG(sb.st_mode)) {
if (unlink(timestampdir) == 0)
status = TS_MISSING;
} else
log_error(NO_EXIT, "%s exists but is not a directory (0%o)",
timestampdir, (unsigned int) sb.st_mode);
} else if (sb.st_uid != timestamp_uid)
log_error(NO_EXIT, "%s owned by uid %lu, should be uid %lu",
timestampdir, (unsigned long) sb.st_uid,
(unsigned long) timestamp_uid);
else if ((sb.st_mode & 0000022))
log_error(NO_EXIT,
"%s writable by non-owner (0%o), should be mode 0700",
timestampdir, (unsigned int) sb.st_mode);
else {
if ((sb.st_mode & 0000777) != 0700)
(void) chmod(timestampdir, 0700);
status = TS_OLD;
}
} else if (errno != ENOENT) {
log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampdir);
} else
status = TS_MISSING;
if (status == TS_MISSING && timestampfile && ISSET(flags, TS_MAKE_DIRS)) {
if (mkdir(timestampdir, S_IRWXU) == -1) {
status = TS_ERROR;
log_error(NO_EXIT|USE_ERRNO, "can't mkdir %s", timestampdir);
}
}
if (timestampfile && status != TS_ERROR) {
if (status != TS_MISSING)
status = TS_NOFILE;
if (def_tty_tickets && !user_ttypath)
goto done;
if (lstat(timestampfile, &sb) == 0) {
if (!S_ISREG(sb.st_mode)) {
status = TS_ERROR;
log_error(NO_EXIT, "%s exists but is not a regular file (0%o)",
timestampfile, (unsigned int) sb.st_mode);
} else {
if (sb.st_uid != timestamp_uid) {
log_error(NO_EXIT,
"%s owned by uid %lu, should be uid %lu",
timestampfile, (unsigned long) sb.st_uid,
(unsigned long) timestamp_uid);
(void) unlink(timestampfile);
} else if ((sb.st_mode & 0000022)) {
log_error(NO_EXIT,
"%s writable by non-owner (0%o), should be mode 0600",
timestampfile, (unsigned int) sb.st_mode);
(void) unlink(timestampfile);
} else {
if ((sb.st_mode & 0000777) != 0600)
(void) chmod(timestampfile, 0600);
if (ISSET(flags, TS_REMOVE)) {
status = TS_OLD;
} else if (sb.st_size != 0) {
struct tty_info info;
int fd = open(timestampfile, O_RDONLY, 0644);
if (fd != -1) {
if (read(fd, &info, sizeof(info)) == sizeof(info) &&
memcmp(&info, &tty_info, sizeof(info)) == 0) {
status = TS_OLD;
}
close(fd);
}
}
}
}
} else if (errno != ENOENT) {
log_error(NO_EXIT|USE_ERRNO, "can't stat %s", timestampfile);
status = TS_ERROR;
}
}
if (status == TS_OLD && !ISSET(flags, TS_REMOVE)) {
mtim_get(&sb, &mtime);
if (def_timestamp_timeout < 0 && mtime.tv_sec != 0)
status = TS_CURRENT;
else {
now = time(NULL);
if (def_timestamp_timeout &&
now - mtime.tv_sec < 60 * def_timestamp_timeout) {
if (mtime.tv_sec > now + 60 * def_timestamp_timeout * 2) {
time_t tv_sec = (time_t)mtime.tv_sec;
log_error(NO_EXIT,
"timestamp too far in the future: %20.20s",
4 + ctime(&tv_sec));
if (timestampfile)
(void) unlink(timestampfile);
else
(void) rmdir(timestampdir);
status = TS_MISSING;
} else if (get_boottime(&boottime) && timevalcmp(&mtime, &boottime, <)) {
status = TS_OLD;
} else {
status = TS_CURRENT;
}
}
}
}
done:
if (timestamp_uid != 0)
set_perms(PERM_ROOT);
return(status);
}
void
remove_timestamp(remove)
int remove;
{
struct timeval tv;
char *timestampdir, *timestampfile, *path;
int status;
build_timestamp(×tampdir, ×tampfile);
status = timestamp_status(timestampdir, timestampfile, user_name,
TS_REMOVE);
if (status == TS_OLD || status == TS_CURRENT) {
path = timestampfile ? timestampfile : timestampdir;
if (remove) {
if (timestampfile)
status = unlink(timestampfile);
else
status = rmdir(timestampdir);
if (status == -1 && errno != ENOENT) {
log_error(NO_EXIT, "can't remove %s (%s), will reset to Epoch",
path, strerror(errno));
remove = FALSE;
}
} else {
timevalclear(&tv);
if (touch(-1, path, &tv) == -1 && errno != ENOENT)
error(1, "can't reset %s to Epoch", path);
}
}
efree(timestampdir);
efree(timestampfile);
}
static int
tty_is_devpts(tty)
const char *tty;
{
int retval = FALSE;
#ifdef __linux__
struct statfs sfs;
#ifndef DEVPTS_SUPER_MAGIC
# define DEVPTS_SUPER_MAGIC 0x1cd1
#endif
if (statfs(tty, &sfs) == 0) {
if (sfs.f_type == DEVPTS_SUPER_MAGIC)
retval = TRUE;
}
#elif defined(__sun) && defined(__SVR4)
struct statvfs sfs;
if (statvfs(tty, &sfs) == 0) {
if (strcmp(sfs.f_fstr, "devices") == 0)
retval = TRUE;
}
#endif
return retval;
}