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