Imported Upstream version 1.8.5
[debian/sudo] / common / sudo_debug.c
1 /*
2  * Copyright (c) 2011 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/uio.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_STDBOOL_H
33 # include <stdbool.h>
34 #else
35 # include "compat/stdbool.h"
36 #endif
37 #ifdef HAVE_STRING_H
38 # include <string.h>
39 #endif /* HAVE_STRING_H */
40 #ifdef HAVE_STRINGS_H
41 # include <strings.h>
42 #endif /* HAVE_STRINGS_H */
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif /* HAVE_UNISTD_H */
46 #include <ctype.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <time.h>
50
51 #include "missing.h"
52 #include "alloc.h"
53 #include "error.h"
54 #include "gettext.h"
55 #include "sudo_plugin.h"
56 #include "sudo_debug.h"
57
58 /*
59  * The debug priorities and subsystems are currently hard-coded.
60  * In the future we might consider allowing plugins to register their
61  * own subsystems and provide direct access to the debugging API.
62  */
63
64 /* Note: this must match the order in sudo_debug.h */
65 const char *const sudo_debug_priorities[] = {
66     "crit",
67     "err",
68     "warn",
69     "notice",
70     "diag",
71     "info",
72     "trace",
73     "debug",
74     NULL
75 };
76
77 /* Note: this must match the order in sudo_debug.h */
78 const char *const sudo_debug_subsystems[] = {
79     "main",
80     "args",
81     "exec",
82     "pty",
83     "utmp",
84     "conv",
85     "pcomm",
86     "util",
87     "netif",
88     "audit",
89     "edit",
90     "selinux",
91     "ldap",
92     "match",
93     "parser",
94     "alias",
95     "defaults",
96     "auth",
97     "env",
98     "logging",
99     "nss",
100     "rbtree",
101     "perms",
102     "plugin",
103     "hooks",
104     NULL
105 };
106
107 #define NUM_SUBSYSTEMS  (sizeof(sudo_debug_subsystems) / sizeof(sudo_debug_subsystems[0]) - 1)
108
109 /* Values for sudo_debug_mode */
110 #define SUDO_DEBUG_MODE_DISABLED        0
111 #define SUDO_DEBUG_MODE_FILE            1
112 #define SUDO_DEBUG_MODE_CONV            2
113
114 static int sudo_debug_settings[NUM_SUBSYSTEMS];
115 static int sudo_debug_fd = -1;
116 static int sudo_debug_mode;
117 static char sudo_debug_pidstr[(((sizeof(int) * 8) + 2) / 3) + 3];
118 static size_t sudo_debug_pidlen;
119
120 extern sudo_conv_t sudo_conv;
121
122 /*
123  * Parse settings string from sudo.conf and open debugfile.
124  * Returns 1 on success, 0 if cannot open debugfile.
125  * Unsupported subsystems and priorities are silently ignored.
126  */
127 int sudo_debug_init(const char *debugfile, const char *settings)
128 {
129     char *buf, *cp, *subsys, *pri;
130     int i, j;
131
132     /* Init per-subsystems settings to -1 since 0 is a valid priority. */
133     for (i = 0; i < NUM_SUBSYSTEMS; i++)
134         sudo_debug_settings[i] = -1;
135
136     /* Open debug file if specified. */
137     if (debugfile != NULL) {
138         if (sudo_debug_fd != -1)
139             close(sudo_debug_fd);
140         sudo_debug_fd = open(debugfile, O_WRONLY|O_APPEND|O_CREAT,
141             S_IRUSR|S_IWUSR);
142         if (sudo_debug_fd == -1)
143             return 0;
144         (void)fcntl(sudo_debug_fd, F_SETFD, FD_CLOEXEC);
145         sudo_debug_mode = SUDO_DEBUG_MODE_FILE;
146     } else {
147         /* Called from the plugin, no debug file. */
148         sudo_debug_mode = SUDO_DEBUG_MODE_CONV;
149     }
150
151     /* Parse settings string. */
152     buf = estrdup(settings);
153     for ((cp = strtok(buf, ",")); cp != NULL; (cp = strtok(NULL, ","))) {
154         /* Should be in the form subsys@pri. */
155         subsys = cp;
156         if ((pri = strchr(cp, '@')) == NULL)
157             continue;
158         *pri++ = '\0';
159
160         /* Look up priority and subsystem, fill in sudo_debug_settings[]. */
161         for (i = 0; sudo_debug_priorities[i] != NULL; i++) {
162             if (strcasecmp(pri, sudo_debug_priorities[i]) == 0) {
163                 for (j = 0; sudo_debug_subsystems[j] != NULL; j++) {
164                     if (strcasecmp(subsys, "all") == 0) {
165                         sudo_debug_settings[j] = i;
166                         continue;
167                     }
168                     if (strcasecmp(subsys, sudo_debug_subsystems[j]) == 0) {
169                         sudo_debug_settings[j] = i;
170                         break;
171                     }
172                 }
173                 break;
174             }
175         }
176     }
177     efree(buf);
178
179     (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
180         (int)getpid());
181     sudo_debug_pidlen = strlen(sudo_debug_pidstr);
182
183     return 1;
184 }
185
186 pid_t
187 sudo_debug_fork(void)
188 {
189     pid_t pid;
190
191     if ((pid = fork()) == 0) {
192         (void)snprintf(sudo_debug_pidstr, sizeof(sudo_debug_pidstr), "[%d] ",
193             (int)getpid());
194         sudo_debug_pidlen = strlen(sudo_debug_pidstr);
195     }
196
197     return pid;
198 }
199
200 void
201 sudo_debug_enter(const char *func, const char *file, int line,
202     int subsys)
203 {
204     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
205         "-> %s @ %s:%d", func, file, line);
206 }
207
208 void sudo_debug_exit(const char *func, const char *file, int line,
209     int subsys)
210 {
211     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
212         "<- %s @ %s:%d", func, file, line);
213 }
214
215 void sudo_debug_exit_int(const char *func, const char *file, int line,
216     int subsys, int rval)
217 {
218     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
219         "<- %s @ %s:%d := %d", func, file, line, rval);
220 }
221
222 void sudo_debug_exit_long(const char *func, const char *file, int line,
223     int subsys, long rval)
224 {
225     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
226         "<- %s @ %s:%d := %ld", func, file, line, rval);
227 }
228
229 void sudo_debug_exit_size_t(const char *func, const char *file, int line,
230     int subsys, size_t rval)
231 {
232     /* XXX - should use %zu but our snprintf.c doesn't support it */
233     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
234         "<- %s @ %s:%d := %lu", func, file, line, (unsigned long)rval);
235 }
236
237 /* We use int, not bool, here for functions that return -1 on error. */
238 void sudo_debug_exit_bool(const char *func, const char *file, int line,
239     int subsys, int rval)
240 {
241     if (rval == true || rval == false) {
242         sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
243             "<- %s @ %s:%d := %s", func, file, line, rval ? "true" : "false");
244     } else {
245         sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
246             "<- %s @ %s:%d := %d", func, file, line, rval);
247     }
248 }
249
250 void sudo_debug_exit_str(const char *func, const char *file, int line,
251     int subsys, const char *rval)
252 {
253     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
254         "<- %s @ %s:%d := %s", func, file, line, rval ? rval : "(null)");
255 }
256
257 void sudo_debug_exit_str_masked(const char *func, const char *file, int line,
258     int subsys, const char *rval)
259 {
260     static const char stars[] = "********************************************************************************";
261     int len = rval ? strlen(rval) : sizeof("(null)") - 1;
262
263     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
264         "<- %s @ %s:%d := %.*s", func, file, line, len, rval ? stars : "(null)");
265 }
266
267 void sudo_debug_exit_ptr(const char *func, const char *file, int line,
268     int subsys, const void *rval)
269 {
270     sudo_debug_printf2(NULL, NULL, 0, subsys | SUDO_DEBUG_TRACE,
271         "<- %s @ %s:%d := %p", func, file, line, rval);
272 }
273
274 static void
275 sudo_debug_write_conv(const char *func, const char *file, int lineno,
276     const char *str, int len, int errno_val)
277 {
278     struct sudo_conv_message msg;
279     struct sudo_conv_reply repl;
280     char *buf = NULL;
281
282     /* Call conversation function */
283     if (sudo_conv != NULL) {
284         /* Remove the newline at the end if appending extra info. */
285         if (str[len - 1] == '\n')
286             len--;
287
288         if (func != NULL && file != NULL && lineno != 0) {
289             if (errno_val) {
290                 easprintf(&buf, "%.*s: %s @ %s() %s:%d", len, str,
291                     strerror(errno_val), func, file, lineno);
292             } else {
293                 easprintf(&buf, "%.*s @ %s() %s:%d", len, str,
294                     func, file, lineno);
295             }
296             str = buf;
297         } else if (errno_val) {
298             easprintf(&buf, "%.*s: %s", len, str, strerror(errno_val));
299             str = buf;
300         }
301         memset(&msg, 0, sizeof(msg));
302         memset(&repl, 0, sizeof(repl));
303         msg.msg_type = SUDO_CONV_DEBUG_MSG;
304         msg.msg = str;
305         sudo_conv(1, &msg, &repl);
306         if (buf != NULL)
307             efree(buf);
308     }
309 }
310
311 static void
312 sudo_debug_write_file(const char *func, const char *file, int lineno,
313     const char *str, int len, int errno_val)
314 {
315     char *timestr, numbuf[(((sizeof(int) * 8) + 2) / 3) + 2];
316     time_t now;
317     struct iovec iov[12];
318     int iovcnt = 4;
319     bool need_newline = false;
320
321     /* Prepend program name and pid with a trailing space. */
322     iov[1].iov_base = (char *)getprogname();
323     iov[1].iov_len = strlen(iov[1].iov_base);
324     iov[2].iov_base = sudo_debug_pidstr;
325     iov[2].iov_len = sudo_debug_pidlen;
326
327     /* Add string along with newline if it doesn't have one. */
328     iov[3].iov_base = (char *)str;
329     iov[3].iov_len = len;
330     if (str[len - 1] != '\n')
331         need_newline = true;
332
333     /* Append error string if errno is specified. */
334     if (errno_val) {
335         iov[iovcnt].iov_base = ": ";
336         iov[iovcnt].iov_len = 2;
337         iovcnt++;
338         iov[iovcnt].iov_base = strerror(errno_val);
339         iov[iovcnt].iov_len = strlen(iov[iovcnt].iov_base);
340         iovcnt++;
341
342         /* Move newline to the end. */
343         if (!need_newline) {
344             need_newline = true;
345             iov[3].iov_len--;
346         }
347     }
348
349     /* If function, file and lineno are specified, append them. */
350     if (func != NULL && file != NULL && lineno != 0) {
351         iov[iovcnt].iov_base = " @ ";
352         iov[iovcnt].iov_len = 3;
353         iovcnt++;
354
355         iov[iovcnt].iov_base = (char *)func;
356         iov[iovcnt].iov_len = strlen(func);
357         iovcnt++;
358
359         iov[iovcnt].iov_base = "() ";
360         iov[iovcnt].iov_len = 3;
361         iovcnt++;
362
363         iov[iovcnt].iov_base = (char *)file;
364         iov[iovcnt].iov_len = strlen(file);
365         iovcnt++;
366
367         (void)snprintf(numbuf, sizeof(numbuf), ":%d", lineno);
368         iov[iovcnt].iov_base = numbuf;
369         iov[iovcnt].iov_len = strlen(numbuf);
370         iovcnt++;
371
372         /* Move newline to the end. */
373         if (!need_newline) {
374             need_newline = true;
375             iov[3].iov_len--;
376         }
377     }
378
379     /* Append newline as needed. */
380     if (need_newline) {
381         /* force newline */
382         iov[iovcnt].iov_base = "\n";
383         iov[iovcnt].iov_len = 1;
384         iovcnt++;
385     }
386
387     /* Do timestamp last due to ctime's static buffer. */
388     now = time(NULL);
389     timestr = ctime(&now) + 4;
390     timestr[15] = ' ';  /* replace year with a space */
391     timestr[16] = '\0';
392     iov[0].iov_base = timestr;
393     iov[0].iov_len = 16;
394
395     /* Write message in a single syscall */
396     ignore_result(writev(sudo_debug_fd, iov, iovcnt));
397 }
398
399 void
400 sudo_debug_write2(const char *func, const char *file, int lineno,
401     const char *str, int len, int errno_val)
402 {
403     if (len <= 0)
404         return;
405
406     switch (sudo_debug_mode) {
407     case SUDO_DEBUG_MODE_CONV:
408         sudo_debug_write_conv(func, file, lineno, str, len, errno_val);
409         break;
410     case SUDO_DEBUG_MODE_FILE:
411         sudo_debug_write_file(func, file, lineno, str, len, errno_val);
412         break;
413     }
414 }
415
416 /* XXX - turn into a macro */
417 void
418 sudo_debug_write(const char *str, int len, int errno_val)
419 {
420     sudo_debug_write2(NULL, NULL, 0, str, len, errno_val);
421 }
422
423 void
424 sudo_debug_printf2(const char *func, const char *file, int lineno, int level,
425     const char *fmt, ...)
426 {
427     int buflen, pri, subsys, saved_errno = errno;
428     va_list ap;
429     char *buf;
430
431     if (!sudo_debug_mode)
432         return;
433
434     /* Extract pri and subsystem from level. */
435     pri = SUDO_DEBUG_PRI(level);
436     subsys = SUDO_DEBUG_SUBSYS(level);
437
438     /* Make sure we want debug info at this level. */
439     if (subsys < NUM_SUBSYSTEMS && sudo_debug_settings[subsys] >= pri) {
440         va_start(ap, fmt);
441         buflen = vasprintf(&buf, fmt, ap);
442         va_end(ap);
443         if (buflen != -1) {
444             int errcode = ISSET(level, SUDO_DEBUG_ERRNO) ? saved_errno : 0;
445             if (ISSET(level, SUDO_DEBUG_LINENO))
446                 sudo_debug_write2(func, file, lineno, buf, buflen, errcode);
447             else
448                 sudo_debug_write2(NULL, NULL, 0, buf, buflen, errcode);
449             free(buf);
450         }
451     }
452
453     errno = saved_errno;
454 }
455
456 void
457 sudo_debug_execve2(int level, const char *path, char *const argv[], char *const envp[])
458 {
459     char * const *av;
460     char *buf, *cp;
461     int buflen, pri, subsys, log_envp = 0;
462     size_t plen;
463
464     if (!sudo_debug_mode)
465         return;
466
467     /* Extract pri and subsystem from level. */
468     pri = SUDO_DEBUG_PRI(level);
469     subsys = SUDO_DEBUG_SUBSYS(level);
470
471     /* Make sure we want debug info at this level. */
472     if (subsys >= NUM_SUBSYSTEMS || sudo_debug_settings[subsys] < pri)
473         return;
474
475     /* Log envp for debug level "debug". */
476     if (sudo_debug_settings[subsys] >= SUDO_DEBUG_DEBUG - 1 && envp[0] != NULL)
477         log_envp = 1;
478
479 #define EXEC_PREFIX "exec "
480
481     /* Alloc and build up buffer. */
482     plen = strlen(path);
483     buflen = sizeof(EXEC_PREFIX) -1 + plen;
484     if (argv[0] != NULL) {
485         buflen += sizeof(" []") - 1;
486         for (av = argv; *av; av++)
487             buflen += strlen(*av) + 1;
488         buflen--;
489     }
490     if (log_envp) {
491         buflen += sizeof(" []") - 1;
492         for (av = envp; *av; av++)
493             buflen += strlen(*av) + 1;
494         buflen--;
495     }
496     buf = malloc(buflen + 1);
497     if (buf == NULL)
498         return;
499
500     /* Copy prefix and command. */
501     memcpy(buf, EXEC_PREFIX, sizeof(EXEC_PREFIX) - 1);
502     cp = buf + sizeof(EXEC_PREFIX) - 1;
503     memcpy(cp, path, plen);
504     cp += plen;
505
506     /* Copy argv. */
507     if (argv[0] != NULL) {
508         *cp++ = ' ';
509         *cp++ = '[';
510         for (av = argv; *av; av++) {
511             size_t avlen = strlen(*av);
512             memcpy(cp, *av, avlen);
513             cp += avlen;
514             *cp++ = ' ';
515         }
516         cp[-1] = ']';
517     }
518
519     if (log_envp) {
520         *cp++ = ' ';
521         *cp++ = '[';
522         for (av = envp; *av; av++) {
523             size_t avlen = strlen(*av);
524             memcpy(cp, *av, avlen);
525             cp += avlen;
526             *cp++ = ' ';
527         }
528         cp[-1] = ']';
529     }
530
531     *cp = '\0';
532
533     sudo_debug_write(buf, buflen, 0);
534     free(buf);
535 }
536
537 /*
538  * Dup sudo_debug_fd to the specified value so we don't
539  * close it when calling closefrom().
540  */
541 int
542 sudo_debug_fd_set(int fd)
543 {
544     if (sudo_debug_fd != -1 && fd != sudo_debug_fd) {
545         if (dup2(sudo_debug_fd, fd) == -1)
546             return -1;
547         (void)fcntl(fd, F_SETFD, FD_CLOEXEC);
548         close(sudo_debug_fd);
549         sudo_debug_fd = fd;
550     }
551     return sudo_debug_fd;
552 }