1/* $NetBSD: bounce_log.c,v 1.3 2020/03/18 19:05:16 christos 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/* Wietse Venema 85/* Google, Inc. 86/* 111 8th Avenue 87/* New York, NY 10011, USA 88/*--*/ 89 90/* System library. */ 91 92#include <sys_defs.h> 93#include <string.h> 94#include <ctype.h> 95#include <unistd.h> 96#include <stdlib.h> 97 98/* Utility library. */ 99 100#include <msg.h> 101#include <mymalloc.h> 102#include <vstream.h> 103#include <vstring.h> 104#include <vstring_vstream.h> 105#include <stringops.h> 106 107/* Global library. */ 108 109#include <mail_params.h> 110#include <mail_proto.h> 111#include <mail_queue.h> 112#include <dsn_mask.h> 113#include <bounce_log.h> 114 115/* Application-specific. */ 116 117#define STR(x) vstring_str(x) 118 119/* bounce_log_open - open bounce read stream */ 120 121BOUNCE_LOG *bounce_log_open(const char *queue_name, const char *queue_id, 122 int flags, mode_t mode) 123{ 124 BOUNCE_LOG *bp; 125 VSTREAM *fp; 126 127#define STREQ(x,y) (strcmp((x),(y)) == 0) 128 129 /* 130 * Logfiles may contain a mixture of old-style (<recipient>: text) and 131 * new-style entries with multiple attributes per recipient. 132 * 133 * Kluge up default DSN status and action for old-style logfiles. 134 */ 135 if ((fp = mail_queue_open(queue_name, queue_id, flags, mode)) == 0) { 136 return (0); 137 } else { 138 bp = (BOUNCE_LOG *) mymalloc(sizeof(*bp)); 139 bp->fp = fp; 140 bp->buf = vstring_alloc(100); 141 if (STREQ(queue_name, MAIL_QUEUE_DEFER)) { 142 bp->compat_status = mystrdup("4.0.0"); 143 bp->compat_action = mystrdup("delayed"); 144 } else { 145 bp->compat_status = mystrdup("5.0.0"); 146 bp->compat_action = mystrdup("failed"); 147 } 148 return (bp); 149 } 150} 151 152/* bounce_log_read - read one record from bounce log file */ 153 154BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp, RCPT_BUF *rcpt_buf, 155 DSN_BUF *dsn_buf) 156{ 157 char *recipient; 158 char *text; 159 char *cp; 160 int state; 161 162 /* 163 * Our trivial logfile parser state machine. 164 */ 165#define START 0 /* still searching */ 166#define FOUND 1 /* in logfile entry */ 167 168 /* 169 * Initialize. 170 */ 171 state = START; 172 rcpb_reset(rcpt_buf); 173 dsb_reset(dsn_buf); 174 175 /* 176 * Support mixed logfile formats to make migration easier. The same file 177 * can start with old-style records and end with new-style records. With 178 * backwards compatibility, we even have old format followed by new 179 * format within the same logfile entry! 180 */ 181 for (;;) { 182 if ((vstring_get_nonl(bp->buf, bp->fp) == VSTREAM_EOF)) 183 return (0); 184 185 /* 186 * Logfile entries are separated by blank lines. Even the old ad-hoc 187 * logfile format has a blank line after the last record. This means 188 * we can safely use blank lines to detect the start and end of 189 * logfile entries. 190 */ 191 if (STR(bp->buf)[0] == 0) { 192 if (state == FOUND) 193 break; 194 state = START; 195 continue; 196 } 197 198 /* 199 * Sanitize. XXX This needs to be done more carefully with new-style 200 * logfile entries. 201 */ 202 cp = printable(STR(bp->buf), '?'); 203 204 if (state == START) 205 state = FOUND; 206 207 /* 208 * New style logfile entries are in "name = value" format. 209 */ 210 if (ISALNUM(*cp)) { 211 const char *err; 212 char *name; 213 char *value; 214 long offset; 215 int notify; 216 217 /* 218 * Split into name and value. 219 */ 220 if ((err = split_nameval(cp, &name, &value)) != 0) { 221 msg_warn("%s: malformed record: %s", VSTREAM_PATH(bp->fp), err); 222 continue; 223 } 224 225 /* 226 * Save attribute value. 227 */ 228 if (STREQ(name, MAIL_ATTR_RECIP)) { 229 vstring_strcpy(rcpt_buf->address, *value ? 230 value : "(MAILER-DAEMON)"); 231 } else if (STREQ(name, MAIL_ATTR_ORCPT)) { 232 vstring_strcpy(rcpt_buf->orig_addr, *value ? 233 value : "(MAILER-DAEMON)"); 234 } else if (STREQ(name, MAIL_ATTR_DSN_ORCPT)) { 235 vstring_strcpy(rcpt_buf->dsn_orcpt, value); 236 } else if (STREQ(name, MAIL_ATTR_DSN_NOTIFY)) { 237 if ((notify = atoi(value)) > 0 && DSN_NOTIFY_OK(notify)) 238 rcpt_buf->dsn_notify = notify; 239 } else if (STREQ(name, MAIL_ATTR_OFFSET)) { 240 if ((offset = atol(value)) > 0) 241 rcpt_buf->offset = offset; 242 } else if (STREQ(name, MAIL_ATTR_DSN_STATUS)) { 243 vstring_strcpy(dsn_buf->status, value); 244 } else if (STREQ(name, MAIL_ATTR_DSN_ACTION)) { 245 vstring_strcpy(dsn_buf->action, value); 246 } else if (STREQ(name, MAIL_ATTR_DSN_DTYPE)) { 247 vstring_strcpy(dsn_buf->dtype, value); 248 } else if (STREQ(name, MAIL_ATTR_DSN_DTEXT)) { 249 vstring_strcpy(dsn_buf->dtext, value); 250 } else if (STREQ(name, MAIL_ATTR_DSN_MTYPE)) { 251 vstring_strcpy(dsn_buf->mtype, value); 252 } else if (STREQ(name, MAIL_ATTR_DSN_MNAME)) { 253 vstring_strcpy(dsn_buf->mname, value); 254 } else if (STREQ(name, MAIL_ATTR_WHY)) { 255 vstring_strcpy(dsn_buf->reason, value); 256 } else { 257 msg_warn("%s: unknown attribute name: %s, ignored", 258 VSTREAM_PATH(bp->fp), name); 259 } 260 continue; 261 } 262 263 /* 264 * Old-style logfile record. Find the recipient address. 265 */ 266 if (*cp != '<') { 267 msg_warn("%s: malformed record: %.30s...", 268 VSTREAM_PATH(bp->fp), cp); 269 continue; 270 } 271 recipient = cp + 1; 272 if ((cp = strstr(recipient, ">: ")) == 0) { 273 msg_warn("%s: malformed record: %.30s...", 274 VSTREAM_PATH(bp->fp), recipient - 1); 275 continue; 276 } 277 *cp = 0; 278 vstring_strcpy(rcpt_buf->address, *recipient ? 279 recipient : "(MAILER-DAEMON)"); 280 281 /* 282 * Find the text that explains why mail was not deliverable. 283 */ 284 text = cp + 2; 285 while (*text && ISSPACE(*text)) 286 text++; 287 vstring_strcpy(dsn_buf->reason, text); 288 } 289 290 /* 291 * Specify place holders for missing fields. See also DSN_FROM_DSN_BUF() 292 * and RECIPIENT_FROM_RCPT_BUF() for null and non-null fields. 293 */ 294#define BUF_NODATA(buf) (STR(buf)[0] == 0) 295#define BUF_ASSIGN(buf, text) vstring_strcpy((buf), (text)) 296 297 if (BUF_NODATA(rcpt_buf->address)) 298 BUF_ASSIGN(rcpt_buf->address, "(recipient address unavailable)"); 299 if (BUF_NODATA(dsn_buf->status)) 300 BUF_ASSIGN(dsn_buf->status, bp->compat_status); 301 if (BUF_NODATA(dsn_buf->action)) 302 BUF_ASSIGN(dsn_buf->action, bp->compat_action); 303 if (BUF_NODATA(dsn_buf->reason)) 304 BUF_ASSIGN(dsn_buf->reason, "(description unavailable)"); 305 (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); 306 (void) DSN_FROM_DSN_BUF(dsn_buf); 307 return (bp); 308} 309 310/* bounce_log_close - close bounce reader stream */ 311 312int bounce_log_close(BOUNCE_LOG *bp) 313{ 314 int ret; 315 316 ret = vstream_fclose(bp->fp); 317 vstring_free(bp->buf); 318 myfree(bp->compat_status); 319 myfree(bp->compat_action); 320 myfree((void *) bp); 321 322 return (ret); 323} 324