bounce_log.c revision 1.1
1/* $NetBSD: bounce_log.c,v 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 116BOUNCE_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 149BOUNCE_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 307int 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