Imported Upstream version 1.7.6p1
[debian/sudo] / iolog.c
1 /*
2  * Copyright (c) 2009-2010 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/param.h>
21 #include <sys/stat.h>
22 #include <sys/time.h>
23 #include <stdio.h>
24 #ifdef STDC_HEADERS
25 # include <stdlib.h>
26 # include <stddef.h>
27 #else
28 # ifdef HAVE_STDLIB_H
29 #  include <stdlib.h>
30 # endif
31 #endif /* STDC_HEADERS */
32 #ifdef HAVE_STRING_H
33 # include <string.h>
34 #endif /* HAVE_STRING_H */
35 #ifdef HAVE_STRINGS_H
36 # include <strings.h>
37 #endif /* HAVE_STRINGS_H */
38 #ifdef HAVE_UNISTD_H
39 # include <unistd.h>
40 #endif /* HAVE_UNISTD_H */
41 #if TIME_WITH_SYS_TIME
42 # include <time.h>
43 #endif
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <signal.h>
47 #include <pwd.h>
48 #include <grp.h>
49 #ifdef HAVE_ZLIB_H
50 # include <zlib.h>
51 #endif
52
53 #include "sudo.h"
54
55 union io_fd {
56     FILE *f;
57 #ifdef HAVE_ZLIB_H
58     gzFile g;
59 #endif
60     void *v;
61 };
62
63 struct script_buf {
64     int len; /* buffer length (how much read in) */
65     int off; /* write position (how much already consumed) */
66     char buf[16 * 1024];
67 };
68
69 #define IOFD_STDIN      0
70 #define IOFD_STDOUT     1
71 #define IOFD_STDERR     2
72 #define IOFD_TTYIN      3
73 #define IOFD_TTYOUT     4
74 #define IOFD_TIMING     5
75 #define IOFD_MAX        6
76
77 #ifdef __STDC__
78 # define SESSID_MAX     2176782336U
79 #else
80 # define SESSID_MAX     (unsigned long)2176782336
81 #endif
82
83 static sigset_t ttyblock;
84 static struct timeval last_time;
85 static union io_fd io_fds[IOFD_MAX];
86
87 void
88 io_nextid()
89 {
90     struct stat sb;
91     char buf[32], *ep;
92     int fd, i;
93     unsigned long id = 0;
94     int len;
95     ssize_t nread;
96     char pathbuf[PATH_MAX];
97     static const char b36char[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
98
99     /*
100      * Create I/O log directory if it doesn't already exist.
101      */
102     if (stat(def_iolog_dir, &sb) != 0) {
103         if (mkdir(def_iolog_dir, S_IRWXU) != 0)
104             log_error(USE_ERRNO, "Can't mkdir %s", def_iolog_dir);
105     } else if (!S_ISDIR(sb.st_mode)) {
106         log_error(0, "%s exists but is not a directory (0%o)",
107             def_iolog_dir, (unsigned int) sb.st_mode);
108     }
109
110     /*
111      * Open sequence file
112      */
113     len = snprintf(pathbuf, sizeof(pathbuf), "%s/seq", def_iolog_dir);
114     if (len <= 0 || len >= sizeof(pathbuf)) {
115         errno = ENAMETOOLONG;
116         log_error(USE_ERRNO, "%s/seq", pathbuf);
117     }
118     fd = open(pathbuf, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
119     if (fd == -1)
120         log_error(USE_ERRNO, "cannot open %s", pathbuf);
121     lock_file(fd, SUDO_LOCK);
122
123     /* Read seq number (base 36). */
124     nread = read(fd, buf, sizeof(buf));
125     if (nread != 0) {
126         if (nread == -1)
127             log_error(USE_ERRNO, "cannot read %s", pathbuf);
128         id = strtoul(buf, &ep, 36);
129         if (buf == ep || id >= SESSID_MAX)
130             log_error(0, "invalid sequence number %s", pathbuf);
131     }
132     id++;
133
134     /*
135      * Convert id to a string and stash in sudo_user.sessid.
136      * Note that that least significant digits go at the end of the string.
137      */
138     for (i = 5; i >= 0; i--) {
139         buf[i] = b36char[id % 36];
140         id /= 36;
141     }
142     buf[6] = '\n';
143
144     /* Stash id logging purposes */
145     memcpy(sudo_user.sessid, buf, 6);
146     sudo_user.sessid[6] = '\0';
147
148     /* Rewind and overwrite old seq file. */
149     if (lseek(fd, 0, SEEK_SET) == (off_t)-1 || write(fd, buf, 7) != 7)
150         log_error(USE_ERRNO, "Can't write to %s", pathbuf);
151     close(fd);
152 }
153
154 static int
155 build_idpath(pathbuf, pathsize)
156     char *pathbuf;
157     size_t pathsize;
158 {
159     struct stat sb;
160     int i, len;
161
162     if (sudo_user.sessid[0] == '\0')
163         log_error(0, "tried to build a session id path without a session id");
164
165     /*
166      * Path is of the form /var/log/sudo-io/00/00/01.
167      */
168     len = snprintf(pathbuf, pathsize, "%s/%c%c/%c%c/%c%c", def_iolog_dir,
169         sudo_user.sessid[0], sudo_user.sessid[1], sudo_user.sessid[2],
170         sudo_user.sessid[3], sudo_user.sessid[4], sudo_user.sessid[5]);
171     if (len <= 0 && len >= pathsize) {
172         errno = ENAMETOOLONG;
173         log_error(USE_ERRNO, "%s/%s", def_iolog_dir, sudo_user.sessid);
174     }
175
176     /*
177      * Create the intermediate subdirs as needed.
178      */
179     for (i = 6; i > 0; i -= 3) {
180         pathbuf[len - i] = '\0';
181         if (stat(pathbuf, &sb) != 0) {
182             if (mkdir(pathbuf, S_IRWXU) != 0)
183                 log_error(USE_ERRNO, "Can't mkdir %s", pathbuf);
184         } else if (!S_ISDIR(sb.st_mode)) {
185             log_error(0, "%s: %s", pathbuf, strerror(ENOTDIR));
186         }
187         pathbuf[len - i] = '/';
188     }
189
190     return len;
191 }
192
193 static void *
194 open_io_fd(pathbuf, len, suffix, docompress)
195     char *pathbuf;
196     int len;
197     const char *suffix;
198     int docompress;
199 {
200     void *vfd = NULL;
201     int fd;
202
203     pathbuf[len] = '\0';
204     strlcat(pathbuf, suffix, PATH_MAX);
205     fd = open(pathbuf, O_CREAT|O_EXCL|O_WRONLY, S_IRUSR|S_IWUSR);
206     if (fd != -1) {
207         fcntl(fd, F_SETFD, FD_CLOEXEC);
208 #ifdef HAVE_ZLIB_H
209         if (docompress)
210             vfd = gzdopen(fd, "w");
211         else
212 #endif
213             vfd = fdopen(fd, "w");
214     }
215     return vfd;
216 }
217
218 int
219 io_log_open()
220 {
221     char pathbuf[PATH_MAX];
222     FILE *io_logfile;
223     int len;
224
225     if (!def_log_input && !def_log_output)
226         return FALSE;
227
228     /*
229      * Build a path containing the session id split into two-digit subdirs,
230      * so ID 000001 becomes /var/log/sudo-io/00/00/01.
231      */
232     len = build_idpath(pathbuf, sizeof(pathbuf));
233     if (len == -1)
234         return -1;
235
236     if (mkdir(pathbuf, S_IRUSR|S_IWUSR|S_IXUSR) != 0)
237         log_error(USE_ERRNO, "Can't mkdir %s", pathbuf);
238
239     /*
240      * We create 7 files: a log file, a timing file and 5 for input/output.
241      */
242     io_logfile = open_io_fd(pathbuf, len, "/log", FALSE);
243     if (io_logfile == NULL)
244         log_error(USE_ERRNO, "Can't create %s", pathbuf);
245
246     io_fds[IOFD_TIMING].v = open_io_fd(pathbuf, len, "/timing", def_compress_io);
247     if (io_fds[IOFD_TIMING].v == NULL)
248         log_error(USE_ERRNO, "Can't create %s", pathbuf);
249
250     if (def_log_input) {
251         io_fds[IOFD_TTYIN].v = open_io_fd(pathbuf, len, "/ttyin", def_compress_io);
252         if (io_fds[IOFD_TTYIN].v == NULL)
253             log_error(USE_ERRNO, "Can't create %s", pathbuf);
254     }
255
256     if (def_log_output) {
257         io_fds[IOFD_TTYOUT].v = open_io_fd(pathbuf, len, "/ttyout", def_compress_io);
258         if (io_fds[IOFD_TTYOUT].v == NULL)
259             log_error(USE_ERRNO, "Can't create %s", pathbuf);
260     }
261
262     if (def_log_input) {
263         io_fds[IOFD_STDIN].v = open_io_fd(pathbuf, len, "/stdin", def_compress_io);
264         if (io_fds[IOFD_STDIN].v == NULL)
265             log_error(USE_ERRNO, "Can't create %s", pathbuf);
266     }
267
268     if (def_log_output) {
269         io_fds[IOFD_STDOUT].v = open_io_fd(pathbuf, len, "/stdout", def_compress_io);
270         if (io_fds[IOFD_STDOUT].v == NULL)
271             log_error(USE_ERRNO, "Can't create %s", pathbuf);
272     }
273
274     if (def_log_output) {
275         io_fds[IOFD_STDERR].v = open_io_fd(pathbuf, len, "/stderr", def_compress_io);
276         if (io_fds[IOFD_STDERR].v == NULL)
277             log_error(USE_ERRNO, "Can't create %s", pathbuf);
278     }
279
280     /* So we can block tty-generated signals */
281     sigemptyset(&ttyblock);
282     sigaddset(&ttyblock, SIGINT);
283     sigaddset(&ttyblock, SIGQUIT);
284     sigaddset(&ttyblock, SIGTSTP);
285     sigaddset(&ttyblock, SIGTTIN);
286     sigaddset(&ttyblock, SIGTTOU);
287
288     gettimeofday(&last_time, NULL);
289
290     /* XXX - log more stuff?  window size? environment? */
291     fprintf(io_logfile, "%ld:%s:%s:%s:%s\n", (long)last_time.tv_sec, user_name,
292         runas_pw->pw_name, runas_gr ? runas_gr->gr_name : "", user_tty);
293     fprintf(io_logfile, "%s\n", user_cwd);
294     fprintf(io_logfile, "%s%s%s\n", user_cmnd, user_args ? " " : "",
295         user_args ? user_args : "");
296     fclose(io_logfile);
297
298     return TRUE;
299 }
300
301 void
302 io_log_close()
303 {
304     int i;
305
306     for (i = 0; i < IOFD_MAX; i++) {
307         if (io_fds[i].v == NULL)
308             continue;
309 #ifdef HAVE_ZLIB_H
310         if (def_compress_io)
311             gzclose(io_fds[i].g);
312         else
313 #endif
314             fclose(io_fds[i].f);
315     }
316 }
317
318 static int
319 log_io(buf, len, idx)
320     const char *buf;
321     unsigned int len;
322     int idx;
323 {
324     struct timeval now, delay;
325     sigset_t omask;
326
327     gettimeofday(&now, NULL);
328
329     sigprocmask(SIG_BLOCK, &ttyblock, &omask);
330
331 #ifdef HAVE_ZLIB_H
332     if (def_compress_io)
333         gzwrite(io_fds[idx].g, buf, len);
334     else
335 #endif
336         fwrite(buf, 1, len, io_fds[idx].f);
337     delay.tv_sec = now.tv_sec;
338     delay.tv_usec = now.tv_usec;
339     timevalsub(&delay, &last_time);
340 #ifdef HAVE_ZLIB_H
341     if (def_compress_io)
342         gzprintf(io_fds[IOFD_TIMING].g, "%d %f %d\n", idx,
343             delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
344     else
345 #endif
346         fprintf(io_fds[IOFD_TIMING].f, "%d %f %d\n", idx,
347             delay.tv_sec + ((double)delay.tv_usec / 1000000), len);
348     last_time.tv_sec = now.tv_sec;
349     last_time.tv_usec = now.tv_usec;
350
351     sigprocmask(SIG_SETMASK, &omask, NULL);
352
353     return TRUE;
354 }
355
356 int
357 log_ttyin(buf, len)
358     const char *buf;
359     unsigned int len;
360 {
361     if (!io_fds[IOFD_TTYIN].v)
362         return TRUE;
363     return log_io(buf, len, IOFD_TTYIN);
364 }
365
366 int
367 log_ttyout(buf, len)
368     const char *buf;
369     unsigned int len;
370 {
371     if (!io_fds[IOFD_TTYOUT].v)
372         return TRUE;
373     return log_io(buf, len, IOFD_TTYOUT);
374 }
375
376 int
377 log_stdin(buf, len)
378     const char *buf;
379     unsigned int len;
380 {
381     if (!io_fds[IOFD_STDIN].v)
382         return TRUE;
383     return log_io(buf, len, IOFD_STDIN);
384 }
385
386 int
387 log_stdout(buf, len)
388     const char *buf;
389     unsigned int len;
390 {
391     if (!io_fds[IOFD_STDOUT].v)
392         return TRUE;
393     return log_io(buf, len, IOFD_STDOUT);
394 }
395
396 int
397 log_stderr(buf, len)
398     const char *buf;
399     unsigned int len;
400 {
401     if (!io_fds[IOFD_STDOUT].v)
402         return TRUE;
403     return log_io(buf, len, IOFD_STDERR);
404 }