/* * CU sudo version 1.4 (based on Root Group sudo version 1.1) * * This software comes with no waranty whatsoever, use at your own risk. * * Please send bugs, changes, problems to sudo-bugs@cs.colorado.edu * */ /* * sudo version 1.1 allows users to execute commands as root * Copyright (C) 1991 The Root Group, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * **************************************************************** * * logging.c * * this file supports the general logging facilities * if you want to change any error messages, this is probably * the place to be... * * Jeff Nieusma Thu Mar 21 23:39:04 MST 1991 */ #ifndef lint static char rcsid[] = "$Id$"; #endif /* lint */ #include "config.h" #include #ifdef STDC_HEADERS #include #endif /* STDC_HEADERS */ #ifdef HAVE_UNISTD_H #include #endif /* HAVE_UNISTD_H */ #ifdef HAVE_STRING_H #include #endif /* HAVE_STRING_H */ #ifdef HAVE_STRINGS_H #include #endif /* HAVE_STRINGS_H */ #if defined(HAVE_MALLOC_H) && !defined(STDC_HEADERS) #include #endif /* HAVE_MALLOC_H && !STDC_HEADERS */ #include #include #include #include #include #include #include #include #include #include "sudo.h" #include /* * Prototypes for local functions */ static void send_mail __P((void)); static RETSIGTYPE reapchild __P((int)); static int appropriate __P((int)); #ifdef BROKEN_SYSLOG static void syslog_wrapper __P((int, char *, char *, char *)); #endif /* BROKEN_SYSLOG */ /* * Globals */ static char *logline; extern int errorlineno; /* * length of syslog-like header info used for mail and file logs * is len("Mon MM HH:MM:SS : username : ") */ #define LOG_HEADER_LEN 29 #ifdef BROKEN_SYSLOG #define MAXSYSLOGTRIES 16 /* num of retries for broken syslogs */ #define SYSLOG(a,b,c,d) syslog_wrapper(a,b,c,d) /**************************************************************** * * syslog_wrapper() * * This function logs via syslog w/ a priority and 3 strings args. * It really shouldn't be necesary but some syslog()'s don't * guarantee that the syslog() operation will succeed! */ static void syslog_wrapper(pri, fmt, arg1, arg2) int pri; char *fmt; char *arg1; char *arg2; { int i; for (i = 0; i < MAXSYSLOGTRIES; i++) if (syslog(pri, fmt, arg1, arg2) == 0) break; } #else #define SYSLOG(a,b,c,d) syslog(a,b,c,d) #endif /* BROKEN_SYSLOG */ /********************************************************************** * * log_error() * * This function attempts to deliver mail to ALERTMAIL and either * syslogs the error or writes it to the log file */ void log_error(code) int code; { char *p, *cmnd_args = NULL; int count; time_t now; #if (LOGGING & SLOG_FILE) mode_t oldmask; FILE *fp; #endif /* LOGGING & SLOG_FILE */ #if (LOGGING & SLOG_SYSLOG) int pri = Syslog_priority_NO; /* syslog priority, assume the worst */ char *tmp, save; #endif /* LOGGING & SLOG_SYSLOG */ /* Get the command line args from the environment */ if (NewArgc > 1 && (cmnd_args = getenv("SUDO_COMMAND"))) { if ((cmnd_args = strchr(cmnd_args, ' '))) cmnd_args++; else cmnd_args = NULL; } /* * Allocate enough memory for logline so we won't overflow it */ count = LOG_HEADER_LEN + 136 + 2 * MAXPATHLEN + strlen(tty) + strlen(cwd) + strlen(runas_user); if (cmnd_args) count += strlen(cmnd_args); logline = (char *) malloc(count); if (logline == NULL) { perror("malloc"); (void) fprintf(stderr, "%s: cannot allocate memory!\n", Argv[0]); exit(1); } /* * we will skip this stuff when using syslog(3) but it is * necesary for mail and file logs. */ now = time((time_t) 0); p = ctime(&now) + 4; (void) sprintf(logline, "%15.15s : %8.8s : ", p, user_name); /* * we need a pointer to the end of logline for cheap appends. */ p = logline + LOG_HEADER_LEN; switch (code) { case ALL_SYSTEMS_GO: (void) sprintf(p, "TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); #if (LOGGING & SLOG_SYSLOG) pri = Syslog_priority_OK; #endif /* LOGGING & SLOG_SYSLOG */ break; case VALIDATE_NO_USER: (void) sprintf(p, "user NOT in sudoers ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); break; case VALIDATE_NOT_OK: (void) sprintf(p, "command not allowed ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); break; case VALIDATE_ERROR: (void) sprintf(p, "error in %s, line %d ; TTY=%s ; PWD=%s ; USER=%s. ", _PATH_SUDO_SUDOERS, errorlineno, tty, cwd, runas_user); break; case GLOBAL_NO_PW_ENT: (void) sprintf(p, "There is no passwd entry for uid %ld (TTY=%s). ", (long) user_uid, tty); break; case PASSWORD_NOT_CORRECT: (void) sprintf(p, "password incorrect ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); break; case PASSWORDS_NOT_CORRECT: (void) sprintf(p, "%d incorrect passwords ; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", TRIES_FOR_PASSWORD, tty, cwd, runas_user); break; case GLOBAL_NO_HOSTNAME: strcat(p, "This machine does not have a hostname "); break; case NO_SUDOERS_FILE: switch (errno) { case ENOENT: (void) sprintf(p, "There is no %s file. ", _PATH_SUDO_SUDOERS); break; case EACCES: (void) sprintf(p, "Can't read %s. ", Argv[0]); break; default: (void) sprintf(p, "There is a problem opening %s ", _PATH_SUDO_SUDOERS); break; } break; case GLOBAL_HOST_UNREGISTERED: (void) sprintf(p, "gethostbyname() cannot find host %s ", host); break; case SUDOERS_NOT_FILE: (void) sprintf(p, "%s is not a regular file ", _PATH_SUDO_SUDOERS); break; case SUDOERS_WRONG_OWNER: (void) sprintf(p, "%s is not owned by uid %d and gid %d ", _PATH_SUDO_SUDOERS, SUDOERS_UID, SUDOERS_GID); break; case SUDOERS_WRONG_MODE: (void) sprintf(p, "%s is not mode %o ", _PATH_SUDO_SUDOERS, SUDOERS_MODE); break; case SPOOF_ATTEMPT: (void) sprintf(p, "probable spoofing attempt; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); break; case BAD_STAMPDIR: (void) sprintf(p, "%s owned by non-root or not mode 0700; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", _PATH_SUDO_TIMEDIR, tty, cwd, runas_user); break; case BAD_STAMPFILE: (void) sprintf(p, "preposterous stampfile date; TTY=%s ; PWD=%s ; USER=%s ; COMMAND=", tty, cwd, runas_user); break; default: strcat(p, "found a wierd error : "); break; } /* * If this is a parse error or if the error is from load_globals() * don't put argv in the message. */ if (code != VALIDATE_ERROR && !(code & GLOBAL_PROBLEM)) { /* stuff the command into the logline */ p = logline + strlen(logline); strcpy(p, cmnd); /* add a trailing space */ p += strlen(cmnd); *p++ = ' '; *p = '\0'; /* cat on command args if they exist */ if (cmnd_args) { (void) strcpy(p, cmnd_args); p += strlen(cmnd_args); *p++ = ' '; *p = '\0'; } } #if (LOGGING & SLOG_SYSLOG) #ifdef Syslog_facility openlog(Syslog_ident, Syslog_options, Syslog_facility); #else openlog(Syslog_ident, Syslog_options); #endif /* Syslog_facility */ /* * Log the full line, breaking into multiple syslog(3) calls if necesary */ p = &logline[LOG_HEADER_LEN]; /* skip past the date and user */ for (count = 0; count < strlen(logline) / MAXSYSLOGLEN + 1; count++) { if (strlen(p) > MAXSYSLOGLEN) { /* * Break up the line into what will fit on one syslog(3) line * Try to break on a word boundary if possible. */ for (tmp = p + MAXSYSLOGLEN; tmp > p && *tmp != ' '; tmp--) ; if (tmp <= p) tmp = p + MAXSYSLOGLEN; /* NULL terminate line, but save the char to restore later */ save = *tmp; *tmp = '\0'; if (count == 0) SYSLOG(pri, "%8.8s : %s", user_name, p); else SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p); *tmp = save; /* restore saved character */ /* eliminate leading whitespace */ for (p=tmp; *p != ' '; p++) ; } else { if (count == 0) SYSLOG(pri, "%8.8s : %s", user_name, p); else SYSLOG(pri, "%8.8s : (command continued) %s", user_name, p); } } closelog(); #endif /* LOGGING & SLOG_SYSLOG */ #if (LOGGING & SLOG_FILE) /* become root */ set_perms(PERM_ROOT); oldmask = umask(077); fp = fopen(_PATH_SUDO_LOGFILE, "a"); (void) umask(oldmask); if (fp == NULL) { (void) sprintf(logline, "Can\'t open log file: %s", _PATH_SUDO_LOGFILE); send_mail(); } else { char *beg, *oldend, *end; int maxlen = MAXLOGFILELEN; /* * Print out logline with word wrap */ beg = end = logline; while (beg) { oldend = end; end = strchr(oldend, ' '); if (end) { *end = '\0'; if (strlen(beg) > maxlen) { /* too far, need to back up & print the line */ if (beg == (char *)logline) maxlen -= 4; /* don't indent first line */ *end = ' '; if (oldend != beg) { /* rewind & print */ end = oldend-1; while (*end == ' ') --end; *(++end) = '\0'; (void) fprintf(fp, "%s\n ", beg); *end = ' '; } else { (void) fprintf(fp, "%s\n ", beg); } /* reset beg to point to the start of the new substring */ beg = end; while (*beg == ' ') ++beg; } else { /* we still have room */ *end = ' '; } /* remove leading whitespace */ while (*end == ' ') ++end; } else { /* final line */ (void) fprintf(fp, "%s\n", beg); beg = NULL; /* exit condition */ } } (void) fclose(fp); } /* relinquish root */ set_perms(PERM_USER); #endif /* LOGGING & SLOG_FILE */ /* send mail if appropriate */ if (appropriate(code)) send_mail(); } #ifdef MAILER /********************************************************************** * * send_mail() * * This function attempts to mail to ALERTMAIL about the sudo error * */ static char *mail_argv[] = { "sendmail", "-t", (char *) NULL }; static void send_mail() { char *mailer = MAILER; char *subject = MAILSUBJECT; int fd[2]; #ifdef POSIX_SIGNALS struct sigaction action; (void) memset((VOID *)&action, 0, sizeof(action)); #endif /* POSIX_SIGNALS */ /* catch children as they die */ #ifdef POSIX_SIGNALS action.sa_handler = reapchild; (void) sigaction(SIGCHLD, &action, NULL); #else (void) signal(SIGCHLD, reapchild); #endif /* POSIX_SIGNALS */ if (fork()) return; /* * we don't want any security problems ... */ set_perms(PERM_FULL_USER); #ifdef POSIX_SIGNALS action.sa_handler = SIG_IGN; (void) sigaction(SIGHUP, &action, NULL); (void) sigaction(SIGINT, &action, NULL); (void) sigaction(SIGQUIT, &action, NULL); #else (void) signal(SIGHUP, SIG_IGN); (void) signal(SIGINT, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); #endif /* POSIX_SIGNALS */ if (pipe(fd)) { perror("send_mail: pipe"); exit(1); } (void) dup2(fd[0], 0); (void) dup2(fd[1], 1); (void) close(fd[0]); (void) close(fd[1]); if (!fork()) { /* child */ (void) close(1); EXEC(mailer, mail_argv); /* this should not happen */ perror(mailer); exit(1); } else { /* parent */ (void) close(0); /* feed the data to sendmail */ /* XXX - do we need to fdopen this fd #1 to a new stream??? */ (void) fprintf(stdout, "To: %s\nSubject: %s\n\n%s : %s\n\n", ALERTMAIL, subject, host, logline); fclose(stdout); exit(0); } } #else static void send_mail() { /* no mailer defined */ return; } #endif /* MAILER */ /**************************************************************** * * reapchild() * * This function gets rid of all the ugly zombies */ static RETSIGTYPE reapchild(sig) int sig; { (void) wait(NULL); #ifndef POSIX_SIGNALS (void) signal(SIGCHLD, reapchild); #endif /* POSIX_SIGNALS */ } /********************************************************************** * * inform_user () * * This function lets the user know what is happening * when an error occurs */ void inform_user(code) int code; { char *cmnd_args = NULL; switch (code) { case VALIDATE_NO_USER: (void) fprintf(stderr, "%s is not in the sudoers file. This incident will be reported.\n\n", user_name); break; case VALIDATE_NOT_OK: if (NewArgc > 1 && (cmnd_args = getenv("SUDO_COMMAND"))) { if ((cmnd_args = strchr(cmnd_args, ' '))) cmnd_args++; else cmnd_args = NULL; } (void) fprintf(stderr, "Sorry, user %s is not allowed to execute \"%s", user_name, cmnd); /* print command args if they exist */ if (cmnd_args) { fputc(' ', stderr); fputs(cmnd_args, stderr); } (void) fprintf(stderr, "\" as %s on %s.\n\n", runas_user, host); break; case VALIDATE_ERROR: (void) fprintf(stderr, "Sorry, there is a fatal error in the sudoers file.\n\n"); break; case GLOBAL_NO_PW_ENT: (void) fprintf(stderr, "Intruder Alert! You don't exist in the passwd file\n\n"); break; case GLOBAL_NO_SPW_ENT: (void) fprintf(stderr, "Intruder Alert! You don't exist in the shadow passwd file\n\n"); break; case GLOBAL_NO_HOSTNAME: (void) fprintf(stderr, "This machine does not have a hostname\n\n"); break; case GLOBAL_HOST_UNREGISTERED: (void) fprintf(stderr, "This machine is not available via gethostbyname()\n\n"); break; case PASSWORD_NOT_CORRECT: (void) fprintf(stderr, "Password not entered correctly\n\n"); break; case PASSWORDS_NOT_CORRECT: (void) fprintf(stderr, "Password not entered correctly after %d tries\n\n", TRIES_FOR_PASSWORD); break; case NO_SUDOERS_FILE: switch (errno) { case ENOENT: (void) fprintf(stderr, "There is no %s file.\n", _PATH_SUDO_SUDOERS); break; default: (void) fprintf(stderr, "Can't read %s: ", _PATH_SUDO_SUDOERS); perror(""); break; } break; case SUDOERS_NOT_FILE: (void) fprintf(stderr, "%s is not a regular file!\n", _PATH_SUDO_SUDOERS); break; case SUDOERS_WRONG_OWNER: (void) fprintf(stderr, "%s is not owned by uid %d and gid %d!\n", _PATH_SUDO_SUDOERS, SUDOERS_UID, SUDOERS_GID); break; case SUDOERS_WRONG_MODE: (void) fprintf(stderr, "%s must be mode %o!\n", _PATH_SUDO_SUDOERS, SUDOERS_MODE); break; case SPOOF_ATTEMPT: (void) fprintf(stderr, "%s is not the same command that was validated, disallowing.\n", cmnd); break; case BAD_STAMPDIR: (void) fprintf(stderr, "Timestamp directory has wrong permissions, ignoring.\n"); break; case BAD_STAMPFILE: (void) fprintf(stderr, "Your timestamp file has a preposterous date, ignoring.\n"); break; default: (void) fprintf(stderr, "Something wierd happened.\n\n"); break; } } /**************************************************************** * * appropriate() * * This function determines whether to send mail or not... */ static int appropriate(code) int code; { switch (code) { /* * these will NOT send mail */ case VALIDATE_OK: case VALIDATE_OK_NOPASS: case PASSWORD_NOT_CORRECT: case PASSWORDS_NOT_CORRECT: /* case ALL_SYSTEMS_GO: this is the same as OK */ return (0); break; case VALIDATE_NO_USER: #ifdef SEND_MAIL_WHEN_NO_USER return (1); #else return (0); #endif break; case VALIDATE_NOT_OK: #ifdef SEND_MAIL_WHEN_NOT_OK return (1); #else return (0); #endif break; /* * these WILL send mail */ case VALIDATE_ERROR: case NO_SUDOERS_FILE: case SPOOF_ATTEMPT: case BAD_STAMPDIR: case BAD_STAMPFILE: default: return (1); break; } }