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