diff options
Diffstat (limited to 'src/postcat/postcat.c')
-rw-r--r-- | src/postcat/postcat.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/src/postcat/postcat.c b/src/postcat/postcat.c new file mode 100644 index 0000000..36f2740 --- /dev/null +++ b/src/postcat/postcat.c @@ -0,0 +1,598 @@ +/*++ +/* NAME +/* postcat 1 +/* SUMMARY +/* show Postfix queue file contents +/* SYNOPSIS +/* \fBpostcat\fR [\fB-bdehnoqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...] +/* DESCRIPTION +/* The \fBpostcat\fR(1) command prints the contents of the +/* named \fIfiles\fR in human-readable form. The files are +/* expected to be in Postfix queue file format. If no \fIfiles\fR +/* are specified on the command line, the program reads from +/* standard input. +/* +/* By default, \fBpostcat\fR(1) shows the envelope and message +/* content, as if the options \fB-beh\fR were specified. To +/* view message content only, specify \fB-bh\fR (Postfix 2.7 +/* and later). +/* +/* Options: +/* .IP \fB-b\fR +/* Show body content. The \fB-b\fR option starts producing +/* output at the first non-header line, and stops when the end +/* of the message is reached. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. +/* .IP \fB-d\fR +/* Print the decimal type of each record. +/* .IP \fB-e\fR +/* Show message envelope content. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP \fB-h\fR +/* Show message header content. The \fB-h\fR option produces +/* output from the beginning of the message up to, but not +/* including, the first non-header line. +/* .sp +/* This feature is available in Postfix 2.7 and later. +/* .IP \fB-o\fR +/* Print the queue file offset of each record. +/* .IP \fB-q\fR +/* Search the Postfix queue for the named \fIfiles\fR instead +/* of taking the names literally. +/* +/* This feature is available in Postfix 2.0 and later. +/* .IP \fB-r\fR +/* Print records in file order, don't follow pointer records. +/* +/* This feature is available in Postfix 3.7 and later. +/* .IP "\fB-s \fIoffset\fR" +/* Skip to the specified queue file offset. +/* +/* This feature is available in Postfix 3.7 and later. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* DIAGNOSTICS +/* Problems are reported to the standard error stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP \fBMAIL_CONFIG\fR +/* Directory with Postfix configuration files. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBimport_environment (see 'postconf -d' output)\fR" +/* The list of environment parameters that a privileged Postfix +/* process will import from a non-Postfix parent process, or name=value +/* environment overrides. +/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" +/* The location of the Postfix top-level queue directory. +/* FILES +/* /var/spool/postfix, Postfix queue directory +/* SEE ALSO +/* postconf(5), Postfix configuration +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <fcntl.h> +#include <string.h> +#include <stdio.h> /* sscanf() */ + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <msg_vstream.h> +#include <vstring_vstream.h> +#include <stringops.h> +#include <warn_stat.h> +#include <clean_env.h> + +/* Global library. */ + +#include <record.h> +#include <rec_type.h> +#include <mail_queue.h> +#include <mail_conf.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_proto.h> +#include <is_header.h> +#include <lex_822.h> +#include <mail_parm_split.h> + +/* Application-specific. */ + +#define PC_FLAG_SEARCH_QUEUE (1<<0) /* search queue */ +#define PC_FLAG_PRINT_OFFSET (1<<1) /* print record offsets */ +#define PC_FLAG_PRINT_ENV (1<<2) /* print envelope records */ +#define PC_FLAG_PRINT_HEADER (1<<3) /* print header records */ +#define PC_FLAG_PRINT_BODY (1<<4) /* print body records */ +#define PC_FLAG_PRINT_RTYPE_DEC (1<<5) /* print decimal record type */ +#define PC_FLAG_PRINT_RTYPE_SYM (1<<6) /* print symbolic record type */ +#define PC_FLAG_RAW (1<<7) /* don't follow pointers */ + +#define PC_MASK_PRINT_TEXT (PC_FLAG_PRINT_HEADER | PC_FLAG_PRINT_BODY) +#define PC_MASK_PRINT_ALL (PC_FLAG_PRINT_ENV | PC_MASK_PRINT_TEXT) + + /* + * State machine. + */ +#define PC_STATE_ENV 0 /* initial or extracted envelope */ +#define PC_STATE_HEADER 1 /* primary header */ +#define PC_STATE_BODY 2 /* other */ + +off_t start_offset = 0; + +#define STR vstring_str +#define LEN VSTRING_LEN + +/* postcat - visualize Postfix queue file contents */ + +static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) +{ + int prev_type = 0; + int rec_type; + struct timeval tv; + time_t time; + int ch; + off_t offset; + const char *error_text; + char *attr_name; + char *attr_value; + int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); + int state; /* state machine, input type */ + int do_print; /* state machine, output control */ + long data_offset; /* state machine, read optimization */ + long data_size; /* state machine, read optimization */ + +#define TEXT_RECORD(rec_type) \ + (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) + + /* + * Skip over or absorb some bytes. + */ + if (start_offset > 0) { + if (fp == VSTREAM_IN) { + for (offset = 0; offset < start_offset; offset++) + if (VSTREAM_GETC(fp) == VSTREAM_EOF) + msg_fatal("%s: skip %ld bytes failed after %ld", + VSTREAM_PATH(fp), (long) start_offset, + (long) offset); + } else { + if (vstream_fseek(fp, start_offset, SEEK_SET) < 0) + msg_fatal("%s: seek to %ld: %m", + VSTREAM_PATH(fp), (long) start_offset); + } + } + + /* + * See if this is a plausible file. + */ + if (start_offset == 0 && (ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { + if (!strchr(REC_TYPE_ENVELOPE, ch)) { + msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); + return; + } + vstream_ungetc(fp, ch); + } + + /* + * Other preliminaries. + */ + if (start_offset == 0 && (flags & PC_FLAG_PRINT_ENV)) + vstream_printf("*** ENVELOPE RECORDS %s ***\n", + VSTREAM_PATH(fp)); + state = PC_STATE_ENV; + do_print = (flags & PC_FLAG_PRINT_ENV); + data_offset = data_size = -1; + + /* + * Now look at the rest. + */ + for (;;) { + if (flags & PC_FLAG_PRINT_OFFSET) + offset = vstream_ftell(fp); + rec_type = rec_get_raw(fp, buffer, 0, rec_flags); + if (rec_type == REC_TYPE_ERROR) + msg_fatal("record read error"); + if (rec_type == REC_TYPE_EOF) + break; + + /* + * First inspect records that have side effects on the (envelope, + * header, body) state machine or on the record reading order. + * + * XXX Comments marked "Optimization:" identify subtle code that will + * likely need to be revised when the queue file organization is + * changed. + */ +#define PRINT_MARKER(flags, fp, offset, type, text) do { \ + if ((flags) & PC_FLAG_PRINT_OFFSET) \ + vstream_printf("%9lu ", (unsigned long) (offset)); \ + if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ + vstream_printf("%3d ", (type)); \ + vstream_printf("*** %s %s ***\n", (text), VSTREAM_PATH(fp)); \ + vstream_fflush(VSTREAM_OUT); \ +} while (0) + +#define PRINT_RECORD(flags, offset, type, value) do { \ + if ((flags) & PC_FLAG_PRINT_OFFSET) \ + vstream_printf("%9lu ", (unsigned long) (offset)); \ + if (flags & PC_FLAG_PRINT_RTYPE_DEC) \ + vstream_printf("%3d ", (type)); \ + vstream_printf("%s: %s\n", rec_type_name(rec_type), (value)); \ + vstream_fflush(VSTREAM_OUT); \ +} while (0) + + if (TEXT_RECORD(rec_type)) { + /* This is wrong when the message starts with whitespace. */ + if (state == PC_STATE_HEADER && (flags & (PC_MASK_PRINT_TEXT)) + && prev_type != REC_TYPE_CONT && TEXT_RECORD(rec_type) + && !(is_header(STR(buffer)) || IS_SPACE_TAB(STR(buffer)[0]))) { + /* Update the state machine. */ + state = PC_STATE_BODY; + do_print = (flags & PC_FLAG_PRINT_BODY); + /* Optimization: terminate if nothing left to print. */ + if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) == 0) + break; + /* Optimization: skip to extracted segment marker. */ + if (do_print == 0 && (flags & PC_FLAG_PRINT_ENV) + && data_offset >= 0 && data_size >= 0 + && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + } + /* Optional output happens further down below. */ + } else if (rec_type == REC_TYPE_MESG) { + /* Sanity check. */ + if (state != PC_STATE_ENV) + msg_warn("%s: out-of-order message content marker", + VSTREAM_PATH(fp)); + /* Optional output. */ + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE CONTENTS"); + /* Optimization: skip to extracted segment marker. */ + if ((flags & PC_MASK_PRINT_TEXT) == 0 + && data_offset >= 0 && data_size >= 0 + && vstream_fseek(fp, data_offset + data_size, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + /* Update the state machine, even when skipping. */ + state = PC_STATE_HEADER; + do_print = (flags & PC_FLAG_PRINT_HEADER); + continue; + } else if (rec_type == REC_TYPE_XTRA) { + /* Sanity check. */ + if (state != PC_STATE_HEADER && state != PC_STATE_BODY) + msg_warn("%s: out-of-order extracted segment marker", + VSTREAM_PATH(fp)); + /* Optional output (terminate preceding header/body line). */ + if (do_print && prev_type == REC_TYPE_CONT) + VSTREAM_PUTCHAR('\n'); + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "HEADER EXTRACTED"); + /* Update the state machine. */ + state = PC_STATE_ENV; + do_print = (flags & PC_FLAG_PRINT_ENV); + /* Optimization: terminate if nothing left to print. */ + if (do_print == 0) + break; + continue; + } else if (rec_type == REC_TYPE_END) { + /* Sanity check. */ + if (state != PC_STATE_ENV) + msg_warn("%s: out-of-order message end marker", + VSTREAM_PATH(fp)); + /* Optional output. */ + if (flags & PC_FLAG_PRINT_ENV) + PRINT_MARKER(flags, fp, offset, rec_type, "MESSAGE FILE END"); + if (flags & PC_FLAG_RAW) + continue; + /* Terminate the state machine. */ + break; + } else if (rec_type == REC_TYPE_PTR) { + /* Optional output. */ + /* This record type is exposed only with '-v'. */ + if (do_print) + PRINT_RECORD(flags, offset, rec_type, STR(buffer)); + /* Skip to the pointer's target record. */ + if ((flags & PC_FLAG_RAW) == 0 + && rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) + msg_fatal("bad pointer record, or input is not seekable"); + continue; + } else if (rec_type == REC_TYPE_SIZE) { + /* Optional output (here before we update the state machine). */ + if (do_print) + PRINT_RECORD(flags, offset, rec_type, STR(buffer)); + /* Read the message size/offset for the state machine optimizer. */ + if (data_size >= 0 || data_offset >= 0) { + msg_warn("file contains multiple size records"); + } else { + if (sscanf(STR(buffer), "%ld %ld", &data_size, &data_offset) != 2 + || data_offset <= 0 || data_size <= 0) + msg_warn("invalid size record: %.100s", STR(buffer)); + /* Optimization: skip to the message header. */ + if ((flags & PC_FLAG_PRINT_ENV) == 0) { + if (vstream_fseek(fp, data_offset, SEEK_SET) < 0) + msg_fatal("seek error: %m"); + /* Update the state machine. */ + state = PC_STATE_HEADER; + do_print = (flags & PC_FLAG_PRINT_HEADER); + } + } + continue; + } + + /* + * Don't inspect side-effect-free records that aren't printed. + */ + if (do_print == 0) + continue; + if (flags & PC_FLAG_PRINT_OFFSET) + vstream_printf("%9lu ", (unsigned long) offset); + if (flags & PC_FLAG_PRINT_RTYPE_DEC) + vstream_printf("%3d ", rec_type); + switch (rec_type) { + case REC_TYPE_TIME: + REC_TYPE_TIME_SCAN(STR(buffer), tv); + time = tv.tv_sec; + vstream_printf("%s: %s", rec_type_name(rec_type), + asctime(localtime(&time))); + break; + case REC_TYPE_WARN: + REC_TYPE_WARN_SCAN(STR(buffer), time); + vstream_printf("%s: %s", rec_type_name(rec_type), + asctime(localtime(&time))); + break; + case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ + if (state == PC_STATE_ENV) + vstream_printf("%s: ", rec_type_name(rec_type)); + else if (msg_verbose) + vstream_printf("unterminated_text: "); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + if (state == PC_STATE_ENV || msg_verbose + || (flags & PC_FLAG_PRINT_OFFSET) != 0) { + rec_type = 0; + VSTREAM_PUTCHAR('\n'); + } + break; + case REC_TYPE_NORM: + if (msg_verbose) + vstream_printf("%s: ", rec_type_name(rec_type)); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + VSTREAM_PUTCHAR('\n'); + break; + case REC_TYPE_DTXT: + /* This record type is exposed only with '-v'. */ + vstream_printf("%s: ", rec_type_name(rec_type)); + vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); + VSTREAM_PUTCHAR('\n'); + break; + case REC_TYPE_ATTR: + error_text = split_nameval(STR(buffer), &attr_name, &attr_value); + if (error_text != 0) { + msg_warn("%s: malformed attribute: %s: %.100s", + VSTREAM_PATH(fp), error_text, STR(buffer)); + break; + } + if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { + time = atol(attr_value); + vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, + asctime(localtime(&time))); + } else { + vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), + attr_name, attr_value); + } + break; + default: + vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); + break; + } + prev_type = rec_type; + + /* + * In case the next record is broken. + */ + vstream_fflush(VSTREAM_OUT); + } +} + +/* usage - explain and terminate */ + +static NORETURN usage(char *myname) +{ + msg_fatal("usage: %s [-b (body text)] [-c config_dir] [-d (decimal record type)] [-e (envelope records)] [-h (header text)] [-q (access queue)] [-v] [file(s)...]", + myname); +} + +MAIL_VERSION_STAMP_DECLARE; + +int main(int argc, char **argv) +{ + VSTRING *buffer; + VSTREAM *fp; + int ch; + int fd; + struct stat st; + int flags = 0; + static char *queue_names[] = { + MAIL_QUEUE_MAILDROP, + MAIL_QUEUE_INCOMING, + MAIL_QUEUE_ACTIVE, + MAIL_QUEUE_DEFERRED, + MAIL_QUEUE_HOLD, + MAIL_QUEUE_SAVED, + 0, + }; + char **cpp; + int tries; + ARGV *import_env; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + /* + * To minimize confusion, make sure that the standard file descriptors + * are open before opening anything else. XXX Work around for 44BSD where + * fstat can return EBADF on an open file descriptor. + */ + for (fd = 0; fd < 3; fd++) + if (fstat(fd, &st) == -1 + && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) + msg_fatal("open /dev/null: %m"); + + /* + * Set up logging. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Check the Postfix library version as soon as we enable logging. + */ + MAIL_VERSION_CHECK; + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "bc:dehoqrs:v")) > 0) { + switch (ch) { + case 'b': + flags |= PC_FLAG_PRINT_BODY; + break; + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'd': + flags |= PC_FLAG_PRINT_RTYPE_DEC; + break; + case 'e': + flags |= PC_FLAG_PRINT_ENV; + break; + case 'h': + flags |= PC_FLAG_PRINT_HEADER; + break; + case 'o': + flags |= PC_FLAG_PRINT_OFFSET; + break; + case 'q': + flags |= PC_FLAG_SEARCH_QUEUE; + break; + case 'r': + flags |= PC_FLAG_RAW; + break; + case 's': + if (!alldig(optarg) || (start_offset = atol(optarg)) < 0) + msg_fatal("bad offset: %s", optarg); + break; + case 'v': + msg_verbose++; + break; + default: + usage(argv[0]); + } + } + if ((flags & PC_MASK_PRINT_ALL) == 0) + flags |= PC_MASK_PRINT_ALL; + + /* + * Further initialization... + */ + mail_conf_read(); + import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); + update_env(import_env->argv); + argv_free(import_env); + + /* + * Initialize. + */ + buffer = vstring_alloc(10); + + /* + * If no file names are given, copy stdin. + */ + if (argc == optind) { + vstream_control(VSTREAM_IN, + CA_VSTREAM_CTL_PATH("stdin"), + CA_VSTREAM_CTL_END); + postcat(VSTREAM_IN, buffer, flags); + } + + /* + * Copy the named queue files in the specified order. + */ + else if (flags & PC_FLAG_SEARCH_QUEUE) { + if (chdir(var_queue_dir)) + msg_fatal("chdir %s: %m", var_queue_dir); + while (optind < argc) { + if (!mail_queue_id_ok(argv[optind])) + msg_fatal("bad mail queue ID: %s", argv[optind]); + for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++) + for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++) + fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0); + if (fp == 0) + msg_fatal("open queue file %s: %m", argv[optind]); + postcat(fp, buffer, flags); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", argv[optind]); + optind++; + } + } + + /* + * Copy the named files in the specified order. + */ + else { + while (optind < argc) { + if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0) + msg_fatal("open %s: %m", argv[optind]); + postcat(fp, buffer, flags); + if (vstream_fclose(fp)) + msg_warn("close %s: %m", argv[optind]); + optind++; + } + } + + /* + * Clean up. + */ + vstring_free(buffer); + exit(0); +} |