/*++ /* NAME /* postcat 1 /* SUMMARY /* show Postfix queue file contents /* SYNOPSIS /* \fBpostcat\fR [\fB-oqv\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. /* /* Options: /* .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-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. /* /* Available in Postfix version 2.0 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 "\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 /*--*/ /* System library. */ #include #include #include #include #include #include #include /* Utility library. */ #include #include #include #include #include /* Global library. */ #include #include #include #include #include /* Application-specific. */ #define PC_FLAG_QUEUE (1<<0) /* search queue */ #define PC_FLAG_OFFSET (1<<1) /* print record offsets */ #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; time_t time; int first = 1; int ch; off_t offset; int in_message = 0; #define TEXT_RECORD(rec_type) \ (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) /* * See if this is a plausible file. */ if ((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); } /* * Now look at the rest. */ for (;;) { if (flags & PC_FLAG_OFFSET) offset = vstream_ftell(fp); rec_type = rec_get(fp, buffer, 0); if (rec_type == REC_TYPE_ERROR) msg_fatal("record read error"); if (rec_type == REC_TYPE_EOF) break; if (first == 1) { vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp)); first = 0; } if (prev_type == REC_TYPE_CONT && !TEXT_RECORD(rec_type)) VSTREAM_PUTCHAR('\n'); if (flags & PC_FLAG_OFFSET) vstream_printf("%9lu ", (unsigned long) offset); switch (rec_type) { case REC_TYPE_TIME: case REC_TYPE_WARN: time = atol(STR(buffer)); vstream_printf("%s: %s", rec_type_name(rec_type), asctime(localtime(&time))); break; case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ if (!in_message) 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 (!in_message || msg_verbose || (flags & PC_FLAG_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_MESG: vstream_printf("*** MESSAGE CONTENTS %s ***\n", VSTREAM_PATH(fp)); in_message = 1; break; case REC_TYPE_XTRA: vstream_printf("*** HEADER EXTRACTED %s ***\n", VSTREAM_PATH(fp)); in_message = 0; break; case REC_TYPE_END: vstream_printf("*** MESSAGE FILE END %s ***\n", VSTREAM_PATH(fp)); 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 [-c config_dir] [-q (access queue)] [-v] [file(s)...]", myname); } 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, 0, }; char **cpp; int tries; /* * 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); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "c:oqv")) > 0) { switch (ch) { case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'o': flags |= PC_FLAG_OFFSET; break; case 'q': flags |= PC_FLAG_QUEUE; break; case 'v': msg_verbose++; break; default: usage(argv[0]); } } /* * Further initialization... */ mail_conf_read(); /* * Initialize. */ buffer = vstring_alloc(10); /* * If no file names are given, copy stdin. */ if (argc == optind) { vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); postcat(VSTREAM_IN, buffer, flags); } /* * Copy the named queue files in the specified order. */ else if (flags & PC_FLAG_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); }