1 /* $NetBSD: delivered_hdr.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* delivered_hdr 3 6 /* SUMMARY 7 /* process Delivered-To: headers 8 /* SYNOPSIS 9 /* #include <delivered_hdr.h> 10 /* 11 /* DELIVERED_HDR_INFO *delivered_hdr_init(stream, offset, flags) 12 /* VSTREAM *stream; 13 /* off_t offset; 14 /* int flags; 15 /* 16 /* int delivered_hdr_find(info, address) 17 /* DELIVERED_HDR_INFO *info; 18 /* const char *address; 19 /* 20 /* void delivered_hdr_free(info) 21 /* DELIVERED_HDR_INFO *info; 22 /* DESCRIPTION 23 /* This module processes addresses in Delivered-To: headers. 24 /* These headers are added by some mail delivery systems, for the 25 /* purpose of breaking mail forwarding loops. N.B. This solves 26 /* a different problem than the Received: hop count limit. Hop 27 /* counts are used to limit the impact of mail routing problems. 28 /* 29 /* delivered_hdr_init() extracts Delivered-To: header addresses 30 /* from the specified message, and returns a table with the 31 /* result. The file seek pointer is changed. 32 /* 33 /* delivered_hdr_find() looks up the address in the lookup table, 34 /* and returns non-zero when the address was found. The 35 /* address argument must be in internalized form. 36 /* 37 /* delivered_hdr_free() releases storage that was allocated by 38 /* delivered_hdr_init(). 39 /* 40 /* Arguments: 41 /* .IP stream 42 /* The open queue file. 43 /* .IP offset 44 /* Offset of the first message content record. 45 /* .IP flags 46 /* Zero, or the bit-wise OR ot: 47 /* .RS 48 /* .IP FOLD_ADDR_USER 49 /* Case fold the address local part. 50 /* .IP FOLD_ADDR_HOST 51 /* Case fold the address domain part. 52 /* .IP FOLD_ADDR_ALL 53 /* Alias for (FOLD_ADDR_USER | FOLD_ADDR_HOST). 54 /* .RE 55 /* .IP info 56 /* Extracted Delivered-To: addresses information. 57 /* .IP address 58 /* A recipient address, internal form. 59 /* DIAGNOSTICS 60 /* Fatal errors: out of memory. 61 /* SEE ALSO 62 /* mail_copy(3), producer of Delivered-To: and other headers. 63 /* LICENSE 64 /* .ad 65 /* .fi 66 /* The Secure Mailer license must be distributed with this software. 67 /* AUTHOR(S) 68 /* Wietse Venema 69 /* IBM T.J. Watson Research 70 /* P.O. Box 704 71 /* Yorktown Heights, NY 10598, USA 72 /* 73 /* Wietse Venema 74 /* Google, Inc. 75 /* 111 8th Avenue 76 /* New York, NY 10011, USA 77 /*--*/ 78 79 /* System library. */ 80 81 #include <sys_defs.h> 82 #include <unistd.h> 83 #include <string.h> 84 #include <ctype.h> 85 86 /* Utility library. */ 87 88 #include <msg.h> 89 #include <mymalloc.h> 90 #include <htable.h> 91 #include <vstring.h> 92 #include <vstream.h> 93 #include <vstring_vstream.h> 94 #include <stringops.h> 95 96 /* Global library. */ 97 98 #include <record.h> 99 #include <rec_type.h> 100 #include <is_header.h> 101 #include <quote_822_local.h> 102 #include <header_opts.h> 103 #include <delivered_hdr.h> 104 #include <fold_addr.h> 105 106 /* 107 * Application-specific. 108 */ 109 struct DELIVERED_HDR_INFO { 110 int flags; 111 VSTRING *buf; 112 VSTRING *fold; 113 HTABLE *table; 114 }; 115 116 #define STR(x) vstring_str(x) 117 118 /* delivered_hdr_init - extract delivered-to information from the message */ 119 120 DELIVERED_HDR_INFO *delivered_hdr_init(VSTREAM *fp, off_t offset, int flags) 121 { 122 char *cp; 123 DELIVERED_HDR_INFO *info; 124 const HEADER_OPTS *hdr; 125 int curr_type; 126 int prev_type; 127 128 /* 129 * Sanity check. 130 */ 131 info = (DELIVERED_HDR_INFO *) mymalloc(sizeof(*info)); 132 info->flags = flags; 133 info->buf = vstring_alloc(10); 134 info->fold = vstring_alloc(10); 135 info->table = htable_create(0); 136 137 if (vstream_fseek(fp, offset, SEEK_SET) < 0) 138 msg_fatal("seek queue file %s: %m", VSTREAM_PATH(fp)); 139 140 /* 141 * XXX Assume that mail_copy() produces delivered-to headers that fit in 142 * a REC_TYPE_NORM or REC_TYPE_CONT record. Lowercase the delivered-to 143 * addresses for consistency. 144 * 145 * XXX Don't get bogged down by gazillions of delivered-to headers. 146 */ 147 #define DELIVERED_HDR_LIMIT 1000 148 149 for (prev_type = REC_TYPE_NORM; 150 info->table->used < DELIVERED_HDR_LIMIT 151 && ((curr_type = rec_get(fp, info->buf, 0)) == REC_TYPE_NORM 152 || curr_type == REC_TYPE_CONT); 153 prev_type = curr_type) { 154 if (prev_type == REC_TYPE_CONT) 155 continue; 156 if (is_header(STR(info->buf))) { 157 if ((hdr = header_opts_find(STR(info->buf))) != 0 158 && hdr->type == HDR_DELIVERED_TO) { 159 cp = STR(info->buf) + strlen(hdr->name) + 1; 160 while (ISSPACE(*cp)) 161 cp++; 162 cp = fold_addr(info->fold, cp, info->flags); 163 if (msg_verbose) 164 msg_info("delivered_hdr_init: %s", cp); 165 htable_enter(info->table, cp, (void *) 0); 166 } 167 } else if (ISSPACE(STR(info->buf)[0])) { 168 continue; 169 } else { 170 break; 171 } 172 } 173 return (info); 174 } 175 176 /* delivered_hdr_find - look up recipient in delivered table */ 177 178 int delivered_hdr_find(DELIVERED_HDR_INFO *info, const char *address) 179 { 180 HTABLE_INFO *ht; 181 const char *addr_key; 182 183 /* 184 * mail_copy() uses quote_822_local() when writing the Delivered-To: 185 * header. We must therefore apply the same transformation when looking 186 * up the recipient. Lowercase the delivered-to address for consistency. 187 */ 188 quote_822_local(info->buf, address); 189 addr_key = fold_addr(info->fold, STR(info->buf), info->flags); 190 ht = htable_locate(info->table, addr_key); 191 return (ht != 0); 192 } 193 194 /* delivered_hdr_free - destructor */ 195 196 void delivered_hdr_free(DELIVERED_HDR_INFO *info) 197 { 198 vstring_free(info->buf); 199 vstring_free(info->fold); 200 htable_free(info->table, (void (*) (void *)) 0); 201 myfree((void *) info); 202 } 203 204 #ifdef TEST 205 206 #include <msg_vstream.h> 207 #include <mail_params.h> 208 209 char *var_drop_hdrs; 210 211 int main(int arc, char **argv) 212 { 213 214 /* 215 * We write test records to a VSTRING, then read with delivered_hdr_init. 216 */ 217 VSTRING *mem_bp; 218 VSTREAM *mem_fp; 219 DELIVERED_HDR_INFO *dp; 220 struct test_case { 221 int rec_type; 222 const char *content; 223 int expect_find; 224 }; 225 const struct test_case test_cases[] = { 226 REC_TYPE_CONT, "Delivered-To: one", 1, 227 REC_TYPE_NORM, "Delivered-To: two", 0, 228 REC_TYPE_NORM, "Delivered-To: three", 1, 229 0, 230 }; 231 const struct test_case *tp; 232 int actual_find; 233 int errors; 234 235 msg_vstream_init(argv[0], VSTREAM_ERR); 236 237 var_drop_hdrs = mystrdup(DEF_DROP_HDRS); 238 239 mem_bp = vstring_alloc(VSTREAM_BUFSIZE); 240 if ((mem_fp = vstream_memopen(mem_bp, O_WRONLY)) == 0) 241 msg_panic("vstream_memopen O_WRONLY failed: %m"); 242 243 #define REC_PUT_LIT(fp, type, lit) rec_put((fp), (type), (lit), strlen(lit)) 244 245 for (tp = test_cases; tp->content != 0; tp++) 246 REC_PUT_LIT(mem_fp, tp->rec_type, tp->content); 247 248 if (vstream_fclose(mem_fp)) 249 msg_panic("vstream_fclose fail: %m"); 250 251 if ((mem_fp = vstream_memopen(mem_bp, O_RDONLY)) == 0) 252 msg_panic("vstream_memopen O_RDONLY failed: %m"); 253 254 dp = delivered_hdr_init(mem_fp, 0, FOLD_ADDR_ALL); 255 256 for (errors = 0, tp = test_cases; tp->content != 0; tp++) { 257 actual_find = 258 delivered_hdr_find(dp, tp->content + sizeof("Delivered-To:")); 259 msg_info("test case: %c >%s<: %s (expected: %s)", 260 tp->rec_type, tp->content, 261 actual_find == tp->expect_find ? "PASS" : "FAIL", 262 tp->expect_find ? "MATCH" : "NO MATCH"); 263 errors += (actual_find != tp->expect_find);; 264 } 265 exit(errors); 266 } 267 268 #endif 269