o Move lock_file() and touch() into fileops.c so visudo can use them

o Visudo now locks the sudoers temp file instead of bailing when
the temp file already exists.  This fixes the problem of stale
temp files but it does *require* that you not try to put the
temp file in a world-writable directory.  This shoud not be
an issue as the temp file should live in the same dir as sudoers.
o Visudo now only installs the temp file as sudoers if it changed.
This commit is contained in:
Todd C. Miller
1999-08-07 09:59:43 +00:00
parent 68a2b16781
commit 76148d5316
14 changed files with 306 additions and 150 deletions

16
CHANGES
View File

@@ -1106,3 +1106,19 @@ Sudo 1.5.9 released.
346) Sudo is now available under a BSD/Apache style license. This is
possible because it no longer contains any of the original 1.1 code.
347) Added configuration info when sudo is run with the -V flag by root.
348) Change visudo tmp file from /etc/stmp -> /etc/sudoers.tmp since
Solaris uses stmp for shadow temp file. Also rename _PATH_SUDO_SUDOERS
to _PATH_SUDOERS and _PATH_SUDO_STMP to _PATH_SUDOERS_TMP.
349) Added configure option to set syslog priorities.
350) Sudo now locks its log file to prevent mangled entries.
351) Visudo now locks the sudoers temp file instead of bailing when
the temp file already exists. This fixes the problem of stale
temp files but it does *require* that you not try to put the
temp file in a world-writable directory. This shoud not be
an issue as the temp file should live in the same dir as sudoers.

View File

@@ -108,9 +108,9 @@ SHELL = /bin/sh
PROGS = @PROGS@
SRCS = alloc.c check.c find_path.c getspwuid.c goodpath.c interfaces.c \
logging.c parse.c parse.lex parse.yacc sudo.c sudo_setenv.c tgetpass.c \
version.c visudo.c $(AUTH_SRCS)
SRCS = alloc.c check.c fileops.c find_path.c getspwuid.c goodpath.c \
interfaces.c logging.c parse.c parse.lex parse.yacc sudo.c \
sudo_setenv.c tgetpass.c version.c visudo.c $(AUTH_SRCS)
AUTH_SRCS = auth/afs.c auth/aix_auth.c auth/dce.c auth/fwtk.c auth/kerb4.c \
auth/kerb5.c auth/pam.c auth/passwd.c auth/rfc1938.c \
@@ -120,11 +120,11 @@ AUTH_OBJS = sudo_auth.o @AUTH_OBJS@
PARSEOBJS = sudo.tab.o lex.yy.o alloc.o
SUDOBJS = check.o getspwuid.o goodpath.o find_path.o interfaces.o logging.o \
parse.o sudo.o sudo_setenv.o tgetpass.o version.o \
SUDOBJS = check.o getspwuid.o goodpath.o fileops.o find_path.o interfaces.o \
logging.o parse.o sudo.o sudo_setenv.o tgetpass.o version.o \
$(AUTH_OBJS) $(PARSEOBJS)
VISUDOBJS = visudo.o $(PARSEOBJS)
VISUDOBJS = visudo.o fileops.o $(PARSEOBJS)
TESTOBJS = interfaces.o testsudoers.o $(PARSEOBJS)

2
TODO
View File

@@ -47,5 +47,3 @@ TODO list (most will be addressed in the next rewrite)
19) Sudo should have a separate error message for when the user is in sudoers
but not allowed to run stuff on that host, and send mail.
20) Add configure check for locking functions and lock logfile.

View File

@@ -120,10 +120,7 @@ A) You probably didn't install the gcc-fixed include files.
Q) When I run "visudo" it says "sudoers file busy, try again later."
and doesn't do anything.
A) You have a stale sudoers temporary file. The default location is
/etc/sudoers.tmp. If you delete this file visudo will be happy again,
but make sure to check that no one else is running visudo at
the time.
A) Someone else is currently editing the sudoers file with visudo.
Q) When I try to use "cd" with sudo it says "cd: command not found".
A) "cd" is a shell builtin, you can't run it as a command since

31
check.c
View File

@@ -57,13 +57,6 @@
#include <sys/file.h>
#include <pwd.h>
#include <grp.h>
#ifdef HAVE_UTIME
# ifdef HAVE_UTIME_H
# include <utime.h>
# endif /* HAVE_UTIME_H */
#else
# include "emul/utime.h"
#endif /* HAVE_UTIME */
#include "sudo.h"
@@ -81,7 +74,6 @@ static const char rcsid[] = "$Sudo$";
int user_is_exempt __P((void));
static void build_timestamp __P((char **, char **));
static int timestamp_status __P((char *, char *, char *, int));
static int touch __P((char *, time_t));
#ifndef NO_PASSWD
static char *expand_prompt __P((char *, char *, char *));
static void lecture __P((void));
@@ -265,29 +257,6 @@ user_is_exempt()
return(FALSE);
}
/*
* Update the access and modify times on a file.
*/
static int
touch(path, when)
char *path;
time_t when;
{
#ifdef HAVE_UTIME_POSIX
struct utimbuf ut, *utp;
ut.actime = ut.modtime = when;
utp = &ut;
#else
/* BSD <= 4.3 has no struct utimbuf */
time_t utp[2];
utp[0] = utp[1] = when;
#endif /* HAVE_UTIME_POSIX */
return(utime(path, utp));
}
/*
* Fills in timestampdir as well as timestampfile if using tty tickets.
*/

View File

@@ -68,8 +68,8 @@
* 4.2BSD lacks FD_* macros (we only use FD_SET and FD_ZERO)
*/
#ifndef FD_SETSIZE
#define FD_SET(fd, fds) ((fds) -> fds_bits[0] |= (1 << (fd)))
#define FD_ZERO(fds) ((fds) -> fds_bits[0] = 0)
# define FD_SET(fd, fds) ((fds) -> fds_bits[0] |= (1 << (fd)))
# define FD_ZERO(fds) ((fds) -> fds_bits[0] = 0)
#endif /* !FD_SETSIZE */
/*
@@ -85,7 +85,7 @@
# define _S_IFDIR S_IFDIR
#endif /* _S_IFDIR */
#ifndef _S_IFLNK
#define _S_IFLNK S_IFLNK
# define _S_IFLNK S_IFLNK
#endif /* _S_IFLNK */
#ifndef S_ISREG
# define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
@@ -105,7 +105,7 @@
* In case this is not defined in <sys/types.h> or <sys/select.h>
*/
#ifndef howmany
#define howmany(x, y) (((x) + ((y) - 1)) / (y))
# define howmany(x, y) (((x) + ((y) - 1)) / (y))
#endif
/*
@@ -121,14 +121,27 @@
# define STDERR_FILENO 2
#endif
/*
* These should be defined in <unistd.h> but not everyone has them.
*/
#ifndef SEEK_SET
# define SEEK_SET 0
#endif
#ifndef SEEK_CUR
# define SEEK_CUR 1
#endif
#ifndef SEEK_END
# define SEEK_END 2
#endif
/*
* BSD defines these in <sys/param.h> but others may not.
*/
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
# define MIN(a,b) (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
# define MAX(a,b) (((a)>(b))?(a):(b))
#endif
/*

View File

@@ -292,6 +292,9 @@
/* Define if you have flock(2). */
#undef HAVE_FLOCK
/* Define if you have ftruncate(2). */
#undef HAVE_FTRUNCATE
/* Define if you have snprintf(3). */
#undef HAVE_SNPRINTF

2
configure vendored
View File

@@ -5254,7 +5254,7 @@ EOF
;;
esac
for ac_func in strchr strrchr memcpy memset sysconf sigaction tzset strcasecmp seteuid
for ac_func in strchr strrchr memcpy memset sysconf sigaction tzset strcasecmp seteuid ftruncate
do
echo $ac_n "checking for $ac_func""... $ac_c" 1>&6
echo "configure:5261: checking for $ac_func" >&5

View File

@@ -1349,7 +1349,7 @@ esac
dnl
dnl Function checks
dnl
AC_CHECK_FUNCS(strchr strrchr memcpy memset sysconf sigaction tzset strcasecmp seteuid)
AC_CHECK_FUNCS(strchr strrchr memcpy memset sysconf sigaction tzset strcasecmp seteuid ftruncate)
if test -n "$SECUREWARE"; then
AC_CHECK_FUNCS(bigcrypt)
AC_CHECK_FUNCS(set_auth_parameters)

149
fileops.c Normal file
View File

@@ -0,0 +1,149 @@
/*
* Copyright (c) 1999 Todd C. Miller <Todd.Miller@courtesan.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 4. Products derived from this software may not be called "Sudo" nor
* may "Sudo" appear in their names without specific prior written
* permission from the author.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /* HAVE_UNISTD_H */
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/param.h>
#ifdef HAVE_UTIME
# ifdef HAVE_UTIME_H
# include <utime.h>
# endif /* HAVE_UTIME_H */
#else
# include "emul/utime.h"
#endif /* HAVE_UTIME */
#include "sudo.h"
#ifndef lint
static const char rcsid[] = "$Sudo$";
#endif /* lint */
/*
* Update the access and modify times on a file.
*/
int
touch(path, when)
char *path;
time_t when;
{
#ifdef HAVE_UTIME_POSIX
struct utimbuf ut, *utp;
ut.actime = ut.modtime = when;
utp = &ut;
#else
/* BSD <= 4.3 has no struct utimbuf */
time_t utp[2];
utp[0] = utp[1] = when;
#endif /* HAVE_UTIME_POSIX */
return(utime(path, utp));
}
/*
* Lock/unlock a file.
*/
#ifdef HAVE_LOCKF
int
lock_file(fd, lockit)
int fd;
int lockit;
{
int op = 0;
switch (lockit) {
case SUDO_LOCK:
op = F_LOCK;
break;
case SUDO_TLOCK:
op = F_TLOCK;
break;
case SUDO_UNLOCK:
op = F_ULOCK;
break;
}
return(lockf(fd, op, 0) == 0);
}
#elif HAVE_FLOCK
int
lock_file(fd, lockit)
int fd;
int lockit;
{
int op = 0;
switch (lockit) {
case SUDO_LOCK:
op = LOCK_EX;
break;
case SUDO_TLOCK:
op = LOCK_EX | LOCK_NB;
break;
case SUDO_UNLOCK:
op = LOCK_EX;
break;
}
return(flock(fd, op) == 0);
}
#else
int
lock_file(fd, lockit)
int fd;
int lockit;
{
#ifdef F_SETLK
int func;
struct flock lock;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = getpid();
lock.l_type = (lockit == SUDO_UNLOCK) ? F_UNLCK : F_WRLCK;
lock.l_whence = SEEK_SET;
func = (lockit == SUDO_TLOCK) ? F_SETLK : F_SETLKW;
return(fcntl(fd, func, &lock) == 0);
#else
return(TRUE);
#endif
}
#endif

View File

@@ -47,7 +47,6 @@
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif /* HAVE_STRINGS_H */
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <time.h>
@@ -68,7 +67,6 @@ static void do_syslog __P((int, char *));
#endif
#if (LOGGING & SLOG_FILE)
static void do_logfile __P((char *));
static int lock_file __P((FILE *, int));
#endif
static void send_mail __P((char *));
@@ -179,7 +177,7 @@ do_logfile(msg)
_PATH_SUDO_LOGFILE, strerror(errno));
send_mail(full_line);
free(full_line);
} else if (!lock_file(fp, TRUE)) {
} else if (!lock_file(fileno(fp), SUDO_LOCK)) {
easprintf(&full_line, "Can't lock log file: %s: %s",
_PATH_SUDO_LOGFILE, strerror(errno));
send_mail(full_line);
@@ -252,7 +250,7 @@ do_logfile(msg)
free(full_line);
# endif
(void) fflush(fp);
(void) lock_file(fp, FALSE);
(void) lock_file(fileno(fp), SUDO_UNLOCK);
(void) fclose(fp);
}
@@ -572,50 +570,3 @@ reapchild(sig)
#endif /* POSIX_SIGNALS */
errno = serrno;
}
/*
* Lock/unlock a file.
*/
#ifdef HAVE_LOCKF
static int
lock_file(fp, lockit)
FILE *fp;
int lockit;
{
int op;
op = lockit ? F_LOCK : F_ULOCK;
return(lockf(fileno(fp), op, 0) == 0);
}
#elif HAVE_FLOCK
static int
lock_file(fp, lockit)
FILE *fp;
int lockit;
{
int op;
op = lockit ? LOCK_EX : LOCK_UN;
return(flock(fileno(fp), op) == 0);
}
#else
static int
lock_file(fp, lockit)
FILE *fp;
int lockit;
{
#ifdef F_SETLK
struct flock lock;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = getpid();
lock.l_type = lockit ? F_WRLCK : F_UNLCK;
lock.l_whence = SEEK_SET;
return(fcntl(fileno(fp), F_SETLKW, &lock) == 0);
#else
return(TRUE);
#endif
}
#endif

9
sudo.h
View File

@@ -133,6 +133,13 @@ struct sudo_user {
*/
#define SUDO_PASS_MAX 256
/*
* Flags for lock_file()
*/
#define SUDO_LOCK 1 /* lock a file */
#define SUDO_TLOCK 2 /* test & lock a file (non-blocking) */
#define SUDO_UNLOCK 4 /* unlock a file */
/*
* Function prototypes
*/
@@ -164,6 +171,8 @@ char *estrdup __P((const char *));
void easprintf __P((char **, const char *, ...));
void evasprintf __P((char **, const char *, va_list));
void print_version __P((void));
int lock_file __P((int, int));
int touch __P((char *, time_t));
YY_DECL;
/* Only provide extern declarations outside of sudo.c. */

View File

@@ -76,12 +76,19 @@ print_version()
/*
* Print compile-time options if root.
* At some point these will all be in a structure of some kind
* to allow overriding the defaults from sudoers.
* XXX - reorganize for readability.
*/
if (getuid() == 0) {
(void) printf("\nSudoers file: %s, mode 0%o, uid %d, gid %d\n",
_PATH_SUDOERS, SUDOERS_MODE, SUDOERS_UID, SUDOERS_GID);
(void) printf("Sudoers temp file: %s\n", _PATH_SUDOERS_TMP);
#ifdef WITHOUT_PASSWD
(void) puts("\nNo Authentication configured\n");
(void) puts("No Authentication configured\n");
#else
(void) fputs("\nAuthentication methods:", stdout);
(void) fputs("Authentication methods:", stdout);
for (auth = auth_switch; auth->name; auth++) {
(void) putchar(' ');
(void) fputs(auth->name, stdout);
@@ -137,6 +144,8 @@ print_version()
(void) fputs(" goons", stdout);
# endif
(void) putchar('\n');
# else
(void) printf("Incorrect password message: %s\n", INCORRECT_PASSWORD);
#endif
#ifdef SUDO_UMASK
@@ -169,14 +178,39 @@ print_version()
(void) printf("Editor for visudo: %s\n", EDITOR);
#endif
#if defined(IGNORE_DOT_PATH) || defined(DONT_LEAK_PATH_INFO) || defined(SECURE_PATH)
puts("$PATH options:");
#ifdef SECURE_PATH
(void) printf("Secure PATH: %s\n", SECURE_PATH);
(void) printf(" PATH override: %s\n", SECURE_PATH);
#endif
# ifdef IGNORE_DOT_PATH
(void) puts(" ignore '.'");
# endif
# ifdef DONT_LEAK_PATH_INFO
(void) puts(" no information leakage");
# endif
#endif
#ifdef _PATH_SENDMAIL
(void) printf("Mailer path: %s\n", _PATH_SENDMAIL);
(void) printf("Send mail to: %s\n", ALERTMAIL);
(void) printf("Mail subject: %s\n", MAILSUBJECT);
(void) fputs("Send mail on: 'error'", stdout);
# ifdef SEND_MAIL_WHEN_NO_USER
(void) fputs(" 'unlisted user'", stdout);
# endif
# ifdef SEND_MAIL_WHEN_NOT_OK
(void) fputs(" 'authentication failure'", stdout);
# endif
(void) putchar('\n');
#endif
#ifdef SHELL_IF_NO_ARGS
puts("When invoked with no arguments sudo will start a shell")
#endif
#ifdef SHELL_SETS_HOME
puts("Sudo will set $HOME to the homedir of the target user");
#endif
(void) printf("Default password prompt: %s\n", PASSPROMPT);
@@ -187,14 +221,6 @@ print_version()
#else
(void) puts("no");
#endif
/* stopped at INCORRECT_PASSWORD */
/* XXX - more */
/*
-D_PATH_SUDO_SUDOERS=\"/etc/sudoers\" -D_PATH_SUDO_STMP=\"/etc/stmp\" -DSUDOERS_UID=0 -DSUDOERS_GID=0 -DSUDOERS_MODE=0440
*/
}
}

View File

@@ -56,6 +56,7 @@
#endif /* HAVE_MALLOC_H && !STDC_HEADERS */
#include <ctype.h>
#include <pwd.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <fcntl.h>
@@ -124,8 +125,9 @@ main(argc, argv)
int sudoers_fd; /* sudoers file descriptor */
int stmp_fd; /* stmp file descriptor */
int n; /* length parameter */
time_t now; /* time now */
struct stat stmp_sb, sudoers_sb; /* to check for changes */
(void) setbuf(stderr, (char *)NULL); /* unbuffered stderr */
/*
* Parse command line options
@@ -166,30 +168,44 @@ main(argc, argv)
#endif /* ENV_EDITOR */
/*
* Copy sudoers file to stmp
* Open sudoers temp file and grab a lock.
*/
stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_EXCL, 0600);
stmp_fd = open(stmp, O_WRONLY | O_CREAT, 0600);
if (stmp_fd < 0) {
if (errno == EEXIST) {
(void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
exit(1);
}
if (!lock_file(stmp_fd, SUDO_TLOCK)) {
(void) fprintf(stderr, "%s: sudoers file busy, try again later.\n",
Argv[0]);
exit(1);
}
(void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
#ifdef HAVE_FTRUNCATE
if (ftruncate(stmp_fd, 0) == -1) {
#else
if (truncate(stmp, 0) == -1) {
#endif
(void) fprintf(stderr, "%s: can't truncate %s: %s\n", Argv[0],
stmp, strerror(errno));
Exit(-1);
}
/* Install signal handlers to clean up stmp if we are killed. */
setup_signals();
(void) memset(&sudoers_sb, 0, sizeof(sudoers_sb));
if (stat(sudoers, &sudoers_sb) == -1 && errno != ENOENT) {
(void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
Exit(-1);
}
sudoers_fd = open(sudoers, O_RDONLY);
if (sudoers_fd < 0 && errno != ENOENT) {
if (sudoers_fd == -1 && errno != ENOENT) {
(void) fprintf(stderr, "%s: %s\n", Argv[0], strerror(errno));
Exit(-1);
}
/* Copy sudoers -> stmp */
if (sudoers_fd >= 0) {
/* Copy sudoers -> stmp and reset the mtime */
if (sudoers_fd != -1) {
while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0)
if (write(stmp_fd, buf, n) != n) {
(void) fprintf(stderr, "%s: Write failed: %s\n", Argv[0],
@@ -200,6 +216,7 @@ main(argc, argv)
(void) close(sudoers_fd);
}
(void) close(stmp_fd);
(void) touch(stmp, sudoers_sb.st_mtime);
/*
* Edit the temp file and parse it (for sanity checking)
@@ -219,20 +236,19 @@ main(argc, argv)
(void) sprintf(buf, "%s %s", Editor, stmp);
/* Do the edit -- some SYSV editors exit with 1 instead of 0 */
now = time(NULL);
n = system(buf);
if (n != -1 && ((n >> 8) == 0 || (n >> 8) == 1)) {
struct stat statbuf; /* for sanity checking */
/*
* Sanity checks.
*/
if (stat(stmp, &statbuf) < 0) {
if (stat(stmp, &stmp_sb) < 0) {
(void) fprintf(stderr,
"%s: Can't stat temporary file (%s), %s unchanged.\n",
Argv[0], stmp, sudoers);
Exit(-1);
}
if (statbuf.st_size == 0) {
if (stmp_sb.st_size == 0) {
(void) fprintf(stderr,
"%s: Zero length temporary file (%s), %s unchanged.\n",
Argv[0], stmp, sudoers);
@@ -285,6 +301,15 @@ main(argc, argv)
}
} while (parse_error == TRUE);
/*
* If the user didn't change the temp file, just unlink it.
*/
if (sudoers_sb.st_mtime != now && sudoers_sb.st_mtime == stmp_sb.st_mtime &&
sudoers_sb.st_size == stmp_sb.st_size) {
(void) fprintf(stderr, "%s: sudoers file unchanged.\n", Argv[0]);
Exit(0);
}
/*
* Change mode and ownership of temp file so when
* we move it to sudoers things are kosher.
@@ -469,5 +494,5 @@ static void
usage()
{
(void) fprintf(stderr, "usage: %s [-V]\n", Argv[0]);
Exit(-1);
exit(1);
}