1/*++
2/* NAME
3/*	showq 8
4/* SUMMARY
5/*	list the Postfix mail queue
6/* SYNOPSIS
7/*	\fBshowq\fR [generic Postfix daemon options]
8/* DESCRIPTION
9/*	The \fBshowq\fR(8) daemon reports the Postfix mail queue status.
10/*	It is the program that emulates the sendmail `mailq' command.
11/*
12/*	The \fBshowq\fR(8) daemon can also be run in stand-alone mode
13/*	by the superuser. This mode of operation is used to emulate
14/*	the `mailq' command while the Postfix mail system is down.
15/* SECURITY
16/* .ad
17/* .fi
18/*	The \fBshowq\fR(8) daemon can run in a chroot jail at fixed low
19/*	privilege, and takes no input from the client. Its service port
20/*	is accessible to local untrusted users, so the service can be
21/*	susceptible to denial of service attacks.
22/* STANDARDS
23/* .ad
24/* .fi
25/*	None. The \fBshowq\fR(8) daemon does not interact with the
26/*	outside world.
27/* DIAGNOSTICS
28/*	Problems and transactions are logged to \fBsyslogd\fR(8).
29/* CONFIGURATION PARAMETERS
30/* .ad
31/* .fi
32/*	Changes to \fBmain.cf\fR are picked up automatically as \fBshowq\fR(8)
33/*	processes run for only a limited amount of time. Use the command
34/*	"\fBpostfix reload\fR" to speed up a change.
35/*
36/*	The text below provides only a parameter summary. See
37/*	\fBpostconf\fR(5) for more details including examples.
38/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
39/*	The default location of the Postfix main.cf and master.cf
40/*	configuration files.
41/* .IP "\fBdaemon_timeout (18000s)\fR"
42/*	How much time a Postfix daemon process may take to handle a
43/*	request before it is terminated by a built-in watchdog timer.
44/* .IP "\fBduplicate_filter_limit (1000)\fR"
45/*	The maximal number of addresses remembered by the address
46/*	duplicate filter for \fBaliases\fR(5) or \fBvirtual\fR(5) alias expansion, or
47/*	for \fBshowq\fR(8) queue displays.
48/* .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
49/*	The recipient of mail addressed to the null address.
50/* .IP "\fBipc_timeout (3600s)\fR"
51/*	The time limit for sending or receiving information over an internal
52/*	communication channel.
53/* .IP "\fBmax_idle (100s)\fR"
54/*	The maximum amount of time that an idle Postfix daemon process waits
55/*	for an incoming connection before terminating voluntarily.
56/* .IP "\fBmax_use (100)\fR"
57/*	The maximal number of incoming connections that a Postfix daemon
58/*	process will service before terminating voluntarily.
59/* .IP "\fBprocess_id (read-only)\fR"
60/*	The process ID of a Postfix command or daemon process.
61/* .IP "\fBprocess_name (read-only)\fR"
62/*	The process name of a Postfix command or daemon process.
63/* .IP "\fBqueue_directory (see 'postconf -d' output)\fR"
64/*	The location of the Postfix top-level queue directory.
65/* .IP "\fBsyslog_facility (mail)\fR"
66/*	The syslog facility of Postfix logging.
67/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
68/*	The mail system name that is prepended to the process name in syslog
69/*	records, so that "smtpd" becomes, for example, "postfix/smtpd".
70/* .PP
71/*	Available in Postfix version 2.9 and later:
72/* .IP "\fBenable_long_queue_ids (no)\fR"
73/*	Enable long, non-repeating, queue IDs (queue file names).
74/* FILES
75/*	/var/spool/postfix, queue directories
76/* SEE ALSO
77/*	pickup(8), local mail pickup service
78/*	cleanup(8), canonicalize and enqueue mail
79/*	qmgr(8), queue manager
80/*	postconf(5), configuration parameters
81/*	master(8), process manager
82/*	syslogd(8), system logging
83/* LICENSE
84/* .ad
85/* .fi
86/*	The Secure Mailer license must be distributed with this software.
87/* AUTHOR(S)
88/*	Wietse Venema
89/*	IBM T.J. Watson Research
90/*	P.O. Box 704
91/*	Yorktown Heights, NY 10598, USA
92/*--*/
93
94/* System library. */
95
96#include <sys_defs.h>
97#include <sys/stat.h>
98#include <dirent.h>
99#include <stdlib.h>
100#include <unistd.h>
101#include <errno.h>
102#include <fcntl.h>
103#include <time.h>
104#include <string.h>
105#include <ctype.h>
106
107/* Utility library. */
108
109#include <msg.h>
110#include <scan_dir.h>
111#include <vstring.h>
112#include <vstream.h>
113#include <vstring_vstream.h>
114#include <stringops.h>
115#include <mymalloc.h>
116#include <htable.h>
117
118/* Global library. */
119
120#include <mail_queue.h>
121#include <mail_open_ok.h>
122#include <mail_proto.h>
123#include <mail_date.h>
124#include <mail_params.h>
125#include <mail_version.h>
126#include <mail_scan_dir.h>
127#include <mail_conf.h>
128#include <record.h>
129#include <rec_type.h>
130#include <quote_822_local.h>
131#include <mail_addr.h>
132#include <bounce_log.h>
133
134/* Single-threaded server skeleton. */
135
136#include <mail_server.h>
137
138/* Application-specific. */
139
140int     var_dup_filter_limit;
141char   *var_empty_addr;
142
143#define S_STRING_FORMAT	"%-10s %8s %-20s %s\n"
144#define S_SENDER_FORMAT	"%-11s %7ld %20.20s %s\n"
145#define S_DROP_FORMAT	"%-10s%c %7ld %20.20s (maildrop queue, sender UID %u)\n"
146#define S_HEADINGS	"-Queue ID-", "--Size--", \
147			    "----Arrival Time----", "-Sender/Recipient-------"
148
149#define L_STRING_FORMAT	"%-17s %8s %-19s %s\n"
150#define L_SENDER_FORMAT	"%-17s %8ld %19.19s %s\n"
151#define L_DROP_FORMAT	"%-16s%c %8ld %19.19s (maildrop queue, sender UID %u)\n"
152#define L_HEADINGS	"----Queue ID-----", "--Size--", \
153			    "---Arrival Time----", "--Sender/Recipient------"
154
155static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
156			          HTABLE *);
157
158#define STR(x)	vstring_str(x)
159
160/* showq_report - report status of sender and recipients */
161
162static void showq_report(VSTREAM *client, char *queue, char *id,
163			         VSTREAM *qfile, long size, time_t mtime)
164{
165    VSTRING *buf = vstring_alloc(100);
166    VSTRING *printable_quoted_addr = vstring_alloc(100);
167    int     rec_type;
168    time_t  arrival_time = 0;
169    char   *start;
170    long    msg_size = 0;
171    BOUNCE_LOG *logfile;
172    HTABLE *dup_filter = 0;
173    RCPT_BUF *rcpt_buf = 0;
174    DSN_BUF *dsn_buf = 0;
175    char    status = (strcmp(queue, MAIL_QUEUE_ACTIVE) == 0 ? '*' :
176		      strcmp(queue, MAIL_QUEUE_HOLD) == 0 ? '!' : ' ');
177    int     msg_size_ok = 0;
178
179    /*
180     * XXX addresses in defer logfiles are in printable quoted form, while
181     * addresses in message envelope records are in raw unquoted form. This
182     * may change once we replace the present ad-hoc bounce/defer logfile
183     * format by one that is transparent for control etc. characters. See
184     * also: bounce/bounce_append_service.c.
185     *
186     * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
187     * from previous cleanup runs. Skip the obsolete size records.
188     */
189    while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
190	start = vstring_str(buf);
191	if (msg_verbose)
192	    msg_info("record %c %s", rec_type, printable(start, '?'));
193	switch (rec_type) {
194	case REC_TYPE_TIME:
195	    arrival_time = atol(start);
196	    break;
197	case REC_TYPE_SIZE:
198	    if (msg_size == 0) {
199		if ((msg_size_ok = ((msg_size = atol(start)) > 0)) == 0) {
200		    msg_warn("%s: malformed size record: %.100s",
201			     id, printable(start, '?'));
202		    msg_size = size;
203		}
204	    }
205	    break;
206	case REC_TYPE_FROM:
207	    if (*start == 0)
208		start = var_empty_addr;
209	    quote_822_local(printable_quoted_addr, start);
210	    printable(STR(printable_quoted_addr), '?');
211	    /* quote_822_local() saves buf, so we can reuse its space. */
212	    vstring_sprintf(buf, "%s%c", id, status);
213	    vstream_fprintf(client, var_long_queue_ids ?
214			    L_SENDER_FORMAT : S_SENDER_FORMAT, STR(buf),
215			  msg_size > 0 ? msg_size : size, arrival_time > 0 ?
216			    asctime(localtime(&arrival_time)) :
217			    asctime(localtime(&mtime)),
218			    STR(printable_quoted_addr));
219	    break;
220	case REC_TYPE_RCPT:
221	    if (*start == 0)			/* can't happen? */
222		start = var_empty_addr;
223	    quote_822_local(printable_quoted_addr, start);
224	    printable(STR(printable_quoted_addr), '?');
225	    if (dup_filter == 0
226	      || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
227		vstream_fprintf(client, var_long_queue_ids ?
228				L_STRING_FORMAT : S_STRING_FORMAT,
229				"", "", "", STR(printable_quoted_addr));
230	    break;
231	case REC_TYPE_MESG:
232	    if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
233		msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
234	    break;
235	case REC_TYPE_END:
236	    break;
237	}
238
239	/*
240	 * With the heading printed, see if there is a defer logfile. The
241	 * defer logfile is not necessarily complete: delivery may be
242	 * interrupted (postfix stop or reload) before all recipients have
243	 * been tried.
244	 *
245	 * Therefore we keep a record of recipients found in the defer logfile,
246	 * and try to avoid listing those recipients again when processing
247	 * the remainder of the queue file.
248	 */
249	if (rec_type == REC_TYPE_FROM
250	    && dup_filter == 0
251	    && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
252	    dup_filter = htable_create(var_dup_filter_limit);
253	    if (rcpt_buf == 0)
254		rcpt_buf = rcpb_create();
255	    if (dsn_buf == 0)
256		dsn_buf = dsb_create();
257	    showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
258	    if (bounce_log_close(logfile))
259		msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
260	}
261    }
262    vstring_free(buf);
263    vstring_free(printable_quoted_addr);
264    if (rcpt_buf)
265	rcpb_free(rcpt_buf);
266    if (dsn_buf)
267	dsb_free(dsn_buf);
268    if (dup_filter)
269	htable_free(dup_filter, (void (*) (char *)) 0);
270}
271
272/* showq_reasons - show deferral reasons */
273
274static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
275			          DSN_BUF *dsn_buf, HTABLE *dup_filter)
276{
277    char   *saved_reason = 0;
278    int     padding;
279    RECIPIENT *rcpt = &rcpt_buf->rcpt;
280    DSN    *dsn = &dsn_buf->dsn;
281
282    while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
283
284	/*
285	 * Update the duplicate filter.
286	 */
287	if (var_dup_filter_limit == 0
288	    || dup_filter->used < var_dup_filter_limit)
289	    if (htable_locate(dup_filter, rcpt->address) == 0)
290		htable_enter(dup_filter, rcpt->address, (char *) 0);
291
292	/*
293	 * Don't print the reason when the previous recipient had the same
294	 * problem.
295	 */
296	if (saved_reason == 0 || strcmp(saved_reason, dsn->reason) != 0) {
297	    if (saved_reason)
298		myfree(saved_reason);
299	    saved_reason = mystrdup(dsn->reason);
300	    if ((padding = 76 - strlen(saved_reason)) < 0)
301		padding = 0;
302	    vstream_fprintf(client, "%*s(%s)\n", padding, "", saved_reason);
303	}
304	vstream_fprintf(client, var_long_queue_ids ? L_STRING_FORMAT :
305			S_STRING_FORMAT, "", "", "", rcpt->address);
306    }
307    if (saved_reason)
308	myfree(saved_reason);
309}
310
311
312/* showq_service - service client */
313
314static void showq_service(VSTREAM *client, char *unused_service, char **argv)
315{
316    VSTREAM *qfile;
317    const char *path;
318    int     status;
319    char   *id;
320    int     file_count;
321    unsigned long queue_size = 0;
322    struct stat st;
323    struct queue_info {
324	char   *name;			/* queue name */
325	char   *(*scan_next) (SCAN_DIR *);	/* flat or recursive */
326    };
327    struct queue_info *qp;
328
329    static struct queue_info queue_info[] = {
330	MAIL_QUEUE_MAILDROP, scan_dir_next,
331	MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
332	MAIL_QUEUE_INCOMING, mail_scan_dir_next,
333	MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
334	MAIL_QUEUE_HOLD, mail_scan_dir_next,
335	0,
336    };
337
338    /*
339     * Sanity check. This service takes no command-line arguments.
340     */
341    if (argv[0])
342	msg_fatal("unexpected command-line argument: %s", argv[0]);
343
344    /*
345     * Skip any files that have the wrong permissions. If we can't open an
346     * existing file, assume the system is out of resources or that it is
347     * mis-configured, and force backoff by raising a fatal error.
348     */
349    file_count = 0;
350    for (qp = queue_info; qp->name != 0; qp++) {
351	SCAN_DIR *scan = scan_dir_open(qp->name);
352	char   *saved_id = 0;
353
354	while ((id = qp->scan_next(scan)) != 0) {
355
356	    /*
357	     * XXX I have seen showq loop on the same queue id. That would be
358	     * an operating system bug, but who cares whose fault it is. Make
359	     * sure this will never happen again.
360	     */
361	    if (saved_id) {
362		if (strcmp(saved_id, id) == 0) {
363		    msg_warn("readdir loop on queue %s id %s", qp->name, id);
364		    break;
365		}
366		myfree(saved_id);
367	    }
368	    saved_id = mystrdup(id);
369	    status = mail_open_ok(qp->name, id, &st, &path);
370	    if (status == MAIL_OPEN_YES) {
371		if (file_count == 0) {
372		    if (var_long_queue_ids)
373			vstream_fprintf(client, L_STRING_FORMAT, L_HEADINGS);
374		    else
375			vstream_fprintf(client, S_STRING_FORMAT, S_HEADINGS);
376		} else
377		    vstream_fprintf(client, "\n");
378		if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
379		    queue_size += st.st_size;
380		    showq_report(client, qp->name, id, qfile, (long) st.st_size,
381				 st.st_mtime);
382		    if (vstream_fclose(qfile))
383			msg_warn("close file %s %s: %m", qp->name, id);
384		} else if (strcmp(qp->name, MAIL_QUEUE_MAILDROP) == 0) {
385		    queue_size += st.st_size;
386		    vstream_fprintf(client, var_long_queue_ids ?
387				    L_DROP_FORMAT : S_DROP_FORMAT, id, ' ',
388				    (long) st.st_size,
389				    asctime(localtime(&st.st_mtime)),
390				    (unsigned) st.st_uid);
391		} else if (errno != ENOENT)
392		    msg_fatal("open %s %s: %m", qp->name, id);
393		file_count++;
394		vstream_fflush(client);
395	    }
396	    vstream_fflush(client);
397	}
398	if (saved_id)
399	    myfree(saved_id);
400	scan_dir_close(scan);
401    }
402    if (file_count == 0)
403	vstream_fprintf(client, "Mail queue is empty\n");
404    else {
405	vstream_fprintf(client, "\n-- %lu Kbytes in %d Request%s.\n",
406			queue_size / 1024, file_count,
407			file_count == 1 ? "" : "s");
408    }
409}
410
411MAIL_VERSION_STAMP_DECLARE;
412
413/* main - pass control to the single-threaded server skeleton */
414
415int     main(int argc, char **argv)
416{
417    static const CONFIG_INT_TABLE int_table[] = {
418	VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
419	0,
420    };
421    CONFIG_STR_TABLE str_table[] = {
422	VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
423	0,
424    };
425
426    /*
427     * Fingerprint executables and core dumps.
428     */
429    MAIL_VERSION_STAMP_ALLOCATE;
430
431    single_server_main(argc, argv, showq_service,
432		       MAIL_SERVER_INT_TABLE, int_table,
433		       MAIL_SERVER_STR_TABLE, str_table,
434		       0);
435}
436