mbox_extractor.c (61661B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/mbox_extractor.c 22 * @brief plugin to support Unix mbox mailboxes and RFC 5322 messages 23 * @author Christian Grothoff 24 * 25 * What is worth having out of a mail file is the routing metadata, not 26 * the prose: the `Received' chain and the IP literals in it say which 27 * machines the message actually passed through, `Message-ID' usually 28 * carries the originating host's name on the right of the `@', 29 * `X-Mailer' fingerprints the client, and the `d=' tag of a 30 * `DKIM-Signature' names the domain that vouched for it. Those are the 31 * fields this plugin goes after. 32 * 33 * Only the *first* message is parsed in full; the rest are counted. 34 * That is the deliberate trade-off of a first pass over a large volume: 35 * a mailbox can be gigabytes, and a characterisation plus a count beats 36 * an inventory that never finishes. 37 * 38 * Known limits of the scanner, stated once here: 39 * 40 * - The message count is the number of lines that begin with `From ' 41 * (plus the first message). That is the mbox format's well-known 42 * ambiguity: a *body* line beginning with "From " that the writer 43 * failed to escape as ">From " is indistinguishable from a separator, 44 * and inflates the count. There is no fix that does not involve 45 * parsing every message. 46 * - MIME parts are found by looking for `Content-Type' and 47 * `Content-Disposition' lines in the body, not by following the 48 * boundary; a part header quoted inside a body would be picked up. 49 * - Only the first message's body is searched for attachment names. 50 * - Encoded words are decoded for UTF-8, US-ASCII and the ISO-8859-1 51 * family; ISO-8859-15 and windows-1252 are decoded *as* ISO-8859-1, 52 * which is right for every position except the handful those two 53 * redefine. Any other charset is left as the raw encoded word rather 54 * than guessed at. 55 */ 56 #include "platform.h" 57 #include "extractor.h" 58 #include "forensics.h" 59 60 61 /** 62 * How many bytes of the mailbox we are willing to read. 63 */ 64 #define MBOX_SCAN_CAP (256 * 1024) 65 66 /** 67 * How much of the start of the file the magic check looks at. 68 */ 69 #define MBOX_MAGIC_WINDOW 8192 70 71 /** 72 * Largest header block we will walk, in bytes. 73 */ 74 #define MBOX_MAX_HDR_BLOCK 65536 75 76 /** 77 * Most header fields we will look at in one message. 78 */ 79 #define MBOX_MAX_HDR_LINES 1024 80 81 /** 82 * Working buffer for one unfolded header value. 83 */ 84 #define MBOX_HDR_VALUE 4096 85 86 /** 87 * How many bytes of the first message's body we search for MIME part 88 * headers. 89 */ 90 #define MBOX_BODY_SCAN (128 * 1024) 91 92 /** 93 * Longest text an RFC 2231 parameter may assemble to. 94 */ 95 #define MBOX_MAX_PARAM 512 96 97 /** 98 * Most RFC 2231 continuation segments we will join. 99 */ 100 #define MBOX_MAX_CONT 16 101 102 /** 103 * Most distinct attachment names we will remember for de-duplication. 104 */ 105 #define MBOX_MAX_FILENAMES 32 106 107 108 /** 109 * Distinct IP literals seen in the routing headers. 110 */ 111 struct MboxIps 112 { 113 /** 114 * The addresses, in the order they were found. 46 bytes is the 115 * longest textual IPv6 address plus a NUL. 116 */ 117 char ip[EXTRACTOR_FORENSIC_MAX_ITEMS][46]; 118 119 /** 120 * Number of entries used in @e ip. 121 */ 122 unsigned int count; 123 }; 124 125 126 /** 127 * The single-valued headers of the first message. First occurrence 128 * wins: a header that appears twice is either a duplicate or an 129 * injection attempt, and the first one is the one the reader saw. 130 */ 131 struct MboxHeaders 132 { 133 char from[1024]; 134 char to[2048]; 135 char cc[2048]; 136 char subject[1024]; 137 char date[256]; 138 char msgid[512]; 139 char inreplyto[512]; 140 char references[2048]; 141 char mailer[512]; 142 char org[512]; 143 char ctype[1024]; 144 char dkim[2048]; 145 char authres[1024]; 146 char charset[128]; 147 }; 148 149 150 /** 151 * Everything the plugin allocates in one block, so that every exit path 152 * is a single `free()'. 153 */ 154 struct MboxState 155 { 156 struct MboxHeaders h; 157 struct MboxIps ips; 158 char val[MBOX_HDR_VALUE]; 159 160 /** 161 * Attachment names already reported. A MIME part usually carries the 162 * same name twice, once as the `name' of its `Content-Type' and once 163 * as the `filename' of its `Content-Disposition'; reporting it twice 164 * would suggest two attachments. 165 */ 166 char fnames[MBOX_MAX_FILENAMES][MBOX_MAX_PARAM]; 167 168 /** 169 * Number of entries used in @e fnames. 170 */ 171 unsigned int nfnames; 172 }; 173 174 175 /** 176 * ASCII lower case. Header field names and MIME parameter names are 177 * ASCII by definition, so no locale is involved. 178 * 179 * @param c the character 180 * @return @a c folded to lower case 181 */ 182 static char 183 mbox_lc (char c) 184 { 185 if ( ('A' <= c) && ('Z' >= c) ) 186 return (char) (c - 'A' + 'a'); 187 return c; 188 } 189 190 191 /** 192 * Value of a hexadecimal digit. 193 * 194 * @param c the character 195 * @return 0-15, or -1 if @a c is not a hexadecimal digit 196 */ 197 static int 198 mbox_hex (char c) 199 { 200 if ( ('0' <= c) && ('9' >= c) ) 201 return c - '0'; 202 if ( ('a' <= c) && ('f' >= c) ) 203 return c - 'a' + 10; 204 if ( ('A' <= c) && ('F' >= c) ) 205 return c - 'A' + 10; 206 return -1; 207 } 208 209 210 /** 211 * Case-insensitive comparison of a counted string against a 212 * NUL-terminated one. 213 * 214 * @param a the counted string 215 * @param alen number of bytes in @a a 216 * @param b the NUL-terminated string 217 * @return 1 if they are equal ignoring case, 0 otherwise 218 */ 219 static int 220 mbox_ieq (const char *a, 221 size_t alen, 222 const char *b) 223 { 224 size_t blen = strlen (b); 225 226 if (alen != blen) 227 return 0; 228 for (size_t i = 0; i < alen; i++) 229 if (mbox_lc (a[i]) != mbox_lc (b[i])) 230 return 0; 231 return 1; 232 } 233 234 235 /** 236 * Store a value in a fixed-width slot, first writer wins. 237 * 238 * @param dst the slot 239 * @param dstsize number of bytes in @a dst 240 * @param v the value 241 * @param vlen number of bytes in @a v 242 */ 243 static void 244 mbox_store (char *dst, 245 size_t dstsize, 246 const char *v, 247 size_t vlen) 248 { 249 if ('\0' != dst[0]) 250 return; 251 if (vlen >= dstsize) 252 vlen = dstsize - 1; 253 memcpy (dst, 254 v, 255 vlen); 256 dst[vlen] = '\0'; 257 } 258 259 260 /** 261 * Days since 1970-01-01 for a proleptic Gregorian date. (Howard 262 * Hinnant's `days_from_civil'.) 263 * 264 * @param y year 265 * @param m month, 1-12 266 * @param d day of month, 1-31 267 * @return day number, negative before the epoch 268 */ 269 static int64_t 270 mbox_days_from_civil (int64_t y, 271 int64_t m, 272 int64_t d) 273 { 274 int64_t era; 275 int64_t yoe; 276 int64_t doy; 277 int64_t doe; 278 279 y -= (m <= 2); 280 era = (y >= 0 ? y : y - 399) / 400; 281 yoe = y - era * 400; 282 doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; 283 doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; 284 return era * 146097 + doe - 719468; 285 } 286 287 288 /** 289 * Parse an RFC 5322 `Date:' value into seconds since the Unix epoch. 290 * 291 * Handles the current syntax (`Tue, 12 Mar 2024 09:41:07 +0100') and the 292 * obsolete forms that are still common in archives: a two- or 293 * three-digit year, a missing seconds field, and an alphabetic zone 294 * (`GMT', `UT', `EST' and the rest of the US set). An unrecognised 295 * alphabetic zone is treated as UTC, which is what RFC 5322 says to do 296 * with the obsolete military single letters. 297 * 298 * @param s the value 299 * @param len number of bytes in @a s 300 * @param[out] when where to store the result 301 * @return 1 on success, 0 if @a s is not a date 302 */ 303 static int 304 mbox_parse_date (const char *s, 305 size_t len, 306 int64_t *when) 307 { 308 static const char *months[12] = { 309 "jan", "feb", "mar", "apr", "may", "jun", 310 "jul", "aug", "sep", "oct", "nov", "dec" 311 }; 312 static const struct 313 { 314 const char *name; 315 int hours; 316 } zones[] = { 317 { "ut", 0 }, { "gmt", 0 }, { "z", 0 }, 318 { "est", -5 }, { "edt", -4 }, 319 { "cst", -6 }, { "cdt", -5 }, 320 { "mst", -7 }, { "mdt", -6 }, 321 { "pst", -8 }, { "pdt", -7 }, 322 { NULL, 0 } 323 }; 324 size_t i = 0; 325 int64_t day; 326 int64_t mon = -1; 327 int64_t year; 328 int64_t hh; 329 int64_t mm; 330 int64_t ss = 0; 331 int64_t off = 0; 332 size_t digits; 333 334 #define SKIP_WS() while ( (i < len) && \ 335 ( (' ' == s[i]) || ('\t' == s[i]) ) ) i++ 336 337 SKIP_WS (); 338 /* optional `Tue, ' */ 339 { 340 size_t j = i; 341 342 while ( (j < len) && 343 ( ( ('a' <= mbox_lc (s[j])) && ('z' >= mbox_lc (s[j])) ) ) ) 344 j++; 345 if ( (j > i) && 346 (j < len) && 347 (',' == s[j]) ) 348 i = j + 1; 349 } 350 SKIP_WS (); 351 digits = 0; 352 day = 0; 353 while ( (i < len) && 354 (digits < 2) && 355 ('0' <= s[i]) && ('9' >= s[i]) ) 356 { 357 day = day * 10 + (s[i] - '0'); 358 i++; 359 digits++; 360 } 361 if ( (0 == digits) || 362 (day < 1) || (day > 31) ) 363 return 0; 364 SKIP_WS (); 365 if (i + 3 > len) 366 return 0; 367 for (unsigned int m = 0; m < 12; m++) 368 if ( (mbox_lc (s[i]) == months[m][0]) && 369 (mbox_lc (s[i + 1]) == months[m][1]) && 370 (mbox_lc (s[i + 2]) == months[m][2]) ) 371 { 372 mon = m + 1; 373 break; 374 } 375 if (0 > mon) 376 return 0; 377 i += 3; 378 /* an obsolete long month name may follow; skip the rest of the word */ 379 while ( (i < len) && 380 ('a' <= mbox_lc (s[i])) && ('z' >= mbox_lc (s[i])) ) 381 i++; 382 SKIP_WS (); 383 digits = 0; 384 year = 0; 385 while ( (i < len) && 386 (digits < 4) && 387 ('0' <= s[i]) && ('9' >= s[i]) ) 388 { 389 year = year * 10 + (s[i] - '0'); 390 i++; 391 digits++; 392 } 393 if (0 == digits) 394 return 0; 395 if (2 == digits) 396 year += (year < 50) ? 2000 : 1900; /* RFC 5322 4.3 */ 397 else if (3 == digits) 398 year += 1900; 399 else if (4 != digits) 400 return 0; 401 SKIP_WS (); 402 digits = 0; 403 hh = 0; 404 while ( (i < len) && 405 (digits < 2) && 406 ('0' <= s[i]) && ('9' >= s[i]) ) 407 { 408 hh = hh * 10 + (s[i] - '0'); 409 i++; 410 digits++; 411 } 412 if ( (0 == digits) || 413 (i >= len) || 414 (':' != s[i]) ) 415 return 0; 416 i++; 417 digits = 0; 418 mm = 0; 419 while ( (i < len) && 420 (digits < 2) && 421 ('0' <= s[i]) && ('9' >= s[i]) ) 422 { 423 mm = mm * 10 + (s[i] - '0'); 424 i++; 425 digits++; 426 } 427 if (0 == digits) 428 return 0; 429 if ( (i < len) && 430 (':' == s[i]) ) 431 { 432 i++; 433 digits = 0; 434 while ( (i < len) && 435 (digits < 2) && 436 ('0' <= s[i]) && ('9' >= s[i]) ) 437 { 438 ss = ss * 10 + (s[i] - '0'); 439 i++; 440 digits++; 441 } 442 if (0 == digits) 443 return 0; 444 } 445 if ( (hh > 23) || (mm > 59) || (ss > 60) ) 446 return 0; 447 SKIP_WS (); 448 if ( (i < len) && 449 ( ('+' == s[i]) || ('-' == s[i]) ) ) 450 { 451 int neg = ('-' == s[i]); 452 int64_t v = 0; 453 454 i++; 455 digits = 0; 456 while ( (i < len) && 457 (digits < 4) && 458 ('0' <= s[i]) && ('9' >= s[i]) ) 459 { 460 v = v * 10 + (s[i] - '0'); 461 i++; 462 digits++; 463 } 464 if (4 != digits) 465 return 0; 466 off = (v / 100) * 3600 + (v % 100) * 60; 467 if (neg) 468 off = -off; 469 } 470 else if (i < len) 471 { 472 size_t j = i; 473 474 while ( (j < len) && 475 (j - i < 8) && 476 ('a' <= mbox_lc (s[j])) && ('z' >= mbox_lc (s[j])) ) 477 j++; 478 for (unsigned int z = 0; NULL != zones[z].name; z++) 479 if (mbox_ieq (&s[i], 480 j - i, 481 zones[z].name)) 482 { 483 off = zones[z].hours * 3600; 484 break; 485 } 486 } 487 #undef SKIP_WS 488 *when = mbox_days_from_civil (year, 489 mon, 490 day) * 86400 491 + hh * 3600 + mm * 60 + ss 492 - off; 493 return 1; 494 } 495 496 497 /** 498 * Decode a base64 body into @a out, ignoring white space. 499 * 500 * @param in the encoded text 501 * @param inlen number of bytes in @a in 502 * @param[out] out where to write the bytes 503 * @param outsize number of bytes available in @a out 504 * @return number of bytes written, or 0 on a malformed input 505 */ 506 static size_t 507 mbox_b64 (const char *in, 508 size_t inlen, 509 char *out, 510 size_t outsize) 511 { 512 static const char alpha[] = 513 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 514 uint32_t acc = 0; 515 unsigned int bits = 0; 516 size_t o = 0; 517 518 for (size_t i = 0; i < inlen; i++) 519 { 520 const char *p; 521 522 if ( (' ' == in[i]) || ('\t' == in[i]) || 523 ('\r' == in[i]) || ('\n' == in[i]) ) 524 continue; 525 if ('=' == in[i]) 526 break; 527 p = memchr (alpha, 528 in[i], 529 64); 530 if (NULL == p) 531 return 0; /* not base64 after all */ 532 acc = (acc << 6) | (uint32_t) (p - alpha); 533 bits += 6; 534 if (bits >= 8) 535 { 536 bits -= 8; 537 if (o >= outsize) 538 return o; 539 out[o++] = (char) ((acc >> bits) & 0xFF); 540 } 541 } 542 return o; 543 } 544 545 546 /** 547 * Decode the quoted-printable variant used inside RFC 2047 encoded 548 * words, where `_' stands for a space. 549 * 550 * @param in the encoded text 551 * @param inlen number of bytes in @a in 552 * @param[out] out where to write the bytes 553 * @param outsize number of bytes available in @a out 554 * @return number of bytes written 555 */ 556 static size_t 557 mbox_qp (const char *in, 558 size_t inlen, 559 char *out, 560 size_t outsize) 561 { 562 size_t o = 0; 563 564 for (size_t i = 0; (i < inlen) && (o < outsize); i++) 565 { 566 if ('_' == in[i]) 567 { 568 out[o++] = ' '; 569 continue; 570 } 571 if ( ('=' == in[i]) && 572 (i + 2 < inlen) && 573 (0 <= mbox_hex (in[i + 1])) && 574 (0 <= mbox_hex (in[i + 2])) ) 575 { 576 out[o++] = (char) ((mbox_hex (in[i + 1]) << 4) 577 | mbox_hex (in[i + 2])); 578 i += 2; 579 continue; 580 } 581 out[o++] = in[i]; 582 } 583 return o; 584 } 585 586 587 /** 588 * Which charsets we know how to turn into UTF-8. 589 */ 590 enum MboxCharset 591 { 592 /** 593 * Not a charset we handle; leave the encoded word alone. 594 */ 595 MBOX_CS_UNKNOWN = 0, 596 597 /** 598 * Already UTF-8 (or a subset of it). 599 */ 600 MBOX_CS_UTF8 = 1, 601 602 /** 603 * A single-byte charset whose lower half is ASCII; decoded as 604 * ISO-8859-1. 605 */ 606 MBOX_CS_LATIN1 = 2 607 }; 608 609 610 /** 611 * Classify a charset name from an encoded word. 612 * 613 * @param name the charset name (a `*' language suffix is tolerated) 614 * @param len number of bytes in @a name 615 * @return how to decode the octets 616 */ 617 static enum MboxCharset 618 mbox_charset (const char *name, 619 size_t len) 620 { 621 /* RFC 2231 allows a `*language' suffix on the charset of an encoded 622 word; cut it off before comparing. */ 623 for (size_t i = 0; i < len; i++) 624 if ('*' == name[i]) 625 { 626 len = i; 627 break; 628 } 629 if (mbox_ieq (name, len, "utf-8") || 630 mbox_ieq (name, len, "utf8") || 631 mbox_ieq (name, len, "us-ascii") || 632 mbox_ieq (name, len, "ascii") ) 633 return MBOX_CS_UTF8; 634 /* ISO-8859-15 and windows-1252 differ from ISO-8859-1 only in a few 635 positions; decoding them as Latin-1 gets everything else right and 636 never produces invalid UTF-8. */ 637 if (mbox_ieq (name, len, "iso-8859-1") || 638 mbox_ieq (name, len, "iso8859-1") || 639 mbox_ieq (name, len, "latin1") || 640 mbox_ieq (name, len, "iso-8859-15") || 641 mbox_ieq (name, len, "iso8859-15") || 642 mbox_ieq (name, len, "windows-1252") || 643 mbox_ieq (name, len, "cp1252") ) 644 return MBOX_CS_LATIN1; 645 return MBOX_CS_UNKNOWN; 646 } 647 648 649 /** 650 * Append @a len bytes of Latin-1 to @a out as UTF-8. 651 * 652 * @param in the bytes 653 * @param len number of bytes in @a in 654 * @param[out] out output buffer 655 * @param outsize number of bytes available in @a out 656 * @param o current write offset in @a out 657 * @return the new write offset 658 */ 659 static size_t 660 mbox_latin1 (const char *in, 661 size_t len, 662 char *out, 663 size_t outsize, 664 size_t o) 665 { 666 for (size_t i = 0; i < len; i++) 667 { 668 unsigned char c = (unsigned char) in[i]; 669 670 if (c < 0x80) 671 { 672 if (o + 1 >= outsize) 673 break; 674 out[o++] = (char) c; 675 } 676 else 677 { 678 if (o + 2 >= outsize) 679 break; 680 out[o++] = (char) (0xC0 | (c >> 6)); 681 out[o++] = (char) (0x80 | (c & 0x3F)); 682 } 683 } 684 return o; 685 } 686 687 688 /** 689 * Decode the RFC 2047 encoded words in a header value. 690 * 691 * Encoded words that are adjacent (separated only by white space) have 692 * that white space removed, as RFC 2047 section 6.2 requires -- without 693 * that, a subject split across two words gains a stray space. 694 * 695 * @param in the raw header value 696 * @param inlen number of bytes in @a in 697 * @param[out] out where to write the decoded text 698 * @param outsize number of bytes available in @a out 699 * @return number of bytes written to @a out 700 */ 701 static size_t 702 mbox_decode_words (const char *in, 703 size_t inlen, 704 char *out, 705 size_t outsize) 706 { 707 size_t i = 0; 708 size_t o = 0; 709 int prev_was_word = 0; 710 711 while ( (i < inlen) && 712 (o + 1 < outsize) ) 713 { 714 size_t cs; 715 size_t cse; 716 size_t enc; 717 size_t txt; 718 size_t end; 719 enum MboxCharset kind; 720 721 if ( ('=' == in[i]) && 722 (i + 1 < inlen) && 723 ('?' == in[i + 1]) ) 724 { 725 /* =?charset?E?text?= */ 726 cs = i + 2; 727 cse = cs; 728 while ( (cse < inlen) && 729 (cse - cs < 64) && 730 ('?' != in[cse]) ) 731 cse++; 732 if ( (cse >= inlen) || 733 ('?' != in[cse]) || 734 (cse == cs) ) 735 { 736 out[o++] = in[i++]; 737 prev_was_word = 0; 738 continue; 739 } 740 enc = cse + 1; 741 if ( (enc + 1 >= inlen) || 742 ('?' != in[enc + 1]) ) 743 { 744 out[o++] = in[i++]; 745 prev_was_word = 0; 746 continue; 747 } 748 txt = enc + 2; 749 end = txt; 750 while ( (end + 1 < inlen) && 751 (end - txt < 1024) && 752 ! ( ('?' == in[end]) && ('=' == in[end + 1]) ) ) 753 end++; 754 if ( (end + 1 >= inlen) || 755 ('?' != in[end]) ) 756 { 757 out[o++] = in[i++]; 758 prev_was_word = 0; 759 continue; 760 } 761 kind = mbox_charset (&in[cs], 762 cse - cs); 763 if (MBOX_CS_UNKNOWN == kind) 764 { 765 out[o++] = in[i++]; /* leave the word verbatim */ 766 prev_was_word = 0; 767 continue; 768 } 769 { 770 char raw[1024]; 771 size_t rlen = 0; 772 773 if ( ('b' == mbox_lc (in[enc])) ) 774 rlen = mbox_b64 (&in[txt], 775 end - txt, 776 raw, 777 sizeof (raw)); 778 else if ('q' == mbox_lc (in[enc])) 779 rlen = mbox_qp (&in[txt], 780 end - txt, 781 raw, 782 sizeof (raw)); 783 else 784 rlen = 0; 785 if (0 == rlen) 786 { 787 out[o++] = in[i++]; 788 prev_was_word = 0; 789 continue; 790 } 791 if (MBOX_CS_UTF8 == kind) 792 { 793 if (rlen > outsize - o - 1) 794 rlen = outsize - o - 1; 795 memcpy (&out[o], 796 raw, 797 rlen); 798 o += rlen; 799 } 800 else 801 { 802 o = mbox_latin1 (raw, 803 rlen, 804 out, 805 outsize, 806 o); 807 } 808 } 809 i = end + 2; 810 prev_was_word = 1; 811 continue; 812 } 813 if ( (' ' == in[i]) || 814 ('\t' == in[i]) ) 815 { 816 size_t j = i; 817 818 while ( (j < inlen) && 819 ( (' ' == in[j]) || ('\t' == in[j]) ) ) 820 j++; 821 if (prev_was_word && 822 (j + 1 < inlen) && 823 ('=' == in[j]) && ('?' == in[j + 1]) ) 824 { 825 i = j; /* white space between adjacent encoded words: drop */ 826 continue; 827 } 828 out[o++] = ' '; 829 i = j; 830 prev_was_word = 0; 831 continue; 832 } 833 out[o++] = in[i++]; 834 prev_was_word = 0; 835 } 836 return o; 837 } 838 839 840 /** 841 * Emit a header value, decoding its encoded words first. If decoding 842 * produced something that is not valid UTF-8 -- a mislabelled charset, 843 * say -- the raw value is emitted instead, which is at worst unreadable 844 * but is never wrong. 845 * 846 * @param ec extraction context 847 * @param type meta data type 848 * @param raw the raw (unfolded) header value 849 * @param rawlen number of bytes in @a raw 850 * @return 1 if the caller should stop extracting, 0 to continue 851 */ 852 static int 853 mbox_emit_hdr (struct EXTRACTOR_ExtractContext *ec, 854 enum EXTRACTOR_MetaType type, 855 const char *raw, 856 size_t rawlen) 857 { 858 char dec[EXTRACTOR_FORENSIC_MAX_STRING]; 859 size_t dlen; 860 861 dlen = mbox_decode_words (raw, 862 rawlen, 863 dec, 864 sizeof (dec)); 865 if (EXTRACTOR_forensic_utf8_valid_ (dec, 866 dlen)) 867 return EXTRACTOR_forensic_emit_text_ (ec, 868 "mbox", 869 type, 870 dec, 871 dlen); 872 return EXTRACTOR_forensic_emit_text_ (ec, 873 "mbox", 874 type, 875 raw, 876 rawlen); 877 } 878 879 880 /** 881 * Is @a s, of length @a len, a dotted-quad IPv4 address? 882 * 883 * @param s the candidate 884 * @param len number of bytes in @a s 885 * @return 1 if it is, 0 if not 886 */ 887 static int 888 mbox_is_ipv4 (const char *s, 889 size_t len) 890 { 891 size_t i = 0; 892 893 for (unsigned int part = 0; part < 4; part++) 894 { 895 unsigned int v = 0; 896 unsigned int digits = 0; 897 898 while ( (i < len) && 899 ('0' <= s[i]) && ('9' >= s[i]) && 900 (digits < 3) ) 901 { 902 v = v * 10 + (unsigned int) (s[i] - '0'); 903 i++; 904 digits++; 905 } 906 if ( (0 == digits) || 907 (v > 255) ) 908 return 0; 909 if (part < 3) 910 { 911 if ( (i >= len) || 912 ('.' != s[i]) ) 913 return 0; 914 i++; 915 } 916 } 917 return (i == len); 918 } 919 920 921 /** 922 * Is @a s, of length @a len, an IPv6 address? 923 * 924 * Deliberately conservative: hexadecimal groups separated by colons, 925 * at most one `::', at most eight groups, optionally ending in a 926 * dotted-quad (the IPv4-mapped form that shows up in `Received' lines). 927 * A zone index (`%eth0') is rejected rather than guessed at. 928 * 929 * @param s the candidate 930 * @param len number of bytes in @a s 931 * @return 1 if it is, 0 if not 932 */ 933 static int 934 mbox_is_ipv6 (const char *s, 935 size_t len) 936 { 937 unsigned int groups = 0; 938 int dbl = 0; 939 size_t i = 0; 940 size_t tail; 941 942 if ( (0 == len) || 943 (len > 45) ) 944 return 0; 945 /* a trailing dotted-quad counts as two groups */ 946 tail = len; 947 for (size_t k = 0; k < len; k++) 948 if ('.' == s[k]) 949 { 950 size_t start = 0; 951 952 for (size_t j = len; j > 0; j--) 953 if (':' == s[j - 1]) 954 { 955 start = j; 956 break; 957 } 958 if (0 == start) 959 return 0; 960 if (! mbox_is_ipv4 (&s[start], 961 len - start)) 962 return 0; 963 tail = start - 1; /* keep the colon that precedes it */ 964 groups = 2; 965 break; 966 } 967 while (i < tail) 968 { 969 unsigned int digits = 0; 970 971 if (':' == s[i]) 972 { 973 if ( (i + 1 < tail) && 974 (':' == s[i + 1]) ) 975 { 976 if (dbl) 977 return 0; /* only one `::' is allowed */ 978 dbl = 1; 979 i += 2; 980 continue; 981 } 982 if (0 == i) 983 return 0; /* a single leading colon is not valid */ 984 i++; 985 continue; 986 } 987 while ( (i < tail) && 988 (digits < 4) && 989 (0 <= mbox_hex (s[i])) ) 990 { 991 i++; 992 digits++; 993 } 994 if (0 == digits) 995 return 0; 996 groups++; 997 if (groups > 8) 998 return 0; 999 if ( (i < tail) && 1000 (':' != s[i]) ) 1001 return 0; 1002 } 1003 if ( (tail < len) && 1004 (0 == groups) ) 1005 return 0; 1006 if (dbl) 1007 return (groups <= 7); 1008 return (8 == groups); 1009 } 1010 1011 1012 /** 1013 * Remember an IP literal, ignoring duplicates. 1014 * 1015 * @param ips the list 1016 * @param s the address text 1017 * @param len number of bytes in @a s 1018 */ 1019 static void 1020 mbox_add_ip (struct MboxIps *ips, 1021 const char *s, 1022 size_t len) 1023 { 1024 /* `IPv6:' prefixes the literal inside the brackets of a Received 1025 line, per RFC 5321. */ 1026 if ( (len > 5) && 1027 mbox_ieq (s, 1028 5, 1029 "ipv6:") ) 1030 { 1031 s += 5; 1032 len -= 5; 1033 } 1034 if ( (0 == len) || 1035 (len >= sizeof (ips->ip[0])) ) 1036 return; 1037 if ( (! mbox_is_ipv4 (s, len)) && 1038 (! mbox_is_ipv6 (s, len)) ) 1039 return; 1040 for (unsigned int i = 0; i < ips->count; i++) 1041 if ( (strlen (ips->ip[i]) == len) && 1042 (0 == memcmp (ips->ip[i], s, len)) ) 1043 return; 1044 if (ips->count >= EXTRACTOR_FORENSIC_MAX_ITEMS) 1045 return; 1046 memcpy (ips->ip[ips->count], 1047 s, 1048 len); 1049 ips->ip[ips->count][len] = '\0'; 1050 ips->count++; 1051 } 1052 1053 1054 /** 1055 * Harvest the bracketed IP literals out of a `Received' value. 1056 * 1057 * @param v the value 1058 * @param vlen number of bytes in @a v 1059 * @param ips where to collect the addresses 1060 */ 1061 static void 1062 mbox_harvest_bracketed (const char *v, 1063 size_t vlen, 1064 struct MboxIps *ips) 1065 { 1066 for (size_t i = 0; i < vlen; i++) 1067 { 1068 size_t j; 1069 1070 if ('[' != v[i]) 1071 continue; 1072 j = i + 1; 1073 while ( (j < vlen) && 1074 (j - i <= 64) && 1075 (']' != v[j]) ) 1076 j++; 1077 if ( (j >= vlen) || 1078 (']' != v[j]) ) 1079 continue; 1080 mbox_add_ip (ips, 1081 &v[i + 1], 1082 j - i - 1); 1083 i = j; 1084 } 1085 } 1086 1087 1088 /** 1089 * Harvest IP literals out of a value that is a bare list of them, as in 1090 * `X-Originating-IP' and `X-Forwarded-For'. 1091 * 1092 * @param v the value 1093 * @param vlen number of bytes in @a v 1094 * @param ips where to collect the addresses 1095 */ 1096 static void 1097 mbox_harvest_tokens (const char *v, 1098 size_t vlen, 1099 struct MboxIps *ips) 1100 { 1101 size_t i = 0; 1102 1103 while (i < vlen) 1104 { 1105 size_t start; 1106 1107 while ( (i < vlen) && 1108 (NULL != strchr (" \t,;[]()<>", v[i])) ) 1109 i++; 1110 start = i; 1111 while ( (i < vlen) && 1112 (NULL == strchr (" \t,;[]()<>", v[i])) ) 1113 i++; 1114 if (i > start) 1115 mbox_add_ip (ips, 1116 &v[start], 1117 i - start); 1118 } 1119 } 1120 1121 1122 /** 1123 * Split a mailbox into its display name and its addr-spec. 1124 * 1125 * Handles `Name <a@b>', `"Name" <a@b>', `a@b' and the obsolete 1126 * `a@b (Name)'. 1127 * 1128 * @param in the mailbox text 1129 * @param len number of bytes in @a in 1130 * @param[out] nstart offset of the display name in @a in 1131 * @param[out] nlen length of the display name, 0 if there is none 1132 * @param[out] astart offset of the addr-spec in @a in 1133 * @param[out] alen length of the addr-spec, 0 if there is none 1134 */ 1135 static void 1136 mbox_split_addr (const char *in, 1137 size_t len, 1138 size_t *nstart, 1139 size_t *nlen, 1140 size_t *astart, 1141 size_t *alen) 1142 { 1143 size_t lt = (size_t) -1; 1144 size_t gt = (size_t) -1; 1145 1146 *nstart = 0; 1147 *nlen = 0; 1148 *astart = 0; 1149 *alen = 0; 1150 for (size_t i = 0; i < len; i++) 1151 if ('<' == in[i]) 1152 { 1153 lt = i; 1154 break; 1155 } 1156 if (((size_t) -1) != lt) 1157 for (size_t i = len; i > lt + 1; i--) 1158 if ('>' == in[i - 1]) 1159 { 1160 gt = i - 1; 1161 break; 1162 } 1163 if ( (((size_t) -1) != lt) && 1164 (((size_t) -1) != gt) ) 1165 { 1166 *astart = lt + 1; 1167 *alen = gt - lt - 1; 1168 *nstart = 0; 1169 *nlen = lt; 1170 } 1171 else 1172 { 1173 size_t par = (size_t) -1; 1174 1175 for (size_t i = 0; i < len; i++) 1176 if ('(' == in[i]) 1177 { 1178 par = i; 1179 break; 1180 } 1181 if (((size_t) -1) != par) 1182 { 1183 size_t close = len; 1184 1185 for (size_t i = len; i > par + 1; i--) 1186 if (')' == in[i - 1]) 1187 { 1188 close = i - 1; 1189 break; 1190 } 1191 *astart = 0; 1192 *alen = par; 1193 *nstart = par + 1; 1194 *nlen = (close > par + 1) ? close - par - 1 : 0; 1195 } 1196 else 1197 { 1198 *astart = 0; 1199 *alen = len; 1200 } 1201 } 1202 /* trim both parts and strip the quotes around a quoted display name */ 1203 while ( (*nlen > 0) && 1204 ( (' ' == in[*nstart]) || ('\t' == in[*nstart]) ) ) 1205 { 1206 (*nstart)++; 1207 (*nlen)--; 1208 } 1209 while ( (*nlen > 0) && 1210 ( (' ' == in[*nstart + *nlen - 1]) || 1211 ('\t' == in[*nstart + *nlen - 1]) ) ) 1212 (*nlen)--; 1213 if ( (*nlen >= 2) && 1214 ('"' == in[*nstart]) && 1215 ('"' == in[*nstart + *nlen - 1]) ) 1216 { 1217 (*nstart)++; 1218 *nlen -= 2; 1219 } 1220 while ( (*alen > 0) && 1221 ( (' ' == in[*astart]) || ('\t' == in[*astart]) ) ) 1222 { 1223 (*astart)++; 1224 (*alen)--; 1225 } 1226 while ( (*alen > 0) && 1227 ( (' ' == in[*astart + *alen - 1]) || 1228 ('\t' == in[*astart + *alen - 1]) ) ) 1229 (*alen)--; 1230 } 1231 1232 1233 /** 1234 * Emit each address of an address list, capped. 1235 * 1236 * The list is split on commas that are not inside a quoted string, an 1237 * angle-addr or a comment, so that a display name containing a comma 1238 * does not become two recipients. 1239 * 1240 * @param ec extraction context 1241 * @param type meta data type to report each address under 1242 * @param v the header value 1243 * @param vlen number of bytes in @a v 1244 * @param[in,out] emitted how many have been emitted so far 1245 * @return 1 if the caller should stop extracting, 0 to continue 1246 */ 1247 static int 1248 mbox_emit_addr_list (struct EXTRACTOR_ExtractContext *ec, 1249 enum EXTRACTOR_MetaType type, 1250 const char *v, 1251 size_t vlen, 1252 unsigned int *emitted) 1253 { 1254 size_t start = 0; 1255 int quote = 0; 1256 int angle = 0; 1257 int paren = 0; 1258 1259 for (size_t i = 0; i <= vlen; i++) 1260 { 1261 int split = (i == vlen); 1262 1263 if (! split) 1264 { 1265 char c = v[i]; 1266 1267 if (quote) 1268 { 1269 if ('"' == c) 1270 quote = 0; 1271 continue; 1272 } 1273 switch (c) 1274 { 1275 case '"': 1276 quote = 1; 1277 break; 1278 case '<': 1279 angle++; 1280 break; 1281 case '>': 1282 if (angle > 0) 1283 angle--; 1284 break; 1285 case '(': 1286 paren++; 1287 break; 1288 case ')': 1289 if (paren > 0) 1290 paren--; 1291 break; 1292 case ',': 1293 if ( (0 == angle) && (0 == paren) ) 1294 split = 1; 1295 break; 1296 default: 1297 break; 1298 } 1299 } 1300 if (! split) 1301 continue; 1302 if (i > start) 1303 { 1304 size_t s = start; 1305 size_t l = i - start; 1306 1307 while ( (l > 0) && 1308 ( (' ' == v[s]) || ('\t' == v[s]) ) ) 1309 { 1310 s++; 1311 l--; 1312 } 1313 while ( (l > 0) && 1314 ( (' ' == v[s + l - 1]) || ('\t' == v[s + l - 1]) ) ) 1315 l--; 1316 if (0 != l) 1317 { 1318 if (*emitted >= EXTRACTOR_FORENSIC_MAX_ITEMS) 1319 return 0; 1320 (*emitted)++; 1321 if (mbox_emit_hdr (ec, 1322 type, 1323 &v[s], 1324 l)) 1325 return 1; 1326 } 1327 } 1328 start = i + 1; 1329 } 1330 return 0; 1331 } 1332 1333 1334 /** 1335 * Find the value of a MIME parameter in a structured header value. 1336 * 1337 * Understands `name=token', `name="quoted string"', the RFC 2231 1338 * extended form `name*=charset'lang'pct-encoded' and the RFC 2231 1339 * continuation form `name*0=', `name*1=', ... which is joined back 1340 * together in index order. 1341 * 1342 * @param v the header value, after the media type 1343 * @param vlen number of bytes in @a v 1344 * @param name the parameter name, lower case 1345 * @param[out] out where to write the value 1346 * @param outsize number of bytes available in @a out 1347 * @return length of the value, 0 if the parameter is absent 1348 */ 1349 static size_t 1350 mbox_param (const char *v, 1351 size_t vlen, 1352 const char *name, 1353 char *out, 1354 size_t outsize) 1355 { 1356 size_t nlen = strlen (name); 1357 size_t i = 0; 1358 size_t o = 0; 1359 unsigned int segs = 0; 1360 int simple = 0; 1361 1362 while ( (i < vlen) && 1363 (segs < MBOX_MAX_CONT) ) 1364 { 1365 size_t ps; 1366 size_t pe; 1367 size_t vs; 1368 size_t ve; 1369 int extended = 0; 1370 int is_cont = 0; 1371 1372 /* advance to the next `;' separated parameter */ 1373 while ( (i < vlen) && 1374 (';' != v[i]) ) 1375 { 1376 if ('"' == v[i]) 1377 { 1378 i++; 1379 while ( (i < vlen) && 1380 ('"' != v[i]) ) 1381 { 1382 if ( ('\\' == v[i]) && 1383 (i + 1 < vlen) ) 1384 i++; 1385 i++; 1386 } 1387 } 1388 if (i < vlen) 1389 i++; 1390 } 1391 if (i >= vlen) 1392 break; 1393 i++; /* skip the `;' */ 1394 while ( (i < vlen) && 1395 ( (' ' == v[i]) || ('\t' == v[i]) ) ) 1396 i++; 1397 ps = i; 1398 while ( (i < vlen) && 1399 (';' != v[i]) && ('=' != v[i]) && 1400 (' ' != v[i]) && ('\t' != v[i]) ) 1401 i++; 1402 pe = i; 1403 while ( (i < vlen) && 1404 ( (' ' == v[i]) || ('\t' == v[i]) ) ) 1405 i++; 1406 if ( (i >= vlen) || 1407 ('=' != v[i]) ) 1408 continue; 1409 i++; 1410 while ( (i < vlen) && 1411 ( (' ' == v[i]) || ('\t' == v[i]) ) ) 1412 i++; 1413 if ( (i < vlen) && 1414 ('"' == v[i]) ) 1415 { 1416 i++; 1417 vs = i; 1418 while ( (i < vlen) && 1419 ('"' != v[i]) ) 1420 { 1421 if ( ('\\' == v[i]) && 1422 (i + 1 < vlen) ) 1423 i++; 1424 i++; 1425 } 1426 ve = i; 1427 if (i < vlen) 1428 i++; 1429 } 1430 else 1431 { 1432 vs = i; 1433 while ( (i < vlen) && 1434 (';' != v[i]) ) 1435 i++; 1436 ve = i; 1437 while ( (ve > vs) && 1438 ( (' ' == v[ve - 1]) || ('\t' == v[ve - 1]) ) ) 1439 ve--; 1440 } 1441 /* does the parameter name match? */ 1442 if (pe - ps < nlen) 1443 continue; 1444 if (! mbox_ieq (&v[ps], 1445 nlen, 1446 name)) 1447 continue; 1448 if (pe - ps == nlen) 1449 { 1450 simple = 1; 1451 } 1452 else if ('*' == v[ps + nlen]) 1453 { 1454 size_t k = ps + nlen + 1; 1455 1456 while ( (k < pe) && 1457 ('0' <= v[k]) && ('9' >= v[k]) ) 1458 { 1459 is_cont = 1; 1460 k++; 1461 } 1462 if ( (k < pe) && 1463 ('*' == v[k]) ) 1464 { 1465 extended = 1; 1466 k++; 1467 } 1468 if (k != pe) 1469 continue; /* something else entirely */ 1470 if (! is_cont) 1471 extended = 1; 1472 } 1473 else 1474 { 1475 continue; 1476 } 1477 if (simple && 1478 (0 != segs) ) 1479 continue; /* continuations already won */ 1480 if (extended) 1481 { 1482 /* charset'language'pct-encoded -- drop the first two fields */ 1483 size_t q = vs; 1484 unsigned int seen = 0; 1485 1486 if (! is_cont) 1487 { 1488 while ( (q < ve) && 1489 (seen < 2) ) 1490 { 1491 if ('\'' == v[q]) 1492 seen++; 1493 q++; 1494 } 1495 if (2 != seen) 1496 q = vs; 1497 } 1498 while ( (q < ve) && 1499 (o + 1 < outsize) ) 1500 { 1501 if ( ('%' == v[q]) && 1502 (q + 2 < ve) && 1503 (0 <= mbox_hex (v[q + 1])) && 1504 (0 <= mbox_hex (v[q + 2])) ) 1505 { 1506 out[o++] = (char) ((mbox_hex (v[q + 1]) << 4) 1507 | mbox_hex (v[q + 2])); 1508 q += 3; 1509 continue; 1510 } 1511 out[o++] = v[q++]; 1512 } 1513 } 1514 else 1515 { 1516 size_t q = vs; 1517 1518 while ( (q < ve) && 1519 (o + 1 < outsize) ) 1520 { 1521 if ( ('\\' == v[q]) && 1522 (q + 1 < ve) ) 1523 q++; 1524 out[o++] = v[q++]; 1525 } 1526 } 1527 segs++; 1528 if (simple) 1529 break; 1530 } 1531 if (o < outsize) 1532 out[o] = '\0'; 1533 return o; 1534 } 1535 1536 1537 /** 1538 * Walk the header block once, handing each field to @a cb. 1539 * 1540 * Continuation lines (those starting with a space or a tab) are folded 1541 * into the previous value with the fold replaced by a single space. 1542 * 1543 * @param buf the buffer 1544 * @param hstart offset of the first header line 1545 * @param hend offset one past the last header byte 1546 * @param[in,out] pos iteration cursor, initialise to @a hstart 1547 * @param[out] nstart offset of the field name 1548 * @param[out] nlen length of the field name 1549 * @param[out] val where to copy the unfolded value 1550 * @param valsize number of bytes available in @a val 1551 * @param[out] vlen number of bytes written to @a val 1552 * @return 1 if a field was returned, 0 at the end of the block 1553 */ 1554 static int 1555 mbox_next_header (const char *buf, 1556 size_t hstart, 1557 size_t hend, 1558 size_t *pos, 1559 size_t *nstart, 1560 size_t *nlen, 1561 char *val, 1562 size_t valsize, 1563 size_t *vlen) 1564 { 1565 (void) hstart; 1566 while (*pos < hend) 1567 { 1568 size_t ls = *pos; 1569 size_t le; 1570 size_t colon = (size_t) -1; 1571 size_t vs; 1572 size_t o = 0; 1573 1574 le = ls; 1575 while ( (le < hend) && 1576 ('\n' != buf[le]) ) 1577 le++; 1578 for (size_t i = ls; (i < le) && (i - ls < 128); i++) 1579 { 1580 if (':' == buf[i]) 1581 { 1582 colon = i; 1583 break; 1584 } 1585 /* a field name is printable ASCII other than the colon */ 1586 if ( (buf[i] < 33) || 1587 (buf[i] > 126) ) 1588 break; 1589 } 1590 if ( (((size_t) -1) == colon) || 1591 (colon == ls) ) 1592 { 1593 *pos = (le < hend) ? le + 1 : hend; 1594 continue; /* not a header line; skip it */ 1595 } 1596 *nstart = ls; 1597 *nlen = colon - ls; 1598 vs = colon + 1; 1599 while ( (vs < le) && 1600 ( (' ' == buf[vs]) || ('\t' == buf[vs]) ) ) 1601 vs++; 1602 { 1603 size_t e = le; 1604 1605 while ( (e > vs) && 1606 ('\r' == buf[e - 1]) ) 1607 e--; 1608 if (e - vs > valsize - 1) 1609 e = vs + valsize - 1; 1610 memcpy (val, 1611 &buf[vs], 1612 e - vs); 1613 o = e - vs; 1614 } 1615 *pos = (le < hend) ? le + 1 : hend; 1616 /* fold in the continuation lines */ 1617 while (*pos < hend) 1618 { 1619 size_t cs = *pos; 1620 size_t ce; 1621 size_t ts; 1622 1623 if ( (' ' != buf[cs]) && 1624 ('\t' != buf[cs]) ) 1625 break; 1626 ce = cs; 1627 while ( (ce < hend) && 1628 ('\n' != buf[ce]) ) 1629 ce++; 1630 ts = cs; 1631 while ( (ts < ce) && 1632 ( (' ' == buf[ts]) || ('\t' == buf[ts]) ) ) 1633 ts++; 1634 { 1635 size_t e = ce; 1636 1637 while ( (e > ts) && 1638 ('\r' == buf[e - 1]) ) 1639 e--; 1640 if ( (o + 1 < valsize) && 1641 (0 != o) ) 1642 val[o++] = ' '; 1643 if (e - ts > valsize - 1 - o) 1644 e = ts + (valsize - 1 - o); 1645 if (e > ts) 1646 { 1647 memcpy (&val[o], 1648 &buf[ts], 1649 e - ts); 1650 o += e - ts; 1651 } 1652 } 1653 *pos = (ce < hend) ? ce + 1 : hend; 1654 } 1655 val[o] = '\0'; 1656 *vlen = o; 1657 return 1; 1658 } 1659 return 0; 1660 } 1661 1662 1663 /** 1664 * Does the buffer start with something that is plausibly an RFC 5322 1665 * header block? 1666 * 1667 * Deliberately strict: "a text file with colons in it" is not a mail 1668 * message, and claiming one would be worse than missing a real message. 1669 * We require a well-formed `Name: value' block, with folded 1670 * continuations, terminated by a blank line, and at least one of the 1671 * fields a message cannot really do without. 1672 * 1673 * @param buf the buffer 1674 * @param len number of bytes in @a buf 1675 * @return 1 if this looks like a message, 0 if not 1676 */ 1677 static int 1678 mbox_looks_like_message (const char *buf, 1679 size_t len) 1680 { 1681 size_t i = 0; 1682 unsigned int fields = 0; 1683 int required = 0; 1684 1685 if (len > MBOX_MAGIC_WINDOW) 1686 len = MBOX_MAGIC_WINDOW; 1687 for (unsigned int line = 0; line < 200; line++) 1688 { 1689 size_t ls = i; 1690 size_t le; 1691 size_t colon = (size_t) -1; 1692 1693 if (ls >= len) 1694 return 0; /* ran out before the blank line: too little evidence */ 1695 le = ls; 1696 while ( (le < len) && 1697 ('\n' != buf[le]) ) 1698 le++; 1699 if ( (le == ls) || 1700 ( (le == ls + 1) && ('\r' == buf[ls]) ) ) 1701 return ( (0 != required) && (fields >= 2) ); /* blank line */ 1702 if ( (' ' == buf[ls]) || 1703 ('\t' == buf[ls]) ) 1704 { 1705 if (0 == fields) 1706 return 0; /* a continuation cannot come first */ 1707 i = (le < len) ? le + 1 : len; 1708 continue; 1709 } 1710 for (size_t k = ls; (k < le) && (k - ls < 100); k++) 1711 { 1712 if (':' == buf[k]) 1713 { 1714 colon = k; 1715 break; 1716 } 1717 if ( (buf[k] < 33) || 1718 (buf[k] > 126) ) 1719 return 0; /* not a field name */ 1720 } 1721 if ( (((size_t) -1) == colon) || 1722 (colon == ls) ) 1723 return 0; 1724 fields++; 1725 if (mbox_ieq (&buf[ls], colon - ls, "received") || 1726 mbox_ieq (&buf[ls], colon - ls, "message-id") || 1727 mbox_ieq (&buf[ls], colon - ls, "from") || 1728 mbox_ieq (&buf[ls], colon - ls, "date") || 1729 mbox_ieq (&buf[ls], colon - ls, "subject") ) 1730 required = 1; 1731 if (le >= len) 1732 return 0; 1733 i = le + 1; 1734 } 1735 return 0; 1736 } 1737 1738 1739 /** 1740 * Report the attachment names, and the charset of the first MIME part, 1741 * out of the first message's body. 1742 * 1743 * @param ec extraction context 1744 * @param buf the buffer 1745 * @param from offset of the first body byte 1746 * @param to offset one past the last body byte 1747 * @param[in,out] st plugin state, for the charset fallback 1748 * @return 1 if the caller should stop extracting, 0 to continue 1749 */ 1750 static int 1751 mbox_do_body (struct EXTRACTOR_ExtractContext *ec, 1752 const char *buf, 1753 size_t from, 1754 size_t to, 1755 struct MboxState *st) 1756 { 1757 unsigned int emitted = 0; 1758 size_t i = from; 1759 1760 if (to > from + MBOX_BODY_SCAN) 1761 to = from + MBOX_BODY_SCAN; 1762 while ( (i < to) && 1763 (emitted < EXTRACTOR_FORENSIC_MAX_ITEMS) ) 1764 { 1765 size_t ls = i; 1766 size_t nstart; 1767 size_t nlen; 1768 size_t vlen; 1769 size_t pos = ls; 1770 int is_disp; 1771 int is_type; 1772 1773 /* advance to the start of the next line first, so that we always 1774 make progress even when the line is not one we want */ 1775 { 1776 size_t le = ls; 1777 1778 while ( (le < to) && 1779 ('\n' != buf[le]) ) 1780 le++; 1781 i = (le < to) ? le + 1 : to; 1782 } 1783 is_disp = ( (ls + 20 <= to) && 1784 mbox_ieq (&buf[ls], 20, "Content-Disposition:") ); 1785 is_type = ( (ls + 13 <= to) && 1786 mbox_ieq (&buf[ls], 13, "Content-Type:") ); 1787 if ( (! is_disp) && 1788 (! is_type) ) 1789 continue; 1790 if (! mbox_next_header (buf, 1791 ls, 1792 to, 1793 &pos, 1794 &nstart, 1795 &nlen, 1796 st->val, 1797 sizeof (st->val), 1798 &vlen)) 1799 continue; 1800 i = pos; /* skip the folded continuation lines as well */ 1801 { 1802 char param[MBOX_MAX_PARAM]; 1803 size_t plen; 1804 1805 plen = mbox_param (st->val, 1806 vlen, 1807 "filename", 1808 param, 1809 sizeof (param)); 1810 if (0 == plen) 1811 plen = mbox_param (st->val, 1812 vlen, 1813 "name", 1814 param, 1815 sizeof (param)); 1816 if (0 != plen) 1817 { 1818 int seen = 0; 1819 1820 for (unsigned int k = 0; k < st->nfnames; k++) 1821 if ( (strlen (st->fnames[k]) == plen) && 1822 (0 == memcmp (st->fnames[k], 1823 param, 1824 plen)) ) 1825 { 1826 seen = 1; 1827 break; 1828 } 1829 if (! seen) 1830 { 1831 if (st->nfnames < MBOX_MAX_FILENAMES) 1832 { 1833 memcpy (st->fnames[st->nfnames], 1834 param, 1835 plen); 1836 st->fnames[st->nfnames][plen] = '\0'; 1837 st->nfnames++; 1838 } 1839 emitted++; 1840 if (mbox_emit_hdr (ec, 1841 EXTRACTOR_METATYPE_FILENAME, 1842 param, 1843 plen)) 1844 return 1; 1845 } 1846 } 1847 if (is_type && 1848 ('\0' == st->h.charset[0]) ) 1849 { 1850 plen = mbox_param (st->val, 1851 vlen, 1852 "charset", 1853 param, 1854 sizeof (param)); 1855 if (0 != plen) 1856 mbox_store (st->h.charset, 1857 sizeof (st->h.charset), 1858 param, 1859 plen); 1860 } 1861 } 1862 } 1863 return 0; 1864 } 1865 1866 1867 /** 1868 * Main entry method for the mbox extraction plugin. 1869 * 1870 * @param ec extraction context provided to the plugin 1871 */ 1872 void 1873 EXTRACTOR_mbox_extract_method (struct EXTRACTOR_ExtractContext *ec); 1874 1875 void 1876 EXTRACTOR_mbox_extract_method (struct EXTRACTOR_ExtractContext *ec) 1877 { 1878 char head[MBOX_MAGIC_WINDOW]; 1879 char *buf = NULL; 1880 struct MboxState *st = NULL; 1881 size_t hlen = 0; 1882 size_t len = 0; 1883 size_t cap; 1884 size_t hstart; 1885 size_t hend; 1886 size_t body; 1887 size_t msg1_end; 1888 size_t pos; 1889 size_t nstart; 1890 size_t nlen; 1891 size_t vlen; 1892 uint64_t fsize; 1893 uint64_t messages = 1; 1894 unsigned int received = 0; 1895 unsigned int recipients = 0; 1896 int is_mbox; 1897 int truncated; 1898 1899 /* Magic first: for a mailbox the file must literally start with 1900 `From ', and for a bare message the header block has to hold up to 1901 inspection. Nothing else gets past this point. */ 1902 { 1903 void *data; 1904 ssize_t ret; 1905 1906 if (0 != ec->seek (ec->cls, 1907 0, 1908 SEEK_SET)) 1909 return; 1910 while (hlen < sizeof (head)) 1911 { 1912 ret = ec->read (ec->cls, 1913 &data, 1914 sizeof (head) - hlen); 1915 if (0 >= ret) 1916 break; 1917 if (((size_t) ret) > sizeof (head) - hlen) 1918 return; /* the IPC layer is misbehaving */ 1919 memcpy (&head[hlen], 1920 data, 1921 (size_t) ret); 1922 hlen += (size_t) ret; 1923 } 1924 } 1925 if (hlen < 32) 1926 return; 1927 is_mbox = (0 == memcmp (head, 1928 "From ", 1929 5)); 1930 if (! is_mbox) 1931 { 1932 if (! mbox_looks_like_message (head, 1933 hlen)) 1934 return; 1935 } 1936 else 1937 { 1938 /* the envelope line must be followed by a header block */ 1939 size_t nl = 0; 1940 1941 while ( (nl < hlen) && 1942 ('\n' != head[nl]) ) 1943 nl++; 1944 if ( (nl + 1 >= hlen) || 1945 (! mbox_looks_like_message (&head[nl + 1], 1946 hlen - nl - 1)) ) 1947 return; 1948 } 1949 1950 fsize = ec->get_size (ec->cls); 1951 cap = MBOX_SCAN_CAP; 1952 if ( (UINT64_MAX != fsize) && 1953 (fsize < (uint64_t) cap) ) 1954 cap = (size_t) fsize; 1955 if (cap < hlen) 1956 cap = hlen; 1957 buf = malloc (cap); 1958 if (NULL == buf) 1959 return; 1960 st = malloc (sizeof (struct MboxState)); 1961 if (NULL == st) 1962 { 1963 free (buf); 1964 return; 1965 } 1966 memset (st, 1967 0, 1968 sizeof (struct MboxState)); 1969 if (0 != ec->seek (ec->cls, 1970 0, 1971 SEEK_SET)) 1972 goto out; 1973 while (len < cap) 1974 { 1975 void *data; 1976 ssize_t ret; 1977 1978 ret = ec->read (ec->cls, 1979 &data, 1980 cap - len); 1981 if (0 >= ret) 1982 break; 1983 if (((size_t) ret) > cap - len) 1984 break; /* the IPC layer is misbehaving */ 1985 memcpy (&buf[len], 1986 data, 1987 (size_t) ret); 1988 len += (size_t) ret; 1989 } 1990 if (len < 32) 1991 goto out; 1992 truncated = ( (UINT64_MAX == fsize) || 1993 (fsize > (uint64_t) len) ); 1994 1995 if (0 != ec->proc (ec->cls, 1996 "mbox", 1997 EXTRACTOR_METATYPE_MIMETYPE, 1998 EXTRACTOR_METAFORMAT_UTF8, 1999 "text/plain", 2000 is_mbox ? "application/mbox" : "message/rfc822", 2001 is_mbox 2002 ? strlen ("application/mbox") + 1 2003 : strlen ("message/rfc822") + 1)) 2004 goto out; 2005 2006 /* Where the first message's headers start, and where the next message 2007 begins. A line that starts with "From " is a separator; the mboxo 2008 escaping of a body line that would look like one is ">From ", which 2009 is why this counts fewer than it might otherwise. */ 2010 hstart = 0; 2011 msg1_end = len; 2012 if (is_mbox) 2013 { 2014 while ( (hstart < len) && 2015 ('\n' != buf[hstart]) ) 2016 hstart++; 2017 if (hstart < len) 2018 hstart++; 2019 for (size_t i = hstart; i + 6 <= len; i++) 2020 if ( ('\n' == buf[i]) && 2021 (0 == memcmp (&buf[i + 1], 2022 "From ", 2023 5)) ) 2024 { 2025 if (len == msg1_end) 2026 msg1_end = i + 1; 2027 messages++; 2028 } 2029 } 2030 2031 /* the header block ends at the first blank line */ 2032 hend = len; 2033 { 2034 size_t i = hstart; 2035 2036 for (unsigned int line = 0; 2037 (line < MBOX_MAX_HDR_LINES) && (i < len); 2038 line++) 2039 { 2040 size_t le = i; 2041 2042 while ( (le < len) && 2043 ('\n' != buf[le]) ) 2044 le++; 2045 if ( (le == i) || 2046 ( (le == i + 1) && ('\r' == buf[i]) ) ) 2047 { 2048 hend = i; 2049 break; 2050 } 2051 if (le >= len) 2052 { 2053 hend = len; 2054 break; 2055 } 2056 i = le + 1; 2057 if (i - hstart > MBOX_MAX_HDR_BLOCK) 2058 { 2059 hend = i; 2060 break; 2061 } 2062 } 2063 if (hend > msg1_end) 2064 hend = msg1_end; 2065 body = hend; 2066 while ( (body < msg1_end) && 2067 ( ('\r' == buf[body]) || ('\n' == buf[body]) ) ) 2068 body++; 2069 } 2070 2071 /* pass over the header fields */ 2072 pos = hstart; 2073 while (mbox_next_header (buf, 2074 hstart, 2075 hend, 2076 &pos, 2077 &nstart, 2078 &nlen, 2079 st->val, 2080 sizeof (st->val), 2081 &vlen)) 2082 { 2083 const char *n = &buf[nstart]; 2084 2085 if (mbox_ieq (n, nlen, "received")) 2086 { 2087 /* The route the message actually took, most recent hop first. 2088 Everything up to the `;' is the routing part; what follows is 2089 the receiving MTA's timestamp, which we do not need. */ 2090 size_t l = vlen; 2091 2092 for (size_t k = 0; k < vlen; k++) 2093 if (';' == st->val[k]) 2094 { 2095 l = k; 2096 break; 2097 } 2098 mbox_harvest_bracketed (st->val, 2099 vlen, 2100 &st->ips); 2101 if (received < EXTRACTOR_FORENSIC_MAX_ITEMS) 2102 { 2103 received++; 2104 if (EXTRACTOR_forensic_emit_text_ (ec, 2105 "mbox", 2106 EXTRACTOR_METATYPE_RECEIVED_FROM, 2107 st->val, 2108 l)) 2109 goto out; 2110 } 2111 continue; 2112 } 2113 if (mbox_ieq (n, nlen, "x-originating-ip") || 2114 mbox_ieq (n, nlen, "x-forwarded-for") || 2115 mbox_ieq (n, nlen, "x-sender-ip") ) 2116 { 2117 mbox_harvest_tokens (st->val, 2118 vlen, 2119 &st->ips); 2120 continue; 2121 } 2122 if (mbox_ieq (n, nlen, "from")) 2123 mbox_store (st->h.from, sizeof (st->h.from), st->val, vlen); 2124 else if (mbox_ieq (n, nlen, "to")) 2125 mbox_store (st->h.to, sizeof (st->h.to), st->val, vlen); 2126 else if (mbox_ieq (n, nlen, "cc")) 2127 mbox_store (st->h.cc, sizeof (st->h.cc), st->val, vlen); 2128 else if (mbox_ieq (n, nlen, "subject")) 2129 mbox_store (st->h.subject, sizeof (st->h.subject), st->val, vlen); 2130 else if (mbox_ieq (n, nlen, "date")) 2131 mbox_store (st->h.date, sizeof (st->h.date), st->val, vlen); 2132 else if (mbox_ieq (n, nlen, "message-id")) 2133 mbox_store (st->h.msgid, sizeof (st->h.msgid), st->val, vlen); 2134 else if (mbox_ieq (n, nlen, "in-reply-to")) 2135 mbox_store (st->h.inreplyto, sizeof (st->h.inreplyto), st->val, vlen); 2136 else if (mbox_ieq (n, nlen, "references")) 2137 mbox_store (st->h.references, sizeof (st->h.references), st->val, vlen); 2138 else if (mbox_ieq (n, nlen, "x-mailer") || 2139 mbox_ieq (n, nlen, "user-agent") ) 2140 mbox_store (st->h.mailer, sizeof (st->h.mailer), st->val, vlen); 2141 else if (mbox_ieq (n, nlen, "organization")) 2142 mbox_store (st->h.org, sizeof (st->h.org), st->val, vlen); 2143 else if (mbox_ieq (n, nlen, "content-type")) 2144 mbox_store (st->h.ctype, sizeof (st->h.ctype), st->val, vlen); 2145 else if (mbox_ieq (n, nlen, "dkim-signature")) 2146 mbox_store (st->h.dkim, sizeof (st->h.dkim), st->val, vlen); 2147 else if (mbox_ieq (n, nlen, "authentication-results")) 2148 mbox_store (st->h.authres, sizeof (st->h.authres), st->val, vlen); 2149 } 2150 2151 /* From: split into the display name and the addr-spec */ 2152 if ('\0' != st->h.from[0]) 2153 { 2154 size_t ns; 2155 size_t nl2; 2156 size_t as; 2157 size_t al; 2158 2159 mbox_split_addr (st->h.from, 2160 strlen (st->h.from), 2161 &ns, 2162 &nl2, 2163 &as, 2164 &al); 2165 if ( (0 != nl2) && 2166 mbox_emit_hdr (ec, 2167 EXTRACTOR_METATYPE_AUTHOR_NAME, 2168 &st->h.from[ns], 2169 nl2) ) 2170 goto out; 2171 if ( (0 != al) && 2172 EXTRACTOR_forensic_emit_text_ (ec, 2173 "mbox", 2174 EXTRACTOR_METATYPE_AUTHOR_EMAIL, 2175 &st->h.from[as], 2176 al) ) 2177 goto out; 2178 } 2179 if ( ('\0' != st->h.to[0]) && 2180 mbox_emit_addr_list (ec, 2181 EXTRACTOR_METATYPE_RECIPIENT, 2182 st->h.to, 2183 strlen (st->h.to), 2184 &recipients) ) 2185 goto out; 2186 if ( ('\0' != st->h.cc[0]) && 2187 mbox_emit_addr_list (ec, 2188 EXTRACTOR_METATYPE_RECIPIENT, 2189 st->h.cc, 2190 strlen (st->h.cc), 2191 &recipients) ) 2192 goto out; 2193 if ('\0' != st->h.subject[0]) 2194 { 2195 if (mbox_emit_hdr (ec, 2196 EXTRACTOR_METATYPE_SUBJECT, 2197 st->h.subject, 2198 strlen (st->h.subject))) 2199 goto out; 2200 if (mbox_emit_hdr (ec, 2201 EXTRACTOR_METATYPE_TITLE, 2202 st->h.subject, 2203 strlen (st->h.subject))) 2204 goto out; 2205 } 2206 if ('\0' != st->h.date[0]) 2207 { 2208 int64_t when; 2209 2210 if (mbox_parse_date (st->h.date, 2211 strlen (st->h.date), 2212 &when)) 2213 { 2214 if (EXTRACTOR_forensic_emit_unix_time_ (ec, 2215 "mbox", 2216 EXTRACTOR_METATYPE_CREATION_DATE, 2217 when)) 2218 goto out; 2219 } 2220 else if (EXTRACTOR_forensic_emit_text_ (ec, 2221 "mbox", 2222 EXTRACTOR_METATYPE_UNKNOWN_DATE, 2223 st->h.date, 2224 strlen (st->h.date))) 2225 { 2226 goto out; 2227 } 2228 } 2229 /* Message-ID: the right-hand side of the `@' is, for most mailers, 2230 the name of the machine that composed the message -- often an 2231 internal host name that appears nowhere else in the file. Where it 2232 does not match any domain the sender is known to use, that is a 2233 lead worth following. */ 2234 if ('\0' != st->h.msgid[0]) 2235 { 2236 const char *v = st->h.msgid; 2237 size_t l = strlen (v); 2238 2239 if ( (l >= 2) && 2240 ('<' == v[0]) && 2241 ('>' == v[l - 1]) ) 2242 { 2243 v++; 2244 l -= 2; 2245 } 2246 if (EXTRACTOR_forensic_emit_text_ (ec, 2247 "mbox", 2248 EXTRACTOR_METATYPE_MESSAGE_ID, 2249 v, 2250 l)) 2251 goto out; 2252 } 2253 { 2254 const char *v = NULL; 2255 size_t l = 0; 2256 2257 if ('\0' != st->h.inreplyto[0]) 2258 { 2259 v = st->h.inreplyto; 2260 l = strlen (v); 2261 } 2262 else if ('\0' != st->h.references[0]) 2263 { 2264 /* the last entry of References is the message being replied to */ 2265 size_t rl = strlen (st->h.references); 2266 size_t lt = (size_t) -1; 2267 2268 for (size_t i = rl; i > 0; i--) 2269 if ('<' == st->h.references[i - 1]) 2270 { 2271 lt = i - 1; 2272 break; 2273 } 2274 if (((size_t) -1) != lt) 2275 { 2276 v = &st->h.references[lt]; 2277 l = rl - lt; 2278 } 2279 } 2280 if (NULL != v) 2281 { 2282 if ( (l >= 2) && 2283 ('<' == v[0]) && 2284 ('>' == v[l - 1]) ) 2285 { 2286 v++; 2287 l -= 2; 2288 } 2289 if ( (0 != l) && 2290 EXTRACTOR_forensic_emit_text_ (ec, 2291 "mbox", 2292 EXTRACTOR_METATYPE_IN_REPLY_TO, 2293 v, 2294 l) ) 2295 goto out; 2296 } 2297 } 2298 for (unsigned int i = 0; i < st->ips.count; i++) 2299 if (EXTRACTOR_forensic_emit_text_ (ec, 2300 "mbox", 2301 EXTRACTOR_METATYPE_IP_ADDRESS, 2302 st->ips.ip[i], 2303 strlen (st->ips.ip[i]))) 2304 goto out; 2305 if ( ('\0' != st->h.mailer[0]) && 2306 mbox_emit_hdr (ec, 2307 EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE, 2308 st->h.mailer, 2309 strlen (st->h.mailer)) ) 2310 goto out; 2311 if ('\0' != st->h.dkim[0]) 2312 { 2313 /* the `d=' tag names the domain that signed the message */ 2314 char dom[256]; 2315 size_t dl; 2316 2317 dl = mbox_param (st->h.dkim, 2318 strlen (st->h.dkim), 2319 "d", 2320 dom, 2321 sizeof (dom)); 2322 if ( (0 == dl) && 2323 (strlen (st->h.dkim) > 2) && 2324 ('d' == st->h.dkim[0]) && 2325 ('=' == st->h.dkim[1]) ) 2326 { 2327 /* mbox_param() only sees parameters after a `;'; a signature that 2328 opens with the d= tag needs this. */ 2329 size_t k = 2; 2330 2331 while ( (k < strlen (st->h.dkim)) && 2332 (';' != st->h.dkim[k]) ) 2333 k++; 2334 dl = k - 2; 2335 if (dl >= sizeof (dom)) 2336 dl = sizeof (dom) - 1; 2337 memcpy (dom, 2338 &st->h.dkim[2], 2339 dl); 2340 dom[dl] = '\0'; 2341 } 2342 if ( (0 != dl) && 2343 EXTRACTOR_forensic_emit_text_ (ec, 2344 "mbox", 2345 EXTRACTOR_METATYPE_SIGNER, 2346 dom, 2347 dl) ) 2348 goto out; 2349 } 2350 if ( ('\0' != st->h.authres[0]) && 2351 EXTRACTOR_forensic_emit_ (ec, 2352 "mbox", 2353 EXTRACTOR_METATYPE_COMMENT, 2354 "Authentication-Results: %s", 2355 st->h.authres) ) 2356 goto out; 2357 if ( ('\0' != st->h.org[0]) && 2358 mbox_emit_hdr (ec, 2359 EXTRACTOR_METATYPE_ORGANIZATION, 2360 st->h.org, 2361 strlen (st->h.org)) ) 2362 goto out; 2363 if ('\0' != st->h.ctype[0]) 2364 { 2365 size_t l = strlen (st->h.ctype); 2366 size_t m = 0; 2367 char param[MBOX_MAX_PARAM]; 2368 size_t plen; 2369 2370 while ( (m < l) && 2371 (';' != st->h.ctype[m]) ) 2372 m++; 2373 if ( (0 != m) && 2374 EXTRACTOR_forensic_emit_text_ (ec, 2375 "mbox", 2376 EXTRACTOR_METATYPE_FORMAT, 2377 st->h.ctype, 2378 m) ) 2379 goto out; 2380 plen = mbox_param (st->h.ctype, 2381 l, 2382 "charset", 2383 param, 2384 sizeof (param)); 2385 if (0 != plen) 2386 mbox_store (st->h.charset, 2387 sizeof (st->h.charset), 2388 param, 2389 plen); 2390 plen = mbox_param (st->h.ctype, 2391 l, 2392 "boundary", 2393 param, 2394 sizeof (param)); 2395 if ( (0 != plen) && 2396 EXTRACTOR_forensic_emit_ (ec, 2397 "mbox", 2398 EXTRACTOR_METATYPE_COMMENT, 2399 "MIME boundary: %.*s", 2400 (int) plen, 2401 param) ) 2402 goto out; 2403 } 2404 /* Attachment names, and -- when the message itself is multipart, so 2405 that its own Content-Type carries no charset -- the charset of the 2406 first part. */ 2407 if ( (body < msg1_end) && 2408 mbox_do_body (ec, 2409 buf, 2410 body, 2411 msg1_end, 2412 st) ) 2413 goto out; 2414 if ( ('\0' != st->h.charset[0]) && 2415 EXTRACTOR_forensic_emit_text_ (ec, 2416 "mbox", 2417 EXTRACTOR_METATYPE_CHARACTER_SET, 2418 st->h.charset, 2419 strlen (st->h.charset)) ) 2420 goto out; 2421 if (EXTRACTOR_forensic_emit_ (ec, 2422 "mbox", 2423 EXTRACTOR_METATYPE_ENTRY_COUNT, 2424 "%llu", 2425 (unsigned long long) messages)) 2426 goto out; 2427 if (truncated && 2428 EXTRACTOR_forensic_emit_ (ec, 2429 "mbox", 2430 EXTRACTOR_METATYPE_COMMENT, 2431 "scan truncated at 256 KiB; the message" 2432 " count above covers only that prefix of" 2433 " the file") ) 2434 goto out; 2435 out: 2436 free (st); 2437 free (buf); 2438 } 2439 2440 2441 /* end of mbox_extractor.c */