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