win32/clamav-for-windows/clamav-for-windows/interface.c
6b7abaa7
 /*
  * Copyright (C) 2010 Sourcefire, Inc.
  * Authors: aCaB <acab@clamav.net>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  * License version 2.1 as published by the Free Software Foundation.
  *
  * This library is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this library; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
  * USA
  */
 
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
 #include "clamav.h"
23281981
 #include "others.h"
6b7abaa7
 #include "shared/output.h"
d9641dd3
 #include "mpool.h"
6b7abaa7
 #include "clscanapi.h"
 #include "interface.h"
 
4d08b6d6
 const char *types[] = {
996fb478
     "<<rsvd>>",		/*  0 */
99f01b4b
     "MSEXE",		/*  1 */
     "HTML",		/*  2 */
     "HTML_UTF16",	/*  3 */
     "GRAPHICS",		/*  4 */
     "TEXT_ASCII",	/*  5 */
     "TEXT_UTF8",	/*  6 */
     "TEXT_UTF16LE",	/*  7 */
     "TEXT_UTF16BE",	/*  8 */
     "PDF",		/*  9 */
     "SCRIPT",		/* 10 */
     "RTF",		/* 11 */
     "RIFF",		/* 12 */
     "MSCHM",		/* 13 */
     "MSCAB",		/* 14 */
     "MSOLE2",		/* 15 */
     "MSSZDD",		/* 16 */
     "ZIP",		/* 17 */
     "RAR",		/* 18 */
     "7Z",		/* 19 */
     "BZ",		/* 20 */
     "GZ",		/* 21 */
     "ARJ",		/* 22 */
     "ZIPSFX",		/* 23 */
     "RARSFX",		/* 24 */
     "CABSFX",		/* 25 */
     "ARJSFX",		/* 26 */
     "NULSFT",		/* 27 */
     "AUTOIT",		/* 28 */
     "ISHIELD_MSI",	/* 29 */
     "SFX",		/* 30 */
     "BINHEX",		/* 31 */
     "MAIL",		/* 32 */
     "TNEF",		/* 33 */
     "BINARY_DATA",	/* 34 */
     "CRYPTFF",		/* 35 */
     "UUENCODED",	/* 36 */
     "SCRENC",		/* 37 */
     "POSIX_TAR",	/* 38 */
     "OLD_TAR",		/* 39 */
     "ELF",		/* 40 */
     "MACHO",		/* 41 */
     "MACHO_UNIBIN",	/* 42 */
     "SIS",		/* 43 */
     "SWF",		/* 44 */
     "CPIO_ODC",		/* 45 */
     "CPIO_NEWC",	/* 46 */
     "CPIO_CRC",		/* 47 */
a8afbec8
     NULL
4d08b6d6
 };
 
68c3f799
 int WINAPI SHCreateDirectoryExA(HWND, LPCTSTR, SECURITY_ATTRIBUTES *); /* cannot include Shlobj.h due to DATADIR collision */
b81ed285
 
c8baa1b9
 #define FMT(s) "!"__FUNCTION__": "s"\n"
5b9d7d31
 #define FAIL(errcode, fmt, ...) do { logg(FMT(fmt), __VA_ARGS__); return (errcode); } while(0)
f675a85d
 #define WIN() do { logg("*%s completed successfully\n", __FUNCTION__); return CLAMAPI_SUCCESS; } while(0)
 #define INFN() do { logg("*in %s\n", __FUNCTION__); } while(0)
6b7abaa7
 
d3c908c7
 #define MAX_VIRNAME_LEN 1024
 
f26ba7fa
 HANDLE reload_event;
 volatile LONG reload_waiters = 0;
c8abf221
 
32952445
 HANDLE monitor_event;
 HANDLE monitor_hdl = NULL;
 
6b7abaa7
 HANDLE engine_mutex;
c8abf221
 /* protects the following items */
 struct cl_engine *engine = NULL;
 char dbdir[PATH_MAX];
219e22e6
 char tmpdir[PATH_MAX];
32952445
 FILETIME last_chk_time = {0, 0};
c8abf221
 /* end of protected items */
 
7a5baa31
 typedef struct {
     CLAM_SCAN_CALLBACK scancb;
     void *scancb_ctx;
     unsigned int scanopts;
4d08b6d6
     _int64 *filetype;
7a5baa31
 } instance;
 
 struct {
     instance *inst;
     unsigned int refcnt;
 } *instances = NULL;
 unsigned int ninsts_total = 0;
 unsigned int ninsts_avail = 0;
7c254329
 unsigned int official_sigs, custom_sigs;
7a5baa31
 HANDLE instance_mutex;
 
ff72e2d2
 BOOL minimal_definitions = FALSE;
 
c8abf221
 #define lock_engine()(WaitForSingleObject(engine_mutex, INFINITE) == WAIT_FAILED)
 #define unlock_engine() do {ReleaseMutex(engine_mutex);} while(0)
6b7abaa7
 
7a5baa31
 #define lock_instances()(WaitForSingleObject(instance_mutex, INFINITE) == WAIT_FAILED)
 #define unlock_instances() do {ReleaseMutex(instance_mutex);} while(0)
 
4d08b6d6
 cl_error_t filetype_cb(int fd, const char *detected_file_type, void *context);
b9765d43
 cl_error_t prescan_cb(int fd, const char *detected_file_type, void *context);
aa7380df
 cl_error_t postscan_cb(int fd, int result, const char *virname, void *context);
 
32952445
 
 DWORD WINAPI monitor_thread(VOID *p) {
     char watchme[PATH_MAX];
     HANDLE harr[2], fff;
 
     if(lock_engine()) {
f675a85d
 	logg("^monitor_thread: failed to lock engine\n");
32952445
 	return 0;
     }
 
     snprintf(watchme, sizeof(watchme), "%s\\forcerld", dbdir);
     watchme[sizeof(watchme)-1] = '\0';
 
     harr[0] = monitor_event;
3709a4ce
     harr[1] = FindFirstChangeNotification(dbdir, FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME);
32952445
 
     unlock_engine();
 
     if(harr[1] == INVALID_HANDLE_VALUE) {
1a401901
 	logg("^monitor_thread: failed to monitor directory changes on %s\n", dbdir);
32952445
 	return 0;
     }
 
1a401901
     logg("monitor_thread: watching directory changes on %s\n", dbdir);
 
32952445
     while(1) {
 	WIN32_FIND_DATA wfd;
 	SYSTEMTIME st;
 
 	switch(WaitForMultipleObjects(2, harr, FALSE, INFINITE)) {
 	case WAIT_OBJECT_0:
1a401901
 	    logg("*monitor_thread: terminating upon request\n");
89c78883
 	    FindCloseChangeNotification(harr[1]);
32952445
 	    return 0;
 	case WAIT_OBJECT_0 + 1:
 	    break;
 	default:
1a401901
 	    logg("*monitor_thread: unexpected wait failure - %u\n", GetLastError());
32952445
 	    Sleep(1000);
 	    continue;
 	}
89c78883
 	FindNextChangeNotification(harr[1]);
 	if((fff = FindFirstFile(watchme, &wfd)) == INVALID_HANDLE_VALUE)
32952445
 	    continue;
 	FindClose(fff);
 
 	GetSystemTime(&st);
 	SystemTimeToFileTime(&st, &wfd.ftCreationTime);
 	if(CompareFileTime(&wfd.ftLastWriteTime, &wfd.ftCreationTime) > 0)
 	    wfd.ftLastWriteTime = wfd.ftCreationTime;
 	if(CompareFileTime(&wfd.ftLastWriteTime, &last_chk_time) <= 0)
 	    continue;
 
1a401901
 	logg("monitor_thread: reload requested!\n");
f66130c9
 	Scan_ReloadDatabase(minimal_definitions);
32952445
 	GetSystemTime(&st);
 	SystemTimeToFileTime(&st, &last_chk_time); /* FIXME: small race here */
     }
 }
 
d3c908c7
 static wchar_t *threat_type(const char *virname) {
     if(!virname)
 	return NULL;
     if(!strncmp(virname, "Trojan", 6))
 	return L"Trojan";
     if(!strncmp(virname, "Worm", 4))
 	return L"Worm";
     if(!strncmp(virname, "Exploit", 7))
 	return L"Exploit";
     if(!strncmp(virname, "Adware", 6))
 	return L"Adware";
     return L"Malware";
 }
 
7a5baa31
 static int add_instance(instance *inst) {
     unsigned int i;
 
     INFN();
     if(lock_instances()) {
c8baa1b9
 	logg("!add_instance: failed to lock instances\n");
7a5baa31
 	return 1;
     }
     if(!ninsts_avail) {
 	void *freeme, *new_instances = calloc(ninsts_total + 256, sizeof(*instances));
 	if(!new_instances) {
 	    unlock_instances();
c8baa1b9
 	    logg("!add_instance: failed to grow instances\n");
7a5baa31
 	    return 1;
 	}
 	freeme = instances;
 	if(instances && ninsts_total)
 	    memcpy(new_instances, instances, ninsts_total * sizeof(*instances));
 	ninsts_total += 256;
 	ninsts_avail += 256;
 	instances = new_instances;
 	if(freeme)
 	    free(freeme);
f675a85d
 	logg("*add_instance: instances grown to %u\n", ninsts_total);
7a5baa31
     }
     for(i=0; i<ninsts_total; i++) {
 	if(instances[i].inst)
 	    continue;
 	instances[i].inst = inst;
 	instances[i].refcnt = 0;
 	ninsts_avail--;
f675a85d
 	logg("*add_instance: now %u/%u instances available\n", ninsts_avail, ninsts_total);
7a5baa31
 	unlock_instances();
 	return 0;
     }
c8baa1b9
     logg("!add_instances: you should not be reading this\n");
7a5baa31
     unlock_instances();
     return 1;
 }
 
 static int del_instance(instance *inst) {
     unsigned int i;
 
     INFN();
     if(lock_instances()) {
c8baa1b9
 	logg("!del_instance: failed to lock instances\n");
1f87ea8f
 	return CL_ELOCK;
7a5baa31
     }
     for(i=0; i<ninsts_total; i++) {
 	if(instances[i].inst != inst)
 	    continue;
 	if(instances[i].refcnt) {
f675a85d
 	    logg("^del_instance: attempted to free instance with %d active scanners\n", instances[i].refcnt);
7a5baa31
 	    unlock_instances();
1f87ea8f
 	    return CL_EBUSY;
7a5baa31
 	}
 	instances[i].inst = NULL;
 	instances[i].refcnt = 0;
 	ninsts_avail++;
f675a85d
 	logg("*del_instance: %u / %u instances now available\n", ninsts_avail, ninsts_total);
7a5baa31
 	unlock_instances();
1f87ea8f
 	return CL_SUCCESS;
7a5baa31
     }
1a401901
     logg("!del_instances: instance %p not found\n", inst);
7a5baa31
     unlock_instances();
1f87ea8f
     return CL_EARG;
7a5baa31
 }
 
 /* To be called with the instances locked */
 static int is_instance(instance *inst) {
     unsigned int i;
     INFN();
     for(i=0; i<ninsts_total; i++)
 	if(instances[i].inst == inst)
 	    return 1;
c8baa1b9
     logg("^is_instance: lookup failed for instance %p\n", inst);
7a5baa31
     return 0;
 }
 
6b7abaa7
 BOOL interface_setup(void) {
     if(!(engine_mutex = CreateMutex(NULL, FALSE, NULL)))
 	return FALSE;
f26ba7fa
     if(!(reload_event = CreateEvent(NULL, TRUE, TRUE, NULL))) {
7a5baa31
 	CloseHandle(engine_mutex);
 	return FALSE;
     }
32952445
     if(!(monitor_event = CreateEvent(NULL, TRUE, FALSE, NULL))) {
 	CloseHandle(reload_event);
 	CloseHandle(engine_mutex);
 	return FALSE;
     }
7a5baa31
     if(!(instance_mutex = CreateMutex(NULL, FALSE, NULL))) {
32952445
 	CloseHandle(monitor_event);
 	CloseHandle(reload_event);
7a5baa31
 	CloseHandle(engine_mutex);
6b7abaa7
 	return FALSE;
7a5baa31
     }
6b7abaa7
     return TRUE;
 }
 
88d09f48
 static int sigload_callback(const char *type, const char *name, unsigned int custom, void *context) {
f206d31a
     if(!strcmp(type, "fp"))
 	return 0;
     if(minimal_definitions && !custom)
7a5baa31
 	return 1;
7c254329
     if(custom)
 	custom_sigs++;
f206d31a
     else
7c254329
 	official_sigs++;
7a5baa31
     return 0;
 }
 
48075ec2
 const char* cli_ctime(const time_t *timep, char *buf, const size_t bufsize);
32952445
 /* Must be called with engine_mutex locked ! */
48075ec2
 static void touch_last_update(unsigned signo) {
32952445
     char touchme[PATH_MAX];
     HANDLE h;
 
     snprintf(touchme, sizeof(touchme), "%s\\lastupd", dbdir);
     touchme[sizeof(touchme)-1] = '\0';
     if((h = CreateFile(touchme, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) != INVALID_HANDLE_VALUE) {
 	DWORD d;
48075ec2
 	int err;
 	unsigned ver = (unsigned)cl_engine_get_num(engine, CL_ENGINE_DB_VERSION, &err);
 	if (ver) {
 	    char timestr[32];
 	    const char *tstr;
 	    time_t t;
 	    t = cl_engine_get_num(engine, CL_ENGINE_DB_TIME, NULL);
 	    tstr = cli_ctime(&t, timestr, sizeof(timestr));
 	    /* cut trailing \n */
 	    timestr[strlen(tstr)-1] = '\0';
 	    snprintf(touchme, sizeof(touchme), "daily %u/%u sigs\n"
 		     "Database version: %u/%s\n"
 		     "Known viruses: %u\n"
 		     "Reloaded at: %d\n",
 		     ver, signo, ver, tstr, signo, (unsigned)time(NULL));
 	} else {
 	    snprintf(touchme, sizeof(touchme), "no daily/%u sigs\n"
 		     "Known viruses: %u\n"
 		     "Reloaded at: %d\n",
 		     signo, signo, (unsigned)time(NULL));
 	}
32952445
 	touchme[sizeof(touchme)-1] = '\0';
 	if(WriteFile(h, touchme, strlen(touchme), &d, NULL)) {
 	    /* SetEndOfFile(h); */
 	    GetFileTime(h, NULL, NULL, &last_chk_time);
 	}
 	CloseHandle(h);
     } else
1a401901
 	logg("^touch_last_lastcheck: failed to touch lastreload\n");
32952445
 }
 
 
7a5baa31
 /* Must be called with engine_mutex locked ! */
c8abf221
 static int load_db(void) {
7a5baa31
     unsigned int signo = 0;
32952445
     size_t used, total;
     int ret;
 
 
1f37f717
     INFN();
7a5baa31
 
     cl_engine_set_clcb_sigload(engine, sigload_callback, NULL);
7c254329
     official_sigs = custom_sigs = 0;
d9641dd3
     if((ret = cl_load(dbdir, engine, &signo, CL_DB_STDOPT & ~CL_DB_PHISHING & ~CL_DB_PHISHING_URLS)) != CL_SUCCESS) {
bc7db351
 	cl_engine_free(engine);
c8abf221
 	engine = NULL;
5b9d7d31
 	FAIL(ret, "Failed to load database: %s", cl_strerror(ret));
c8abf221
     }
 
     if((ret = cl_engine_compile(engine))) {
 	cl_engine_free(engine);
 	engine = NULL;
5b9d7d31
 	FAIL(ret, "Failed to compile engine: %s", cl_strerror(ret));
c8abf221
     }
 
c4190741
     logg("load_db: loaded %d signatures (%u official, %u custom)\n", signo, official_sigs, custom_sigs); 
32952445
     if (!mpool_getstats(engine, &used, &total))
 	logg("load_db: memory %.3f MB / %.3f MB\n", used/(1024*1024.0), total/(1024*1024.0));
 
48075ec2
     touch_last_update(signo);
32952445
 
c8abf221
     WIN();
 }
 
7c254329
 int CLAMAPI Scan_HaveSigs(unsigned int *official, unsigned int *custom) {
     int ret;
     if(lock_engine())
f206d31a
 	FAIL(CL_ELOCK, "failed to lock engine");
7c254329
     if(!engine) {
 	unlock_engine();
f206d31a
 	FAIL(CL_ESTATE, "Engine unavailable");
7c254329
     }
     ret = ((official_sigs + custom_sigs) > 0);
     if(official) *official = official_sigs;
     if(custom) *custom = custom_sigs;
     unlock_engine();
     return ret;
 }
 
6b7abaa7
 static void free_engine_and_unlock(void) {
     cl_engine_free(engine);
     engine = NULL;
     unlock_engine();
 }
 
ff72e2d2
 int CLAMAPI Scan_Initialize(const wchar_t *pEnginesFolder, const wchar_t *pTempRoot, const wchar_t *pLicenseKey, BOOL bLoadMinDefs) {
6b7abaa7
     BOOL cant_convert;
     int ret;
 
1a401901
     logg("*in Scan_Initialize(pEnginesFolder = %S, pTempRoot = %S)\n", pEnginesFolder, pTempRoot);
7a5baa31
     if(!pEnginesFolder)
 	FAIL(CL_ENULLARG, "pEnginesFolder is NULL");
     if(!pTempRoot)
 	FAIL(CL_ENULLARG, "pTempRoot is NULL");
6b7abaa7
     if(lock_engine())
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock engine");
6b7abaa7
     if(engine) {
 	unlock_engine();
1f87ea8f
 	FAIL(CL_ESTATE, "Already initialized");
6b7abaa7
     }
7a5baa31
 
6b7abaa7
     if(!(engine = cl_engine_new())) {
 	unlock_engine();
5b9d7d31
 	FAIL(CL_EMEM, "Not enough memory for a new engine");
     }
4d08b6d6
     cl_engine_set_clcb_pre_cache(engine, filetype_cb);
     cl_engine_set_clcb_pre_scan(engine, prescan_cb);
1f37f717
     cl_engine_set_clcb_post_scan(engine, postscan_cb);
ff72e2d2
     
     minimal_definitions = bLoadMinDefs;
     if(bLoadMinDefs)
1a401901
 	logg("^MINIMAL DEFINITIONS MODE ON!\n");
ff72e2d2
 
5b9d7d31
     if(!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pTempRoot, -1, tmpdir, sizeof(tmpdir), NULL, &cant_convert) || cant_convert) {
 	free_engine_and_unlock();
 	FAIL(CL_EARG, "Can't translate pTempRoot");
     }
23281981
     ret = strlen(tmpdir);
     while(ret>0 && tmpdir[--ret] == '\\')
 	tmpdir[ret] = '\0';
     if(!ret || ret + 8 + 1 >= sizeof(tmpdir)) {
 	free_engine_and_unlock();
 	FAIL(CL_EARG, "Bad or too long pTempRoot '%s'", tmpdir);
     }
     memcpy(&tmpdir[ret+1], "\\clamtmp", 9);
     cli_rmdirs(tmpdir);
68c3f799
     if((ret = SHCreateDirectoryExA(NULL, tmpdir, NULL) != ERROR_SUCCESS) && ret != ERROR_ALREADY_EXISTS) {
b81ed285
 	free_engine_and_unlock();
 	FAIL(CL_ETMPDIR, "Cannot create pTempRoot '%s': error %d", tmpdir, ret);
23281981
     }
5b9d7d31
     if((ret = cl_engine_set_str(engine, CL_ENGINE_TMPDIR, tmpdir))) {
 	free_engine_and_unlock();
23281981
 	FAIL(ret, "Failed to set engine tempdir to '%s': %s", tmpdir, cl_strerror(ret));
6b7abaa7
     }
     if(!WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, pEnginesFolder, -1, dbdir, sizeof(dbdir), NULL, &cant_convert) || cant_convert) {
 	free_engine_and_unlock();
5b9d7d31
 	FAIL(CL_EARG, "Can't translate pEnginesFolder");
6b7abaa7
     }
c8abf221
     ret = load_db();
6b7abaa7
     unlock_engine();
32952445
 
a183ba35
     if(!ret) {
 	ResetEvent(monitor_event);
 	if(!(monitor_hdl = CreateThread(NULL, 0, monitor_thread, NULL, 0, NULL)))
 	    logg("^Failed to start db monitoring thread\n");
     }
32952445
 
f675a85d
     logg("*Scan_Initialize: returning %d\n", ret);
c8abf221
     return ret;
6b7abaa7
 }
 
5bdde179
 int uninitialize_called = 0;
6b7abaa7
 int CLAMAPI Scan_Uninitialize(void) {
c8abf221
  //   int rett;
  //   __asm {
 	//MOV eax, [ebp + 4]
 	//mov rett, eax
  //   }
  //   logg("%x", rett);
5bdde179
     uninitialize_called = 1;
1f37f717
     INFN();
faea131e
 
     if(monitor_hdl) {
 	SetEvent(monitor_event);
 	if(WaitForSingleObject(monitor_hdl, 5000) != WAIT_OBJECT_0) {
1a401901
 	    logg("^Scan_Uninitialize: forcibly terminating monitor thread after 5 seconds\n");
faea131e
 	    TerminateThread(monitor_hdl, 0);
 	}
     }
     monitor_hdl = NULL;
 
a183ba35
     if(lock_engine())
 	FAIL(CL_ELOCK, "failed to lock engine");
     if(!engine) {
 	unlock_engine();
 	FAIL(CL_ESTATE, "attempted to uninit a NULL engine");
     }
 
faea131e
     if(lock_instances()) {
7a5baa31
 	unlock_engine();
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances");
6b7abaa7
     }
7a5baa31
     if(ninsts_avail != ninsts_total) {
 	volatile unsigned int refcnt = ninsts_total - ninsts_avail;
 	unlock_instances();
6b7abaa7
 	unlock_engine();
1f87ea8f
 	FAIL(CL_EBUSY, "Attempted to uninit the engine with %u active instances", refcnt);
6b7abaa7
     }
7a5baa31
     unlock_instances();
6b7abaa7
     free_engine_and_unlock();
32952445
 
6b7abaa7
     WIN();
 }
 
 int CLAMAPI Scan_CreateInstance(CClamAVScanner **ppScanner) {
7a5baa31
     instance *inst;
 
1f37f717
     INFN();
7a5baa31
     if(!ppScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
4d08b6d6
     inst = (instance *)calloc(1, sizeof(*inst));
6b7abaa7
     if(!inst)
5b9d7d31
 	FAIL(CL_EMEM, "CreateInstance: OOM");
6b7abaa7
     if(lock_engine()) {
 	free(inst);
1f87ea8f
 	FAIL(CL_ELOCK, "Failed to lock engine");
6b7abaa7
     }
     if(!engine) {
 	free(inst);
 	unlock_engine();
1f87ea8f
 	FAIL(CL_ESTATE, "Create instance called with no engine");
6b7abaa7
     }
7a5baa31
     if(add_instance(inst)) {
 	free(inst);
 	unlock_engine();
 	FAIL(CL_EMEM, "add_instance failed");
     }
6b7abaa7
     unlock_engine();
3b35bf25
     inst->scanopts = CL_SCAN_STDOPT;
     if (logg_verbose)
 	inst->scanopts |= CL_SCAN_PERFORMANCE_INFO;
6b7abaa7
     *ppScanner = (CClamAVScanner *)inst;
1a401901
     logg("Created new instance %p\n", inst);
6b7abaa7
     WIN();
 }
 
faea131e
 // Caller: if we return error will retry once after 2 seconds.
 // No point in retrying more times since we are shutting down anyway.
6b7abaa7
 int CLAMAPI Scan_DestroyInstance(CClamAVScanner *pScanner) {
1f87ea8f
     int rc;
1f37f717
     INFN();
7a5baa31
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
faea131e
     if((rc = del_instance((instance *)pScanner))) {
 	if (rc == CL_EBUSY) {
 	    // wait for one of the scanner threads to finish, and retry again,
 	    // thats better than caller always waiting 2 seconds to retry.
 	    if (WaitForSingleObject(reload_event, 1000) != WAIT_OBJECT_0)
 		logg("Scan_DestroyInstance: timeout");
 	    rc = del_instance((instance *)pScanner);
 	}
 	if (rc)
 	    FAIL(rc, "del_instance failed for %p", pScanner);
     }
6b7abaa7
     free(pScanner);
1a401901
     logg("in Scan_DestroyInstance: Instance %p destroyed\n", pScanner);
6b7abaa7
     WIN();
 }
 
 int CLAMAPI Scan_SetScanCallback(CClamAVScanner *pScanner, CLAM_SCAN_CALLBACK pfnCallback, void *pContext) {
7a5baa31
     instance *inst;
 
f675a85d
     logg("*in SetScanCallback(pScanner = %p, pfnCallback = %p, pContext = %p)\n", pScanner, pfnCallback, pContext);
7a5baa31
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
     if(lock_instances())
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);
7a5baa31
 
     inst = (instance *)pScanner;
     if(is_instance(inst)) {
 	inst->scancb = pfnCallback;
 	inst->scancb_ctx = pContext;
 	unlock_instances();
 	WIN();
     }
     unlock_instances();
     FAIL(CL_EARG, "invalid instance %p", inst);
6b7abaa7
 }
 
 int CLAMAPI Scan_SetOption(CClamAVScanner *pScanner, int option, void *value, unsigned long inputLength) {
7a5baa31
     instance *inst;
5b9d7d31
     unsigned int whichopt, newval;
1f37f717
     
     INFN();
7a5baa31
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
     if(!value)
 	FAIL(CL_ENULLARG, "NULL value");
     if(lock_instances())
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances");
7a5baa31
 
     inst = (instance *)pScanner;
     if(!is_instance(inst)) {
 	unlock_instances();
 	FAIL(CL_EARG, "invalid instance %p", inst);
     }
c8baa1b9
     newval = *(unsigned int *)value;
6b7abaa7
     switch(option) {
c8abf221
 	case CLAM_OPTION_SCAN_ARCHIVE:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_ARCHIVE: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_ARCHIVE;
 	    break;
 	case CLAM_OPTION_SCAN_MAIL:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_MAIL: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_MAIL;
 	    break;
 	case CLAM_OPTION_SCAN_OLE2:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_OLE2: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_OLE2;
 	    break;
 	case CLAM_OPTION_SCAN_HTML:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_HTML: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_HTML;
 	    break;
 	case CLAM_OPTION_SCAN_PE:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_PE: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_PE;
 	    break;
 	case CLAM_OPTION_SCAN_PDF:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_PDF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_PDF;
 	    break;
 	case CLAM_OPTION_SCAN_ALGORITHMIC:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_ALGORITHMIC: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_ALGORITHMIC;
 	    break;
 	case CLAM_OPTION_SCAN_ELF:
c8baa1b9
 	    logg("CLAM_OPTION_SCAN_ELF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
5b9d7d31
 	    whichopt = CL_SCAN_ELF;
 	    break;
1e41fdba
 	case CLAM_OPTION_SCAN_SWF:
 	    logg("CLAM_OPTION_SCAN_SWF: %s on instance %p\n", newval ? "enabled" : "disabled", inst);
 	    whichopt = CL_SCAN_SWF;
 	    break;
6b7abaa7
 	default:
7a5baa31
 	    unlock_instances();
 	    FAIL(CL_EARG, "Unsupported option: %d", option);
6b7abaa7
     }
5b9d7d31
 
     if(!newval)
 	inst->scanopts &= ~whichopt;
     else
 	inst->scanopts |= whichopt;
7a5baa31
     unlock_instances();
5b9d7d31
     WIN();
6b7abaa7
 }
 
 int CLAMAPI Scan_GetOption(CClamAVScanner *pScanner, int option, void *value, unsigned long inputLength, unsigned long *outLength) {
7a5baa31
     instance *inst;
5b9d7d31
     unsigned int whichopt;
6b7abaa7
 
1f37f717
     INFN();
7a5baa31
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
     if(!value || !inputLength)
 	FAIL(CL_ENULLARG, "NULL value");
     if(lock_instances())
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances");
7a5baa31
 
     inst = (instance *)pScanner;
     if(!is_instance(inst)) {
 	unlock_instances();
 	FAIL(CL_EARG, "invalid instance %p", inst);
     }
5b9d7d31
     switch(option) {
 	case CLAM_OPTION_SCAN_ARCHIVE:
 	    whichopt = CL_SCAN_ARCHIVE;
 	    break;
 	case CLAM_OPTION_SCAN_MAIL:
 	    whichopt = CL_SCAN_MAIL;
 	    break;
 	case CLAM_OPTION_SCAN_OLE2:
 	    whichopt = CL_SCAN_OLE2;
 	    break;
 	case CLAM_OPTION_SCAN_HTML:
 	    whichopt = CL_SCAN_HTML;
 	    break;
 	case CLAM_OPTION_SCAN_PE:
 	    whichopt = CL_SCAN_PE;
 	    break;
 	case CLAM_OPTION_SCAN_PDF:
 	    whichopt = CL_SCAN_PDF;
 	    break;
 	case CLAM_OPTION_SCAN_ALGORITHMIC:
 	    whichopt = CL_SCAN_ALGORITHMIC;
 	    break;
 	case CLAM_OPTION_SCAN_ELF:
 	    whichopt = CL_SCAN_ELF;
 	    break;
1e41fdba
 	case CLAM_OPTION_SCAN_SWF:
 	    whichopt = CL_SCAN_SWF;
 	    break;
6b7abaa7
 	default:
7a5baa31
 	    unlock_instances();
 	    FAIL(CL_EARG, "Unsupported option: %d", option);
6b7abaa7
     }
5b9d7d31
 
     *(unsigned int *)value = (inst->scanopts & whichopt) != 0;
7a5baa31
     unlock_instances();
5b9d7d31
     WIN();
6b7abaa7
 }
 
d4afcf98
 
 int CLAMAPI Scan_GetLimit(int option, unsigned int *value) {
     enum cl_engine_field limit;
     long long curlimit;
     int err;
 
     INFN();
     if(lock_engine())
1f87ea8f
 	FAIL(CL_ELOCK, "Failed to lock engine");
d4afcf98
     if(!engine) {
 	unlock_engine();
1f87ea8f
 	FAIL(CL_ESTATE, "Engine is NULL");
d4afcf98
     }
     switch((enum CLAM_LIMIT_TYPE)option) {
     case CLAM_LIMIT_FILESIZE:
 	limit = CL_ENGINE_MAX_FILESIZE;
 	break;
     case CLAM_LIMIT_SCANSIZE:
 	limit = CL_ENGINE_MAX_SCANSIZE;
 	break;
     case CLAM_LIMIT_RECURSION:
 	limit = CL_ENGINE_MAX_SCANSIZE;
 	break;
     default:
 	unlock_engine();
 	FAIL(CL_EARG, "Unsupported limit type: %d", option);
     }
     curlimit = cl_engine_get_num(engine, limit, &err);
     if(err) {
 	unlock_engine();
 	FAIL(err, "Failed to get engine value: %s", cl_strerror(err));
     }
     if(curlimit > 0xffffffff)
 	*value = 0xffffffff;
     else
 	*value = (unsigned int)curlimit;
     unlock_engine();
     WIN();
 }
 
 
 int CLAMAPI Scan_SetLimit(int option, unsigned int value) {
     enum cl_engine_field limit;
     int err;
 
     INFN();
     if(lock_engine())
1f87ea8f
 	FAIL(CL_ELOCK, "Failed to lock engine");
d4afcf98
     if(!engine) {
 	unlock_engine();
1f87ea8f
 	FAIL(CL_ESTATE, "Engine is NULL");
d4afcf98
     }
     switch((enum CLAM_LIMIT_TYPE)option) {
     case CLAM_LIMIT_FILESIZE:
f675a85d
 	logg("CLAM_LIMIT_FILESIZE: set to %u\n", value);
d4afcf98
 	limit = CL_ENGINE_MAX_FILESIZE;
 	break;
     case CLAM_LIMIT_SCANSIZE:
f675a85d
 	logg("CLAM_LIMIT_SCANSIZE: set to %u\n", value);
d4afcf98
 	limit = CL_ENGINE_MAX_SCANSIZE;
 	break;
     case CLAM_LIMIT_RECURSION:
f675a85d
 	logg("CLAM_LIMIT_RECURSION: set to %u\n", value);
d4afcf98
 	limit = CL_ENGINE_MAX_SCANSIZE;
 	break;
     default:
 	unlock_engine();
 	FAIL(CL_EARG, "Unsupported limit type: %d", option);
     }
     err = cl_engine_set_num(engine, limit, (long long)value);
     unlock_engine();
     if(err)
 	FAIL(err, "Failed to set engine value: %s", cl_strerror(err));
     WIN();
 }
 
 
cf85afda
 static wchar_t *uncpathw(const wchar_t *path) {
     DWORD len = 0;
     unsigned int pathlen;
     wchar_t *stripme, *strip_from, *dest = malloc((PATH_MAX + 1) * sizeof(wchar_t));
 
     if(!dest)
 	return NULL;
 
     pathlen = wcslen(path);
     if(wcsncmp(path, L"\\\\", 2)) {
 	/* NOT already UNC */
 	memcpy(dest, L"\\\\?\\", 8);
 	if(pathlen < 2 || path[1] != L':' || *path < L'A' || *path > L'z' || (*path > L'Z' && *path < L'a')) {
 	    /* Relative path */
 	    len = GetCurrentDirectoryW(PATH_MAX - 5, &dest[4]);
 	    if(!len || len > PATH_MAX - 5) {
 		free(dest);
 		return NULL;
 	    }
 	    if(*path == L'\\')
 		len = 6; /* Current drive root */
 	    else {
 		len += 4; /* A 'really' relative path */
 		dest[len] = L'\\';
 		len++;
 	    }
 	} else {
 	    /* C:\ and friends */
 	    len = 4;
 	}
     } else {
 	/* UNC already */
 	len = 0;
     }
 
     if(pathlen >= PATH_MAX - len) {
 	free(dest);
         return NULL;
     }
     wcscpy(&dest[len], path);
     len = wcslen(dest);
     strip_from = &dest[3];
     /* append a backslash to naked drives and get rid of . and .. */
     if(!wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
 	if(len == 6) {
 	    dest[6] = L'\\';
 	    dest[7] = L'\0';
 	}
 	strip_from = &dest[6];
     }
     while((stripme = wcsstr(strip_from, L"\\."))) {
 	wchar_t *copy_from, *copy_to;
 	if(!stripme[2] || stripme[2] == L'\\') {
 	    copy_from = &stripme[2];
 	    copy_to = stripme;
 	} else if (stripme[2] == L'.' && (!stripme[3] || stripme[3] == L'\\')) {
 	    *stripme = L'\0';
 	    copy_from = &stripme[3];
 	    copy_to = wcsrchr(strip_from, L'\\');
 	    if(!copy_to)
 		copy_to = stripme;
 	} else {
 	    strip_from = &stripme[1];
 	    continue;
 	}
 	while(1) {
 	    *copy_to = *copy_from;
 	    if(!*copy_from) break;
 	    copy_to++;
 	    copy_from++;
 	}
     }
 
     /* strip double slashes */
     if((stripme = wcsstr(&dest[4], L"\\\\"))) {
 	strip_from = stripme;
 	while(1) {
 	    wchar_t c = *strip_from;
 	    strip_from++;
 	    if(c == L'\\' && *strip_from == L'\\')
 		continue;
 	    *stripme = c;
 	    stripme++;
 	    if(!c)
 		break;
 	}
     }
     if(wcslen(dest) == 6 && !wcsncmp(dest, L"\\\\?\\", 4) && (dest[5] == L':') && ((dest[4] >= L'A' && dest[4] <= L'Z') || (dest[4] >= L'a' && dest[4] <= L'z'))) {
 	dest[6] = L'\\';
 	dest[7] = L'\0';
     }
     return dest;
 }
 
 
5b9d7d31
 int CLAMAPI Scan_ScanObject(CClamAVScanner *pScanner, const wchar_t *pObjectPath, int *pScanStatus, PCLAM_SCAN_INFO_LIST *pInfoList) {
6b7abaa7
     HANDLE fhdl;
     int res;
c8abf221
     instance *inst = (instance *)pScanner;
6b7abaa7
 
f675a85d
     logg("*in Scan_ScanObject(pScanner = %p, pObjectPath = %S)\n", pScanner, pObjectPath);
8557628a
     if((fhdl = CreateFileW(pObjectPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL)) == INVALID_HANDLE_VALUE) {
cf85afda
 	wchar_t *uncfname = uncpathw(pObjectPath);
 	if(!uncfname)
 	    FAIL(CL_EMEM, "uncpathw() failed");
8557628a
 	fhdl = CreateFileW(uncfname, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
cf85afda
 	logg("*Scan_ScanObject translating '%S' to '%S'\n", pObjectPath, uncfname);
 	free(uncfname);
 	if(fhdl == INVALID_HANDLE_VALUE)
 	    FAIL(CL_EOPEN, "open() failed");
     }
f675a85d
     logg("*Scan_ScanObject (instance %p) invoking Scan_ScanObjectByHandle for handle %p (%S)\n", pScanner, fhdl, pObjectPath);
5b9d7d31
     res = Scan_ScanObjectByHandle(pScanner, fhdl, pScanStatus, pInfoList);
f675a85d
     logg("*Scan_ScanObject (instance %p) invoking Scan_ScanObjectByHandle returned %d\n", pScanner, res);
6b7abaa7
     CloseHandle(fhdl);
     return res;
 }
 
aa7380df
 struct scan_ctx {
     int entryfd;
     instance *inst;
593e71c0
     DWORD cb_times;
cefb07dc
     DWORD copy_times;
aa7380df
 };
 
5b9d7d31
 int CLAMAPI Scan_ScanObjectByHandle(CClamAVScanner *pScanner, HANDLE object, int *pScanStatus, PCLAM_SCAN_INFO_LIST *pInfoList) {
7a5baa31
     instance *inst;
6b7abaa7
     HANDLE duphdl, self;
9f3ff0a4
     char *virname = NULL;
6b7abaa7
     int fd, res;
7a5baa31
     unsigned int i;
aa7380df
     struct scan_ctx sctx;
c8baa1b9
     DWORD perf;
c8abf221
 
f675a85d
     logg("*in Scan_ScanObjectByHandle(pScanner = %p, HANDLE = %p, pScanStatus = %p, pInfoList = %p)\n", pScanner, object, pScanStatus, pInfoList);
7a5baa31
 
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
     if(!pScanStatus)
c8baa1b9
 	FAIL(CL_ENULLARG, "NULL pScanStatus on instance %p", pScanner);
6b7abaa7
 
     self = GetCurrentProcess();
7a5baa31
     if(!DuplicateHandle(self, object, self, &duphdl, GENERIC_READ, FALSE, 0))
c8baa1b9
 	FAIL(CL_EDUP, "Duplicate handle failed for instance %p", pScanner);
6b7abaa7
 
     if((fd = _open_osfhandle((intptr_t)duphdl, _O_RDONLY)) == -1) {
 	CloseHandle(duphdl);
c8baa1b9
 	FAIL(CL_EOPEN, "Open handle failed for instance %p", pScanner);
6b7abaa7
     }
 
7a5baa31
     if(lock_instances()) {
 	close(fd);
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);
7a5baa31
     }
     inst = (instance *)pScanner;
     for(i=0; i<ninsts_total; i++) {
 	if(instances[i].inst == inst)
 	    break;
     }
     if(i == ninsts_total) {
 	unlock_instances();
 	close(fd);
 	FAIL(CL_EARG, "invalid instance %p", inst);
     }
     instances[i].refcnt++;
219e22e6
     ResetEvent(reload_event);
7a5baa31
     unlock_instances();
 
aa7380df
     sctx.entryfd = fd;
     sctx.inst = inst;
593e71c0
     sctx.cb_times = 0;
cefb07dc
     sctx.copy_times = 0;
f675a85d
     logg("*Scan_ScanObjectByHandle (instance %p) invoking cl_scandesc with clamav context %p\n", inst, &sctx);
c8baa1b9
     perf = GetTickCount();
aa7380df
     res = cl_scandesc_callback(fd, &virname, NULL, engine, inst->scanopts, &sctx);
c31cb63d
 
4d08b6d6
     if(!inst->filetype) do {
c31cb63d
 	CLAM_SCAN_INFO si;
 	CLAM_ACTION act;
 	DWORD cbperf;
8aacea51
 	wchar_t wvirname[MAX_VIRNAME_LEN] = L"Clam.";
c31cb63d
 	LONG lo = 0, hi = 0, hi2 = 0;
 
 	si.cbSize = sizeof(si);
 	si.flags = 0;
 	si.scanPhase = SCAN_PHASE_FINAL;
 	si.errorCode = CLAMAPI_SUCCESS;
 	if(res == CL_VIRUS) {
8aacea51
 	    if(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN - 5))
c31cb63d
 		si.pThreatName = wvirname;
 	    else
b4053863
 		si.pThreatName = L"Clam.UNOFFICIAL";
c31cb63d
 	} else
 	    si.pThreatName = NULL;
f675a85d
 	logg("*in final_cb with clamav context %p, instance %p, fd %d, result %d, virusname %S)\n", &sctx, inst, fd, res, si.pThreatName);
c31cb63d
 	si.pThreatType = threat_type(virname);
1aff6a35
 	si.object = INVALID_HANDLE_VALUE;
 	si.objectId = INVALID_HANDLE_VALUE;
c31cb63d
 	si.pInnerObjectPath = NULL;
 	lo = SetFilePointer(duphdl, 0, &hi, FILE_CURRENT);
 	SetFilePointer(duphdl, 0, &hi2, FILE_BEGIN);
f675a85d
 	logg("*final_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", &sctx, inst, inst->scancb, inst->scancb_ctx);
c31cb63d
 	cbperf = GetTickCount();
 	inst->scancb(&si, &act, inst->scancb_ctx);
 	cbperf = GetTickCount() - cbperf;
593e71c0
 	sctx.cb_times += cbperf;
f675a85d
 	logg("*final_cb (clamav context %p, instance %p) callback completed with %u (result ignored) in %u ms\n", &sctx, inst, act, cbperf);
c31cb63d
 	SetFilePointer(duphdl, lo, &hi, FILE_BEGIN);
     } while(0);
 
c8baa1b9
     perf = GetTickCount() - perf;
6b7abaa7
     close(fd);
45684758
     logg("*Scan_ScanObjectByHandle (instance %p): cl_scandesc returned %d in %u ms (%d ms own, %d ms copy)\n", inst, res, perf, perf - sctx.cb_times - sctx.copy_times, sctx.copy_times);
6b7abaa7
 
7a5baa31
     if(lock_instances())
1f87ea8f
 	FAIL(CL_ELOCK, "failed to lock instances for instance %p", pScanner);
7a5baa31
     instances[i].refcnt--;
219e22e6
     if(!instances[i].refcnt)
 	SetEvent(reload_event);
7a5baa31
     unlock_instances();
 
6b7abaa7
     if(res == CL_VIRUS) {
1a401901
 	logg("Scan_ScanObjectByHandle (instance %p): file is INFECTED with %s\n", inst, virname);
7a5baa31
 	if(pInfoList) {
4d08b6d6
 	    CLAM_SCAN_INFO_LIST *infolist = (CLAM_SCAN_INFO_LIST *)calloc(1, sizeof(CLAM_SCAN_INFO_LIST) + sizeof(CLAM_SCAN_INFO) + MAX_VIRNAME_LEN * 2);
7a5baa31
 	    PCLAM_SCAN_INFO scaninfo;
 	    wchar_t *wvirname;
 	    if(!infolist)
c8baa1b9
 		FAIL(CL_EMEM, "ScanByHandle (instance %p): OOM while allocating result list", inst);
7a5baa31
 	    scaninfo = (PCLAM_SCAN_INFO)(infolist + 1);
 	    infolist->cbCount = 1;
 	    scaninfo->cbSize = sizeof(*scaninfo);
 	    scaninfo->scanPhase = SCAN_PHASE_FINAL;
 	    scaninfo->errorCode = CLAMAPI_SUCCESS;
d3c908c7
 	    scaninfo->pThreatType = threat_type(virname);
1aff6a35
 	    scaninfo->object = INVALID_HANDLE_VALUE;
 	    scaninfo->objectId = INVALID_HANDLE_VALUE;
7a5baa31
 	    wvirname = (wchar_t *)(scaninfo + 1);
 	    scaninfo->pThreatName = wvirname;
8aacea51
 	    memcpy(wvirname, L"Clam.", 10);
 	    if(!MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN-5))
b4053863
 		scaninfo->pThreatName = L"Clam.UNOFFICIAL";
7a5baa31
 	    *pInfoList = infolist;
1a401901
 	    logg("*Scan_ScanObjectByHandle (instance %p): created result list %p\n", inst, infolist);
7a5baa31
 	}
5b9d7d31
 	*pScanStatus = CLAM_INFECTED;
c8baa1b9
     } else if(res == CL_CLEAN) {
f675a85d
 	logg("*Scan_ScanObjectByHandle (instance %p): file is CLEAN\n", inst);
7a5baa31
         if(pInfoList) *pInfoList = NULL;
5b9d7d31
 	*pScanStatus = CLAM_CLEAN;
c8baa1b9
     } else {
 	FAIL(res, "Scan failed for instance %p: %s", inst, cl_strerror(res));
6b7abaa7
     }
     WIN();
 }
 
4d08b6d6
 int CLAMAPI Scan_GetFileType(HANDLE hFile, _int64 *filetype) {
     instance *inst;
     int status, ret = Scan_CreateInstance((CClamAVScanner **)&inst);
8bf75982
     logg("*in Scan_GetFileType(HANDLE = %x, filetype = %p)\n", hFile, filetype);
     if(ret != CLAMAPI_SUCCESS) {
 	FAIL(ret, "Failed to create instance, error %d", ret);
     }
4d08b6d6
     inst->filetype = filetype;
     ret = Scan_ScanObjectByHandle((CClamAVScanner *)inst, hFile, &status, NULL);
8bf75982
     logg("Scan_GetFileType: Scan_ScanObjectByHandle returned %d, type %016llx%016llx\n", ret, filetype[1], filetype[0]);
4d08b6d6
     Scan_DestroyInstance((CClamAVScanner *)inst);
     return ret;
 }
6b7abaa7
 
 int CLAMAPI Scan_DeleteScanInfo(CClamAVScanner *pScanner, PCLAM_SCAN_INFO_LIST pInfoList) {
f675a85d
     logg("*in Scan_DeleteScanInfo(pScanner = %p, pInfoList = %p)\n", pScanner, pInfoList);
7a5baa31
     if(!pScanner)
 	FAIL(CL_ENULLARG, "NULL pScanner");
     if(!pInfoList)
 	FAIL(CL_ENULLARG, "NULL pInfoList");
     /* FIXME checking this is pointelss as the infolist is independent from pscanner */
  //   if(lock_instances())
 	//FAIL(CL_EMEM, "failed to lock instances");
 
  //   inst = (instance *)pScanner;
  //   if(!is_instance(inst)) {
 	//unlock_instances();
 	//FAIL(CL_EARG, "invalid instance");
  //   }
  //   unlock_instances();
 
6b7abaa7
     free(pInfoList);
     WIN();
 }
c8abf221
 
a8afbec8
 
 static void ftype_bits(const char *type, _int64 *filetype) {
     int i;
8bf75982
     if(!strncmp(type, "CL_TYPE_", 8)) {
99f01b4b
 	for(i=1; types[i]; i++) {
a8afbec8
 	    if(!strcmp(&type[8], types[i]))
 		break;
 	}
 	if(!types[i]) i = -1;
     } else
 	i = -1;
     if(i<0) {
 	filetype[0] = 0;
 	filetype[1] = 0;
     } else if(i<64) {
 	filetype[0] = 1LL << i;
 	filetype[1] = 0;
     } else {
 	filetype[0] = 0;
 	filetype[1] = 1LL << (i-64);
     }
8bf75982
     logg("*ftype_bits setting type to %016llx%016llx\n", filetype[1], filetype[0]);
a8afbec8
 }
 
4d08b6d6
 cl_error_t filetype_cb(int fd, const char *type, void *context) {
     struct scan_ctx *sctx = (struct scan_ctx *)context;
     if(sctx && sctx->inst && sctx->inst->filetype) {
a8afbec8
 	ftype_bits(type, sctx->inst->filetype);
8bf75982
         logg("*in filetype_cb with clamav context %p, instance %p, fd %d, type %s, typenum %016llx%016llx)\n", context, sctx->inst, fd, type, sctx->inst->filetype[1], sctx->inst->filetype[0]);
4d08b6d6
 	return CL_BREAK;
     }
     return CL_CLEAN;
 }
 
b9765d43
 cl_error_t prescan_cb(int fd, const char *type, void *context) {
aa7380df
     struct scan_ctx *sctx = (struct scan_ctx *)context;
8038c673
     char tmpf[4096];
c8baa1b9
     instance *inst;
aa7380df
     CLAM_SCAN_INFO si;
     CLAM_ACTION act;
7a5baa31
     HANDLE fdhdl;
cefb07dc
     DWORD perf, perf2 = 0;
aa7380df
 
c8baa1b9
     if(!context) {
 	logg("!prescan_cb called with NULL clamav context\n");
 	return CL_CLEAN;
     }
     inst = sctx->inst;
4d08b6d6
     if(inst && inst->filetype)
 	return CL_CLEAN; /* Just in case, this shouldn't happen */
 
b9765d43
     logg("*in prescan_cb with clamav context %p, instance %p, fd %d, type %s)\n", context, inst, fd, type);
a8afbec8
     ftype_bits(type, si.filetype);
aa7380df
     si.cbSize = sizeof(si);
     si.flags = 0;
     si.scanPhase = (fd == sctx->entryfd) ? SCAN_PHASE_INITIAL : SCAN_PHASE_PRESCAN;
     si.errorCode = CLAMAPI_SUCCESS;
     si.pThreatType = NULL;
     si.pThreatName = NULL;
     si.pInnerObjectPath = NULL;
7a5baa31
 
1aff6a35
     if(si.scanPhase == SCAN_PHASE_PRESCAN) {
 	long fpos;
 	int rsz;
45684758
 	perf2 = GetTickCount();
1aff6a35
 	while(1) {
 	    static int tmpn;
 	    snprintf(tmpf, sizeof(tmpf), "%s\\%08x.tmp", tmpdir, ++tmpn);
 	    tmpf[sizeof(tmpf)-1] = '\0';
694a6384
 	    fdhdl = CreateFile(tmpf, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL);
1aff6a35
 	    if(fdhdl != INVALID_HANDLE_VALUE) {
 		logg("*prescan_cb: dumping content to tempfile %s (handle %p)\n", tmpf, fdhdl);
 		break;
 	    }
 	    if((perf = GetLastError()) != ERROR_FILE_EXISTS) {
 		logg("!prescan_cb: failed to create tempfile %s - error %u\n", tmpf, perf);
 		return CL_CLEAN;
 	    }
cf6c69a0
 	}
8038c673
 
1aff6a35
 	fpos = lseek(fd, 0, SEEK_CUR);
 	lseek(fd, 0, SEEK_SET);
 	while((rsz = read(fd, tmpf, sizeof(tmpf))) > 0) {
 	    int wsz = 0;
 	    while(wsz != rsz) {
 		DWORD rwsz;
 		if(!WriteFile(fdhdl, &tmpf[wsz], rsz - wsz, &rwsz, NULL)) {
 		    logg("!prescan_cb: failed to write to tempfile %s - error %u\n", GetLastError());
 		    lseek(fd, fpos, SEEK_SET);
 		    CloseHandle(fdhdl);
 		    return CL_CLEAN;
 		}
 		wsz += rwsz;
8038c673
 	    }
cf6c69a0
 	}
1aff6a35
 	if(rsz) {
 	    logg("!prescan_cb: failed to read from clamav tempfile - errno = %d\n", errno);
 	    lseek(fd, fpos, SEEK_SET);
 	    CloseHandle(fdhdl);
 	    return CL_CLEAN;
 	}
cf6c69a0
 	lseek(fd, fpos, SEEK_SET);
1aff6a35
 	SetFilePointer(fdhdl, 0, NULL, FILE_BEGIN);
 	si.object = fdhdl;
 	si.objectId = (HANDLE)_get_osfhandle(fd);
cefb07dc
 	perf2 = GetTickCount() - perf2;
6b4faa63
 	sctx->copy_times += perf2;
1aff6a35
     } else { /* SCAN_PHASE_INITIAL */
 	si.object = INVALID_HANDLE_VALUE;
 	si.objectId = INVALID_HANDLE_VALUE;
8038c673
     }
f675a85d
     logg("*prescan_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", context, inst, inst->scancb, inst->scancb_ctx);
c8baa1b9
     perf = GetTickCount();
aa7380df
     inst->scancb(&si, &act, inst->scancb_ctx);
c8baa1b9
     perf = GetTickCount() - perf;
593e71c0
     sctx->cb_times += perf;
cefb07dc
     logg("*prescan_cb (clamav context %p, instance %p) callback completed with %u in %u + %u ms\n", context, inst, act, perf, perf2);
aa7380df
     switch(act) {
 	case CLAM_ACTION_SKIP:
f675a85d
 	    logg("*prescan_cb (clamav context %p, instance %p) cb result: SKIP\n", context, inst);
aa7380df
 	    return CL_BREAK;
 	case CLAM_ACTION_ABORT:
f675a85d
 	    logg("*prescan_cb (clamav context %p, instance %p) cb result: ABORT\n", context, inst);
aa7380df
 	    return CL_VIRUS;
 	case CLAM_ACTION_CONTINUE:
f675a85d
 	    logg("*prescan_cb (clamav context %p, instance %p) cb result: CONTINUE\n", context, inst);
c8baa1b9
 	    return CL_CLEAN;
 	default:
 	    logg("^prescan_cb (clamav context %p, instance %p) cb result: INVALID result %d, assuming continue\n", context, inst, act);
aa7380df
 	    return CL_CLEAN;
     }
 }
 
 cl_error_t postscan_cb(int fd, int result, const char *virname, void *context) {
     struct scan_ctx *sctx = (struct scan_ctx *)context;
c8baa1b9
     instance *inst;
aa7380df
     CLAM_SCAN_INFO si;
     CLAM_ACTION act;
c8baa1b9
     DWORD perf;
8aacea51
     wchar_t wvirname[MAX_VIRNAME_LEN] = L"Clam.";
aa7380df
 
c8baa1b9
     if(!context) {
 	logg("!postscan_cb called with NULL clamav context\n");
 	return CL_CLEAN;
     }
4d08b6d6
     inst = sctx->inst;
     if(inst && inst->filetype)
 	return CL_CLEAN; /* No callback, we are just filetyping */
 
c31cb63d
     if(fd == sctx->entryfd)
 	return CL_CLEAN; /* Moved to after cl_scandesc returns due to heuristic results not being yet set in magicscan */
 
aa7380df
     si.cbSize = sizeof(si);
     si.flags = 0;
c31cb63d
     si.scanPhase = SCAN_PHASE_POSTSCAN;
aa7380df
     si.errorCode = CLAMAPI_SUCCESS;
d3c908c7
     if(result == CL_VIRUS) {
8aacea51
 	if(MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, virname, -1, &wvirname[5], MAX_VIRNAME_LEN-5))
d3c908c7
 	    si.pThreatName = wvirname;
 	else
b4053863
 	    si.pThreatName = L"Clam.UNOFFICIAL";
d3c908c7
     } else
 	    si.pThreatName = NULL;
f675a85d
     logg("*in postscan_cb with clamav context %p, instance %p, fd %d, result %d, virusname %S)\n", context, inst, fd, result, si.pThreatName);
d3c908c7
     si.pThreatType = threat_type(virname);
8038c673
     si.objectId = (HANDLE)_get_osfhandle(fd);
     si.object = INVALID_HANDLE_VALUE;
aa7380df
     si.pInnerObjectPath = NULL;
f675a85d
     logg("*postscan_cb (clamav context %p, instance %p) invoking callback %p with context %p\n", context, inst, inst->scancb, inst->scancb_ctx);
c8baa1b9
     perf = GetTickCount();
aa7380df
     inst->scancb(&si, &act, inst->scancb_ctx);
c8baa1b9
     perf = GetTickCount() - perf;
593e71c0
     sctx->cb_times += perf;
f675a85d
     logg("*postscan_cb (clamav context %p, instance %p) callback completed with %u in %u ms\n", context, inst, act, perf);
aa7380df
     switch(act) {
 	case CLAM_ACTION_SKIP:
f675a85d
 	    logg("*postscan_cb (clamav context %p, instance %p) cb result: SKIP\n", context, inst);
aa7380df
 	    return CL_BREAK;
 	case CLAM_ACTION_ABORT:
f675a85d
 	    logg("*postscan_cb (clamav context %p, instance %p) cb result: ABORT\n", context, inst);
aa7380df
 	    return CL_VIRUS;
 	case CLAM_ACTION_CONTINUE:
f675a85d
 	    logg("*postscan_cb (clamav context %p, instance %p) cb result: CONTINUE\n", context, inst);
c8baa1b9
 	    return CL_CLEAN;
 	default:
 	    logg("^postscan_cb (clamav context %p, instance %p) cb result: INVALID result %d, assuming continue\n", context, inst, act);
aa7380df
 	    return CL_CLEAN;
     }
 }
8f415c86
 
ce048a0a
 CLAMAPI void Scan_ReloadDatabase(BOOL bLoadMinDefs) {
f26ba7fa
     if(InterlockedIncrement(&reload_waiters)==1) {
 	int reload_ok = 0;
f675a85d
 	logg("*Scan_ReloadDatabase: Database reload requested received, waiting for idle state\n");
f26ba7fa
 	while(1) {
219e22e6
 	    unsigned int i;
f2d1cf00
 	    struct cl_settings *settings;
eebe17b2
 
f26ba7fa
 	    if(WaitForSingleObject(reload_event, INFINITE) == WAIT_FAILED) {
219e22e6
 		logg("!Scan_ReloadDatabase: failed to wait on reload event\n");
f26ba7fa
 		continue;
 	    }
f675a85d
 	    logg("*Scan_ReloadDatabase: Now idle, acquiring engine lock\n");
f26ba7fa
 	    if(lock_engine()) {
219e22e6
 		logg("!Scan_ReloadDatabase: failed to lock engine\n");
f26ba7fa
 		break;
 	    }
 	    if(!engine) {
219e22e6
 		logg("!Scan_ReloadDatabase: engine is NULL\n");
f26ba7fa
 		unlock_engine();
 		break;
 	    }
f675a85d
 	    logg("*Scan_ReloadDatabase: Engine locked, acquiring instance lock\n");
f26ba7fa
 	    if(lock_instances()) {
219e22e6
 		logg("!Scan_ReloadDatabase: failed to lock instances\n");
f26ba7fa
 		unlock_engine();
 		break;
 	    }
219e22e6
             for(i=0; i<ninsts_total; i++) {
 		if(instances[i].inst && instances[i].refcnt)
 		    break;
 	    }
 	    if(i!=ninsts_total) {
 		logg("Scan_ScanObjectByHandle: some instances are still in use\n");
 		ResetEvent(reload_event);
f26ba7fa
 		unlock_instances();
219e22e6
 		unlock_engine();
f26ba7fa
 		continue;
 	    }
eebe17b2
 	    settings = cl_engine_settings_copy(engine);
 	    if (!settings) {
 		logg("!Scan_ReloadDatabase: Not enough memory for engine settings\n");
 		unlock_instances();
 		unlock_engine();
 		break;
 	    }
 
1a401901
 	    logg("Scan_ReloadDatabase: Destroying old engine\n");
f26ba7fa
 	    cl_engine_free(engine);
ce048a0a
 	    minimal_definitions = bLoadMinDefs;
1a401901
 	    logg("Scan_ReloadDatabase: Loading new engine\n");
219e22e6
 
 	    // NEW STUFF //
 	    if(!(engine = cl_engine_new())) {
 		logg("!Scan_ReloadDatabase: Not enough memory for a new engine\n");
 		unlock_instances();
 		unlock_engine();
 		break;
 	    }
eebe17b2
 	    cl_engine_settings_apply(engine, settings);
 	    cl_engine_settings_free(settings);
219e22e6
 
f26ba7fa
 	    load_db(); /* FIXME: FIAL? */
 	    unlock_instances();
219e22e6
 	    unlock_engine();
f26ba7fa
 	    reload_ok = 1;
 	    break;
 	}
 	if(reload_ok)
1a401901
 	    logg("Scan_ReloadDatabase: Database successfully reloaded\n");
f26ba7fa
 	else
219e22e6
 	    logg("!Scan_ReloadDatabase: Database reload failed\n");
f26ba7fa
     } else
1a401901
 	logg("*Database reload requested received while reload is pending\n");
f26ba7fa
     InterlockedDecrement(&reload_waiters);
8f415c86
 }
1f1b1318
 
 void msg_callback(enum cl_msg severity, const char *fullmsg, const char *msg, void *ctx)
 {
     struct scan_ctx *sctx = (struct scan_ctx*)ctx;
     const void *instance = sctx ? sctx->inst : NULL;
     int fd = sctx ? sctx->entryfd : -1;
     char sv;
     switch (severity) {
 	case CL_MSG_ERROR:
 	    sv = '!';
 	    break;
 	case CL_MSG_WARN:
 	    sv = '^';
 	    break;
 	default:
 	    sv = '*';
 	    break;
     }
 
     logg("%c[LibClamAV] (instance %p, clamav context %p, fd %d): %s",
 	 sv, instance, sctx, fd, msg);
 }