xref: /netbsd-src/external/ibm-public/postfix/dist/src/showq/showq.c (revision d909946ca08dceb44d7d0f22ec9488679695d976)
1 /*	$NetBSD: showq.c,v 1.1.1.2 2013/01/02 18:59:07 tron Exp $	*/
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 /* .PP
73 /*	Available in Postfix version 2.9 and later:
74 /* .IP "\fBenable_long_queue_ids (no)\fR"
75 /*	Enable long, non-repeating, queue IDs (queue file names).
76 /* FILES
77 /*	/var/spool/postfix, queue directories
78 /* SEE ALSO
79 /*	pickup(8), local mail pickup service
80 /*	cleanup(8), canonicalize and enqueue mail
81 /*	qmgr(8), queue manager
82 /*	postconf(5), configuration parameters
83 /*	master(8), process manager
84 /*	syslogd(8), system logging
85 /* LICENSE
86 /* .ad
87 /* .fi
88 /*	The Secure Mailer license must be distributed with this software.
89 /* AUTHOR(S)
90 /*	Wietse Venema
91 /*	IBM T.J. Watson Research
92 /*	P.O. Box 704
93 /*	Yorktown Heights, NY 10598, USA
94 /*--*/
95 
96 /* System library. */
97 
98 #include <sys_defs.h>
99 #include <sys/stat.h>
100 #include <dirent.h>
101 #include <stdlib.h>
102 #include <unistd.h>
103 #include <errno.h>
104 #include <fcntl.h>
105 #include <time.h>
106 #include <string.h>
107 #include <ctype.h>
108 
109 /* Utility library. */
110 
111 #include <msg.h>
112 #include <scan_dir.h>
113 #include <vstring.h>
114 #include <vstream.h>
115 #include <vstring_vstream.h>
116 #include <stringops.h>
117 #include <mymalloc.h>
118 #include <htable.h>
119 
120 /* Global library. */
121 
122 #include <mail_queue.h>
123 #include <mail_open_ok.h>
124 #include <mail_proto.h>
125 #include <mail_date.h>
126 #include <mail_params.h>
127 #include <mail_version.h>
128 #include <mail_scan_dir.h>
129 #include <mail_conf.h>
130 #include <record.h>
131 #include <rec_type.h>
132 #include <quote_822_local.h>
133 #include <mail_addr.h>
134 #include <bounce_log.h>
135 
136 /* Single-threaded server skeleton. */
137 
138 #include <mail_server.h>
139 
140 /* Application-specific. */
141 
142 int     var_dup_filter_limit;
143 char   *var_empty_addr;
144 
145 #define S_STRING_FORMAT	"%-10s %8s %-20s %s\n"
146 #define S_SENDER_FORMAT	"%-11s %7ld %20.20s %s\n"
147 #define S_DROP_FORMAT	"%-10s%c %7ld %20.20s (maildrop queue, sender UID %u)\n"
148 #define S_HEADINGS	"-Queue ID-", "--Size--", \
149 			    "----Arrival Time----", "-Sender/Recipient-------"
150 
151 #define L_STRING_FORMAT	"%-17s %8s %-19s %s\n"
152 #define L_SENDER_FORMAT	"%-17s %8ld %19.19s %s\n"
153 #define L_DROP_FORMAT	"%-16s%c %8ld %19.19s (maildrop queue, sender UID %u)\n"
154 #define L_HEADINGS	"----Queue ID-----", "--Size--", \
155 			    "---Arrival Time----", "--Sender/Recipient------"
156 
157 static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
158 			          HTABLE *);
159 
160 #define STR(x)	vstring_str(x)
161 
162 /* showq_report - report status of sender and recipients */
163 
164 static void showq_report(VSTREAM *client, char *queue, char *id,
165 			         VSTREAM *qfile, long size, time_t mtime)
166 {
167     VSTRING *buf = vstring_alloc(100);
168     VSTRING *printable_quoted_addr = vstring_alloc(100);
169     int     rec_type;
170     time_t  arrival_time = 0;
171     char   *start;
172     long    msg_size = 0;
173     BOUNCE_LOG *logfile;
174     HTABLE *dup_filter = 0;
175     RCPT_BUF *rcpt_buf = 0;
176     DSN_BUF *dsn_buf = 0;
177     char    status = (strcmp(queue, MAIL_QUEUE_ACTIVE) == 0 ? '*' :
178 		      strcmp(queue, MAIL_QUEUE_HOLD) == 0 ? '!' : ' ');
179     int     msg_size_ok = 0;
180 
181     /*
182      * XXX addresses in defer logfiles are in printable quoted form, while
183      * addresses in message envelope records are in raw unquoted form. This
184      * may change once we replace the present ad-hoc bounce/defer logfile
185      * format by one that is transparent for control etc. characters. See
186      * also: bounce/bounce_append_service.c.
187      *
188      * XXX With Postfix <= 2.0, "postsuper -r" results in obsolete size records
189      * from previous cleanup runs. Skip the obsolete size records.
190      */
191     while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) {
192 	start = vstring_str(buf);
193 	if (msg_verbose)
194 	    msg_info("record %c %s", rec_type, printable(start, '?'));
195 	switch (rec_type) {
196 	case REC_TYPE_TIME:
197 	    arrival_time = atol(start);
198 	    break;
199 	case REC_TYPE_SIZE:
200 	    if (msg_size == 0) {
201 		if ((msg_size_ok = ((msg_size = atol(start)) > 0)) == 0) {
202 		    msg_warn("%s: malformed size record: %.100s",
203 			     id, printable(start, '?'));
204 		    msg_size = size;
205 		}
206 	    }
207 	    break;
208 	case REC_TYPE_FROM:
209 	    if (*start == 0)
210 		start = var_empty_addr;
211 	    quote_822_local(printable_quoted_addr, start);
212 	    printable(STR(printable_quoted_addr), '?');
213 	    /* quote_822_local() saves buf, so we can reuse its space. */
214 	    vstring_sprintf(buf, "%s%c", id, status);
215 	    vstream_fprintf(client, var_long_queue_ids ?
216 			    L_SENDER_FORMAT : S_SENDER_FORMAT, STR(buf),
217 			  msg_size > 0 ? msg_size : size, arrival_time > 0 ?
218 			    asctime(localtime(&arrival_time)) :
219 			    asctime(localtime(&mtime)),
220 			    STR(printable_quoted_addr));
221 	    break;
222 	case REC_TYPE_RCPT:
223 	    if (*start == 0)			/* can't happen? */
224 		start = var_empty_addr;
225 	    quote_822_local(printable_quoted_addr, start);
226 	    printable(STR(printable_quoted_addr), '?');
227 	    if (dup_filter == 0
228 	      || htable_locate(dup_filter, STR(printable_quoted_addr)) == 0)
229 		vstream_fprintf(client, var_long_queue_ids ?
230 				L_STRING_FORMAT : S_STRING_FORMAT,
231 				"", "", "", STR(printable_quoted_addr));
232 	    break;
233 	case REC_TYPE_MESG:
234 	    if (msg_size_ok && vstream_fseek(qfile, msg_size, SEEK_CUR) < 0)
235 		msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile));
236 	    break;
237 	case REC_TYPE_END:
238 	    break;
239 	}
240 
241 	/*
242 	 * With the heading printed, see if there is a defer logfile. The
243 	 * defer logfile is not necessarily complete: delivery may be
244 	 * interrupted (postfix stop or reload) before all recipients have
245 	 * been tried.
246 	 *
247 	 * Therefore we keep a record of recipients found in the defer logfile,
248 	 * and try to avoid listing those recipients again when processing
249 	 * the remainder of the queue file.
250 	 */
251 	if (rec_type == REC_TYPE_FROM
252 	    && dup_filter == 0
253 	    && (logfile = bounce_log_open(MAIL_QUEUE_DEFER, id, O_RDONLY, 0)) != 0) {
254 	    dup_filter = htable_create(var_dup_filter_limit);
255 	    if (rcpt_buf == 0)
256 		rcpt_buf = rcpb_create();
257 	    if (dsn_buf == 0)
258 		dsn_buf = dsb_create();
259 	    showq_reasons(client, logfile, rcpt_buf, dsn_buf, dup_filter);
260 	    if (bounce_log_close(logfile))
261 		msg_warn("close %s %s: %m", MAIL_QUEUE_DEFER, id);
262 	}
263     }
264     vstring_free(buf);
265     vstring_free(printable_quoted_addr);
266     if (rcpt_buf)
267 	rcpb_free(rcpt_buf);
268     if (dsn_buf)
269 	dsb_free(dsn_buf);
270     if (dup_filter)
271 	htable_free(dup_filter, (void (*) (char *)) 0);
272 }
273 
274 /* showq_reasons - show deferral reasons */
275 
276 static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf,
277 			          DSN_BUF *dsn_buf, HTABLE *dup_filter)
278 {
279     char   *saved_reason = 0;
280     int     padding;
281     RECIPIENT *rcpt = &rcpt_buf->rcpt;
282     DSN    *dsn = &dsn_buf->dsn;
283 
284     while (bounce_log_read(bp, rcpt_buf, dsn_buf) != 0) {
285 
286 	/*
287 	 * Update the duplicate filter.
288 	 */
289 	if (var_dup_filter_limit == 0
290 	    || dup_filter->used < var_dup_filter_limit)
291 	    if (htable_locate(dup_filter, rcpt->address) == 0)
292 		htable_enter(dup_filter, rcpt->address, (char *) 0);
293 
294 	/*
295 	 * Don't print the reason when the previous recipient had the same
296 	 * problem.
297 	 */
298 	if (saved_reason == 0 || strcmp(saved_reason, dsn->reason) != 0) {
299 	    if (saved_reason)
300 		myfree(saved_reason);
301 	    saved_reason = mystrdup(dsn->reason);
302 	    if ((padding = 76 - strlen(saved_reason)) < 0)
303 		padding = 0;
304 	    vstream_fprintf(client, "%*s(%s)\n", padding, "", saved_reason);
305 	}
306 	vstream_fprintf(client, var_long_queue_ids ? L_STRING_FORMAT :
307 			S_STRING_FORMAT, "", "", "", rcpt->address);
308     }
309     if (saved_reason)
310 	myfree(saved_reason);
311 }
312 
313 
314 /* showq_service - service client */
315 
316 static void showq_service(VSTREAM *client, char *unused_service, char **argv)
317 {
318     VSTREAM *qfile;
319     const char *path;
320     int     status;
321     char   *id;
322     int     file_count;
323     unsigned long queue_size = 0;
324     struct stat st;
325     struct queue_info {
326 	char   *name;			/* queue name */
327 	char   *(*scan_next) (SCAN_DIR *);	/* flat or recursive */
328     };
329     struct queue_info *qp;
330 
331     static struct queue_info queue_info[] = {
332 	MAIL_QUEUE_MAILDROP, scan_dir_next,
333 	MAIL_QUEUE_ACTIVE, mail_scan_dir_next,
334 	MAIL_QUEUE_INCOMING, mail_scan_dir_next,
335 	MAIL_QUEUE_DEFERRED, mail_scan_dir_next,
336 	MAIL_QUEUE_HOLD, mail_scan_dir_next,
337 	0,
338     };
339 
340     /*
341      * Sanity check. This service takes no command-line arguments.
342      */
343     if (argv[0])
344 	msg_fatal("unexpected command-line argument: %s", argv[0]);
345 
346     /*
347      * Skip any files that have the wrong permissions. If we can't open an
348      * existing file, assume the system is out of resources or that it is
349      * mis-configured, and force backoff by raising a fatal error.
350      */
351     file_count = 0;
352     for (qp = queue_info; qp->name != 0; qp++) {
353 	SCAN_DIR *scan = scan_dir_open(qp->name);
354 	char   *saved_id = 0;
355 
356 	while ((id = qp->scan_next(scan)) != 0) {
357 
358 	    /*
359 	     * XXX I have seen showq loop on the same queue id. That would be
360 	     * an operating system bug, but who cares whose fault it is. Make
361 	     * sure this will never happen again.
362 	     */
363 	    if (saved_id) {
364 		if (strcmp(saved_id, id) == 0) {
365 		    msg_warn("readdir loop on queue %s id %s", qp->name, id);
366 		    break;
367 		}
368 		myfree(saved_id);
369 	    }
370 	    saved_id = mystrdup(id);
371 	    status = mail_open_ok(qp->name, id, &st, &path);
372 	    if (status == MAIL_OPEN_YES) {
373 		if (file_count == 0) {
374 		    if (var_long_queue_ids)
375 			vstream_fprintf(client, L_STRING_FORMAT, L_HEADINGS);
376 		    else
377 			vstream_fprintf(client, S_STRING_FORMAT, S_HEADINGS);
378 		} else
379 		    vstream_fprintf(client, "\n");
380 		if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
381 		    queue_size += st.st_size;
382 		    showq_report(client, qp->name, id, qfile, (long) st.st_size,
383 				 st.st_mtime);
384 		    if (vstream_fclose(qfile))
385 			msg_warn("close file %s %s: %m", qp->name, id);
386 		} else if (strcmp(qp->name, MAIL_QUEUE_MAILDROP) == 0) {
387 		    queue_size += st.st_size;
388 		    vstream_fprintf(client, var_long_queue_ids ?
389 				    L_DROP_FORMAT : S_DROP_FORMAT, id, ' ',
390 				    (long) st.st_size,
391 				    asctime(localtime(&st.st_mtime)),
392 				    (unsigned) st.st_uid);
393 		} else if (errno != ENOENT)
394 		    msg_fatal("open %s %s: %m", qp->name, id);
395 		file_count++;
396 		vstream_fflush(client);
397 	    }
398 	    vstream_fflush(client);
399 	}
400 	if (saved_id)
401 	    myfree(saved_id);
402 	scan_dir_close(scan);
403     }
404     if (file_count == 0)
405 	vstream_fprintf(client, "Mail queue is empty\n");
406     else {
407 	vstream_fprintf(client, "\n-- %lu Kbytes in %d Request%s.\n",
408 			queue_size / 1024, file_count,
409 			file_count == 1 ? "" : "s");
410     }
411 }
412 
413 MAIL_VERSION_STAMP_DECLARE;
414 
415 /* main - pass control to the single-threaded server skeleton */
416 
417 int     main(int argc, char **argv)
418 {
419     static const CONFIG_INT_TABLE int_table[] = {
420 	VAR_DUP_FILTER_LIMIT, DEF_DUP_FILTER_LIMIT, &var_dup_filter_limit, 0, 0,
421 	0,
422     };
423     CONFIG_STR_TABLE str_table[] = {
424 	VAR_EMPTY_ADDR, DEF_EMPTY_ADDR, &var_empty_addr, 1, 0,
425 	0,
426     };
427 
428     /*
429      * Fingerprint executables and core dumps.
430      */
431     MAIL_VERSION_STAMP_ALLOCATE;
432 
433     single_server_main(argc, argv, showq_service,
434 		       MAIL_SERVER_INT_TABLE, int_table,
435 		       MAIL_SERVER_STR_TABLE, str_table,
436 		       0);
437 }
438