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