libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

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 */