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

📄 odbc.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 5 页
字号:
	   at all.  To handle this we treat GRANT/REVOKE as being available if
	   any information is returned (SQL Server typically returns only
	   SQL_SG_WITH_GRANT_OPTION while other sources like DB2, Postgres, and
	   Sybase return the correct set of flags) and not available if nothing
	   is returned (Access, dBASE, Paradox, etc).  To make things especially
	   challenging, Informix returns nothing for SQL_SQL92_GRANT but does
	   return something for SQL_SQL92_REVOKE so we have to check both and
	   allow GRANT/REVOKE if either test positive */
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_SQL92_GRANT,
							( SQLPOINTER ) &privileges,
							sizeof( SQLUINTEGER ), &bufLen );
	if( sqlStatusOK( sqlStatus ) && privileges )
		*featureFlags |= DBMS_FEATURE_FLAG_PRIVILEGES;
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_SQL92_REVOKE,
							( SQLPOINTER ) &privileges,
							sizeof( SQLUINTEGER ), &bufLen );
	if( sqlStatusOK( sqlStatus ) && privileges )
		*featureFlags |= DBMS_FEATURE_FLAG_PRIVILEGES;

	/* Check how the back-end reacts to commit/rollback operations.  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 
	   transactions are used with CA certificate 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 )
		{
		assert( DEBUG_WARN );
		dbmsInfo->transactIsDestructive = TRUE;
		}
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_CURSOR_ROLLBACK_BEHAVIOR,
							&transactBehaviour, sizeof( SQLUSMALLINT ),
							&bufLen );
	if( sqlStatusOK( sqlStatus ) && transactBehaviour == SQL_CB_DELETE )
		{
		assert( DEBUG_WARN );
		dbmsInfo->transactIsDestructive = TRUE;
		}

	return( CRYPT_OK );
	}

/* Get the back-end type for this data source, which allows us to work 
   around back-end-specific bugs and peculiarities */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int getBackendInfo( INOUT DBMS_STATE_INFO *dbmsInfo )
	{
	SQLRETURN sqlStatus;
	SQLSMALLINT bufLen;
	char buffer[ 128 + 8 ];

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

	/* Check for various back-ends that require special-case handling */
	sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_DBMS_NAME, buffer, 128,
							&bufLen );
	if( sqlStatusOK( sqlStatus ) )
		{
		if( bufLen >= 6 && !strCompare( buffer, "Access", 6 ) )
			dbmsInfo->backendType = DBMS_ACCESS;
		if( bufLen >= 9 && !strCompare( buffer, "Interbase", 9 ) )
			dbmsInfo->backendType = DBMS_INTERBASE;
		if( bufLen >= 12 && !strCompare( buffer, "PostgreSQL", 10 ) )
			dbmsInfo->backendType = DBMS_POSTGRES;
		}

	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 */

STDC_NONNULL_ARG( ( 1 ) ) \
static void closeDatabase( INOUT 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 and it's not
   worth abandoning the database open just because some obscure tweak isn't
   supported.

   In addition to the main hStmt handle 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 */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 5 ) ) \
static int openDatabase( INOUT DBMS_STATE_INFO *dbmsInfo, 
						 IN_BUFFER( nameLen ) const char *name,
						 IN_LENGTH_NAME const int nameLen, 
						 IN_ENUM_OPT( CRYPT_KEYOPT ) \
							const CRYPT_KEYOPT_TYPE options, 
						 OUT_FLAGS_Z( DBMS ) int *featureFlags )
	{
	ERROR_INFO *errorInfo = &dbmsInfo->errorInfo;
	DBMS_NAME_INFO nameInfo;
	SQLRETURN sqlStatus;
	int i, status;

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

	REQUIRES( nameLen >= MIN_NAME_LENGTH && \
			  nameLen < MAX_ATTRIBUTE_SIZE );
	REQUIRES( options >= CRYPT_KEYOPT_NONE && options < CRYPT_KEYOPT_LAST );

	/* Clear return values */
	memset( dbmsInfo, 0, sizeof( DBMS_STATE_INFO ) );
	*featureFlags = DBMS_FEATURE_FLAG_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, nameLen );
	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 that 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__
		setErrorString( errorInfo, 
						"Couldn't allocate database connection handle", 44 );
#else
		setErrorString( errorInfo, 
						"Couldn't allocate database connection handle, this "
						"is probably due to an incorrect ODBC driver install "
						"or an invalid configuration", 130 );
#endif /* __WINDOWS__ */
		return( CRYPT_ERROR_OPEN );
		}
	sqlStatus = SQLSetEnvAttr( dbmsInfo->hEnv, SQL_ATTR_ODBC_VERSION,
								( SQLPOINTER ) SQL_OV_ODBC3, 
								SQL_IS_INTEGER );
	if( sqlStatusOK( sqlStatus ) )
		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 that 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 )
		{
		( void ) 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++ )
		{
		( void ) SQLSetStmtAttr( dbmsInfo->hStmt[ i ], SQL_ATTR_CURSOR_TYPE,
								 ( SQLPOINTER ) SQL_CURSOR_FORWARD_ONLY,
								 SQL_IS_INTEGER );
		if( options == CRYPT_KEYOPT_READONLY )
			{
			( void ) SQLSetStmtAttr( dbmsInfo->hStmt[ i ], 
									 SQL_ATTR_CONCURRENCY,
									 ( SQLPOINTER ) SQL_CONCUR_READ_ONLY,
									 SQL_IS_INTEGER );
			}
		( void ) 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( cryptStatusOK( status ) )
		status = getBackendInfo( dbmsInfo );
	if( cryptStatusError( status ) )
		{
		closeDatabase( dbmsInfo );
		return( status );
		}

	return( CRYPT_OK );
	}

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

/* Fetch data from a query */

CHECK_RETVAL STDC_NONNULL_ARG( ( 2, 4, 6 ) ) \
static int fetchData( const SQLHSTMT hStmt, 
					  OUT_BUFFER( dataMaxLength, *dataLength ) \
						char *data,
					  IN_LENGTH_SHORT const int dataMaxLength, 
					  OUT_LENGTH_SHORT_Z int *dataLength, 
					  IN_ENUM( DBMS_QUERY ) const DBMS_QUERY_TYPE queryType,
					  INOUT DBMS_STATE_INFO *dbmsInfo )
	{
	ERROR_INFO *errorInfo = &dbmsInfo->errorInfo;
	const SQLSMALLINT dataType = ( dbmsInfo->hasBinaryBlobs ) ? \
								 SQL_C_BINARY : SQL_C_CHAR;
	SQLRETURN sqlStatus;
	SQLUINTEGER length;

	assert( ( queryType == DBMS_QUERY_CHECK && \
			  data == NULL && dataMaxLength == 0 && dataLength == NULL ) || \
			( queryType != DBMS_QUERY_CHECK && \
			  isWritePtr( data, dataMaxLength ) && \
			  isWritePtr( dataLength, sizeof( int ) ) ) );
	assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );

	REQUIRES( ( queryType == DBMS_QUERY_CHECK && \
				data == NULL && dataMaxLength == 0 && \
				dataLength == NULL ) || \
			  ( queryType != DBMS_QUERY_CHECK && \
				data != NULL && dataMaxLength >= 16 && \
				dataMaxLength < MAX_INTLENGTH_SHORT && \
				dataLength != NULL ) );
	REQUIRES( queryType > DBMS_QUERY_NONE && \
			  queryType < DBMS_QUERY_LAST );

	/* Clear return value */
	if( data != NULL )
		{
		memset( data, 0, min( 16, dataMaxLength ) );
		*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
		   information to be fetched by SQLGetDiagRec() */

⌨️ 快捷键说明

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