version 1.9, 2001/01/19 17:58:19 |
version 1.9.4.1, 2002/01/18 16:14:46 |
|
|
/* |
/* |
* Copyright (c) 1996, 1998-2000 Todd C. Miller <Todd.Miller@courtesan.com> |
* Copyright (c) 1996, 1998-2001 Todd C. Miller <Todd.Miller@courtesan.com> |
* All rights reserved. |
* All rights reserved. |
* |
* |
* Redistribution and use in source and binary forms, with or without |
* Redistribution and use in source and binary forms, with or without |
|
|
|
|
#include "config.h" |
#include "config.h" |
|
|
|
#include <sys/types.h> |
|
#include <sys/param.h> |
|
#include <sys/stat.h> |
|
#include <sys/file.h> |
|
#include <sys/wait.h> |
#include <stdio.h> |
#include <stdio.h> |
#ifdef STDC_HEADERS |
#ifdef STDC_HEADERS |
#include <stdlib.h> |
# include <stdlib.h> |
|
# include <stddef.h> |
|
#else |
|
# ifdef HAVE_STDLIB_H |
|
# include <stdlib.h> |
|
# endif |
#endif /* STDC_HEADERS */ |
#endif /* STDC_HEADERS */ |
|
#ifdef HAVE_STRING_H |
|
# include <string.h> |
|
#else |
|
# ifdef HAVE_STRINGS_H |
|
# include <strings.h> |
|
# endif |
|
#endif /* HAVE_STRING_H */ |
#ifdef HAVE_UNISTD_H |
#ifdef HAVE_UNISTD_H |
#include <unistd.h> |
#include <unistd.h> |
#endif /* HAVE_UNISTD_H */ |
#endif /* HAVE_UNISTD_H */ |
#ifdef HAVE_STRING_H |
|
#include <string.h> |
|
#endif /* HAVE_STRING_H */ |
|
#ifdef HAVE_STRINGS_H |
|
#include <strings.h> |
|
#endif /* HAVE_STRINGS_H */ |
|
#if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS) |
|
#include <malloc.h> |
|
#endif /* HAVE_MALLOC_H && !STDC_HEADERS */ |
|
#include <ctype.h> |
#include <ctype.h> |
#include <pwd.h> |
#include <pwd.h> |
#include <time.h> |
#include <time.h> |
#include <signal.h> |
#include <signal.h> |
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <sys/types.h> |
|
#include <sys/param.h> |
|
#include <sys/stat.h> |
|
#include <sys/file.h> |
|
|
|
#include "sudo.h" |
#include "sudo.h" |
#include "version.h" |
#include "version.h" |
|
|
#ifndef STDC_HEADERS |
|
#ifndef __GNUC__ /* gcc has its own malloc */ |
|
extern char *malloc __P((size_t)); |
|
#endif /* __GNUC__ */ |
|
extern char *getenv __P((const char *)); |
|
extern int stat __P((const char *, struct stat *)); |
|
#endif /* !STDC_HEADERS */ |
|
|
|
#if defined(POSIX_SIGNALS) && !defined(SA_RESETHAND) |
|
#define SA_RESETHAND 0 |
|
#endif /* POSIX_SIGNALS && !SA_RESETHAND */ |
|
|
|
#ifndef lint |
#ifndef lint |
static const char rcsid[] = "$Sudo: visudo.c,v 1.126 2000/03/23 04:38:22 millert Exp $"; |
static const char rcsid[] = "$Sudo: visudo.c,v 1.146 2002/01/17 15:35:54 millert Exp $"; |
#endif /* lint */ |
#endif /* lint */ |
|
|
/* |
/* |
|
|
static char whatnow __P((void)); |
static char whatnow __P((void)); |
static RETSIGTYPE Exit __P((int)); |
static RETSIGTYPE Exit __P((int)); |
static void setup_signals __P((void)); |
static void setup_signals __P((void)); |
|
static int run_command __P((char *, char **)); |
|
static int check_syntax __P((int)); |
int command_matches __P((char *, char *, char *, char *)); |
int command_matches __P((char *, char *, 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 *)); |
|
|
extern FILE *yyin, *yyout; |
extern FILE *yyin, *yyout; |
extern int errorlineno; |
extern int errorlineno; |
extern int pedantic; |
extern int pedantic; |
|
extern int quiet; |
|
|
|
/* For getopt(3) */ |
|
extern char *optarg; |
|
extern int optind; |
|
|
/* |
/* |
* Globals |
* Globals |
*/ |
*/ |
|
|
struct sudo_user sudo_user; |
struct sudo_user sudo_user; |
int parse_error = FALSE; |
int parse_error = FALSE; |
|
|
|
|
int |
int |
main(argc, argv) |
main(argc, argv) |
int argc; |
int argc; |
|
|
{ |
{ |
char buf[MAXPATHLEN*2]; /* buffer used for copying files */ |
char buf[MAXPATHLEN*2]; /* buffer used for copying files */ |
char *Editor; /* editor to use */ |
char *Editor; /* editor to use */ |
|
char *UserEditor; /* editor user wants to use */ |
|
char *EditorPath; /* colon-separated list of editors */ |
|
char *av[4]; /* argument vector for run_command */ |
|
int checkonly; /* only check existing file? */ |
int sudoers_fd; /* sudoers file descriptor */ |
int sudoers_fd; /* sudoers file descriptor */ |
int stmp_fd; /* stmp file descriptor */ |
int stmp_fd; /* stmp file descriptor */ |
int n; /* length parameter */ |
int n; /* length parameter */ |
|
int ch; /* getopt char */ |
time_t now; /* time now */ |
time_t now; /* time now */ |
struct stat stmp_sb, sudoers_sb; /* to check for changes */ |
struct stat stmp_sb, sudoers_sb; /* to check for changes */ |
|
|
|
|
/* |
/* |
* Arg handling. |
* Arg handling. |
*/ |
*/ |
while (--argc) { |
checkonly = 0; |
if (!strcmp(argv[argc], "-V")) { |
while ((ch = getopt(argc, argv, "Vcf:sq")) != -1) { |
(void) printf("visudo version %s\n", version); |
switch (ch) { |
exit(0); |
case 'V': |
} else if (!strcmp(argv[argc], "-s")) { |
(void) printf("visudo version %s\n", version); |
pedantic++; /* strict mode */ |
exit(0); |
} else { |
case 'c': |
usage(); |
checkonly++; /* check mode */ |
|
break; |
|
case 'f': |
|
sudoers = optarg; /* sudoers file path */ |
|
easprintf(&stmp, "%s.tmp", optarg); |
|
break; |
|
case 's': |
|
pedantic++; /* strict mode */ |
|
break; |
|
case 'q': |
|
quiet++; /* quiet mode */ |
|
break; |
|
default: |
|
usage(); |
} |
} |
} |
} |
|
argc -= optind; |
|
argv += optind; |
|
if (argc) |
|
usage(); |
|
|
/* Mock up a fake sudo_user struct. */ |
/* Mock up a fake sudo_user struct. */ |
user_host = user_shost = user_cmnd = ""; |
user_host = user_shost = user_cmnd = ""; |
|
|
/* Setup defaults data structures. */ |
/* Setup defaults data structures. */ |
init_defaults(); |
init_defaults(); |
|
|
|
if (checkonly) |
|
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. |
|
|
if (sudoers_fd == -1) { |
if (sudoers_fd == -1) { |
(void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers, |
(void) fprintf(stderr, "%s: %s: %s\n", Argv[0], sudoers, |
strerror(errno)); |
strerror(errno)); |
Exit(-1); |
exit(1); |
} |
} |
if (!lock_file(sudoers_fd, SUDO_TLOCK)) { |
if (!lock_file(sudoers_fd, SUDO_TLOCK)) { |
(void) fprintf(stderr, "%s: sudoers file busy, try again later.\n", |
(void) fprintf(stderr, "%s: sudoers file busy, try again later.\n", |
|
|
#endif |
#endif |
(void) fprintf(stderr, "%s: can't stat %s: %s\n", |
(void) fprintf(stderr, "%s: can't stat %s: %s\n", |
Argv[0], sudoers, strerror(errno)); |
Argv[0], sudoers, strerror(errno)); |
Exit(-1); |
exit(1); |
} |
} |
|
|
/* |
/* |
|
|
(void) close(stmp_fd); |
(void) close(stmp_fd); |
|
|
/* |
/* |
* If we are allowing EDITOR and VISUAL envariables set Editor |
* Check EDITOR and VISUAL environment variables to see which editor |
* base on whichever exists... |
* 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 |
|
* the specified executable actually exists. |
*/ |
*/ |
if (!def_flag(I_ENV_EDITOR) || |
if ((UserEditor = getenv("EDITOR")) == NULL || *UserEditor == '\0') |
(!(Editor = getenv("EDITOR")) && !(Editor = getenv("VISUAL")))) |
UserEditor = getenv("VISUAL"); |
Editor = estrdup(def_str(I_EDITOR)); |
if (UserEditor && *UserEditor == '\0') |
|
UserEditor = NULL; |
|
else if (UserEditor) { |
|
if (find_path(UserEditor, &Editor, getenv("PATH")) == FOUND) { |
|
UserEditor = Editor; |
|
} else { |
|
if (def_flag(I_ENV_EDITOR)) { |
|
/* If we are honoring $EDITOR this is a fatal error. */ |
|
(void) fprintf(stderr, |
|
"%s: specified editor (%s) doesn't exist!\n", |
|
Argv[0], UserEditor); |
|
Exit(-1); |
|
} else { |
|
/* Otherwise, just ignore $EDITOR. */ |
|
UserEditor = NULL; |
|
} |
|
} |
|
} |
|
|
/* |
/* |
* Edit the temp file and parse it (for sanity checking) |
* See if we can use the user's choice of editors either because |
|
* we allow any $EDITOR or because $EDITOR is in the allowable list. |
*/ |
*/ |
do { |
Editor = EditorPath = NULL; |
/* |
if (def_flag(I_ENV_EDITOR) && UserEditor) |
* Build up a buffer to execute |
Editor = UserEditor; |
*/ |
else if (UserEditor) { |
if (strlen(Editor) + strlen(stmp) + 30 > sizeof(buf)) { |
struct stat editor_sb; |
(void) fprintf(stderr, "%s: Buffer too short (line %d).\n", |
struct stat user_editor_sb; |
Argv[0], __LINE__); |
char *base, *userbase; |
|
|
|
if (stat(UserEditor, &user_editor_sb) != 0) { |
|
/* Should never happen since we already checked above. */ |
|
(void) fprintf(stderr, "%s: unable to stat editor (%s): %s\n", |
|
Argv[0], UserEditor, strerror(errno)); |
Exit(-1); |
Exit(-1); |
} |
} |
if (parse_error == TRUE) |
EditorPath = estrdup(def_str(I_EDITOR)); |
(void) sprintf(buf, "%s +%d %s", Editor, errorlineno, stmp); |
Editor = strtok(EditorPath, ":"); |
|
do { |
|
/* |
|
* Both Editor and UserEditor should be fully qualified but |
|
* check anyway... |
|
*/ |
|
if ((base = strrchr(Editor, '/')) == NULL) |
|
continue; |
|
if ((userbase = strrchr(UserEditor, '/')) == NULL) { |
|
Editor = NULL; |
|
break; |
|
} |
|
base++, userbase++; |
|
|
|
/* |
|
* We compare the basenames first and then use stat to match |
|
* for sure. |
|
*/ |
|
if (strcmp(base, userbase) == 0) { |
|
if (stat(Editor, &editor_sb) == 0 && S_ISREG(editor_sb.st_mode) |
|
&& (editor_sb.st_mode & 0000111) && |
|
editor_sb.st_dev == user_editor_sb.st_dev && |
|
editor_sb.st_ino == user_editor_sb.st_ino) |
|
break; |
|
} |
|
} while ((Editor = strtok(NULL, ":"))); |
|
} |
|
|
|
/* |
|
* Can't use $EDITOR, try each element of I_EDITOR until we |
|
* find one that exists, is regular, and is executable. |
|
*/ |
|
if (Editor == NULL || *Editor == '\0') { |
|
if (EditorPath != NULL) |
|
free(EditorPath); |
|
EditorPath = estrdup(def_str(I_EDITOR)); |
|
Editor = strtok(EditorPath, ":"); |
|
do { |
|
if (sudo_goodpath(Editor)) |
|
break; |
|
} while ((Editor = strtok(NULL, ":"))); |
|
|
|
/* Bleah, none of the editors existed! */ |
|
if (Editor == NULL || *Editor == '\0') { |
|
(void) fprintf(stderr, "%s: no editor found (editor path = %s)\n", |
|
Argv[0], def_str(I_EDITOR)); |
|
Exit(-1); |
|
} |
|
} |
|
|
|
/* |
|
* Edit the temp file and parse it (for sanity checking) |
|
*/ |
|
do { |
|
char linestr[64]; |
|
|
|
/* Build up argument vector for the command */ |
|
if ((av[0] = strrchr(Editor, '/')) != NULL) |
|
av[0]++; |
else |
else |
(void) sprintf(buf, "%s %s", Editor, stmp); |
av[0] = Editor; |
|
n = 1; |
|
if (parse_error == TRUE) { |
|
(void) snprintf(linestr, sizeof(linestr), "+%d", errorlineno); |
|
av[n++] = linestr; |
|
} |
|
av[n++] = stmp; |
|
av[n++] = NULL; |
|
|
/* Do the edit -- some SYSV editors exit with 1 instead of 0 */ |
/* |
|
* Do the edit: |
|
* We cannot check the editor's exit value against 0 since |
|
* XPG4 specifies that vi's exit value is a function of the |
|
* number of errors during editing (?!?!). |
|
*/ |
now = time(NULL); |
now = time(NULL); |
n = system(buf); |
if (run_command(Editor, av) != -1) { |
if (n != -1 && ((n >> 8) == 0 || (n >> 8) == 1)) { |
|
/* |
/* |
* Sanity checks. |
* Sanity checks. |
*/ |
*/ |
|
|
} |
} |
} else { |
} else { |
(void) fprintf(stderr, |
(void) fprintf(stderr, |
"%s: Editor (%s) failed with exit status %d, %s unchanged.\n", |
"%s: Editor (%s) failed, %s unchanged.\n", Argv[0], |
Argv[0], Editor, n, sudoers); |
Editor, sudoers); |
Exit(-1); |
Exit(-1); |
} |
} |
|
|
|
|
*/ |
*/ |
if (rename(stmp, sudoers)) { |
if (rename(stmp, sudoers)) { |
if (errno == EXDEV) { |
if (errno == EXDEV) { |
char *tmpbuf; |
|
|
|
(void) fprintf(stderr, |
(void) fprintf(stderr, |
"%s: %s and %s not on the same filesystem, using mv to rename.\n", |
"%s: %s and %s not on the same filesystem, using mv to rename.\n", |
Argv[0], stmp, sudoers); |
Argv[0], stmp, sudoers); |
|
|
/* Allocate just enough space for tmpbuf */ |
/* Build up argument vector for the command */ |
n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) + |
if ((av[0] = strrchr(_PATH_MV, '/')) != NULL) |
strlen(sudoers) + 4); |
av[0]++; |
if ((tmpbuf = (char *) malloc(n)) == NULL) { |
else |
(void) fprintf(stderr, |
av[0] = _PATH_MV; |
"%s: Cannot alocate memory, %s unchanged: %s\n", |
av[1] = stmp; |
Argv[0], sudoers, strerror(errno)); |
av[2] = sudoers; |
Exit(-1); |
av[3] = NULL; |
} |
|
|
|
/* Build up command and execute it */ |
/* And run it... */ |
(void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers); |
if (run_command(_PATH_MV, av)) { |
if (system(tmpbuf)) { |
|
(void) fprintf(stderr, |
(void) fprintf(stderr, |
"%s: Command failed: '%s', %s unchanged.\n", |
"%s: Command failed: '%s %s %s', %s unchanged.\n", |
Argv[0], tmpbuf, sudoers); |
Argv[0], _PATH_MV, stmp, sudoers, sudoers); |
Exit(-1); |
Exit(-1); |
} |
} |
free(tmpbuf); |
|
} else { |
} else { |
(void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n", |
(void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: %s\n", |
Argv[0], stmp, sudoers, strerror(errno)); |
Argv[0], stmp, sudoers, strerror(errno)); |
|
|
return; |
return; |
} |
} |
|
|
|
int |
|
user_is_exempt() |
|
{ |
|
return(TRUE); |
|
} |
|
|
|
void |
|
init_envtables() |
|
{ |
|
return; |
|
} |
|
|
/* |
/* |
* Assuming a parse error occurred, prompt the user for what they want |
* Assuming a parse error occurred, prompt the user for what they want |
* to do now. Returns the first letter of their choice. |
* to do now. Returns the first letter of their choice. |
|
|
static void |
static void |
setup_signals() |
setup_signals() |
{ |
{ |
#ifdef POSIX_SIGNALS |
sigaction_t sa; |
struct sigaction action; /* POSIX signal structure */ |
|
#endif /* POSIX_SIGNALS */ |
|
|
|
/* |
/* |
* Setup signal handlers to cleanup nicely. |
* Setup signal handlers to cleanup nicely. |
*/ |
*/ |
#ifdef POSIX_SIGNALS |
sigemptyset(&sa.sa_mask); |
(void) memset((VOID *)&action, 0, sizeof(action)); |
sa.sa_flags = SA_RESTART; |
action.sa_handler = Exit; |
sa.sa_handler = Exit; |
action.sa_flags = SA_RESETHAND; |
(void) sigaction(SIGTERM, &sa, NULL); |
(void) sigaction(SIGTERM, &action, NULL); |
(void) sigaction(SIGHUP, &sa, NULL); |
(void) sigaction(SIGHUP, &action, NULL); |
(void) sigaction(SIGINT, &sa, NULL); |
(void) sigaction(SIGINT, &action, NULL); |
(void) sigaction(SIGQUIT, &sa, NULL); |
(void) sigaction(SIGQUIT, &action, NULL); |
} |
|
|
|
static int |
|
run_command(path, argv) |
|
char *path; |
|
char **argv; |
|
{ |
|
int status; |
|
pid_t pid; |
|
sigset_t set, oset; |
|
|
|
(void) sigemptyset(&set); |
|
(void) sigaddset(&set, SIGCHLD); |
|
(void) sigprocmask(SIG_BLOCK, &set, &oset); |
|
|
|
switch (pid = fork()) { |
|
case -1: |
|
(void) fprintf(stderr, |
|
"%s: unable to run %s: %s\n", Argv[0], path, strerror(errno)); |
|
Exit(-1); |
|
break; /* NOTREACHED */ |
|
case 0: |
|
(void) sigprocmask(SIG_SETMASK, &oset, NULL); |
|
execv(path, argv); |
|
(void) fprintf(stderr, |
|
"%s: unable to run %s: %s\n", Argv[0], path, strerror(errno)); |
|
_exit(127); |
|
break; /* NOTREACHED */ |
|
} |
|
|
|
#ifdef sudo_waitpid |
|
pid = sudo_waitpid(pid, &status, 0); |
#else |
#else |
(void) signal(SIGTERM, Exit); |
pid = wait(&status); |
(void) signal(SIGHUP, Exit); |
#endif |
(void) signal(SIGINT, Exit); |
|
(void) signal(SIGQUIT, Exit); |
(void) sigprocmask(SIG_SETMASK, &oset, NULL); |
#endif /* POSIX_SIGNALS */ |
|
|
/* XXX - should use WEXITSTATUS() */ |
|
return(pid == -1 ? -1 : (status >> 8)); |
} |
} |
|
|
|
static int |
|
check_syntax(quiet) |
|
int quiet; |
|
{ |
|
|
|
if ((yyin = fopen(sudoers, "r")) == NULL) { |
|
if (!quiet) |
|
(void) fprintf(stderr, "%s: unable to open %s: %s\n", Argv[0], |
|
sudoers, strerror(errno)); |
|
exit(1); |
|
} |
|
yyout = stdout; |
|
init_parser(); |
|
if (yyparse() && parse_error != TRUE) { |
|
if (!quiet) |
|
(void) fprintf(stderr, |
|
"%s: failed to parse %s file, unknown error.\n", |
|
Argv[0], sudoers); |
|
parse_error = TRUE; |
|
} |
|
if (!quiet){ |
|
if (parse_error) |
|
(void) printf("parse error in %s near line %d\n", sudoers, |
|
errorlineno); |
|
else |
|
(void) printf("%s file parsed OK\n", sudoers); |
|
} |
|
|
|
return(parse_error == TRUE); |
|
} |
|
|
/* |
/* |
* Unlink the sudoers temp file (if it exists) and exit. |
* Unlink the sudoers temp file (if it exists) and exit. |
* Used in place of a normal exit() and as a signal handler. |
* Used in place of a normal exit() and as a signal handler. |
* A positive parameter is considered to be a signal and is reported. |
* A positive parameter indicates we were called as a signal handler. |
*/ |
*/ |
static RETSIGTYPE |
static RETSIGTYPE |
Exit(sig) |
Exit(sig) |
int sig; |
int sig; |
{ |
{ |
|
char *emsg = " exiting due to signal.\n"; |
|
|
(void) unlink(stmp); |
(void) unlink(stmp); |
|
|
if (sig > 0) /* XXX signal race */ |
if (sig > 0) { |
(void) fprintf(stderr, "%s exiting, caught signal %d.\n", Argv[0], sig); |
write(STDERR_FILENO, Argv[0], strlen(Argv[0])); |
|
write(STDERR_FILENO, emsg, sizeof(emsg) - 1); |
exit(-sig); /* XXX for signal case, should be _exit() */ |
_exit(-sig); |
|
} |
|
exit(-sig); |
} |
} |
|
|
static void |
static void |
usage() |
usage() |
{ |
{ |
(void) fprintf(stderr, "usage: %s [-s] [-V]\n", Argv[0]); |
(void) fprintf(stderr, "usage: %s [-c] [-f sudoers] [-q] [-s] [-V]\n", |
|
Argv[0]); |
exit(1); |
exit(1); |
} |
} |