From bea4fcbac6e67cd952c671827d3710d61811c31c Mon Sep 17 00:00:00 2001 From: Kelly Kelly <kelly@basistech.com> Date: Mon, 27 Apr 2020 14:50:28 -0400 Subject: [PATCH] merged in 1441 branch --- bindings/java/jni/auto_db_java.cpp | 2097 +++++++++++++++++ bindings/java/jni/auto_db_java.h | 247 ++ .../datamodel/CaseDatabaseFactory.java | 616 +++++ .../org/sleuthkit/datamodel/JniDbHelper.java | 400 ++++ .../org/sleuthkit/datamodel/SQLHelper.java | 91 + 5 files changed, 3451 insertions(+) create mode 100644 bindings/java/jni/auto_db_java.cpp create mode 100644 bindings/java/jni/auto_db_java.h create mode 100644 bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java create mode 100644 bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java create mode 100644 bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java diff --git a/bindings/java/jni/auto_db_java.cpp b/bindings/java/jni/auto_db_java.cpp new file mode 100644 index 000000000..56b2d7581 --- /dev/null +++ b/bindings/java/jni/auto_db_java.cpp @@ -0,0 +1,2097 @@ +/* + ** The Sleuth Kit + ** + ** Brian Carrier [carrier <at> sleuthkit [dot] org] + ** Copyright (c) 2020 Brian Carrier. All Rights reserved + ** + ** This software is distributed under the Common Public License 1.0 + ** + */ + +/** + * \file auto_db_java.cpp + * Contains code to populate database with volume and file system information from a specific image. + */ + +#include "auto_db_java.h" +#include "jni.h" +#include "tsk/img/img_writer.h" +#if HAVE_LIBEWF +#include "tsk/img/ewf.h" +#include "tsk/img/tsk_img_i.h" +#endif +#include <string.h> + +#include <algorithm> +#include <sstream> + +using std::stringstream; +using std::for_each; + +/** + */ +TskAutoDbJava::TskAutoDbJava() +{ + m_curImgId = 0; + m_curVsId = 0; + m_curVolId = 0; + m_curFsId = 0; + m_curFileId = 0; + m_curUnallocDirId = 0; + m_curDirAddr = 0; + m_curDirPath = ""; + m_vsFound = false; + m_volFound = false; + m_poolFound = false; + m_stopped = false; + m_foundStructure = false; + m_attributeAdded = false; + m_addFileSystems = true; + m_noFatFsOrphans = false; + m_addUnallocSpace = false; + m_minChunkSize = -1; + m_maxChunkSize = -1; + + m_jniEnv = NULL; + + tsk_init_lock(&m_curDirPathLock); +} + +TskAutoDbJava::~TskAutoDbJava() +{ + closeImage(); + tsk_deinit_lock(&m_curDirPathLock); +} + +/** +* Look up all callback method IDs +* @param jniEnv pointer to java environment this was called from +* @param jobj the JniDbHelper object this was called from +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::initializeJni(JNIEnv * jniEnv, jobject jobj) { + m_jniEnv = jniEnv; + m_javaDbObj = m_jniEnv->NewGlobalRef(jobj); + + jclass localCallbackClass = m_jniEnv->FindClass("org/sleuthkit/datamodel/JniDbHelper"); + if (localCallbackClass == NULL) { + return TSK_ERR; + } + m_callbackClass = (jclass)m_jniEnv->NewGlobalRef(localCallbackClass); + + m_addImageMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageInfo", "(IJLjava/lang/String;JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J"); + if (m_addImageMethodID == NULL) { + return TSK_ERR; + } + + m_addImageNameMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addImageName", "(JLjava/lang/String;J)I"); + if (m_addImageNameMethodID == NULL) { + return TSK_ERR; + } + + m_addVolumeSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVsInfo", "(JIJJ)J"); + if (m_addVolumeSystemMethodID == NULL) { + return TSK_ERR; + } + + m_addVolumeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addVolume", "(JJJJLjava/lang/String;J)J"); + if (m_addVolumeMethodID == NULL) { + return TSK_ERR; + } + + m_addPoolMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addPool", "(JI)J"); + if (m_addPoolMethodID == NULL) { + return TSK_ERR; + } + + + m_addFileSystemMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFileSystem", "(JJIJJJJJ)J"); + if (m_addFileSystemMethodID == NULL) { + return TSK_ERR; + } + + m_addFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addFile", "(JJJIIILjava/lang/String;JJIIIIJJJJJIIILjava/lang/String;Ljava/lang/String;)J"); + if (m_addFileMethodID == NULL) { + return TSK_ERR; + } + + m_getParentIdMethodID = m_jniEnv->GetMethodID(m_callbackClass, "findParentObjId", "(JJLjava/lang/String;Ljava/lang/String;)J"); + if (m_getParentIdMethodID == NULL) { + return TSK_ERR; + } + + m_addUnallocParentMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addUnallocFsBlockFilesParent", "(JLjava/lang/String;)J"); + if (m_addUnallocParentMethodID == NULL) { + return TSK_ERR; + } + + m_addLayoutFileMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFile", "(JJJILjava/lang/String;J)J"); + if (m_addLayoutFileMethodID == NULL) { + return TSK_ERR; + } + + m_addLayoutFileRangeMethodID = m_jniEnv->GetMethodID(m_callbackClass, "addLayoutFileRange", "(JJJJ)J"); + if (m_addLayoutFileRangeMethodID == NULL) { + return TSK_ERR; + } + return TSK_OK; +} + +/** +* Cache a database object for later use. Should be called on image, volume system, volume, +* pool, and file system. +* @param objId The object ID of the new object +* @param parObjId The object ID of the new object's parent +* @param type The type of object +*/ +void +TskAutoDbJava::saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type) { + TSK_DB_OBJECT objectInfo; + objectInfo.objId = objId; + objectInfo.parObjId = parObjId; + objectInfo.type = type; + m_savedObjects.push_back(objectInfo); +} + +/** +* Get a previously cached database object. +* @param objId The object ID of the object being loaded +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info) { + for (vector<TSK_DB_OBJECT>::iterator itObjs = m_savedObjects.begin(); + itObjs != m_savedObjects.end(); ++itObjs) { + TSK_DB_OBJECT* tskDbObj = &(*itObjs); + if (tskDbObj->objId == objId) { + *obj_info = tskDbObj; + return TSK_OK; + } + } + + // Object not found + return TSK_ERR; +} + +/** +* Adds image details to the existing database tables. Object ID for new image stored in objId. +* +* @param type Image type +* @param ssize Size of device sector in bytes (or 0 for default) +* @param objId The object id assigned to the image (out param) +* @param timeZone The timezone the image is from +* @param size The size of the image in bytes. +* @param md5 MD5 hash of the image +* @param sha1 SHA1 hash of the image +* @param sha256 SHA256 hash of the image +* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID). +* @param collectionDetails collection details +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5, + const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails) { + + const char *tz_cstr = timezone.c_str(); + jstring tzj = m_jniEnv->NewStringUTF(tz_cstr); + + const char *md5_cstr = md5.c_str(); + jstring md5j = m_jniEnv->NewStringUTF(md5_cstr); + + const char *sha1_cstr = sha1.c_str(); + jstring sha1j = m_jniEnv->NewStringUTF(sha1_cstr); + + const char *sha256_cstr = sha256.c_str(); + jstring sha256j = m_jniEnv->NewStringUTF(sha256_cstr); + + const char *devId_cstr = deviceId.c_str(); + jstring devIdj = m_jniEnv->NewStringUTF(devId_cstr); + + const char *coll_cstr = collectionDetails.c_str(); + jstring collj = m_jniEnv->NewStringUTF(coll_cstr); + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addImageMethodID, + type, ssize, tzj, size, md5j, sha1j, sha256j, devIdj, collj); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + saveObjectInfo(objId, 0, TSK_DB_OBJECT_TYPE_IMG); + return TSK_OK; +} + +/** +* Adds one image name +* @param objId The object ID of the image +* @param imgName The image name +* @param sequence The sequence number for this image name +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addImageName(int64_t objId, char const* imgName, int sequence) { + + jstring imgNamej = m_jniEnv->NewStringUTF(imgName); + + jint res = m_jniEnv->CallIntMethod(m_javaDbObj, m_addImageNameMethodID, + objId, imgNamej, (int64_t)sequence); + + if (res == 0) { + return TSK_OK; + } + else { + return TSK_ERR; + } +} + +/** +* Adds volume system to database. Object ID for new vs stored in objId. +* +* @param vs_info Struct containing info for this volume system +* @param parObjId Parent object ID for the volume system +* @param objId Object ID of new volume system +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId) { + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID, + parObjId, vs_info->vstype, vs_info->offset, (uint64_t)vs_info->block_size); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + // Save the vs info to use for unallocated blocks later + TSK_DB_VS_INFO vs_db; + vs_db.objId = objId; + vs_db.offset = vs_info->offset; + vs_db.vstype = vs_info->vstype; + vs_db.block_size = vs_info->block_size; + m_savedVsInfo.push_back(vs_db); + + saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VS); + return TSK_OK; +} + +/** +* Adds pool and pool volume system to database. Object ID for new pool vs stored in objId. +* +* @param pool_info Struct containing info for this pool +* @param parObjId Parent object ID for the pool +* @param objId Object ID of new pool volume system +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId) { + + // Add the pool + jlong poolObjIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addPoolMethodID, + parObjId, pool_info->ctype); + int64_t poolObjId = (int64_t)poolObjIdj; + + if (poolObjId < 0) { + return TSK_ERR; + } + saveObjectInfo(poolObjId, parObjId, TSK_DB_OBJECT_TYPE_POOL); + + // Add the pool volume + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeSystemMethodID, + poolObjIdj, TSK_VS_TYPE_APFS, pool_info->img_offset, (uint64_t)pool_info->block_size); + objId = (int64_t)objIdj; + + saveObjectInfo(objId, poolObjId, TSK_DB_OBJECT_TYPE_VS); + return TSK_OK; +} + +/** +* Adds a pool volume to database. Object ID for new pool volume stored in objId. +* +* @param pool_vol Struct containing info for this pool volume +* @param parObjId Parent object ID +* @param objId Object ID of new pool volume +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol, + int64_t parObjId, int64_t& objId) { + + jstring descj = m_jniEnv->NewStringUTF(pool_vol->desc); + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID, + parObjId, (int64_t)pool_vol->index, pool_vol->block, pool_vol->num_blocks, + descj, pool_vol->flags); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL); + return TSK_OK; +} + +/** +* Adds a volume to database. Object ID for new volume stored in objId. +* +* @param vs_part Struct containing info for this volume +* @param parObjId Parent object ID +* @param objId Object ID of new volume +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addVolumeInfo(const TSK_VS_PART_INFO* vs_part, + int64_t parObjId, int64_t& objId) { + + jstring descj = m_jniEnv->NewStringUTF(vs_part->desc); + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID, + parObjId, (uint64_t)vs_part->addr, vs_part->start, vs_part->len, + descj, vs_part->flags); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + // Save the volume info for creating unallocated blocks later + TSK_DB_VS_PART_INFO vs_part_db; + vs_part_db.objId = objId; + vs_part_db.addr = vs_part->addr; + vs_part_db.start = vs_part->start; + vs_part_db.len = vs_part->len; + strncpy(vs_part_db.desc, vs_part->desc, TSK_MAX_DB_VS_PART_INFO_DESC_LEN - 1); + vs_part_db.flags = vs_part->flags; + m_savedVsPartInfo.push_back(vs_part_db); + + saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_VOL); + return TSK_OK; +} + +/** +* Adds a file system to database. Object ID for new file system stored in objId. +* +* @param fs_info Struct containing info for this file system +* @param parObjId Parent object ID +* @param objId Object ID of new file system +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId, + int64_t& objId) { + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileSystemMethodID, + parObjId, fs_info->offset, (int)fs_info->ftype, (uint64_t)fs_info->block_size, + fs_info->block_count, fs_info->root_inum, fs_info->first_inum, + fs_info->last_inum); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + // Save the file system info for creating unallocated blocks later + TSK_DB_FS_INFO fs_info_db; + fs_info_db.objId = objId; + fs_info_db.imgOffset = fs_info->offset; + fs_info_db.fType = fs_info->ftype; + fs_info_db.block_size = fs_info->block_size; + fs_info_db.block_count = fs_info->block_count; + fs_info_db.root_inum = fs_info->root_inum; + fs_info_db.first_inum = fs_info->first_inum; + fs_info_db.last_inum = fs_info->last_inum; + m_savedFsInfo.push_back(fs_info_db); + + saveObjectInfo(objId, parObjId, TSK_DB_OBJECT_TYPE_FS); + return TSK_OK; +} + +/** +* Adds a file to database. Object ID for new file stored in objId. +* +* @param fs_file +* @param fs_attr +* @param path File path +* @param parObjId Parent object ID +* @param fsObjId Object ID of the file system +* @param objId Object ID of new file +* @param dataSourceObjId Object ID of the data source +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addFsFile(TSK_FS_FILE* fs_file, + const TSK_FS_ATTR* fs_attr, const char* path, + int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId) { + + if (fs_file->name == NULL) + return TSK_ERR; + + // Find the object id for the parent folder. + int64_t parObjId = 0; + + // Root directory's parent should be the file system object. + // Make sure it doesn't have a name, so that we don't pick up ".." entries + if ((fs_file->fs_info->root_inum == fs_file->name->meta_addr) && + ((fs_file->name->name == NULL) || (strlen(fs_file->name->name) == 0))) { + // File is in the root directory + parObjId = fsObjId; + } else { + // Look up parent object ID + parObjId = findParObjId(fs_file, path, fsObjId); + if (parObjId == -1) { + return TSK_ERR; + } + } + + // Add the file to the database + return addFile(fs_file, fs_attr, path, fsObjId, parObjId, objId, dataSourceObjId); +} + +/** +* Extract the extension from the given file name and store it in the supplied string. +* @param name A file name +* @param extension The file name extension will be extracted to extension. +*/ +void extractExtension(char *name, char *extension) { + char *ext = strrchr(name, '.'); + + //if ext is not null and is not the entire filename... + if (ext && (name != ext)) { + size_t extLen = strlen(ext); + //... and doesn't only contain the '.' and isn't too long to be a real extension. + if ((1 < extLen) && (extLen < 15)) { + strncpy(extension, ext + 1, extLen - 1); + //normalize to lower case, only works for ascii + for (int i = 0; extension[i]; i++) { + extension[i] = tolower(extension[i]); + } + } + } +} + +/** +* Store info about a directory in a complex map structure as a cache for the +* files who are a child of this directory and want to know its object id. +* +* @param fsObjId fs id of this directory +* @param fs_file File for the directory to store +* @param path Full path (parent and this file) of the directory +* @param objId object id of the directory +*/ +void TskAutoDbJava::storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId) +{ + // skip the . and .. entries + if ((fs_file->name) && (fs_file->name->name) && (TSK_FS_ISDOT(fs_file->name->name))) + { + return; + } + + uint32_t seq; + uint32_t path_hash = hash((const unsigned char *)path); + + /* NTFS uses sequence, otherwise we hash the path. We do this to map to the + * correct parent folder if there are two from the root dir that eventually point to + * the same folder (one deleted and one allocated) or two hard links. */ + if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) { + /* Use the sequence stored in meta (which could be one larger than the name value + * if the directory is deleted. We do this because the par_seq gets added to the + * name structure when it is added to the directory based on teh value stored in + * meta. */ + seq = fs_file->meta->seq; + } + else { + seq = path_hash; + } + + map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId]; + if (fsMap.count(fs_file->name->meta_addr) == 0) { + fsMap[fs_file->name->meta_addr][seq][path_hash] = objId; + } + else { + map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->meta_addr]; + if (fileMap.count(seq) == 0) { + fileMap[seq][path_hash] = objId; + } + } +} + + +/** +* Adds a file and its associated slack file to database. Object ID for new file stored in objId. +* +* @param fs_file +* @param fs_attr +* @param path File path +* @param fsObjId Object ID of the file system +* @param parObjId Parent object ID +* @param objId Object ID of new file +* @param dataSourceObjId Object ID of the data source +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addFile(TSK_FS_FILE* fs_file, + const TSK_FS_ATTR* fs_attr, const char* path, + int64_t fsObjId, int64_t parObjId, + int64_t& objId, int64_t dataSourceObjId) +{ + time_t mtime = 0; + time_t crtime = 0; + time_t ctime = 0; + time_t atime = 0; + TSK_OFF_T size = 0; + int meta_type = 0; + int meta_flags = 0; + int meta_mode = 0; + int gid = 0; + int uid = 0; + int type = TSK_FS_ATTR_TYPE_NOT_FOUND; + int idx = 0; + + if (fs_file->name == NULL) + return TSK_OK; + + if (fs_file->meta) { + mtime = fs_file->meta->mtime; + atime = fs_file->meta->atime; + ctime = fs_file->meta->ctime; + crtime = fs_file->meta->crtime; + meta_type = fs_file->meta->type; + meta_flags = fs_file->meta->flags; + meta_mode = fs_file->meta->mode; + gid = fs_file->meta->gid; + uid = fs_file->meta->uid; + } + + size_t attr_nlen = 0; + if (fs_attr) { + type = fs_attr->type; + idx = fs_attr->id; + size = fs_attr->size; + if (fs_attr->name) { + if ((fs_attr->type != TSK_FS_ATTR_TYPE_NTFS_IDXROOT) || + (strcmp(fs_attr->name, "$I30") != 0)) { + attr_nlen = strlen(fs_attr->name); + } + } + } + + // sanity check + if (size < 0) { + size = 0; + } + + // combine name and attribute name + size_t len = strlen(fs_file->name->name); + char * name; + size_t nlen = len + attr_nlen + 11; // Extra space for possible colon and '-slack' + if ((name = (char *)tsk_malloc(nlen)) == NULL) { + return TSK_ERR; + } + + strncpy(name, fs_file->name->name, nlen); + + char extension[24] = ""; + extractExtension(name, extension); + + // Add the attribute name + if (attr_nlen > 0) { + strncat(name, ":", nlen - strlen(name)); + if (fs_attr != NULL) { + strncat(name, fs_attr->name, nlen - strlen(name)); + } + } + + // clean up path + // +2 = space for leading slash and terminating null + size_t path_len = strlen(path) + 2; + char* escaped_path; + if ((escaped_path = (char *)tsk_malloc(path_len)) == NULL) { + free(name); + return TSK_ERR; + } + strncpy(escaped_path, "/", path_len); + strncat(escaped_path, path, path_len - strlen(escaped_path)); + + jstring namej = m_jniEnv->NewStringUTF(name); + jstring pathj = m_jniEnv->NewStringUTF(escaped_path); + jstring extj = m_jniEnv->NewStringUTF(extension); + + // Add the file to the database + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID, + parObjId, fsObjId, + dataSourceObjId, + TSK_DB_FILES_TYPE_FS, + type, idx, namej, + fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq, + fs_file->name->type, meta_type, fs_file->name->flags, meta_flags, + size, + (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime, + meta_mode, gid, uid, + pathj, extj); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + // If dir, update parent ID cache + if (TSK_FS_IS_DIR_META(meta_type)){ + std::string fullPath = std::string(path) + fs_file->name->name; + storeObjId(fsObjId, fs_file, fullPath.c_str(), objId); + } + + // Add entry for the slack space. + // Current conditions for creating a slack file: + // - File name is not empty, "." or ".." + // - Data is non-resident + // - The allocated size is greater than the initialized file size + // See github issue #756 on why initsize and not size. + // - The data is not compressed + if ((fs_attr != NULL) + && ((strlen(name) > 0) && (!TSK_FS_ISDOT(name))) + && (!(fs_file->meta->flags & TSK_FS_META_FLAG_COMP)) + && (fs_attr->flags & TSK_FS_ATTR_NONRES) + && (fs_attr->nrd.allocsize > fs_attr->nrd.initsize)) { + strncat(name, "-slack", 6); + if (strlen(extension) > 0) { + strncat(extension, "-slack", 6); + } + jstring slackNamej = m_jniEnv->NewStringUTF(name); + jstring slackExtj = m_jniEnv->NewStringUTF(extension); + TSK_OFF_T slackSize = fs_attr->nrd.allocsize - fs_attr->nrd.initsize; + + // Add slack file to database + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addFileMethodID, + parObjId, fsObjId, + dataSourceObjId, + TSK_DB_FILES_TYPE_SLACK, + type, idx, slackNamej, + fs_file->name->meta_addr, (uint64_t)fs_file->name->meta_seq, + TSK_FS_NAME_TYPE_REG, TSK_FS_META_TYPE_REG, fs_file->name->flags, meta_flags, + slackSize, + (unsigned long long)crtime, (unsigned long long)ctime, (unsigned long long) atime, (unsigned long long) mtime, + meta_mode, gid, uid, // md5TextPtr, known, + pathj, slackExtj); + int64_t slackObjId = (int64_t)objIdj; + + if (slackObjId < 0) { + return TSK_ERR; + } + } + + free(name); + free(escaped_path); + + return TSK_OK; +} + +// Internal function object to check for range overlap +typedef struct _checkFileLayoutRangeOverlap { + const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges; + bool hasOverlap; + + explicit _checkFileLayoutRangeOverlap(const vector<TSK_DB_FILE_LAYOUT_RANGE> & ranges) + : ranges(ranges), hasOverlap(false) {} + + bool getHasOverlap() const { return hasOverlap; } + void operator() (const TSK_DB_FILE_LAYOUT_RANGE & range) { + if (hasOverlap) + return; //no need to check other + + uint64_t start = range.byteStart; + uint64_t end = start + range.byteLen; + + vector<TSK_DB_FILE_LAYOUT_RANGE>::const_iterator it; + for (it = ranges.begin(); it != ranges.end(); ++it) { + const TSK_DB_FILE_LAYOUT_RANGE * otherRange = &(*it); + if (&range == otherRange) + continue; //skip, it's the same range + uint64_t otherStart = otherRange->byteStart; + uint64_t otherEnd = otherStart + otherRange->byteLen; + if (start <= otherEnd && end >= otherStart) { + hasOverlap = true; + break; + } + } + } + +} checkFileLayoutRangeOverlap; + +/** +* Internal helper method to add unalloc, unused and carved files with layout ranges to db +* Generates file_name and populates tsk_files, tsk_objects and tsk_file_layout tables +* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table +* @param dbFileType Type of file +* @param parentObjId Id of the parent object in the database (fs, volume, or image) +* @param fsObjId parent fs, or NULL if the file is not associated with fs +* @param size Number of bytes in file +* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in) +* @param objId object id of the file object created (output) +* @param dataSourceObjId The object ID for the data source +* @returns TSK_OK on success or TSK_ERR on error. +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addFileWithLayoutRange(const TSK_DB_FILES_TYPE_ENUM dbFileType, const int64_t parentObjId, + const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId) { + + const size_t numRanges = ranges.size(); + + if (numRanges < 1) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("Error addFileWithLayoutRange() - no ranges present"); + return TSK_ERR; + } + + stringstream fileNameSs; + switch (dbFileType) { + case TSK_DB_FILES_TYPE_UNALLOC_BLOCKS: + fileNameSs << "Unalloc"; + break; + + case TSK_DB_FILES_TYPE_UNUSED_BLOCKS: + fileNameSs << "Unused"; + break; + + case TSK_DB_FILES_TYPE_CARVED: + fileNameSs << "Carved"; + break; + default: + stringstream sserr; + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + sserr << "Error addFileWithLayoutRange() - unsupported file type for file layout range: "; + sserr << (int)dbFileType; + tsk_error_set_errstr("%s", sserr.str().c_str()); + return TSK_ERR; + } + + //ensure layout ranges are sorted (to generate file name and to be inserted in sequence order) + sort(ranges.begin(), ranges.end()); + + //dome some checking + //ensure there is no overlap and each range has unique byte range + const checkFileLayoutRangeOverlap & overlapRes = + for_each(ranges.begin(), ranges.end(), checkFileLayoutRangeOverlap(ranges)); + if (overlapRes.getHasOverlap()) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("Error addFileWithLayoutRange() - overlap detected between ranges"); + return TSK_ERR; + } + + //construct filename with parent obj id, start byte of first range, end byte of last range + fileNameSs << "_" << parentObjId << "_" << ranges[0].byteStart; + fileNameSs << "_" << (ranges[numRanges - 1].byteStart + ranges[numRanges - 1].byteLen); + + jstring namej = m_jniEnv->NewStringUTF(fileNameSs.str().c_str()); + + // Insert into tsk files and tsk objects + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileMethodID, + parentObjId, fsObjId, dataSourceObjId, dbFileType, namej, size); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + + // Fill in fileObjId and insert ranges + for (vector<TSK_DB_FILE_LAYOUT_RANGE>::iterator it = ranges.begin(); + it != ranges.end(); ++it) { + TSK_DB_FILE_LAYOUT_RANGE & range = *it; + range.fileObjId = objId; + if (-1 == m_jniEnv->CallLongMethod(m_javaDbObj, m_addLayoutFileRangeMethodID, + objId, range.byteStart, range.byteLen, range.sequence)) { + return TSK_ERR; + } + } + + return TSK_OK; +} + +/** +* Adds information about a unallocated file with layout ranges into the database. +* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table +* @param parentObjId Id of the parent object in the database (fs, volume, or image) +* @param fsObjId parent fs, or NULL if the file is not associated with fs +* @param size Number of bytes in file +* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in) +* @param objId object id of the file object created (output) +* @param dataSourceObjId The object ID for the data source +* @returns TSK_OK on success or TSK_ERR on error. +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId) { + return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNALLOC_BLOCKS, parentObjId, fsObjId, size, ranges, objId, + dataSourceObjId); +} + +/** +* Adds information about a unused file with layout ranges into the database. +* Adds a single entry to tsk_files table with an auto-generated file name, tsk_objects table, and one or more entries to tsk_file_layout table +* @param parentObjId Id of the parent object in the database (fs, volume, or image) +* @param fsObjId parent fs, or NULL if the file is not associated with fs +* @param size Number of bytes in file +* @param ranges vector containing one or more TSK_DB_FILE_LAYOUT_RANGE layout ranges (in) +* @param objId object id of the file object created (output) +* @param dataSourceObjId The object ID for the data source +* @returns TSK_OK on success or TSK_ERR on error. +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId) { + return addFileWithLayoutRange(TSK_DB_FILES_TYPE_UNUSED_BLOCKS, parentObjId, fsObjId, size, ranges, objId, + dataSourceObjId); +} + + + +/** +* Add a virtual dir to hold unallocated block files for this file system. +* @param fsObjId Object ID of the file system +* @param objId Object ID of the created virtual dir +* @param dataSourceObjId Object ID of the data source +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId, + int64_t dataSourceObjId) { + + const char * const unallocDirName = "$Unalloc"; + jstring namej = m_jniEnv->NewStringUTF(unallocDirName); + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addUnallocParentMethodID, + fsObjId, namej); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + return TSK_OK; +} + +/** +* Return a hash of the passed in string. We use this +* for full paths. +* From: http://www.cse.yorku.ca/~oz/hash.html +*/ +uint32_t +TskAutoDbJava::hash(const unsigned char* str) +{ + uint32_t hash = 5381; + int c; + + while ((c = *str++)) { + // skip slashes -> normalizes leading/ending/double slashes + if (c == '/') + continue; + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + return hash; +} + +/* +* Utility method to break up path into parent folder and folder/file name. +* @param path Path of folder that we want to analyze +* @param ret_parent_path pointer to parent path (begins and ends with '/') +* @param ret_name pointer to final folder/file name +* @returns 0 on success, 1 on error +*/ +bool +TskAutoDbJava::getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name) { + // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder + // name to match with the 'name' column in tsk_files table + + // reset all arrays + parent_name[0] = '\0'; + parent_path[0] = '\0'; + + size_t path_len = strlen(path); + if (path_len >= MAX_PATH_LENGTH_JAVA_DB_LOOKUP) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("TskDb::getParentPathAndName: Path is too long. Length = %zd, Max length = %d", path_len, MAX_PATH_LENGTH_JAVA_DB_LOOKUP); + // assign return values to pointers + *ret_parent_path = ""; + *ret_name = ""; + return 1; + } + + // check if empty path or just "/" were passed in + if (path_len == 0 || (strcmp(path, "/") == 0)) { + *ret_name = ""; + *ret_parent_path = "/"; + return 0; + } + + + // step 1, copy everything into parent_path and clean it up + // add leading slash if its not in input. + if (path[0] != '/') { + sprintf(parent_path, "%s", "/"); + } + + strncat(parent_path, path, MAX_PATH_LENGTH_JAVA_DB_LOOKUP); + + // remove trailing slash + if (parent_path[strlen(parent_path) - 1] == '/') { + parent_path[strlen(parent_path) - 1] = '\0'; + } + + // replace all non-UTF8 characters + tsk_cleanupUTF8(parent_path, '^'); + + // Step 2, move the final folder/file to parent_file + + // Find the last '/' + char *chptr = strrchr(parent_path, '/'); + if (chptr) { + // character found in the string + size_t position = chptr - parent_path; + + sprintf(parent_name, "%s", chptr + 1); // copy everything after slash into parent_name + *ret_name = parent_name; + + parent_path[position + 1] = '\0'; // add terminating null after last "/" + *ret_parent_path = parent_path; + } + else { + // "/" character not found. the entire path is parent file name. parent path is "/" + *ret_name = parent_path; + *ret_parent_path = "/"; + } + return 0; +} + +/** +* Find parent object id of TSK_FS_FILE. Use local cache map, if not found, fall back to SQL +* @param fs_file file to find parent obj id for +* @param parentPath Path of parent folder that we want to match +* @param fsObjId fs id of this file +* @returns parent obj id ( > 0), -1 on error +*/ +int64_t +TskAutoDbJava::findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId) +{ + uint32_t seq; + uint32_t path_hash = hash((const unsigned char *)parentPath); + + /* NTFS uses sequence, otherwise we hash the path. We do this to map to the + * correct parent folder if there are two from the root dir that eventually point to + * the same folder (one deleted and one allocated) or two hard links. */ + if (TSK_FS_TYPE_ISNTFS(fs_file->fs_info->ftype)) + { + seq = fs_file->name->par_seq; + } + else + { + seq = path_hash; + } + + //get from cache by parent meta addr, if available + map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > >& fsMap = m_parentDirIdCache[fsObjId]; + if (fsMap.count(fs_file->name->par_addr) > 0) + { + map<uint32_t, map<uint32_t, int64_t> >& fileMap = fsMap[fs_file->name->par_addr]; + if (fileMap.count(seq) > 0) { + map<uint32_t, int64_t>& pathMap = fileMap[seq]; + if (pathMap.count(path_hash) > 0) { + return pathMap[path_hash]; + } + } + } + + // Need to break up 'path' in to the parent folder to match in 'parent_path' and the folder + // name to match with the 'name' column in tsk_files table + const char *parent_name = ""; + const char *parent_path = ""; + if (getParentPathAndName(parentPath, &parent_path, &parent_name)) { + return -1; + } + + jstring jpath = m_jniEnv->NewStringUTF(parent_path); + jstring jname = m_jniEnv->NewStringUTF(parent_name); + + // Look up in the database + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_getParentIdMethodID, + fs_file->name->par_addr, fsObjId, jpath, jname); + int64_t objId = (int64_t)objIdj; + + if (objId < 0) { + return -1; + } + + return objId; +} + +/** +* Adds a new volume that will hold the unallocated blocks for the pool. +* +* @param vol_index The index for the new volume (should be one higher than the number of pool volumes) +* @param parObjId The object ID of the parent volume system +* @param objId Will be set to the object ID of the new volume +* +* @returns TSK_ERR on error, TSK_OK on success +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId) +{ + char *desc = "Unallocated Blocks"; + jstring descj = m_jniEnv->NewStringUTF(desc); + + jlong objIdj = m_jniEnv->CallLongMethod(m_javaDbObj, m_addVolumeMethodID, + parObjId, vol_index, 0, 0, + descj, 0); + objId = (int64_t)objIdj; + + if (objId < 0) { + return TSK_ERR; + } + return TSK_OK; +} + +void TskAutoDbJava::close() { + if (m_jniEnv == NULL) { + return; + } + + if (m_javaDbObj != NULL) { + m_jniEnv->DeleteGlobalRef(m_javaDbObj); + } + + if (m_callbackClass != NULL) { + m_jniEnv->DeleteGlobalRef(m_callbackClass); + } +} + +int64_t TskAutoDbJava::getImageID() { + return m_curImgId; +} + +void TskAutoDbJava::closeImage() { + TskAuto::closeImage(); +} + +void TskAutoDbJava::setAddFileSystems(bool addFileSystems) { + m_addFileSystems = addFileSystems; +} + +void TskAutoDbJava::setNoFatFsOrphans(bool noFatFsOrphans) { + m_noFatFsOrphans = noFatFsOrphans; +} + +void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace) { + setAddUnallocSpace(addUnallocSpace, -1); +} + +void TskAutoDbJava::setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize) { + m_addUnallocSpace = addUnallocSpace; + m_minChunkSize = minChunkSize; + m_maxChunkSize = -1; +} + +void TskAutoDbJava::setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize) { + m_addUnallocSpace = true; + m_minChunkSize = minChunkSize; + m_maxChunkSize = maxChunkSize; +} + +/** + * Adds an image to the database. + * + * @param a_num Number of image parts + * @param a_images Array of paths to the image parts + * @param a_type Image type + * @param a_ssize Size of device sector in bytes (or 0 for default) + * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID). + * @return 0 for success, 1 for failure + */ +uint8_t + TskAutoDbJava::openImageUtf8(int a_num, const char *const a_images[], + TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId) +{ + uint8_t retval = + TskAuto::openImageUtf8(a_num, a_images, a_type, a_ssize); + if (retval != 0) { + return retval; + } + + if (addImageDetails(a_deviceId)) { + return 1; + } + return 0; +} + +/** + * Adds an image to the database. + * + * @param a_num Number of image parts + * @param a_images Array of paths to the image parts + * @param a_type Image type + * @param a_ssize Size of device sector in bytes (or 0 for default) + * @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID). + * @return 0 for success, 1 for failure + */ +uint8_t + TskAutoDbJava::openImage(int a_num, const TSK_TCHAR * const a_images[], + TSK_IMG_TYPE_ENUM a_type, unsigned int a_ssize, const char* a_deviceId) +{ + +#ifdef TSK_WIN32 + + uint8_t retval = TskAuto::openImage(a_num, a_images, a_type, a_ssize); + + if (retval != 0) { + return retval; + } + + return (addImageDetails(a_deviceId)); +#else + return openImageUtf8(a_num, a_images, a_type, a_ssize, a_deviceId); +#endif +} + +/** +* Adds an image to the database. Requires that m_img_info is already initialized +* +* @param a_deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID). +* @return 0 for success, 1 for failure +*/ +uint8_t +TskAutoDbJava::openImage(const char* a_deviceId) +{ + if (m_img_info == NULL) { + return 1; + } + + return(addImageDetails(a_deviceId)); +} + +/** + * Adds image details to the existing database tables. + * + * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID). + * @return Returns 0 for success, 1 for failure + */ +uint8_t +TskAutoDbJava::addImageDetails(const char* deviceId) +{ + string md5 = ""; + string sha1 = ""; + string collectionDetails = ""; +#if HAVE_LIBEWF + if (m_img_info->itype == TSK_IMG_TYPE_EWF_EWF) { + // @@@ This should really probably be inside of a tsk_img_ method + IMG_EWF_INFO *ewf_info = (IMG_EWF_INFO *)m_img_info; + if (ewf_info->md5hash_isset) { + md5 = ewf_info->md5hash; + } + if (ewf_info->sha1hash_isset) { + sha1 = ewf_info->sha1hash; + } + + collectionDetails = ewf_get_details(ewf_info); + } +#endif + + string devId; + if (NULL != deviceId) { + devId = deviceId; + } else { + devId = ""; + } + if (TSK_OK != addImageInfo(m_img_info->itype, m_img_info->sector_size, + m_curImgId, m_curImgTZone, m_img_info->size, md5, sha1, "", devId, collectionDetails)) { + registerError(); + return 1; + } + + + + char **img_ptrs; +#ifdef TSK_WIN32 + // convert image paths to UTF-8 + img_ptrs = (char **)tsk_malloc(m_img_info->num_img * sizeof(char *)); + if (img_ptrs == NULL) { + return 1; + } + + for (int i = 0; i < m_img_info->num_img; i++) { + char * img2 = (char*)tsk_malloc(1024 * sizeof(char)); + UTF8 *ptr8; + UTF16 *ptr16; + + ptr8 = (UTF8 *)img2; + ptr16 = (UTF16 *)m_img_info->images[i]; + + uint8_t retval = + tsk_UTF16toUTF8_lclorder((const UTF16 **)&ptr16, (UTF16 *) + & ptr16[TSTRLEN(m_img_info->images[i]) + 1], &ptr8, + (UTF8 *)((uintptr_t)ptr8 + 1024), TSKlenientConversion); + if (retval != TSKconversionOK) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_UNICODE); + tsk_error_set_errstr("Error converting image to UTF-8\n"); + return 1; + } + img_ptrs[i] = img2; + } +#else + img_ptrs = m_img_info->images; +#endif + + // Add the image names + for (int i = 0; i < m_img_info->num_img; i++) { + const char *img_ptr = img_ptrs[i]; + + if (TSK_OK != addImageName(m_curImgId, img_ptr, i)) { + registerError(); + return 1; + } + } + +#ifdef TSK_WIN32 + //cleanup + for (int i = 0; i < m_img_info->num_img; ++i) { + free(img_ptrs[i]); + } + free(img_ptrs); +#endif + + return 0; +} + + +TSK_FILTER_ENUM +TskAutoDbJava::filterVs(const TSK_VS_INFO * vs_info) +{ + m_vsFound = true; + if (TSK_OK != addVsInfo(vs_info, m_curImgId, m_curVsId)) { + registerError(); + return TSK_FILTER_STOP; + } + + return TSK_FILTER_CONT; +} + +TSK_FILTER_ENUM +TskAutoDbJava::filterPool(const TSK_POOL_INFO * pool_info) +{ + m_poolFound = true; + + if (m_volFound && m_vsFound) { + // there's a volume system and volume + if (TSK_OK != addPoolInfoAndVS(pool_info, m_curVolId, m_curPoolVs)) { + registerError(); + return TSK_FILTER_STOP; + } + // Save the parent obj ID for the pool + m_poolOffsetToParentId[pool_info->img_offset] = m_curVolId; + } + else { + // pool doesn't live in a volume, use image as parent + if (TSK_OK != addPoolInfoAndVS(pool_info, m_curImgId, m_curPoolVs)) { + registerError(); + return TSK_FILTER_STOP; + } + // Save the parent obj ID for the pool + m_poolOffsetToParentId[pool_info->img_offset] = m_curImgId; + } + + // Store the volume system object ID for later use + m_poolOffsetToVsId[pool_info->img_offset] = m_curPoolVs; + + return TSK_FILTER_CONT; +} + +/** +* Adds unallocated pool blocks to a new volume. +* +* @param numPool Will be updated with the number of pools processed +* +* @return Returns 0 for success, 1 for failure +*/ +TSK_RETVAL_ENUM +TskAutoDbJava::addUnallocatedPoolBlocksToDb(size_t & numPool) { + + for (int i = 0; i < m_poolInfos.size(); i++) { + const TSK_POOL_INFO * pool_info = m_poolInfos[i]; + if (m_poolOffsetToVsId.find(pool_info->img_offset) == m_poolOffsetToVsId.end()) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - could not find volume system object ID for pool at offset %lld", pool_info->img_offset); + return TSK_ERR; + } + int64_t curPoolVs = m_poolOffsetToVsId[pool_info->img_offset]; + + /* Make sure the pool_info is still allocated */ + if (pool_info->tag != TSK_POOL_INFO_TAG) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - pool_info is not allocated"); + return TSK_ERR; + } + + /* Only APFS pools are currently supported */ + if (pool_info->ctype != TSK_POOL_TYPE_APFS) { + continue; + } + + /* Increment the count of pools found */ + numPool++; + + /* Create the volume */ + int64_t unallocVolObjId; + if (TSK_ERR == addUnallocatedPoolVolume(pool_info->num_vols, curPoolVs, unallocVolObjId)) { + tsk_error_reset(); + tsk_error_set_errno(TSK_ERR_AUTO_DB); + tsk_error_set_errstr("Error addUnallocatedPoolBlocksToDb() - error createing unallocated space pool volume"); + return TSK_ERR; + } + + /* Create the unallocated space files */ + TSK_FS_ATTR_RUN * unalloc_runs = tsk_pool_unallocated_runs(pool_info); + TSK_FS_ATTR_RUN * current_run = unalloc_runs; + vector<TSK_DB_FILE_LAYOUT_RANGE> ranges; + while (current_run != NULL) { + + TSK_DB_FILE_LAYOUT_RANGE tempRange(current_run->addr * pool_info->block_size, current_run->len * pool_info->block_size, 0); + + ranges.push_back(tempRange); + int64_t fileObjId = 0; + if (TSK_ERR == addUnallocBlockFile(unallocVolObjId, NULL, current_run->len * pool_info->block_size, ranges, fileObjId, m_curImgId)) { + registerError(); + tsk_fs_attr_run_free(unalloc_runs); + return TSK_ERR; + } + + current_run = current_run->next; + ranges.clear(); + } + tsk_fs_attr_run_free(unalloc_runs); + } + + return TSK_OK; +} + +TSK_FILTER_ENUM +TskAutoDbJava::filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol) +{ + + if (TSK_OK != addPoolVolumeInfo(pool_vol, m_curPoolVs, m_curPoolVol)) { + registerError(); + return TSK_FILTER_STOP; + } + + return TSK_FILTER_CONT; +} + +TSK_FILTER_ENUM +TskAutoDbJava::filterVol(const TSK_VS_PART_INFO * vs_part) +{ + m_volFound = true; + m_foundStructure = true; + m_poolFound = false; + + if (TSK_OK != addVolumeInfo(vs_part, m_curVsId, m_curVolId)) { + registerError(); + return TSK_FILTER_STOP; + } + + return TSK_FILTER_CONT; +} + + +TSK_FILTER_ENUM +TskAutoDbJava::filterFs(TSK_FS_INFO * fs_info) +{ + TSK_FS_FILE *file_root; + m_foundStructure = true; + + if (m_poolFound) { + // there's a pool + if (TSK_OK != addFsInfo(fs_info, m_curPoolVol, m_curFsId)) { + registerError(); + return TSK_FILTER_STOP; + } + } + else if (m_volFound && m_vsFound) { + // there's a volume system and volume + if (TSK_OK != addFsInfo(fs_info, m_curVolId, m_curFsId)) { + registerError(); + return TSK_FILTER_STOP; + } + } + else { + // file system doesn't live in a volume, use image as parent + if (TSK_OK != addFsInfo(fs_info, m_curImgId, m_curFsId)) { + registerError(); + return TSK_FILTER_STOP; + } + } + + + // We won't hit the root directory on the walk, so open it now + if ((file_root = tsk_fs_file_open(fs_info, NULL, "/")) != NULL) { + processFile(file_root, ""); + tsk_fs_file_close(file_root); + file_root = NULL; + } + + + // make sure that flags are set to get all files -- we need this to + // find parent directory + + TSK_FS_DIR_WALK_FLAG_ENUM filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM) + (TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC); + + //check if to skip processing of FAT orphans + if (m_noFatFsOrphans + && TSK_FS_TYPE_ISFAT(fs_info->ftype) ) { + filterFlags = (TSK_FS_DIR_WALK_FLAG_ENUM) (filterFlags | TSK_FS_DIR_WALK_FLAG_NOORPHAN); + } + + setFileFilterFlags(filterFlags); + + return TSK_FILTER_CONT; +} + +/* Insert the file data into the file table. + * @param fs_file + * @param fs_attr + * @param path + * Returns TSK_ERR on error. + */ +TSK_RETVAL_ENUM + TskAutoDbJava::insertFileData(TSK_FS_FILE * fs_file, + const TSK_FS_ATTR * fs_attr, const char *path) +{ + if (-1 == addFsFile(fs_file, fs_attr, path, m_curFsId, m_curFileId, + m_curImgId)) { + registerError(); + return TSK_ERR; + } + + return TSK_OK; +} + +/** + * Analyzes the open image and adds image info to a database. + * Does not deal with transactions and such. Refer to startAddImage() + * for more control. + * @returns 1 if a critical error occurred (DB doesn't exist, no file system, etc.), 2 if errors occurred at some point adding files to the DB (corrupt file, etc.), and 0 otherwise. Errors will have been registered. + */ +uint8_t TskAutoDbJava::addFilesInImgToDb() +{ + // @@@ This seems bad because we are overriding what the user may + // have set. We should remove the public API if we are going to + // override it -- presumably this was added so that we always have + // unallocated volume space... + setVolFilterFlags((TSK_VS_PART_FLAG_ENUM) (TSK_VS_PART_FLAG_ALLOC | + TSK_VS_PART_FLAG_UNALLOC)); + + uint8_t retVal = 0; + if (findFilesInImg()) { + // map the boolean return value from findFiles to the three-state return value we use + // @@@ findFiles should probably return this three-state enum too + if (m_foundStructure == false) { + retVal = 1; + } + else { + retVal = 2; + } + } + + TSK_RETVAL_ENUM addUnallocRetval = TSK_OK; + if (m_addUnallocSpace) + addUnallocRetval = addUnallocSpaceToDb(); + + // findFiles return value trumps unalloc since it can return either 2 or 1. + if (retVal) { + return retVal; + } + else if (addUnallocRetval == TSK_ERR) { + return 2; + } + else { + return 0; + } +} + + +/** + * Start the process to add image/file metadata to database inside of a transaction. + * User must call either commitAddImage() to commit the changes, + * or revertAddImage() to revert them. + * + * @param numImg Number of image parts + * @param imagePaths Array of paths to the image parts + * @param imgType Image type + * @param sSize Size of device sector in bytes (or 0 for default) + * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID) + * @return 0 for success, 1 for failure + */ +uint8_t + TskAutoDbJava::startAddImage(int numImg, const TSK_TCHAR * const imagePaths[], + TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId) +{ + if (tsk_verbose) + tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n"); + + if (openImage(numImg, imagePaths, imgType, sSize, deviceId)) { + tsk_error_set_errstr2("TskAutoDbJava::startAddImage"); + registerError(); + return 1; + } + + if (m_imageWriterEnabled) { + tsk_img_writer_create(m_img_info, m_imageWriterPath); + } + + if (m_addFileSystems) { + return addFilesInImgToDb(); + } else { + return 0; + } +} + +/** +* Start the process to add image/file metadata to database inside of a transaction. +* User must call either commitAddImage() to commit the changes, +* or revertAddImage() to revert them. +* +* @param img_info Previously initialized TSK_IMG_INFO object +* @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID) +* @return 0 for success, 1 for failure +*/ +uint8_t +TskAutoDbJava::startAddImage(TSK_IMG_INFO * img_info, const char* deviceId) +{ + openImageHandle(img_info); + + if (m_img_info == NULL) { + return 1; + } + + if (tsk_verbose) + tsk_fprintf(stderr, "TskAutoDbJava::startAddImage: Starting add image process\n"); + + if (openImage(deviceId)) { + tsk_error_set_errstr2("TskAutoDbJava::startAddImage"); + registerError(); + return 1; + } + + if (m_imageWriterEnabled) { + if (tsk_img_writer_create(m_img_info, m_imageWriterPath)) { + registerError(); + return 1; + } + } + + if (m_addFileSystems) { + return addFilesInImgToDb(); + } + else { + return 0; + } +} + + +#ifdef WIN32 +/** + * Start the process to add image/file metadata to database inside of a transaction. + * Same functionality as addFilesInImgToDb(). Reverts + * all changes on error. User must call either commitAddImage() to commit the changes, + * or revertAddImage() to revert them. + * + * @param numImg Number of image parts + * @param imagePaths Array of paths to the image parts + * @param imgType Image type + * @param sSize Size of device sector in bytes (or 0 for default) + * @param deviceId An ASCII-printable identifier for the device associated with the data source that is intended to be unique across multiple cases (e.g., a UUID) + * @return 0 for success 1, for failure + */ +uint8_t + TskAutoDbJava::startAddImage(int numImg, const char *const imagePaths[], + TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId) +{ + if (tsk_verbose) + tsk_fprintf(stderr, "TskAutoDbJava::startAddImage_utf8: Starting add image process\n"); + + if (openImageUtf8(numImg, imagePaths, imgType, sSize, deviceId)) { + tsk_error_set_errstr2("TskAutoDbJava::startAddImage"); + registerError(); + return 1; + } + if (m_imageWriterEnabled) { + tsk_img_writer_create(m_img_info, m_imageWriterPath); + } + + if (m_addFileSystems) { + return addFilesInImgToDb(); + } else { + return 0; + } +} +#endif + + +/** + * Cancel the running process. Will not be handled immediately. + */ +void + TskAutoDbJava::stopAddImage() +{ + if (tsk_verbose) + tsk_fprintf(stderr, "TskAutoDbJava::stopAddImage: Stop request received\n"); + + m_stopped = true; + setStopProcessing(); + // flag is checked every time processFile() is called +} + +/** + * Set the current image's timezone + */ +void +TskAutoDbJava::setTz(string tzone) +{ + m_curImgTZone = tzone; +} + +TSK_RETVAL_ENUM +TskAutoDbJava::processFile(TSK_FS_FILE * fs_file, const char *path) +{ + // Check if the process has been canceled + if (m_stopped) { + if (tsk_verbose) + tsk_fprintf(stderr, "TskAutoDbJava::processFile: Stop request detected\n"); + return TSK_STOP; + } + + /* Update the current directory, which can be used to show + * progress. If we get a directory, then use its name. We + * do this so that when we are searching for orphan files, then + * we at least show $OrphanFiles as status. The secondary check + * is to grab the parent folder from files once we return back + * into a folder when we are doing our depth-first recursion. */ + if (isDir(fs_file)) { + m_curDirAddr = fs_file->name->meta_addr; + tsk_take_lock(&m_curDirPathLock); + m_curDirPath = string(path) + fs_file->name->name; + tsk_release_lock(&m_curDirPathLock); + } + else if (m_curDirAddr != fs_file->name->par_addr) { + m_curDirAddr = fs_file->name->par_addr; + tsk_take_lock(&m_curDirPathLock); + m_curDirPath = path; + tsk_release_lock(&m_curDirPathLock); + } + + /* process the attributes. The case of having 0 attributes can occur + * with virtual / sparse files and HFS directories. + * At some point, this can probably be cleaned + * up if TSK is more consistent about if there should always be an + * attribute or not. Sometimes, none of the attributes are added + * because of their type and we always want to add a reference to + * every file. */ + TSK_RETVAL_ENUM retval = TSK_OK; + m_attributeAdded = false; + if (tsk_fs_file_attr_getsize(fs_file) > 0) { + retval = processAttributes(fs_file, path); + } + + // insert a general row if we didn't add a specific attribute one + if ((retval == TSK_OK) && (m_attributeAdded == false)) { + retval = insertFileData(fs_file, NULL, path); + } + + // reset the file id + m_curFileId = 0; + + if (retval == TSK_STOP) + return TSK_STOP; + else + return TSK_OK; +} + + +// we return only OK or STOP -- errors are registered only and OK is returned. +TSK_RETVAL_ENUM +TskAutoDbJava::processAttribute(TSK_FS_FILE * fs_file, + const TSK_FS_ATTR * fs_attr, const char *path) +{ + // add the file metadata for the default attribute type + if (isDefaultType(fs_file, fs_attr)) { + + if (insertFileData(fs_attr->fs_file, fs_attr, path) == TSK_ERR) { + registerError(); + return TSK_OK; + } + else { + m_attributeAdded = true; + } + } + + return TSK_OK; +} + + +/** +* Callback invoked per every unallocated block in the filesystem +* Creates file ranges and file entries +* A single file entry per consecutive range of blocks +* @param a_block block being walked +* @param a_ptr a pointer to an UNALLOC_BLOCK_WLK_TRACK struct +* @returns TSK_WALK_CONT if continue, otherwise TSK_WALK_STOP if stop processing requested +*/ +TSK_WALK_RET_ENUM TskAutoDbJava::fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr) { + UNALLOC_BLOCK_WLK_TRACK * unallocBlockWlkTrack = (UNALLOC_BLOCK_WLK_TRACK *) a_ptr; + + if (unallocBlockWlkTrack->tskAutoDbJava.m_stopAllProcessing) + return TSK_WALK_STOP; + + // initialize if this is the first block + if (unallocBlockWlkTrack->isStart) { + unallocBlockWlkTrack->isStart = false; + unallocBlockWlkTrack->curRangeStart = a_block->addr; + unallocBlockWlkTrack->prevBlock = a_block->addr; + unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size; + unallocBlockWlkTrack->nextSequenceNo = 0; + return TSK_WALK_CONT; + } + + // We want to keep consecutive blocks in the same run, so simply update prevBlock and the size + // if this one is consecutive with the last call. But, if we have hit the max chunk + // size, then break up this set of consecutive blocks. + if ((a_block->addr == unallocBlockWlkTrack->prevBlock + 1) && ((unallocBlockWlkTrack->maxChunkSize <= 0) || + (unallocBlockWlkTrack->size < unallocBlockWlkTrack->maxChunkSize))) { + unallocBlockWlkTrack->prevBlock = a_block->addr; + unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size; + return TSK_WALK_CONT; + } + + // this block is not contiguous with the previous one or we've hit the maximum size; create and add a range object + const uint64_t rangeStartOffset = unallocBlockWlkTrack->curRangeStart * unallocBlockWlkTrack->fsInfo.block_size + + unallocBlockWlkTrack->fsInfo.offset; + const uint64_t rangeSizeBytes = (1 + unallocBlockWlkTrack->prevBlock - unallocBlockWlkTrack->curRangeStart) + * unallocBlockWlkTrack->fsInfo.block_size; + unallocBlockWlkTrack->ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(rangeStartOffset, rangeSizeBytes, unallocBlockWlkTrack->nextSequenceNo++)); + + // Return (instead of adding this run) if we are going to: + // a) Make one big file with all unallocated space (minChunkSize == 0) + // or + // b) Only make an unallocated file once we have at least chunkSize bytes + // of data in our current run (minChunkSize > 0) + // In either case, reset the range pointers and add this block to the size + if ((unallocBlockWlkTrack->minChunkSize == 0) || + ((unallocBlockWlkTrack->minChunkSize > 0) && + (unallocBlockWlkTrack->size < unallocBlockWlkTrack->minChunkSize))) { + + unallocBlockWlkTrack->size += unallocBlockWlkTrack->fsInfo.block_size; + unallocBlockWlkTrack->curRangeStart = a_block->addr; + unallocBlockWlkTrack->prevBlock = a_block->addr; + return TSK_WALK_CONT; + } + + // at this point we are either chunking and have reached the chunk limit + // or we're not chunking. Either way we now add what we've got to the DB + int64_t fileObjId = 0; + TskAutoDbJava & tskAutoDbJava = unallocBlockWlkTrack->tskAutoDbJava; + if (-1 == tskAutoDbJava.addUnallocBlockFile(tskAutoDbJava.m_curUnallocDirId, + unallocBlockWlkTrack->fsObjId, unallocBlockWlkTrack->size, unallocBlockWlkTrack->ranges, fileObjId, tskAutoDbJava.m_curImgId) == TSK_ERR) { + // @@@ Handle error -> Don't have access to registerError() though... + } + + // reset + unallocBlockWlkTrack->curRangeStart = a_block->addr; + unallocBlockWlkTrack->prevBlock = a_block->addr; + unallocBlockWlkTrack->size = unallocBlockWlkTrack->fsInfo.block_size; // The current block is part of the new range + unallocBlockWlkTrack->ranges.clear(); + unallocBlockWlkTrack->nextSequenceNo = 0; + + //we don't know what the last unalloc block is in advance + //and will handle the last range in addFsInfoUnalloc() + + return TSK_WALK_CONT; +} + + +/** +* Add unallocated space for the given file system to the database. +* Create files for consecutive unalloc block ranges. +* @param dbFsInfo fs to process +* @returns TSK_OK on success, TSK_ERR on error +*/ +TSK_RETVAL_ENUM TskAutoDbJava::addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo) { + + // Unalloc space is handled separately for APFS + if (dbFsInfo.fType == TSK_FS_TYPE_APFS) { + return TSK_OK; + } + + //open the fs we have from database + TSK_FS_INFO * fsInfo = tsk_fs_open_img(m_img_info, dbFsInfo.imgOffset, dbFsInfo.fType); + if (fsInfo == NULL) { + tsk_error_set_errstr2("TskAutoDbJava::addFsInfoUnalloc: error opening fs at offset %" PRIdOFF, dbFsInfo.imgOffset); + registerError(); + return TSK_ERR; + } + + //create a "fake" dir to hold the unalloc files for the fs + if (-1 == addUnallocFsBlockFilesParent(dbFsInfo.objId, m_curUnallocDirId, m_curImgId) == TSK_ERR) { + tsk_error_set_errstr2("addFsInfoUnalloc: error creating dir for unallocated space"); + registerError(); + return TSK_ERR; + } + + //walk unalloc blocks on the fs and process them + //initialize the unalloc block walk tracking + UNALLOC_BLOCK_WLK_TRACK unallocBlockWlkTrack(*this, *fsInfo, dbFsInfo.objId, m_minChunkSize, m_maxChunkSize); + uint8_t block_walk_ret = tsk_fs_block_walk(fsInfo, fsInfo->first_block, fsInfo->last_block, (TSK_FS_BLOCK_WALK_FLAG_ENUM)(TSK_FS_BLOCK_WALK_FLAG_UNALLOC | TSK_FS_BLOCK_WALK_FLAG_AONLY), + fsWalkUnallocBlocksCb, &unallocBlockWlkTrack); + + if (block_walk_ret == 1) { + stringstream errss; + tsk_fs_close(fsInfo); + errss << "TskAutoDbJava::addFsInfoUnalloc: error walking fs unalloc blocks, fs id: "; + errss << unallocBlockWlkTrack.fsObjId; + tsk_error_set_errstr2("%s", errss.str().c_str()); + registerError(); + return TSK_ERR; + } + + if(m_stopAllProcessing) { + tsk_fs_close(fsInfo); + return TSK_OK; + } + + // handle creation of the last range + // make range inclusive from curBlockStart to prevBlock + const uint64_t byteStart = unallocBlockWlkTrack.curRangeStart * fsInfo->block_size + fsInfo->offset; + const uint64_t byteLen = (1 + unallocBlockWlkTrack.prevBlock - unallocBlockWlkTrack.curRangeStart) * fsInfo->block_size; + unallocBlockWlkTrack.ranges.push_back(TSK_DB_FILE_LAYOUT_RANGE(byteStart, byteLen, unallocBlockWlkTrack.nextSequenceNo++)); + int64_t fileObjId = 0; + + if (-1 == addUnallocBlockFile(m_curUnallocDirId, dbFsInfo.objId, unallocBlockWlkTrack.size, unallocBlockWlkTrack.ranges, fileObjId, m_curImgId) == TSK_ERR) { + registerError(); + tsk_fs_close(fsInfo); + return TSK_ERR; + } + + //cleanup + tsk_fs_close(fsInfo); + + return TSK_OK; +} + +/** +* Process all unallocated space for this disk image and create "virtual" files with layouts +* @returns TSK_OK on success, TSK_ERR on error +*/ +TSK_RETVAL_ENUM TskAutoDbJava::addUnallocSpaceToDb() { + if (m_stopAllProcessing) { + return TSK_OK; + } + + size_t numVsP = 0; + size_t numFs = 0; + size_t numPool = 0; + + TSK_RETVAL_ENUM retFsSpace = addUnallocFsSpaceToDb(numFs); + TSK_RETVAL_ENUM retVsSpace = addUnallocVsSpaceToDb(numVsP); + TSK_RETVAL_ENUM retPoolSpace = addUnallocatedPoolBlocksToDb(numPool); + + //handle case when no fs and no vs partitions and no pools + TSK_RETVAL_ENUM retImgFile = TSK_OK; + if (numVsP == 0 && numFs == 0 && numPool == 0) { + retImgFile = addUnallocImageSpaceToDb(); + } + + + if (retFsSpace == TSK_ERR || retVsSpace == TSK_ERR || retPoolSpace == TSK_ERR || retImgFile == TSK_ERR) + return TSK_ERR; + else + return TSK_OK; +} + + +/** +* Process each file system in the database and add its unallocated sectors to virtual files. +* @param numFs (out) number of filesystems found +* @returns TSK_OK on success, TSK_ERR on error (if some or all fs could not be processed) +*/ +TSK_RETVAL_ENUM TskAutoDbJava::addUnallocFsSpaceToDb(size_t & numFs) { + + if(m_stopAllProcessing) { + return TSK_OK; + } + + numFs = m_savedFsInfo.size(); + TSK_RETVAL_ENUM allFsProcessRet = TSK_OK; + for (vector<TSK_DB_FS_INFO>::iterator it = m_savedFsInfo.begin(); it!= m_savedFsInfo.end(); ++it) { + if (m_stopAllProcessing) { + break; + } + if (addFsInfoUnalloc(*it) == TSK_ERR) + allFsProcessRet = TSK_ERR; + } + + //TODO set parent_path for newly created virt dir/file hierarchy for consistency + + return allFsProcessRet; +} + +/** +* Process each volume in the database and add its unallocated sectors to virtual files. +* @param numVsP (out) number of vs partitions found +* @returns TSK_OK on success, TSK_ERR on error +*/ +TSK_RETVAL_ENUM TskAutoDbJava::addUnallocVsSpaceToDb(size_t & numVsP) { + + numVsP = m_savedVsPartInfo.size(); + + //get fs infos to see if this vspart has fs + for (vector<TSK_DB_VS_PART_INFO>::const_iterator it = m_savedVsPartInfo.begin(); + it != m_savedVsPartInfo.end(); ++it) { + if (m_stopAllProcessing) { + break; + } + const TSK_DB_VS_PART_INFO &vsPart = *it; + + //interested in unalloc, meta, or alloc and no fs + if ( (vsPart.flags & (TSK_VS_PART_FLAG_UNALLOC | TSK_VS_PART_FLAG_META)) == 0 ) { + //check if vspart has no fs + bool hasFs = false; + for (vector<TSK_DB_FS_INFO>::const_iterator itFs = m_savedFsInfo.begin(); + itFs != m_savedFsInfo.end(); ++itFs) { + const TSK_DB_FS_INFO & fsInfo = *itFs; + + TSK_DB_OBJECT* fsObjInfo = NULL; + if (getObjectInfo(fsInfo.objId, &fsObjInfo) == TSK_ERR ) { + stringstream errss; + errss << "addUnallocVsSpaceToDb: error getting object info for fs from db, objId: " << fsInfo.objId; + tsk_error_set_errstr2("%s", errss.str().c_str()); + registerError(); + return TSK_ERR; + } + + if (fsObjInfo->parObjId == vsPart.objId) { + hasFs = true; + break; + } + } + + if (hasFs == true) { + //skip processing this vspart + continue; + } + + // Check if the volume contains a pool + bool hasPool = false; + for (std::map<int64_t, int64_t>::iterator iter = m_poolOffsetToParentId.begin(); iter != m_poolOffsetToParentId.end(); ++iter) { + if (iter->second == vsPart.objId) { + hasPool = true; + } + } + if (hasPool) { + // Skip processing this vspart + continue; + } + + } + + // Get sector size and image offset from parent vs info + // Get parent id of this vs part + TSK_DB_OBJECT* vsPartObj = NULL; + if (getObjectInfo(vsPart.objId, &vsPartObj) == TSK_ERR) { + stringstream errss; + errss << "addUnallocVsSpaceToDb: error getting object info for vs part from db, objId: " << vsPart.objId; + tsk_error_set_errstr2("%s", errss.str().c_str()); + registerError(); + return TSK_ERR; + } + if (vsPartObj == NULL) { + return TSK_ERR; + } + + TSK_DB_VS_INFO* vsInfo = NULL; + for (vector<TSK_DB_VS_INFO>::iterator itVs = m_savedVsInfo.begin(); + itVs != m_savedVsInfo.end(); ++itVs) { + TSK_DB_VS_INFO* temp_vs_info = &(*itVs); + if (temp_vs_info->objId == vsPartObj->parObjId) { + vsInfo = temp_vs_info; + } + } + + if (vsInfo == NULL ) { + stringstream errss; + errss << "addUnallocVsSpaceToDb: error getting volume system info from db, objId: " << vsPartObj->parObjId; + tsk_error_set_errstr2("%s", errss.str().c_str()); + registerError(); + return TSK_ERR; + } + + // Create an unalloc file with unalloc part, with vs part as parent + vector<TSK_DB_FILE_LAYOUT_RANGE> ranges; + const uint64_t byteStart = vsInfo->offset + vsInfo->block_size * vsPart.start; + const uint64_t byteLen = vsInfo->block_size * vsPart.len; + TSK_DB_FILE_LAYOUT_RANGE tempRange(byteStart, byteLen, 0); + ranges.push_back(tempRange); + int64_t fileObjId = 0; + if (addUnallocBlockFile(vsPart.objId, 0, tempRange.byteLen, ranges, fileObjId, m_curImgId) == TSK_ERR) { + registerError(); + return TSK_ERR; + } + } + + return TSK_OK; +} + + +/** +* Adds unalloc space for the image if there is no volumes and no file systems. +* +* @returns TSK_OK on success, TSK_ERR on error +*/ +TSK_RETVAL_ENUM TskAutoDbJava::addUnallocImageSpaceToDb() { + + const TSK_OFF_T imgSize = getImageSize(); + if (imgSize == -1) { + tsk_error_set_errstr("addUnallocImageSpaceToDb: error getting current image size, can't create unalloc block file for the image."); + registerError(); + return TSK_ERR; + } + else { + TSK_DB_FILE_LAYOUT_RANGE tempRange(0, imgSize, 0); + //add unalloc block file for the entire image + vector<TSK_DB_FILE_LAYOUT_RANGE> ranges; + ranges.push_back(tempRange); + int64_t fileObjId = 0; + if (-1 == addUnallocBlockFile(m_curImgId, 0, imgSize, ranges, fileObjId, m_curImgId)) { + return TSK_ERR; + } + } + return TSK_OK; +} + +/** +* Returns the directory currently being analyzed by processFile(). +* Safe to use from another thread than processFile(). +* +* @returns curDirPath string representing currently analyzed directory +*/ +const std::string TskAutoDbJava::getCurDir() { + string curDirPath; + tsk_take_lock(&m_curDirPathLock); + curDirPath = m_curDirPath; + tsk_release_lock(&m_curDirPathLock); + return curDirPath; +} diff --git a/bindings/java/jni/auto_db_java.h b/bindings/java/jni/auto_db_java.h new file mode 100644 index 000000000..85a9dc69e --- /dev/null +++ b/bindings/java/jni/auto_db_java.h @@ -0,0 +1,247 @@ +/* + ** The Sleuth Kit + ** + ** Brian Carrier [carrier <at> sleuthkit [dot] org] + ** Copyright (c) 2020 Brian Carrier. All Rights reserved + ** + ** This software is distributed under the Common Public License 1.0 + ** + */ + +/** + * \file auto_db_java.h + * Contains the class that creates a case-level database of file system + * data from the JNI code. + */ + +#ifndef _AUTO_DB_JAVA_H +#define _AUTO_DB_JAVA_H + +#include <map> +using std::map; + +#include <string> +using std::string; + +#include "tsk/auto/tsk_auto_i.h" +#include "tsk/auto/tsk_db.h" +#include "jni.h" + + +/** \internal + * C++ class that implements TskAuto to load file metadata into a database. + * This is used by the TskCaseDb class. + */ +class TskAutoDbJava :public TskAuto { + public: + TskAutoDbJava(); + virtual ~TskAutoDbJava(); + virtual uint8_t openImage(int, const TSK_TCHAR * const images[], + TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL); + virtual uint8_t openImage(const char* a_deviceId = NULL); + virtual uint8_t openImageUtf8(int, const char *const images[], + TSK_IMG_TYPE_ENUM, unsigned int a_ssize, const char* deviceId = NULL); + virtual void closeImage(); + void close(); + virtual void setTz(string tzone); + + virtual TSK_FILTER_ENUM filterVs(const TSK_VS_INFO * vs_info); + virtual TSK_FILTER_ENUM filterVol(const TSK_VS_PART_INFO * vs_part); + virtual TSK_FILTER_ENUM filterPool(const TSK_POOL_INFO * pool_info); + virtual TSK_FILTER_ENUM filterPoolVol(const TSK_POOL_VOLUME_INFO * pool_vol); + virtual TSK_FILTER_ENUM filterFs(TSK_FS_INFO * fs_info); + virtual TSK_RETVAL_ENUM processFile(TSK_FS_FILE * fs_file, + const char *path); + const std::string getCurDir(); + + /** + * Sets whether or not the file systems for an image should be added when + * the image is added to the case database. The default value is true. + */ + void setAddFileSystems(bool addFileSystems); + + /** + * Skip processing of orphans on FAT filesystems. + * This will make the loading of the database much faster + * but you will not have all deleted files. Default value is false. + * @param noFatFsOrphans flag set to true if to skip processing orphans on FAT fs + */ + virtual void setNoFatFsOrphans(bool noFatFsOrphans); + + /** + * When enabled, records for unallocated file system space will be added to the database. Default value is false. + * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors. + */ + virtual void setAddUnallocSpace(bool addUnallocSpace); + + /** + * When enabled, records for unallocated file system space will be added to the database. Default value is false. + * @param addUnallocSpace If true, create records for contiguous unallocated file system sectors. + * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create + * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive + * chunk. + */ + virtual void setAddUnallocSpace(bool addUnallocSpace, int64_t minChunkSize); + + /** + * When enabled, records for unallocated file system space will be added to the database with the given parameters. + * Automatically sets the flag to create records for contiguous unallocated file system sectors. + * @param minChunkSize the number of bytes to group unallocated data into. A value of 0 will create + * one large chunk and group only on volume boundaries. A value of -1 will group each consecutive + * chunk. + * @param maxChunkSize the maximum number of bytes in one record of unallocated data. A value of -1 will not + * split the records based on size + */ + virtual void setAddUnallocSpace(int64_t minChunkSize, int64_t maxChunkSize); + + uint8_t addFilesInImgToDb(); + + /** + * + */ + uint8_t startAddImage(int numImg, const TSK_TCHAR * const imagePaths[], + TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL); + uint8_t startAddImage(TSK_IMG_INFO * img_info, const char* deviceId = NULL); +#ifdef WIN32 + uint8_t startAddImage(int numImg, const char *const imagePaths[], + TSK_IMG_TYPE_ENUM imgType, unsigned int sSize, const char* deviceId = NULL); +#endif + void stopAddImage(); + + int64_t getImageID(); + + TSK_RETVAL_ENUM initializeJni(JNIEnv *, jobject); + + private: + int64_t m_curImgId; ///< Object ID of image currently being processed + int64_t m_curVsId; ///< Object ID of volume system currently being processed + int64_t m_curVolId; ///< Object ID of volume currently being processed + int64_t m_curPoolVol; ///< Object ID of the pool volume currently being processed + int64_t m_curPoolVs; ///< Object ID of the pool volume system currently being processed + int64_t m_curFsId; ///< Object ID of file system currently being processed + int64_t m_curFileId; ///< Object ID of file currently being processed + TSK_INUM_T m_curDirAddr; ///< Meta address the directory currently being processed + int64_t m_curUnallocDirId; + string m_curDirPath; //< Path of the current directory being processed + tsk_lock_t m_curDirPathLock; //< protects concurrent access to m_curDirPath + string m_curImgTZone; + bool m_vsFound; + bool m_volFound; + bool m_poolFound; + bool m_stopped; + bool m_addFileSystems; + bool m_noFatFsOrphans; + bool m_addUnallocSpace; + int64_t m_minChunkSize; ///< -1 for no minimum, 0 for no chunking at all, greater than 0 to wait for that number of chunks before writing to the database + int64_t m_maxChunkSize; ///< Max number of unalloc bytes to process before writing to the database, even if there is no natural break. -1 for no chunking + bool m_foundStructure; ///< Set to true when we find either a volume or file system + bool m_attributeAdded; ///< Set to true when an attribute was added by processAttributes + + // These are used to write unallocated blocks for pools at the end of the add image + // process. We can't load the pool_info objects directly from the database so we will + // store info about them here. + std::map<int64_t, int64_t> m_poolOffsetToParentId; + std::map<int64_t, int64_t> m_poolOffsetToVsId; + + // Used to look up object IDs for files + #define MAX_PATH_LENGTH_JAVA_DB_LOOKUP 2048 + char parent_name[MAX_PATH_LENGTH_JAVA_DB_LOOKUP]; + char parent_path[MAX_PATH_LENGTH_JAVA_DB_LOOKUP + 2]; // +2 is for leading slash and trailing slash + map<int64_t, map<TSK_INUM_T, map<uint32_t, map<uint32_t, int64_t> > > > m_parentDirIdCache; //maps a file system ID to a map, which maps a directory file system meta address to a map, which maps a sequence ID to a map, which maps a hash of a path to its object ID in the database + int64_t findParObjId(const TSK_FS_FILE* fs_file, const char* parentPath, const int64_t& fsObjId); + bool getParentPathAndName(const char *path, const char **ret_parent_path, const char **ret_name); + void storeObjId(const int64_t& fsObjId, const TSK_FS_FILE* fs_file, const char* path, const int64_t& objId); + uint32_t hash(const unsigned char* str); + + // JNI data + JNIEnv * m_jniEnv = NULL; + jclass m_callbackClass = NULL; + jobject m_javaDbObj = NULL; + jmethodID m_addImageMethodID = NULL; + jmethodID m_addImageNameMethodID = NULL; + jmethodID m_addVolumeSystemMethodID = NULL; + jmethodID m_addVolumeMethodID = NULL; + jmethodID m_addPoolMethodID = NULL; + jmethodID m_addFileSystemMethodID = NULL; + jmethodID m_addFileMethodID = NULL; + jmethodID m_getParentIdMethodID = NULL; + jmethodID m_addUnallocParentMethodID = NULL; + jmethodID m_addLayoutFileMethodID = NULL; + jmethodID m_addLayoutFileRangeMethodID = NULL; + + // Cached objects + vector<TSK_DB_FS_INFO> m_savedFsInfo; + vector<TSK_DB_VS_INFO> m_savedVsInfo; + vector<TSK_DB_VS_PART_INFO> m_savedVsPartInfo; + vector<TSK_DB_OBJECT> m_savedObjects; + + void saveObjectInfo(uint64_t objId, uint64_t parObjId, TSK_DB_OBJECT_TYPE_ENUM type); + TSK_RETVAL_ENUM getObjectInfo(uint64_t objId, TSK_DB_OBJECT** obj_info); + + // prevent copying until we add proper logic to handle it + TskAutoDbJava(const TskAutoDbJava&); + TskAutoDbJava & operator=(const TskAutoDbJava&); + + //internal structure to keep track of temp. unalloc block range + typedef struct _UNALLOC_BLOCK_WLK_TRACK { + _UNALLOC_BLOCK_WLK_TRACK(TskAutoDbJava & tskAutoDbJava, const TSK_FS_INFO & fsInfo, const int64_t fsObjId, int64_t minChunkSize, int64_t maxChunkSize) + : tskAutoDbJava(tskAutoDbJava),fsInfo(fsInfo),fsObjId(fsObjId),curRangeStart(0), minChunkSize(minChunkSize), maxChunkSize(maxChunkSize), prevBlock(0), isStart(true), nextSequenceNo(0) {} + TskAutoDbJava & tskAutoDbJava; + const TSK_FS_INFO & fsInfo; + const int64_t fsObjId; + vector<TSK_DB_FILE_LAYOUT_RANGE> ranges; + TSK_DADDR_T curRangeStart; + int64_t size; + const int64_t minChunkSize; + const int64_t maxChunkSize; + TSK_DADDR_T prevBlock; + bool isStart; + uint32_t nextSequenceNo; + } UNALLOC_BLOCK_WLK_TRACK; + + uint8_t addImageDetails(const char *); + TSK_RETVAL_ENUM insertFileData(TSK_FS_FILE * fs_file, + const TSK_FS_ATTR *, const char *path); + virtual TSK_RETVAL_ENUM processAttribute(TSK_FS_FILE *, + const TSK_FS_ATTR * fs_attr, const char *path); + + TSK_RETVAL_ENUM addUnallocatedPoolBlocksToDb(size_t & numPool); + static TSK_WALK_RET_ENUM fsWalkUnallocBlocksCb(const TSK_FS_BLOCK *a_block, void *a_ptr); + TSK_RETVAL_ENUM addFsInfoUnalloc(const TSK_DB_FS_INFO & dbFsInfo); + TSK_RETVAL_ENUM addUnallocFsSpaceToDb(size_t & numFs); + TSK_RETVAL_ENUM addUnallocVsSpaceToDb(size_t & numVsP); + TSK_RETVAL_ENUM addUnallocImageSpaceToDb(); + TSK_RETVAL_ENUM addUnallocSpaceToDb(); + + // JNI methods + TSK_RETVAL_ENUM addImageInfo(int type, TSK_OFF_T ssize, int64_t & objId, const string & timezone, TSK_OFF_T size, const string &md5, + const string& sha1, const string& sha256, const string& deviceId, const string& collectionDetails); + TSK_RETVAL_ENUM addImageName(int64_t objId, char const* imgName, int sequence); + TSK_RETVAL_ENUM addVsInfo(const TSK_VS_INFO* vs_info, int64_t parObjId, int64_t& objId); + TSK_RETVAL_ENUM addPoolInfoAndVS(const TSK_POOL_INFO *pool_info, int64_t parObjId, int64_t& objId); + TSK_RETVAL_ENUM addPoolVolumeInfo(const TSK_POOL_VOLUME_INFO* pool_vol, int64_t parObjId, int64_t& objId); + TSK_RETVAL_ENUM addVolumeInfo(const TSK_VS_PART_INFO* vs_part, int64_t parObjId, int64_t& objId); + TSK_RETVAL_ENUM addFsInfo(const TSK_FS_INFO* fs_info, int64_t parObjId, int64_t& objId); + TSK_RETVAL_ENUM addFsFile(TSK_FS_FILE* fs_file, + const TSK_FS_ATTR* fs_attr, const char* path, + int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId); + TSK_RETVAL_ENUM addFile(TSK_FS_FILE* fs_file, + const TSK_FS_ATTR* fs_attr, const char* path, + int64_t fsObjId, int64_t parObjId, + int64_t& objId, int64_t dataSourceObjId); + TSK_RETVAL_ENUM addFileWithLayoutRange(const TSK_DB_FILES_TYPE_ENUM dbFileType, const int64_t parentObjId, + const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId); + TSK_RETVAL_ENUM addUnallocBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId); + TSK_RETVAL_ENUM addUnusedBlockFile(const int64_t parentObjId, const int64_t fsObjId, const uint64_t size, + vector<TSK_DB_FILE_LAYOUT_RANGE>& ranges, int64_t& objId, + int64_t dataSourceObjId); + TSK_RETVAL_ENUM addUnallocFsBlockFilesParent(const int64_t fsObjId, int64_t& objId, int64_t dataSourceObjId); + TSK_RETVAL_ENUM addUnallocatedPoolVolume(int vol_index, int64_t parObjId, int64_t& objId); + +}; + +#endif diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java new file mode 100644 index 000000000..0de142d73 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java @@ -0,0 +1,616 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import org.sleuthkit.datamodel.SQLHelper.PostgreSQLHelper; +import org.sleuthkit.datamodel.SQLHelper.SQLiteHelper; + +/** + * Creates a SQLite or PostgreSQL case database. + */ +class CaseDatabaseFactory { + + private final SQLHelper dbQueryHelper; + private final DbCreationHelper dbCreationHelper; + + /** + * Create a new SQLite case + * + * @param dbPath Full path to the database + */ + CaseDatabaseFactory(String dbPath) { + this.dbQueryHelper = new SQLiteHelper(); + this.dbCreationHelper = new SQLiteDbCreationHelper(dbPath); + } + + /** + * Create a new PostgreSQL case + * + * @param caseName The name of the case. It will be used to create a case + * database name that can be safely used in SQL commands + * and will not be subject to name collisions on the case + * database server. Use getDatabaseName to get the + * created name. + * @param info The information to connect to the database. + */ + CaseDatabaseFactory(String caseName, CaseDbConnectionInfo info) { + this.dbQueryHelper = new PostgreSQLHelper(); + this.dbCreationHelper = new PostgreSQLDbCreationHelper(caseName, info); + } + + /** + * Creates and initializes the case database. + * Currently the case must be reopened after creation. + * + * @throws TskCoreException + */ + void createCaseDatabase() throws TskCoreException { + createDatabase(); + initializeSchema(); + } + + /** + * Create the database itself (if necessary) + * + * @throws TskCoreException + */ + private void createDatabase() throws TskCoreException { + dbCreationHelper.createDatabase(); + } + + /** + * Initialize the database schema + * + * @throws TskCoreException + */ + private void initializeSchema() throws TskCoreException { + try (Connection conn = dbCreationHelper.getConnection()) { + // Perform any needed steps before creating the tables + dbCreationHelper.performPreInitialization(conn); + + // Add schema version + addDbInfo(conn); + + // Add tables + addTables(conn); + dbCreationHelper.performPostTableInitialization(conn); + + // Add indexes + addIndexes(conn); + } catch (SQLException ex) { + throw new TskCoreException("Error initializing case database", ex); + } + } + + /** + * Create and populate the db_info tables + * + * @param conn the database connection + * + * @throws TskCoreException + */ + private void addDbInfo(Connection conn) throws TskCoreException { + CaseDbSchemaVersionNumber version = SleuthkitCase.CURRENT_DB_SCHEMA_VERSION; + long tskVersionNum = SleuthkitJNI.getSleuthkitVersion(); // This is the current version of TSK + + try (Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE tsk_db_info (schema_ver INTEGER, tsk_ver INTEGER, schema_minor_ver INTEGER)"); + stmt.execute("INSERT INTO tsk_db_info (schema_ver, tsk_ver, schema_minor_ver) VALUES (" + + version.getMajor() + ", " + tskVersionNum + ", " + version.getMinor() + ");"); + + stmt.execute("CREATE TABLE tsk_db_info_extended (name TEXT PRIMARY KEY, value TEXT NOT NULL);"); + stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('TSK_VERSION', '" + tskVersionNum + "');"); + stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('SCHEMA_MAJOR_VERSION', '" + version.getMajor() + "');"); + stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('SCHEMA_MINOR_VERSION', '" + version.getMinor() + "');"); + stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('CREATION_SCHEMA_MAJOR_VERSION', '" + version.getMajor() + "');"); + stmt.execute("INSERT INTO tsk_db_info_extended (name, value) VALUES ('CREATION_SCHEMA_MINOR_VERSION', '" + version.getMinor() + "');"); + } catch (SQLException ex) { + throw new TskCoreException("Error initializing db_info tables", ex); + } + } + + /** + * Add and initialize the database tables + * + * @param conn the database connection + * + * @throws TskCoreException + */ + private void addTables(Connection conn) throws TskCoreException { + try (Statement stmt = conn.createStatement()) { + createFileTables(stmt); + createArtifactTables(stmt); + createTagTables(stmt); + createIngestTables(stmt); + createAccountTables(stmt); + createEventTables(stmt); + } catch (SQLException ex) { + throw new TskCoreException("Error initializing tables", ex); + } + } + + private void createFileTables(Statement stmt) throws SQLException { + // The UNIQUE here on the object ID is to create an index + stmt.execute("CREATE TABLE tsk_objects (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, par_obj_id " + dbQueryHelper.getBigIntType() + + ", type INTEGER NOT NULL, UNIQUE (obj_id), FOREIGN KEY (par_obj_id) REFERENCES tsk_objects (obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_image_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, type INTEGER, ssize INTEGER, " + + "tzone TEXT, size " + dbQueryHelper.getBigIntType() + ", md5 TEXT, sha1 TEXT, sha256 TEXT, display_name TEXT, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_image_names (obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, name TEXT NOT NULL, " + + "sequence INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_vs_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, vs_type INTEGER NOT NULL, " + + "img_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, block_size " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_vs_parts (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "addr " + dbQueryHelper.getBigIntType() + " NOT NULL, start " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "length " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + dbQueryHelper.getVSDescColName() + " TEXT, " + + "flags INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);"); + + stmt.execute("CREATE TABLE tsk_pool_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "pool_type INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);"); + + stmt.execute("CREATE TABLE data_source_info (obj_id " + dbQueryHelper.getBigIntType() + " PRIMARY KEY, device_id TEXT NOT NULL, " + + "time_zone TEXT NOT NULL, acquisition_details TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_fs_info (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "img_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, fs_type INTEGER NOT NULL, " + + "block_size " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "block_count " + dbQueryHelper.getBigIntType() + " NOT NULL, root_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "first_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, last_inum " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "display_name TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_files (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "fs_obj_id " + dbQueryHelper.getBigIntType() + ", data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "attr_type INTEGER, attr_id INTEGER, " + + "name TEXT NOT NULL, meta_addr " + dbQueryHelper.getBigIntType() + ", meta_seq " + dbQueryHelper.getBigIntType() + ", " + + "type INTEGER, has_layout INTEGER, has_path INTEGER, " + + "dir_type INTEGER, meta_type INTEGER, dir_flags INTEGER, meta_flags INTEGER, size " + dbQueryHelper.getBigIntType() + ", " + + "ctime " + dbQueryHelper.getBigIntType() + ", " + + "crtime " + dbQueryHelper.getBigIntType() + ", atime " + dbQueryHelper.getBigIntType() + ", " + + "mtime " + dbQueryHelper.getBigIntType() + ", mode INTEGER, uid INTEGER, gid INTEGER, md5 TEXT, known INTEGER, " + + "parent_path TEXT, mime_type TEXT, extension TEXT, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(fs_obj_id) REFERENCES tsk_fs_info(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE file_encoding_types (encoding_type INTEGER PRIMARY KEY, name TEXT NOT NULL)"); + + stmt.execute("CREATE TABLE tsk_files_path (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, path TEXT NOT NULL, " + + "encoding_type INTEGER NOT NULL, FOREIGN KEY(encoding_type) references file_encoding_types(encoding_type), " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_files_derived (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "derived_id " + dbQueryHelper.getBigIntType() + " NOT NULL, rederive TEXT, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE tsk_files_derived_method (derived_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "tool_name TEXT NOT NULL, tool_version TEXT NOT NULL, other TEXT)"); + + stmt.execute("CREATE TABLE tsk_file_layout (obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "byte_start " + dbQueryHelper.getBigIntType() + " NOT NULL, byte_len " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "sequence INTEGER NOT NULL, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);"); + + stmt.execute("CREATE TABLE reports (obj_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, path TEXT NOT NULL, " + + "crtime INTEGER NOT NULL, src_module_name TEXT NOT NULL, report_name TEXT NOT NULL, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE);"); + } + + private void createArtifactTables(Statement stmt) throws SQLException { + stmt.execute("CREATE TABLE blackboard_artifact_types (artifact_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "type_name TEXT NOT NULL, display_name TEXT)"); + + stmt.execute("CREATE TABLE blackboard_attribute_types (attribute_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "type_name TEXT NOT NULL, display_name TEXT, value_type INTEGER NOT NULL)"); + + stmt.execute("CREATE TABLE review_statuses (review_status_id INTEGER PRIMARY KEY, " + + "review_status_name TEXT NOT NULL, " + + "display_name TEXT NOT NULL)"); + + stmt.execute("CREATE TABLE blackboard_artifacts (artifact_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "artifact_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "artifact_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "review_status_id INTEGER NOT NULL, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(artifact_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(artifact_type_id) REFERENCES blackboard_artifact_types(artifact_type_id), " + + "FOREIGN KEY(review_status_id) REFERENCES review_statuses(review_status_id))"); + + /* Binary representation of BYTEA is a bunch of bytes, which could + * include embedded nulls so we have to pay attention to field length. + * http://www.postgresql.org/docs/9.4/static/libpq-example.html + */ + stmt.execute("CREATE TABLE blackboard_attributes (artifact_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "artifact_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "source TEXT, context TEXT, attribute_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "value_type INTEGER NOT NULL, value_byte " + dbQueryHelper.getBlobType() + ", " + + "value_text TEXT, value_int32 INTEGER, value_int64 " + dbQueryHelper.getBigIntType() + ", value_double NUMERIC(20, 10), " + + "FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE, " + + "FOREIGN KEY(artifact_type_id) REFERENCES blackboard_artifact_types(artifact_type_id), " + + "FOREIGN KEY(attribute_type_id) REFERENCES blackboard_attribute_types(attribute_type_id))"); + } + + private void createTagTables(Statement stmt) throws SQLException { + stmt.execute("CREATE TABLE tsk_tag_sets (tag_set_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, name TEXT UNIQUE)"); + stmt.execute("CREATE TABLE tag_names (tag_name_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, display_name TEXT UNIQUE, " + + "description TEXT NOT NULL, color TEXT NOT NULL, knownStatus INTEGER NOT NULL," + + " tag_set_id INTEGER, FOREIGN KEY(tag_set_id) REFERENCES tsk_tag_sets(tag_set_id) ON DELETE SET NULL)"); + + stmt.execute("CREATE TABLE tsk_examiners (examiner_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "login_name TEXT NOT NULL, display_name TEXT, UNIQUE(login_name))"); + + stmt.execute("CREATE TABLE content_tags (tag_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, tag_name_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "comment TEXT NOT NULL, begin_byte_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "end_byte_offset " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "examiner_id " + dbQueryHelper.getBigIntType() + ", " + + "FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id) ON DELETE CASCADE, " + + "FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(tag_name_id) REFERENCES tag_names(tag_name_id) ON DELETE CASCADE)"); + + stmt.execute("CREATE TABLE blackboard_artifact_tags (tag_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "artifact_id " + dbQueryHelper.getBigIntType() + " NOT NULL, tag_name_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "comment TEXT NOT NULL, examiner_id " + dbQueryHelper.getBigIntType() + ", " + + "FOREIGN KEY(examiner_id) REFERENCES tsk_examiners(examiner_id) ON DELETE CASCADE, " + + "FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE, " + + "FOREIGN KEY(tag_name_id) REFERENCES tag_names(tag_name_id) ON DELETE CASCADE)"); + } + + /** + * Add indexes + * + * @param conn the database connection + * @throws TskCoreException + */ + private void addIndexes(Connection conn) throws TskCoreException { + try (Statement stmt = conn.createStatement()) { + // tsk_objects index + stmt.execute("CREATE INDEX parObjId ON tsk_objects(par_obj_id)"); + + // file layout index + stmt.execute("CREATE INDEX layout_objID ON tsk_file_layout(obj_id)"); + + // blackboard indexes + stmt.execute("CREATE INDEX artifact_objID ON blackboard_artifacts(obj_id)"); + stmt.execute("CREATE INDEX artifact_artifact_objID ON blackboard_artifacts(artifact_obj_id)"); + stmt.execute("CREATE INDEX artifact_typeID ON blackboard_artifacts(artifact_type_id)"); + stmt.execute("CREATE INDEX attrsArtifactID ON blackboard_attributes(artifact_id)"); + + //file type indexes + stmt.execute("CREATE INDEX mime_type ON tsk_files(dir_type,mime_type,type)"); + stmt.execute("CREATE INDEX file_extension ON tsk_files(extension)"); + + // account indexes + stmt.execute("CREATE INDEX relationships_account1 ON account_relationships(account1_id)"); + stmt.execute("CREATE INDEX relationships_account2 ON account_relationships(account2_id)"); + stmt.execute("CREATE INDEX relationships_relationship_source_obj_id ON account_relationships(relationship_source_obj_id)"); + stmt.execute("CREATE INDEX relationships_date_time ON account_relationships(date_time)"); + stmt.execute("CREATE INDEX relationships_relationship_type ON account_relationships(relationship_type)"); + stmt.execute("CREATE INDEX relationships_data_source_obj_id ON account_relationships(data_source_obj_id)"); + + //tsk_events indices + stmt.execute("CREATE INDEX events_data_source_obj_id ON tsk_event_descriptions(data_source_obj_id)"); + stmt.execute("CREATE INDEX events_content_obj_id ON tsk_event_descriptions(content_obj_id)"); + stmt.execute("CREATE INDEX events_artifact_id ON tsk_event_descriptions(artifact_id)"); + stmt.execute("CREATE INDEX events_sub_type_time ON tsk_events(event_type_id, time)"); + stmt.execute("CREATE INDEX events_time ON tsk_events(time)"); + } catch (SQLException ex) { + throw new TskCoreException("Error initializing db_info tables", ex); + } + } + + private void createIngestTables(Statement stmt) throws SQLException { + stmt.execute("CREATE TABLE ingest_module_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); + + stmt.execute("CREATE TABLE ingest_job_status_types (type_id INTEGER PRIMARY KEY, type_name TEXT NOT NULL)"); + + stmt.execute("CREATE TABLE ingest_modules (ingest_module_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "display_name TEXT NOT NULL, unique_name TEXT UNIQUE NOT NULL, type_id INTEGER NOT NULL, " + + "version TEXT NOT NULL, FOREIGN KEY(type_id) REFERENCES ingest_module_types(type_id) ON DELETE CASCADE);"); + + stmt.execute("CREATE TABLE ingest_jobs (ingest_job_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, host_name TEXT NOT NULL, " + + "start_date_time " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "end_date_time " + dbQueryHelper.getBigIntType() + " NOT NULL, status_id INTEGER NOT NULL, " + + "settings_dir TEXT, FOREIGN KEY(obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(status_id) REFERENCES ingest_job_status_types(type_id) ON DELETE CASCADE);"); + + stmt.execute("CREATE TABLE ingest_job_modules (ingest_job_id INTEGER, ingest_module_id INTEGER, " + + "pipeline_position INTEGER, PRIMARY KEY(ingest_job_id, ingest_module_id), " + + "FOREIGN KEY(ingest_job_id) REFERENCES ingest_jobs(ingest_job_id) ON DELETE CASCADE, " + + "FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id) ON DELETE CASCADE);"); + } + + private void createAccountTables(Statement stmt) throws SQLException { + stmt.execute("CREATE TABLE account_types (account_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)"); + + stmt.execute("CREATE TABLE accounts (account_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "account_type_id INTEGER NOT NULL, account_unique_identifier TEXT NOT NULL, " + + "UNIQUE(account_type_id, account_unique_identifier), " + + "FOREIGN KEY(account_type_id) REFERENCES account_types(account_type_id))"); + + stmt.execute("CREATE TABLE account_relationships (relationship_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "account1_id INTEGER NOT NULL, account2_id INTEGER NOT NULL, " + + "relationship_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "date_time " + dbQueryHelper.getBigIntType() + ", relationship_type INTEGER NOT NULL, " + + "data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + "UNIQUE(account1_id, account2_id, relationship_source_obj_id), " + + "FOREIGN KEY(account1_id) REFERENCES accounts(account_id), " + + "FOREIGN KEY(account2_id) REFERENCES accounts(account_id), " + + "FOREIGN KEY(relationship_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + "FOREIGN KEY(data_source_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE)"); + } + + private void createEventTables(Statement stmt) throws SQLException { + stmt.execute("CREATE TABLE tsk_event_types (" + + " event_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY," + + " display_name TEXT UNIQUE NOT NULL , " + + " super_type_id INTEGER REFERENCES tsk_event_types(event_type_id) )"); + + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(0, 'Event Types', null)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(1, 'File System', 0)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(2, 'Web Activity', 0)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(3, 'Misc Types', 0)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(4, 'Modified', 1)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(5, 'Accessed', 1)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(6, 'Created', 1)"); + stmt.execute("INSERT INTO tsk_event_types(event_type_id, display_name, super_type_id) VALUES(7, 'Changed', 1)"); + /* + * Regarding the timeline event tables schema, note that several columns + * in the tsk_event_descriptions table seem, at first glance, to be + * attributes of events rather than their descriptions and would appear + * to belong in tsk_events table instead. The rationale for putting the + * data source object ID, content object ID, artifact ID and the flags + * indicating whether or not the event source has a hash set hit or is + * tagged were motivated by the fact that these attributes are identical + * for each event in a set of file system file MAC time events. The + * decision was made to avoid duplication and save space by placing this + * data in the tsk_event-descriptions table. + */ + stmt.execute( + "CREATE TABLE tsk_event_descriptions ( " + + " event_description_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + " full_description TEXT NOT NULL, " + + " med_description TEXT, " + + " short_description TEXT," + + " data_source_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + " content_obj_id " + dbQueryHelper.getBigIntType() + " NOT NULL, " + + " artifact_id " + dbQueryHelper.getBigIntType() + ", " + + " hash_hit INTEGER NOT NULL, " //boolean + + " tagged INTEGER NOT NULL, " //boolean + + " FOREIGN KEY(data_source_obj_id) REFERENCES data_source_info(obj_id) ON DELETE CASCADE, " + + " FOREIGN KEY(content_obj_id) REFERENCES tsk_objects(obj_id) ON DELETE CASCADE, " + + " FOREIGN KEY(artifact_id) REFERENCES blackboard_artifacts(artifact_id) ON DELETE CASCADE," + + " UNIQUE (full_description, content_obj_id, artifact_id))"); + + stmt.execute( + "CREATE TABLE tsk_events (" + + " event_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + " event_type_id " + dbQueryHelper.getBigIntType() + " NOT NULL REFERENCES tsk_event_types(event_type_id) ," + + " event_description_id " + dbQueryHelper.getBigIntType() + " NOT NULL REFERENCES tsk_event_descriptions(event_description_id) ON DELETE CASCADE ," + + " time " + dbQueryHelper.getBigIntType() + " NOT NULL , " + + " UNIQUE (event_type_id, event_description_id, time))"); + } + + /** + * Helper class for holding code unique to each database type. + */ + private abstract class DbCreationHelper { + + /** + * Create the database itself (if necessary) + * + * @throws TskCoreException + */ + abstract void createDatabase() throws TskCoreException; + + /** + * Get an connection to the case database + * + * @return the connection + */ + abstract Connection getConnection() throws TskCoreException; + + /** + * Do any needed initialization before creating the tables. + * This is where SQLite pragmas are set up. + * + * @param conn The database connection + * + * @throws TskCoreException + */ + abstract void performPreInitialization(Connection conn) throws TskCoreException; + + /** + * Do any additional steps after the tables are created. + * + * @param conn The database connection + * @throws TskCoreException + */ + abstract void performPostTableInitialization(Connection conn) throws TskCoreException; + } + + /** + * Implements the PostgreSQL-specific methods for creating the case + */ + private class PostgreSQLDbCreationHelper extends DbCreationHelper { + + private final static String JDBC_BASE_URI = "jdbc:postgresql://"; // NON-NLS + private final static String JDBC_DRIVER = "org.postgresql.Driver"; // NON-NLS + + final private String caseName; + final private CaseDbConnectionInfo info; + + PostgreSQLDbCreationHelper(String caseName, CaseDbConnectionInfo info) { + this.caseName = caseName; + this.info = info; + } + + @Override + void createDatabase() throws TskCoreException{ + try(Connection conn = getPostgresConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE DATABASE \"" + caseName + "\" WITH ENCODING='UTF8'"); + } catch (SQLException ex) { + throw new TskCoreException("Error creating PostgreSQL case " + caseName, ex); + } + } + + @Override + Connection getConnection() throws TskCoreException { + return getConnection(caseName); + } + + /** + * Connects to the "postgres" database for creating new databases. + * + * @return the connection to the "postgres" database + */ + Connection getPostgresConnection() throws TskCoreException { + return getConnection("postgres"); + } + + /** + * Connects to an existing database with the given name. + * + * @param databaseName the name of the database + * + * @return the connection to the database + */ + Connection getConnection(String databaseName) throws TskCoreException { + + StringBuilder url = new StringBuilder(); + url.append(JDBC_BASE_URI) + .append(info.getHost()) + .append('/') // NON-NLS + .append(databaseName); + + Connection conn; + try { + Properties props = new Properties(); + props.setProperty("user", info.getUserName()); // NON-NLS + props.setProperty("password", info.getPassword()); // NON-NLS + + Class.forName(JDBC_DRIVER); + conn = DriverManager.getConnection(url.toString(), props); + } catch (ClassNotFoundException | SQLException ex) { + throw new TskCoreException("Failed to acquire ephemeral connection to PostgreSQL database " + databaseName, ex); // NON-NLS + } + return conn; + } + + @Override + void performPreInitialization(Connection conn) throws TskCoreException { + // Nothing to do here for PostgreSQL + } + + @Override + void performPostTableInitialization(Connection conn) throws TskCoreException { + try (Statement stmt = conn.createStatement()) { + stmt.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq minvalue -9223372036854775808 restart with -9223372036854775808"); + } catch (SQLException ex) { + throw new TskCoreException("Error altering artifact ID sequence", ex); + } + } + } + + /** + * Implements the SQLite-specific methods for creating the case + */ + private class SQLiteDbCreationHelper extends DbCreationHelper { + + private final static String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF"; // NON-NLS + private final static String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True"; // NON-NLS + private final static String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'"; // NON-NLS + private final static String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096"; // NON-NLS + private final static String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON"; // NON-NLS + + private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS + private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS + + String dbPath; + + SQLiteDbCreationHelper(String dbPath) { + this.dbPath = dbPath; + } + + @Override + void createDatabase() throws TskCoreException { + // SQLite doesn't need to explicitly create the case database but we will + // check that the folder exists and the database does not + File dbFile = new File(dbPath); + if (dbFile.exists()) { + throw new TskCoreException("Case database already exists : " + dbPath); + } + + if (dbFile.getParentFile() != null && !dbFile.getParentFile().exists()) { + throw new TskCoreException("Case database folder does not exist : " + dbFile.getParent()); + } + } + + @Override + Connection getConnection() throws TskCoreException { + + StringBuilder url = new StringBuilder(); + url.append(JDBC_BASE_URI) + .append(dbPath); + + Connection conn; + try { + Class.forName(JDBC_DRIVER); + conn = DriverManager.getConnection(url.toString()); + } catch (ClassNotFoundException | SQLException ex) { + throw new TskCoreException("Failed to acquire ephemeral connection SQLite database " + dbPath, ex); // NON-NLS + } + return conn; + } + + @Override + void performPreInitialization(Connection conn) throws TskCoreException { + try (Statement stmt = conn.createStatement()) { + stmt.execute(PRAGMA_SYNC_OFF); + stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE); + stmt.execute(PRAGMA_ENCODING_UTF8); + stmt.execute(PRAGMA_PAGE_SIZE_4096); + stmt.execute(PRAGMA_FOREIGN_KEYS_ON); + } catch (SQLException ex) { + throw new TskCoreException("Error setting pragmas", ex); + } + } + + @Override + void performPostTableInitialization(Connection conn) throws TskCoreException { + // Nothing to do here for SQLite + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java new file mode 100644 index 000000000..2e9bc7210 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/JniDbHelper.java @@ -0,0 +1,400 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; + +/** + * This is a utility class to allow the native C code to write to the + * case database. All callbacks from the native code should come through this class. + * Any changes to the method signatures in this class will require changes to the + * native code. + */ +class JniDbHelper { + + private static final Logger logger = Logger.getLogger(JniDbHelper.class.getName()); + + private final SleuthkitCase caseDb; + private CaseDbTransaction trans = null; + + private final Map<Long, Long> fsIdToRootDir = new HashMap<>(); + + JniDbHelper(SleuthkitCase caseDb) { + this.caseDb = caseDb; + trans = null; + } + + /** + * Start the add image transaction + * + * @throws TskCoreException + */ + void beginTransaction() throws TskCoreException { + trans = caseDb.beginTransaction(); + } + + /** + * Commit the add image transaction + * + * @throws TskCoreException + */ + void commitTransaction() throws TskCoreException { + trans.commit(); + trans = null; + } + + /** + * Revert the add image transaction + * + * @throws TskCoreException + */ + void revertTransaction() throws TskCoreException { + trans.rollback(); + trans = null; + } + + /** + * Add a new image to the database. + * Intended to be called from the native code during the add image process. + * + * @param type Type of image. + * @param ssize Sector size. + * @param timezone Time zone. + * @param size Image size. + * @param md5 MD5 hash. + * @param sha1 SHA1 hash. + * @param sha256 SHA256 hash. + * @param deviceId Device ID. + * @param collectionDetails The collection details. + * + * @return The object ID of the new image or -1 if an error occurred + */ + long addImageInfo(int type, long ssize, String timezone, + long size, String md5, String sha1, String sha256, String deviceId, + String collectionDetails) { + try { + return caseDb.addImageJNI(TskData.TSK_IMG_TYPE_ENUM.valueOf(type), ssize, size, + timezone, md5, sha1, sha256, deviceId, collectionDetails, trans); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding image to the database", ex); + return -1; + } + } + + /** + * Add an image name to the database. + * Intended to be called from the native code during the add image process. + * + * @param objId The object id of the image. + * @param name The file name for the image + * @param sequence The sequence number of this file. + * + * @return 0 if successful, -1 if not + */ + int addImageName(long objId, String name, long sequence) { + try { + caseDb.addImageNameJNI(objId, name, sequence, trans); + return 0; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding image name to the database - image obj ID: " + objId + ", image name: " + name + + ", sequence: " + sequence, ex); + return -1; + } + } + + /** + * Add a volume system to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId + * @param vsType + * @param imgOffset + * @param blockSize + * + * @return The object ID of the new volume system or -1 if an error occurred + */ + long addVsInfo(long parentObjId, int vsType, long imgOffset, long blockSize) { + try { + VolumeSystem vs = caseDb.addVolumeSystem(parentObjId, TskData.TSK_VS_TYPE_ENUM.valueOf(vsType), imgOffset, blockSize, trans); + return vs.getId(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding volume system to the database - parent obj ID: " + parentObjId + + ", image offset: " + imgOffset, ex); + return -1; + } + } + + /** + * Add a volume to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId + * @param addr + * @param start + * @param length + * @param desc + * @param flags + * + * @return The object ID of the new volume or -1 if an error occurred + */ + long addVolume(long parentObjId, long addr, long start, long length, String desc, + long flags) { + try { + Volume vol = caseDb.addVolume(parentObjId, addr, start, length, desc, flags, trans); + return vol.getId(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding volume to the database - parent object ID: " + parentObjId + + ", addr: " + addr, ex); + return -1; + } + } + + /** + * Add a pool to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId + * @param poolType + * + * @return The object ID of the new pool or -1 if an error occurred + */ + long addPool(long parentObjId, int poolType) { + try { + Pool pool = caseDb.addPool(parentObjId, TskData.TSK_POOL_TYPE_ENUM.valueOf(poolType), trans); + return pool.getId(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding pool to the database - parent object ID: " + parentObjId, ex); + return -1; + } + } + + /** + * Add a file system to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId + * @param imgOffset + * @param fsType + * @param blockSize + * @param blockCount + * @param rootInum + * @param firstInum + * @param lastInum + * + * @return The object ID of the new file system or -1 if an error occurred + */ + long addFileSystem(long parentObjId, long imgOffset, int fsType, long blockSize, long blockCount, + long rootInum, long firstInum, long lastInum) { + try { + FileSystem fs = caseDb.addFileSystem(parentObjId, imgOffset, TskData.TSK_FS_TYPE_ENUM.valueOf(fsType), blockSize, blockCount, + rootInum, firstInum, lastInum, null, trans); + return fs.getId(); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding file system to the database - parent object ID: " + parentObjId + + ", offset: " + imgOffset, ex); + return -1; + } + } + + /** + * Add a file to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId The parent of the file. + * @param fsObjId The object ID of the file system. + * @param dataSourceObjId The data source object ID. + * @param fsType The type. + * @param attrType The type attribute given to the file by the file system. + * @param attrId The type id given to the file by the file system. + * @param name The name of the file. + * @param metaAddr The meta address of the file. + * @param metaSeq The meta sequence number of the file. + * @param dirType The type of the file, usually as reported in + * the name structure of the file system. + * @param metaType The type of the file, usually as reported in + * the metadata structure of the file system. + * @param dirFlags The allocated status of the file, usually as + * reported in the name structure of the file system. + * @param metaFlags The allocated status of the file, usually as + * reported in the metadata structure of the file system. + * @param size The file size. + * @param crtime The created time. + * @param ctime The last changed time + * @param atime The last accessed time. + * @param mtime The last modified time. + * @param meta_mode The modes for the file. + * @param gid The group identifier. + * @param uid The user identifier. + * @param md5 The MD5 hash. + * @param known The file known status. + * @param escaped_path The escaped path to the file. + * @param extension The file extension. + * + * @return The object ID of the new file or -1 if an error occurred + */ + long addFile(long parentObjId, + long fsObjId, long dataSourceObjId, + int fsType, + int attrType, int attrId, String name, + long metaAddr, long metaSeq, + int dirType, int metaType, int dirFlags, int metaFlags, + long size, + long crtime, long ctime, long atime, long mtime, + int meta_mode, int gid, int uid, + String escaped_path, String extension) { + try { + long objId = caseDb.addFileJNI(parentObjId, + fsObjId, dataSourceObjId, + fsType, + attrType, attrId, name, + metaAddr, metaSeq, + dirType, metaType, dirFlags, metaFlags, + size, + crtime, ctime, atime, mtime, + meta_mode, gid, uid, + null, TskData.FileKnown.UNKNOWN, + escaped_path, extension, + false, trans); + + // If we're adding the root directory for the file system, cache it + if (parentObjId == fsObjId) { + fsIdToRootDir.put(fsObjId, objId); + } + return objId; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding file to the database - parent object ID: " + parentObjId + + ", file system object ID: " + fsObjId + ", name: " + name, ex); + return -1; + } + } + + /** + * Add a layout file to the database. + * Intended to be called from the native code during the add image process. + * + * @param parentObjId The parent object ID of the layout file. + * @param fsObjId The file system object ID. + * @param dataSourceObjId The data source object ID. + * @param fileType The file type. + * @param name The file name. + * @param size The file size. + * + * @return The object ID of the new file or -1 if an error occurred + */ + long addLayoutFile(long parentObjId, + long fsObjId, long dataSourceObjId, + int fileType, + String name, long size) { + try { + // The file system may be null for layout files + Long fsObjIdForDb = fsObjId; + if (fsObjId == 0) { + fsObjIdForDb = null; + } + + return caseDb.addFileJNI(parentObjId, + fsObjIdForDb, dataSourceObjId, + fileType, + null, null, name, + null, null, + TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue(), + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue(), + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(), + TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue(), + size, + null, null, null, null, + null, null, null, + null, TskData.FileKnown.UNKNOWN, + null, null, + true, trans); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding layout file to the database - parent object ID: " + parentObjId + + ", file system object ID: " + fsObjId + ", name: " + name, ex); + return -1; + } + } + + /** + * Add a layout file range to the database. + * Intended to be called from the native code during the add image process. + * + * @param objId Object ID of the layout file. + * @param byteStart Start byte. + * @param byteLen Length in bytes. + * @param seq Sequence number of this range. + * + * @return 0 if successful, -1 if not + */ + long addLayoutFileRange(long objId, long byteStart, long byteLen, long seq) { + try { + caseDb.addLayoutFileRangeJNI(objId, byteStart, byteLen, seq, trans); + return 0; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error adding layout file range to the database - layout file ID: " + objId + + ", byte start: " + byteStart, ex); + return -1; + } + } + + /** + * Look up the parent of a file based on metadata address and name/path. + * Intended to be called from the native code during the add image process. + * + * @param metaAddr + * @param fsObjId + * @param path + * @param name + * + * @return The object ID of the parent or -1 if not found + */ + long findParentObjId(long metaAddr, long fsObjId, String path, String name) { + try { + return caseDb.findParentObjIdJNI(metaAddr, fsObjId, path, name, trans); + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Error looking up parent with meta addr: " + metaAddr + " and name " + name, ex); + return -1; + } + } + + /** + * Add a virtual directory to hold unallocated file system blocks. + * Intended to be called from the native code during the add image process. + * + * @param fsObjId + * @param name + * + * @return The object ID of the new virtual directory or -1 if an error occurred + */ + long addUnallocFsBlockFilesParent(long fsObjId, String name) { + try { + if (! fsIdToRootDir.containsKey(fsObjId)) { + logger.log(Level.SEVERE, "Error - root directory for file system ID {0} not found", fsObjId); + return -1; + } + return caseDb.addVirtualDirectoryJNI(fsIdToRootDir.get(fsObjId), name, trans); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error creating virtual directory " + name + " under file system ID " + fsObjId, ex); + return -1; + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java b/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java new file mode 100644 index 000000000..1ae0f8f4c --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/SQLHelper.java @@ -0,0 +1,91 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +/** + * Interface for classes to help create queries for SQLite or PostgreSQL + */ +interface SQLHelper { + + // Get the type for the primary key + String getPrimaryKey(); + + // Get the type for big int-type data + String getBigIntType(); + + // Get the type for blob-type data + String getBlobType(); + + // Get the description column name for the tsk_vs_parts table. + // This varies between SQLite and PostgreSQL. + String getVSDescColName(); + + + /** + * PostgreSQL-specific implementation + */ + class PostgreSQLHelper implements SQLHelper { + + @Override + public String getPrimaryKey() { + return "BIGSERIAL"; + } + + @Override + public String getBigIntType() { + return "BIGINT"; + } + + @Override + public String getBlobType() { + return "BYTEA"; + } + + @Override + public String getVSDescColName() { + return "descr"; + } + } + + /** + * SQLite-specific implementation + */ + class SQLiteHelper implements SQLHelper { + + @Override + public String getPrimaryKey() { + return "INTEGER"; + } + + @Override + public String getBigIntType() { + return "INTEGER"; + } + + @Override + public String getBlobType() { + return "BLOB"; + } + + @Override + public String getVSDescColName() { + return "desc"; + } + } +} \ No newline at end of file -- GitLab