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 121 BOUNCE_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 154 BOUNCE_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 312 int 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