1 /* $NetBSD: bounce_log.c,v 1.1.1.1 2009/06/23 10:08:45 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* bounce_log 3 6 /* SUMMARY 7 /* bounce file API 8 /* SYNOPSIS 9 /* #include <bounce_log.h> 10 /* 11 /* typedef struct { 12 /* .in +4 13 /* /* No public members. */ 14 /* .in -4 15 /* } BOUNCE_LOG; 16 /* 17 /* BOUNCE_LOG *bounce_log_open(queue, id, flags, mode) 18 /* const char *queue; 19 /* const char *id; 20 /* int flags; 21 /* mode_t mode; 22 /* 23 /* BOUNCE_LOG *bounce_log_read(bp, rcpt, dsn) 24 /* BOUNCE_LOG *bp; 25 /* RCPT_BUF *rcpt; 26 /* DSN_BUF *dsn; 27 /* 28 /* void bounce_log_rewind(bp) 29 /* BOUNCE_LOG *bp; 30 /* 31 /* void bounce_log_close(bp) 32 /* BOUNCE_LOG *bp; 33 /* DESCRIPTION 34 /* This module implements a bounce/defer logfile API. Information 35 /* is sanitized for control and non-ASCII characters. Fields not 36 /* present in input are represented by empty strings. 37 /* 38 /* bounce_log_open() opens the named bounce or defer logfile 39 /* and returns a handle that must be used for further access. 40 /* The result is a null pointer if the file cannot be opened. 41 /* The caller is expected to inspect the errno code and deal 42 /* with the problem. 43 /* 44 /* bounce_log_read() reads the next record from the bounce or defer 45 /* logfile (skipping over and warning about malformed data) 46 /* and breaks out the recipient address, the recipient status 47 /* and the text that explains why the recipient was undeliverable. 48 /* bounce_log_read() returns a null pointer when no recipient was read, 49 /* otherwise it returns its argument. 50 /* 51 /* bounce_log_rewind() is a helper that seeks to the first recipient 52 /* in an open bounce or defer logfile (skipping over recipients that 53 /* are marked as done). The result is 0 in case of success, -1 in case 54 /* of problems. 55 /* 56 /* bounce_log_close() closes an open bounce or defer logfile and 57 /* releases memory for the specified handle. The result is non-zero 58 /* in case of I/O errors. 59 /* 60 /* Arguments: 61 /* .IP queue 62 /* The bounce or defer queue name. 63 /* .IP id 64 /* The message queue id of bounce or defer logfile. This 65 /* file has the same name as the original message file. 66 /* .IP flags 67 /* File open flags, as with open(2). 68 /* .IP mode 69 /* File permissions, as with open(2). 70 /* .IP rcpt 71 /* Recipient buffer. The RECIPIENT member is updated. 72 /* .IP dsn 73 /* Delivery status information. The DSN member is updated. 74 /* LICENSE 75 /* .ad 76 /* .fi 77 /* The Secure Mailer license must be distributed with this software. 78 /* AUTHOR(S) 79 /* Wietse Venema 80 /* IBM T.J. Watson Research 81 /* P.O. Box 704 82 /* Yorktown Heights, NY 10598, USA 83 /*--*/ 84 85 /* System library. */ 86 87 #include <sys_defs.h> 88 #include <string.h> 89 #include <ctype.h> 90 #include <unistd.h> 91 #include <stdlib.h> 92 93 /* Utility library. */ 94 95 #include <msg.h> 96 #include <mymalloc.h> 97 #include <vstream.h> 98 #include <vstring.h> 99 #include <vstring_vstream.h> 100 #include <stringops.h> 101 102 /* Global library. */ 103 104 #include <mail_params.h> 105 #include <mail_proto.h> 106 #include <mail_queue.h> 107 #include <dsn_mask.h> 108 #include <bounce_log.h> 109 110 /* Application-specific. */ 111 112 #define STR(x) vstring_str(x) 113 114 /* bounce_log_open - open bounce read stream */ 115 116 BOUNCE_LOG *bounce_log_open(const char *queue_name, const char *queue_id, 117 int flags, mode_t mode) 118 { 119 BOUNCE_LOG *bp; 120 VSTREAM *fp; 121 122 #define STREQ(x,y) (strcmp((x),(y)) == 0) 123 124 /* 125 * Logfiles may contain a mixture of old-style (<recipient>: text) and 126 * new-style entries with multiple attributes per recipient. 127 * 128 * Kluge up default DSN status and action for old-style logfiles. 129 */ 130 if ((fp = mail_queue_open(queue_name, queue_id, flags, mode)) == 0) { 131 return (0); 132 } else { 133 bp = (BOUNCE_LOG *) mymalloc(sizeof(*bp)); 134 bp->fp = fp; 135 bp->buf = vstring_alloc(100); 136 if (STREQ(queue_name, MAIL_QUEUE_DEFER)) { 137 bp->compat_status = mystrdup("4.0.0"); 138 bp->compat_action = mystrdup("delayed"); 139 } else { 140 bp->compat_status = mystrdup("5.0.0"); 141 bp->compat_action = mystrdup("failed"); 142 } 143 return (bp); 144 } 145 } 146 147 /* bounce_log_read - read one record from bounce log file */ 148 149 BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf, 150 DSN_BUF *dsn_buf) 151 { 152 char *recipient; 153 char *text; 154 char *cp; 155 int state; 156 157 /* 158 * Our trivial logfile parser state machine. 159 */ 160 #define START 0 /* still searching */ 161 #define FOUND 1 /* in logfile entry */ 162 163 /* 164 * Initialize. 165 */ 166 state = START; 167 rcpb_reset(rcpt_buf); 168 dsb_reset(dsn_buf); 169 170 /* 171 * Support mixed logfile formats to make migration easier. The same file 172 * can start with old-style records and end with new-style records. With 173 * backwards compatibility, we even have old format followed by new 174 * format within the same logfile entry! 175 */ 176 for (;;) { 177 if ((vstring_get_nonl(bp->buf, bp->fp) == VSTREAM_EOF)) 178 return (0); 179 180 /* 181 * Logfile entries are separated by blank lines. Even the old ad-hoc 182 * logfile format has a blank line after the last record. This means 183 * we can safely use blank lines to detect the start and end of 184 * logfile entries. 185 */ 186 if (STR(bp->buf)[0] == 0) { 187 if (state == FOUND) 188 break; 189 state = START; 190 continue; 191 } 192 193 /* 194 * Sanitize. XXX This needs to be done more carefully with new-style 195 * logfile entries. 196 */ 197 cp = printable(STR(bp->buf), '?'); 198 199 if (state == START) 200 state = FOUND; 201 202 /* 203 * New style logfile entries are in "name = value" format. 204 */ 205 if (ISALNUM(*cp)) { 206 const char *err; 207 char *name; 208 char *value; 209 long offset; 210 int notify; 211 212 /* 213 * Split into name and value. 214 */ 215 if ((err = split_nameval(cp, &name, &value)) != 0) { 216 msg_warn("%s: malformed record: %s", VSTREAM_PATH(bp->fp), err); 217 continue; 218 } 219 220 /* 221 * Save attribute value. 222 */ 223 if (STREQ(name, MAIL_ATTR_RECIP)) { 224 vstring_strcpy(rcpt_buf->address, *value ? 225 value : "(MAILER-DAEMON)"); 226 } else if (STREQ(name, MAIL_ATTR_ORCPT)) { 227 vstring_strcpy(rcpt_buf->orig_addr, *value ? 228 value : "(MAILER-DAEMON)"); 229 } else if (STREQ(name, MAIL_ATTR_DSN_ORCPT)) { 230 vstring_strcpy(rcpt_buf->dsn_orcpt, value); 231 } else if (STREQ(name, MAIL_ATTR_DSN_NOTIFY)) { 232 if ((notify = atoi(value)) > 0 && DSN_NOTIFY_OK(notify)) 233 rcpt_buf->dsn_notify = notify; 234 } else if (STREQ(name, MAIL_ATTR_OFFSET)) { 235 if ((offset = atol(value)) > 0) 236 rcpt_buf->offset = offset; 237 } else if (STREQ(name, MAIL_ATTR_DSN_STATUS)) { 238 vstring_strcpy(dsn_buf->status, value); 239 } else if (STREQ(name, MAIL_ATTR_DSN_ACTION)) { 240 vstring_strcpy(dsn_buf->action, value); 241 } else if (STREQ(name, MAIL_ATTR_DSN_DTYPE)) { 242 vstring_strcpy(dsn_buf->dtype, value); 243 } else if (STREQ(name, MAIL_ATTR_DSN_DTEXT)) { 244 vstring_strcpy(dsn_buf->dtext, value); 245 } else if (STREQ(name, MAIL_ATTR_DSN_MTYPE)) { 246 vstring_strcpy(dsn_buf->mtype, value); 247 } else if (STREQ(name, MAIL_ATTR_DSN_MNAME)) { 248 vstring_strcpy(dsn_buf->mname, value); 249 } else if (STREQ(name, MAIL_ATTR_WHY)) { 250 vstring_strcpy(dsn_buf->reason, value); 251 } else { 252 msg_warn("%s: unknown attribute name: %s, ignored", 253 VSTREAM_PATH(bp->fp), name); 254 } 255 continue; 256 } 257 258 /* 259 * Old-style logfile record. Find the recipient address. 260 */ 261 if (*cp != '<') { 262 msg_warn("%s: malformed record: %.30s...", 263 VSTREAM_PATH(bp->fp), cp); 264 continue; 265 } 266 recipient = cp + 1; 267 if ((cp = strstr(recipient, ">: ")) == 0) { 268 msg_warn("%s: malformed record: %.30s...", 269 VSTREAM_PATH(bp->fp), cp); 270 continue; 271 } 272 *cp = 0; 273 vstring_strcpy(rcpt_buf->address, *recipient ? 274 recipient : "(MAILER-DAEMON)"); 275 276 /* 277 * Find the text that explains why mail was not deliverable. 278 */ 279 text = cp + 2; 280 while (*text && ISSPACE(*text)) 281 text++; 282 vstring_strcpy(dsn_buf->reason, text); 283 } 284 285 /* 286 * Specify place holders for missing fields. See also DSN_FROM_DSN_BUF() 287 * and RECIPIENT_FROM_RCPT_BUF() for null and non-null fields. 288 */ 289 #define BUF_NODATA(buf) (STR(buf)[0] == 0) 290 #define BUF_ASSIGN(buf, text) vstring_strcpy((buf), (text)) 291 292 if (BUF_NODATA(rcpt_buf->address)) 293 BUF_ASSIGN(rcpt_buf->address, "(recipient address unavailable)"); 294 if (BUF_NODATA(dsn_buf->status)) 295 BUF_ASSIGN(dsn_buf->status, bp->compat_status); 296 if (BUF_NODATA(dsn_buf->action)) 297 BUF_ASSIGN(dsn_buf->action, bp->compat_action); 298 if (BUF_NODATA(dsn_buf->reason)) 299 BUF_ASSIGN(dsn_buf->reason, "(description unavailable)"); 300 (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); 301 (void) DSN_FROM_DSN_BUF(dsn_buf); 302 return (bp); 303 } 304 305 /* bounce_log_close - close bounce reader stream */ 306 307 int bounce_log_close(BOUNCE_LOG *bp) 308 { 309 int ret; 310 311 ret = vstream_fclose(bp->fp); 312 vstring_free(bp->buf); 313 myfree(bp->compat_status); 314 myfree(bp->compat_action); 315 myfree((char *) bp); 316 317 return (ret); 318 } 319