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