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 1000 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 s++; 196 /* continue */ 197 break; 198 199 default: 200 ps->state = QUIT; 201 if (ps->pos != 0) 202 goto newaddr; 203 return (0); 204 } 205 /* FALLTHROUGH */ 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, NULL); 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]; 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 nocopy = 0; 389 int ret = -1; 390 391 parse_state.state = NONE; 392 393 error = fprintf(queue->mailf, 394 "Received: from %s (uid %d)\n" 395 "\t(envelope-from %s)\n" 396 "\tid %s\n" 397 "\tby %s (%s on %s);\n" 398 "\t%s\n", 399 username, useruid, 400 queue->sender, 401 queue->id, 402 hostname(), VERSION, systemhostname(), 403 rfc822date()); 404 if ((ssize_t)error < 0) 405 return (-1); 406 407 while (!feof(stdin)) { 408 newline[0] = '\0'; 409 if ((linelen = getline(&line, &linecap, stdin)) <= 0) 410 break; 411 if (!had_first_line) { 412 /* 413 * Ignore a leading RFC-976 From_ or >From_ line mistakenly 414 * inserted by some programs. 415 */ 416 if (strprefixcmp(line, "From ") == 0 || strprefixcmp(line, ">From ") == 0) 417 continue; 418 had_first_line = 1; 419 } 420 if (!had_headers) { 421 if (linelen > MAX_LINE_RFC822) { 422 errlogx(EX_DATAERR, "bad mail input format:" 423 " from %s (uid %d) (envelope-from %s)", 424 username, useruid, queue->sender); 425 } 426 /* 427 * Unless this is a continuation, switch of 428 * the Bcc: nocopy flag. 429 */ 430 if (!(line[0] == ' ' || line[0] == '\t')) 431 nocopy = 0; 432 433 if (strprefixcmp(line, "Date:") == 0) 434 had_date = 1; 435 else if (strprefixcmp(line, "Message-Id:") == 0) 436 had_messagid = 1; 437 else if (strprefixcmp(line, "From:") == 0) 438 had_from = 1; 439 else if (strprefixcmp(line, "Bcc:") == 0) 440 nocopy = 1; 441 442 if (parse_state.state != NONE) { 443 if (parse_addrs(&parse_state, line, queue) < 0) { 444 errlogx(EX_DATAERR, "invalid address in header\n"); 445 /* NOTREACHED */ 446 } 447 } 448 449 if (recp_from_header && ( 450 strprefixcmp(line, "To:") == 0 || 451 strprefixcmp(line, "Cc:") == 0 || 452 strprefixcmp(line, "Bcc:") == 0)) { 453 parse_state.state = START; 454 if (parse_addrs(&parse_state, line, queue) < 0) { 455 errlogx(EX_DATAERR, "invalid address in header\n"); 456 /* NOTREACHED */ 457 } 458 } 459 } 460 461 if (strcmp(line, "\n") == 0 && !had_headers) { 462 had_headers = 1; 463 while (!had_date || !had_messagid || !had_from) { 464 if (!had_date) { 465 had_date = 1; 466 snprintf(newline, sizeof(newline), "Date: %s\n", rfc822date()); 467 } else if (!had_messagid) { 468 /* XXX msgid, assign earlier and log? */ 469 had_messagid = 1; 470 snprintf(newline, sizeof(newline), "Message-Id: <%"PRIxMAX".%s.%"PRIxMAX"@%s>\n", 471 (uintmax_t)time(NULL), 472 queue->id, 473 (uintmax_t)random(), 474 hostname()); 475 } else if (!had_from) { 476 had_from = 1; 477 snprintf(newline, sizeof(newline), "From: <%s>\n", queue->sender); 478 } 479 if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1) 480 goto fail; 481 } 482 strlcpy(newline, "\n", sizeof(newline)); 483 } 484 if (!nodot && linelen == 2 && line[0] == '.') 485 break; 486 if (!nocopy) { 487 if (newline[0]) { 488 if (fwrite(newline, strlen(newline), 1, queue->mailf) != 1) 489 goto fail; 490 } else { 491 if (writeline(queue, line, linelen) != 0) 492 goto fail; 493 } 494 } 495 } 496 497 ret = 0; 498 fail: 499 free(line); 500 return (ret); 501 } 502