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