1 /* $NetBSD: postcat.c,v 1.1.1.1 2009/06/23 10:08:51 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* postcat 1 6 /* SUMMARY 7 /* show Postfix queue file contents 8 /* SYNOPSIS 9 /* \fBpostcat\fR [\fB-oqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...] 10 /* DESCRIPTION 11 /* The \fBpostcat\fR(1) command prints the contents of the named 12 /* \fIfiles\fR in human-readable form. The files are expected 13 /* to be in Postfix queue file format. If no 14 /* \fIfiles\fR are specified on the command line, the program 15 /* reads from standard input. 16 /* 17 /* Options: 18 /* .IP "\fB-c \fIconfig_dir\fR" 19 /* The \fBmain.cf\fR configuration file is in the named directory 20 /* instead of the default configuration directory. 21 /* .IP \fB-o\fR 22 /* Print the queue file offset of each record. 23 /* .IP \fB-q\fR 24 /* Search the Postfix queue for the named \fIfiles\fR instead 25 /* of taking the names literally. 26 /* 27 /* Available in Postfix version 2.0 and later. 28 /* .IP \fB-v\fR 29 /* Enable verbose logging for debugging purposes. Multiple \fB-v\fR 30 /* options make the software increasingly verbose. 31 /* DIAGNOSTICS 32 /* Problems are reported to the standard error stream. 33 /* ENVIRONMENT 34 /* .ad 35 /* .fi 36 /* .IP \fBMAIL_CONFIG\fR 37 /* Directory with Postfix configuration files. 38 /* CONFIGURATION PARAMETERS 39 /* .ad 40 /* .fi 41 /* The following \fBmain.cf\fR parameters are especially relevant to 42 /* this program. 43 /* 44 /* The text below provides only a parameter summary. See 45 /* \fBpostconf\fR(5) for more details including examples. 46 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 47 /* The default location of the Postfix main.cf and master.cf 48 /* configuration files. 49 /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" 50 /* The location of the Postfix top-level queue directory. 51 /* FILES 52 /* /var/spool/postfix, Postfix queue directory 53 /* SEE ALSO 54 /* postconf(5), Postfix configuration 55 /* LICENSE 56 /* .ad 57 /* .fi 58 /* The Secure Mailer license must be distributed with this software. 59 /* AUTHOR(S) 60 /* Wietse Venema 61 /* IBM T.J. Watson Research 62 /* P.O. Box 704 63 /* Yorktown Heights, NY 10598, USA 64 /*--*/ 65 66 /* System library. */ 67 68 #include <sys_defs.h> 69 #include <sys/stat.h> 70 #include <sys/time.h> 71 #include <stdlib.h> 72 #include <unistd.h> 73 #include <time.h> 74 #include <fcntl.h> 75 #include <string.h> 76 77 /* Utility library. */ 78 79 #include <msg.h> 80 #include <vstream.h> 81 #include <vstring.h> 82 #include <msg_vstream.h> 83 #include <vstring_vstream.h> 84 #include <stringops.h> 85 86 /* Global library. */ 87 88 #include <record.h> 89 #include <rec_type.h> 90 #include <mail_queue.h> 91 #include <mail_conf.h> 92 #include <mail_params.h> 93 #include <mail_version.h> 94 #include <mail_proto.h> 95 96 /* Application-specific. */ 97 98 #define PC_FLAG_QUEUE (1<<0) /* search queue */ 99 #define PC_FLAG_OFFSET (1<<1) /* print record offsets */ 100 101 #define STR vstring_str 102 #define LEN VSTRING_LEN 103 104 /* postcat - visualize Postfix queue file contents */ 105 106 static void postcat(VSTREAM *fp, VSTRING *buffer, int flags) 107 { 108 int prev_type = 0; 109 int rec_type; 110 struct timeval tv; 111 time_t time; 112 int first = 1; 113 int ch; 114 off_t offset; 115 int in_message = 0; 116 const char *error_text; 117 char *attr_name; 118 char *attr_value; 119 int rec_flags = (msg_verbose ? REC_FLAG_NONE : REC_FLAG_DEFAULT); 120 121 #define TEXT_RECORD(rec_type) \ 122 (rec_type == REC_TYPE_CONT || rec_type == REC_TYPE_NORM) 123 124 /* 125 * See if this is a plausible file. 126 */ 127 if ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { 128 if (!strchr(REC_TYPE_ENVELOPE, ch)) { 129 msg_warn("%s: input is not a valid queue file", VSTREAM_PATH(fp)); 130 return; 131 } 132 vstream_ungetc(fp, ch); 133 } 134 135 /* 136 * Now look at the rest. 137 */ 138 do { 139 if (flags & PC_FLAG_OFFSET) 140 offset = vstream_ftell(fp); 141 rec_type = rec_get_raw(fp, buffer, 0, rec_flags); 142 if (rec_type == REC_TYPE_ERROR) 143 msg_fatal("record read error"); 144 if (rec_type == REC_TYPE_EOF) 145 break; 146 if (first == 1) { 147 vstream_printf("*** ENVELOPE RECORDS %s ***\n", VSTREAM_PATH(fp)); 148 first = 0; 149 } 150 if (prev_type == REC_TYPE_CONT && !TEXT_RECORD(rec_type)) 151 VSTREAM_PUTCHAR('\n'); 152 if (flags & PC_FLAG_OFFSET) 153 vstream_printf("%9lu ", (unsigned long) offset); 154 switch (rec_type) { 155 case REC_TYPE_TIME: 156 REC_TYPE_TIME_SCAN(STR(buffer), tv); 157 time = tv.tv_sec; 158 vstream_printf("%s: %s", rec_type_name(rec_type), 159 asctime(localtime(&time))); 160 break; 161 case REC_TYPE_WARN: 162 REC_TYPE_WARN_SCAN(STR(buffer), time); 163 vstream_printf("%s: %s", rec_type_name(rec_type), 164 asctime(localtime(&time))); 165 break; 166 case REC_TYPE_PTR: /* pointer */ 167 vstream_printf("%s: ", rec_type_name(rec_type)); 168 vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); 169 VSTREAM_PUTCHAR('\n'); 170 if (rec_goto(fp, STR(buffer)) == REC_TYPE_ERROR) 171 msg_fatal("bad pointer record, or input is not seekable"); 172 break; 173 case REC_TYPE_CONT: /* REC_TYPE_FILT collision */ 174 if (!in_message) 175 vstream_printf("%s: ", rec_type_name(rec_type)); 176 else if (msg_verbose) 177 vstream_printf("unterminated_text: "); 178 vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); 179 if (!in_message || msg_verbose || (flags & PC_FLAG_OFFSET) != 0) { 180 rec_type = 0; 181 VSTREAM_PUTCHAR('\n'); 182 } 183 break; 184 case REC_TYPE_NORM: 185 if (msg_verbose) 186 vstream_printf("%s: ", rec_type_name(rec_type)); 187 vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); 188 VSTREAM_PUTCHAR('\n'); 189 break; 190 case REC_TYPE_DTXT: 191 if (msg_verbose) { 192 vstream_printf("%s: ", rec_type_name(rec_type)); 193 vstream_fwrite(VSTREAM_OUT, STR(buffer), LEN(buffer)); 194 VSTREAM_PUTCHAR('\n'); 195 } 196 break; 197 case REC_TYPE_MESG: 198 vstream_printf("*** MESSAGE CONTENTS %s ***\n", VSTREAM_PATH(fp)); 199 in_message = 1; 200 break; 201 case REC_TYPE_XTRA: 202 vstream_printf("*** HEADER EXTRACTED %s ***\n", VSTREAM_PATH(fp)); 203 in_message = 0; 204 break; 205 case REC_TYPE_END: 206 vstream_printf("*** MESSAGE FILE END %s ***\n", VSTREAM_PATH(fp)); 207 break; 208 case REC_TYPE_ATTR: 209 error_text = split_nameval(STR(buffer), &attr_name, &attr_value); 210 if (error_text != 0) { 211 msg_warn("%s: malformed attribute: %s: %.100s", 212 VSTREAM_PATH(fp), error_text, STR(buffer)); 213 break; 214 } 215 if (strcmp(attr_name, MAIL_ATTR_CREATE_TIME) == 0) { 216 time = atol(attr_value); 217 vstream_printf("%s: %s", MAIL_ATTR_CREATE_TIME, 218 asctime(localtime(&time))); 219 break; 220 } 221 vstream_printf("%s: %s=%s\n", rec_type_name(rec_type), 222 attr_name, attr_value); 223 break; 224 default: 225 vstream_printf("%s: %s\n", rec_type_name(rec_type), STR(buffer)); 226 break; 227 } 228 prev_type = rec_type; 229 230 /* 231 * In case the next record is broken. 232 */ 233 vstream_fflush(VSTREAM_OUT); 234 } while (rec_type != REC_TYPE_END); 235 } 236 237 /* usage - explain and terminate */ 238 239 static NORETURN usage(char *myname) 240 { 241 msg_fatal("usage: %s [-c config_dir] [-q (access queue)] [-v] [file(s)...]", 242 myname); 243 } 244 245 MAIL_VERSION_STAMP_DECLARE; 246 247 int main(int argc, char **argv) 248 { 249 VSTRING *buffer; 250 VSTREAM *fp; 251 int ch; 252 int fd; 253 struct stat st; 254 int flags = 0; 255 static char *queue_names[] = { 256 MAIL_QUEUE_MAILDROP, 257 MAIL_QUEUE_INCOMING, 258 MAIL_QUEUE_ACTIVE, 259 MAIL_QUEUE_DEFERRED, 260 MAIL_QUEUE_HOLD, 261 0, 262 }; 263 char **cpp; 264 int tries; 265 266 /* 267 * Fingerprint executables and core dumps. 268 */ 269 MAIL_VERSION_STAMP_ALLOCATE; 270 271 /* 272 * To minimize confusion, make sure that the standard file descriptors 273 * are open before opening anything else. XXX Work around for 44BSD where 274 * fstat can return EBADF on an open file descriptor. 275 */ 276 for (fd = 0; fd < 3; fd++) 277 if (fstat(fd, &st) == -1 278 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) 279 msg_fatal("open /dev/null: %m"); 280 281 /* 282 * Set up logging. 283 */ 284 msg_vstream_init(argv[0], VSTREAM_ERR); 285 286 /* 287 * Parse JCL. 288 */ 289 while ((ch = GETOPT(argc, argv, "c:oqv")) > 0) { 290 switch (ch) { 291 case 'c': 292 if (setenv(CONF_ENV_PATH, optarg, 1) < 0) 293 msg_fatal("out of memory"); 294 break; 295 case 'o': 296 flags |= PC_FLAG_OFFSET; 297 break; 298 case 'q': 299 flags |= PC_FLAG_QUEUE; 300 break; 301 case 'v': 302 msg_verbose++; 303 break; 304 default: 305 usage(argv[0]); 306 } 307 } 308 309 /* 310 * Further initialization... 311 */ 312 mail_conf_read(); 313 314 /* 315 * Initialize. 316 */ 317 buffer = vstring_alloc(10); 318 319 /* 320 * If no file names are given, copy stdin. 321 */ 322 if (argc == optind) { 323 vstream_control(VSTREAM_IN, 324 VSTREAM_CTL_PATH, "stdin", 325 VSTREAM_CTL_END); 326 postcat(VSTREAM_IN, buffer, flags); 327 } 328 329 /* 330 * Copy the named queue files in the specified order. 331 */ 332 else if (flags & PC_FLAG_QUEUE) { 333 if (chdir(var_queue_dir)) 334 msg_fatal("chdir %s: %m", var_queue_dir); 335 while (optind < argc) { 336 if (!mail_queue_id_ok(argv[optind])) 337 msg_fatal("bad mail queue ID: %s", argv[optind]); 338 for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++) 339 for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++) 340 fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0); 341 if (fp == 0) 342 msg_fatal("open queue file %s: %m", argv[optind]); 343 postcat(fp, buffer, flags); 344 if (vstream_fclose(fp)) 345 msg_warn("close %s: %m", argv[optind]); 346 optind++; 347 } 348 } 349 350 /* 351 * Copy the named files in the specified order. 352 */ 353 else { 354 while (optind < argc) { 355 if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0) 356 msg_fatal("open %s: %m", argv[optind]); 357 postcat(fp, buffer, flags); 358 if (vstream_fclose(fp)) 359 msg_warn("close %s: %m", argv[optind]); 360 optind++; 361 } 362 } 363 364 /* 365 * Clean up. 366 */ 367 vstring_free(buffer); 368 exit(0); 369 } 370