/* * CU sudo version 1.3.1 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Please send bugs, changes, problems to sudo-bugs@cs.colorado.edu * ******************************************************************* * * visudo.c -- locks the sudoers file for safe editing and check * for parse errors. * * Todd C. Miller (millert@colorado.edu) Sat Mar 25 21:50:36 MST 1995 */ #ifndef lint static char rcsid[] = "$Id$"; #endif /* lint */ #include "config.h" #include #ifdef STDC_HEADERS #include #endif /* STDC_HEADERS */ #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #ifdef HAVE_STRING_H #include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H #include #endif /* HAVE_STRINGS_H */ #ifdef HAVE_MALLOC_H #include #endif /* HAVE_MALLOC_H */ #include #include #include #include #include #include #include #include #include #include "sudo.h" #include "options.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 */ /* * Function prototypes */ static void usage __P((void)); static char whatnow __P((void)); static void whatnow_help __P((void)); static RETSIGTYPE Exit __P((int)); int path_matches __P((char *, char *)); int ntwk_matches __P((char *)); /* * External globals */ extern FILE *yyin, *yyout; extern int errorlineno, sudolineno; /* * Globals */ char **Argv; char *sudoers = _PATH_SUDO_SUDOERS; char *stmp = _PATH_SUDO_STMP; int parse_error = FALSE; /* * For the parsing routines */ char host[] = ""; char *user = ""; char *cmnd = ""; int main(argc, argv) int argc; char **argv; { char buf[BUFSIZ]; /* buffer used for copying files */ char * Editor = EDITOR; /* Editor to use (default is EDITOR */ int sudoers_fd; /* sudoers file descriptor */ int stmp_fd; /* stmp file descriptor */ int n; /* length parameter */ struct passwd *pwd; /* to look up info for SUDOERS_OWNER */ #ifdef POSIX_SIGNALS struct sigaction action; /* posix signal structure */ #endif /* POSIX_SIGNALS */ /* * Setup signal handlers */ #ifdef POSIX_SIGNALS (void) memset((VOID *)&action, 0, sizeof(action)); action.sa_handler = Exit; action.sa_flags = SA_RESETHAND; (void) sigaction(SIGILL, &action, NULL); (void) sigaction(SIGTRAP, &action, NULL); (void) sigaction(SIGBUS, &action, NULL); (void) sigaction(SIGSEGV, &action, NULL); (void) sigaction(SIGTERM, &action, NULL); action.sa_handler = SIG_IGN; action.sa_flags = 0; (void) sigaction(SIGHUP, &action, NULL); (void) sigaction(SIGINT, &action, NULL); (void) sigaction(SIGQUIT, &action, NULL); #else (void) signal(SIGILL, Exit); (void) signal(SIGTRAP, Exit); (void) signal(SIGBUS, Exit); (void) signal(SIGSEGV, Exit); (void) signal(SIGTERM, Exit); (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); #endif /* POSIX_SIGNALS */ (void) setbuf(stderr, (char *)NULL); /* unbuffered stderr */ /* * Parse command line options */ Argv = argv; /* * If passesd -V then print version, else print usage * if any other option... */ if (argc == 2) if (!strcmp(Argv[1], "-V")) { (void) printf("visudo version %s\n", version); Exit(0); } else { usage(); } else if (argc != 1) usage(); #ifdef ENV_EDITOR /* * If we are allowing EDITOR and VISUAL envariables set Editor * base on whichever exists... */ if (!(Editor = getenv("EDITOR"))) if (!(Editor = getenv("VISUAL"))) Editor = EDITOR; #endif /* ENV_EDITOR */ /* * Need to find who should own the sudoers file */ if (!(pwd = getpwnam(SUDOERS_OWNER))) { (void) fprintf(stderr, "%s: no passwd entry for sudoers file owner (%s)\n", Argv[0], SUDOERS_OWNER); Exit(1); } /* * Copy sudoers file to stmp */ stmp_fd = open(stmp, O_WRONLY | O_CREAT | O_EXCL, 0600); if (stmp_fd < 0) { if (errno == EEXIST) { (void) fprintf(stderr, "%s: sudoers file busy\n", Argv[0]); Exit(1); } (void) fprintf(stderr, "%s: ", Argv[0]); perror(stmp); Exit(1); } sudoers_fd = open(sudoers, O_RDONLY); if (sudoers_fd < 0) { (void) fprintf(stderr, "%s: ", Argv[0]); perror(sudoers); Exit(1); } /* * Copy the data */ while ((n = read(sudoers_fd, buf, sizeof(buf))) > 0) if (write(stmp_fd, buf, n) != n) { (void) fprintf(stderr, "%s: Write failed: ", Argv[0]); perror(""); Exit(1); } (void) close(sudoers_fd); (void) close(stmp_fd); /* * Change ownership of temp file to SUDOERS_OWNER * so when we move it to sudoers things are kosher. */ (void) chown(stmp, pwd -> pw_uid, -1); /* * Edit the temp file and parse it (for sanity checking) */ do { /* * Build up a buffer to execute */ if (parse_error == TRUE) (void) sprintf(buf, "%s +%d %s", Editor, errorlineno, stmp); else (void) sprintf(buf, "%s %s", Editor, stmp); /* do the edit */ if (system(buf) == 0) { struct stat statbuf; /* for sanity checking */ /* make sure stmp exists */ if (stat(stmp, &statbuf) < 0) { (void) fprintf(stderr, "%s: Can't stat temporary file (%s), %s unchanged.\n", Argv[0], stmp, sudoers); Exit(1); } /* check for zero length file */ if (statbuf.st_size == 0) { (void) fprintf(stderr, "%s: Zero length temporary file (%s), %s unchanged.\n", Argv[0], stmp, sudoers); Exit(1); } /* * passed sanity checks so reopen stmp file and check * for parse errors. */ yyout = stdout; if (parse_error) yyin = freopen(stmp, "r", yyin); else yyin = fopen(stmp, "r"); if (yyin == NULL) { (void) fprintf(stderr, "%s: Can't re-open temporary file (%s), %s unchanged.\n", Argv[0], stmp, sudoers); Exit(1); } /* clean slate for each parse */ parse_error = FALSE; errorlineno = -1; sudolineno = 1; /* parse the sudoers file */ if (yyparse()) { (void) fprintf(stderr, "%s: Failed to parse temporary file (%s), %s unchanged.\n", Argv[0], stmp, sudoers); Exit(1); } } else { (void) fprintf(stderr, "%s: Editor (%s) failed, %s unchanged.\n", Argv[0], Editor, sudoers); Exit(1); } /* * Prompt the user for what to do now */ if (parse_error == TRUE) { switch (whatnow()) { case 'q' : parse_error = FALSE; /* ignore parse error */ break; case 'x' : Exit(0); break; } } } while (parse_error == TRUE); /* * Now that we have a sane stmp file (parse ok) it needs to be * rename(2)'d to sudoers. If the rename(2) fails we try using * mv(1) in case stmp and sudoers are on different filesystems. */ if (rename(stmp, sudoers)) if (errno == EXDEV) { char *tmpbuf; (void) fprintf(stderr, "%s: %s and %s not on the same filesystem, using mv to rename.\n", Argv[0], stmp, sudoers); /* Allocate just enough space for tmpbuf */ n = sizeof(char) * (strlen(_PATH_MV) + strlen(stmp) + strlen(sudoers) + 4); if ((tmpbuf = (char *) malloc(n)) == NULL) { (void) fprintf(stderr, "%s: Cannot alocate memory, %s unchanged: ", Argv[0], sudoers); perror(""); Exit(1); } /* Build up command and execute it */ (void) sprintf(tmpbuf, "%s %s %s", _PATH_MV, stmp, sudoers); if (system(tmpbuf)) { (void) fprintf(stderr, "%s: Command failed: '%s', %s unchanged.\n", Argv[0], tmpbuf, sudoers); Exit(1); } (void) free(tmpbuf); } else { (void) fprintf(stderr, "%s: Error renaming %s, %s unchanged: ", Argv[0], stmp, sudoers); perror(""); Exit(1); } /* * Make the new sudoers file readable only by owner. * If this fail it is ok since the file is only least rw owner. */ if (chmod(sudoers, 0400)) { (void) fprintf(stderr, "%s: Warning, unable to chmod 0400 %s: ", Argv[0], sudoers); perror(""); } return(0); } /* * dummy *_matches routines */ int path_matches(cmnd, path) char *cmnd, *path; { return(TRUE); } int ntwk_matches(n) char *n; { return(TRUE); } /* * usage() -- prints a help message and exits. */ static void usage() { (void) fprintf(stderr, "usage: %s [-V]\n", Argv[0]); Exit(1); } /* * Exit() -- unlinks the sudoers temp file (if there) and exits. * Used in place of a normal exit() and as a signal handler. */ static RETSIGTYPE Exit(sig) int sig; { (void) unlink(stmp); exit(sig); } /* * whatnow() -- assuming a parse error occurred, prompt the user for * what they want to do now. Returns first letter * of their choice (always lowercase). */ static char whatnow() { char choice; int ok; do { ok = FALSE; printf("What now? "); if ((choice = fgetc(stdin)) != '\n') while (fgetc(stdin) != '\n') ; /* safely force to lower case */ if (isupper(choice)) choice = tolower(choice); if (choice == 'e' || choice == 'x' || choice == 'q') ok = TRUE; /* help message if they gavce us garbage */ if (!ok) whatnow_help(); } while (!ok); return(choice); } /* * whatnow_help() -- print out a help message for whatnow(). */ static void whatnow_help() { printf("Options are:\n"); printf(" (e)dit sudoers file again\n"); printf(" e(x)it without saving changes to sudoers file\n"); printf(" (q)uit and save changes to sudoers file (DANGER!)\n\n"); }