📄 odbc.c
字号:
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 + -