ODBC SQLDriverConnect Crashes – Additional Info

The purpose of this post is to provide additional information regarding an issue that I am experiencing with an application written in C / C++ that can also run as a service. This is legacy code and has been operating for almost two decades. The software can be invoked to run interactively or as a service. The code just stopped to work when invokes as a service about two months ago. I tried to replicate it on my development system and on a new computer system. The issue is that the software runs when invoked as an application but fails to start when started as a service.

02/28/20 14:37:46 0x00001b18 - SDM <<< starting line: 313 ...
02/28/20 14:37:46 0x00001b18 - SDM <<< _WIN32 defined line: 397
02/28/20 14:37:46 0x00001b18 - SDM <<< _WIN64 NOT defined line: 405
02/28/20 14:37:46 0x00001b18 - SDM <<< bitFileTrailers: 1 (bool) controlled by NoBitFileTrailers line: 594
02/28/20 14:37:46 0x00001b18 - SDM <<< deDup: 0 (bool) controlled by DeDup line: 633
02/28/20 14:37:46 0x00001b18 - SDM <<< sdmServerIP ==><== controlled by SDMServerIP (blank to DISABLE failover) line: 672
02/28/20 14:37:46 0x00001b18 - SDM <<< sdmServerPort: 4444 controlled by SDMServerPort line: 718
02/28/20 14:37:46 0x00001b18 - SDM <<< sdmMyIP ==><== controlled by SDMMyIP line: 757
02/28/20 14:37:46 0x00001b18 - SDM <<< sdmStandByIP ==><== controlled by SDMStandByIP (blank to DISABLE failover) line: 796
02/28/20 14:37:46 0x00001b18 - SDM <<< iCAS NOT configured for failover line: 809
02/28/20 14:37:46 0x00001b18 - SDM <<<   debugFlag: 0 (bool) line: 812
02/28/20 14:37:46 0x00001b18 - SDM <<<  extendFlag: 0 line: 813
02/28/20 14:37:46 0x00001b18 - SDM <<< recoverFlag: 0 (bool) line: 814
02/28/20 14:37:46 0x00001b18 - SDM <<<  reloadFlag: 0 (bool) line: 815
02/28/20 14:37:46 0x00001b18 - SDM <<< BEFORE CompareSoftwareVersions line: 820 ...
02/28/20 14:37:46 0x00001b18 - LicDongleVersionAndBuild <<< LicReadDongle WAR_NO_DONGLE line: 8749
02/28/20 14:37:46 0x00001b18 - CompareSoftwareVersions <<< LicDongleVersionAndBuild WAR_NO_DONGLE line: 37525
02/28/20 14:37:46 0x00001b18 - SDM <<<  AFTER CompareSoftwareVersions line: 828
02/28/20 14:37:46 0x00001b18 - SDM <<< CompareSoftwareVersions WAR_NO_DONGLE line: 842
02/28/20 14:37:46 0x00001b18 - SDM <<< BEFORE GetSDMPath line: 868 ...
02/28/20 14:37:46 0x00001b18 - SDM <<< AFTER GetSDMPath status: 0 sdmInstallationPath ==>c:\sdm\bin<== line: 876
02/28/20 14:37:46 0x00001b18 - SDM <<< BEFORE SetCurrentDirectory sdmInstallationPath ==>c:\sdm\bin<== line: 916 ...
02/28/20 14:37:46 0x00001b18 - SDM <<< AFTER SetCurrentDirectory status: 1 sdmInstallationPath ==>c:\sdm\bin<== line: 924
02/28/20 14:37:46 0x00001b18 - SDM <<< BEFORE SDMDatabaseAvailable customerString ==><== line: 941 ...
02/28/20 14:37:46 0x00001b18 - ServerReadConfig <<< serverConfig->sqlServer ==>localhost<== line: 2789
02/28/20 14:37:46 0x00001b18 - SDBOpen <<<        sizeof(SQLLEN): 4 line: 264
02/28/20 14:37:46 0x00001b18 - SDBOpen <<<       sizeof(SQLULEN): 4 line: 265
02/28/20 14:37:46 0x00001b18 - SDBOpen <<<          sizeof(long): 4 line: 266
02/28/20 14:37:46 0x00001b18 - SDBOpen <<< sizeof(unsigned long): 4 line: 267
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< sqlPassword ==>sdmsql<== line: 780
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE calloc statement line: 795 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER calloc statement line: 805
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE calloc dbProc line: 826 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER calloc dbProc line: 836
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_ENV line: 855 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_ENV status: 0 line: 866
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_DBC line: 891 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_DBC status: 0 line: 902
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 963
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLDrivers line: 969 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER SQLDrivers status: 0 line: 986
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< driverFound: 1 (bool) description ==>ODBC Driver 17 for SQL Server<== odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 1019
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< driverAttributes ==>UsageCount=1<== line: 1072
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT SDB_LOGIN_TIMEOUT: 5 sec line: 1081 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT status: 0 line: 1093
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT SDB_CONNECTION_TIMEOUT: 5 sec line: 1122 ...
02/28/20 14:37:46 0x00001b18 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT status: 0 line: 1134
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< hostName ==>Condor<== line: 1223
02/28/20 14:37:46 0x00001b18 - sdbOpen <<< BEFORE SQLDriverConnect inConnStr ==>Driver=ODBC Driver 17 for SQL Server;server=Condor;database=sdmsql;uid=sdmsql;pwd=xxxxxx;<== line: 1245 ...

The log file is for a run as a service.

Very early in the initialization process we need to access some configuration in a SQL Server database. We use the sdbOpen function. The last line indicates the issue. The ODBC call SQLDriverConnect is issued with the associated connection string. The application seems to crash.

Log Name:      Application
Source:        Application Error
Date:          2/28/2020 2:37:47 PM
Event ID:      1000
Task Category: (100)
Level:         Error
Keywords:      Classic
User:          N/A
Computer:      Condor
Description:
Faulting application name: sdm.exe, version: 0.0.0.0, time stamp: 0x5e597945
Faulting module name: MSVCR80.dll, version: 8.0.50727.9659, time stamp: 0x5c7d6589
Exception code: 0xc0000005
Fault offset: 0x00001637
Faulting process id: 0x3d64
Faulting application start time: 0x01d5ee76ef1cbf9e
Faulting application path: C:\sdm\bin\sdm.exe
Faulting module path: C:\WINDOWS\WinSxS\x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.9659_none_d08cfd96442b25cc\MSVCR80.dll
Report Id: 377408b6-0c0d-43d1-ad9e-3c6d29a08edb
Faulting package full name: 
Faulting package-relative application ID: 
Event Xml:
<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
  <System>
    <Provider Name="Application Error" />
    <EventID Qualifiers="0">1000</EventID>
    <Level>2</Level>
    <Task>100</Task>
    <Keywords>0x80000000000000</Keywords>
    <TimeCreated SystemTime="2020-02-28T20:37:47.150193200Z" />
    <EventRecordID>27513</EventRecordID>
    <Channel>Application</Channel>
    <Computer>Condor</Computer>
    <Security />
  </System>
  <EventData>
    <Data>sdm.exe</Data>
    <Data>0.0.0.0</Data>
    <Data>5e597945</Data>
    <Data>MSVCR80.dll</Data>
    <Data>8.0.50727.9659</Data>
    <Data>5c7d6589</Data>
    <Data>c0000005</Data>
    <Data>00001637</Data>
    <Data>3d64</Data>
    <Data>01d5ee76ef1cbf9e</Data>
    <Data>C:\sdm\bin\sdm.exe</Data>
    <Data>C:\WINDOWS\WinSxS\x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.9659_none_d08cfd96442b25cc\MSVCR80.dll</Data>
    <Data>377408b6-0c0d-43d1-ad9e-3c6d29a08edb</Data>
    <Data>
    </Data>
    <Data>
    </Data>
  </EventData>
</Event>

The application event viewer confirms that the MSVCR80.dll is at fault. The DLL is in a folder that as an administrator I do not have access to it. At this time I am not sure what created such folder. I am in the process of investigating. That said; if I try to change the names of the files in that folder, not even the administrator is allowed. The files appear to be owned by TrustedInstaller.

C:\Windows\WinSxS\x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.9659_none_d08cfd96442b25cc>dir /A
 Volume in drive C is OS
 Volume Serial Number is 26E8-87B0

 Directory of C:\Windows\WinSxS\x86_microsoft.vc80.crt_1fc8b3b9a1e18e3b_8.0.50727.9659_none_d08cfd96442b25cc

03/18/2019  10:44 PM    <DIR>          .
03/18/2019  10:44 PM    <DIR>          ..
03/18/2019  10:44 PM           479,232 msvcm80.dll
03/18/2019  10:44 PM           548,864 msvcp80.dll
03/18/2019  10:44 PM           626,688 msvcr80.dll
               3 File(s)      1,654,784 bytes
               2 Dir(s)  537,156,374,528 bytes free

There are 3 DLLs which for Windows 10 and Visual Studio 2019 Enterprise edition seem to be somewhat old.

__declspec(dllexport) int __stdcall	sdbOpen	(
											ODBC_HANDLE	**dbProc,
											char		*sqlLogin,
											char		*sqlPassword,
											char		*sqlServer,

											char		*databaseName
											)

//	***************************************************************@@
//	- This function opens a handle to the specified database.
//	*****************************************************************

{

static	BOOL	firstPass = (BOOL)(1 == 1);						// first pass flag

static	char	odbcDriver[BUFSIZ];								// ODBC driver

BOOL			driverFound;									// driver found flag

char			computerName[BUFSIZ],							// computer name
				query[BUFSIZ];									// query buffer

int				retVal,											// returned by this function
				status;											// returned by function calls

SQLCHAR			description[BUFSIZ],							// driver description
				driverAttributes[BUFSIZ],						// driver attributes
				hostName[BUFSIZ / 8],							// host name
				messageText[BUFSIZ],							// SQL message text

				serverName[BUFSIZ],								// server name
				sqlState[6];									// SQL state

SQLINTEGER		nativeError;									// native error

SQLSMALLINT		connectionStringOutLength,						// length of connection string out
				descriptionLength,								// length of description string
				driverAttributesLength,							// length of driver attributes string
				messageLength,									// message length
				serverNameLength;

unsigned long	connectionTimeOut,								// connection time out
				length;											// of registry key value

// **** initialization ****
retVal						= 0;								// hope all goes well

connectionStringOutLength	= (SQLSMALLINT)0;					// for starters
connectionTimeOut			= (unsigned long)0L;				// for starters
descriptionLength			= (SQLSMALLINT)0;					// for starters
driverAttributesLength		= (SQLSMALLINT)0;					// for starters

driverFound					= (BOOL)(0 == 1);					// for starters
length						= (unsigned long)0L;				// for starters
messageLength				= (SQLSMALLINT)0;					// for starters
nativeError					= (SQLINTEGER)0;					// for starters

serverNameLength			= (SQLSMALLINT)0;					// for starters

memset((void *)computerName,		(int)0x00, (size_t)sizeof(computerName));
memset((void *)description,			(int)0x00, (size_t)sizeof(description));
memset((void *)driverAttributes,	(int)0x00, (size_t)sizeof(driverAttributes));
memset((void*)hostName,				(int)0x00, (size_t)sizeof(hostName));

memset((void *)messageText,			(int)0x00, (size_t)sizeof(messageText));
memset((void *)query,				(int)0x00, (size_t)sizeof(query));
memset((void *)serverName,			(int)0x00, (size_t)sizeof(serverName));
memset((void *)sqlState,			(int)0x00, (size_t)sizeof(sqlState));

// **** inform the user what went on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< ENTERING line: %d\n",
	__LINE__);

// **** perform first pass initializations ****
if (firstPass)
	{

	// **** get the value from the registry ****
	length = sizeof(traceExecution);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"TraceSncrDBLo",
							REG_DWORD,
							&length,

							(void *)&traceExecution);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND TraceSncrDBLo line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue status: %d TraceSncrDBLo line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** inform the user what is going in ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< traceExecution: %d controlled by TraceSncrDBLo line: %d\n",
		traceExecution, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(fileCountToMigrate);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"MigrationFileCount",
							REG_DWORD,
							&length,
							(void *)&fileCountToMigrate);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND MigrationFileCount line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue status: %d MigrationFileCount line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** validate the range ****
	if (fileCountToMigrate <= 0) fileCountToMigrate = MAX_FILE_COUNT_TO_MIGRATE; else if (fileCountToMigrate > (MAX_FILE_COUNT_TO_MIGRATE * 10))
		fileCountToMigrate = (MAX_FILE_COUNT_TO_MIGRATE * 10);

	// **** inform the user what is going in ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< fileCountToMigrate: %d controlled by MigrationFileCount line: %d\n",
		fileCountToMigrate, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(useSideAOnly);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"UseSideAOnly",
							REG_DWORD,
							&length,
							(void *)&useSideAOnly);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND UseSideAOnly line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue UseSideAOnly status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** inform the user what is going in ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< useSideAOnly: %d (bool) controlled by UseSideAOnly line: %d\n",
		useSideAOnly, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(creationTimeWindow);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"CreationTimeWindow",
							REG_DWORD,
							&length,
							(void *)&creationTimeWindow);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND CreationTimeWindow line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue CreationTimeWindow status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** validate the range ****
	if (creationTimeWindow < 0) creationTimeWindow = 0; else if (creationTimeWindow > (60 * 24))					// one (1) day = 24 hours
		creationTimeWindow = (60 * 24);

	// **** inform the user what is going in ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< creationTimeWindow: %d minutes controlled by CreationTimeWindow line: %d\n",
		creationTimeWindow, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(markAsDamaged);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"DoNotMarkAsDamaged",
							REG_DWORD,
							&length,
							(void *)&markAsDamaged);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND DoNotMarkAsDamaged line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue status: %d DoNotMarkAsDamaged line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** ****
	if (markAsDamaged)
		markAsDamaged = (BOOL)(0 == 1);
	else
		markAsDamaged = (BOOL)(1 == 1);
	
	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< markAsDamaged: %d (bool) controlled by DoNotMarkAsDamaged line: %d\n",
		(int)markAsDamaged, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(tapeFullAccessCount);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"TapeFullAccessCount",
							REG_DWORD,
							&length,
							(void *)&tapeFullAccessCount);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND TapeFullAccessCount line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue TapeFullAccessCount status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< tapeFullAccessCount: %lu controlled by TapeFullAccessCount line: %d\n",
		tapeFullAccessCount, __LINE__);

	// **** clear the name of the ODBC driver ****
	memset((void *)odbcDriver, (int)0x00, (size_t)sizeof(odbcDriver));

	// **** get the name of the ODBC driver (ODBC DSN) ****
	length = (unsigned long)sizeof(odbcDriver);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"ODBCDriverName",
							REG_SZ,
							&length,
							(void *)odbcDriver);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND ODBCDriverName line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue ODBCDriverName status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = WAR_SYSTEM_CONFIGURATION_ERROR;
			goto done;
		break;
		}

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< odbcDriver ==>%s<== controlled by ODBCDriverName line: %d\n",
		odbcDriver, __LINE__);
		
	// **** check for a blank entry ****
	if (*odbcDriver == '\0')
		{

		// **** used default ODBC driver ****
		strcpy(odbcDriver, DEFAULT_ODBC_DRIVER_NAME);

		// **** inform the user what is going on ****
		EventLog(EVENT_INFO,
		"sdbOpen <<< odbcDriver ==>%s<== set to DEFAULT_ODBC_DRIVER_NAME ==>%s<== line: %d\n",
		odbcDriver, DEFAULT_ODBC_DRIVER_NAME, __LINE__);
		}

	// **** get the value from the registry ****
	length = sizeof(migrateAccessDelay);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"MigrateAccessDelay",
							REG_DWORD,
							&length,
							(void *)&migrateAccessDelay);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND MigrateAccessDelay line: %d\n",
			__LINE__);
		break;

		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue MigrateAccessDelay status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** ****
	if (migrateAccessDelay == 0)								// the value has NOT been set
		migrateAccessDelay = MIGRATE_ACCESS_DELAY_DEFAULT;		// use the default (in seconds)

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< migrateAccessDelay: %d seconds controlled by MigrateAccessDelay line: %d\n",
		migrateAccessDelay, __LINE__);

	// **** get the licensed features from the dongle ****
	status = LicDongleGetLicensedFeatures(&licensedFeatures);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;
		
		case WAR_NO_DONGLE:
		case WAR_INVALID_LICENSE:

			// **** ignore this conditions (no dongle is present) ****

		break;

		case WAR_DRIVER_NOT_FOUND:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< LicDongleGetLicensedFeatures WAR_DRIVER_NOT_FOUND line: %d file ==>%s<==\n",
			__LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
			
		default:
			PrintErrorCode(status, __LINE__, __FILE__);
			EventLog(EVENT_ERROR,
			"sdbOpen <<< LicDongleGetLicensedFeatures status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< licensedFeatures: 0x%08lx line: %d\n",
		licensedFeatures, __LINE__);

	// **** get my IP from the proper NIC ****
	status = SockGetMyIP(sdmMyIP);
	CDP_CHECK_STATUS("sdbOpen <<< SockGetMyIP", status);

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< sdmMyIP ==>%s<== controlled by SockGetMyIP() line: %d\n",
		sdmMyIP, __LINE__);

	// **** ****
	length = (unsigned long)sizeof(sdmServerIP);
	status = KeyGetKeyValue(ICAS_REGISTRY_SDM,
							"SDMServerIP",
							REG_SZ,
							&length,
							(void *)sdmServerIP);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND SDMServerIP line: %d\n",
			__LINE__);
		break;

		default:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue SDMServerIP status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = WAR_SYSTEM_CONFIGURATION_ERROR;
			goto done;
		break;
		}

	// **** ****
	if (*sdmServerIP == '\0')
		memset((void *)sdmServerIP, (int)0x00, (size_t)sizeof(sdmServerIP));

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< sdmServerIP ==>%s<== controlled by SDMServerIP line: %d\n",
		sdmServerIP, __LINE__);

	// **** ****

	length = (unsigned long)sizeof(sdmStandbyIP);
	status = KeyGetKeyValue(ICAS_REGISTRY_SDM,
							"SDMStandByIP",	
							REG_SZ,
							&length,
							(void *)sdmStandbyIP);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND SDMStandByIP line: %d\n",
			__LINE__);
		break;

		default:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue SDMStandByIP status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = WAR_SYSTEM_CONFIGURATION_ERROR;
			goto done;
		break;
		}

	// **** ****
	if (*sdmStandbyIP == '\0')									// the value has NOT been set
		memset((void *)sdmStandbyIP, (int)0x00, (size_t)sizeof(sdmStandbyIP));

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< sdmStandbyIP ==>%s<== controlled by SDMStandByIP line: %d\n",
		sdmStandbyIP, __LINE__);

	// **** determine if we are the iCAS master ****
	status = _stricmp(	sdmMyIP,								// will always be defined
						sdmServerIP);							// might be blank ""
	if (status == 0)
		casMaster = (BOOL)(1 == 1);
	else
		casMaster = (BOOL)(0 == 1);

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< casMaster: %d (bool) line: %d\n",
		(int)casMaster, __LINE__);

	// **** get the value from the registry ****
	length = sizeof(deDup);
	status = KeyGetKeyValue(ICAS_REGISTRY_LIBRARY,
							"DeDup",
							REG_DWORD,
							&length,
							(void *)&deDup);

	// **** proceed based on the returned status ****
	switch (status)
		{
		case 0:
		
			// **** all is well so far ****
			
		break;

		case WAR_RECORD_NOT_FOUND:			
			EventLog(EVENT_INFO,
			"sdbOpen <<< KeyGetKeyValue WAR_RECORD_NOT_FOUND DeDup line: %d\n",
			__LINE__);
		break;

		default:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< KeyGetKeyValue DeDup status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = status;
			goto done;
		break;
		}

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO, 
		"sdbOpen <<< deDup: %d (bool) controlled by DeDup line: %d\n",
		(int)deDup, __LINE__);

	// **** flag that we executed the first pass of this function ****
	firstPass = (BOOL)(0 == 1);
	}

#ifdef _SENCOR_DEBUG
int	traceExecution = 1;											// for testing purspose only
#endif

// **** perform some sanity checks ****
if (dbProc == (ODBC_HANDLE **)NULL)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED dbProc == NULL line: %d file ==>%s<==\n",
	__LINE__, __FILE__);
	retVal = WAR_INVALID_ARGUMENT;
	goto done;
	}
*dbProc = (ODBC_HANDLE *)NULL;

if (sqlLogin == (char *)NULL)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED sqlLogin == NULL line: %d file ==>%s<==\n",
	__LINE__, __FILE__);
	retVal = WAR_INVALID_ARGUMENT;
	goto done;	
	}

if (sqlPassword == (char *)NULL)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED sqlPassword == NULL line: %d file ==>%s<==\n",
	__LINE__, __FILE__);
	retVal = WAR_INVALID_ARGUMENT;
	goto done;
	}

if (sqlServer == (char *)NULL)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED sqlServer == NULL line: %d file ==>%s<==\n",
	__LINE__, __FILE__);
	retVal = WAR_INVALID_ARGUMENT;
	goto done;
	}

if (*sqlServer == '\0')
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED sqlServer ==>%s<== line: %d file ==>%s<==\n",
	sqlServer, __LINE__, __FILE__);
	retVal = WAR_INVALID_ARGUMENT_VALUE;
	goto done;
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	{
	EventLog(EVENT_INFO, "sdbOpen <<< sqlLogin ==>%s<== line: %d\n",	sqlLogin, __LINE__);
	EventLog(EVENT_INFO, "sdbOpen <<< sqlPassword ==>%s<== line: %d\n", sqlPassword, __LINE__); } // **** inform the user what is going on **** if (traceExecution > 1)
	EventLog(EVENT_INFO, "sdbOpen <<< sqlServer ==>%s<== line: %d\n", sqlServer, __LINE__);

// **** check to see if our statement has NOT been allocated yet ****
if (statement == (SQLCHAR *)NULL)
	{
	
	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< BEFORE calloc statement line: %d ...\n",
		__LINE__);

	// **** allocate our statement handle ****
	statement = (SQLCHAR *)calloc(	(size_t)1,
									(size_t)ODBC_STATEMENT_LEN);

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
			"sdbOpen <<<  AFTER calloc statement line: %d\n",
			__LINE__);

	// **** proceed based on the returned value ****
	if (statement == (SQLCHAR *)NULL)
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< calloc ODBC_STATEMENT_LEN: %lu line: %d file ==>%s<==\n",
		ODBC_STATEMENT_LEN, __LINE__, __FILE__);
		retVal = WAR_NO_MORE_MEMORY;
		goto done;
		}
	}

// **** allocate the statement on the other database source file ****
status = odbc2AllocStatement();
CDP_CHECK_STATUS("sdbOpen <<< odbc2AllocStatement", status);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE calloc dbProc line: %d ...\n",
	__LINE__);

// **** allocate the database handle ****
*dbProc = (ODBC_HANDLE *)calloc((size_t)1,
								(size_t)sizeof(ODBC_HANDLE));

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<<  AFTER calloc dbProc line: %d\n",
	__LINE__);

// **** proceed based on the returned value ****
if (*dbProc == (ODBC_HANDLE *)NULL)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< calloc ODBC_HANDLE line: %d file ==>%s<==\n", __LINE__, __FILE__); retVal = WAR_NO_MORE_MEMORY; goto done; } // **** copy the database name **** strcpy((*dbProc)->sqlLogin, sqlLogin);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_ENV line: %d ...\n", __LINE__); // **** allocate environment handle (ODBC 3.0) (SQLAllocEnv has been replaced by SQLAllocHandle) **** status = (int)SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &((*dbProc)->environmentHandle));

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_ENV status: %d line: %d\n",
	status, __LINE__);

// **** proceed based on the returned status 
ODBC_CHECK_STATUS("sdbOpen <<< SQLAllocHandle", WAR_ODBC_CALL_FAILED); // **** set the attribute that govern aspect of environment (ODBC 3.0) **** status = (int)SQLSetEnvAttr((*dbProc)->environmentHandle,
							SQL_ATTR_ODBC_VERSION,
							(SQLPOINTER)SQL_OV_ODBC3,
							SQL_IS_UINTEGER);
ODBC_CHECK_ENVIRONMENT("sdbOpen <<< SQLSetEnvAttr", WAR_ODBC_CALL_FAILED); // **** set the attribute for the connection pooling // A single connection pool is supported for each environment. // Every connection in a pool is associated with one environment. **** status = (int)SQLSetEnvAttr((*dbProc)->environmentHandle,
							SQL_ATTR_CONNECTION_POOLING,
							(SQLPOINTER)SQL_CP_ONE_PER_HENV,
							SQL_IS_INTEGER);
ODBC_CHECK_ENVIRONMENT("sdbOpen <<< SQLSetEnvAttr", WAR_ODBC_CALL_FAILED);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_DBC line: %d ...\n", __LINE__); // **** allocate a connection handle **** status = (int)SQLAllocHandle( SQL_HANDLE_DBC, (*dbProc)->environmentHandle,
								&(*dbProc)->connectionHandle);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_DBC status: %d line: %d\n",
	status, __LINE__);

// **** proceed based on the returned value ****
ODBC_CHECK_STATUS("sdbOpen <<< SQLAllocHandle", WAR_ODBC_CALL_FAILED); // **** figure out the ODBC mode **** if (*odbcDriver == '\0') { (*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen,  ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}
	
else if (_stricmp(odbcDriver, "SQL Server") == 0)
	{
	(*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen,  ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}
	
else if (_stricmp(odbcDriver, "MySQL ODBC 3.51 Driver") == 0)
	{
	(*dbProc)->odbcMode = ODBC_MODE_MYSQL;
	strcpy((*dbProc)->escapeOpen,  ODBC_MYSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MYSQL_NUMERIC_CLOSE);
	}

else if (_stricmp(odbcDriver, "ODBC Driver 11 for SQL Server") == 0)
	{
	(*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen, ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}

else if (_stricmp(odbcDriver, "SQL Server Native Client 11.0") == 0)
	{
	(*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen, ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}

else if (_stricmp(odbcDriver, "ODBC Driver 17 for SQL Server") == 0)
	{
	(*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen, ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}

else
	{

	// **** default to use SQL Server (DatCard is a Microsoft shop) ****
	(*dbProc)->odbcMode = ODBC_MODE_MSSQL;
	strcpy((*dbProc)->escapeOpen, ODBC_MSSQL_NUMERIC_OPEN);
	strcpy((*dbProc)->escapeClose, ODBC_MSSQL_NUMERIC_CLOSE);
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< odbcDriver ==>%s<== line: %d\n",
	odbcDriver, __LINE__);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLDrivers line: %d ...\n", __LINE__); // **** return information about available database drivers **** status = (int)SQLDrivers( (*dbProc)->environmentHandle,		// environment handle
							SQL_FETCH_FIRST,					// start with the begining of the list
							description,						// description
							(SQLSMALLINT)sizeof(description),	// length of description

							&descriptionLength,					// returned length of description
							driverAttributes,					// attributes of driver
							(SQLSMALLINT)sizeof(driverAttributes),
							&driverAttributesLength);			// returned length of driver attributes

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<<  AFTER SQLDrivers status: %d line: %d\n",
	status, __LINE__);

// **** proceed based on the returned status ****
ODBC_CHECK_ENVIRONMENT("sdbOpen <<< SQLDrivers", WAR_ODBC_CALL_FAILED); // **** loop searching for the specified driver **** for ( driverFound = (BOOL)(0 == 1); !driverFound; ) { // **** inform the user what is going on **** if (traceExecution > 1)
		{
		EventLog(EVENT_INFO, "sdbOpen <<< odbcDriver ==>%s<== line: %d\n", odbcDriver, __LINE__);
		EventLog(EVENT_INFO, "sdbOpen <<< description ==>%s<== line: %d\n", description, __LINE__);
		}

	// **** perform a lowercase comparison of the strings ****
	status = _stricmp(	description,
						odbcDriver);

	// **** check if driver was found ****
	if (status == 0)
		{

		// **** flag that the driver was found ****
		driverFound = (BOOL)(1 == 1);

		// **** inform the user what is going on ****
		if (traceExecution != 0)
			EventLog(EVENT_INFO,
			"sdbOpen <<< driverFound: %d (bool) description ==>%s<== odbcDriver ==>%s<== line: %d\n", (int)driverFound, description, odbcDriver, __LINE__); // **** exit the loop **** continue; } // **** inform the user what is going on **** if (traceExecution > 1)
		EventLog(EVENT_INFO,
		"sdbOpen <<< BEFORE SQLDrivers line: %d ...\n", __LINE__); // **** return information about data source (ODBC 1.0) **** memset((void *)description, (int)0x00, (size_t)sizeof(description)); memset((void *)driverAttributes, (int)0x00, (size_t)sizeof(driverAttributes)); status = (int)SQLDrivers( (*dbProc)->environmentHandle,	// environment handle
								SQL_FETCH_NEXT,					// next
								description,					// description
								(SQLSMALLINT)sizeof(description),

								&descriptionLength,				// returned length of description
								driverAttributes,				// attributes of driver
								(SQLSMALLINT)sizeof(driverAttributes),
								&driverAttributesLength);		// returned length of driver attributes

	// **** inform the user what is going on ****
	if (traceExecution > 1)
		EventLog(EVENT_INFO,
		"sdbOpen <<<  AFTER SQLDrivers line: %d\n",
		__LINE__);

	// **** check if we are done with the loop ****
	if (status == SQL_NO_DATA)									// 100
		break;													// done with the loop

	// **** check for errors ****
	ODBC_CHECK_ENVIRONMENT("sdbOpen <<< SQLDrivers", WAR_ODBC_CALL_FAILED);
	}

// **** determine if the driver was NOT found ****
if (!driverFound)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< UNEXPECTED driverFound: %d (bool) odbcDriver ==>%s<== NOT found line: %d file ==>%s<==\n",
	(int)driverFound, odbcDriver, __LINE__, __FILE__);
	retVal = WAR_SYSTEM_CONFIGURATION_ERROR;
	goto done;
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< driverAttributes ==>%s<== line: %d\n",
	driverAttributes, __LINE__);

// **** set the login timeout ****
connectionTimeOut = SDB_LOGIN_TIMEOUT;							// in seconds

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT SDB_LOGIN_TIMEOUT: %ld sec line: %d ...\n", SDB_LOGIN_TIMEOUT, __LINE__); // **** set the login timeout **** status = (int)SQLSetConnectAttr((*dbProc)->connectionHandle,	// connection handle
								SQL_ATTR_LOGIN_TIMEOUT,			// attribute
								&connectionTimeOut,				// value (in seconds)
								sizeof(connectionTimeOut));		// string length

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< AFTER SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT status: %d line: %d\n", status, __LINE__); // **** proceed based on the returned status **** if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { SQLGetDiagRec( SQL_HANDLE_DBC, (*dbProc)->connectionHandle,
					1,
					sqlState,

					&nativeError,
					messageText,
					sizeof(messageText),
					&messageLength);
	EventLog(EVENT_ERROR,
	"sdbOpen <<< SQLSetConnectAttr status: %d nativeError: %ld messageText ==>%s<== line: %d file ==>%s<==\n",
	status, nativeError, messageText, __LINE__, __FILE__);
	retVal = WAR_ODBC_CALL_FAILED;
	goto done;
	}

// **** set the connecion timeout ****
connectionTimeOut = SDB_CONNECTION_TIMEOUT;						// in seconds

// **** inform the user what is going on ****
if (traceExecution != 0)
EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT SDB_CONNECTION_TIMEOUT: %ld sec line: %d ...\n", SDB_LOGIN_TIMEOUT, __LINE__); // **** set the connecion timeout **** status = (int)SQLSetConnectAttr((*dbProc)->connectionHandle,	// connection handle
								SQL_ATTR_CONNECTION_TIMEOUT,	// attribute
								&connectionTimeOut,				// value (in seconds)
								sizeof(connectionTimeOut));		// string length

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< AFTER SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT status: %d line: %d\n", status, __LINE__); /// **** proceed based on the returned status **** if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { SQLGetDiagRec( SQL_HANDLE_DBC, (*dbProc)->connectionHandle,
					1,
					sqlState,

					&nativeError,
					messageText,
					sizeof(messageText),
					&messageLength);
	EventLog(EVENT_ERROR,
	"sdbOpen <<< SQLSetConnectAttr status: %d nativeError: %ld messageText ==>%s<== line: %d file ==>%s<==\n", status, nativeError, messageText, __LINE__, __FILE__); retVal = WAR_ODBC_CALL_FAILED; goto done; } //// **** establish a connection to the ODBC driver and data source **** //__try //{ //status = (int)SQLConnect( (*dbProc)->connectionHandle,			// connection handle
//
//							sqlLogin,								// server name
//							(SQLSMALLINT)strlen(sqlLogin),			// server name length
//
//							sqlLogin,								// user name
//							(SQLSMALLINT)strlen(sqlLogin),			// user name length
//
//							sqlPassword,							// authentication
//							(SQLSMALLINT)strlen(sqlPassword));		// authentication length
//}
//__except (EXCEPTION_EXECUTE_HANDLER)
//{
//	retVal = WAR_EXCEPTION_CAUGHT;
//	PrintErrorCode(retVal, __LINE__, __FILE__);
//	goto done;
//}


// **** ****
char			inConnStr[BUFSIZ * 2];

char			outConnStr[BUFSIZ * 2];

unsigned short	outConnStrLen = 0;

memset((void *)inConnStr,	(int)0x00, (size_t)sizeof(inConnStr));
memset((void *)outConnStr,	(int)0x00, (size_t)sizeof(outConnStr));

// **** retrieve the standard host name for the local computer ****
status = gethostname(	hostName,
						sizeof(hostName));
if (status != 0)
	{

	// **** ****
	status = WSAGetLastError();

	// **** proceed based on the returned status ****
	switch (status)
		{
		case WSANOTINITIALISED:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< gethostname WSANOTINITIALISED line: %d file ==>%s<==\n",
			__LINE__, __FILE__);
			retVal = WAR_SOCKET_SYSTEM_NOT_INIT;
			goto done;
		break;

		default:
			EventLog(EVENT_ERROR,
			"sdbOpen <<< gethostname status: %d line: %d file ==>%s<==\n",
			status, __LINE__, __FILE__);
			retVal = WAR_SYSTEM_CALL_FAILED;
			goto done;
		break;
		}
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< hostName ==>%s<== line: %d\n",
	hostName, __LINE__);

// **** generate the ODBC connection string ****
status = sprintf(	inConnStr,
					//"Driver=%s;server=localhost;database=sdmsql;trusted_connection=Yes;",
					"Driver=%s;server=%s;database=sdmsql;uid=sdmsql;pwd=sdmsql;",
					odbcDriver, hostName);

// **** determine if something went wrong ****
if (status <= 0)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< sprintf status: %d odbcDriver ==>%s<== hostName ==>%s<== line: %d file ==>%s<==\n",
	status, odbcDriver, hostName, __LINE__, __FILE__);
	retVal = WAR_INTERNAL_ERROR;
	goto done;
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE SQLDriverConnect inConnStr ==>%s<== line: %d ...\n", inConnStr, __LINE__); // **** establish a connection to the ODBC driver and data source **** __try { status = (int)SQLDriverConnect( (*dbProc)->connectionHandle,	// connection handle
									(SQLHWND)NULL,					// windows handle

									inConnStr,						// in connection string
									(SQLSMALLINT)strlen(inConnStr),	// length of in connection string

									outConnStr,						// out connection string
									(SQLSMALLINT)sizeof(outConnStr),// length of out connection string

									&outConnStrLen,					// length of out connection string
									SQL_DRIVER_NOPROMPT);			// driver completion flag
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
	retVal = WAR_EXCEPTION_CAUGHT;
	PrintErrorCode(retVal, __LINE__, __FILE__);
	goto done;
}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< AFTER SQLDriverConnect status: %d outConnStrLen: %hd outConnStr ==>%s<== line: %d\n", status, outConnStrLen, outConnStr, __LINE__); // **** proceed based on the returned status **** if ((status != SQL_SUCCESS) && (status != SQL_SUCCESS_WITH_INFO)) { // **** **** SQLGetDiagRec( SQL_HANDLE_DBC, (*dbProc)->connectionHandle,
					1,
					sqlState,

					&nativeError,
					messageText,
					sizeof(messageText),
					&messageLength);

	// **** ****
	if ((strcmp(sqlState, "42000") == 0) ||
		(strcmp(sqlState, "HY000") == 0))
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< SQLConnect status: %d sqlState ==>%s<== nativeError: %ld sqlServer ==>%s<== sqlLogin ==>%s<== line: %d file ==>%s<==\n",
		status, sqlState, nativeError, sqlServer, sqlLogin, __LINE__, __FILE__);
		retVal = WAR_DATABASE_ACCESS_FAILED;
		}

	// **** ****
	else if (strcmp(sqlState, "28000") == 0)
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< SQLConnect sqlState ==>%s<== sqlServer ==>%s<== sqlLogin ==>%s<== line: %d file ==>%s<==\n",
		sqlState, sqlServer, sqlLogin, __LINE__, __FILE__);
		retVal = WAR_LOGIN_FAILED;
		PrintErrorCode(retVal, __LINE__, __FILE__);
		}

	// **** ****
	else if (strcmp(sqlState, "IM002") == 0)
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< SQLConnect sqlState ==>%s<== odbcDriver ==>%s<== sqlServer ==>%s<== sqlLogin ==>%s<== line: %d file ==>%s<==\n",
		sqlState, odbcDriver, sqlServer, sqlLogin, __LINE__, __FILE__);
		retVal = WAR_MISSING_DATA_SOURCE;
		}

	// **** ****
	else if (strcmp(sqlState, "IM014") == 0)
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< SQLConnect sqlState ==>%s<== odbcDriver ==>%s<== sqlServer ==>%s<== sqlLogin ==>%s<== line: %d file ==>%s<==\n",
		sqlState, odbcDriver, sqlServer, sqlLogin, __LINE__, __FILE__);
		retVal = WAR_DSN_ARCHITECTURE_MISMATCH;
		}

	// **** ****
	else
		{
		EventLog(EVENT_ERROR,
		"sdbOpen <<< SQLConnect status: %d sqlState ==>%s<== nativeError: %ld messageText ==>%s<== odbcDriver ==>%s<== sqlServer ==>%s<== sqlLogin ==>%s<== line: %d file ==>%s<==\n",
		status, sqlState, nativeError, messageText, odbcDriver, sqlServer, sqlLogin, __LINE__, __FILE__);
		retVal = WAR_ODBC_CALL_FAILED;
		PrintErrorCode(retVal, __LINE__, __FILE__);
		}
	
	// **** nothing else to do ****
	goto done;
	}
// **** generate the use database query ****
if ((databaseName  != (char *)NULL) &&
	(*databaseName != '\0'))
	status = sprintf(	query,
						"use %s;--",
						databaseName);
else
	status = sprintf(	query,
						"use % s;--",
						sqlLogin);
if (status <= 0)
	{
	EventLog(EVENT_ERROR,
	"sdbOpen <<< sprintf line: %d file ==>%s<==\n",
	__LINE__, __FILE__);
	retVal = WAR_INTERNAL_ERROR;
	goto done;
	}

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<< BEFORE sdbQueryNoResults query ==>%s<== line: %d ...\n",
	query, __LINE__);

// **** issue the database query ****
status = sdbQueryNoResults(	*dbProc,
							query);

// **** inform the user what is going on ****
if (traceExecution != 0)
	EventLog(EVENT_INFO,
	"sdbOpen <<<  AFTER sdbQueryNoResults status: %d line: %d\n",
	status, __LINE__);

// **** proceed based on th ereturned status ****
switch (status)
	{
	case 0:

		// **** all is well so far ****

	break;

	default:
		EventLog(EVENT_ERROR,
		"sdbOpen <<< sdbQueryNoResults status: %d query ==>%s<== line: %d file ==>%s<==\n",
		status, query, __LINE__, __FILE__);
		retVal = status;
		goto done;
	break;
	}

// **** clean up ****
done:

// **** close the database handles (if needed) ****
if ((retVal  != 0) &&
	(dbProc  != (ODBC_HANDLE **)NULL) &&
	(*dbProc != (ODBC_HANDLE *)NULL))
	{

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< BEFORE sdbClose line: %d ...\n",
		__LINE__);

	// **** close the database ****
	status = sdbClose(dbProc);

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<<  AFTER sdbClose status: %d line: %d\n",
		__LINE__);

	// **** proceed based on the returned status ****
	if ((status         != 0) &&
		(traceExecution != 0))
		EventLog(EVENT_ERROR,
		"sdbOpen <<< sdbClose status: %d line: %d file ==>%s<==\n",
		status, __LINE__, __FILE__);

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<< BEFORE free dbProc line: %d ...\n",
		__LINE__);

	// **** free the database handle ****
	free((void *)*dbProc);
	*dbProc = (ODBC_HANDLE *)NULL;

	// **** inform the user what is going on ****
	if (traceExecution != 0)
		EventLog(EVENT_INFO,
		"sdbOpen <<<  AFTER free dbProc line: %d\n",
		__LINE__);
	}

// **** inform the user what is going on ****
if ((traceExecution != 0) || (retVal != 0))
	EventLog(EVENT_INFO,
	"sdbOpen <<< retVal: %d line: %d file ==>%s<==\n",
	retVal, __LINE__, __FILE__);

// **** inform the caller what went on ****
return retVal;
}

This is the actual function. You can associate the log file to this source code.

 

Please note that is the code is started as an application it will come up. I can then start tests that send to the storage server (the app in questions) hundreds of thousands of files. All works as expected.

1. C:\Temp\Microsoft Visual C++ Redistributable for Visual Studio 2019\x64\VC_redist.x64.exe

2. C:\Temp\en_sql_server_2014_express_with_service_pack_3_x64_fbd21895.exe

3. C:\Temp\en_sql_server_2014_express_with_service_pack_3_x64_fbd21895\SETUP.EXE
	Microsoft SQL Server 2014 (SP3) (KB4022619) - 12.0.6024.0 (X64)   Sep  7 2018 01:37:51   Copyright (c) Microsoft Corporation  Express Edition (64-bit) on Windows NT 6.3 <X64> (Build 18363: ) 

4. Download SQL Server Management Studio (SSMS)
	C:\Temp\SSMS-Setup-ENU.exe
	
5. C:\Temp\iCAS_20200228.exe

6. C:\databases

7. C:\sdm\db\create.sql

8. C:\Windows\SysWOW64\odbcad32.exe
	ODBC Driver 17 for SQL Server

The list indicates the steps that I took in a Windows machine after removing ODBC drivers, redistributable run time libraries and other things. Please note that between steps I restarted the computer to make sure things were taking effect.

The SQL Server is 2014 64-bit. The application has been built in release and runs in 32-bit.

The iCAS_20200228.exe was used to make available the six folders that contain the software.

After I had all the steps completed and the database installed I created a data source.

I must have missed something because the issue was not resolved. The software runs as an application but crashes when accessing the database via the ODBC driver.

02/28/20 14:54:28 0x00003860 - SDM <<< starting line: 313 ...
02/28/20 14:54:28 0x00003860 - SDM <<< _WIN32 defined line: 397
02/28/20 14:54:28 0x00003860 - SDM <<< _WIN64 NOT defined line: 405
02/28/20 14:54:28 0x00003860 - SDM <<< bitFileTrailers: 1 (bool) controlled by NoBitFileTrailers line: 594
02/28/20 14:54:28 0x00003860 - SDM <<< deDup: 0 (bool) controlled by DeDup line: 633
02/28/20 14:54:28 0x00003860 - SDM <<< sdmServerIP ==><== controlled by SDMServerIP (blank to DISABLE failover) line: 672
02/28/20 14:54:28 0x00003860 - SDM <<< sdmServerPort: 4444 controlled by SDMServerPort line: 718
02/28/20 14:54:28 0x00003860 - SDM <<< sdmMyIP ==><== controlled by SDMMyIP line: 757
02/28/20 14:54:28 0x00003860 - SDM <<< sdmStandByIP ==><== controlled by SDMStandByIP (blank to DISABLE failover) line: 796
02/28/20 14:54:28 0x00003860 - SDM <<< iCAS NOT configured for failover line: 809
02/28/20 14:54:28 0x00003860 - SDM <<<   debugFlag: 1 (bool) line: 812
02/28/20 14:54:28 0x00003860 - SDM <<<  extendFlag: 0 line: 813
02/28/20 14:54:28 0x00003860 - SDM <<< recoverFlag: 0 (bool) line: 814
02/28/20 14:54:28 0x00003860 - SDM <<<  reloadFlag: 0 (bool) line: 815
02/28/20 14:54:28 0x00003860 - SDM <<< BEFORE CompareSoftwareVersions line: 820 ...
02/28/20 14:54:28 0x00003860 - LicDongleVersionAndBuild <<< LicReadDongle WAR_NO_DONGLE line: 8749
02/28/20 14:54:28 0x00003860 - CompareSoftwareVersions <<< LicDongleVersionAndBuild WAR_NO_DONGLE line: 37525
02/28/20 14:54:28 0x00003860 - SDM <<<  AFTER CompareSoftwareVersions line: 828
02/28/20 14:54:28 0x00003860 - SDM <<< CompareSoftwareVersions WAR_NO_DONGLE line: 842
02/28/20 14:54:28 0x00003860 - SDM <<< BEFORE GetSDMPath line: 868 ...
02/28/20 14:54:28 0x00003860 - SDM <<< AFTER GetSDMPath status: 0 sdmInstallationPath ==>c:\sdm\bin<== line: 876
02/28/20 14:54:28 0x00003860 - SDM <<< BEFORE SetCurrentDirectory sdmInstallationPath ==>c:\sdm\bin<== line: 916 ...
02/28/20 14:54:28 0x00003860 - SDM <<< AFTER SetCurrentDirectory status: 1 sdmInstallationPath ==>c:\sdm\bin<== line: 924
02/28/20 14:54:28 0x00003860 - SDM <<< BEFORE SDMDatabaseAvailable customerString ==><== line: 941 ...
02/28/20 14:54:28 0x00003860 - ServerReadConfig <<< serverConfig->sqlServer ==>localhost<== line: 2789
02/28/20 14:54:28 0x00003860 - SDBOpen <<<        sizeof(SQLLEN): 4 line: 264
02/28/20 14:54:28 0x00003860 - SDBOpen <<<       sizeof(SQLULEN): 4 line: 265
02/28/20 14:54:28 0x00003860 - SDBOpen <<<          sizeof(long): 4 line: 266
02/28/20 14:54:28 0x00003860 - SDBOpen <<< sizeof(unsigned long): 4 line: 267
02/28/20 14:54:28 0x00003860 - sdbOpen <<< sqlLogin ==>sdmsql<== line: 779
02/28/20 14:54:28 0x00003860 - sdbOpen <<< sqlPassword ==>sdmsql<== line: 780
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE calloc statement line: 795 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER calloc statement line: 805
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE calloc dbProc line: 826 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER calloc dbProc line: 836
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_ENV line: 855 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_ENV status: 0 line: 866
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_DBC line: 891 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_DBC status: 0 line: 902
02/28/20 14:54:28 0x00003860 - sdbOpen <<< odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 963
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLDrivers line: 969 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER SQLDrivers status: 0 line: 986
02/28/20 14:54:28 0x00003860 - sdbOpen <<< driverFound: 1 (bool) description ==>ODBC Driver 17 for SQL Server<== odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 1019
02/28/20 14:54:28 0x00003860 - sdbOpen <<< driverAttributes ==>UsageCount=1<== line: 1072
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT SDB_LOGIN_TIMEOUT: 5 sec line: 1081 ...

^^^^ ^^^^ ^^^^ When running as a service the software dies here !!!
vvvv vvvv vvvv Now that it is running as an application it continues ...

02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT status: 0 line: 1093
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT SDB_CONNECTION_TIMEOUT: 5 sec line: 1122 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT status: 0 line: 1134
02/28/20 14:54:28 0x00003860 - sdbOpen <<< hostName ==>Condor<== line: 1223
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE SQLDriverConnect inConnStr ==>Driver=ODBC Driver 17 for SQL Server;server=Condor;database=sdmsql;uid=sdmsql;pwd=sdmsql;<== line: 1245 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<< AFTER SQLDriverConnect status: 1 outConnStrLen: 101 outConnStr ==>DRIVER=ODBC Driver 17 for SQL Server;SERVER=Condor;UID=sdmsql;PWD=sdmsql;WSID=CONDOR;DATABASE=sdmsql;<== line: 1273
02/28/20 14:54:28 0x00003860 - sdbOpen <<< BEFORE sdbQueryNoResults query ==>use sdmsql;--<== line: 1365 ...
02/28/20 14:54:28 0x00003860 - sdbOpen <<<  AFTER sdbQueryNoResults status: 0 line: 1375
02/28/20 14:54:28 0x00003860 - sdbOpen <<< retVal: 0 line: 1447 file ==>C:\SencorSource\source\sncrodbclo.c<==
02/28/20 14:54:28 0x00003860 - SDMDatabaseAvailable <<< LicCheckLicenseFile exists: 0 (bool) licenseString ==>ACFD-BCE2-0B63-D92E-0A19-4F93-56FC-EB70<== line: 10473
02/28/20 14:54:28 0x00003860 - SDM <<< AFTER SDMDatabaseAvailable status: 0 customerString ==>D8DB-0B46-87B1-24FC<== line: 952
02/28/20 14:54:29 0x00003860 - SDM <<< BEFORE startUpDelay: 0 seconds controlled by SDMStartupDelay line: 1054 ...
02/28/20 14:54:29 0x00003860 - SDM <<<  AFTER startUpDelay: 0 seconds line: 1085
02/28/20 14:54:29 0x00003860 - SDM <<< enableKeepAlive: 1 (bool) controlled by DisableKeepAlive line: 1131
02/28/20 14:54:29 0x00003860 - SDM <<< BEFORE SockGetMyIP serverIP ==><== line: 1213
02/28/20 14:54:29 0x00003860 - SDM <<< AFTER SockGetMyIP serverIP ==>192.168.1.110<== line: 1221
02/28/20 14:54:29 0x00003860 - SDM <<< BEFORE InitSockMyIPPort line: 1245 ...
02/28/20 14:54:29 0x00003860 - ServerReadConfig <<< serverConfig->sqlServer ==>localhost<== line: 2789
02/28/20 14:54:29 0x00003860 - sdbOpen <<< sqlLogin ==>sdmsql<== line: 779
02/28/20 14:54:29 0x00003860 - sdbOpen <<< sqlPassword ==>sdmsql<== line: 780
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE calloc dbProc line: 826 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER calloc dbProc line: 836
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_ENV line: 855 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_ENV status: 0 line: 866
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLAllocHandle SQL_HANDLE_DBC line: 891 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER SQLAllocHandle SQL_HANDLE_DBC status: 0 line: 902
02/28/20 14:54:29 0x00003860 - sdbOpen <<< odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 963
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLDrivers line: 969 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER SQLDrivers status: 0 line: 986
02/28/20 14:54:29 0x00003860 - sdbOpen <<< driverFound: 1 (bool) description ==>ODBC Driver 17 for SQL Server<== odbcDriver ==>ODBC Driver 17 for SQL Server<== line: 1019
02/28/20 14:54:29 0x00003860 - sdbOpen <<< driverAttributes ==>UsageCount=1<== line: 1072
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT SDB_LOGIN_TIMEOUT: 5 sec line: 1081 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_LOGIN_TIMEOUT status: 0 line: 1093
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT SDB_CONNECTION_TIMEOUT: 5 sec line: 1122 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT status: 0 line: 1134
02/28/20 14:54:29 0x00003860 - sdbOpen <<< hostName ==>Condor<== line: 1223
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE SQLDriverConnect inConnStr ==>Driver=ODBC Driver 17 for SQL Server;server=Condor;database=sdmsql;uid=sdmsql;pwd=sdmsql;<== line: 1245 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<< AFTER SQLDriverConnect status: 1 outConnStrLen: 101 outConnStr ==>DRIVER=ODBC Driver 17 for SQL Server;SERVER=Condor;UID=sdmsql;PWD=sdmsql;WSID=CONDOR;DATABASE=sdmsql;<== line: 1273
02/28/20 14:54:29 0x00003860 - sdbOpen <<< BEFORE sdbQueryNoResults query ==>use sdmsql;--<== line: 1365 ...
02/28/20 14:54:29 0x00003860 - sdbOpen <<<  AFTER sdbQueryNoResults status: 0 line: 1375
02/28/20 14:54:29 0x00003860 - sdbOpen <<< retVal: 0 line: 1447 file ==>C:\SencorSource\source\sncrodbclo.c<==

I placed a comment to illustrate when the software fails when executing as a service. When executing as an application it works fine.

I humble opinion is that something might have changed with an update or I am missing an obvious step. One way or the other if you need something else, have any questions or suggestions, please let me know by leaving a comment or sending me a message to: john.canessa@gmail.com.

One last thing, thanks to all 342 subscribers to my blog!!!

John

Twitter:  @john_canessa

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.