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