⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 odbc.c

📁 cryptlib是功能强大的安全工具集。允许开发人员快速在自己的软件中集成加密和认证服务。
💻 C
📖 第 1 页 / 共 4 页
字号:

	/* Check how the back-end reacts to commit/rollback commands.  If
	   transactions are destructive (that is, prepared statements are
	   cleared when a commit/rollback is performed), we have to clear the
	   hStmtPrepared[] array to indicate that all statements have to be
	   re-prepared.  Fortunately this is quite rare, both because most
	   back-ends don't do this (for virtually all ODBC-accessible data
	   sources (SQL Server, Access, dBASE, Paradox, etc etc) the behaviour
	   is SQL_CB_CLOSE, meaning that the currently active cursor is closed
	   but there's no need to call SQLPrepare() again) and because it only
	   affects CA cert stores opened in read/write mode */
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_CURSOR_COMMIT_BEHAVIOR,
							&transactBehaviour, sizeof( SQLUSMALLINT ),
							&bufLen );
	if( sqlStatusOK( sqlStatus ) && transactBehaviour == SQL_CB_DELETE )
		dbmsInfo->transactIsDestructive = TRUE;
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
							&transactBehaviour, sizeof( SQLUSMALLINT ),
							&bufLen );
	if( sqlStatusOK( sqlStatus ) && transactBehaviour == SQL_CB_DELETE )
		dbmsInfo->transactIsDestructive = TRUE;

	/* Finally, determine the escape char being used.  This is usually '\',
	   but it may have been changed for some reason */
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_SEARCH_PATTERN_ESCAPE,
							buffer, sizeof( buffer ), &bufLen );
	dbmsInfo->escapeChar = sqlStatusOK( sqlStatus ) ? buffer[ 0 ] : '\\';

	return( CRYPT_OK );
	}

/* Bind parameters for a query/update.  The caller has to supply the bound
   data storage since it still has to exist later on when the query is
   executed */

static int bindParameters( const SQLHSTMT hStmt, const char *boundData,
						   const int boundDataLength, const time_t boundDate,
						   TIMESTAMP_STRUCT *timestampStorage,
						   SQLINTEGER *lengthStorage,
						   DBMS_STATE_INFO *dbmsInfo,
						   const BOOLEAN bindForQuery )
	{
	SQLUSMALLINT paramNo = 1;

	assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );

	/* Bind in any necessary parameters to the hStmt.  If there's a bound
	   date parameter present it'll always come before the bound data, so
	   we bind the date first */
	if( boundDate > 0 )
		{
		SQLRETURN sqlStatus;
		const struct tm *timeInfo = gmtime( &boundDate );

		assert( isWritePtr( timestampStorage, sizeof( TIMESTAMP_STRUCT ) ) );

		/* Sanity check on input parameters */
		if( timestampStorage == NULL )
			return( CRYPT_ERROR_BADDATA );

		/* Bind in the date.  The handling of the ColumnSize parameter is
		   ugly, this value should be implicit in the underlying data type,
		   but a small number of back-ends (e.g. ones derived from the
		   Sybase 4.2 code line, which includes the current Sybase and SQL
		   Server) may support multiple time representations and require an
		   explicit length indicator to decide which one they should use
		   (not helped by the fact that the sample code in the
		   SQLBindParameter() manpage gives the ColumnSize parameter for
		   date/time types as zero, implying that it's ignored by the
		   driver).

		   Unfortunately the fact that some drivers specifically require
		   this parameter means that we have to provide an explicit length
		   value, see the comment in getDatatypeInfo() for how this is
		   obtained.  Luckily the majority of back-ends have a single pre-
		   set value for this and ignore the length value, so the chances of
		   running into something that both requires the parameter and fails
		   the guesstimation procedure used in getDatatypeInfo() is small */
		memset( timestampStorage, 0, sizeof( TIMESTAMP_STRUCT ) );
		timestampStorage->year = timeInfo->tm_year + 1900;
		timestampStorage->month = timeInfo->tm_mon + 1;
		timestampStorage->day = timeInfo->tm_mday;
		timestampStorage->hour = timeInfo->tm_hour;
		timestampStorage->minute = timeInfo->tm_min;
		timestampStorage->second = timeInfo->tm_sec;
		sqlStatus = SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
									  SQL_C_TIMESTAMP, SQL_TIMESTAMP,
									  dbmsInfo->dateTimeNameColSize, 0,
									  timestampStorage, 0, NULL );
		if( !sqlStatusOK( sqlStatus ) )
			return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
								  CRYPT_ERROR_BADDATA ) );
		}
	if( boundData != NULL )
		{
		SQLSMALLINT valueType, parameterType;
		SQLRETURN sqlStatus;

		assert( boundDataLength > 0 && \
				isReadPtr( boundData, boundDataLength ) );
		assert( isWritePtr( lengthStorage, sizeof( SQLINTEGER ) ) );

		/* Bind the query data in one of two ways depending on whether we're
		   binding for a query or an update.  The effective difference
		   between the two is mostly ODBC voodoo related to how lengths are
		   specified, if it isn't done this way then Access (the default
		   ODBC data source on most Windows systems) returns "String data,
		   right truncated (null)" errors at random.  No-one knows what the
		   cause is, and the only known fix is to juggle parameters until it
		   stops happening, although in some cases it appears to be because
		   it ignores the length value for SQL_CHAR data and tries to find a
		   terminating null character past the end of the string */
		if( bindForQuery )
			valueType = parameterType = SQL_C_CHAR;
		else
			{
			valueType = ( dbmsInfo->hasBinaryBlobs ) ? SQL_C_BINARY : \
													   SQL_C_CHAR;
			parameterType = dbmsInfo->blobType;
			}
		*lengthStorage = boundDataLength;
		sqlStatus = SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
									  valueType, parameterType,
									  boundDataLength, 0,
									  ( SQLPOINTER ) boundData,
									  boundDataLength, lengthStorage );
		if( !sqlStatusOK( sqlStatus ) )
			return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
								  CRYPT_ERROR_BADDATA ) );
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						 	Database Open/Close Routines					*
*																			*
****************************************************************************/

/* Close a previously-opened ODBC connection.  We have to have this before
   openDatabase() since it may be called by openDatabase() if the open
   process fails.  This is necessary because the complex ODBC open may
   require a fairly extensive cleanup afterwards */

static void closeDatabase( DBMS_STATE_INFO *dbmsInfo )
	{
	int i;

	assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );

	/* Commit the transaction.  The default transaction mode is auto-commit
	   so the SQLEndTran() call isn't strictly necessary, but we play it
	   safe anyway */
	if( dbmsInfo->needsUpdate )
		{
		SQLEndTran( SQL_HANDLE_DBC, dbmsInfo->hDbc, SQL_COMMIT );
		dbmsInfo->needsUpdate = FALSE;
		}

	/* Clean up */
	for( i = 0; i < NO_CACHED_QUERIES; i++ )
		if( dbmsInfo->hStmt[ i ] != NULL )
			{
			SQLFreeHandle( SQL_HANDLE_STMT, dbmsInfo->hStmt[ i ] );
			dbmsInfo->hStmtPrepared[ i ] = FALSE;
			dbmsInfo->hStmt[ i ] = NULL;
			}
	SQLDisconnect( dbmsInfo->hDbc );
	SQLFreeHandle( SQL_HANDLE_DBC, dbmsInfo->hDbc );
	SQLFreeHandle( SQL_HANDLE_ENV, dbmsInfo->hEnv );
	dbmsInfo->hDbc = NULL;
	dbmsInfo->hEnv = NULL;
	}

/* Open a connection to a data source.  We don't check the return codes for
   many of the parameter-fiddling functions since the worst that can happen
   if they fail is that performance will be somewhat suboptimal.

   For the somewhat flaky Win16 ODBC 1.x/2.x, it wasn't safe to allocate
   statement handles at this point since these were handled in various
   strange and peculiar ways by different ODBC drivers.  The main problem was
   that some drivers didn't support more than one hStmt per hDbc, some
   supported only one active hStmt (an hStmt with results pending) per hDbc,
   and some supported multiple active hStmt's per hDbc.  For this reason the
   older ODBC glue code used a strategy of allocating an hStmt, performing a
   transaction, and then immediately freeing it again afterwards.

   For any newer ODBC driver this isn't a problem any more (particularly when
   it's necessary to accomodate threads), so we can allocate the hStmt here.
   In addition to the main hStmt we also allocate a number of additional
   hStmts used to contain pre-prepared, cached instances of frequently-
   executed queries.  This means that the expensive step of parsing the SQL
   query, validating it against the system catalog, preparing an access
   plan, and optimising the plan, are only performed once on the first query
   rather than at every single access.  If it's necessary to work with a
   buggy ODBC driver that can't support multiple hStmts then everything can
   be directed through the primary hStmt, at some loss in performance */

static int openDatabase( DBMS_STATE_INFO *dbmsInfo, const char *name,
						 const int options, int *featureFlags )
	{
	DBMS_NAME_INFO nameInfo;
	SQLRETURN sqlStatus;
	int i, status;

	assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
	assert( isReadPtr( name, 2 ) );
	assert( isWritePtr( featureFlags, sizeof( int ) ) );

	/* Clear return values */
	memset( dbmsInfo, 0, sizeof( DBMS_STATE_INFO ) );
	*featureFlags = DBMS_HAS_NONE;

#ifdef DYNAMIC_LOAD
	/* Make sure that the driver is bound in */
	if( hODBC == NULL_INSTANCE )
		return( CRYPT_ERROR_OPEN );
#endif /* DYNAMIC_LOAD */

	/* Parse the data source into its individual components */
	status = dbmsParseName( &nameInfo, name, SQL_NTS );
	if( cryptStatusError( status ) )
		return( status );

	/* Allocate environment and connection handles.  Before we do anything
	   with the environment handle we have to set the ODBC version to 3 or
	   any succeeding calls will fail with a function sequence error.  God
	   knows why they couldn't assume a default setting of ODBC 3.x for this
	   value when it requires an ODBC 3.x function call to get here in the
	   first place */
	sqlStatus = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE,
								&dbmsInfo->hEnv );
	if( !sqlStatusOK( sqlStatus ) )
		{
		/* We can't get any error details without at least an environment
		   handle, so all we can do is return a generic allocation error
		   message.  If we get a failure at this point (and in particular
		   on the very first ODBC call) it's usually a sign of an incorrect
		   ODBC install or config (on non-Windows systems where it's not
		   part of the OS), since the ODBC driver can't initialise itself */
#ifdef __WINDOWS__
		strcpy( dbmsInfo->errorMessage, "Couldn't allocate database "
				"connection handle" );
#else
		strcpy( dbmsInfo->errorMessage, "Couldn't allocate database "
				"connection handle, this is probably due to an incorrect "
				"ODBC driver install or an invalid configuration" );
#endif /* __WINDOWS__ */
		return( CRYPT_ERROR_OPEN );
		}
	SQLSetEnvAttr( dbmsInfo->hEnv, SQL_ATTR_ODBC_VERSION,
				   ( SQLPOINTER ) SQL_OV_ODBC3, SQL_IS_INTEGER );
	sqlStatus = SQLAllocHandle( SQL_HANDLE_DBC, dbmsInfo->hEnv,
								&dbmsInfo->hDbc );
	if( !sqlStatusOK( sqlStatus ) )
		{
		status = getErrorInfo( dbmsInfo, SQL_ERRLVL_ENV, SQL_NULL_HSTMT,
							   CRYPT_ERROR_OPEN );
		SQLFreeHandle( SQL_HANDLE_ENV, dbmsInfo->hEnv );
		return( status );
		}

	/* Once everything is set up the way we want it, try to connect to a data
	   source and allocate a statement handle */
	sqlStatus = SQLConnect( dbmsInfo->hDbc,
							nameInfo.name, ( SQLSMALLINT ) nameInfo.nameLen,
							nameInfo.user, ( SQLSMALLINT ) nameInfo.userLen,
							nameInfo.password, ( SQLSMALLINT ) nameInfo.passwordLen );
	if( !sqlStatusOK( sqlStatus ) )
		{
		status = getErrorInfo( dbmsInfo, SQL_ERRLVL_DBC, SQL_NULL_HSTMT,
							   CRYPT_ERROR_OPEN );
		closeDatabase( dbmsInfo );
		return( status );
		}

	/* Now that the connection is open, allocate the statement handles */
	for( i = 0; i < NO_CACHED_QUERIES && sqlStatusOK( sqlStatus ); i++ )
		sqlStatus = SQLAllocHandle( SQL_HANDLE_STMT, dbmsInfo->hDbc,
									&dbmsInfo->hStmt[ i ] );
	if( !sqlStatusOK( sqlStatus ) )
		{
		status = getErrorInfo( dbmsInfo, SQL_ERRLVL_DBC, SQL_NULL_HSTMT,
							   CRYPT_ERROR_OPEN );
		closeDatabase( dbmsInfo );
		return( status );
		}

	/* Set the access mode to read-only if we can.  The default is R/W, but
	   setting it to read-only optimises transaction management */
	if( options == CRYPT_KEYOPT_READONLY )
		SQLSetStmtAttr( dbmsInfo->hDbc, SQL_ATTR_ACCESS_MODE,
						( SQLPOINTER ) SQL_MODE_READ_ONLY, SQL_IS_INTEGER );

	/* Set the cursor type to forward-only (which should be the default
	   anyway), concurrency to read-only if we're opening the database in
	   read-only mode (this again should be the default), and turn off
	   scanning for escape clauses in the SQL strings, which lets the driver
	   pass the string directly to the data source.  The latter improves
	   both performance and (to some extent) security by reducing the
	   chances of hostile SQL injection, or at least by requiring specially
	   crafted back-end specific SQL rather than generic ODBC SQL to
	   function */
	for( i = 0; i < NO_CACHED_QUERIES; i++ )
		{
		SQLSetStmtAttr( dbmsInfo->hStmt[ i ], SQL_ATTR_CURSOR_TYPE,
						( SQLPOINTER ) SQL_CURSOR_FORWARD_ONLY,
						SQL_IS_INTEGER );
		if( options == CRYPT_KEYOPT_READONLY )
			SQLSetStmtAttr( dbmsInfo->hStmt[ i ], SQL_ATTR_CONCURRENCY,
							( SQLPOINTER ) SQL_CONCUR_READ_ONLY,
							SQL_IS_INTEGER );
		SQLSetStmtAttr( dbmsInfo->hStmt[ i ], SQL_ATTR_NOSCAN,
						( SQLPOINTER ) SQL_NOSCAN_ON, SQL_IS_INTEGER );
		}

	/* Get various driver and data source-specific information that we may
	   need later on */
	status = getDatatypeInfo( dbmsInfo, featureFlags );
	if( cryptStatusError( status ) )
		{
		closeDatabase( dbmsInfo );
		return( status );
		}

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*						 	Database Read Routines							*
*																			*
****************************************************************************/

/* Fetch data from a query */

static int fetchData( const SQLHSTMT hStmt, char *data,
					  int *dataLength, const int maxLength,
					  const DBMS_QUERY_TYPE queryType,
					  DBMS_STATE_INFO *dbmsInfo )
	{
	const SQLSMALLINT dataType = ( dbmsInfo->hasBinaryBlobs ) ? \
								 SQL_C_BINARY : SQL_C_CHAR;
	SQLRETURN sqlStatus;
	SQLUINTEGER length;

	/* Clear return value */
	if( dataLength != NULL )
		*dataLength = 0;

	/* Get the results of the transaction */
	sqlStatus = SQLFetch( hStmt );
	if( !sqlStatusOK( sqlStatus ) )
		{
		/* If the fetch status is SQL_NO_DATA, indicating the end of the
		   result set, we handle it specially since some drivers only return
		   the basic error code and don't provide any further diagnostic
		   info to be fetched by SQLGetDiagRec() */
		if( sqlStatus == SQL_NO_DATA )
			{
			strcpy( dbmsInfo->errorMessage, "No data found" );
			return( CRYPT_ERROR_NOTFOUND );
			}
		return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
							  CRYPT_ERROR_READ ) );
		}

	/* If we're just doing a presence check, we don't bother fetching data */

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -