/* Used to process orphan directories and make sure that their contents * are now marked as reachable */ static TSK_WALK_RET_ENUM load_orphan_dir_walk_cb(TSK_FS_FILE * a_fs_file, const char *a_path, void *a_ptr) { FIND_ORPHAN_DATA *data = (FIND_ORPHAN_DATA *) a_ptr; // ignore DOT entries if ((a_fs_file->name) && (a_fs_file->name->name) && (TSK_FS_ISDOT(a_fs_file->name->name))) return TSK_WALK_CONT; // add this entry to the orphan list if (a_fs_file->meta) { tsk_list_add(&data->orphan_subdir_list, a_fs_file->meta->addr); /* FAT file systems spend a lot of time hunting for parent * directory addresses, so we put this code in here to save * the info when we have it. */ if ((a_fs_file->meta->type == TSK_FS_META_TYPE_DIR) && (TSK_FS_TYPE_ISFAT(a_fs_file->fs_info->ftype))) { if (fatfs_dir_buf_add((FATFS_INFO *) a_fs_file->fs_info, a_fs_file->name->par_addr, a_fs_file->meta->addr)) return TSK_WALK_ERROR; } } return TSK_WALK_CONT; }
/* Check whether the entry is dot and is not Root. * Return 1 if it is dot, 0 otherwise or if it is the Root entry. */ static int entry_is_dot (TSK_FS_FILE *fsfile) { return (TSK_FS_ISDOT (fsfile->name->name) && !(fsfile->fs_info->root_inum == fsfile->name->meta_addr && /* Root */ STREQ (fsfile->name->name, "."))); /* Avoid 'bin/..' 'etc/..' */ }
/* Used to process orphan directories and make sure that their contents * are now marked as reachable */ static TSK_WALK_RET_ENUM load_orphan_dir_walk_cb(TSK_FS_FILE * a_fs_file, const char *a_path, void *a_ptr) { FIND_ORPHAN_DATA *data = (FIND_ORPHAN_DATA *) a_ptr; // ignore DOT entries if ((a_fs_file->name) && (a_fs_file->name->name) && (TSK_FS_ISDOT(a_fs_file->name->name))) return TSK_WALK_CONT; // add this entry to the orphan list if (a_fs_file->meta) { /* Stop if we hit an allocated entry. We shouldn't get these, but did * have some trouble images that went into allocated clusters on * a FAT file system. */ if (a_fs_file->meta->flags & TSK_FS_META_FLAG_ALLOC) { if (tsk_verbose) { tsk_fprintf(stderr, "load_orphan_dir_walk_cb: Skipping an allocated file (ID: %" PRIuINUM ")\n", a_fs_file->meta->addr); } return TSK_WALK_STOP; } /* check if we have already added it as an orphan (in a subdirectory) * Not entirely sure how possible this is, but it was added while * debugging an infinite loop problem. */ if (tsk_list_find(data->orphan_subdir_list, a_fs_file->meta->addr)) { if (tsk_verbose) fprintf(stderr, "load_orphan_dir_walk_cb: Detected loop with address %" PRIuINUM, a_fs_file->meta->addr); return TSK_WALK_STOP; } tsk_list_add(&data->orphan_subdir_list, a_fs_file->meta->addr); /* FAT file systems spend a lot of time hunting for parent * directory addresses, so we put this code in here to save * the info when we have it. */ if ((a_fs_file->meta->type == TSK_FS_META_TYPE_DIR) && (TSK_FS_TYPE_ISFAT(a_fs_file->fs_info->ftype))) { if (fatfs_dir_buf_add((FATFS_INFO *) a_fs_file->fs_info, a_fs_file->name->par_addr, a_fs_file->meta->addr)) return TSK_WALK_ERROR; } } return TSK_WALK_CONT; }
/* callback function for dent_walk used by listdir */ static TSK_WALK_RET_ENUM listdent_walk_callback_list(TSK_FS_INFO *fs, TSK_FS_DENT *fs_dent, void *ptr) { PyObject *tmp; PyObject *list = (PyObject *)ptr; /* we dont want to add '.' and '..' */ if(TSK_FS_ISDOT(fs_dent->name)) return TSK_WALK_CONT; tmp = PyString_FromString(fs_dent->name); PyList_Append(list, tmp); Py_DECREF(tmp); return TSK_WALK_CONT; }
/* dir_walk local function that is used for recursive calls. Callers * should initially call the non-local version. */ static TSK_WALK_RET_ENUM tsk_fs_dir_walk_lcl(TSK_FS_INFO * a_fs, DENT_DINFO * a_dinfo, TSK_INUM_T a_addr, TSK_FS_DIR_WALK_FLAG_ENUM a_flags, TSK_FS_DIR_WALK_CB a_action, void *a_ptr) { TSK_FS_DIR *fs_dir; TSK_FS_FILE *fs_file; size_t i; // get the list of entries in the directory if ((fs_dir = tsk_fs_dir_open_meta(a_fs, a_addr)) == NULL) { return TSK_WALK_ERROR; } /* Allocate a file structure for the callbacks. We * will allocate fs_meta structures as needed and * point into the fs_dir structure for the names. */ if ((fs_file = tsk_fs_file_alloc(a_fs)) == NULL) { tsk_fs_dir_close(fs_dir); return TSK_WALK_ERROR; } for (i = 0; i < fs_dir->names_used; i++) { TSK_WALK_RET_ENUM retval; /* Point name to the buffer of names. We need to be * careful about resetting this before we free fs_file */ fs_file->name = (TSK_FS_NAME *) & fs_dir->names[i]; /* load the fs_meta structure if possible. * Must have non-zero inode addr or have allocated name (if inode is 0) */ if (((fs_file->name->meta_addr) || (fs_file->name->flags & TSK_FS_NAME_FLAG_ALLOC))) { if (a_fs->file_add_meta(a_fs, fs_file, fs_file->name->meta_addr)) { if (tsk_verbose) tsk_error_print(stderr); tsk_error_reset(); } } // call the action if we have the right flags. if ((fs_file->name->flags & a_flags) == fs_file->name->flags) { retval = a_action(fs_file, a_dinfo->dirs, a_ptr); if (retval == TSK_WALK_STOP) { tsk_fs_dir_close(fs_dir); fs_file->name = NULL; tsk_fs_file_close(fs_file); /* free the list -- fs_dir_walk has no way * of knowing that we stopped early w/out error. */ if (a_dinfo->save_inum_named) { tsk_list_free(a_dinfo->list_inum_named); a_dinfo->list_inum_named = NULL; a_dinfo->save_inum_named = 0; } return TSK_WALK_STOP; } else if (retval == TSK_WALK_ERROR) { tsk_fs_dir_close(fs_dir); fs_file->name = NULL; tsk_fs_file_close(fs_file); return TSK_WALK_ERROR; } } // save the inode info for orphan finding - if requested if ((a_dinfo->save_inum_named) && (fs_file->meta) && (fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC)) { if (tsk_list_add(&a_dinfo->list_inum_named, fs_file->meta->addr)) { // if there is an error, then clear the list tsk_list_free(a_dinfo->list_inum_named); a_dinfo->list_inum_named = NULL; a_dinfo->save_inum_named = 0; } } /* Recurse into a directory if: * - Both dir entry and inode have DIR type (or name is undefined) * - Recurse flag is set * - dir entry is allocated OR both are unallocated * - not one of the '.' or '..' entries * - A Non-Orphan Dir or the Orphan Dir with the NOORPHAN flag not set. */ if (((fs_file->name->type == TSK_FS_NAME_TYPE_DIR) || (fs_file->name->type == TSK_FS_NAME_TYPE_UNDEF)) && (fs_file->meta) && (fs_file->meta->type == TSK_FS_META_TYPE_DIR) && (a_flags & TSK_FS_DIR_WALK_FLAG_RECURSE) && ((fs_file->name->flags & TSK_FS_NAME_FLAG_ALLOC) || ((fs_file->name->flags & TSK_FS_NAME_FLAG_UNALLOC) && (fs_file->meta->flags & TSK_FS_META_FLAG_UNALLOC)) ) && (!TSK_FS_ISDOT(fs_file->name->name)) && ((fs_file->name->meta_addr != TSK_FS_ORPHANDIR_INUM(a_fs)) || ((a_flags & TSK_FS_DIR_WALK_FLAG_NOORPHAN) == 0)) ) { /* Make sure we do not get into an infinite loop */ if (0 == tsk_stack_find(a_dinfo->stack_seen, fs_file->name->meta_addr)) { int depth_added = 0; uint8_t save_bak = 0; if (tsk_stack_push(a_dinfo->stack_seen, fs_file->name->meta_addr)) { tsk_fs_dir_close(fs_dir); fs_file->name = NULL; tsk_fs_file_close(fs_file); return TSK_WALK_ERROR; } if ((a_dinfo->depth < MAX_DEPTH) && (DIR_STRSZ > strlen(a_dinfo->dirs) + strlen(fs_file->name->name))) { a_dinfo->didx[a_dinfo->depth] = &a_dinfo->dirs[strlen(a_dinfo->dirs)]; strncpy(a_dinfo->didx[a_dinfo->depth], fs_file->name->name, DIR_STRSZ - strlen(a_dinfo->dirs)); strncat(a_dinfo->dirs, "/", DIR_STRSZ); depth_added = 1; } a_dinfo->depth++; /* We do not want to save info about named unalloc files * when we go into the Orphan directory (because then we have * no orphans). So, disable it for this recursion. */ if (fs_file->name->meta_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) { save_bak = a_dinfo->save_inum_named; a_dinfo->save_inum_named = 0; } retval = tsk_fs_dir_walk_lcl(a_fs, a_dinfo, fs_file->name->meta_addr, a_flags, a_action, a_ptr); if (retval == TSK_WALK_ERROR) { /* If this fails because the directory could not be * loaded, then we still continue */ if (tsk_verbose) { tsk_fprintf(stderr, "tsk_fs_dir_walk_lcl: error reading directory: %" PRIuINUM "\n", fs_file->name->meta_addr); tsk_error_print(stderr); } tsk_error_reset(); } else if (retval == TSK_WALK_STOP) { tsk_fs_dir_close(fs_dir); fs_file->name = NULL; tsk_fs_file_close(fs_file); return TSK_WALK_STOP; } // reset the save status if (fs_file->name->meta_addr == TSK_FS_ORPHANDIR_INUM(a_fs)) { a_dinfo->save_inum_named = save_bak; } tsk_stack_pop(a_dinfo->stack_seen); a_dinfo->depth--; if (depth_added) *a_dinfo->didx[a_dinfo->depth] = '\0'; } else { if (tsk_verbose) fprintf(stderr, "tsk_fs_dir_walk_lcl: Loop detected with address %" PRIuINUM, fs_file->name->meta_addr); } } // remove the pointer to name buffer fs_file->name = NULL; // free the metadata if we allocated it if (fs_file->meta) { tsk_fs_meta_close(fs_file->meta); fs_file->meta = NULL; } } tsk_fs_dir_close(fs_dir); fs_file->name = NULL; tsk_fs_file_close(fs_file); return TSK_WALK_CONT; }
/** * /internal * Parse a buffer containing the contents of a directory and add TSK_FS_NAME * objects for each named file found to the TSK_FS_DIR representation of the * directory. * * @param fatfs File system information structure for file system that * contains the directory. * @param a_fs_dir Directory structure into to which parsed file metadata will * be added. * @param buf Buffer that contains the directory contents. * @param len Length of buffer in bytes (must be a multiple of sector * size). * @param addrs Array where each element is the original address of * the corresponding sector in a_buf (size of array is number of sectors in * the directory). * @return TSK_RETVAL_ENUM */ TSK_RETVAL_ENUM fatxxfs_dent_parse_buf(FATFS_INFO *fatfs, TSK_FS_DIR *a_fs_dir, char *buf, TSK_OFF_T len, TSK_DADDR_T *addrs) { char *func_name = "fatxxfs_dent_parse_buf"; unsigned int idx = 0; unsigned int sidx = 0; int a = 0; int b = 0; TSK_INUM_T ibase = 0; FATXXFS_DENTRY *dep = NULL; TSK_FS_INFO *fs = (TSK_FS_INFO*)&fatfs->fs_info; int sectalloc = 0; TSK_FS_NAME *fs_name = NULL; FATXXFS_LFN lfninfo; int entrySeenCount = 0; int entryInvalidCount = 0; uint8_t isCorruptDir = 0; tsk_error_reset(); if (fatfs_ptr_arg_is_null(fatfs, "fatfs", func_name) || fatfs_ptr_arg_is_null(a_fs_dir, "a_fs_dir", func_name) || fatfs_ptr_arg_is_null(buf, "buf", func_name) || fatfs_ptr_arg_is_null(addrs, "addrs", func_name)) { return TSK_ERR; } assert(len > 0); if (len < 0) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_ARG); tsk_error_set_errstr("%s: invalid buffer length", func_name); return TSK_ERR; } dep = (FATXXFS_DENTRY*)buf; if ((fs_name = tsk_fs_name_alloc(FATFS_MAXNAMLEN_UTF8, 32)) == NULL) { return TSK_ERR; } memset(&lfninfo, 0, sizeof(FATXXFS_LFN)); lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; /* Loop through the sectors in the buffer. */ for (sidx = 0; sidx < (unsigned int) (len / fatfs->ssize); sidx++) { /* Get the base inode for the current sector */ ibase = FATFS_SECT_2_INODE(fatfs, addrs[sidx]); if (ibase > fs->last_inum) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_ARG); tsk_error_set_errstr ("fatfs_parse: inode address is too large"); tsk_fs_name_free(fs_name); return TSK_COR; } if (tsk_verbose) tsk_fprintf(stderr, "fatfs_dent_parse_buf: Parsing sector %" PRIuDADDR " for dir %" PRIuINUM "\n", addrs[sidx], a_fs_dir->addr); /* Get the allocation status of the current sector. */ if ((sectalloc = fatfs_is_sectalloc(fatfs, addrs[sidx])) == -1) { if (tsk_verbose) { tsk_fprintf(stderr, "fatfs_dent_parse_buf: Error looking up sector allocation: %" PRIuDADDR "\n", addrs[sidx]); tsk_error_print(stderr); } tsk_error_reset(); continue; } /* Loop through the putative directory entries in the current sector. */ for (idx = 0; idx < fatfs->dentry_cnt_se; idx++, dep++) { FATXXFS_DENTRY *dir; TSK_INUM_T inode; entrySeenCount++; /* Is the current entry a valid entry? */ if (0 == fatxxfs_is_dentry(fatfs, (FATFS_DENTRY*)dep, (FATFS_DATA_UNIT_ALLOC_STATUS_ENUM)sectalloc, ((isCorruptDir == 0) && (sectalloc)) ? 1 : 0)) { if (tsk_verbose) tsk_fprintf(stderr, "fatfs_dent_parse_buf: Entry %u is invalid\n", idx); entryInvalidCount++; /* If we have seen four entries and all of them are corrupt, * then test every remaining entry in this folder -- * even if the sector is allocated. The scenario is one * where we are processing a cluster that is allocated * to a file and we happen to get some data that matches * every now and then. */ if ((entrySeenCount == 4) && (entryInvalidCount == 4)) { isCorruptDir = 1; } continue; } dir = dep; /* Compute the inode address corresponding to this directory entry. */ inode = ibase + idx; if ((dir->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) { /* The current entry is a long file name entry. */ FATXXFS_DENTRY_LFN *dirl = (FATXXFS_DENTRY_LFN *) dir; /* Store the name in dinfo until we get the 8.3 name * Use the checksum to identify a new sequence. */ if (((dirl->seq & FATXXFS_LFN_SEQ_FIRST) && (dirl->seq != FATXXFS_SLOT_DELETED)) || (dirl->chksum != lfninfo.chk)) { // @@@ Do a partial output here /* This is the last long file name entry in a sequence. * Reset the sequence number, check sum, and next char * address. */ lfninfo.seq = dirl->seq & FATXXFS_LFN_SEQ_MASK; lfninfo.chk = dirl->chksum; lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; } else if (dirl->seq != lfninfo.seq - 1) { // @@@ Check the sequence number - the checksum is correct though... } /* Copy the UTF16 values starting at end of buffer */ for (a = 3; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part3[a]; } for (a = 11; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part2[a]; } for (a = 9; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part1[a]; } // Skip ahead until we get a new sequence num or the 8.3 name continue; } else if ((dir->attrib & FATFS_ATTR_VOLUME) == FATFS_ATTR_VOLUME) { /* Special case for volume label: name does not have an * extension and we add a note at the end that it is a label */ a = 0; for (b = 0; b < 8; b++) { if ((dir->name[b] >= 0x20) && (dir->name[b] != 0xff)) { fs_name->name[a++] = dir->name[b]; } else { fs_name->name[a++] = '^'; } } for (b = 0; b < 3; b++) { if ((dir->ext[b] >= 0x20) && (dir->ext[b] != 0xff)) { fs_name->name[a++] = dir->ext[b]; } else { fs_name->name[a++] = '^'; } } fs_name->name[a] = '\0'; /* Append a string to show it is a label */ if (a + 22 < FATFS_MAXNAMLEN_UTF8) { const char *volstr = " (Volume Label Entry)"; strncat(fs_name->name, volstr, FATFS_MAXNAMLEN_UTF8 - a); } } else { /* A short (8.3) entry */ char *name_ptr; // The dest location for the short name /* if we have a lfn, copy it into fs_name->name * and put the short name in fs_name->shrt_name */ if (lfninfo.start != FATFS_MAXNAMLEN_UTF8 - 1) { int retVal; /* @@@ Check the checksum */ /* Convert the UTF16 to UTF8 */ UTF16 *name16 = (UTF16 *) ((uintptr_t) & lfninfo. name[lfninfo.start + 1]); UTF8 *name8 = (UTF8 *) fs_name->name; retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, (UTF16 *) & lfninfo.name[FATFS_MAXNAMLEN_UTF8], &name8, (UTF8 *) ((uintptr_t) name8 + FATFS_MAXNAMLEN_UTF8), TSKlenientConversion); if (retVal != TSKconversionOK) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_UNICODE); tsk_error_set_errstr ("fatfs_parse: Error converting FAT LFN to UTF8: %d", retVal); continue; } /* Make sure it is NULL Terminated */ if ((uintptr_t) name8 > (uintptr_t) fs_name->name + FATFS_MAXNAMLEN_UTF8) fs_name->name[FATFS_MAXNAMLEN_UTF8 - 1] = '\0'; else *name8 = '\0'; lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; name_ptr = fs_name->shrt_name; // put 8.3 into shrt_name } /* We don't have a LFN, so put the short name in * fs_name->name */ else { fs_name->shrt_name[0] = '\0'; name_ptr = fs_name->name; // put 8.3 into normal location } /* copy in the short name into the place specified above. * Skip spaces and put in the . */ a = 0; for (b = 0; b < 8; b++) { if ((dir->name[b] != 0) && (dir->name[b] != 0xff) && (dir->name[b] != 0x20)) { if ((b == 0) && (dir->name[0] == FATXXFS_SLOT_DELETED)) { name_ptr[a++] = '_'; } else if ((dir->lowercase & FATXXFS_CASE_LOWER_BASE) && (dir->name[b] >= 'A') && (dir->name[b] <= 'Z')) { name_ptr[a++] = dir->name[b] + 32; } else { name_ptr[a++] = dir->name[b]; } } } for (b = 0; b < 3; b++) { if ((dir->ext[b] != 0) && (dir->ext[b] != 0xff) && (dir->ext[b] != 0x20)) { if (b == 0) name_ptr[a++] = '.'; if ((dir->lowercase & FATXXFS_CASE_LOWER_EXT) && (dir->ext[b] >= 'A') && (dir->ext[b] <= 'Z')) name_ptr[a++] = dir->ext[b] + 32; else name_ptr[a++] = dir->ext[b]; } } name_ptr[a] = '\0'; // make sure that only ASCII is in the short name fatfs_cleanup_ascii(name_ptr); } /* file type: FAT only knows DIR and FILE */ if ((dir->attrib & FATFS_ATTR_DIRECTORY) == FATFS_ATTR_DIRECTORY) fs_name->type = TSK_FS_NAME_TYPE_DIR; else fs_name->type = TSK_FS_NAME_TYPE_REG; /* set the inode */ fs_name->meta_addr = inode; inode = 0; // so that we don't use it anymore -- use only fs_name->meta_addr /* Handle the . and .. entries specially * The current inode 'address' they have is for the current * slot in the cluster, but it needs to refer to the original * slot */ if (TSK_FS_ISDOT(fs_name->name) && (fs_name->type == TSK_FS_NAME_TYPE_DIR) && idx < 2) { if (fs_name->name[1] == '\0') { /* Current directory - "." */ fs_name->meta_addr = a_fs_dir->fs_file->meta->addr; } /* for the parent directory, look up in the list that * is maintained in fafs_info */ else if (fs_name->name[1] == '.') { /* Parent directory - ".." */ uint8_t dir_found = 0; if (fatfs_dir_buf_get(fatfs, a_fs_dir->fs_file->meta->addr, &(fs_name->meta_addr)) == 0) { dir_found = 1; } if ((dir_found == 0) && (addrs[0] == fatfs->firstdatasect)) { /* if we are currently in the root directory, we aren't going to find * a parent. This shouldn't happen, but could result in an infinite loop. */ fs_name->meta_addr = 0; dir_found = 1; } if (dir_found == 0) { if (tsk_verbose) fprintf(stderr, "fatfs_dent_parse_buf: Walking directory to find parent\n"); /* The parent directory is not in the list. We are going to walk * the directory until we hit this directory. This process will * populate the buffer table and we will then rescan it */ if (tsk_fs_dir_walk(fs, fs->root_inum, (TSK_FS_DIR_WALK_FLAG_ENUM)(TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE), fatfs_find_parent_act, (void *) &a_fs_dir->fs_file->meta->addr)) { return TSK_OK; } if (tsk_verbose) fprintf(stderr, "fatfs_dent_parse_buf: Finished walking directory to find parent\n"); if (fatfs_dir_buf_get(fatfs, a_fs_dir->fs_file->meta->addr, &(fs_name->meta_addr)) == 0) { dir_found = 1; } // if we did not find it, then it was probably // from the orphan directory... if (dir_found == 0) fs_name->meta_addr = TSK_FS_ORPHANDIR_INUM(fs); } } } else { /* Save the (non-. or ..) directory to parent directory info to local * structures so that we can later fill into the inode * info for '..' entries */ if (fs_name->type == TSK_FS_NAME_TYPE_DIR) { if (fatfs_dir_buf_add(fatfs, a_fs_dir->fs_file->meta->addr, fs_name->meta_addr)) return TSK_ERR; } } /* The allocation status of an entry is based on the allocation * status of the sector it is in and the flag. Deleted directories * do not always clear the flags of each entry */ if (sectalloc == 1) { if(FATXXFS_IS_DELETED(dep->name, fatfs)){ fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; } else{ fs_name->flags = TSK_FS_NAME_FLAG_ALLOC; } } else { fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; } tsk_fs_dir_add(a_fs_dir, fs_name); } } tsk_fs_name_free(fs_name); return TSK_OK; }
/* * call back action function for dent_walk */ static TSK_WALK_RET_ENUM print_dent_act(TSK_FS_FILE * fs_file, const char *a_path, void *ptr) { FLS_DATA *fls_data = (FLS_DATA *) ptr; /* only print dirs if TSK_FS_FLS_DIR is set and only print everything ** else if TSK_FS_FLS_FILE is set (or we aren't sure what it is) */ if (((fls_data->flags & TSK_FS_FLS_DIR) && ((fs_file->meta) && (fs_file->meta->type == TSK_FS_META_TYPE_DIR))) || ((fls_data->flags & TSK_FS_FLS_FILE) && (((fs_file->meta) && (fs_file->meta->type != TSK_FS_META_TYPE_DIR)) || (!fs_file->meta)))) { /* Make a special case for NTFS so we can identify all of the * alternate data streams! */ if ((TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) && (fs_file->meta)) { uint8_t printed = 0; int i, cnt; // cycle through the attributes cnt = tsk_fs_file_attr_getsize(fs_file); for (i = 0; i < cnt; i++) { const TSK_FS_ATTR *fs_attr = tsk_fs_file_attr_get_idx(fs_file, i); if (!fs_attr) continue; if (fs_attr->type == TSK_FS_ATTR_TYPE_NTFS_DATA) { printed = 1; if (fs_file->meta->type == TSK_FS_META_TYPE_DIR) { /* we don't want to print the ..:blah stream if * the -a flag was not given */ if ((fs_file->name->name[0] == '.') && (fs_file->name->name[1]) && (fs_file->name->name[2] == '\0') && ((fls_data->flags & TSK_FS_FLS_DOT) == 0)) { continue; } } printit(fs_file, a_path, fs_attr, fls_data); } else if (fs_attr->type == TSK_FS_ATTR_TYPE_NTFS_IDXROOT) { printed = 1; /* If it is . or .. only print it if the flags say so, * we continue with other streams though in case the * directory has a data stream */ if (!((TSK_FS_ISDOT(fs_file->name->name)) && ((fls_data->flags & TSK_FS_FLS_DOT) == 0))) printit(fs_file, a_path, fs_attr, fls_data); } /* Print the FILE_NAME times if this is the same attribute * that we collected the times from. */ else if ((fs_attr->type == TSK_FS_ATTR_TYPE_NTFS_FNAME) && (fs_attr->id == fs_file->meta->time2.ntfs.fn_id) && (fls_data->flags & TSK_FS_FLS_MAC)) { /* If it is . or .. only print it if the flags say so, * we continue with other streams though in case the * directory has a data stream */ if (!((TSK_FS_ISDOT(fs_file->name->name)) && ((fls_data->flags & TSK_FS_FLS_DOT) == 0))) printit(fs_file, a_path, fs_attr, fls_data); } } /* A user reported that an allocated file had the standard * attributes, but no $Data. We should print something */ if (printed == 0) { printit(fs_file, a_path, NULL, fls_data); } } else { /* skip it if it is . or .. and we don't want them */ if (!((TSK_FS_ISDOT(fs_file->name->name)) && ((fls_data->flags & TSK_FS_FLS_DOT) == 0))) printit(fs_file, a_path, NULL, fls_data); } } return TSK_WALK_CONT; }
/* * call back action function for dent_walk * This is a based on the callback in fls_lib.c, it adds an entry for each * named ADS in NTFS */ static TSK_WALK_RET_ENUM listdent_walk_callback_dent(TSK_FS_INFO * fs, TSK_FS_DENT * fs_dent, void *ptr) { struct dentwalk *dentlist = (struct dentwalk *)ptr; /* Make a special case for NTFS so we can identify all of the * alternate data streams! */ if (((fs->ftype & TSK_FS_INFO_TYPE_FS_MASK) == TSK_FS_INFO_TYPE_NTFS_TYPE) && (fs_dent->fsi)) { TSK_FS_DATA *fs_data = fs_dent->fsi->attr; uint8_t printed = 0; while ((fs_data) && (fs_data->flags & TSK_FS_DATA_INUSE)) { if (fs_data->type == NTFS_ATYPE_DATA) { mode_t mode = fs_dent->fsi->mode; uint8_t ent_type = fs_dent->ent_type; printed = 1; /* * A directory can have a Data stream, in which * case it would be printed with modes of a * directory, although it is really a file * So, to avoid confusion we will set the modes * to a file so it is printed that way. The * entry for the directory itself will still be * printed as a directory */ if ((fs_dent->fsi->mode & TSK_FS_INODE_MODE_FMT) == TSK_FS_INODE_MODE_DIR) { /* we don't want to print the ..:blah stream if * the -a flag was not given */ if ((fs_dent->name[0] == '.') && (fs_dent->name[1]) && (fs_dent->name[2] == '\0')) { fs_data = fs_data->next; continue; } fs_dent->fsi->mode &= ~TSK_FS_INODE_MODE_FMT; fs_dent->fsi->mode |= TSK_FS_INODE_MODE_REG; fs_dent->ent_type = TSK_FS_DENT_TYPE_REG; } listdent_add_dent(fs_dent, fs_data, dentlist); fs_dent->fsi->mode = mode; fs_dent->ent_type = ent_type; } else if (fs_data->type == NTFS_ATYPE_IDXROOT) { printed = 1; /* If it is . or .. only print it if the flags say so, * we continue with other streams though in case the * directory has a data stream */ if (!(TSK_FS_ISDOT(fs_dent->name))) listdent_add_dent(fs_dent, fs_data, dentlist); } fs_data = fs_data->next; } /* A user reported that an allocated file had the standard * attributes, but no $Data. We should print something */ if (printed == 0) { listdent_add_dent(fs_dent, fs_data, dentlist); } } else { /* skip it if it is . or .. and we don't want them */ if (!(TSK_FS_ISDOT(fs_dent->name))) listdent_add_dent(fs_dent, NULL, dentlist); } return TSK_WALK_CONT; }
/** * Open a directory and cycle through its contents. Read each file and recurse * into each directory. * * @param fs_info File system to process * @param stack Stack to prevent infinite recursion loops * @param dir_inum Metadata address of directory to open * @param path Path of directory being opened * @returns 1 on error */ static uint8_t proc_dir(TSK_FS_INFO * fs_info, TSK_STACK * stack, TSK_INUM_T dir_inum, const char *path) { TSK_FS_DIR *fs_dir; size_t i; char *path2 = NULL; char *buf = NULL; // open the directory if ((fs_dir = tsk_fs_dir_open_meta(fs_info, dir_inum)) == NULL) { fprintf(stderr, "Error opening directory: %" PRIuINUM "\n", dir_inum); tsk_error_print(stderr); return 1; } /* These should be dynamic lengths, but this is just a sample program. * Allocate heap space instead of stack to prevent overflow for deep * directories. */ if ((path2 = (char *) malloc(4096)) == NULL) { return 1; } if ((buf = (char *) malloc(2048)) == NULL) { free(path2); return 1; } // cycle through each entry for (i = 0; i < tsk_fs_dir_getsize(fs_dir); i++) { TSK_FS_FILE *fs_file; TSK_OFF_T off = 0; size_t len = 0; // get the entry if ((fs_file = tsk_fs_dir_get(fs_dir, i)) == NULL) { fprintf(stderr, "Error getting directory entry %" PRIuSIZE " in directory %" PRIuINUM "\n", i, dir_inum); tsk_error_print(stderr); free(path2); free(buf); return 1; } /* Ignore NTFS System files */ if ((TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) && (fs_file->name->name[0] == '$')) { tsk_fs_file_close(fs_file); continue; } //printf("Processing %s/%s\n", path, fs_file->name->name); // make sure it's got metadata and not only a name if (fs_file->meta) { ssize_t cnt; /* Note that we could also cycle through all of the attributes in the * file by using one of the tsk_fs_attr_get() functions and reading it * with tsk_fs_attr_read(). See the File Systems section of the Library * User's Guide for more details: * http://www.sleuthkit.org/sleuthkit/docs/api-docs/ */ // read file contents if (fs_file->meta->type == TSK_FS_META_TYPE_REG) { int myflags = 0; for (off = 0; off < fs_file->meta->size; off += len) { if (fs_file->meta->size - off < 2048) len = (size_t) (fs_file->meta->size - off); else len = 2048; cnt = tsk_fs_file_read(fs_file, off, buf, len, (TSK_FS_FILE_READ_FLAG_ENUM) myflags); if (cnt == -1) { // could check tsk_errno here for a recovery error (TSK_ERR_FS_RECOVER) fprintf(stderr, "Error reading %s file: %s\n", ((fs_file->name-> flags & TSK_FS_NAME_FLAG_UNALLOC) || (fs_file->meta-> flags & TSK_FS_META_FLAG_UNALLOC)) ? "unallocated" : "allocated", fs_file->name->name); tsk_error_print(stderr); break; } else if (cnt != (ssize_t) len) { fprintf(stderr, "Warning: %" PRIuSIZE " of %" PRIuSIZE " bytes read from %s file %s\n", cnt, len, ((fs_file->name-> flags & TSK_FS_NAME_FLAG_UNALLOC) || (fs_file->meta-> flags & TSK_FS_META_FLAG_UNALLOC)) ? "unallocated" : "allocated", fs_file->name->name); } // do something with the data... } } // recurse into another directory (unless it is a '.' or '..') else if (fs_file->meta->type == TSK_FS_META_TYPE_DIR) { if (TSK_FS_ISDOT(fs_file->name->name) == 0) { // only go in if it is not on our stack if (tsk_stack_find(stack, fs_file->meta->addr) == 0) { // add the address to the top of the stack tsk_stack_push(stack, fs_file->meta->addr); snprintf(path2, 4096, "%s/%s", path, fs_file->name->name); if (proc_dir(fs_info, stack, fs_file->meta->addr, path2)) { tsk_fs_file_close(fs_file); tsk_fs_dir_close(fs_dir); free(path2); free(buf); return 1; } // pop the address tsk_stack_pop(stack); } } } } tsk_fs_file_close(fs_file); } tsk_fs_dir_close(fs_dir); free(path2); free(buf); return 0; }
/* ** ** Read contents of directory block ** ** if entry is active call action with myflags set to TSK_FS_DENT_FLAG_ALLOC, if ** it is deleted then call action with TSK_FS_DENT_FLAG_UNALLOC. ** len is the size of buf ** ** return 1 to stop, 0 on success, and -1 on error */ static int ext2fs_dent_parse_block(EXT2FS_INFO * ext2fs, EXT2FS_DINFO * dinfo, TSK_LIST ** list_seen, char *buf, int len, int flags, TSK_FS_DENT_TYPE_WALK_CB action, void *ptr) { TSK_FS_INFO *fs = &(ext2fs->fs_info); int dellen = 0; int idx; uint16_t reclen; uint32_t inode; char *dirPtr; TSK_FS_DENT *fs_dent; int minreclen = 4; if ((fs_dent = tsk_fs_dent_alloc(EXT2FS_MAXNAMLEN + 1, 0)) == NULL) return -1; /* update each time by the actual length instead of the ** recorded length so we can view the deleted entries */ for (idx = 0; idx <= len - EXT2FS_DIRSIZ_lcl(1); idx += minreclen) { unsigned int namelen; dirPtr = &buf[idx]; if (ext2fs->deentry_type == EXT2_DE_V1) { ext2fs_dentry1 *dir = (ext2fs_dentry1 *) dirPtr; inode = tsk_getu32(fs->endian, dir->inode); namelen = tsk_getu16(fs->endian, dir->name_len); reclen = tsk_getu16(fs->endian, dir->rec_len); } else { ext2fs_dentry2 *dir = (ext2fs_dentry2 *) dirPtr; inode = tsk_getu32(fs->endian, dir->inode); namelen = dir->name_len; reclen = tsk_getu16(fs->endian, dir->rec_len); } minreclen = EXT2FS_DIRSIZ_lcl(namelen); /* ** Check if we may have a valid directory entry. If we don't, ** then increment to the next word and try again. */ if ((inode > fs->last_inum) || (inode < 0) || (namelen > EXT2FS_MAXNAMLEN) || (namelen <= 0) || (reclen < minreclen) || (reclen % 4) || (idx + reclen > len)) { minreclen = 4; if (dellen > 0) dellen -= 4; continue; } /* Before we process an entry in unallocated space, make * sure that it also ends in the unalloc space */ if ((dellen) && (dellen < minreclen)) { minreclen = 4; if (dellen > 0) dellen -= 4; continue; } if (ext2fs_dent_copy(ext2fs, dinfo, dirPtr, fs_dent)) { tsk_fs_dent_free(fs_dent); return -1; } /* Do we have a deleted entry? */ if ((dellen > 0) || (inode == 0)) { fs_dent->flags = TSK_FS_DENT_FLAG_UNALLOC; if (dellen > 0) dellen -= minreclen; if (flags & TSK_FS_DENT_FLAG_UNALLOC) { int retval; retval = action(fs, fs_dent, ptr); if (retval == TSK_WALK_STOP) { tsk_fs_dent_free(fs_dent); return 1; } else if (retval == TSK_WALK_ERROR) { tsk_fs_dent_free(fs_dent); return -1; } } } /* We have a non-deleted entry */ else { fs_dent->flags = TSK_FS_DENT_FLAG_ALLOC; if (flags & TSK_FS_DENT_FLAG_ALLOC) { int retval; retval = action(fs, fs_dent, ptr); if (retval == TSK_WALK_STOP) { tsk_fs_dent_free(fs_dent); return 1; } else if (retval == TSK_WALK_ERROR) { tsk_fs_dent_free(fs_dent); return -1; } } } /* If the actual length is shorter then the ** recorded length, then the next entry(ies) have been ** deleted. Set dellen to the length of data that ** has been deleted ** ** Because we aren't guaranteed with Ext2FS that the next ** entry begins right after this one, we will check to ** see if the difference is less than a possible entry ** before we waste time searching it */ if ((reclen - minreclen >= EXT2FS_DIRSIZ_lcl(1)) && (dellen <= 0)) dellen = reclen - minreclen; /* we will be recursing directories */ if ((fs_dent->flags & TSK_FS_DENT_FLAG_ALLOC) && (flags & TSK_FS_DENT_FLAG_RECURSE) && (!TSK_FS_ISDOT(fs_dent->name)) && ((fs_dent->fsi->mode & TSK_FS_INODE_MODE_FMT) == TSK_FS_INODE_MODE_DIR)) { int depth_added = 0; /* Make sure we do not get into an infinite loop */ if (0 == tsk_list_find(*list_seen, fs_dent->inode)) { if (tsk_list_add(list_seen, fs_dent->inode)) { tsk_fs_dent_free(fs_dent); return -1; } if ((dinfo->depth < MAX_DEPTH) && (DIR_STRSZ > strlen(dinfo->dirs) + strlen(fs_dent->name))) { dinfo->didx[dinfo->depth] = &dinfo->dirs[strlen(dinfo->dirs)]; strncpy(dinfo->didx[dinfo->depth], fs_dent->name, DIR_STRSZ - strlen(dinfo->dirs)); strncat(dinfo->dirs, "/", DIR_STRSZ); depth_added = 1; } dinfo->depth++; if (ext2fs_dent_walk_lcl(&(ext2fs->fs_info), dinfo, list_seen, fs_dent->inode, flags, action, ptr)) { /* If this fails because the directory could not be * loaded, then we still continue */ if (tsk_verbose) { tsk_fprintf(stderr, "ffs_dent_parse_block: error reading directory: %" PRIuINUM "\n", fs_dent->inode); tsk_error_print(stderr); } tsk_error_reset(); } dinfo->depth--; if (depth_added) *dinfo->didx[dinfo->depth] = '\0'; } } } tsk_fs_dent_free(fs_dent); return 0; } /* end ext2fs_dent_parse_block() */
/* * Process the contents of a directory and add them to FS_DIR. * * @param fatfs File system information structure * @param a_fs_dir Structure to store the files in. * @param list_seen List of directory inodes that have been seen thus far in * directory walking (can be a pointer to a NULL pointer on first call). * @param buf Buffer that contains the directory contents. * @param len Length of buffer in bytes (must be a multiple of sector size) * @param addrs Array where each element is the original address of the * corresponding block in buf (size of array is number of blocks in directory). * * @return -1 on error, 0 on success, and 1 to stop */ static TSK_RETVAL_ENUM fatfs_dent_parse_buf(FATFS_INFO * fatfs, TSK_FS_DIR * a_fs_dir, char *buf, TSK_OFF_T len, TSK_DADDR_T * addrs) { unsigned int idx, sidx; int a, b; TSK_INUM_T inode, ibase; fatfs_dentry *dep; TSK_FS_INFO *fs = (TSK_FS_INFO *) & fatfs->fs_info; int sectalloc; TSK_FS_NAME *fs_name; FATFS_LFN lfninfo; if (buf == NULL) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_ARG; snprintf(tsk_errstr, TSK_ERRSTR_L, "fatfs_dent_parse_buf: buffer is NULL"); return TSK_ERR; } dep = (fatfs_dentry *) buf; if ((fs_name = tsk_fs_name_alloc(FATFS_MAXNAMLEN_UTF8, 32)) == NULL) { return TSK_ERR; } memset(&lfninfo, 0, sizeof(FATFS_LFN)); lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; for (sidx = 0; sidx < (unsigned int) (len / fatfs->ssize); sidx++) { /* Get the base inode for this sector */ ibase = FATFS_SECT_2_INODE(fatfs, addrs[sidx]); if (ibase > fs->last_inum) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_ARG; snprintf(tsk_errstr, TSK_ERRSTR_L, "fatfs_parse: inode address is too large"); tsk_fs_name_free(fs_name); return TSK_COR; } if (tsk_verbose) tsk_fprintf(stderr, "fatfs_dent_parse_buf: Parsing sector %" PRIuDADDR "\n", addrs[sidx]); if ((sectalloc = fatfs_is_sectalloc(fatfs, addrs[sidx])) == -1) { if (tsk_verbose) { tsk_fprintf(stderr, "fatfs_dent_parse_buf: Error looking up sector allocation: %" PRIuDADDR "\n", addrs[sidx]); tsk_error_print(stderr); } tsk_error_reset(); continue; } /* cycle through the directory entries */ for (idx = 0; idx < fatfs->dentry_cnt_se; idx++, dep++) { fatfs_dentry *dir; int i; /* is it a valid dentry? */ if (0 == fatfs_isdentry(fatfs, dep)) { if (tsk_verbose) tsk_fprintf(stderr, "fatfs_dent_parse_buf: Entry %u is invalid\n", idx); continue; } /* Copy the directory entry into the TSK_FS_NAME structure */ dir = (fatfs_dentry *) dep; inode = ibase + idx; /* Take care of the name * Copy a long name to a buffer and take action if it * is a small name */ if ((dir->attrib & FATFS_ATTR_LFN) == FATFS_ATTR_LFN) { fatfs_dentry_lfn *dirl = (fatfs_dentry_lfn *) dir; /* Store the name in dinfo until we get the 8.3 name * Use the checksum to identify a new sequence * */ if (((dirl->seq & FATFS_LFN_SEQ_FIRST) && (dirl->seq != FATFS_SLOT_DELETED)) || (dirl->chksum != lfninfo.chk)) { // @@@ Do a partial output here /* Reset the values */ lfninfo.seq = dirl->seq & FATFS_LFN_SEQ_MASK; lfninfo.chk = dirl->chksum; lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; } else if (dirl->seq != lfninfo.seq - 1) { // @@@ Check the sequence number - the checksum is correct though... } /* Copy the UTF16 values starting at end of buffer */ for (a = 3; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part3[a]; } for (a = 11; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part2[a]; } for (a = 9; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part1[a]; } // Skip ahead until we get a new sequence num or the 8.3 name continue; } /* Special case for volume label: name does not have an * extension and we add a note at the end that it is a label */ else if ((dir->attrib & FATFS_ATTR_VOLUME) == FATFS_ATTR_VOLUME) { a = 0; for (b = 0; b < 8; b++) { if ((dir->name[b] >= 0x20) && (dir->name[b] != 0xff)) { fs_name->name[a++] = dir->name[b]; } else { fs_name->name[a++] = '^'; } } for (b = 0; b < 3; b++) { if ((dir->ext[b] >= 0x20) && (dir->ext[b] != 0xff)) { fs_name->name[a++] = dir->ext[b]; } else { fs_name->name[a++] = '^'; } } fs_name->name[a] = '\0'; /* Append a string to show it is a label */ if (a + 22 < FATFS_MAXNAMLEN_UTF8) { char *volstr = " (Volume Label Entry)"; strncat(fs_name->name, volstr, FATFS_MAXNAMLEN_UTF8 - a); } } /* A short (8.3) entry */ else { char *name_ptr; // The dest location for the short name /* if we have a lfn, copy it into fs_name->name * and put the short name in fs_name->shrt_name */ if (lfninfo.start != FATFS_MAXNAMLEN_UTF8 - 1) { int retVal; /* @@@ Check the checksum */ /* Convert the UTF16 to UTF8 */ UTF16 *name16 = (UTF16 *) ((uintptr_t) & lfninfo.name[lfninfo. start + 1]); UTF8 *name8 = (UTF8 *) fs_name->name; retVal = tsk_UTF16toUTF8(fs->endian, (const UTF16 **) &name16, (UTF16 *) & lfninfo.name[FATFS_MAXNAMLEN_UTF8], &name8, (UTF8 *) ((uintptr_t) name8 + FATFS_MAXNAMLEN_UTF8), TSKlenientConversion); if (retVal != TSKconversionOK) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_UNICODE; snprintf(tsk_errstr, TSK_ERRSTR_L, "fatfs_parse: Error converting FAT LFN to UTF8: %d", retVal); continue; } /* Make sure it is NULL Terminated */ if ((uintptr_t) name8 > (uintptr_t) fs_name->name + FATFS_MAXNAMLEN_UTF8) fs_name->name[FATFS_MAXNAMLEN_UTF8 - 1] = '\0'; else *name8 = '\0'; /* Clean up name */ i = 0; while (fs_name->name[i] != '\0') { if (TSK_IS_CNTRL(fs_name->name[i])) fs_name->name[i] = '^'; i++; } lfninfo.start = FATFS_MAXNAMLEN_UTF8 - 1; name_ptr = fs_name->shrt_name; // put 8.3 into shrt_name } /* We don't have a LFN, so put the short name in * fs_name->name */ else { fs_name->shrt_name[0] = '\0'; name_ptr = fs_name->name; // put 8.3 into normal location } /* copy in the short name into the place specified above. * Skip spaces and put in the . */ a = 0; for (b = 0; b < 8; b++) { if ((dir->name[b] != 0) && (dir->name[b] != 0xff) && (dir->name[b] != 0x20)) { if ((b == 0) && (dir->name[0] == FATFS_SLOT_DELETED)) { name_ptr[a++] = '_'; } else if ((dir->lowercase & FATFS_CASE_LOWER_BASE) && (dir->name[b] >= 'A') && (dir->name[b] <= 'Z')) { name_ptr[a++] = dir->name[b] + 32; } else { name_ptr[a++] = dir->name[b]; } } } for (b = 0; b < 3; b++) { if ((dir->ext[b] != 0) && (dir->ext[b] != 0xff) && (dir->ext[b] != 0x20)) { if (b == 0) name_ptr[a++] = '.'; if ((dir->lowercase & FATFS_CASE_LOWER_EXT) && (dir->ext[b] >= 'A') && (dir->ext[b] <= 'Z')) name_ptr[a++] = dir->ext[b] + 32; else name_ptr[a++] = dir->ext[b]; } } name_ptr[a] = '\0'; } /* Clean up name to remove control chars */ i = 0; while (fs_name->name[i] != '\0') { if (TSK_IS_CNTRL(fs_name->name[i])) fs_name->name[i] = '^'; i++; } /* file type: FAT only knows DIR and FILE */ if ((dir->attrib & FATFS_ATTR_DIRECTORY) == FATFS_ATTR_DIRECTORY) fs_name->type = TSK_FS_NAME_TYPE_DIR; else fs_name->type = TSK_FS_NAME_TYPE_REG; /* Get inode */ fs_name->meta_addr = inode; /* Handle the . and .. entries specially * The current inode 'address' they have is for the current * slot in the cluster, but it needs to refer to the original * slot */ if (TSK_FS_ISDOT(fs_name->name)) { if (fs_name->name[1] == '\0') { inode = fs_name->meta_addr = a_fs_dir->fs_file->meta->addr; } /* for the parent directory, look up in the list that * is maintained in fafs_info */ else if (fs_name->name[1] == '.') { size_t q; uint8_t dir_found = 0; for (q = 0; q < fatfs->dir_buf_next; q++) { if (fatfs->dir_buf[q] == a_fs_dir->fs_file->meta->addr) { inode = fs_name->meta_addr = fatfs->par_buf[q]; dir_found = 1; break; } } if ((dir_found == 0) && (fs->isOrphanHunting)) { /* if we are currently scanning the fs to determine the orphan files, * then we do not care about the value of '..' and this can only cause * infinite loop problems */ inode = fs_name->meta_addr = 0; dir_found = 1; } if ((dir_found == 0) && (addrs[0] == fatfs->firstdatasect)) { /* if we are currently in the root directory, we aren't going to find * a parent. This shouldn't happen, but could result in an infinite loop. */ inode = fs_name->meta_addr = 0; dir_found = 1; } if (dir_found == 0) { /* The parent directory is not in the list. We are going to walk * the directory until we hit this directory. This process will * populate the buffer table and we will then rescan it */ if (tsk_fs_dir_walk(fs, fs->root_inum, TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE, find_parent_act, (void *) &a_fs_dir->fs_file->meta->addr)) { return 0; } for (q = 0; q < fatfs->dir_buf_next; q++) { if (fatfs->dir_buf[q] == a_fs_dir->fs_file->meta->addr) { inode = fs_name->meta_addr = fatfs->par_buf[q]; dir_found = 1; break; } } // if we did not find it, then it was probably // from the orphan directory... if (dir_found == 0) inode = fs_name->meta_addr = TSK_FS_ORPHANDIR_INUM(fs); } } } else { /* Save the (non-. or ..) directory to parent directory info to local * structures so that we can later fill into the inode * info for '..' entries */ if (fs_name->type == TSK_FS_NAME_TYPE_DIR) { if (fatfs_dir_buf_add(fatfs, a_fs_dir->fs_file->meta->addr, inode)) return TSK_ERR; } } /* The allocation status of an entry is based on the allocation * status of the sector it is in and the flag. Deleted directories * do not always clear the flags of each entry */ if (sectalloc == 1) { fs_name->flags = (dep->name[0] == FATFS_SLOT_DELETED) ? TSK_FS_NAME_FLAG_UNALLOC : TSK_FS_NAME_FLAG_ALLOC; } else { fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; } tsk_fs_dir_add(a_fs_dir, fs_name); } } tsk_fs_name_free(fs_name); return TSK_OK; }
/* * Process the contents of a directory and add them to FS_DIR. * * @param xtaffs File system information structure * @param a_fs_dir Structure to store the files in. * @param list_seen List of directory inodes that have been seen thus far in * directory walking (can be a pointer to a NULL pointer on first call). * @param buf Buffer that contains the directory contents. * @param len Length of buffer in bytes (must be a multiple of sector size) * @param addrs Array where each element is the original address of the * corresponding block in buf (size of array is number of blocks in directory). * * @return -1 on error, 0 on success, and 1 to stop */ static TSK_RETVAL_ENUM xtaffs_dent_parse_buf(XTAFFS_INFO * xtaffs, TSK_FS_DIR * a_fs_dir, char *buf, TSK_OFF_T len, TSK_DADDR_T * addrs) { unsigned int idx, sidx; int a, b; TSK_INUM_T ibase; xtaffs_dentry *dep; TSK_FS_INFO *fs = (TSK_FS_INFO *) & xtaffs->fs_info; int sectalloc; TSK_FS_NAME *fs_name; XTAFFS_LFN lfninfo; int entrySeenCount = 0; int entryInvalidCount = 0; uint8_t isCorruptDir = 0; if (buf == NULL) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_ARG); tsk_error_set_errstr("xtaffs_dent_parse_buf: buffer is NULL"); return TSK_ERR; } dep = (xtaffs_dentry *) buf; if ((fs_name = tsk_fs_name_alloc(XTAFFS_MAXNAMLEN_UTF8, 32)) == NULL) { return TSK_ERR; } memset(&lfninfo, 0, sizeof(XTAFFS_LFN)); lfninfo.start = XTAFFS_MAXNAMLEN_UTF8 - 1; for (sidx = 0; sidx < (unsigned int) (len / xtaffs->ssize); sidx++) { /* Get the base inode for this sector */ ibase = XTAFFS_SECT_2_INODE(xtaffs, addrs[sidx]); if (ibase > fs->last_inum) { tsk_error_reset(); tsk_error_set_errno(TSK_ERR_FS_ARG); tsk_error_set_errstr ("xtaffs_parse: inode address is too large"); tsk_fs_name_free(fs_name); return TSK_COR; } if (tsk_verbose) tsk_fprintf(stderr, "xtaffs_dent_parse_buf: Parsing sector %" PRIuDADDR " for dir %" PRIuINUM "\n", addrs[sidx], a_fs_dir->addr); if ((sectalloc = xtaffs_is_sectalloc(xtaffs, addrs[sidx])) == -1) { if (tsk_verbose) { tsk_fprintf(stderr, "xtaffs_dent_parse_buf: Error looking up sector allocation: %" PRIuDADDR "\n", addrs[sidx]); tsk_error_print(stderr); } tsk_error_reset(); continue; } /* cycle through the directory entries */ for (idx = 0; idx < xtaffs->dentry_cnt_se; idx++, dep++) { xtaffs_dentry *dir; TSK_INUM_T inode; entrySeenCount++; /* is it a valid dentry? */ if (0 == xtaffs_isdentry(xtaffs, dep, ((isCorruptDir == 0) && (sectalloc)) ? 1 : 0)) { if (tsk_verbose) tsk_fprintf(stderr, "xtaffs_dent_parse_buf: Entry %u is invalid\n", idx); entryInvalidCount++; /* If we have seen four entries and all of them are corrupt, * then test every remaining entry in this folder -- * even if the sector is allocated. The scenario is one * where we are processing a cluster that is allocated * to a file and we happen to get some data that matches * every now and then. */ if ((entrySeenCount == 4) && (entryInvalidCount == 4)) { isCorruptDir = 1; } continue; } /* Copy the directory entry into the TSK_FS_NAME structure */ dir = (xtaffs_dentry *) dep; inode = ibase + idx; /* Take care of the name * Copy a long name to a buffer and take action if it * is a small name if ((dir->attrib & XTAFFS_ATTR_LFN) == XTAFFS_ATTR_LFN) { xtaffs_dentry_lfn *dirl = (xtaffs_dentry_lfn *) dir; * Store the name in dinfo until we get the 8.3 name * Use the checksum to identify a new sequence * * if (((dirl->seq & XTAFFS_LFN_SEQ_FIRST) && (dirl->seq != XTAFFS_SLOT_DELETED)) || (dirl->chksum != lfninfo.chk)) { // @@@ Do a partial output here * Reset the values * lfninfo.seq = dirl->seq & XTAFFS_LFN_SEQ_MASK; lfninfo.chk = dirl->chksum; lfninfo.start = XTAFFS_MAXNAMLEN_UTF8 - 1; } else if (dirl->seq != lfninfo.seq - 1) { // @@@ Check the sequence number - the checksum is correct though... } * Copy the UTF16 values starting at end of buffer * for (a = 3; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part3[a]; } for (a = 11; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part2[a]; } for (a = 9; a >= 0; a--) { if ((lfninfo.start > 0)) lfninfo.name[lfninfo.start--] = dirl->part1[a]; } // Skip ahead until we get a new sequence num or the 8.3 name continue; } */ /* Special case for volume label: name does not have an * extension and we add a note at the end that it is a label */ if ((dir->attrib & XTAFFS_ATTR_VOLUME) == XTAFFS_ATTR_VOLUME) { a = 0; for (b = 0; b < 42; b++) { if(dir->name[b] < 32 || dir->name[b] > 126) break; if ((dir->name[b] >= 0x20) && (dir->name[b] != 0xff)) { fs_name->name[a++] = dir->name[b]; } else { fs_name->name[a++] = '^'; } } fs_name->name[a] = '\0'; /* Append a string to show it is a label */ if (a + 22 < XTAFFS_MAXNAMLEN_UTF8) { const char *volstr = " (Volume Label Entry)"; strncat(fs_name->name, volstr, XTAFFS_MAXNAMLEN_UTF8 - a); } } /* AJN TODO Scrap this 8.3 stuff, it isn't in XTAF.*/ /* A short (8.3) entry */ else { char *name_ptr; // The dest location for the short name fs_name->shrt_name[0] = '\0'; name_ptr = fs_name->name; // put 8.3 into normal location /* copy in the short name into the place specified above. */ a = 0; for (b = 0; b < 42; b++) { if(dir->name[b] < 32 || dir->name[b] > 126) break; if ((dir->name[b] != 0) && (dir->name[b] != 0xff)) { if ((b == 0) && (dir->name[0] == XTAFFS_SLOT_DELETED)) { name_ptr[a++] = '_'; } else { name_ptr[a++] = dir->name[b]; } } } name_ptr[a] = '\0'; // make sure that only ASCII is in the short name xtaffs_cleanup_ascii(name_ptr); } /* file type: FAT only knows DIR and FILE */ if ((dir->attrib & XTAFFS_ATTR_DIRECTORY) == XTAFFS_ATTR_DIRECTORY) fs_name->type = TSK_FS_NAME_TYPE_DIR; else fs_name->type = TSK_FS_NAME_TYPE_REG; /* set the inode */ fs_name->meta_addr = inode; inode = 0; // so that we don't use it anymore -- use only fs_name->meta_addr /* Handle the . and .. entries specially * The current inode 'address' they have is for the current * slot in the cluster, but it needs to refer to the original * slot */ if (TSK_FS_ISDOT(fs_name->name) && (fs_name->type == TSK_FS_NAME_TYPE_DIR) && idx < 2) { if (fs_name->name[1] == '\0') { fs_name->meta_addr = a_fs_dir->fs_file->meta->addr; } /* for the parent directory, look up in the list that * is maintained in fafs_info */ else if (fs_name->name[1] == '.') { uint8_t dir_found = 0; if (xtaffs_dir_buf_get(xtaffs, a_fs_dir->fs_file->meta->addr, &(fs_name->meta_addr)) == 0) { dir_found = 1; } if ((dir_found == 0) && (addrs[0] == xtaffs->firstdatasect)) { /* if we are currently in the root directory, we aren't going to find * a parent. This shouldn't happen, but could result in an infinite loop. */ fs_name->meta_addr = 0; dir_found = 1; } if (dir_found == 0) { if (tsk_verbose) fprintf(stderr, "xtaffs_dent_parse_buf: Walking directory to find parent\n"); /* The parent directory is not in the list. We are going to walk * the directory until we hit this directory. This process will * populate the buffer table and we will then rescan it */ if (tsk_fs_dir_walk(fs, fs->root_inum, (TSK_FS_DIR_WALK_FLAG_ENUM)(TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC | TSK_FS_DIR_WALK_FLAG_RECURSE), find_parent_act, (void *) &a_fs_dir->fs_file->meta->addr)) { return TSK_OK; } if (tsk_verbose) fprintf(stderr, "xtaffs_dent_parse_buf: Finished walking directory to find parent\n"); if (xtaffs_dir_buf_get(xtaffs, a_fs_dir->fs_file->meta->addr, &(fs_name->meta_addr)) == 0) { dir_found = 1; } // if we did not find it, then it was probably // from the orphan directory... if (dir_found == 0) fs_name->meta_addr = TSK_FS_ORPHANDIR_INUM(fs); } } } else { /* Save the (non-. or ..) directory to parent directory info to local * structures so that we can later fill into the inode * info for '..' entries */ if (fs_name->type == TSK_FS_NAME_TYPE_DIR) { if (xtaffs_dir_buf_add(xtaffs, a_fs_dir->fs_file->meta->addr, fs_name->meta_addr)) return TSK_ERR; } } /* The allocation status of an entry is based on the allocation * status of the sector it is in and the flag. Deleted directories * do not always clear the flags of each entry */ if (sectalloc == 1) { fs_name->flags = (dep->name[0] == XTAFFS_SLOT_DELETED) ? TSK_FS_NAME_FLAG_UNALLOC : TSK_FS_NAME_FLAG_ALLOC; } else { fs_name->flags = TSK_FS_NAME_FLAG_UNALLOC; } tsk_fs_dir_add(a_fs_dir, fs_name); } } tsk_fs_name_free(fs_name); return TSK_OK; }
/** * Process a lsit of index entries and call the callback for * each. * * @param list_seen List of directories that have already been analyzed * @param idxe Buffer with index entries to process * @param idxe_len Length of idxe buffer (in bytes) * @param used_len Length of data as reported by idexlist header (everything * after which and less then idxe_len is considered deleted) * @param flags (All we care about is ALLOC and UNALLOC) * @param action Callback * @param ptr Pointer to data to pass to callback * * @returns 1 to stop, 0 on success, and -1 on error */ static int ntfs_dent_idxentry(NTFS_INFO * ntfs, NTFS_DINFO * dinfo, TSK_LIST ** list_seen, ntfs_idxentry * idxe, uint32_t idxe_len, uint32_t used_len, int flags, TSK_FS_DENT_TYPE_WALK_CB action, void *ptr) { uintptr_t endaddr, endaddr_alloc; TSK_FS_DENT *fs_dent; TSK_FS_INFO *fs = (TSK_FS_INFO *) & ntfs->fs_info; if ((fs_dent = tsk_fs_dent_alloc(NTFS_MAXNAMLEN_UTF8, 0)) == NULL) { return -1; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Processing index entry: %" PRIu64 " Size: %" PRIu32 " Len: %" PRIu32 " Flags: %x\n", (uint64_t) ((uintptr_t) idxe), idxe_len, used_len, flags); /* Sanity check */ if (idxe_len < used_len) { tsk_error_reset(); tsk_errno = TSK_ERR_FS_INODE_INT; snprintf(tsk_errstr, TSK_ERRSTR_L, "ntfs_dent_idxentry: Allocated length of index entries is larger than buffer length"); return 1; } /* where is the end of the buffer */ endaddr = ((uintptr_t) idxe + idxe_len); /* where is the end of the allocated data */ endaddr_alloc = ((uintptr_t) idxe + used_len); /* cycle through the index entries, based on provided size */ while (((uintptr_t) & (idxe->stream) + sizeof(ntfs_attr_fname)) < endaddr) { ntfs_attr_fname *fname = (ntfs_attr_fname *) & idxe->stream; if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: New IdxEnt: %" PRIu64 " $FILE_NAME Entry: %" PRIu64 " File Ref: %" PRIu64 " IdxEnt Len: %" PRIu16 " StrLen: %" PRIu16 "\n", (uint64_t) ((uintptr_t) idxe), (uint64_t) ((uintptr_t) fname), (uint64_t) tsk_getu48(fs->endian, idxe->file_ref), tsk_getu16(fs->endian, idxe->idxlen), tsk_getu16(fs->endian, idxe->strlen)); /* perform some sanity checks on index buffer head * and advance by 4-bytes if invalid */ if ((tsk_getu48(fs->endian, idxe->file_ref) > fs->last_inum) || (tsk_getu48(fs->endian, idxe->file_ref) < fs->first_inum) || (tsk_getu16(fs->endian, idxe->idxlen) <= tsk_getu16(fs->endian, idxe->strlen)) || (tsk_getu16(fs->endian, idxe->idxlen) % 4) || (tsk_getu16(fs->endian, idxe->idxlen) > idxe_len)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); continue; } /* do some sanity checks on the deleted entries */ if ((tsk_getu16(fs->endian, idxe->strlen) == 0) || (((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)) > endaddr_alloc)) { /* name space checks */ if ((fname->nspace != NTFS_FNAME_POSIX) && (fname->nspace != NTFS_FNAME_WIN32) && (fname->nspace != NTFS_FNAME_DOS) && (fname->nspace != NTFS_FNAME_WINDOS)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of invalid name space\n"); continue; } if ((tsk_getu64(fs->endian, fname->alloc_fsize) < tsk_getu64(fs->endian, fname->real_fsize)) || (fname->nlen == 0) || (*(uint8_t *) & fname->name == 0)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of reported file sizes, name length, or NULL name\n"); continue; } if ((is_time(tsk_getu64(fs->endian, fname->crtime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->atime)) == 0) || (is_time(tsk_getu64(fs->endian, fname->mtime)) == 0)) { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + 4); if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of invalid times\n"); continue; } } /* For all fname entries, there will exist a DOS style 8.3 * entry. We don't process those because we already processed * them before in their full version. If the type is * full POSIX or WIN32 that does not satisfy DOS, then a * type NTFS_FNAME_DOS will exist. If the name is WIN32, * but already satisfies DOS, then a type NTFS_FNAME_WINDOS * will exist * * Note that we could be missing some info from deleted files * if the windows version was deleted and the DOS wasn't... * * @@@ This should be added to the shrt_name entry of TSK_FS_DENT. The short * name entry typically comes after the long name */ if (fname->nspace == NTFS_FNAME_DOS) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because of name space: %d\n", fname->nspace); goto incr_entry; } /* Copy it into the generic form */ if (ntfs_dent_copy(ntfs, dinfo, idxe, fs_dent)) { if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Skipping because error copying dent_entry\n"); goto incr_entry; } /* * Check if this entry is deleted * * The final check is to see if the end of this entry is * within the space that the idxallocbuf claimed was valid */ if ((tsk_getu16(fs->endian, idxe->strlen) == 0) || (((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)) > endaddr_alloc)) { /* we know deleted entries with an inode of 0 are not legit because * that is the MFT value. Free it so it does not confuse * people with invalid data */ if ((fs_dent->inode == 0) && (fs_dent->fsi)) { tsk_fs_inode_free(fs_dent->fsi); fs_dent->fsi = NULL; } fs_dent->flags = TSK_FS_DENT_FLAG_UNALLOC; } else { fs_dent->flags = TSK_FS_DENT_FLAG_ALLOC; } if (tsk_verbose) tsk_fprintf(stderr, "ntfs_dent_idxentry: Entry Details of %s: Str Len: %" PRIu16 " Len to end after current: %" PRIu64 " flags: %x\n", fs_dent->name, tsk_getu16(fs->endian, idxe->strlen), (uint64_t) (endaddr_alloc - (uintptr_t) idxe - tsk_getu16(fs->endian, idxe->idxlen)), fs_dent->flags); if ((flags & fs_dent->flags) == fs_dent->flags) { int retval = action(fs, fs_dent, ptr); if (retval == TSK_WALK_STOP) { tsk_fs_dent_free(fs_dent); return 1; } else if (retval == TSK_WALK_ERROR) { tsk_fs_dent_free(fs_dent); return -1; } } /* Recurse if we need to */ if ((fs_dent->flags & TSK_FS_DENT_FLAG_ALLOC) && (flags & TSK_FS_DENT_FLAG_RECURSE) && (!TSK_FS_ISDOT(fs_dent->name)) && (fs_dent->fsi) && ((fs_dent->fsi->mode & TSK_FS_INODE_MODE_FMT) == TSK_FS_INODE_MODE_DIR) && (fs_dent->inode)) { int depth_added = 0; /* Make sure we do not get into an infinite loop */ if (0 == tsk_list_find(*list_seen, fs_dent->inode)) { if (tsk_list_add(list_seen, fs_dent->inode)) { tsk_fs_dent_free(fs_dent); return -1; } if ((dinfo->depth < MAX_DEPTH) && (DIR_STRSZ > strlen(dinfo->dirs) + strlen(fs_dent->name))) { dinfo->didx[dinfo->depth] = &dinfo->dirs[strlen(dinfo->dirs)]; strncpy(dinfo->didx[dinfo->depth], fs_dent->name, DIR_STRSZ - strlen(dinfo->dirs)); strncat(dinfo->dirs, "/", DIR_STRSZ); depth_added = 1; } dinfo->depth++; if (ntfs_dent_walk_lcl(&(ntfs->fs_info), dinfo, list_seen, fs_dent->inode, flags, action, ptr)) { if (tsk_verbose) tsk_fprintf(stderr, "Error recursing into directory\n"); tsk_error_reset(); } dinfo->depth--; if (depth_added) *dinfo->didx[dinfo->depth] = '\0'; } } /* end of recurse */ incr_entry: /* the theory here is that deleted entries have strlen == 0 and * have been found to have idxlen == 16 * * if the strlen is 0, then guess how much the indexlen was * before it was deleted */ /* 16: size of idxentry before stream * 66: size of fname before name * 2*nlen: size of name (in unicode) */ if (tsk_getu16(fs->endian, idxe->strlen) == 0) { idxe = (ntfs_idxentry *) ((((uintptr_t) idxe + 16 + 66 + 2 * fname->nlen + 3) / 4) * 4); } else { idxe = (ntfs_idxentry *) ((uintptr_t) idxe + tsk_getu16(fs->endian, idxe->idxlen)); } } /* end of loop of index entries */ tsk_fs_dent_free(fs_dent); return 0; }