/* #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 #include #include #include #include #include #ifdef STDC_HEADERS # include # include #else # ifdef HAVE_STDLIB_H # include # endif #endif #ifdef HAVE_STRING_H # if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) # include # endif # include #endif #ifdef HAVE_STRINGS_H # include #endif #ifdef __STDC__ # include #else # include #endif #if defined(HAVE_UNISTD_H) # include #endif #if defined(UNIX_SYS5) || defined(POSIX_OS) || defined(USE_FCNTL) # include #else # if defined(UNIX_BSD) # include # endif #endif #ifdef __GLIBC__ # include #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 * "
[orig-to: ]" 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
. */ *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 */ *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: */