Imported Upstream version 1.8.7
[debian/sudo] / src / sudo_edit.c
1 /*
2  * Copyright (c) 2004-2008, 2010-2013 Todd C. Miller <Todd.Miller@courtesan.com>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16
17 #include <config.h>
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/time.h>
22 #include <sys/wait.h>
23 #include <sys/socket.h>
24 #include <stdio.h>
25 #ifdef STDC_HEADERS
26 # include <stdlib.h>
27 # include <stddef.h>
28 #else
29 # ifdef HAVE_STDLIB_H
30 #  include <stdlib.h>
31 # endif
32 #endif /* STDC_HEADERS */
33 #ifdef HAVE_STRING_H
34 # include <string.h>
35 #endif /* HAVE_STRING_H */
36 #ifdef HAVE_STRINGS_H
37 # include <strings.h>
38 #endif /* HAVE_STRINGS_H */
39 #ifdef HAVE_UNISTD_H
40 # include <unistd.h>
41 #endif /* HAVE_UNISTD_H */
42 #include <ctype.h>
43 #include <grp.h>
44 #include <pwd.h>
45 #include <signal.h>
46 #include <errno.h>
47 #include <fcntl.h>
48 #if TIME_WITH_SYS_TIME
49 # include <time.h>
50 #endif
51
52 #include "sudo.h"
53
54 #if defined(HAVE_SETRESUID) || defined(HAVE_SETREUID) || defined(HAVE_SETEUID)
55
56 static void
57 switch_user(uid_t euid, gid_t egid, int ngroups, GETGROUPS_T *groups)
58 {
59     int serrno = errno;
60     debug_decl(switch_user, SUDO_DEBUG_EDIT)
61
62     /* When restoring root, change euid first; otherwise change it last. */
63     if (euid == ROOT_UID) {
64         if (seteuid(ROOT_UID) != 0)
65             fatal("seteuid(ROOT_UID)");
66     }
67     if (setegid(egid) != 0)
68         fatal("setegid(%d)", (int)egid);
69     if (ngroups != -1) {
70         if (sudo_setgroups(ngroups, groups) != 0)
71             fatal("setgroups");
72     }
73     if (euid != ROOT_UID) {
74         if (seteuid(euid) != 0)
75             fatal("seteuid(%d)", (int)euid);
76     }
77     errno = serrno;
78
79     debug_return;
80 }
81
82 /*
83  * Wrapper to allow users to edit privileged files with their own uid.
84  */
85 int
86 sudo_edit(struct command_details *command_details)
87 {
88     struct command_details editor_details;
89     ssize_t nread, nwritten;
90     const char *tmpdir;
91     char *cp, *suff, **nargv, **ap, **files = NULL;
92     char buf[BUFSIZ];
93     int rc, i, j, ac, ofd, tfd, nargc, rval, tmplen;
94     int editor_argc = 0, nfiles = 0;
95     struct stat sb;
96     struct timeval tv, tv1, tv2;
97     struct tempfile {
98         char *tfile;
99         char *ofile;
100         struct timeval omtim;
101         off_t osize;
102     } *tf = NULL;
103     debug_decl(sudo_edit, SUDO_DEBUG_EDIT)
104
105     /*
106      * Set real, effective and saved uids to root.
107      * We will change the euid as needed below.
108      */
109     if (setuid(ROOT_UID) != 0) {
110         warning(_("unable to change uid to root (%u)"), ROOT_UID);
111         goto cleanup;
112     }
113
114     /*
115      * Find our temporary directory, one of /var/tmp, /usr/tmp, or /tmp
116      */
117     if (stat(_PATH_VARTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
118         tmpdir = _PATH_VARTMP;
119 #ifdef _PATH_USRTMP
120     else if (stat(_PATH_USRTMP, &sb) == 0 && S_ISDIR(sb.st_mode))
121         tmpdir = _PATH_USRTMP;
122 #endif
123     else
124         tmpdir = _PATH_TMP;
125     tmplen = strlen(tmpdir);
126     while (tmplen > 0 && tmpdir[tmplen - 1] == '/')
127         tmplen--;
128
129     /*
130      * The user's editor must be separated from the files to be
131      * edited by a "--" option.
132      */
133     for (ap = command_details->argv; *ap != NULL; ap++) {
134         if (files)
135             nfiles++;
136         else if (strcmp(*ap, "--") == 0)
137             files = ap + 1;
138         else
139             editor_argc++;
140     }
141     if (nfiles == 0) {
142         warningx(_("plugin error: missing file list for sudoedit"));
143         goto cleanup;
144     }
145
146     /*
147      * For each file specified by the user, make a temporary version
148      * and copy the contents of the original to it.
149      */
150     tf = emalloc2(nfiles, sizeof(*tf));
151     zero_bytes(tf, nfiles * sizeof(*tf));
152     for (i = 0, j = 0; i < nfiles; i++) {
153         rc = -1;
154         switch_user(command_details->euid, command_details->egid,
155             command_details->ngroups, command_details->groups);
156         if ((ofd = open(files[i], O_RDONLY, 0644)) != -1 || errno == ENOENT) {
157             if (ofd == -1) {
158                 zero_bytes(&sb, sizeof(sb));            /* new file */
159                 rc = 0;
160             } else {
161                 rc = fstat(ofd, &sb);
162             }
163         }
164         switch_user(ROOT_UID, user_details.egid,
165             user_details.ngroups, user_details.groups);
166         if (rc || (ofd != -1 && !S_ISREG(sb.st_mode))) {
167             if (rc)
168                 warning("%s", files[i]);
169             else
170                 warningx(_("%s: not a regular file"), files[i]);
171             if (ofd != -1)
172                 close(ofd);
173             continue;
174         }
175         tf[j].ofile = files[i];
176         tf[j].osize = sb.st_size;
177         mtim_get(&sb, &tf[j].omtim);
178         if ((cp = strrchr(tf[j].ofile, '/')) != NULL)
179             cp++;
180         else
181             cp = tf[j].ofile;
182         suff = strrchr(cp, '.');
183         if (suff != NULL) {
184             easprintf(&tf[j].tfile, "%.*s/%.*sXXXXXXXX%s", tmplen, tmpdir,
185                 (int)(size_t)(suff - cp), cp, suff);
186         } else {
187             easprintf(&tf[j].tfile, "%.*s/%s.XXXXXXXX", tmplen, tmpdir, cp);
188         }
189         if (seteuid(user_details.uid) != 0)
190             fatal("seteuid(%d)", (int)user_details.uid);
191         tfd = mkstemps(tf[j].tfile, suff ? strlen(suff) : 0);
192         if (seteuid(ROOT_UID) != 0)
193             fatal("seteuid(ROOT_UID)");
194         if (tfd == -1) {
195             warning("mkstemps");
196             goto cleanup;
197         }
198         if (ofd != -1) {
199             while ((nread = read(ofd, buf, sizeof(buf))) != 0) {
200                 if ((nwritten = write(tfd, buf, nread)) != nread) {
201                     if (nwritten == -1)
202                         warning("%s", tf[j].tfile);
203                     else
204                         warningx(_("%s: short write"), tf[j].tfile);
205                     goto cleanup;
206                 }
207             }
208             close(ofd);
209         }
210         /*
211          * We always update the stashed mtime because the time
212          * resolution of the filesystem the temporary file is on may
213          * not match that of the filesystem where the file to be edited
214          * resides.  It is OK if touch() fails since we only use the info
215          * to determine whether or not a file has been modified.
216          */
217         (void) touch(tfd, NULL, &tf[j].omtim);
218         rc = fstat(tfd, &sb);
219         if (!rc)
220             mtim_get(&sb, &tf[j].omtim);
221         close(tfd);
222         j++;
223     }
224     if ((nfiles = j) == 0)
225         goto cleanup;           /* no files readable, you lose */
226
227     /*
228      * Allocate space for the new argument vector and fill it in.
229      * We concatenate the editor with its args and the file list
230      * to create a new argv.
231      */
232     nargc = editor_argc + nfiles;
233     nargv = (char **) emalloc2(nargc + 1, sizeof(char *));
234     for (ac = 0; ac < editor_argc; ac++)
235         nargv[ac] = command_details->argv[ac];
236     for (i = 0; i < nfiles && ac < nargc; )
237         nargv[ac++] = tf[i++].tfile;
238     nargv[ac] = NULL;
239
240     /*
241      * Run the editor with the invoking user's creds,
242      * keeping track of the time spent in the editor.
243      */
244     gettimeofday(&tv1, NULL);
245     memcpy(&editor_details, command_details, sizeof(editor_details));
246     editor_details.uid = user_details.uid;
247     editor_details.euid = user_details.uid;
248     editor_details.gid = user_details.gid;
249     editor_details.egid = user_details.gid;
250     editor_details.ngroups = user_details.ngroups;
251     editor_details.groups = user_details.groups;
252     editor_details.argv = nargv;
253     rval = run_command(&editor_details);
254     gettimeofday(&tv2, NULL);
255
256     /* Copy contents of temp files to real ones */
257     for (i = 0; i < nfiles; i++) {
258         rc = -1;
259         if (seteuid(user_details.uid) != 0)
260             fatal("seteuid(%d)", (int)user_details.uid);
261         if ((tfd = open(tf[i].tfile, O_RDONLY, 0644)) != -1) {
262             rc = fstat(tfd, &sb);
263         }
264         if (seteuid(ROOT_UID) != 0)
265             fatal("seteuid(ROOT_UID)");
266         if (rc || !S_ISREG(sb.st_mode)) {
267             if (rc)
268                 warning("%s", tf[i].tfile);
269             else
270                 warningx(_("%s: not a regular file"), tf[i].tfile);
271             warningx(_("%s left unmodified"), tf[i].ofile);
272             if (tfd != -1)
273                 close(tfd);
274             continue;
275         }
276         mtim_get(&sb, &tv);
277         if (tf[i].osize == sb.st_size && timevalcmp(&tf[i].omtim, &tv, ==)) {
278             /*
279              * If mtime and size match but the user spent no measurable
280              * time in the editor we can't tell if the file was changed.
281              */
282             timevalsub(&tv1, &tv2);
283             if (timevalisset(&tv2)) {
284                 warningx(_("%s unchanged"), tf[i].ofile);
285                 unlink(tf[i].tfile);
286                 close(tfd);
287                 continue;
288             }
289         }
290         switch_user(command_details->euid, command_details->egid,
291             command_details->ngroups, command_details->groups);
292         ofd = open(tf[i].ofile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
293         switch_user(ROOT_UID, user_details.egid,
294             user_details.ngroups, user_details.groups);
295         if (ofd == -1) {
296             warning(_("unable to write to %s"), tf[i].ofile);
297             warningx(_("contents of edit session left in %s"), tf[i].tfile);
298             close(tfd);
299             continue;
300         }
301         while ((nread = read(tfd, buf, sizeof(buf))) > 0) {
302             if ((nwritten = write(ofd, buf, nread)) != nread) {
303                 if (nwritten == -1)
304                     warning("%s", tf[i].ofile);
305                 else
306                     warningx(_("%s: short write"), tf[i].ofile);
307                 break;
308             }
309         }
310         if (nread == 0) {
311             /* success, got EOF */
312             unlink(tf[i].tfile);
313         } else if (nread < 0) {
314             warning(_("unable to read temporary file"));
315             warningx(_("contents of edit session left in %s"), tf[i].tfile);
316         } else {
317             warning(_("unable to write to %s"), tf[i].ofile);
318             warningx(_("contents of edit session left in %s"), tf[i].tfile);
319         }
320         close(ofd);
321     }
322     debug_return_int(rval);
323
324 cleanup:
325     /* Clean up temp files and return. */
326     if (tf != NULL) {
327         for (i = 0; i < nfiles; i++) {
328             if (tf[i].tfile != NULL)
329                 unlink(tf[i].tfile);
330         }
331     }
332     debug_return_int(1);
333 }
334
335 #else /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */
336
337 /*
338  * Must have the ability to change the effective uid to use sudoedit.
339  */
340 int
341 sudo_edit(struct command_details *command_details)
342 {
343     debug_decl(sudo_edit, SUDO_DEBUG_EDIT)
344     debug_return_int(1);
345 }
346
347 #endif /* HAVE_SETRESUID || HAVE_SETREUID || HAVE_SETEUID */