/*
#ident "@(#)smail/src:RELEASE-3_2_0_121:log.c,v 1.77 2005/07/09 17:26:32 woods Exp"
*/
/*
* Copyright (C) 1987, 1988 Ronald S. Karr and Landon Curt Noll
* Copyright (C) 1992 Ronald S. Karr
*
* See the file COPYING, distributed with smail, for restriction
* and warranty information.
*/
/*
* log.c:
* system and per-message logging functions
*
* These functions send information to a per-system log file and to
* a per-message log file, manage the creation use and removal of
* per-message logs and handle panic and fatal messages.
*
* external functions: open_system_logs, close_system_logs,
* open_msg_log, close_msg_log, unlink_msg_log,
* panic, write_log, send_log, scan_msg_log
* process_msg_log, decode_x_line
*/
#include "defs.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
# include <stddef.h>
#else
# ifdef HAVE_STDLIB_H
# include <stdlib.h>
# endif
#endif
#ifdef HAVE_STRING_H
# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H)
# include <memory.h>
# endif
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#ifdef __STDC__
# include <stdarg.h>
#else
# include <varargs.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL)
# include <fcntl.h>
#else
# if defined(UNIX_BSD)
# include <sys/file.h>
# endif
#endif
#ifdef __GLIBC__
# include <sys/file.h>
#endif
#include "smail.h"
#include "list.h"
#include "alloc.h"
#include "main.h"
#include "addr.h"
#include "hash.h"
#include "smailstring.h"
#include "dys.h"
#include "log.h"
#include "spool.h"
#include "exitcodes.h"
#include "extern.h"
#include "debug.h"
#include "smailport.h"
#ifdef STANDALONE
# define xmalloc malloc
# define xrealloc realloc
# define xfree free
#endif /* STANDALONE */
/* exported variables */
struct str logstr = { NULL, 0, 0 }; /* STR for write_log_va() */
FILE *msg_logfile = NULL; /* open stream to per-message log */
FILE *panicfile = NULL; /* open stream to panic log file */
FILE *logfile = NULL; /* open stream to system log file */
/* variables local to this file */
static char *msg_log_fn; /* name of per-message log file */
/* functions local to this file */
static void build_msg_log_fn __P((void));
static FILE *open_a_log __P((char *, char *, char *, unsigned int));
static void try_mkdir __P((char *));
static void write_log_va __P((int, char *, va_list));
/*
* open_system_logs - open the panic and the general information log files.
*
* Access to the system log should not require that much be functional
* or that resources be allocated to send to the system log. For that
* reason, we allocate all resources in advance, while resources are
* available and the system is assumed to be somewhat sane.
*
* If reasonable, the system logs should be opened after a message has
* been read to a spool file so that if a panic occurs because a log
* could not be opened we can recover mail later when the problem is
* solved.
*/
void
open_system_logs()
{
static char panicbuf[BUFSIZ]; /* stdio buffer to avoid unchecked mallocs */
static char logbuf[BUFSIZ]; /* stdio buffer to avoid unchecked mallocs */
if (!panicfile) {
panicfile = open_a_log(panic_fn, "panic log", panicbuf, log_mode);
if (!panicfile) {
/*
* use separate fprintf() calls even though they are half the same
* so as to avoid having to preserve errno
*/
if (!only_testing) {
fprintf(stderr, "%s: [%ld] cannot open panic log %s: %s\n", program, (long int) getpid(), panic_fn, strerror(errno));
} else {
fprintf(stderr, "%s: cannot open panic log %s: %s\n", program, panic_fn, strerror(errno));
}
panicfile = stderr;
}
}
if (!logfile) {
logfile = open_a_log(log_fn, "system log", logbuf, log_mode);
if (!logfile) {
if (!only_testing) {
fprintf(stderr, "%s: [%ld] cannot open system log %s: %s\n", program, (long int) getpid(), log_fn, strerror(errno));
} else {
fprintf(stderr, "%s: cannot open system log %s: %s\n", program, log_fn, strerror(errno));
}
logfile = stderr;
}
}
return;
}
/*
* open_a_log() - open a log file for append, creating it if necessary, and if
* auto_mkdir is set then perhaps also creating its directory.
*
* If 'buf' is set then call setbuf() with it.
*
* Returns a pointer to a STDIO FILE structure.
*/
/*
* XXX this should probably be rewritten to use fopen(fqfn, "a")
* [with a fix to use umask((~mode) & 0777)) too of course]
*/
static FILE *
open_a_log(fqfn, fn, buf, mode)
char *fqfn; /* fully qualified filename */
char *fn; /* internal filename (for debug/error msgs) */
char *buf; /* stdio buffer for fp */
unsigned int mode; /* access modes to create with */
{
FILE *fp = NULL;
int fd;
unsigned int oumask = umask(0); /* we supply an explicit mode */
#ifdef O_APPEND
fd = open(fqfn, O_CREAT | O_APPEND | O_WRONLY, mode);
#else
/* limited to V7 open semantics XXX this old junk should go away.... */
fd = open(fqfn, 1);
if (fd < 0) {
DEBUG3(DBG_LOG_HI, "open_a_log(%s): %s: open() failed, will try creat(): %s\n", fqfn, fn, strerror(errno));
fd = creat(fqfn, mode);
} else {
(void) lseek(fd, (off_t) 0L, SEEK_END); /* XXX could this ever fail? */
}
#endif
if (fd < 0) {
#ifdef O_APPEND
DEBUG3(DBG_LOG_MID, "open_a_log(%s): %s: initial open() failed: %s\n", fqfn, fn, strerror(errno));
#else
DEBUG3(DBG_LOG_MID, "open_a_log(%s): %s: initial creat() failed: %s\n", fqfn, fn, strerror(errno));
#endif
/* perhaps the directory just needs to be created */
if (auto_mkdir && errno == ENOENT) {
try_mkdir(fqfn);
#ifdef O_APPEND
fd = open(fqfn, O_CREAT | O_APPEND | O_WRONLY, mode);
#else
/* limited to V7 open semantics XXX this old junk should go away.... */
/* this time we know it's not there so just create it */
fd = creat(fqfn, mode);
#endif
if (fd < 0) {
#ifdef O_APPEND
DEBUG3(DBG_LOG_LO, "open_a_log(%s): %s: second try open() failed: %s\n", fqfn, fn, strerror(errno));
#else
DEBUG3(DBG_LOG_LO, "open_a_log(%s): %s: second try creat() failed: %s\n", fqfn, fn, strerror(errno));
#endif
}
}
}
if (fd >= 0) {
if (!(fp = fdopen(fd, "a"))) {
DEBUG3(DBG_LOG_LO, "open_a_log(%s): %s: fdopen() failed: %s\n", fqfn, fn, strerror(errno));
}
if (fp && buf) {
setbuf(fp, buf);
}
}
(void) umask(oumask);
return fp;
}
/*
* try_mkdir - try to build the directory for the given filename
*/
static void
try_mkdir(fn)
char *fn;
{
char *slash = strrchr(fn, '/');
char *dr;
struct stat st;
if (slash == NULL) {
return; /* ignore bad filename */
}
/* figure directory name */
while (slash > fn && *(slash - 1) == '/') {
--slash;
}
if (slash == fn) {
return; /* root directory */
}
dr = xmalloc((size_t) (slash - fn + 1));
(void) memcpy(dr, fn, (size_t) (slash - fn));
dr[slash - fn] = '\0';
DEBUG1(DBG_LOG_LO, "make directory %s: ", dr);
errno = 0;
(void) mkdir(dr, auto_mkdir_mode);
DEBUG1(DBG_LOG_LO, "%s\n", (errno != 0) ? strerror(errno) : "OK");
if (stat(dr, &st) == -1 && errno == ENOENT) {
char *slash2 = strrchr(dr, '/');
if (slash2) {
while (slash2 > dr && *(slash2 - 1) == '/') {
--slash2;
}
if (slash2 != dr) {
*slash2 = '\0';
DEBUG1(DBG_LOG_LO, " make parent directory %s: ", dr);
errno = 0;
(void) mkdir(dr, auto_mkdir_mode);
DEBUG1(DBG_LOG_LO, "%s\n", (errno != 0) ? strerror(errno) : "OK");
*slash2 = '/';
DEBUG1(DBG_LOG_LO, " re-make directory %s: ", dr);
errno = 0;
(void) mkdir(dr, auto_mkdir_mode);
DEBUG1(DBG_LOG_LO, "%s\n", (errno != 0) ? strerror(errno) : "OK");
}
}
}
xfree(dr);
}
/*
* close_system_logs - close the panic and general info log files.
*/
void
close_system_logs()
{
if (logfile) {
(void) fclose(logfile);
logfile = NULL;
}
if (panicfile) {
(void) fclose(panicfile);
panicfile = NULL;
}
}
/*
* open_msg_log - open message log file, one per message
*
* a per-message log should be opened once for each message and closed
* when done processing a message. It is intended to be information
* that a sender might be interested in, and will be sent back to
* the sender if the return_to_sender flag is set when processing of
* the message is completed.
*/
void
open_msg_log()
{
static char msgbuf[BUFSIZ]; /* stdio buffer to avoid unchecked mallocs */
/*
* if msg_fn not yet set up create a suitably unique value
*/
if (msg_logfile) {
(void) fclose(msg_logfile);
msg_logfile = NULL;
}
build_msg_log_fn();
msg_logfile = open_a_log(msg_log_fn, "message log", msgbuf, message_log_mode);
if (!msg_logfile) {
/*
* otherwise, panic. We are assuming that the mail queue entry can be
* scanned at a later date (note: errno may not be useful from open_a_log)
*/
panic(EX_OSFILE, "cannot open %s/%s: %s", spool_dir, msg_log_fn, strerror(errno));
/*NOTREACHED*/
}
return;
}
/*
* build_msg_log_fn - build the name for the per-message log file
*/
static void
build_msg_log_fn()
{
static char buf[sizeof("msglog/") + SPOOL_FN_LEN + 1];
(void) sprintf(buf, "msglog/%s", spool_fn);
msg_log_fn = buf;
}
/*
* close_msg_log - close the per-message log file
*
* This should be called when further processing of a message is
* being postponed to some point in the future.
*/
void
close_msg_log()
{
if (msg_logfile) {
(void) fclose(msg_logfile);
msg_logfile = NULL;
}
}
/*
* unlink_msg_log - close and unlink the per-message log file
*
* use this when a message has been processed completely.
*/
void
unlink_msg_log()
{
close_msg_log();
if (msg_log_fn) {
(void) unlink(msg_log_fn);
msg_log_fn = NULL;
}
}
/*
* panic - panic and die!
*
* Attempt to write to the panic and system log files. and no matter what we
* also try to write to stderr.
*
* If this is called after spooling a message, then the message will be put in
* the error queue so that the postmaster can reprocess it after correcting
* whatever error caused the panic in the first place.
*
*/
/*VARARGS2*/
#ifdef __STDC__
void
panic(int exitcode, char *fmt, ...)
#else
void
panic(exitcode, fmt, va_alist)
int exitcode; /* we will call exit(exitcode) */
char *fmt; /* printf(3) format */
va_dcl /* arguments for printf */
#endif
{
va_list ap;
if (errfile) {
VA_START(ap, fmt);
write_log_va(WRITE_LOG_TTY, fmt, ap);
va_end(ap);
if (only_testing) {
exit(force_zero_exitvalue ? 0 : exitcode);
}
}
VA_START(ap, fmt);
write_log_va(WRITE_LOG_PANIC, fmt, ap);
va_end(ap);
VA_START(ap, fmt);
write_log_va(WRITE_LOG_SYS, fmt, ap);
va_end(ap);
if (daemon_pid != 0 && daemon_pid == getpid()) {
write_log(WRITE_LOG_SYS, "smail daemon exiting on panic");
if (daemon_pidfile && *daemon_pidfile == '/') {
(void) unlink(daemon_pidfile);
}
}
if (spool_fn) {
freeze_message(); /* put message in error/ directory */
}
/* XXX we _REALLY_ should have an option to (try to) trigger a core dump here.... */
exit(force_zero_exitvalue ? 0 : exitcode);
/* NOTREACHED */
}
#ifndef NODEBUG
# define WRITE_LOG_DEBUG (WRITE_LOG_LAST << 2) /* magic for write_log_va() */
#endif
/*
* write_log - write to the per-message log, and perhaps the system log
*
* write a log message to the various log files, where `who' is the
* bitwise OR of WRITE_LOG_SYS, WRITE_LOG_MLOG and/or WRITE_LOG_PANIC.
*
* Note that messages with a leading 'X' are for per-message logs.
*/
/*VARARGS2*/
#ifdef __STDC__
void
write_log(int who, char *fmt, ...)
#else
void
write_log(who, fmt, va_alist)
int who; /* mask of log files to be written */
char *fmt; /* printf(3) format */
va_dcl /* arguments for printf */
#endif
{
va_list ap;
#ifndef NODEBUG
int wct = 0;
if (debug && errfile && (debug > 1 || fmt[0] != 'X')) {
/* if we are debugging at all, print all logged messages */
if (debug > 90 || operation_mode == DAEMON_MODE) {
(void) fprintf(errfile, "[%d]", getpid());
}
(void) fputs("write_log(", errfile);
if (who & WRITE_LOG_SYS) {
(void) fputs("SYS", errfile);
wct++;
}
if (who & WRITE_LOG_PANIC) {
(void) fprintf(errfile, "%sPANIC", wct++ ? "|" : "");
}
if (who & WRITE_LOG_MLOG) {
(void) fprintf(errfile, "%sMLOG", wct++ ? "|" : "");
}
if (who & WRITE_LOG_TTY) {
(void) fprintf(errfile, "%sTTY", wct++ ? "|" : "");
}
if (who & WRITE_LOG_DEBUG) {
(void) fprintf(errfile, "%sDEBUG", wct++ ? "|" : "");
}
(void) fputs("): ", errfile);
if (message_id) {
(void) fprintf(errfile, "[%s] ", message_id);
}
VA_START(ap, fmt);
write_log_va(WRITE_LOG_DEBUG, fmt, ap);
va_end(ap);
if (only_testing || who == WRITE_LOG_TTY) {
return;
}
}
#endif /* NODEBUG */
if (errfile && ((who & WRITE_LOG_TTY) ||
((who & (WRITE_LOG_MLOG|WRITE_LOG_PANIC)) &&
(error_processing == TERMINAL ||
error_processing == ERROR_DEFAULT) && /* XXX ??? */
fmt[0] != 'X'))) {
VA_START(ap, fmt);
write_log_va(WRITE_LOG_TTY, fmt, ap);
va_end(ap);
}
if (only_testing) {
return;
}
if (who & WRITE_LOG_SYS) {
VA_START(ap, fmt);
write_log_va(WRITE_LOG_SYS, fmt, ap);
va_end(ap);
}
if (who & WRITE_LOG_PANIC) {
VA_START(ap, fmt);
write_log_va(WRITE_LOG_PANIC, fmt, ap);
va_end(ap);
}
if (who & WRITE_LOG_MLOG) {
VA_START(ap, fmt);
write_log_va(WRITE_LOG_MLOG, fmt, ap);
va_end(ap);
}
return;
}
static void
write_log_va(who, fmt, ap)
int who; /* mask of log files to be written */
char *fmt; /* printf(3) format */
va_list ap; /* arguments for vfprintf() */
{
STR_CLEAR(&logstr);
STR_CHECK(&logstr);
str_printf_va(&logstr, fmt, ap);
/* log messages don't normally come with their own \n */
if (STR_GET(&logstr, STR_LEN(&logstr) - 1) != '\n') {
STR_CAT(&logstr, "\n");
}
switch (who) {
#ifndef NODEBUG
case WRITE_LOG_DEBUG:
(void) fputs(STR(&logstr), errfile);
(void) fflush(errfile);
break;
#endif /* NODEBUG */
case WRITE_LOG_TTY:
if (only_testing) {
(void) fprintf(errfile, "%s: ", program);
} else {
(void) fprintf(errfile, "%s: %s: [%ld] ", program, time_stamp(), (long int) getpid());
}
if (message_id) {
(void) fprintf(errfile, "[%s] ", message_id);
}
(void) fputs(STR(&logstr), errfile);
(void) fflush(errfile);
if (ferror(errfile)) {
int oerrno = errno;
int fd;
fd = fileno(errfile);
errfile = NULL;
panic(EX_IOERR, "error writing %s[%d]: %s", "errfile", fd, strerror(oerrno));
/*NOTREACHED*/
}
break;
case WRITE_LOG_SYS:
/* write out permanent log to the system log file */
if (logfile == NULL || panicfile == NULL) {
open_system_logs(); /* if system log not open, open it */
}
#ifdef LOG_EXTRA_NEWLINE
(void) fputc('\n', logfile);
#endif
if (only_testing) {
(void) fprintf(logfile, "%s: logging: ", program);
}
(void) fprintf(logfile, "%s: [%ld] ", time_stamp(), (long int) getpid());
if (message_id) {
(void) fprintf(logfile, "[%s] ", message_id);
}
(void) fputs(STR(&logstr), logfile);
(void) fflush(logfile);
if (ferror(logfile)) {
int oerrno = errno;
int fd;
fd = fileno(logfile);
logfile = NULL;
panic(EX_IOERR, "error writing the following message to %s[%d]: %s\n%s",
log_fn, fd, strerror(oerrno), STR(&logstr));
/*NOTREACHED*/
}
break;
case WRITE_LOG_PANIC:
if (panicfile == NULL) {
open_system_logs(); /* if system log not open, open it */
}
#ifdef LOG_EXTRA_NEWLINE
(void) fputc('\n', logfile);
#endif
if (only_testing) {
(void) fprintf(panicfile, "%s: panic: ", program);
}
(void) fprintf(panicfile, "%s: [%ld] ", time_stamp(), (long int) getpid());
if (message_id) {
(void) fprintf(panicfile, "[%s] ", message_id);
}
(void) fputs(STR(&logstr), panicfile);
(void) fflush(panicfile);
if (ferror(panicfile)) {
int oerrno = errno;
int fd;
/*
* Note: no sense potentially going in a loop here by calling
* panic() again, so instead just clear the errant panicfile so
* that nothing tries again, and so in case panic() itself calls
* itself then mabye the open_system_logs() above will work. In
* the mean time just try writing the message to errfile and
* logfile if possible.
*/
fd = fileno(panicfile);
panicfile = NULL;
if (errfile) {
(void) fprintf(errfile, "error writing following message to %s[%d]: %s\n", panic_fn, fd, strerror(oerrno));
(void) fputs(STR(&logstr), errfile);
(void) fflush(errfile);
}
if (logfile) {
(void) fprintf(logfile, "error writing following message to %s[%d]: %s", panic_fn, fd, strerror(oerrno));
(void) fputs(STR(&logstr), errfile);
(void) fflush(logfile);
}
}
break;
/*
* NOTE: if there is no spool_dir set, then a per-message logfile
* cannot be created, so don't bother writing per-message
* log entries.
*/
case WRITE_LOG_MLOG:
if (spool_dir) {
if (msg_logfile == NULL) {
open_msg_log(); /* if message log not open, open it */
}
/* write out the message to the per-message log file */
(void) fputs(STR(&logstr), msg_logfile);
/* finally append the timestamp and PID */
(void) fprintf(msg_logfile, "[%s: [%ld]]\n", time_stamp(), (long int) getpid());
(void) fflush(msg_logfile);
if (ferror(msg_logfile)) {
int oerrno = errno;
msg_logfile = NULL;
panic(EX_IOERR, "error writing the following message to %s/%s[%d]: %s\n%s",
spool_dir, msg_log_fn, fileno(msg_logfile), strerror(oerrno), STR(&logstr));
/*NOTREACHED*/
}
}
break;
}
}
/*
* send_log - write out the per-message log file.
*
* Send the "interesting" entries of the per-message log to a file 'fp',
* without their leading 'X' marker characters. Prepend an optional banner
* string before any log data, IFF there is any log data.
*/
void
send_log(fp, banner)
FILE *fp; /* send to this file */
char *banner; /* if non-NULL precedes log output */
{
char *s; /* line of text from scan_msg_log() */
int seen_some = FALSE;
struct defer_addr *deferrals = NULL;
struct defer_addr *cd;
#ifndef NDEBUG
if (debug > DBG_LOG_HI) {
FILE *mlog;
/*
* just copy the whole message-log to the specified file so that we can
* see exactly what's in it without worrying about odd-ball stuff
* hiding from scan_msg_log().
*
* Coincidently this will avoid some duplication of the scan_msg_log()
* debug output.
*/
build_msg_log_fn();
/* open log file separately to avoid changing the write position */
if (! (mlog = fopen(msg_log_fn, "r"))) {
DEBUG3(DBG_LOG_MID, "send_log(): no message log file: %s/%s: %s.\n", spool_dir, msg_log_fn, strerror(errno));
return;
}
while ((s = read_line(mlog))) {
if (banner) {
(void) fputs(banner, fp);
banner = NULL; /* output banner at most once */
}
/* the %v escapes trailing newline so include an explict one */
dprintf(fp, " %v\n", s); /* do use %v to make sure it's clean */
}
(void) fclose(mlog); /* don't need this anymore */
(void) fputc('\n', fp); /* always leave a blank line */
return;
}
#endif
/*
* first scan through all of the entries in the message log file to gather
* up the most recent deferral entries for each address.
*
* (unless debug is turned up -- in which case just print everything)
*/
for (s = scan_msg_log(TRUE); s; s = scan_msg_log(FALSE)) {
if (!seen_some) {
seen_some = TRUE;
if (banner) {
(void) fputs(banner, fp);
banner = NULL; /* output banner at most once */
}
}
if (debug > DBG_LOG_MID) {
/* scan_msg_log() trims the trailing newline */
(void) fprintf(fp, "%s\n", s + 1);
} else if (HDREQ("Xdefer", s)) {
char *address = NULL; /* the address to remove */
char *parent = NULL; /* parent of address to remove */
unsigned long int ecode; /* error code from address */
char *entry; /* wrk storage for decode_x_line() */
struct defer_addr *nd;
entry = COPY_STRING(s); /* decode_x_line() is destructive */
(void) decode_x_line(strchr(entry, ':') + 2, &address, &parent, &ecode);
if (!address) {
DEBUG1(DBG_LOG_LO, "found bogus Xdefer:\n%s\n", s);
xfree(entry);
continue;
}
for (cd = deferrals; cd; cd = cd->succ) {
if (EQ(cd->address, address) &&
(!parent ||
(cd->parent && EQ(cd->parent, parent)))) {
/* match! */
break;
}
}
if (cd) {
xfree(cd->message);
cd->message = COPY_STRING(s);
xfree(cd->address);
cd->address = COPY_STRING(address);
if (cd->parent) {
xfree(cd->parent);
cd->parent = NULL;
}
if (parent) {
cd->parent = COPY_STRING(parent);
}
} else {
nd = (struct defer_addr *) xmalloc(sizeof(*nd));
nd->message = COPY_STRING(s);
nd->address = COPY_STRING(address);
if (parent) {
nd->parent = COPY_STRING(parent);
} else {
nd->parent = NULL;
}
nd->succ = deferrals;
deferrals = nd;
}
xfree(entry);
}
}
if (debug > DBG_LOG_MID) {
/* if we've already printed everything... */
if (seen_some) {
(void) fputc('\n', fp); /* leave a blank line */
}
/* ... we're done. */
return;
}
/*
* First we print any deferrals from the most-recent deferrals list. This
* conveniently puts the most interesting entries first for "mailq -v"
* readers, and it also ensures they appear before any final Xfail entry
* should the message bounce after the retry_duration has expired for the
* destination. [[ ideally we would merge them back into the other entries
* in cronological order, but that would require a lot more processing and
* for very little gain ]]
*/
for (cd = deferrals; cd; ) {
struct defer_addr *ocd = cd;
/* XXX we don't want to escape newlines, but currently dprintf()'s "%v" does so... */
fprintf(fp, "%s\n", (cd->message) + 1);
/* XXX there's no free_defer_addr() so we do it by hand */
xfree(cd->message);
xfree(cd->address);
if (cd->parent) {
xfree(cd->parent);
}
cd = cd->succ;
xfree((char *) ocd);
}
/* now go through the log again and print everything else... */
for (s = scan_msg_log(TRUE); s; s = scan_msg_log(FALSE)) {
if (! HDREQ("Xdefer", s)) {
/* scan_msg_log() trims the trailing newline */
/* XXX we don't want to escape newlines, but currently dprintf()'s "%v" does so... */
(void) fprintf(fp, "%s\n", s + 1);
}
}
if (seen_some) {
(void) fputc('\n', fp); /* leave a blank line */
}
return;
}
/*
* scan_msg_log - scan for X entries in the per-message logfile
*
* If 'first' is TRUE, open the message log and scan for the first line marked
* with an X, return the entry starting with that line.
*
* On successive calls, where 'first' is FALSE, return successive entries marked
* with a line starting with an X. Return NULL if no more such lines are found.
*
* returned value points to a string area which may be reused on subsequent
* calls to scan_msg_log.
*
* Note: we keep a private file pointer (and thus file descriptor) to keep
* from changing the write position of msg_logfile while reading.
*/
char *
scan_msg_log(first)
int first; /* TRUE to re-open the message log */
{
static FILE *mlog = NULL; /* opened message log */
static struct str entry; /* entry marked with X */
static int inited_entry = FALSE; /* TRUE if STR_INIT called for entry */
register int c, last_c;
build_msg_log_fn();
if (!mlog) {
DEBUG2(DBG_LOG_HI, "scan_msg_log(): %s scanning message log file: %s.\n", first ? "starting" : "re-starting", msg_log_fn);
if (!(mlog = fopen(msg_log_fn, "r"))) { /* reopen log file */
DEBUG2(DBG_LOG_MID, "scan_msg_log(): no message log file: %s: %s.\n", msg_log_fn, strerror(errno));
return NULL;
}
}
if (first) {
rewind(mlog);
}
last_c = '\n'; /* assume starting at BOL */
/* scan for a line beginning with an 'X' */
while ((c = getc(mlog)) != EOF) {
if (c == 'X' && last_c == '\n') {
break;
}
last_c = c;
}
if (c == EOF) {
/* reached end of file without finding another X line */
DEBUG1(DBG_LOG_HI, "scan_msg_log(): finished scanning message log file: %s.\n", msg_log_fn);
(void) fclose(mlog);
mlog = NULL;
return NULL;
}
/*
* we've found a line marked with a leading 'X', read it and any following
* lines in until another line starting with 'X' is found, or EOF is
* reached.
*/
if (! inited_entry) {
STR_INIT(&entry);
inited_entry = TRUE;
} else {
STR_CHECK(&entry);
STR_CLEAR(&entry);
}
STR_NEXT(&entry, 'X');
last_c = 'X';
while ((c = getc(mlog)) != EOF) {
if (last_c == '\n' && c == 'X') {
break;
}
STR_NEXT(&entry, c);
last_c = c;
}
if (c == 'X') {
ungetc(c, mlog); /* put back 'X' for next time */
}
if (last_c == '\n') {
STR_PREV(&entry); /* trim off the last newline */
}
STR_NEXT(&entry, '\0');
/* return the entry */
DEBUG1(DBG_LOG_HI, "scan_msg_log() returns:\n'%v'\n", STR(&entry));
return STR(&entry);
}
/*
* process_msg_log - process X lines in the per-message log
*
* Lines beginning with X in the per-message log contain information on what
* mail processing was completed in a previous run on the input spool file.
*
* This function takes, as input, the set of addresses which were produced by
* resolve_addr_list() for delivery and returns a list which does not contain
* addresses which have already been delivered, or which have already
* permanently failed delivery.
*
* A list of "identify" addresses for which notifications have already been
* sent is returned by reference through the sent_errors parameter. This list
* is used to avoid further processing of failed (and deferred) addresses if
* notifications have already been sent for them.
*/
struct addr *
process_msg_log(in, sent_errors)
struct addr *in; /* input resolved addresses */
struct identify_addr **sent_errors; /* return errors that have been sent */
{
register struct addr *ret; /* addr list to return */
char *s; /* line of text from scan_msg_log() */
ret = in;
/* scan through all of the message log entries, if any */
for (s = scan_msg_log(TRUE); s; s = scan_msg_log(FALSE)) {
char *content; /* content of log entry */
char *address = NULL; /* the address to remove */
char *parent = NULL; /* parent of address to remove */
unsigned long int ecode = 0; /* error info from log */
char *msg; /* error message from log */
if (!(content = strchr(s, ':'))) {
DEBUG1(DBG_LOG_LO, "process_msg_log(): encountered non-header entry:\n%v\n-- end entry.\n", s);
continue;
}
content += 2;
msg = decode_x_line(content, &address, &parent, &ecode);
if (HDREQ("Xfail", s)) {
/*
* remove any matching occurance from the input list
*/
if (address) {
ret = remove_addr(ret, address, parent);
}
} else if (HDREQ("Xsucceed", s)) {
/*
* remove any matching occurance from the input list
*/
if (address) {
ret = remove_addr(ret, address, parent);
}
} else if (HDREQ("Xdefer", s)) {
/*
* overwrite the address' error->message with the deferal message,
* adjusting the error->info code if we can discern it.
*
* this will happen for every deferral message in the log, leaving
* only the most recent in addr->error.
*
* Note also we may still remove this address if there's a later
* Xfail entry for it.
*/
if (address && msg) {
struct addr *cur;
struct addr *next;
for (cur = ret; cur; cur = next) {
int found_it = FALSE;
register struct addr *top; /* the ultimate parent address to compare */
next = cur->succ;
/* find the top parent to log the original in_addr */
for (top = cur; top->parent && top->parent->in_addr; top = top->parent) {
;
}
if (top == cur) {
top = NULL;
}
if (EQ(cur->in_addr, address)) {
/* the address does match */
if (parent) {
/* a matching parent is also required for a match */
if (top && EQ(parent, top->in_addr)) {
found_it = TRUE;
}
} else if (top == NULL) {
found_it = TRUE;
}
}
if (found_it) {
if (cur->error) {
free_error(cur->error);
}
cur->error = note_error(ecode, xprintf("deferred: %s", msg));
/* XXX there should never be another the same.... */
break;
}
}
}
} else if (HDREQ("Xstderr", s)) {
/* append the stderr text to the address' error */
if (address && msg) {
/*
* NOTE: The pipe driver may be handed several addresses to
* deliver at once (which would be expanded on its command-line
* with the "$(${user}$)" iterator trick), and thus it creates
* msglog entries with possibly multiple comma-separated
* "<address> [orig-to: <parent>]" sub-fields and if we were to
* really handle these properly we would have to parse them all
* out and append to all the appropriate addresses.
*/
}
} else if (HDREQ("Xnotice", s)) {
/*
* basically we just process then ignore these entries as there's
* usually no address (other than <>) associated with them.
*/
} else if (strncmp(s, "Xsent_error:", sizeof("Xsent_error:")-1) == 0) {
register struct identify_addr *new_sent;
if (address) {
/* add this address to the list returned via sent_errors */
new_sent = (struct identify_addr *) xmalloc(sizeof(*new_sent));
new_sent->address = COPY_STRING(address);
if (parent) {
new_sent->parent = COPY_STRING(parent);
} else {
new_sent->parent = NULL;
}
new_sent->succ = *sent_errors;
*sent_errors = new_sent;
/*
* in theory this address has already been removed from the
* input list since there should also be an Xfail entry for it
*
* XXX we should check to be sure!
*/
}
} else {
DEBUG1(DBG_LOG_LO, "process_msg_log(): encountered misc. entry:\n%v\n-- end entry.\n", s);
}
}
return ret;
}
/*
* decode_x_line - decode an X-line from the per-message log file
*
* The input string pointed to by 'line' should be of the form:
*
* "<"address"> orig-to: <"parent_address"> "message...
* [more message...]
* "["MM"/"DD"/"YYYY" "HH":"MM":"SS": ["PID"]]"
*
* or:
*
* "<"address"> "message...
* [more message...]
* "["MM"/"DD"/"YYYY" "HH":"MM":"SS": ["PID"]]"
*
* (i.e. with the "X*: " field name removed)
*
* this routine returns the address and the parent_address. It also returns a
* pointer to the explanitory message (which may include the reason code).
*/
char *
decode_x_line(content, addressp, parentp, ecodep)
char *content; /* content of log entry field */
char **addressp; /* return address here */
char **parentp; /* return parent address here */
unsigned long int *ecodep; /* error code parsed from log entry */
{
int level; /* count < and > nesting */
register char *p = content; /* points to parts of the line */
*addressp = NULL;
*parentp = NULL;
/* skip initial white-space */
while (isspace((int) *p)) {
p++;
}
/* ignore anything not of the proper form */
if (*p != '<') {
DEBUG1(DBG_LOG_LO, "decode_x_line(): WARNING: no '<' for address: %v\n", content);
return NULL;
}
p++; /* skip the initial < */
/*
* extract the address as the text up until before the balancing
* > character, in <address>.
*/
*addressp = p; /* mark this spot as the address */
level = 1; /* start at nesting level 1 */
if (*p == '<') {
level++;
}
while (level > 0) {
if (!(p = address_token(p))) {
DEBUG1(DBG_LOG_LO, "decode_x_line(): WARNING: no address token in address: %v\n", p);
return NULL;
}
if (*p == '<') {
level++;
} else if (*p == '>') {
--level;
}
}
/*
* XXX instead of destructively turning line into multiple strings we
* should be copying the substrings and returning them as separate storage.
*/
*p++ = '\0'; /* the last > is the end of the address */
/*
* XXX in order to be able to store quoted addresses in the msglog entries
* we'll have to also decode the quoting so that when we go back to compare
* to input addresses they would match....
*/
while (isspace((int) *p)) {
p++;
}
/* is there a parent address? */
if (strncmp(p, "orig-to:", sizeof("orig-to:") - 1) == 0) {
p += sizeof("orig-to:") - 1;
/* skip any initial white-space before actual parent address */
while (isspace((int) *p)) {
p++;
}
/* ignore anything not of the proper form */
if (*p != '<') {
DEBUG1(DBG_LOG_LO, "decode_x_line(): WARNING: no '<' for parent address: %v\n", p);
return NULL;
}
p++; /* skip the initial < */
/*
* extract the parent as the text up until before the balancing
* > character, in <parent_addres>
*/
*parentp = p;
level = 1;
if (*p == '<') {
level++;
}
while (level > 0){
if (!(p = address_token(p))) {
DEBUG1(DBG_LOG_LO, "decode_x_line(): WARNING: no address token in parent: %v\n", p);
return NULL;
}
if (*p == '<') {
level++;
} else if (*p == '>') {
--level;
}
}
/*
* XXX instead of destructively turning line into multiple strings we
* should be copying the substrings and returning them as separate
* storage.
*/
*p++ = '\0'; /* the last > is the end of the address */
/*
* XXX in order to be able to store quoted addresses in the msglog
* entries we'll have to also decode the quoting so that when we go
* back to compare to input addresses they would match....
*/
while (isspace((int) *p)) {
p++;
}
} else {
DEBUG1(DBG_LOG_HI, "decode_x_line(): no parent ('orig-to:') for: %v\n", *addressp);
}
if (strncmp(p, "(ERR", (size_t) 4) == 0) {
/*
* This is a bit of a hack since we assume the value of the error code
* constant is the same as the number in its name...
*
* XXX we should use c_atol() but it expects the string to end at the
* end of the number, and we don't use strtoul() as it's not as
* portable as we'd like.
*/
*ecodep = (unsigned long int) atol(p + 4);
if (*ecodep == ULONG_MAX) {
DEBUG1(DBG_LOG_LO, "decode_x_line(): NOTICE: bad error code value in: %s\n", p);
*ecodep = ERR_NPOSTMAST; /* leave it zero, but set NPOSTMAST flag */
}
*ecodep |= ERR_NSOWNER; /* the best default notify choice */
}
/* XXX for now we leave the timestamp on the end of the message */
return p;
}
#ifdef STANDALONE
/*
cc -g -DSTANDALONE -o log.stand log.c spool.o alloc.o string.o sysdep.o config.o ascii.o
*/
int debug = 0; /* adjust as desired */
static char *panic_fn = "paniclog";
static char *log_fn = "logfile";
static char *cons_fn = "/dev/console";
static char *spool_fn = "spoolfile";
char *msg_log_dir = "/tmp";
char *program = "log";
int exitvalue = 0;
enum er_proc error_processing = MAIL_BACK;
int return_to_sender = FALSE;
int islocal = TRUE;
char *sender = "nsc!tron";
char *sender_name = "MasterControlProgram";
char *local_sender;
int force_zero_exitvalue = FALSE;
FILE *errfile = stderr;
long pid_t daemon_pid = 4000000000; /* 0xEE6B2800 */
uid_t real_uid = 0;
char *
bind_compute_domain()
{
return NULL;
}
/*
* excersize the logging code by performing the various operations
* in a sequence given at run time on the standard input.
*/
/* ARGSUSED */
int
main(argc, argv)
int argc;
char *argv[];
{
register int c; /* hold key for current operation */
char *line; /* a line of input */
char *p;
while ((c = getchar()) != EOF) {
switch (c) {
case 'O':
open_system_logs();
break;
case 'C':
close_system_logs();
break;
case 'o':
open_msg_log();
break;
case 'c':
close_msg_log();
break;
case 's':
send_log(stdout, FALSE, "|----- log without X lines -----|\n");
break;
case 'S':
send_log(stdout, TRUE, "|----- log with X lines -----|\n");
break;
case 'X':
for (p = scan_msg_log(TRUE); p; p = scan_msg_log(FALSE)) {
printf("%s\n", p);
}
break;
case 'p':
if ((line = read_line(stdin)))
panic(EX_OSERR, line);
break;
case 'l':
if ((line = read_line(stdin)))
write_log(WRITE_LOG_SYS, line);
break;
case 'm':
if ((line = read_line(stdin)))
write_log(WRITE_LOG_MLOG, line);
break;
case 'L':
if ((line = read_line(stdin)))
write_log(WRITE_LOG_MLOG|WRITE_LOG_SYS|WRITE_LOG_PANIC, line);
break;
case ' ':
case '\t':
case '\n':
break;
default:
(void)fprintf(stderr, "%c -- huh?\n", c);
break;
}
}
exit(EX_OK);
}
#endif /* STANDALONE */
/*
* Local Variables:
* c-file-style: "smail"
* End:
*/
syntax highlighted by Code2HTML, v. 0.9.1