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