/* ==================================================================== * Copyright (c) 1995-1999 The Apache Group. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * 4. The names "Apache Server" and "Apache Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * apache@apache.org. * * 5. Products derived from this software may not be called "Apache" * nor may "Apache" appear in their names without prior written * permission of the Apache Group. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by the Apache Group * for use in the Apache HTTP server project (http://www.apache.org/)." * * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Apache Group and was originally based * on public domain software written at the National Center for * Supercomputing Applications, University of Illinois, Urbana-Champaign. * For more information on the Apache Group and the Apache HTTP server * project, please see . * */ /* * http_log.c: Dealing with the logs and errors * * Rob McCool * */ #define CORE_PRIVATE #include "httpd.h" #include "http_conf_globals.h" #include "http_config.h" #include "http_core.h" #include "http_log.h" #include "http_main.h" #include typedef struct { char *t_name; int t_val; } TRANS; #ifdef HAVE_SYSLOG static const TRANS facilities[] = { {"auth", LOG_AUTH}, #ifdef LOG_AUTHPRIV {"authpriv",LOG_AUTHPRIV}, #endif #ifdef LOG_CRON {"cron", LOG_CRON}, #endif #ifdef LOG_DAEMON {"daemon", LOG_DAEMON}, #endif #ifdef LOG_FTP {"ftp", LOG_FTP}, #endif #ifdef LOG_KERN {"kern", LOG_KERN}, #endif #ifdef LOG_LPR {"lpr", LOG_LPR}, #endif #ifdef LOG_MAIL {"mail", LOG_MAIL}, #endif #ifdef LOG_NEWS {"news", LOG_NEWS}, #endif #ifdef LOG_SYSLOG {"syslog", LOG_SYSLOG}, #endif #ifdef LOG_USER {"user", LOG_USER}, #endif #ifdef LOG_UUCP {"uucp", LOG_UUCP}, #endif #ifdef LOG_LOCAL0 {"local0", LOG_LOCAL0}, #endif #ifdef LOG_LOCAL1 {"local1", LOG_LOCAL1}, #endif #ifdef LOG_LOCAL2 {"local2", LOG_LOCAL2}, #endif #ifdef LOG_LOCAL3 {"local3", LOG_LOCAL3}, #endif #ifdef LOG_LOCAL4 {"local4", LOG_LOCAL4}, #endif #ifdef LOG_LOCAL5 {"local5", LOG_LOCAL5}, #endif #ifdef LOG_LOCAL6 {"local6", LOG_LOCAL6}, #endif #ifdef LOG_LOCAL7 {"local7", LOG_LOCAL7}, #endif {NULL, -1}, }; #endif static const TRANS priorities[] = { {"emerg", APLOG_EMERG}, {"alert", APLOG_ALERT}, {"crit", APLOG_CRIT}, {"error", APLOG_ERR}, {"warn", APLOG_WARNING}, {"notice", APLOG_NOTICE}, {"info", APLOG_INFO}, {"debug", APLOG_DEBUG}, {NULL, -1}, }; static int error_log_child(void *cmd, child_info *pinfo) { /* Child process code for 'ErrorLog "|..."'; * may want a common framework for this, since I expect it will * be common for other foo-loggers to want this sort of thing... */ int child_pid = 0; ap_cleanup_for_exec(); #ifdef SIGHUP /* No concept of a child process on Win32 */ signal(SIGHUP, SIG_IGN); #endif /* ndef SIGHUP */ #if defined(WIN32) child_pid = spawnl(_P_NOWAIT, SHELL_PATH, SHELL_PATH, "/c", (char *)cmd, NULL); return(child_pid); #elif defined(OS2) /* For OS/2 we need to use a '/' and spawn the child rather than exec as * we haven't forked */ child_pid = spawnl(P_NOWAIT, SHELL_PATH, SHELL_PATH, "/c", (char *)cmd, NULL); return(child_pid); #else execl(SHELL_PATH, SHELL_PATH, "-c", (char *)cmd, NULL); #endif exit(1); /* NOT REACHED */ return(child_pid); } static void open_error_log(server_rec *s, pool *p) { char *fname; if (*s->error_fname == '|') { FILE *dummy; #ifdef TPF TPF_FORK_CHILD cld; cld.filename = s->error_fname+1; cld.subprocess_env = NULL; cld.prog_type = FORK_NAME; if (!ap_spawn_child(p, NULL, &cld, kill_after_timeout, &dummy, NULL, NULL)) { #else if (!ap_spawn_child(p, error_log_child, (void *)(s->error_fname+1), kill_after_timeout, &dummy, NULL, NULL)) { #endif /* TPF */ perror("ap_spawn_child"); fprintf(stderr, "Couldn't fork child for ErrorLog process\n"); exit(1); } s->error_log = dummy; } #ifdef HAVE_SYSLOG else if (!strncasecmp(s->error_fname, "syslog", 6)) { if ((fname = strchr(s->error_fname, ':'))) { const TRANS *fac; fname++; for (fac = facilities; fac->t_name; fac++) { if (!strcasecmp(fname, fac->t_name)) { openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID, fac->t_val); s->error_log = NULL; return; } } } else openlog(ap_server_argv0, LOG_NDELAY|LOG_CONS|LOG_PID, LOG_LOCAL7); s->error_log = NULL; } #endif else { fname = ap_server_root_relative(p, s->error_fname); if (!(s->error_log = ap_pfopen(p, fname, "a"))) { perror("fopen"); fprintf(stderr, "%s: could not open error log file %s.\n", ap_server_argv0, fname); exit(1); } } } void ap_open_logs(server_rec *s_main, pool *p) { server_rec *virt, *q; int replace_stderr; open_error_log(s_main, p); replace_stderr = 1; if (s_main->error_log) { /* replace stderr with this new log */ fflush(stderr); if (dup2(fileno(s_main->error_log), STDERR_FILENO) == -1) { ap_log_error(APLOG_MARK, APLOG_CRIT, s_main, "unable to replace stderr with error_log"); } else { replace_stderr = 0; } } /* note that stderr may still need to be replaced with something * because it points to the old error log, or back to the tty * of the submitter. */ if (replace_stderr && freopen("/dev/null", "w", stderr) == NULL) { ap_log_error(APLOG_MARK, APLOG_CRIT, s_main, "unable to replace stderr with /dev/null"); } for (virt = s_main->next; virt; virt = virt->next) { if (virt->error_fname) { for (q=s_main; q != virt; q = q->next) if (q->error_fname != NULL && strcmp(q->error_fname, virt->error_fname) == 0) break; if (q == virt) open_error_log(virt, p); else virt->error_log = q->error_log; } else virt->error_log = s_main->error_log; } } API_EXPORT(void) ap_error_log2stderr(server_rec *s) { if ( s->error_log != NULL && fileno(s->error_log) != STDERR_FILENO) dup2(fileno(s->error_log), STDERR_FILENO); } static void log_error_core(const char *file, int line, int level, const server_rec *s, const request_rec *r, const char *fmt, va_list args) { char errstr[MAX_STRING_LEN]; size_t len; int save_errno = errno; FILE *logf; if (s == NULL) { /* * If we are doing stderr logging (startup), don't log messages that are * above the default server log level unless it is a startup/shutdown * notice */ if (((level & APLOG_LEVELMASK) != APLOG_NOTICE) && ((level & APLOG_LEVELMASK) > DEFAULT_LOGLEVEL)) return; logf = stderr; } else if (s->error_log) { /* * If we are doing normal logging, don't log messages that are * above the server log level unless it is a startup/shutdown notice */ if (((level & APLOG_LEVELMASK) != APLOG_NOTICE) && ((level & APLOG_LEVELMASK) > s->loglevel)) return; logf = s->error_log; } #ifdef TPF else if (tpf_child) { /* * If we are doing normal logging, don't log messages that are * above the server log level unless it is a startup/shutdown notice */ if (((level & APLOG_LEVELMASK) != APLOG_NOTICE) && ((level & APLOG_LEVELMASK) > s->loglevel)) return; logf = stderr; } #endif /* TPF */ else { /* * If we are doing syslog logging, don't log messages that are * above the server log level (including a startup/shutdown notice) */ if ((level & APLOG_LEVELMASK) > s->loglevel) return; logf = NULL; } if (logf) { len = ap_snprintf(errstr, sizeof(errstr), "[%s] ", ap_get_time()); } else { len = 0; } len += ap_snprintf(errstr + len, sizeof(errstr) - len, "[%s] ", priorities[level & APLOG_LEVELMASK].t_name); #ifndef TPF if (file && (level & APLOG_LEVELMASK) == APLOG_DEBUG) { #ifdef _OSD_POSIX char tmp[256]; char *e = strrchr(file, '/'); /* In OSD/POSIX, the compiler returns for __FILE__ * a string like: __FILE__="*POSIX(/usr/include/stdio.h)" * (it even returns an absolute path for sources in * the current directory). Here we try to strip this * down to the basename. */ if (e != NULL && e[1] != '\0') { ap_snprintf(tmp, sizeof(tmp), "%s", &e[1]); e = &tmp[strlen(tmp)-1]; if (*e == ')') *e = '\0'; file = tmp; } #endif /*_OSD_POSIX*/ len += ap_snprintf(errstr + len, sizeof(errstr) - len, "%s(%d): ", file, line); } #endif /* TPF */ if (r) { /* XXX: TODO: add a method of selecting whether logged client * addresses are in dotted quad or resolved form... dotted * quad is the most secure, which is why I'm implementing it * first. -djg */ len += ap_snprintf(errstr + len, sizeof(errstr) - len, "[client %s] ", r->connection->remote_ip); } if (!(level & APLOG_NOERRNO) && (save_errno != 0) #ifdef WIN32 && !(level & APLOG_WIN32ERROR) #endif ) { len += ap_snprintf(errstr + len, sizeof(errstr) - len, "(%d)%s: ", save_errno, strerror(save_errno)); } #ifdef WIN32 if (level & APLOG_WIN32ERROR) { int nChars; int nErrorCode; nErrorCode = GetLastError(); len += ap_snprintf(errstr + len, sizeof(errstr) - len, "(%d)", nErrorCode); nChars = FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, nErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) errstr + len, sizeof(errstr) - len, NULL ); len += nChars; if (nChars == 0) { /* Um, error occurred, but we can't recurse to log it again * (and it would probably only fail anyway), so lets just * log the numeric value. */ nErrorCode = GetLastError(); len += ap_snprintf(errstr + len, sizeof(errstr) - len, "(FormatMessage failed with code %d): ", nErrorCode); } else { /* FormatMessage put the message in the buffer, but it may * have appended a newline (\r\n). So remove it and use * ": " instead like the Unix errors. The error may also * end with a . before the return - if so, trash it. */ if (len > 1 && errstr[len-2] == '\r' && errstr[len-1] == '\n') { if (len > 2 && errstr[len-3] == '.') len--; errstr[len-2] = ':'; errstr[len-1] = ' '; } } } #endif len += ap_vsnprintf(errstr + len, sizeof(errstr) - len, fmt, args); /* NULL if we are logging to syslog */ if (logf) { fputs(errstr, logf); fputc('\n', logf); fflush(logf); } #ifdef HAVE_SYSLOG else { syslog(level & APLOG_LEVELMASK, "%s", errstr); } #endif } API_EXPORT(void) ap_log_error(const char *file, int line, int level, const server_rec *s, const char *fmt, ...) { va_list args; va_start(args, fmt); log_error_core(file, line, level, s, NULL, fmt, args); va_end(args); } API_EXPORT(void) ap_log_rerror(const char *file, int line, int level, const request_rec *r, const char *fmt, ...) { va_list args; va_start(args, fmt); log_error_core(file, line, level, r->server, r, fmt, args); /* * IF the error level is 'warning' or more severe, * AND there isn't already error text associated with this request, * THEN make the message text available to ErrorDocument and * other error processors. This can be disabled by stuffing * something, even an empty string, into the "error-notes" cell * before calling this routine. */ va_end(args); va_start(args,fmt); if (((level & APLOG_LEVELMASK) <= APLOG_WARNING) && (ap_table_get(r->notes, "error-notes") == NULL)) { ap_table_setn(r->notes, "error-notes", ap_pvsprintf(r->pool, fmt, args)); } va_end(args); } void ap_log_pid(pool *p, char *fname) { FILE *pid_file; struct stat finfo; static pid_t saved_pid = -1; pid_t mypid; if (!fname) return; fname = ap_server_root_relative(p, fname); mypid = getpid(); if (mypid != saved_pid && stat(fname, &finfo) == 0) { /* USR1 and HUP call this on each restart. * Only warn on first time through for this pid. * * XXX: Could just write first time through too, although * that may screw up scripts written to do something * based on the last modification time of the pid file. */ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, ap_psprintf(p, "pid file %s overwritten -- Unclean shutdown of previous Apache run?", fname) ); } if(!(pid_file = fopen(fname, "w"))) { perror("fopen"); fprintf(stderr, "%s: could not log pid to file %s\n", ap_server_argv0, fname); exit(1); } fprintf(pid_file, "%ld\n", (long)mypid); fclose(pid_file); saved_pid = mypid; } API_EXPORT(void) ap_log_error_old(const char *err, server_rec *s) { ap_log_error(APLOG_MARK, APLOG_ERR, s, "%s", err); } API_EXPORT(void) ap_log_unixerr(const char *routine, const char *file, const char *msg, server_rec *s) { ap_log_error(file, 0, APLOG_ERR, s, "%s", msg); } API_EXPORT(void) ap_log_printf(const server_rec *s, const char *fmt, ...) { va_list args; va_start(args, fmt); log_error_core(APLOG_MARK, APLOG_ERR, s, NULL, fmt, args); va_end(args); } API_EXPORT(void) ap_log_reason(const char *reason, const char *file, request_rec *r) { ap_log_error(APLOG_MARK, APLOG_ERR, r->server, "access to %s failed for %s, reason: %s", file, ap_get_remote_host(r->connection, r->per_dir_config, REMOTE_NAME), reason); } API_EXPORT(void) ap_log_assert(const char *szExp, const char *szFile, int nLine) { fprintf(stderr, "[%s] file %s, line %d, assertion \"%s\" failed\n", ap_get_time(), szFile, nLine, szExp); #ifndef WIN32 /* unix assert does an abort leading to a core dump */ abort(); #else exit(1); #endif } /* piped log support */ #ifndef NO_RELIABLE_PIPED_LOGS /* forward declaration */ static void piped_log_maintenance(int reason, void *data, ap_wait_t status); static int piped_log_spawn(piped_log *pl) { int pid; ap_block_alarms(); pid = fork(); if (pid == 0) { /* XXX: this needs porting to OS2 and WIN32 */ /* XXX: need to check what open fds the logger is actually passed, * XXX: and CGIs for that matter ... cleanup_for_exec *should* * XXX: close all the relevant stuff, but hey, it could be broken. */ RAISE_SIGSTOP(PIPED_LOG_SPAWN); /* we're now in the child */ close(STDIN_FILENO); dup2(pl->fds[0], STDIN_FILENO); ap_cleanup_for_exec(); signal(SIGCHLD, SIG_DFL); /* for HPUX */ signal(SIGHUP, SIG_IGN); execl(SHELL_PATH, SHELL_PATH, "-c", pl->program, NULL); fprintf(stderr, "piped_log_spawn: unable to exec %s -c '%s': %s\n", SHELL_PATH, pl->program, strerror (errno)); exit(1); } if (pid == -1) { fprintf(stderr, "piped_log_spawn: unable to fork(): %s\n", strerror (errno)); ap_unblock_alarms(); return -1; } ap_unblock_alarms(); pl->pid = pid; ap_register_other_child(pid, piped_log_maintenance, pl, pl->fds[1]); return 0; } static void piped_log_maintenance(int reason, void *data, ap_wait_t status) { piped_log *pl = data; switch (reason) { case OC_REASON_DEATH: case OC_REASON_LOST: pl->pid = -1; ap_unregister_other_child(pl); if (pl->program == NULL) { /* during a restart */ break; } if (piped_log_spawn(pl) == -1) { /* what can we do? This could be the error log we're having * problems opening up... */ fprintf(stderr, "piped_log_maintenance: unable to respawn '%s': %s\n", pl->program, strerror(errno)); } break; case OC_REASON_UNWRITABLE: if (pl->pid != -1) { kill(pl->pid, SIGTERM); } break; case OC_REASON_RESTART: pl->program = NULL; if (pl->pid != -1) { kill(pl->pid, SIGTERM); } break; case OC_REASON_UNREGISTER: break; } } static void piped_log_cleanup(void *data) { piped_log *pl = data; if (pl->pid != -1) { kill(pl->pid, SIGTERM); } ap_unregister_other_child(pl); close(pl->fds[0]); close(pl->fds[1]); } static void piped_log_cleanup_for_exec(void *data) { piped_log *pl = data; close(pl->fds[0]); close(pl->fds[1]); } API_EXPORT(piped_log *) ap_open_piped_log(pool *p, const char *program) { piped_log *pl; pl = ap_palloc(p, sizeof (*pl)); pl->p = p; pl->program = ap_pstrdup(p, program); pl->pid = -1; ap_block_alarms (); if (pipe(pl->fds) == -1) { int save_errno = errno; ap_unblock_alarms(); errno = save_errno; return NULL; } ap_register_cleanup(p, pl, piped_log_cleanup, piped_log_cleanup_for_exec); if (piped_log_spawn(pl) == -1) { int save_errno = errno; ap_kill_cleanup(p, pl, piped_log_cleanup); close(pl->fds[0]); close(pl->fds[1]); ap_unblock_alarms(); errno = save_errno; return NULL; } ap_unblock_alarms(); return pl; } API_EXPORT(void) ap_close_piped_log(piped_log *pl) { ap_block_alarms(); piped_log_cleanup(pl); ap_kill_cleanup(pl->p, pl, piped_log_cleanup); ap_unblock_alarms(); } #else static int piped_log_child(void *cmd, child_info *pinfo) { /* Child process code for 'TransferLog "|..."'; * may want a common framework for this, since I expect it will * be common for other foo-loggers to want this sort of thing... */ int child_pid = 1; ap_cleanup_for_exec(); #ifdef SIGHUP signal(SIGHUP, SIG_IGN); #endif #if defined(WIN32) child_pid = spawnl(_P_NOWAIT, SHELL_PATH, SHELL_PATH, "/c", (char *)cmd, NULL); return(child_pid); #elif defined(OS2) /* For OS/2 we need to use a '/' and spawn the child rather than exec as * we haven't forked */ child_pid = spawnl(P_NOWAIT, SHELL_PATH, SHELL_PATH, "/c", (char *)cmd, NULL); return(child_pid); #else execl (SHELL_PATH, SHELL_PATH, "-c", (char *)cmd, NULL); #endif perror("exec"); fprintf(stderr, "Exec of shell for logging failed!!!\n"); return(child_pid); } API_EXPORT(piped_log *) ap_open_piped_log(pool *p, const char *program) { piped_log *pl; FILE *dummy; #ifdef TPF TPF_FORK_CHILD cld; cld.filename = (char *)program; cld.subprocess_env = NULL; cld.prog_type = FORK_NAME; if (!ap_spawn_child (p, NULL, &cld, kill_after_timeout, &dummy, NULL, NULL)){ #else if (!ap_spawn_child(p, piped_log_child, (void *)program, kill_after_timeout, &dummy, NULL, NULL)) { #endif /* TPF */ perror("ap_spawn_child"); fprintf(stderr, "Couldn't fork child for piped log process\n"); exit (1); } pl = ap_palloc(p, sizeof (*pl)); pl->p = p; pl->write_f = dummy; return pl; } API_EXPORT(void) ap_close_piped_log(piped_log *pl) { ap_pfclose(pl->p, pl->write_f); } #endif