Merge commit 'upstream/1.7.2p6'
[debian/sudo] / sudo_edit.c
index f765e8a0a7d7473fce5a3913d314c4d984f40697..04d5d7434e4743af140dec6ee55713b531d5137a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004 Todd C. Miller <Todd.Miller@courtesan.com>
+ * Copyright (c) 2004-2008 Todd C. Miller <Todd.Miller@courtesan.com>
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
@@ -14,7 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include "config.h"
+#include <config.h>
 
 #include <sys/types.h>
 #include <sys/param.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif /* HAVE_UNISTD_H */
-#ifdef HAVE_ERR_H
-# include <err.h>
-#else
-# include "emul/err.h"
-#endif /* HAVE_ERR_H */
 #include <ctype.h>
 #include <pwd.h>
 #include <signal.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <time.h>
+#if TIME_WITH_SYS_TIME
+# include <time.h>
+#endif
+#ifndef HAVE_TIMESPEC
+# include <emul/timespec.h>
+#endif
 
 #include "sudo.h"
 
-#ifndef lint
-static const char rcsid[] = "$Sudo: sudo_edit.c,v 1.16 2004/09/15 16:16:20 millert Exp $";
-#endif /* lint */
+extern sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp;
+extern char **environ;
 
-extern sigaction_t saved_sa_int, saved_sa_quit, saved_sa_tstp, saved_sa_chld;
+static char *find_editor();
 
 /*
  * Wrapper to allow users to edit privileged files with their own uid.
  */
-int sudo_edit(argc, argv)
+int
+sudo_edit(argc, argv, envp)
     int argc;
     char **argv;
+    char **envp;
 {
     ssize_t nread, nwritten;
     pid_t kidpid, pid;
     const char *tmpdir;
     char **nargv, **ap, *editor, *cp;
     char buf[BUFSIZ];
-    int i, ac, ofd, tfd, nargc, rval, tmplen;
-    sigaction_t sa;
+    int error, i, ac, ofd, tfd, nargc, rval, tmplen, wasblank;
     struct stat sb;
     struct timespec ts1, ts2;
     struct tempfile {
@@ -99,39 +99,42 @@ int sudo_edit(argc, argv)
     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
        tmplen--;
 
+    /*
+     * Close password, shadow, and group files before we try to open
+     * user-specified files to prevent the opening of things like /dev/fd/4
+     */
+    sudo_endpwent();
+    sudo_endgrent();
+
     /*
      * For each file specified by the user, make a temporary version
      * and copy the contents of the original to it.
-     * XXX - It would be nice to lock the original files but that means
-     *       keeping an extra fd open for each file.
      */
     tf = emalloc2(argc - 1, sizeof(*tf));
-    memset(tf, 0, (argc - 1) * sizeof(*tf));
+    zero_bytes(tf, (argc - 1) * sizeof(*tf));
     for (i = 0, ap = argv + 1; i < argc - 1 && *ap != NULL; i++, ap++) {
+       error = -1;
        set_perms(PERM_RUNAS);
-       ofd = open(*ap, O_RDONLY, 0644);
-       if (ofd != -1) {
+       if ((ofd = open(*ap, O_RDONLY, 0644)) != -1 || errno == ENOENT) {
+           if (ofd == -1) {
+               zero_bytes(&sb, sizeof(sb));            /* new file */
+               error = 0;
+           } else {
 #ifdef HAVE_FSTAT
-           if (fstat(ofd, &sb) != 0) {
+               error = fstat(ofd, &sb);
 #else
-           if (stat(tf[i].ofile, &sb) != 0) {
+               error = stat(tf[i].ofile, &sb);
 #endif
-               close(ofd);     /* XXX - could reset errno */
-               ofd = -1;
            }
        }
        set_perms(PERM_ROOT);
-       if (ofd == -1) {
-           if (errno != ENOENT) {
-               warn("%s", *ap);
-               argc--;
-               i--;
-               continue;
-           }
-           memset(&sb, 0, sizeof(sb));
-       } else if (!S_ISREG(sb.st_mode)) {
-           warnx("%s: not a regular file", *ap);
-           close(ofd);
+       if (error || (ofd != -1 && !S_ISREG(sb.st_mode))) {
+           if (error)
+               warning("%s", *ap);
+           else
+               warningx("%s: not a regular file", *ap);
+           if (ofd != -1)
+               close(ofd);
            argc--;
            i--;
            continue;
@@ -149,54 +152,45 @@ int sudo_edit(argc, argv)
        tfd = mkstemp(tf[i].tfile);
        set_perms(PERM_ROOT);
        if (tfd == -1) {
-           warn("mkstemp");
+           warning("mkstemp");
            goto cleanup;
        }
        if (ofd != -1) {
            while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
                if ((nwritten = write(tfd, buf, nread)) != nread) {
                    if (nwritten == -1)
-                       warn("%s", tf[i].tfile);
+                       warning("%s", tf[i].tfile);
                    else
-                       warnx("%s: short write", tf[i].tfile);
+                       warningx("%s: short write", tf[i].tfile);
                    goto cleanup;
                }
            }
            close(ofd);
        }
-#ifdef HAVE_FSTAT
        /*
-        * If we are unable to set the mtime on the temp file to the value
-        * of the original file just make the stashed mtime match the temp
-        * file's mtime.  It is better than nothing and we only use the info
+        * We always update the stashed mtime because the time
+        * resolution of the filesystem the temporary file is on may
+        * not match that of the filesystem where the file to be edited
+        * resides.  It is OK if touch() fails since we only use the info
         * to determine whether or not a file has been modified.
         */
-       if (touch(tfd, NULL, &tf[i].omtim) == -1) {
-           if (fstat(tfd, &sb) == 0) {
-               tf[i].omtim.tv_sec = mtim_getsec(sb);
-               tf[i].omtim.tv_nsec = mtim_getnsec(sb);
-           }
-           /* XXX - else error? */
-       }
+       (void) touch(tfd, NULL, &tf[i].omtim);
+#ifdef HAVE_FSTAT
+       error = fstat(tfd, &sb);
+#else
+       error = stat(tf[i].tfile, &sb);
 #endif
+       if (!error) {
+           tf[i].omtim.tv_sec = mtim_getsec(sb);
+           tf[i].omtim.tv_nsec = mtim_getnsec(sb);
+       }
        close(tfd);
     }
     if (argc == 1)
        return(1);                      /* no files readable, you lose */
 
-    /*
-     * Determine which editor to use.  We don't bother restricting this
-     * based on def_env_editor or def_editor since the editor runs with
-     * the uid of the invoking user, not the runas (privileged) user.
-     */
-    if (((editor = getenv("VISUAL")) != NULL && *editor != '\0') ||
-       ((editor = getenv("EDITOR")) != NULL && *editor != '\0')) {
-       editor = estrdup(editor);
-    } else {
-       editor = estrdup(def_editor);
-       if ((cp = strchr(editor, ':')) != NULL)
-           *cp = '\0';                 /* def_editor could be a path */
-    }
+    environ = envp;
+    editor = find_editor();
 
     /*
      * Allocate space for the new argument vector and fill it in.
@@ -204,9 +198,13 @@ int sudo_edit(argc, argv)
      * line args so look for those and alloc space for them too.
      */
     nargc = argc;
-    for (cp = editor + 1; *cp != '\0'; cp++) {
-       if (isblank((unsigned char)cp[0]) && !isblank((unsigned char)cp[-1]))
+    for (wasblank = FALSE, cp = editor; *cp != '\0'; cp++) {
+       if (isblank((unsigned char) *cp))
+           wasblank = TRUE;
+       else if (wasblank) {
+           wasblank = FALSE;
            nargc++;
+       }
     }
     nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
     ac = 0;
@@ -216,11 +214,7 @@ int sudo_edit(argc, argv)
        nargv[ac++] = tf[i++].tfile;
     nargv[ac] = NULL;
 
-    /* We wait for our own children and can be suspended. */
-    sigemptyset(&sa.sa_mask);
-    sa.sa_flags = SA_RESTART;
-    sa.sa_handler = SIG_DFL;
-    (void) sigaction(SIGCHLD, &sa, NULL);
+    /* Allow the editor to be suspended. */
     (void) sigaction(SIGTSTP, &saved_sa_tstp, NULL);
 
     /*
@@ -230,23 +224,23 @@ int sudo_edit(argc, argv)
     gettime(&ts1);
     kidpid = fork();
     if (kidpid == -1) {
-       warn("fork");
+       warning("fork");
        goto cleanup;
     } else if (kidpid == 0) {
        /* child */
        (void) sigaction(SIGINT, &saved_sa_int, NULL);
        (void) sigaction(SIGQUIT, &saved_sa_quit, NULL);
-       (void) sigaction(SIGCHLD, &saved_sa_chld, NULL);
        set_perms(PERM_FULL_USER);
+       closefrom(def_closefrom);
        execvp(nargv[0], nargv);
-       warn("unable to execute %s", nargv[0]);
+       warning("unable to execute %s", nargv[0]);
        _exit(127);
     }
 
     /*
      * Wait for status from the child.  Most modern kernels
      * will not let an unprivileged child process send a
-     * signal to its privileged parent to we have to request
+     * signal to its privileged parent so we have to request
      * status when the child is stopped and then send the
      * same signal to our own pid.
      */
@@ -271,57 +265,59 @@ int sudo_edit(argc, argv)
 
     /* Copy contents of temp files to real ones */
     for (i = 0; i < argc - 1; i++) {
+       error = -1;
        set_perms(PERM_USER);
-       tfd = open(tf[i].tfile, O_RDONLY, 0644);
+       if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) {
+#ifdef HAVE_FSTAT
+           error = fstat(tfd, &sb);
+#else
+           error = stat(tf[i].tfile, &sb);
+#endif
+       }
        set_perms(PERM_ROOT);
-       if (tfd < 0) {
-           warn("unable to read %s", tf[i].tfile);
-           warnx("%s left unmodified", tf[i].ofile);
+       if (error || !S_ISREG(sb.st_mode)) {
+           if (error)
+               warning("%s", tf[i].tfile);
+           else
+               warningx("%s: not a regular file", tf[i].tfile);
+           warningx("%s left unmodified", tf[i].ofile);
+           if (tfd != -1)
+               close(tfd);
            continue;
        }
-#ifdef HAVE_FSTAT
-       if (fstat(tfd, &sb) == 0) {
-           if (!S_ISREG(sb.st_mode)) {
-               warnx("%s: not a regular file", tf[i].tfile);
-               warnx("%s left unmodified", tf[i].ofile);
-               continue;
-           }
-           if (tf[i].osize == sb.st_size &&
-               tf[i].omtim.tv_sec == mtim_getsec(sb) &&
-               tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
-               /*
-                * If mtime and size match but the user spent no measurable
-                * time in the editor we can't tell if the file was changed.
-                */
+       if (tf[i].osize == sb.st_size && tf[i].omtim.tv_sec == mtim_getsec(sb)
+           && tf[i].omtim.tv_nsec == mtim_getnsec(sb)) {
+           /*
+            * If mtime and size match but the user spent no measurable
+            * time in the editor we can't tell if the file was changed.
+            */
 #ifdef HAVE_TIMESPECSUB2
-               timespecsub(&ts1, &ts2);
+           timespecsub(&ts1, &ts2);
 #else
-               timespecsub(&ts1, &ts2, &ts2);
+           timespecsub(&ts1, &ts2, &ts2);
 #endif
-               if (timespecisset(&ts2)) {
-                   warnx("%s unchanged", tf[i].ofile);
-                   unlink(tf[i].tfile);
-                   close(tfd);
-                   continue;
-               }
+           if (timespecisset(&ts2)) {
+               warningx("%s unchanged", tf[i].ofile);
+               unlink(tf[i].tfile);
+               close(tfd);
+               continue;
            }
        }
-#endif
        set_perms(PERM_RUNAS);
        ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
        set_perms(PERM_ROOT);
        if (ofd == -1) {
-           warn("unable to write to %s", tf[i].ofile);
-           warnx("contents of edit session left in %s", tf[i].tfile);
+           warning("unable to write to %s", tf[i].ofile);
+           warningx("contents of edit session left in %s", tf[i].tfile);
            close(tfd);
            continue;
        }
        while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
            if ((nwritten = write(ofd, buf, nread)) != nread) {
                if (nwritten == -1)
-                   warn("%s", tf[i].ofile);
+                   warning("%s", tf[i].ofile);
                else
-                   warnx("%s: short write", tf[i].ofile);
+                   warningx("%s: short write", tf[i].ofile);
                break;
            }
        }
@@ -329,11 +325,11 @@ int sudo_edit(argc, argv)
            /* success, got EOF */
            unlink(tf[i].tfile);
        } else if (nread < 0) {
-           warn("unable to read temporary file");
-           warnx("contents of edit session left in %s", tf[i].tfile);
+           warning("unable to read temporary file");
+           warningx("contents of edit session left in %s", tf[i].tfile);
        } else {
-           warn("unable to write to %s", tf[i].ofile);
-           warnx("contents of edit session left in %s", tf[i].tfile);
+           warning("unable to write to %s", tf[i].ofile);
+           warningx("contents of edit session left in %s", tf[i].tfile);
        }
        close(ofd);
     }
@@ -347,3 +343,40 @@ cleanup:
     }
     return(1);
 }
+
+/*
+ * Determine which editor to use.  We don't bother restricting this
+ * based on def_env_editor or def_editor since the editor runs with
+ * the uid of the invoking user, not the runas (privileged) user.
+ */
+static char *
+find_editor()
+{
+    char *cp, *editor = NULL, **ev, *ev0[4];
+
+    ev0[0] = "SUDO_EDITOR";
+    ev0[1] = "VISUAL";
+    ev0[2] = "EDITOR";
+    ev0[3] = NULL;
+    for (ev = ev0; *ev != NULL; ev++) {
+       if ((editor = getenv(*ev)) != NULL && *editor != '\0') {
+           if ((cp = strrchr(editor, '/')) != NULL)
+               cp++;
+           else
+               cp = editor;
+           /* Ignore "sudoedit" and "sudo" to avoid an endless loop. */
+           if (strncmp(cp, "sudo", 4) != 0 ||
+               (cp[4] != ' ' && cp[4] != '\0' && strcmp(cp + 4, "edit") != 0)) {
+               editor = estrdup(editor);
+               break;
+           }
+       }
+       editor = NULL;
+    }
+    if (editor == NULL) {
+       editor = estrdup(def_editor);
+       if ((cp = strchr(editor, ':')) != NULL)
+           *cp = '\0';                 /* def_editor could be a path */
+    }
+    return(editor);
+}