Annotation of src/usr.bin/sudo/logging.c, Revision 1.1
1.1 ! millert 1: /*
! 2: * Copyright (c) 1994-1996,1998-1999 Todd C. Miller <Todd.Miller@courtesan.com>
! 3: * All rights reserved.
! 4: *
! 5: * Redistribution and use in source and binary forms, with or without
! 6: * modification, are permitted provided that the following conditions
! 7: * are met:
! 8: *
! 9: * 1. Redistributions of source code must retain the above copyright
! 10: * notice, this list of conditions and the following disclaimer.
! 11: *
! 12: * 2. Redistributions in binary form must reproduce the above copyright
! 13: * notice, this list of conditions and the following disclaimer in the
! 14: * documentation and/or other materials provided with the distribution.
! 15: *
! 16: * 3. The name of the author may not be used to endorse or promote products
! 17: * derived from this software without specific prior written permission.
! 18: *
! 19: * 4. Products derived from this software may not be called "Sudo" nor
! 20: * may "Sudo" appear in their names without specific prior written
! 21: * permission from the author.
! 22: *
! 23: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
! 24: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
! 25: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
! 26: * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
! 27: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
! 28: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
! 29: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
! 30: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
! 31: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
! 32: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 33: */
! 34:
! 35: #include "config.h"
! 36:
! 37: #include <stdio.h>
! 38: #ifdef STDC_HEADERS
! 39: #include <stdlib.h>
! 40: #endif /* STDC_HEADERS */
! 41: #ifdef HAVE_UNISTD_H
! 42: #include <unistd.h>
! 43: #endif /* HAVE_UNISTD_H */
! 44: #ifdef HAVE_STRING_H
! 45: #include <string.h>
! 46: #endif /* HAVE_STRING_H */
! 47: #ifdef HAVE_STRINGS_H
! 48: #include <strings.h>
! 49: #endif /* HAVE_STRINGS_H */
! 50: #include <pwd.h>
! 51: #include <signal.h>
! 52: #include <time.h>
! 53: #include <errno.h>
! 54: #include <sys/types.h>
! 55: #include <sys/param.h>
! 56: #include <sys/stat.h>
! 57: #include <sys/wait.h>
! 58:
! 59: #include "sudo.h"
! 60:
! 61: #ifndef lint
! 62: static const char rcsid[] = "$Sudo: logging.c,v 1.139 1999/10/09 05:01:48 millert Exp $";
! 63: #endif /* lint */
! 64:
! 65: static void do_syslog __P((int, char *));
! 66: static void do_logfile __P((char *));
! 67: static void send_mail __P((char *));
! 68: static void mail_auth __P((int, char *));
! 69: static char *get_timestr __P((void));
! 70:
! 71: #ifdef BROKEN_SYSLOG
! 72: # define MAXSYSLOGTRIES 16 /* num of retries for broken syslogs */
! 73: # define SYSLOG syslog_wrapper
! 74:
! 75: static void syslog_wrapper __P((int, char *, char *, char *));
! 76:
! 77: /*
! 78: * Some versions of syslog(3) don't guarantee success and return
! 79: * an int (notably HP-UX < 10.0). So, if at first we don't succeed,
! 80: * try, try again...
! 81: */
! 82: static void
! 83: syslog_wrapper(pri, fmt, ap)
! 84: int pri;
! 85: const char *fmt;
! 86: va_list ap;
! 87: {
! 88: int i;
! 89:
! 90: for (i = 0; i < MAXSYSLOGTRIES; i++)
! 91: if (vsyslog(pri, fmt, ap) == 0)
! 92: break;
! 93: }
! 94: #else
! 95: # define SYSLOG syslog
! 96: #endif /* BROKEN_SYSLOG */
! 97:
! 98: /*
! 99: * Log a message to syslog, pre-pending the username and splitting the
! 100: * message into parts if it is longer than MAXSYSLOGLEN.
! 101: */
! 102: static void
! 103: do_syslog(pri, msg)
! 104: int pri;
! 105: char *msg;
! 106: {
! 107: int count;
! 108: char *p;
! 109: char *tmp;
! 110: char save;
! 111:
! 112: /*
! 113: * Log the full line, breaking into multiple syslog(3) calls if necessary
! 114: */
! 115: for (p = msg, count = 0; count < strlen(msg) / MAXSYSLOGLEN + 1; count++) {
! 116: if (strlen(p) > MAXSYSLOGLEN) {
! 117: /*
! 118: * Break up the line into what will fit on one syslog(3) line
! 119: * Try to break on a word boundary if possible.
! 120: */
! 121: for (tmp = p + MAXSYSLOGLEN; tmp > p && *tmp != ' '; tmp--)
! 122: ;
! 123: if (tmp <= p)
! 124: tmp = p + MAXSYSLOGLEN;
! 125:
! 126: /* NULL terminate line, but save the char to restore later */
! 127: save = *tmp;
! 128: *tmp = '\0';
! 129:
! 130: if (count == 0)
! 131: SYSLOG(pri, "%8.8s : %s", user_name, p);
! 132: else
! 133: SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p);
! 134:
! 135: *tmp = save; /* restore saved character */
! 136:
! 137: /* Eliminate leading whitespace */
! 138: for (p = tmp; *p != ' '; p++)
! 139: ;
! 140: } else {
! 141: if (count == 0)
! 142: SYSLOG(pri, "%8.8s : %s", user_name, p);
! 143: else
! 144: SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p);
! 145: }
! 146: }
! 147: }
! 148:
! 149: static void
! 150: do_logfile(msg)
! 151: char *msg;
! 152: {
! 153: char *full_line;
! 154: char *beg, *oldend, *end;
! 155: FILE *fp;
! 156: mode_t oldmask;
! 157: int maxlen = def_ival(I_LOGLEN);
! 158:
! 159: oldmask = umask(077);
! 160: fp = fopen(def_str(I_LOGFILE), "a");
! 161: (void) umask(oldmask);
! 162: if (fp == NULL) {
! 163: easprintf(&full_line, "Can't open log file: %s: %s",
! 164: def_str(I_LOGFILE), strerror(errno));
! 165: send_mail(full_line);
! 166: free(full_line);
! 167: } else if (!lock_file(fileno(fp), SUDO_LOCK)) {
! 168: easprintf(&full_line, "Can't lock log file: %s: %s",
! 169: def_str(I_LOGFILE), strerror(errno));
! 170: send_mail(full_line);
! 171: free(full_line);
! 172: } else {
! 173: if (def_ival(I_LOGLEN) == 0) {
! 174: /* Don't pretty-print long log file lines (hard to grep) */
! 175: if (def_flag(I_LOG_HOST))
! 176: (void) fprintf(fp, "%s : %s : HOST=%s : %s\n", get_timestr(),
! 177: user_name, user_shost, msg);
! 178: else
! 179: (void) fprintf(fp, "%s : %s : %s\n", get_timestr(),
! 180: user_name, msg);
! 181: } else {
! 182: if (def_flag(I_LOG_HOST))
! 183: easprintf(&full_line, "%s : %s : HOST=%s : %s", get_timestr(),
! 184: user_name, user_shost, msg);
! 185: else
! 186: easprintf(&full_line, "%s : %s : %s", get_timestr(),
! 187: user_name, msg);
! 188:
! 189: /*
! 190: * Print out full_line with word wrap
! 191: */
! 192: beg = end = full_line;
! 193: while (beg) {
! 194: oldend = end;
! 195: end = strchr(oldend, ' ');
! 196:
! 197: if (maxlen > 0 && end) {
! 198: *end = '\0';
! 199: if (strlen(beg) > maxlen) {
! 200: /* too far, need to back up & print the line */
! 201:
! 202: if (beg == (char *)full_line)
! 203: maxlen -= 4; /* don't indent first line */
! 204:
! 205: *end = ' ';
! 206: if (oldend != beg) {
! 207: /* rewind & print */
! 208: end = oldend-1;
! 209: while (*end == ' ')
! 210: --end;
! 211: *(++end) = '\0';
! 212: (void) fprintf(fp, "%s\n ", beg);
! 213: *end = ' ';
! 214: } else {
! 215: (void) fprintf(fp, "%s\n ", beg);
! 216: }
! 217:
! 218: /* reset beg to point to the start of the new substr */
! 219: beg = end;
! 220: while (*beg == ' ')
! 221: ++beg;
! 222: } else {
! 223: /* we still have room */
! 224: *end = ' ';
! 225: }
! 226:
! 227: /* remove leading whitespace */
! 228: while (*end == ' ')
! 229: ++end;
! 230: } else {
! 231: /* final line */
! 232: (void) fprintf(fp, "%s\n", beg);
! 233: beg = NULL; /* exit condition */
! 234: }
! 235: }
! 236: free(full_line);
! 237: }
! 238: (void) fflush(fp);
! 239: (void) lock_file(fileno(fp), SUDO_UNLOCK);
! 240: (void) fclose(fp);
! 241: }
! 242: }
! 243:
! 244: /*
! 245: * Two main functions, log_error() to log errors and log_auth() to
! 246: * log allow/deny messages.
! 247: */
! 248: void
! 249: log_auth(status, inform_user)
! 250: int status;
! 251: int inform_user;
! 252: {
! 253: char *message;
! 254: char *logline;
! 255: int pri;
! 256:
! 257: if (status & VALIDATE_OK)
! 258: pri = def_ival(I_GOODPRI);
! 259: else
! 260: pri = def_ival(I_BADPRI);
! 261:
! 262: /* Set error message, if any. */
! 263: if (status & VALIDATE_OK)
! 264: message = "";
! 265: else if (status & FLAG_NO_USER)
! 266: message = "user NOT in sudoers ; ";
! 267: else if (status & FLAG_NO_HOST)
! 268: message = "user NOT authorized on host ; ";
! 269: else if (status & VALIDATE_NOT_OK)
! 270: message = "command not allowed ; ";
! 271: else
! 272: message = "unknown error ; ";
! 273:
! 274: easprintf(&logline, "%sTTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s%s%s",
! 275: message, user_tty, user_cwd, *user_runas, user_cmnd,
! 276: user_args ? " " : "", user_args ? user_args : "");
! 277:
! 278: mail_auth(status, logline); /* send mail based on status */
! 279:
! 280: /* Inform the user if they failed to authenticate. */
! 281: if (inform_user && (status & VALIDATE_NOT_OK)) {
! 282: if (status & FLAG_NO_USER)
! 283: (void) fprintf(stderr, "%s is not in the sudoers file. %s",
! 284: user_name, "This incident will be reported.\n");
! 285: else if (status & FLAG_NO_HOST)
! 286: (void) fprintf(stderr, "%s is not allowed to run sudo on %s. %s",
! 287: user_name, user_shost, "This incident will be reported.\n");
! 288: else if (status & FLAG_NO_CHECK)
! 289: (void) fprintf(stderr, "Sorry, user %s may not run sudo on %s.\n",
! 290: user_name, user_shost);
! 291: else
! 292: (void) fprintf(stderr,
! 293: "Sorry, user %s is not allowed to execute '%s%s%s' as %s on %s.\n",
! 294: user_name, user_cmnd, user_args ? " " : "",
! 295: user_args ? user_args : "", *user_runas, user_host);
! 296: }
! 297:
! 298: /*
! 299: * Log via syslog and/or a file.
! 300: */
! 301: if (def_str(I_LOGFACSTR))
! 302: do_syslog(pri, logline);
! 303: if (def_str(I_LOGFILE))
! 304: do_logfile(logline);
! 305:
! 306: free(logline);
! 307: }
! 308:
! 309: void
! 310: #ifdef __STDC__
! 311: log_error(int flags, const char *fmt, ...)
! 312: #else
! 313: log_error(va_alist)
! 314: va_dcl
! 315: #endif
! 316: {
! 317: int serrno = errno;
! 318: char *message;
! 319: char *logline;
! 320: va_list ap;
! 321: #ifdef __STDC__
! 322: va_start(ap, fmt);
! 323: #else
! 324: int flags;
! 325: const char *fmt;
! 326:
! 327: va_start(ap);
! 328: flags = va_arg(ap, int);
! 329: fmt = va_arg(ap, const char *);
! 330: #endif
! 331:
! 332: /* Become root if we are not already to avoid user control */
! 333: if (geteuid() != 0)
! 334: set_perms(PERM_ROOT, 0);
! 335:
! 336: /* Expand printf-style format + args. */
! 337: evasprintf(&message, fmt, ap);
! 338: va_end(ap);
! 339:
! 340: if (flags & MSG_ONLY)
! 341: logline = message;
! 342: else if (flags & USE_ERRNO) {
! 343: if (user_args) {
! 344: easprintf(&logline,
! 345: "%s: %s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s %s",
! 346: message, strerror(serrno), user_tty, user_cwd, *user_runas,
! 347: user_cmnd, user_args);
! 348: } else {
! 349: easprintf(&logline,
! 350: "%s: %s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s", message,
! 351: strerror(serrno), user_tty, user_cwd, *user_runas, user_cmnd);
! 352: }
! 353: } else {
! 354: if (user_args) {
! 355: easprintf(&logline,
! 356: "%s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s %s", message,
! 357: user_tty, user_cwd, *user_runas, user_cmnd, user_args);
! 358: } else {
! 359: easprintf(&logline,
! 360: "%s ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=%s", message,
! 361: user_tty, user_cwd, *user_runas, user_cmnd);
! 362: }
! 363: }
! 364:
! 365: /*
! 366: * Tell the user.
! 367: */
! 368: (void) fprintf(stderr, "%s: %s", Argv[0], message);
! 369: if (flags & USE_ERRNO)
! 370: (void) fprintf(stderr, ": %s", strerror(serrno));
! 371: (void) fputc('\n', stderr);
! 372:
! 373: /*
! 374: * Send a copy of the error via mail.
! 375: */
! 376: if (!(flags & NO_MAIL))
! 377: send_mail(logline);
! 378:
! 379: /*
! 380: * Log to syslog and/or a file.
! 381: */
! 382: if (def_str(I_LOGFACSTR))
! 383: do_syslog(def_ival(I_BADPRI), logline);
! 384: if (def_str(I_LOGFILE))
! 385: do_logfile(logline);
! 386:
! 387: free(logline);
! 388: if (message != logline);
! 389: free(message);
! 390:
! 391: if (!(flags & NO_EXIT))
! 392: exit(1);
! 393: }
! 394:
! 395: #define MAX_MAILFLAGS 63
! 396:
! 397: /*
! 398: * Send a message to MAILTO user
! 399: */
! 400: static void
! 401: send_mail(line)
! 402: char *line;
! 403: {
! 404: FILE *mail;
! 405: char *p;
! 406: int pfd[2], pid;
! 407:
! 408: /* Just return if mailer is disabled. */
! 409: if (!def_str(I_MAILERPATH) || !def_str(I_MAILTO))
! 410: return;
! 411:
! 412: if ((pid = fork()) > 0) { /* Child. */
! 413:
! 414: /* We do an explicit wait() later on... */
! 415: (void) signal(SIGCHLD, SIG_IGN);
! 416:
! 417: if (pipe(pfd) == -1) {
! 418: (void) fprintf(stderr, "%s: cannot open pipe: %s\n",
! 419: Argv[0], strerror(errno));
! 420: exit(1);
! 421: }
! 422:
! 423: switch (pid = fork()) {
! 424: case -1:
! 425: /* Error. */
! 426: /* XXX - parent will continue, return an exit val to
! 427: let parent know and abort? */
! 428: (void) fprintf(stderr, "%s: cannot fork: %s\n",
! 429: Argv[0], strerror(errno));
! 430: exit(1);
! 431: break;
! 432: case 0:
! 433: {
! 434: char *argv[MAX_MAILFLAGS + 1];
! 435: char *mpath, *mflags;
! 436: int i;
! 437:
! 438: /* Grandchild. */
! 439: (void) close(pfd[1]);
! 440: (void) dup2(pfd[0], STDIN_FILENO);
! 441: (void) close(pfd[0]);
! 442:
! 443: /* Build up an argv based the mailer path and flags */
! 444: mflags = estrdup(def_str(I_MAILERFLAGS));
! 445: mpath = estrdup(def_str(I_MAILERPATH));
! 446: if ((argv[0] = strrchr(mpath, ' ')))
! 447: argv[0]++;
! 448: else
! 449: argv[0] = mpath;
! 450:
! 451: i = 1;
! 452: if ((p = strtok(mflags, " \t"))) {
! 453: do {
! 454: argv[i] = p;
! 455: } while (++i < MAX_MAILFLAGS && (p = strtok(NULL, " \t")));
! 456: }
! 457: argv[i] = NULL;
! 458:
! 459: /* Run mailer as root so user cannot kill it. */
! 460: set_perms(PERM_ROOT, 0);
! 461: execv(mpath, argv);
! 462: _exit(127);
! 463: }
! 464: break;
! 465: }
! 466:
! 467: mail = fdopen(pfd[1], "w");
! 468: (void) close(pfd[0]);
! 469:
! 470: /* Pipes are all setup, send message via sendmail. */
! 471: (void) fprintf(mail, "To: %s\nFrom: %s\nSubject: ",
! 472: def_str(I_MAILTO), user_name);
! 473: for (p = def_str(I_MAILSUB); *p; p++) {
! 474: /* Expand escapes in the subject */
! 475: if (*p == '%' && *(p+1) != '%') {
! 476: switch (*(++p)) {
! 477: case 'h':
! 478: (void) fputs(user_host, mail);
! 479: break;
! 480: case 'u':
! 481: (void) fputs(user_name, mail);
! 482: break;
! 483: default:
! 484: p--;
! 485: break;
! 486: }
! 487: } else
! 488: (void) fputc(*p, mail);
! 489: }
! 490: (void) fprintf(mail, "\n\n%s : %s : %s : %s\n\n", user_host,
! 491: get_timestr(), user_name, line);
! 492: fclose(mail);
! 493: reapchild(0);
! 494: _exit(0);
! 495: } else {
! 496: /* Parent, just return unless there is an error. */
! 497: if (pid == -1) {
! 498: (void) fprintf(stderr, "%s: cannot fork: %s\n",
! 499: Argv[0], strerror(errno));
! 500: exit(1);
! 501: }
! 502: }
! 503: }
! 504:
! 505: /*
! 506: * Send mail based on the value of "status" and compile-time options.
! 507: */
! 508: static void
! 509: mail_auth(status, line)
! 510: int status;
! 511: char *line;
! 512: {
! 513: int mail_mask;
! 514:
! 515: /* If any of these bits are set in status, we send mail. */
! 516: if (def_flag(I_MAIL_ALWAYS))
! 517: mail_mask =
! 518: VALIDATE_ERROR|VALIDATE_OK|FLAG_NO_USER|FLAG_NO_HOST|VALIDATE_NOT_OK;
! 519: else {
! 520: mail_mask = VALIDATE_ERROR;
! 521: if (def_flag(I_MAIL_NOUSER))
! 522: mail_mask |= FLAG_NO_USER;
! 523: if (def_flag(I_MAIL_NOHOST))
! 524: mail_mask |= FLAG_NO_HOST;
! 525: if (def_flag(I_MAIL_NOPERMS))
! 526: mail_mask |= VALIDATE_NOT_OK;
! 527: }
! 528:
! 529: if ((status & mail_mask) != 0)
! 530: send_mail(line);
! 531: }
! 532:
! 533: /*
! 534: * SIGCHLD sig handler--wait for children as they die.
! 535: */
! 536: RETSIGTYPE
! 537: reapchild(sig)
! 538: int sig;
! 539: {
! 540: int status, serrno = errno;
! 541:
! 542: #ifdef sudo_waitpid
! 543: while (sudo_waitpid(-1, &status, WNOHANG) != -1)
! 544: ;
! 545: #else
! 546: (void) wait(&status);
! 547: #endif
! 548: #ifndef POSIX_SIGNALS
! 549: (void) signal(SIGCHLD, reapchild);
! 550: #endif /* POSIX_SIGNALS */
! 551: errno = serrno;
! 552: }
! 553:
! 554: /*
! 555: * Return an ascii string with the current date + time
! 556: * Uses strftime() if available, else falls back to ctime().
! 557: */
! 558: static char *
! 559: get_timestr()
! 560: {
! 561: char *s;
! 562: time_t now = time((time_t) 0);
! 563: #ifdef HAVE_STRFTIME
! 564: static char buf[128];
! 565: struct tm *timeptr;
! 566:
! 567: timeptr = localtime(&now);
! 568: if (def_flag(I_LOG_YEAR))
! 569: s = "%h %e %T %Y";
! 570: else
! 571: s = "%h %e %T";
! 572:
! 573: /* strftime() does not guarantee to NUL-terminate so we must check. */
! 574: buf[sizeof(buf) - 1] = '\0';
! 575: if (strftime(buf, sizeof(buf), s, timeptr) && buf[sizeof(buf) - 1] == '\0')
! 576: return(buf);
! 577:
! 578: #endif /* HAVE_STRFTIME */
! 579:
! 580: s = ctime(&now) + 4; /* skip day of the week */
! 581: if (def_flag(I_LOG_YEAR))
! 582: s[20] = '\0'; /* avoid the newline */
! 583: else
! 584: s[15] = '\0'; /* don't care about year */
! 585:
! 586: return(s);
! 587: }