xref: /netbsd-src/external/ibm-public/postfix/dist/src/postcat/postcat.c (revision abb0f93cd77b67f080613360c65701f85e5f5cfe)
1 /*	$NetBSD: postcat.c,v 1.1.1.1 2009/06/23 10:08:51 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	postcat 1
6 /* SUMMARY
7 /*	show Postfix queue file contents
8 /* SYNOPSIS
9 /*	\fBpostcat\fR [\fB-oqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...]
10 /* DESCRIPTION
11 /*	The \fBpostcat\fR(1) command prints the contents of the named
12 /*	\fIfiles\fR in human-readable form. The files are expected
13 /*	to be in Postfix queue file format. If no
14 /*	\fIfiles\fR are specified on the command line, the program
15 /*	reads from standard input.
16 /*
17 /*	Options:
18 /* .IP "\fB-c \fIconfig_dir\fR"
19 /*	The \fBmain.cf\fR configuration file is in the named directory
20 /*	instead of the default configuration directory.
21 /* .IP \fB-o\fR
22 /*	Print the queue file offset of each record.
23 /* .IP \fB-q\fR
24 /*	Search the Postfix queue for the named \fIfiles\fR instead
25 /*	of taking the names literally.
26 /*
27 /*	Available in Postfix version 2.0 and later.
28 /* .IP \fB-v\fR
29 /*	Enable verbose logging for debugging purposes. Multiple \fB-v\fR
30 /*	options make the software increasingly verbose.
31 /* DIAGNOSTICS
32 /*	Problems are reported to the standard error stream.
33 /* ENVIRONMENT
34 /* .ad
35 /* .fi
36 /* .IP \fBMAIL_CONFIG\fR
37 /*	Directory with Postfix configuration files.
38 /* CONFIGURATION PARAMETERS
39 /* .ad
40 /* .fi
41 /*	The following \fBmain.cf\fR parameters are especially relevant to
42 /*	this program.
43 /*
44 /*	The text below provides only a parameter summary. See
45 /*	\fBpostconf\fR(5) for more details including examples.
46 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
47 /*	The default location of the Postfix main.cf and master.cf
48 /*	configuration files.
49 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
50 /*	The location of the Postfix top-level queue directory.
51 /* FILES
52 /*	/var/spool/postfix, Postfix queue directory
53 /* SEE ALSO
54 /*	postconf(5), Postfix configuration
55 /* LICENSE
56 /* .ad
57 /* .fi
58 /*	The Secure Mailer license must be distributed with this software.
59 /* AUTHOR(S)
60 /*	Wietse Venema
61 /*	IBM T.J. Watson Research
62 /*	P.O. Box 704
63 /*	Yorktown Heights, NY 10598, USA
64 /*--*/
65 
66 /* System library. */
67 
68 #include <sys_defs.h>
69 #include <sys/stat.h>
70 #include <sys/time.h>
71 #include <stdlib.h>
72 #include <unistd.h>
73 #include <time.h>
74 #include <fcntl.h>
75 #include <string.h>
76 
77 /* Utility library. */
78 
79 #include <msg.h>
80 #include <vstream.h>
81 #include <vstring.h>
82 #include <msg_vstream.h>
83 #include <vstring_vstream.h>
84 #include <stringops.h>
85 
86 /* Global library. */
87 
88 #include <record.h>
89 #include <rec_type.h>
90 #include <mail_queue.h>
91 #include <mail_conf.h>
92 #include <mail_params.h>
93 #include <mail_version.h>
94 #include <mail_proto.h>
95 
96 /* Application-specific. */
97 
98 #define PC_FLAG_QUEUE	(1<<0)		/* search queue */
99 #define PC_FLAG_OFFSET	(1<<1)		/* print record offsets */
100 
101 #define STR	vstring_str
102 #define LEN	VSTRING_LEN
103 
104 /* postcat - visualize Postfix queue file contents */
105 
106 static void postcat(VSTREAM *fp, VSTRING *buffer, int flags)
107 {
108     int     prev_type = 0;
109     int     rec_type;
110     struct timeval tv;
111     time_t  time;
112     int     first = 1;
113     int     ch;
114     off_t   offset;
115     int     in_message = 0;
116     const char *error_text;
117     char   *attr_name;
118     char   *attr_value;
119     int     rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT);
120 
121 #define TEXT_RECORD(rec_type) \
122 	    (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM)
123 
124     /*
125      * See if this is a plausible file.
126      */
127     if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) {
128 	if (!strchr(REC_TYPE_ENVELOPE, ch)) {
129 	    msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp));
130 	    return;
131 	}
132 	vstream_ungetc(fp, ch);
133     }
134 
135     /*
136      * Now look at the rest.
137      */
138     do {
139 	if (flags & PC_FLAG_OFFSET)
140 	    offset = vstream_ftell(fp);
141 	rec_type = rec_get_raw(fp, buffer, 0, rec_flags);
142 	if (rec_type == REC_TYPE_ERROR)
143 	    msg_fatal("record read error");
144 	if (rec_type == REC_TYPE_EOF)
145 	    break;
146 	if (first == 1) {
147 	    vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp));
148 	    first = 0;
149 	}
150 	if (prev_type == REC_TYPE_CONT && !TEXT_RECORD(rec_type))
151 	    VSTREAM_PUTCHAR('\n');
152 	if (flags & PC_FLAG_OFFSET)
153 	    vstream_printf("%9lu ", (unsigned long) offset);
154 	switch (rec_type) {
155 	case REC_TYPE_TIME:
156 	    REC_TYPE_TIME_SCAN(STR(buffer), tv);
157 	    time = tv.tv_sec;
158 	    vstream_printf("%s: %s", rec_type_name(rec_type),
159 			   asctime(localtime(&time)));
160 	    break;
161 	case REC_TYPE_WARN:
162 	    REC_TYPE_WARN_SCAN(STR(buffer), time);
163 	    vstream_printf("%s: %s", rec_type_name(rec_type),
164 			   asctime(localtime(&time)));
165 	    break;
166 	case REC_TYPE_PTR:			/* pointer */
167 	    vstream_printf("%s: ", rec_type_name(rec_type));
168 	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
169 	    VSTREAM_PUTCHAR('\n');
170 	    if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR)
171 		msg_fatal("bad pointer record, or input is not seekable");
172 	    break;
173 	case REC_TYPE_CONT:			/* REC_TYPE_FILT collision */
174 	    if (!in_message)
175 		vstream_printf("%s: ", rec_type_name(rec_type));
176 	    else if (msg_verbose)
177 		vstream_printf("unterminated_text: ");
178 	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
179 	    if (!in_message || msg_verbose || (flags & PC_FLAG_OFFSET) != 0) {
180 		rec_type = 0;
181 		VSTREAM_PUTCHAR('\n');
182 	    }
183 	    break;
184 	case REC_TYPE_NORM:
185 	    if (msg_verbose)
186 		vstream_printf("%s: ", rec_type_name(rec_type));
187 	    vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
188 	    VSTREAM_PUTCHAR('\n');
189 	    break;
190 	case REC_TYPE_DTXT:
191 	    if (msg_verbose) {
192 		vstream_printf("%s: ", rec_type_name(rec_type));
193 		vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer));
194 		VSTREAM_PUTCHAR('\n');
195 	    }
196 	    break;
197 	case REC_TYPE_MESG:
198 	    vstream_printf("*** MESSAGE CONTENTS %s ***\n", VSTREAM_PATH(fp));
199 	    in_message = 1;
200 	    break;
201 	case REC_TYPE_XTRA:
202 	    vstream_printf("*** HEADER EXTRACTED %s ***\n", VSTREAM_PATH(fp));
203 	    in_message = 0;
204 	    break;
205 	case REC_TYPE_END:
206 	    vstream_printf("*** MESSAGE FILE END %s ***\n", VSTREAM_PATH(fp));
207 	    break;
208 	case REC_TYPE_ATTR:
209 	    error_text = split_nameval(STR(buffer), &attr_name, &attr_value);
210 	    if (error_text != 0) {
211 		msg_warn("%s: malformed attribute: %s: %.100s",
212 			 VSTREAM_PATH(fp), error_text, STR(buffer));
213 		break;
214 	    }
215 	    if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) {
216 		time = atol(attr_value);
217 		vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME,
218 			       asctime(localtime(&time)));
219 		break;
220 	    }
221 	    vstream_printf("%s: %s=%s\n", rec_type_name(rec_type),
222 			   attr_name, attr_value);
223 	    break;
224 	default:
225 	    vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer));
226 	    break;
227 	}
228 	prev_type = rec_type;
229 
230 	/*
231 	 * In case the next record is broken.
232 	 */
233 	vstream_fflush(VSTREAM_OUT);
234     } while (rec_type != REC_TYPE_END);
235 }
236 
237 /* usage - explain and terminate */
238 
239 static NORETURN usage(char *myname)
240 {
241     msg_fatal("usage: %s [-c config_dir] [-q (access queue)] [-v] [file(s)...]",
242 	      myname);
243 }
244 
245 MAIL_VERSION_STAMP_DECLARE;
246 
247 int     main(int argc, char **argv)
248 {
249     VSTRING *buffer;
250     VSTREAM *fp;
251     int     ch;
252     int     fd;
253     struct stat st;
254     int     flags = 0;
255     static char *queue_names[] = {
256 	MAIL_QUEUE_MAILDROP,
257 	MAIL_QUEUE_INCOMING,
258 	MAIL_QUEUE_ACTIVE,
259 	MAIL_QUEUE_DEFERRED,
260 	MAIL_QUEUE_HOLD,
261 	0,
262     };
263     char  **cpp;
264     int     tries;
265 
266     /*
267      * Fingerprint executables and core dumps.
268      */
269     MAIL_VERSION_STAMP_ALLOCATE;
270 
271     /*
272      * To minimize confusion, make sure that the standard file descriptors
273      * are open before opening anything else. XXX Work around for 44BSD where
274      * fstat can return EBADF on an open file descriptor.
275      */
276     for (fd = 0; fd < 3; fd++)
277 	if (fstat(fd, &st) == -1
278 	    && (close(fd), open("/dev/null", O_RDWR, 0)) != fd)
279 	    msg_fatal("open /dev/null: %m");
280 
281     /*
282      * Set up logging.
283      */
284     msg_vstream_init(argv[0], VSTREAM_ERR);
285 
286     /*
287      * Parse JCL.
288      */
289     while ((ch = GETOPT(argc, argv, "c:oqv")) > 0) {
290 	switch (ch) {
291 	case 'c':
292 	    if (setenv(CONF_ENV_PATH, optarg, 1) < 0)
293 		msg_fatal("out of memory");
294 	    break;
295 	case 'o':
296 	    flags |= PC_FLAG_OFFSET;
297 	    break;
298 	case 'q':
299 	    flags |= PC_FLAG_QUEUE;
300 	    break;
301 	case 'v':
302 	    msg_verbose++;
303 	    break;
304 	default:
305 	    usage(argv[0]);
306 	}
307     }
308 
309     /*
310      * Further initialization...
311      */
312     mail_conf_read();
313 
314     /*
315      * Initialize.
316      */
317     buffer = vstring_alloc(10);
318 
319     /*
320      * If no file names are given, copy stdin.
321      */
322     if (argc == optind) {
323 	vstream_control(VSTREAM_IN,
324 			VSTREAM_CTL_PATH, "stdin",
325 			VSTREAM_CTL_END);
326 	postcat(VSTREAM_IN, buffer, flags);
327     }
328 
329     /*
330      * Copy the named queue files in the specified order.
331      */
332     else if (flags & PC_FLAG_QUEUE) {
333 	if (chdir(var_queue_dir))
334 	    msg_fatal("chdir %s: %m", var_queue_dir);
335 	while (optind < argc) {
336 	    if (!mail_queue_id_ok(argv[optind]))
337 		msg_fatal("bad mail queue ID: %s", argv[optind]);
338 	    for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++)
339 		for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++)
340 		    fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0);
341 	    if (fp == 0)
342 		msg_fatal("open queue file %s: %m", argv[optind]);
343 	    postcat(fp, buffer, flags);
344 	    if (vstream_fclose(fp))
345 		msg_warn("close %s: %m", argv[optind]);
346 	    optind++;
347 	}
348     }
349 
350     /*
351      * Copy the named files in the specified order.
352      */
353     else {
354 	while (optind < argc) {
355 	    if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0)
356 		msg_fatal("open %s: %m", argv[optind]);
357 	    postcat(fp, buffer, flags);
358 	    if (vstream_fclose(fp))
359 		msg_warn("close %s: %m", argv[optind]);
360 	    optind++;
361 	}
362     }
363 
364     /*
365      * Clean up.
366      */
367     vstring_free(buffer);
368     exit(0);
369 }
370