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