version 1.16, 2004/04/05 16:44:00 |
version 1.17, 2004/09/28 15:10:51 |
|
|
/* |
/* |
* Copyright (c) 1996, 1998-2003 Todd C. Miller <Todd.Miller@courtesan.com> |
* Copyright (c) 1996, 1998-2004 Todd C. Miller <Todd.Miller@courtesan.com> |
* All rights reserved. |
|
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Permission to use, copy, modify, and distribute this software for any |
* modification, are permitted provided that the following conditions |
* purpose with or without fee is hereby granted, provided that the above |
* are met: |
* copyright notice and this permission notice appear in all copies. |
* |
* |
* 1. Redistributions of source code must retain the above copyright |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
* notice, this list of conditions and the following disclaimer. |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
* |
* |
* 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. |
|
* |
|
* Sponsored in part by the Defense Advanced Research Projects |
* Sponsored in part by the Defense Advanced Research Projects |
* Agency (DARPA) and Air Force Research Laboratory, Air Force |
* Agency (DARPA) and Air Force Research Laboratory, Air Force |
* Materiel Command, USAF, under agreement number F39502-99-1-0512. |
* Materiel Command, USAF, under agreement number F39502-99-1-0512. |
|
|
|
|
#define _SUDO_MAIN |
#define _SUDO_MAIN |
|
|
|
#ifdef __TANDEM |
|
# include <floss.h> |
|
#endif |
|
|
#include "config.h" |
#include "config.h" |
|
|
#include <sys/types.h> |
#include <sys/types.h> |
#include <sys/param.h> |
#include <sys/param.h> |
#include <sys/stat.h> |
#include <sys/stat.h> |
#include <sys/file.h> |
#include <sys/time.h> |
|
#ifndef __TANDEM |
|
# include <sys/file.h> |
|
#endif |
#include <sys/wait.h> |
#include <sys/wait.h> |
#include <stdio.h> |
#include <stdio.h> |
#ifdef STDC_HEADERS |
#ifdef STDC_HEADERS |
|
|
#include "version.h" |
#include "version.h" |
|
|
#ifndef lint |
#ifndef lint |
static const char rcsid[] = "$Sudo: visudo.c,v 1.151 2003/04/16 00:42:10 millert Exp $"; |
static const char rcsid[] = "$Sudo: visudo.c,v 1.170 2004/09/08 15:48:23 millert Exp $"; |
#endif /* lint */ |
#endif /* lint */ |
|
|
/* |
/* |
|
|
static void setup_signals __P((void)); |
static void setup_signals __P((void)); |
static int run_command __P((char *, char **)); |
static int run_command __P((char *, char **)); |
static int check_syntax __P((int)); |
static int check_syntax __P((int)); |
int command_matches __P((char *, char *, char *, char *)); |
int command_matches __P((char *, char *)); |
int addr_matches __P((char *)); |
int addr_matches __P((char *)); |
int hostname_matches __P((char *, char *, char *)); |
int hostname_matches __P((char *, char *, char *)); |
int netgr_matches __P((char *, char *, char *, char *)); |
int netgr_matches __P((char *, char *, char *, char *)); |
int usergr_matches __P((char *, char *)); |
int usergr_matches __P((char *, char *, struct passwd *)); |
|
int userpw_matches __P((char *, char *, struct passwd *)); |
void init_parser __P((void)); |
void init_parser __P((void)); |
|
void yyerror __P((char *)); |
void yyrestart __P((FILE *)); |
void yyrestart __P((FILE *)); |
|
|
/* |
/* |
|
|
char *sudoers = _PATH_SUDOERS; |
char *sudoers = _PATH_SUDOERS; |
char *stmp = _PATH_SUDOERS_TMP; |
char *stmp = _PATH_SUDOERS_TMP; |
struct sudo_user sudo_user; |
struct sudo_user sudo_user; |
int parse_error = FALSE; |
int Argc, parse_error = FALSE; |
|
|
int |
int |
main(argc, argv) |
main(argc, argv) |
int argc; |
int argc; |
char **argv; |
char **argv; |
{ |
{ |
char buf[MAXPATHLEN*2]; /* buffer used for copying files */ |
char buf[PATH_MAX*2]; /* buffer used for copying files */ |
char *Editor; /* editor to use */ |
char *Editor; /* editor to use */ |
char *UserEditor; /* editor user wants to use */ |
char *UserEditor; /* editor user wants to use */ |
char *EditorPath; /* colon-separated list of editors */ |
char *EditorPath; /* colon-separated list of editors */ |
|
|
int stmp_fd; /* stmp file descriptor */ |
int stmp_fd; /* stmp file descriptor */ |
int n; /* length parameter */ |
int n; /* length parameter */ |
int ch; /* getopt char */ |
int ch; /* getopt char */ |
time_t now; /* time now */ |
struct timespec ts1, ts2; /* time before and after edit */ |
struct stat stmp_sb, sudoers_sb; /* to check for changes */ |
struct timespec sudoers_mtim; /* starting mtime of sudoers file */ |
|
off_t sudoers_size; /* starting size of sudoers file */ |
|
struct stat sb; /* stat buffer */ |
|
|
/* Warn about aliases that are used before being defined. */ |
/* Warn about aliases that are used before being defined. */ |
pedantic = 1; |
pedantic = 1; |
|
|
Argv = argv; /* for warn/err */ |
Argv = argv; |
|
if ((Argc = argc) < 1) |
|
usage(); |
|
|
/* |
/* |
* Arg handling. |
* Arg handling. |
|
|
exit(check_syntax(quiet)); |
exit(check_syntax(quiet)); |
|
|
/* |
/* |
* Open sudoers, lock it and stat it. |
* Open sudoers, lock it and stat it. |
* sudoers_fd must remain open throughout in order to hold the lock. |
* sudoers_fd must remain open throughout in order to hold the lock. |
*/ |
*/ |
sudoers_fd = open(sudoers, O_RDWR | O_CREAT, SUDOERS_MODE); |
sudoers_fd = open(sudoers, O_RDWR | O_CREAT, SUDOERS_MODE); |
|
|
if (!lock_file(sudoers_fd, SUDO_TLOCK)) |
if (!lock_file(sudoers_fd, SUDO_TLOCK)) |
errx(1, "sudoers file busy, try again later"); |
errx(1, "sudoers file busy, try again later"); |
#ifdef HAVE_FSTAT |
#ifdef HAVE_FSTAT |
if (fstat(sudoers_fd, &sudoers_sb) == -1) |
if (fstat(sudoers_fd, &sb) == -1) |
#else |
#else |
if (stat(sudoers, &sudoers_sb) == -1) |
if (stat(sudoers, &sb) == -1) |
#endif |
#endif |
err(1, "can't stat %s", sudoers); |
err(1, "can't stat %s", sudoers); |
|
sudoers_size = sb.st_size; |
|
sudoers_mtim.tv_sec = mtim_getsec(sb); |
|
sudoers_mtim.tv_nsec = mtim_getnsec(sb); |
|
|
/* |
/* |
* Open sudoers temp file. |
* Open sudoers temp file. |
|
|
setup_signals(); |
setup_signals(); |
|
|
/* Copy sudoers -> stmp and reset the mtime */ |
/* Copy sudoers -> stmp and reset the mtime */ |
if (sudoers_sb.st_size) { |
if (sudoers_size) { |
while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0) |
while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0) |
if (write(stmp_fd, buf, n) != n) |
if (write(stmp_fd, buf, n) != n) |
err(1, "write error"); |
err(1, "write error"); |
|
|
write(stmp_fd, buf, 1); |
write(stmp_fd, buf, 1); |
} |
} |
|
|
|
(void) touch(stmp_fd, stmp, &sudoers_mtim); |
(void) close(stmp_fd); |
(void) close(stmp_fd); |
(void) touch(stmp, sudoers_sb.st_mtime); |
|
|
|
/* Parse sudoers to pull in editor and env_editor conf values. */ |
/* Parse sudoers to pull in editor and env_editor conf values. */ |
if ((yyin = fopen(stmp, "r"))) { |
if ((yyin = fopen(stmp, "r"))) { |
|
|
(void) close(stmp_fd); |
(void) close(stmp_fd); |
|
|
/* |
/* |
* Check EDITOR and VISUAL environment variables to see which editor |
* Check VISUAL and EDITOR environment variables to see which editor |
* the user wants to use (we may not end up using it though). |
* the user wants to use (we may not end up using it though). |
* If the path is not fully-qualified, make it so and check that |
* If the path is not fully-qualified, make it so and check that |
* the specified executable actually exists. |
* the specified executable actually exists. |
*/ |
*/ |
if ((UserEditor = getenv("EDITOR")) == NULL || *UserEditor == '\0') |
if ((UserEditor = getenv("VISUAL")) == NULL || *UserEditor == '\0') |
UserEditor = getenv("VISUAL"); |
UserEditor = getenv("EDITOR"); |
if (UserEditor && *UserEditor == '\0') |
if (UserEditor && *UserEditor == '\0') |
UserEditor = NULL; |
UserEditor = NULL; |
else if (UserEditor) { |
else if (UserEditor) { |
if (find_path(UserEditor, &Editor, getenv("PATH")) == FOUND) { |
if (find_path(UserEditor, &Editor, NULL, getenv("PATH")) == FOUND) { |
UserEditor = Editor; |
UserEditor = Editor; |
} else { |
} else { |
if (def_flag(I_ENV_EDITOR)) { |
if (def_env_editor) { |
/* If we are honoring $EDITOR this is a fatal error. */ |
/* If we are honoring $EDITOR this is a fatal error. */ |
warnx("specified editor (%s) doesn't exist!", UserEditor); |
warnx("specified editor (%s) doesn't exist!", UserEditor); |
Exit(-1); |
Exit(-1); |
|
|
* we allow any $EDITOR or because $EDITOR is in the allowable list. |
* we allow any $EDITOR or because $EDITOR is in the allowable list. |
*/ |
*/ |
Editor = EditorPath = NULL; |
Editor = EditorPath = NULL; |
if (def_flag(I_ENV_EDITOR) && UserEditor) |
if (def_env_editor && UserEditor) |
Editor = UserEditor; |
Editor = UserEditor; |
else if (UserEditor) { |
else if (UserEditor) { |
struct stat editor_sb; |
struct stat editor_sb; |
|
|
warn("unable to stat editor (%s)", UserEditor); |
warn("unable to stat editor (%s)", UserEditor); |
Exit(-1); |
Exit(-1); |
} |
} |
EditorPath = estrdup(def_str(I_EDITOR)); |
EditorPath = estrdup(def_editor); |
Editor = strtok(EditorPath, ":"); |
Editor = strtok(EditorPath, ":"); |
do { |
do { |
/* |
/* |
|
|
} |
} |
|
|
/* |
/* |
* Can't use $EDITOR, try each element of I_EDITOR until we |
* Can't use $EDITOR, try each element of def_editor until we |
* find one that exists, is regular, and is executable. |
* find one that exists, is regular, and is executable. |
*/ |
*/ |
if (Editor == NULL || *Editor == '\0') { |
if (Editor == NULL || *Editor == '\0') { |
if (EditorPath != NULL) |
if (EditorPath != NULL) |
free(EditorPath); |
free(EditorPath); |
EditorPath = estrdup(def_str(I_EDITOR)); |
EditorPath = estrdup(def_editor); |
Editor = strtok(EditorPath, ":"); |
Editor = strtok(EditorPath, ":"); |
do { |
do { |
if (sudo_goodpath(Editor)) |
if (sudo_goodpath(Editor, NULL)) |
break; |
break; |
} while ((Editor = strtok(NULL, ":"))); |
} while ((Editor = strtok(NULL, ":"))); |
|
|
/* Bleah, none of the editors existed! */ |
/* Bleah, none of the editors existed! */ |
if (Editor == NULL || *Editor == '\0') { |
if (Editor == NULL || *Editor == '\0') { |
warnx("no editor found (editor path = %s)", def_str(I_EDITOR)); |
warnx("no editor found (editor path = %s)", def_editor); |
Exit(-1); |
Exit(-1); |
} |
} |
} |
} |
|
|
* XPG4 specifies that vi's exit value is a function of the |
* XPG4 specifies that vi's exit value is a function of the |
* number of errors during editing (?!?!). |
* number of errors during editing (?!?!). |
*/ |
*/ |
now = time(NULL); |
gettime(&ts1); |
if (run_command(Editor, av) != -1) { |
if (run_command(Editor, av) != -1) { |
|
gettime(&ts2); |
/* |
/* |
* Sanity checks. |
* Sanity checks. |
*/ |
*/ |
if (stat(stmp, &stmp_sb) < 0) { |
if (stat(stmp, &sb) < 0) { |
warnx("cannot stat temporary file (%s), %s unchanged", |
warnx("cannot stat temporary file (%s), %s unchanged", |
stmp, sudoers); |
stmp, sudoers); |
Exit(-1); |
Exit(-1); |
} |
} |
if (stmp_sb.st_size == 0) { |
if (sb.st_size == 0) { |
warnx("zero length temporary file (%s), %s unchanged", |
warnx("zero length temporary file (%s), %s unchanged", |
stmp, sudoers); |
stmp, sudoers); |
Exit(-1); |
Exit(-1); |
|
|
switch (whatnow()) { |
switch (whatnow()) { |
case 'Q' : parse_error = FALSE; /* ignore parse error */ |
case 'Q' : parse_error = FALSE; /* ignore parse error */ |
break; |
break; |
case 'x' : if (sudoers_sb.st_size == 0) |
case 'x' : if (sudoers_size == 0) |
unlink(sudoers); |
unlink(sudoers); |
Exit(0); |
Exit(0); |
break; |
break; |
|
|
/* |
/* |
* If the user didn't change the temp file, just unlink it. |
* 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 && |
if (sudoers_size == sb.st_size && |
sudoers_sb.st_size == stmp_sb.st_size) { |
sudoers_mtim.tv_sec == mtim_getsec(sb) && |
warnx("sudoers file unchanged"); |
sudoers_mtim.tv_nsec == mtim_getnsec(sb)) { |
Exit(0); |
/* |
|
* If mtime and size match but the user spent no measurable |
|
* time in the editor we can't tell if the file was changed. |
|
*/ |
|
timespecsub(&ts1, &ts2, &ts2); |
|
if (timespecisset(&ts2)) { |
|
warnx("sudoers file unchanged"); |
|
Exit(0); |
|
} |
} |
} |
|
|
/* |
/* |
|
|
/* |
/* |
* Now that we have a sane stmp file (parses ok) it needs to be |
* Now that we have a sane stmp file (parses ok) it needs to be |
* rename(2)'d to sudoers. If the rename(2) fails we try using |
* rename(2)'d to sudoers. If the rename(2) fails we try using |
* mv(1) in case stmp and sudoers are on different filesystems. |
* mv(1) in case stmp and sudoers are on different file systems. |
*/ |
*/ |
if (rename(stmp, sudoers)) { |
if (rename(stmp, sudoers)) { |
if (errno == EXDEV) { |
if (errno == EXDEV) { |
warnx("%s and %s not on the same filesystem, using mv to rename", |
warnx("%s and %s not on the same file system, using mv to rename", |
stmp, sudoers); |
stmp, sudoers); |
|
|
/* Build up argument vector for the command */ |
/* Build up argument vector for the command */ |
|
|
* These exist to allow us to use the same parser as sudo(8). |
* These exist to allow us to use the same parser as sudo(8). |
*/ |
*/ |
int |
int |
command_matches(cmnd, cmnd_args, path, sudoers_args) |
command_matches(path, sudoers_args) |
char *cmnd; |
|
char *cmnd_args; |
|
char *path; |
char *path; |
char *sudoers_args; |
char *sudoers_args; |
{ |
{ |
|
|
} |
} |
|
|
int |
int |
usergr_matches(g, u) |
usergr_matches(g, u, pw) |
char *g, *u; |
char *g, *u; |
|
struct passwd *pw; |
{ |
{ |
return(TRUE); |
return(TRUE); |
} |
} |
|
|
int |
int |
|
userpw_matches(s, u, pw) |
|
char *s, *u; |
|
struct passwd *pw; |
|
{ |
|
return(TRUE); |
|
} |
|
|
|
int |
netgr_matches(n, h, sh, u) |
netgr_matches(n, h, sh, u) |
char *n, *h, *sh, *u; |
char *n, *h, *sh, *u; |
{ |
{ |
|
|
} |
} |
|
|
int |
int |
|
set_runaspw(user) |
|
char *user; |
|
{ |
|
extern int sudolineno, used_runas; |
|
|
|
if (used_runas) { |
|
(void) fprintf(stderr, |
|
"%s: runas_default set after old value is in use near line %d\n", |
|
pedantic > 1 ? "Error" : "Warning", sudolineno); |
|
if (pedantic > 1) |
|
yyerror(NULL); |
|
} |
|
return(TRUE); |
|
} |
|
|
|
int |
user_is_exempt() |
user_is_exempt() |
{ |
{ |
return(TRUE); |
return(TRUE); |
|
|
|
|
(void) sigprocmask(SIG_SETMASK, &oset, NULL); |
(void) sigprocmask(SIG_SETMASK, &oset, NULL); |
|
|
/* XXX - should use WEXITSTATUS() */ |
if (pid == -1 || !WIFEXITED(status)) |
return(pid == -1 ? -1 : (status >> 8)); |
return(-1); |
|
return(WEXITSTATUS(status)); |
} |
} |
|
|
static int |
static int |