libextractor

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

lnk_extractor.c (25222B)


      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/lnk_extractor.c
     22  * @brief plugin to support Windows shell links (.lnk shortcuts)
     23  * @author Christian Grothoff
     24  *
     25  * A shell link describes a file that is somewhere else, and in doing so
     26  * it records the machine that file was on: the volume serial number and
     27  * label, the target's own timestamps and size, and -- in the link
     28  * tracker block Windows adds so that a moved target can be found again
     29  * -- the NetBIOS name of the creating machine and a pair of object
     30  * identifiers.  Those identifiers are version 1 UUIDs, so their node
     31  * field is the MAC address of the network card that generated them.
     32  * None of that survives in the file the link points at, which is why a
     33  * .lnk is worth more to an investigator than its 2 KB suggest.
     34  *
     35  * Reference: [MS-SHLLINK], "Shell Link (.LNK) Binary File Format",
     36  * https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/
     37  */
     38 #include "platform.h"
     39 #include "extractor.h"
     40 #include "forensics.h"
     41 
     42 
     43 /**
     44  * Size of the ShellLinkHeader, and the value its first field must have.
     45  */
     46 #define LNK_HEADER_SIZE 0x4C
     47 
     48 /**
     49  * How much of the file we read.  Shell links are a couple of kilobytes;
     50  * anything past this is a property store or an icon we do not parse.
     51  */
     52 #define LNK_MAX_READ (64 * 1024)
     53 
     54 /**
     55  * Most ExtraData blocks we walk.
     56  */
     57 #define LNK_MAX_BLOCKS 32
     58 
     59 /**
     60  * Longest path we assemble from a base and a suffix, in bytes.
     61  */
     62 #define LNK_MAX_PATH 1024
     63 
     64 /* LinkFlags */
     65 #define LNK_HAS_ID_LIST 0x00000001
     66 #define LNK_HAS_LINK_INFO 0x00000002
     67 #define LNK_HAS_NAME 0x00000004
     68 #define LNK_HAS_RELATIVE_PATH 0x00000008
     69 #define LNK_HAS_WORKING_DIR 0x00000010
     70 #define LNK_HAS_ARGUMENTS 0x00000020
     71 #define LNK_HAS_ICON_LOCATION 0x00000040
     72 #define LNK_IS_UNICODE 0x00000080
     73 
     74 
     75 /**
     76  * The class identifier every shell link starts with,
     77  * 00021401-0000-0000-C000-000000000046, as it is stored: the first
     78  * three fields little-endian, the rest in order.
     79  */
     80 static const unsigned char lnk_clsid[16] = {
     81   0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
     82   0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46
     83 };
     84 
     85 
     86 /**
     87  * Name of a drive type as recorded in a VolumeID.
     88  *
     89  * @param type the raw field
     90  * @return a static string, NULL if the value is not one we know
     91  */
     92 static const char *
     93 lnk_drive_type (uint32_t type)
     94 {
     95   switch (type)
     96   {
     97   case 0:
     98     return "unknown";
     99   case 1:
    100     return "no root directory";
    101   case 2:
    102     return "removable";
    103   case 3:
    104     return "fixed disk";
    105   case 4:
    106     return "network share";
    107   case 5:
    108     return "CD-ROM";
    109   case 6:
    110     return "RAM disk";
    111   default:
    112     return NULL;
    113   }
    114 }
    115 
    116 
    117 /**
    118  * Append @a what to the comma-separated list in @a buf.
    119  *
    120  * @param buf destination buffer
    121  * @param size number of bytes in @a buf
    122  * @param what text to append
    123  */
    124 static void
    125 lnk_append (char *buf,
    126             size_t size,
    127             const char *what)
    128 {
    129   size_t used = strlen (buf);
    130   size_t need = strlen (what);
    131 
    132   if (0 != used)
    133   {
    134     if (used + 2 >= size)
    135       return;
    136     buf[used++] = ',';
    137     buf[used++] = ' ';
    138     buf[used] = '\0';
    139   }
    140   if (used + need + 1 > size)
    141     return;
    142   memcpy (&buf[used],
    143           what,
    144           need + 1);
    145 }
    146 
    147 
    148 /**
    149  * Emit a path made of a base and a suffix, either of which may be
    150  * empty.
    151  *
    152  * [MS-SHLLINK] splits the target path into a part that identifies the
    153  * volume and a part that is common to the local and the network
    154  * spelling, and both halves are NUL-terminated in the file.  Joining
    155  * them here keeps the caller from having to care.
    156  *
    157  * @param ec extraction context
    158  * @param type meta data type to report under
    159  * @param base first half
    160  * @param base_len number of bytes readable at @a base
    161  * @param suffix second half, NULL for none
    162  * @param suffix_len number of bytes readable at @a suffix
    163  * @param unicode 1 if both halves are UTF-16LE, 0 for single bytes
    164  * @return 1 if the caller should stop extracting, 0 to continue
    165  */
    166 static int
    167 lnk_emit_path (struct EXTRACTOR_ExtractContext *ec,
    168                enum EXTRACTOR_MetaType type,
    169                const unsigned char *base,
    170                size_t base_len,
    171                const unsigned char *suffix,
    172                size_t suffix_len,
    173                int unicode)
    174 {
    175   unsigned char buf[LNK_MAX_PATH];
    176   size_t out = 0;
    177   size_t step = unicode ? 2 : 1;
    178 
    179   if ( (NULL == base) ||
    180        (0 == base_len) )
    181     return 0;
    182   while ( (out + step <= base_len) &&
    183           (out + step <= sizeof (buf)) )
    184   {
    185     if (unicode)
    186     {
    187       if (0 == EXTRACTOR_forensic_le16_ (&base[out]))
    188         break;
    189     }
    190     else if ('\0' == base[out])
    191     {
    192       break;
    193     }
    194     buf[out] = base[out];
    195     if (unicode)
    196       buf[out + 1] = base[out + 1];
    197     out += step;
    198   }
    199   if (NULL != suffix)
    200   {
    201     size_t i = 0;
    202 
    203     while ( (i + step <= suffix_len) &&
    204             (out + step <= sizeof (buf)) )
    205     {
    206       if (unicode)
    207       {
    208         if (0 == EXTRACTOR_forensic_le16_ (&suffix[i]))
    209           break;
    210       }
    211       else if ('\0' == suffix[i])
    212       {
    213         break;
    214       }
    215       buf[out] = suffix[i];
    216       if (unicode)
    217         buf[out + 1] = suffix[i + 1];
    218       out += step;
    219       i += step;
    220     }
    221   }
    222   if (0 == out)
    223     return 0;
    224   if (unicode)
    225     return EXTRACTOR_forensic_emit_utf16le_ (ec,
    226                                              "lnk",
    227                                              type,
    228                                              buf,
    229                                              out);
    230   return EXTRACTOR_forensic_emit_text_ (ec,
    231                                         "lnk",
    232                                         type,
    233                                         (const char *) buf,
    234                                         out);
    235 }
    236 
    237 
    238 /**
    239  * Report the volume the target lives on.
    240  *
    241  * The drive serial number is assigned when the volume is formatted and
    242  * is the cheapest way to tell whether two shortcuts came off the same
    243  * disk.
    244  *
    245  * @param ec extraction context
    246  * @param v start of the VolumeID structure
    247  * @param len number of bytes readable at @a v
    248  * @return 1 if the caller should stop extracting, 0 to continue
    249  */
    250 static int
    251 lnk_parse_volume_id (struct EXTRACTOR_ExtractContext *ec,
    252                      const unsigned char *v,
    253                      size_t len)
    254 {
    255   uint32_t size;
    256   uint32_t drive_type;
    257   uint32_t serial;
    258   uint32_t label;
    259   const char *name;
    260 
    261   if (len < 0x10)
    262     return 0;
    263   size = EXTRACTOR_forensic_le32_ (&v[0]);
    264   if (size < 0x10)
    265     return 0;
    266   if (size < len)
    267     len = size;
    268   drive_type = EXTRACTOR_forensic_le32_ (&v[4]);
    269   serial = EXTRACTOR_forensic_le32_ (&v[8]);
    270   label = EXTRACTOR_forensic_le32_ (&v[12]);
    271   if (0 != serial)
    272   {
    273     /* The spelling Windows itself uses in `dir' and `vol'. */
    274     if (0 !=
    275         EXTRACTOR_forensic_emit_ (ec,
    276                                   "lnk",
    277                                   EXTRACTOR_METATYPE_VOLUME_SERIAL,
    278                                   "%04X-%04X",
    279                                   (unsigned int) (serial >> 16),
    280                                   (unsigned int) (serial & 0xFFFF)))
    281       return 1;
    282   }
    283   if (NULL != (name = lnk_drive_type (drive_type)))
    284   {
    285     if (0 !=
    286         EXTRACTOR_forensic_emit_ (ec,
    287                                   "lnk",
    288                                   EXTRACTOR_METATYPE_FILESYSTEM_TYPE,
    289                                   "%s",
    290                                   name))
    291       return 1;
    292   }
    293   if (0x14 == label)
    294   {
    295     /* The ANSI label is still there, but the unicode one supersedes it. */
    296     uint32_t wide;
    297 
    298     if (len < 0x14)
    299       return 0;
    300     wide = EXTRACTOR_forensic_le32_ (&v[0x10]);
    301     if ( (wide < len) &&
    302          (wide >= 0x14) )
    303       return EXTRACTOR_forensic_emit_utf16le_ (ec,
    304                                                "lnk",
    305                                                EXTRACTOR_METATYPE_VOLUME_NAME,
    306                                                &v[wide],
    307                                                len - wide);
    308     return 0;
    309   }
    310   if ( (label < len) &&
    311        (label >= 0x10) )
    312     return EXTRACTOR_forensic_emit_text_ (ec,
    313                                           "lnk",
    314                                           EXTRACTOR_METATYPE_VOLUME_NAME,
    315                                           (const char *) &v[label],
    316                                           len - label);
    317   return 0;
    318 }
    319 
    320 
    321 /**
    322  * Report what the LinkInfo structure says about the target's location.
    323  *
    324  * @param ec extraction context
    325  * @param li start of the LinkInfo structure
    326  * @param len number of bytes readable at @a li
    327  * @return 1 if the caller should stop extracting, 0 to continue
    328  */
    329 static int
    330 lnk_parse_link_info (struct EXTRACTOR_ExtractContext *ec,
    331                      const unsigned char *li,
    332                      size_t len)
    333 {
    334   uint32_t header_size;
    335   uint32_t flags;
    336   uint32_t suffix_off;
    337   const unsigned char *suffix = NULL;
    338   size_t suffix_len = 0;
    339   int wide = 0;
    340 
    341   if (len < 0x1C)
    342     return 0;
    343   header_size = EXTRACTOR_forensic_le32_ (&li[4]);
    344   flags = EXTRACTOR_forensic_le32_ (&li[8]);
    345   /* A header of 0x24 or more means the unicode spellings of the two
    346      path halves are present and are the ones to prefer. */
    347   if ( (header_size >= 0x24) &&
    348        (len >= 0x24) )
    349     wide = 1;
    350   suffix_off = wide
    351                ? EXTRACTOR_forensic_le32_ (&li[0x20])
    352                : EXTRACTOR_forensic_le32_ (&li[0x18]);
    353   if ( (0 != suffix_off) &&
    354        (suffix_off < len) )
    355   {
    356     suffix = &li[suffix_off];
    357     suffix_len = len - suffix_off;
    358   }
    359   if (0 != (flags & 0x1))
    360   {
    361     uint32_t vol_off = EXTRACTOR_forensic_le32_ (&li[0x0C]);
    362     uint32_t base_off = wide
    363                         ? EXTRACTOR_forensic_le32_ (&li[0x1C])
    364                         : EXTRACTOR_forensic_le32_ (&li[0x10]);
    365 
    366     if ( (0 != vol_off) &&
    367          (vol_off < len) &&
    368          (0 !=
    369           lnk_parse_volume_id (ec,
    370                                &li[vol_off],
    371                                len - vol_off)) )
    372       return 1;
    373     if ( (0 != base_off) &&
    374          (base_off < len) &&
    375          (0 !=
    376           lnk_emit_path (ec,
    377                          EXTRACTOR_METATYPE_TARGET_PATH,
    378                          &li[base_off],
    379                          len - base_off,
    380                          suffix,
    381                          suffix_len,
    382                          wide)) )
    383       return 1;
    384   }
    385   if (0 != (flags & 0x2))
    386   {
    387     uint32_t cnrl_off = EXTRACTOR_forensic_le32_ (&li[0x14]);
    388     const unsigned char *cnrl;
    389     size_t cnrl_len;
    390     uint32_t net_off;
    391     int net_wide = 0;
    392 
    393     if ( (0 == cnrl_off) ||
    394          (cnrl_off >= len) )
    395       return 0;
    396     cnrl = &li[cnrl_off];
    397     cnrl_len = len - cnrl_off;
    398     if (cnrl_len < 0x14)
    399       return 0;
    400     net_off = EXTRACTOR_forensic_le32_ (&cnrl[8]);
    401     if ( (net_off > 0x14) &&
    402          (cnrl_len >= 0x18) )
    403     {
    404       uint32_t wide_off = EXTRACTOR_forensic_le32_ (&cnrl[0x14]);
    405 
    406       if ( (0 != wide_off) &&
    407            (wide_off < cnrl_len) )
    408       {
    409         net_off = wide_off;
    410         net_wide = 1;
    411       }
    412     }
    413     if ( (0 != net_off) &&
    414          (net_off < cnrl_len) &&
    415          (0 !=
    416           lnk_emit_path (ec,
    417                          EXTRACTOR_METATYPE_TARGET_PATH,
    418                          &cnrl[net_off],
    419                          cnrl_len - net_off,
    420                          net_wide ? NULL : suffix,
    421                          net_wide ? 0 : suffix_len,
    422                          net_wide)) )
    423       return 1;
    424   }
    425   return 0;
    426 }
    427 
    428 
    429 /**
    430  * Report a droid identifier, and the MAC address hiding in it.
    431  *
    432  * The two identifiers in a TrackerDataBlock are the NTFS object
    433  * identifiers of the volume and of the file, generated as version 1
    434  * UUIDs.  A version 1 UUID ends in the node field, which the generator
    435  * fills with the MAC address of a network interface unless it had none
    436  * -- in which case it must set the multicast bit to say so.  Checking
    437  * both the version and that bit is what keeps a random identifier from
    438  * being reported as somebody's hardware address.
    439  *
    440  * @param ec extraction context
    441  * @param guid the 16 bytes
    442  * @param[in,out] seen previously emitted identifier, to suppress the
    443  *        usual case where the birth and current values are the same
    444  * @return 1 if the caller should stop extracting, 0 to continue
    445  */
    446 static int
    447 lnk_emit_droid (struct EXTRACTOR_ExtractContext *ec,
    448                 const unsigned char *guid,
    449                 unsigned char *seen)
    450 {
    451   if (0 == memcmp (seen,
    452                    guid,
    453                    16))
    454     return 0;
    455   memcpy (seen,
    456           guid,
    457           16);
    458   if (0 !=
    459       EXTRACTOR_forensic_emit_guid_ (ec,
    460                                      "lnk",
    461                                      EXTRACTOR_METATYPE_SYSTEM_IDENTIFIER,
    462                                      guid,
    463                                      1))
    464     return 1;
    465   if (1 != (guid[7] >> 4))
    466     return 0;   /* not a time-based UUID, so no node field */
    467   if (0 != (guid[10] & 0x01))
    468     return 0;   /* the generator flagged the node as made up */
    469   if ( (0 == guid[10]) && (0 == guid[11]) && (0 == guid[12]) &&
    470        (0 == guid[13]) && (0 == guid[14]) && (0 == guid[15]) )
    471     return 0;
    472   return EXTRACTOR_forensic_emit_ (ec,
    473                                    "lnk",
    474                                    EXTRACTOR_METATYPE_MAC_ADDRESS,
    475                                    "%02x:%02x:%02x:%02x:%02x:%02x",
    476                                    guid[10], guid[11], guid[12],
    477                                    guid[13], guid[14], guid[15]);
    478 }
    479 
    480 
    481 /**
    482  * Report a TrackerDataBlock.
    483  *
    484  * @param ec extraction context
    485  * @param b start of the block
    486  * @param len number of bytes readable at @a b
    487  * @return 1 if the caller should stop extracting, 0 to continue
    488  */
    489 static int
    490 lnk_parse_tracker (struct EXTRACTOR_ExtractContext *ec,
    491                    const unsigned char *b,
    492                    size_t len)
    493 {
    494   unsigned char seen[16];
    495 
    496   if (len < 0x60)
    497     return 0;
    498   if (EXTRACTOR_forensic_le32_ (&b[8]) < 0x58)
    499     return 0;   /* the declared payload is too short to hold the fields */
    500   if (0 !=
    501       EXTRACTOR_forensic_emit_text_ (ec,
    502                                      "lnk",
    503                                      EXTRACTOR_METATYPE_SOURCE_HOST,
    504                                      (const char *) &b[0x10],
    505                                      16))
    506     return 1;
    507   memset (seen,
    508           0,
    509           sizeof (seen));
    510   if (0 !=
    511       lnk_emit_droid (ec,
    512                       &b[0x20],
    513                       seen))
    514     return 1;   /* DroidVolumeId */
    515   if (0 !=
    516       lnk_emit_droid (ec,
    517                       &b[0x40],
    518                       seen))
    519     return 1;   /* BirthVolumeId */
    520   memset (seen,
    521           0,
    522           sizeof (seen));
    523   if (0 !=
    524       lnk_emit_droid (ec,
    525                       &b[0x30],
    526                       seen))
    527     return 1;   /* DroidFileId */
    528   return lnk_emit_droid (ec,
    529                          &b[0x50],
    530                          seen);   /* BirthFileId */
    531 }
    532 
    533 
    534 /**
    535  * Walk the ExtraData block chain at the end of the file.
    536  *
    537  * @param ec extraction context
    538  * @param buf the file contents
    539  * @param len number of bytes in @a buf
    540  * @param pos offset of the first block
    541  * @return 1 if the caller should stop extracting, 0 to continue
    542  */
    543 static int
    544 lnk_parse_extra (struct EXTRACTOR_ExtractContext *ec,
    545                  const unsigned char *buf,
    546                  size_t len,
    547                  size_t pos)
    548 {
    549   for (unsigned int n = 0;
    550        (pos + 8 <= len) && (n < LNK_MAX_BLOCKS);
    551        n++)
    552   {
    553     uint32_t size = EXTRACTOR_forensic_le32_ (&buf[pos]);
    554     uint32_t sig = EXTRACTOR_forensic_le32_ (&buf[pos + 4]);
    555     size_t have;
    556 
    557     if (size < 8)
    558       break;   /* the terminal block, or a size that cannot progress */
    559     have = len - pos;
    560     if (have > size)
    561       have = size;
    562     switch (sig)
    563     {
    564     case 0xA0000001U:   /* EnvironmentVariableDataBlock */
    565       /* An expandable path, used when the target is behind an
    566          environment variable.  The unicode copy starts at 0x110. */
    567       if (have >= 0x314)
    568       {
    569         if (0 !=
    570             lnk_emit_path (ec,
    571                            EXTRACTOR_METATYPE_TARGET_PATH,
    572                            &buf[pos + 0x110],
    573                            0x204,
    574                            NULL,
    575                            0,
    576                            1))
    577           return 1;
    578       }
    579       else if (have >= 0x110)
    580       {
    581         if (0 !=
    582             lnk_emit_path (ec,
    583                            EXTRACTOR_METATYPE_TARGET_PATH,
    584                            &buf[pos + 0x0C],
    585                            0x104,
    586                            NULL,
    587                            0,
    588                            0))
    589           return 1;
    590       }
    591       break;
    592     case 0xA0000003U:   /* TrackerDataBlock */
    593       if (0 !=
    594           lnk_parse_tracker (ec,
    595                              &buf[pos],
    596                              have))
    597         return 1;
    598       break;
    599     case 0xA0000006U:   /* DarwinDataBlock */
    600       /* The Windows Installer product code the shortcut belongs to. */
    601       if (have >= 0x314)
    602       {
    603         if (0 !=
    604             lnk_emit_path (ec,
    605                            EXTRACTOR_METATYPE_APPLICATION_ID,
    606                            &buf[pos + 0x110],
    607                            0x204,
    608                            NULL,
    609                            0,
    610                            1))
    611           return 1;
    612       }
    613       break;
    614     default:
    615       break;
    616     }
    617     pos += size;
    618   }
    619   return 0;
    620 }
    621 
    622 
    623 /**
    624  * Main entry method for the shell link extraction plugin.
    625  *
    626  * @param ec extraction context provided to the plugin
    627  */
    628 void
    629 EXTRACTOR_lnk_extract_method (struct EXTRACTOR_ExtractContext *ec);
    630 
    631 void
    632 EXTRACTOR_lnk_extract_method (struct EXTRACTOR_ExtractContext *ec)
    633 {
    634   unsigned char head[LNK_HEADER_SIZE];
    635   unsigned char *buf;
    636   uint64_t fsize;
    637   size_t len;
    638   size_t pos;
    639   uint32_t flags;
    640   uint32_t attrs;
    641   uint32_t target_size;
    642   uint64_t created;
    643   uint64_t accessed;
    644   uint64_t written;
    645   int wide;
    646   char list[256];
    647   static const struct
    648   {
    649     uint32_t flag;
    650     enum EXTRACTOR_MetaType type;
    651   } strings[] = {
    652     { LNK_HAS_NAME, EXTRACTOR_METATYPE_DESCRIPTION },
    653     { LNK_HAS_RELATIVE_PATH, EXTRACTOR_METATYPE_TARGET_PATH },
    654     { LNK_HAS_WORKING_DIR, EXTRACTOR_METATYPE_WORKING_DIRECTORY },
    655     { LNK_HAS_ARGUMENTS, EXTRACTOR_METATYPE_COMMAND_LINE },
    656     { LNK_HAS_ICON_LOCATION, EXTRACTOR_METATYPE_URI }
    657   };
    658 
    659   /* The magic is a fixed size and a fixed class identifier at offset
    660      zero, so a non-match costs one short read. */
    661   if (! EXTRACTOR_forensic_read_ (ec,
    662                                   0,
    663                                   head,
    664                                   sizeof (head)))
    665     return;
    666   if (LNK_HEADER_SIZE != EXTRACTOR_forensic_le32_ (&head[0]))
    667     return;
    668   if (0 != memcmp (&head[4],
    669                    lnk_clsid,
    670                    sizeof (lnk_clsid)))
    671     return;
    672   if (0 !=
    673       ec->proc (ec->cls,
    674                 "lnk",
    675                 EXTRACTOR_METATYPE_MIMETYPE,
    676                 EXTRACTOR_METAFORMAT_UTF8,
    677                 "text/plain",
    678                 "application/x-ms-shortcut",
    679                 strlen ("application/x-ms-shortcut") + 1))
    680     return;
    681   flags = EXTRACTOR_forensic_le32_ (&head[0x14]);
    682   attrs = EXTRACTOR_forensic_le32_ (&head[0x18]);
    683   target_size = EXTRACTOR_forensic_le32_ (&head[0x34]);
    684   wide = (0 != (flags & LNK_IS_UNICODE));
    685   /* These three are the target's timestamps as they were when the
    686      shortcut was made, not the shortcut's own. */
    687   created = EXTRACTOR_forensic_le64_ (&head[0x1C]);
    688   accessed = EXTRACTOR_forensic_le64_ (&head[0x24]);
    689   written = EXTRACTOR_forensic_le64_ (&head[0x2C]);
    690   if (0 !=
    691       EXTRACTOR_forensic_emit_filetime_ (ec,
    692                                          "lnk",
    693                                          EXTRACTOR_METATYPE_CREATION_DATE,
    694                                          created))
    695     return;
    696   if (0 !=
    697       EXTRACTOR_forensic_emit_filetime_ (ec,
    698                                          "lnk",
    699                                          EXTRACTOR_METATYPE_ACCESS_DATE,
    700                                          accessed))
    701     return;
    702   if (0 !=
    703       EXTRACTOR_forensic_emit_filetime_ (ec,
    704                                          "lnk",
    705                                          EXTRACTOR_METATYPE_MODIFICATION_DATE,
    706                                          written))
    707     return;
    708   if ( (0 != target_size) &&
    709        (0 !=
    710         EXTRACTOR_forensic_emit_size_ (ec,
    711                                        "lnk",
    712                                        EXTRACTOR_METATYPE_EMBEDDED_FILE_SIZE,
    713                                        target_size)) )
    714     return;
    715   list[0] = '\0';
    716   if (0 != (attrs & 0x0001))
    717     lnk_append (list, sizeof (list), "READONLY");
    718   if (0 != (attrs & 0x0002))
    719     lnk_append (list, sizeof (list), "HIDDEN");
    720   if (0 != (attrs & 0x0004))
    721     lnk_append (list, sizeof (list), "SYSTEM");
    722   if (0 != (attrs & 0x0010))
    723     lnk_append (list, sizeof (list), "DIRECTORY");
    724   if (0 != (attrs & 0x0020))
    725     lnk_append (list, sizeof (list), "ARCHIVE");
    726   if (0 != (attrs & 0x0080))
    727     lnk_append (list, sizeof (list), "NORMAL");
    728   if (0 != (attrs & 0x0100))
    729     lnk_append (list, sizeof (list), "TEMPORARY");
    730   if (0 != (attrs & 0x0200))
    731     lnk_append (list, sizeof (list), "SPARSE");
    732   if (0 != (attrs & 0x0400))
    733     lnk_append (list, sizeof (list), "REPARSE_POINT");
    734   if (0 != (attrs & 0x0800))
    735     lnk_append (list, sizeof (list), "COMPRESSED");
    736   if (0 != (attrs & 0x1000))
    737     lnk_append (list, sizeof (list), "OFFLINE");
    738   if (0 != (attrs & 0x2000))
    739     lnk_append (list, sizeof (list), "NOT_CONTENT_INDEXED");
    740   if (0 != (attrs & 0x4000))
    741     lnk_append (list, sizeof (list), "ENCRYPTED");
    742   if ( ('\0' != list[0]) &&
    743        (0 !=
    744         EXTRACTOR_forensic_emit_ (ec,
    745                                   "lnk",
    746                                   EXTRACTOR_METATYPE_ATTRIBUTES,
    747                                   "%s",
    748                                   list)) )
    749     return;
    750 
    751   fsize = ec->get_size (ec->cls);
    752   if ( (0 == fsize) ||
    753        (UINT64_MAX == fsize) )
    754     return;
    755   len = (fsize > LNK_MAX_READ) ? LNK_MAX_READ : (size_t) fsize;
    756   if (len <= LNK_HEADER_SIZE)
    757     return;
    758   if (NULL == (buf = malloc (len)))
    759     return;
    760   if (! EXTRACTOR_forensic_read_ (ec,
    761                                   0,
    762                                   buf,
    763                                   len))
    764   {
    765     free (buf);
    766     return;
    767   }
    768   pos = LNK_HEADER_SIZE;
    769   if (0 != (flags & LNK_HAS_ID_LIST))
    770   {
    771     size_t id_size;
    772 
    773     if (pos + 2 > len)
    774       goto cleanup;
    775     id_size = EXTRACTOR_forensic_le16_ (&buf[pos]);
    776     pos += 2;
    777     if (id_size > len - pos)
    778       goto cleanup;
    779     pos += id_size;
    780   }
    781   if (0 != (flags & LNK_HAS_LINK_INFO))
    782   {
    783     uint32_t li_size;
    784     size_t have;
    785 
    786     if (pos + 4 > len)
    787       goto cleanup;
    788     li_size = EXTRACTOR_forensic_le32_ (&buf[pos]);
    789     if (li_size < 0x1C)
    790       goto cleanup;
    791     have = len - pos;
    792     if (have > li_size)
    793       have = li_size;
    794     if (0 !=
    795         lnk_parse_link_info (ec,
    796                              &buf[pos],
    797                              have))
    798       goto cleanup;
    799     if (li_size > len - pos)
    800       goto cleanup;
    801     pos += li_size;
    802   }
    803   for (unsigned int i = 0; i < sizeof (strings) / sizeof (strings[0]); i++)
    804   {
    805     size_t count;
    806     size_t bytes;
    807 
    808     if (0 == (flags & strings[i].flag))
    809       continue;
    810     if (pos + 2 > len)
    811       goto cleanup;
    812     count = EXTRACTOR_forensic_le16_ (&buf[pos]);
    813     pos += 2;
    814     bytes = wide ? count * 2 : count;
    815     if (bytes > len - pos)
    816       goto cleanup;
    817     if (0 != bytes)
    818     {
    819       int stop;
    820 
    821       if (wide)
    822         stop = EXTRACTOR_forensic_emit_utf16le_ (ec,
    823                                                  "lnk",
    824                                                  strings[i].type,
    825                                                  &buf[pos],
    826                                                  bytes);
    827       else
    828         stop = EXTRACTOR_forensic_emit_text_ (ec,
    829                                               "lnk",
    830                                               strings[i].type,
    831                                               (const char *) &buf[pos],
    832                                               bytes);
    833       if (0 != stop)
    834         goto cleanup;
    835     }
    836     pos += bytes;
    837   }
    838   (void) lnk_parse_extra (ec,
    839                           buf,
    840                           len,
    841                           pos);
    842 cleanup:
    843   free (buf);
    844 }
    845 
    846 
    847 /* end of lnk_extractor.c */