1 /* 2 * Copyright (c) 2008 The DragonFly Project. All rights reserved. 3 * 4 * This code is derived from software contributed to The DragonFly Project 5 * by Simon 'corecode' Schubert <corecode@fs.ei.tum.de>. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * 3. Neither the name of The DragonFly Project nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific, prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #include <errno.h> 36 #include <inttypes.h> 37 #include <signal.h> 38 #include <syslog.h> 39 #include <unistd.h> 40 41 #include "dma.h" 42 43 void 44 bounce(struct qitem *it, const char *reason) 45 { 46 struct queue bounceq; 47 char line[1000]; 48 size_t pos; 49 int error; 50 51 /* Don't bounce bounced mails */ 52 if (it->sender[0] == 0) { 53 syslog(LOG_INFO, "can not bounce a bounce message, discarding"); 54 exit(1); 55 } 56 57 bzero(&bounceq, sizeof(bounceq)); 58 LIST_INIT(&bounceq.queue); 59 bounceq.sender = ""; 60 if (add_recp(&bounceq, it->sender, EXPAND_WILDCARD) != 0) 61 goto fail; 62 63 if (newspoolf(&bounceq) != 0) 64 goto fail; 65 66 syslog(LOG_ERR, "delivery failed, bouncing as %s", bounceq.id); 67 setlogident("%s", bounceq.id); 68 69 error = fprintf(bounceq.mailf, 70 "Received: from MAILER-DAEMON\n" 71 "\tid %s\n" 72 "\tby %s (%s);\n" 73 "\t%s\n" 74 "X-Original-To: <%s>\n" 75 "From: MAILER-DAEMON <>\n" 76 "To: %s\n" 77 "Subject: Mail delivery failed\n" 78 "Message-Id: <%s@%s>\n" 79 "Date: %s\n" 80 "\n" 81 "This is the %s at %s.\n" 82 "\n" 83 "There was an error delivering your mail to <%s>.\n" 84 "\n" 85 "%s\n" 86 "\n" 87 "%s\n" 88 "\n", 89 bounceq.id, 90 hostname(), VERSION, 91 rfc822date(), 92 it->addr, 93 it->sender, 94 bounceq.id, hostname(), 95 rfc822date(), 96 VERSION, hostname(), 97 it->addr, 98 reason, 99 config.features & FULLBOUNCE ? 100 "Original message follows." : 101 "Message headers follow."); 102 if (error < 0) 103 goto fail; 104 105 if (fseek(it->mailf, 0, SEEK_SET) != 0) 106 goto fail; 107 if (config.features & FULLBOUNCE) { 108 while ((pos = fread(line, 1, sizeof(line), it->mailf)) > 0) { 109 if (fwrite(line, 1, pos, bounceq.mailf) != pos) 110 goto fail; 111 } 112 } else { 113 while (!feof(it->mailf)) { 114 if (fgets(line, sizeof(line), it->mailf) == NULL) 115 break; 116 if (line[0] == '\n') 117 break; 118 if (fwrite(line, strlen(line), 1, bounceq.mailf) != 1) 119 goto fail; 120 } 121 } 122 123 if (linkspool(&bounceq) != 0) 124 goto fail; 125 /* bounce is safe */ 126 127 delqueue(it); 128 129 run_queue(&bounceq); 130 /* NOTREACHED */ 131 132 fail: 133 syslog(LOG_CRIT, "error creating bounce: %m"); 134 delqueue(it); 135 exit(1); 136 } 137 138 struct parse_state { 139 char addr[1000]; 140 int pos; 141 142 enum { 143 NONE = 0, 144 START, 145 MAIN, 146 EOL, 147 QUIT 148 } state; 149 int comment; 150 int quote; 151 int brackets; 152 int esc; 153 }; 154 155 /* 156 * Simplified RFC2822 header/address parsing. 157 * We copy escapes and quoted strings directly, since 158 * we have to pass them like this to the mail server anyways. 159 * XXX local addresses will need treatment 160 */ 161 static int 162 parse_addrs(struct parse_state *ps, char *s, struct queue *queue) 163 { 164 char *addr; 165 166 again: 167 switch (ps->state) { 168 case NONE: 169 return (-1); 170 171 case START: 172 /* init our data */ 173 bzero(ps, sizeof(*ps)); 174 175 /* skip over header name */ 176 while (*s != ':') 177 s++; 178 s++; 179 ps->state = MAIN; 180 break; 181 182 case MAIN: 183 /* all fine */ 184 break; 185 186 case EOL: 187 switch (*s) { 188 case ' ': 189 case '\t': 190 s++; 191 /* continue */ 192 break; 193 194 default: 195 ps->state = QUIT; 196 if (ps->pos != 0) 197 goto newaddr; 198 return (0); 199 } 200 201 case QUIT: 202 return (0); 203 } 204 205 for (; *s != 0; s++) { 206 if (ps->esc) { 207 ps->esc = 0; 208 209 switch (*s) { 210 case '\r': 211 case '\n': 212 goto err; 213 214 default: 215 goto copy; 216 } 217 } 218 219 if (ps->quote) { 220 switch (*s) { 221 case '"': 222 ps->quote = 0; 223 goto copy; 224 225 case '\\': 226 ps->esc = 1; 227 goto copy; 228 229 case '\r': 230 case '\n': 231 goto eol; 232 233 default: 234 goto copy; 235 } 236 } 237 238 switch (*s) { 239 case '(': 240 ps->comment++; 241 break; 242 243 case ')': 244 if (ps->comment) 245 ps->comment--; 246 else 247 goto err; 248 goto skip; 249 250 case '"': 251 ps->quote = 1; 252 goto copy; 253 254 case '\\': 255 ps->esc = 1; 256 goto copy; 257 258 case '\r': 259 case '\n': 260 goto eol; 261 } 262 263 if (ps->comment) 264 goto skip; 265 266 switch (*s) { 267 case ' ': 268 case '\t': 269 /* ignore whitespace */ 270 goto skip; 271 272 case '<': 273 /* this is the real address now */ 274 ps->brackets = 1; 275 ps->pos = 0; 276 goto skip; 277 278 case '>': 279 if (!ps->brackets) 280 goto err; 281 ps->brackets = 0; 282 283 s++; 284 goto newaddr; 285 286 case ':': 287 /* group - ignore */ 288 ps->pos = 0; 289 goto skip; 290 291 case ',': 292 case ';': 293 /* 294 * Next address, copy previous one. 295 * However, we might be directly after 296 * a <address>, or have two consecutive 297 * commas. 298 * Skip the comma unless there is 299 * really something to copy. 300 */ 301 if (ps->pos == 0) 302 goto skip; 303 s++; 304 goto newaddr; 305 306 default: 307 goto copy; 308 } 309 310 copy: 311 if (ps->comment) 312 goto skip; 313 314 if (ps->pos + 1 == sizeof(ps->addr)) 315 goto err; 316 ps->addr[ps->pos++] = *s; 317 318 skip: 319 ; 320 } 321 322 eol: 323 ps->state = EOL; 324 return (0); 325 326 err: 327 ps->state = QUIT; 328 return (-1); 329 330 newaddr: 331 ps->addr[ps->pos] = 0; 332 ps->pos = 0; 333 addr = strdup(ps->addr); 334 if (addr == NULL) 335 errlog(1, "strdup failed"); 336 337 if (add_recp(queue, addr, EXPAND_WILDCARD) != 0) 338 errlogx(1, "invalid recipient `%s'", addr); 339 340 goto again; 341 } 342 343 int 344 readmail(struct queue *queue, int nodot, int recp_from_header) 345 { 346 struct parse_state parse_state; 347 char line[1000]; /* by RFC2822 */ 348 size_t linelen; 349 size_t error; 350 int had_headers = 0; 351 int had_from = 0; 352 int had_messagid = 0; 353 int had_date = 0; 354 int had_last_line = 0; 355 int nocopy = 0; 356 357 parse_state.state = NONE; 358 359 error = fprintf(queue->mailf, 360 "Received: from %s (uid %d)\n" 361 "\t(envelope-from %s)\n" 362 "\tid %s\n" 363 "\tby %s (%s);\n" 364 "\t%s\n", 365 username, useruid, 366 queue->sender, 367 queue->id, 368 hostname(), VERSION, 369 rfc822date()); 370 if ((ssize_t)error < 0) 371 return (-1); 372 373 while (!feof(stdin)) { 374 if (fgets(line, sizeof(line) - 1, stdin) == NULL) 375 break; 376 if (had_last_line) 377 errlogx(1, "bad mail input format"); 378 linelen = strlen(line); 379 if (linelen == 0 || line[linelen - 1] != '\n') { 380 /* 381 * This line did not end with a newline character. 382 * If we fix it, it better be the last line of 383 * the file. 384 */ 385 line[linelen] = '\n'; 386 line[linelen + 1] = 0; 387 had_last_line = 1; 388 } 389 if (!had_headers) { 390 /* 391 * Unless this is a continuation, switch of 392 * the Bcc: nocopy flag. 393 */ 394 if (!(line[0] == ' ' || line[0] == '\t')) 395 nocopy = 0; 396 397 if (strprefixcmp(line, "Date:") == 0) 398 had_date = 1; 399 else if (strprefixcmp(line, "Message-Id:") == 0) 400 had_messagid = 1; 401 else if (strprefixcmp(line, "From:") == 0) 402 had_from = 1; 403 else if (strprefixcmp(line, "Bcc:") == 0) 404 nocopy = 1; 405 406 if (parse_state.state != NONE) { 407 if (parse_addrs(&parse_state, line, queue) < 0) { 408 errlogx(1, "invalid address in header\n"); 409 /* NOTREACHED */ 410 } 411 } 412 413 if (recp_from_header && ( 414 strprefixcmp(line, "To:") == 0 || 415 strprefixcmp(line, "Cc:") == 0 || 416 strprefixcmp(line, "Bcc:") == 0)) { 417 parse_state.state = START; 418 if (parse_addrs(&parse_state, line, queue) < 0) { 419 errlogx(1, "invalid address in header\n"); 420 /* NOTREACHED */ 421 } 422 } 423 } 424 425 if (strcmp(line, "\n") == 0 && !had_headers) { 426 had_headers = 1; 427 while (!had_date || !had_messagid || !had_from) { 428 if (!had_date) { 429 had_date = 1; 430 snprintf(line, sizeof(line), "Date: %s\n", rfc822date()); 431 } else if (!had_messagid) { 432 /* XXX msgid, assign earlier and log? */ 433 had_messagid = 1; 434 snprintf(line, sizeof(line), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n", 435 (uintmax_t)time(NULL), 436 queue->id, 437 (uintmax_t)random(), 438 hostname()); 439 } else if (!had_from) { 440 had_from = 1; 441 snprintf(line, sizeof(line), "From: <%s>\n", queue->sender); 442 } 443 if (fwrite(line, strlen(line), 1, queue->mailf) != 1) 444 return (-1); 445 } 446 strcpy(line, "\n"); 447 } 448 if (!nodot && linelen == 2 && line[0] == '.') 449 break; 450 if (!nocopy) { 451 if (fwrite(line, strlen(line), 1, queue->mailf) != 1) 452 return (-1); 453 } 454 } 455 456 return (0); 457 } 458