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