📄 odbc.c
字号:
if( queryType == DBMS_QUERY_CHECK )
return( CRYPT_OK );
/* Read the data */
sqlStatus = SQLGetData( hStmt, 1, dataType, data, maxLength, &length );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_READ ) );
*dataLength = ( int ) length;
return( CRYPT_OK );
}
/* Perform a transaction that returns information */
static int performQuery( DBMS_STATE_INFO *dbmsInfo, const char *command,
char *data, int *dataLength, const char *boundData,
const int boundDataLength, const time_t boundDate,
const DBMS_CACHEDQUERY_TYPE queryEntry,
const DBMS_QUERY_TYPE queryType )
{
/* We have to explicitly set the maximum length indicator because some
sources will helpfully zero-pad the data to the maximum indicated size,
which is smaller for binary data */
const int maxLength = dbmsInfo->hasBinaryBlobs ? \
MAX_CERT_SIZE : MAX_QUERY_RESULT_SIZE;
const SQLHSTMT hStmt = dbmsInfo->hStmt[ queryEntry ];
TIMESTAMP_STRUCT timeStamp;
SQLINTEGER lengthInfo;
SQLRETURN sqlStatus;
int status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
assert( ( data == NULL && dataLength == NULL ) || \
isWritePtr( dataLength, sizeof( int ) ) );
assert( ( boundData == NULL && boundDataLength == 0 ) || \
( boundDate == 0 ) );
/* Clear return value */
if( dataLength != NULL )
*dataLength = 0;
/* If we're starting a new query, handle the query initialisation and
parameter binding */
if( queryType == DBMS_QUERY_START || \
queryType == DBMS_QUERY_CHECK || \
queryType == DBMS_QUERY_NORMAL )
{
/* Prepare the query for execution if necessary */
if( queryEntry != DBMS_CACHEDQUERY_NONE && \
!dbmsInfo->hStmtPrepared[ queryEntry ] )
{
char query[ MAX_SQL_QUERY_SIZE ];
convertQuery( dbmsInfo, query, command );
sqlStatus = SQLPrepare( hStmt, query, SQL_NTS );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_READ ) );
dbmsInfo->hStmtPrepared[ queryEntry ] = TRUE;
}
/* Bind in any query parameters that may be required */
status = bindParameters( hStmt, boundData, boundDataLength,
boundDate, &timeStamp, &lengthInfo,
dbmsInfo, TRUE );
if( cryptStatusError( status ) )
return( status );
}
switch( queryType )
{
case DBMS_QUERY_START:
assert( boundDate == 0 );
/* Execute the query */
if( queryEntry == DBMS_CACHEDQUERY_NONE )
{
char query[ MAX_SQL_QUERY_SIZE ];
convertQuery( dbmsInfo, query, command );
sqlStatus = SQLExecDirect( hStmt, query, SQL_NTS );
}
else
sqlStatus = SQLExecute( hStmt );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_READ ) );
/* If we're starting an ongoing query with results to be fetched
later, we're done */
if( data == NULL )
return( CRYPT_OK );
/* Drop through to fetch the first set of results */
case DBMS_QUERY_CONTINUE:
assert( maxLength > 16 && isWritePtr( data, maxLength ) );
/* We're in the middle of a continuing query, fetch the next set
of results. If we've run out of results (indicated by a not-
found status) we explicitly signal to the caller that the
query has completed */
status = fetchData( dbmsInfo->hStmt[ queryEntry ], data,
dataLength, maxLength, DBMS_QUERY_CONTINUE,
dbmsInfo );
return( cryptStatusOK( status ) ? CRYPT_OK : \
( status == CRYPT_ERROR_NOTFOUND ) ? \
CRYPT_ERROR_COMPLETE : status );
case DBMS_QUERY_CANCEL:
/* Cancel any outstanding requests to clear the hStmt ready for
re-use */
SQLCloseCursor( dbmsInfo->hStmt[ queryEntry ] );
return( CRYPT_OK );
case DBMS_QUERY_CHECK:
case DBMS_QUERY_NORMAL:
/* Only return a maximum of a single row in response to a point
query. This is a simple optimisation to ensure that the
database client doesn't start sucking across huge amounts of
data when it's not necessary */
SQLSetStmtAttr( hStmt, SQL_ATTR_MAX_ROWS, ( SQLPOINTER ) 1,
SQL_IS_INTEGER );
/* Execute the SQL statement and fetch the results */
if( queryEntry == DBMS_CACHEDQUERY_NONE )
{
char query[ MAX_SQL_QUERY_SIZE ];
convertQuery( dbmsInfo, query, command );
sqlStatus = SQLExecDirect( hStmt, query, SQL_NTS );
}
else
sqlStatus = SQLExecute( hStmt );
if( sqlStatusOK( sqlStatus ) )
{
status = fetchData( hStmt, data, dataLength, maxLength,
queryType, dbmsInfo );
SQLCloseCursor( hStmt );
}
else
status = getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_READ );
/* Reset the statement handle's multi-row result handling */
SQLSetStmtAttr( hStmt, SQL_ATTR_MAX_ROWS, ( SQLPOINTER ) 0,
SQL_IS_INTEGER );
return( status );
default:
assert( NOTREACHED );
return( CRYPT_ERROR_NOTAVAIL );
}
assert( NOTREACHED );
return( CRYPT_ERROR );
}
/* Fetch extended error information from the database state info */
static void performErrorQuery( DBMS_STATE_INFO *dbmsInfo, int *errorCode,
char *errorMessage )
{
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
assert( isWritePtr( errorCode, sizeof( int ) ) );
assert( isWritePtr( errorMessage, MAX_ERRMSG_SIZE ) );
*errorCode = dbmsInfo->errorCode;
strcpy( errorMessage, dbmsInfo->errorMessage );
}
/****************************************************************************
* *
* Database Write Routines *
* *
****************************************************************************/
/* Perform a transaction that updates the database without returning any
data */
static int performUpdate( DBMS_STATE_INFO *dbmsInfo, const char *command,
const void *boundData, const int boundDataLength,
const time_t boundDate,
const DBMS_UPDATE_TYPE updateType )
{
TIMESTAMP_STRUCT timeStamp;
const SQLHSTMT hStmt = dbmsInfo->hStmt[ 0 ];
SQLINTEGER lengthInfo;
SQLRETURN sqlStatus;
int status = CRYPT_OK;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
/* If we're aborting a transaction, roll it back, re-enable autocommit,
and clean up */
if( updateType == DBMS_UPDATE_ABORT )
{
SQLEndTran( SQL_HANDLE_DBC, dbmsInfo->hDbc, SQL_ROLLBACK );
SQLSetConnectAttr( dbmsInfo->hDbc, SQL_ATTR_AUTOCOMMIT,
( SQLPOINTER ) SQL_AUTOCOMMIT_ON,
SQL_IS_UINTEGER );
return( CRYPT_OK );
}
/* If it's the start of a transaction, turn autocommit off */
if( updateType == DBMS_UPDATE_BEGIN )
SQLSetConnectAttr( dbmsInfo->hDbc, SQL_ATTR_AUTOCOMMIT,
( SQLPOINTER ) SQL_AUTOCOMMIT_OFF,
SQL_IS_UINTEGER );
/* Bind in any necessary parameters to the hStmt. For the older (and
often somewhat flaky) Win16 ODBC 1.x/2.x drivers the binding process
was unlike the behaviour mentioned in the ODBC documentation, which
claimed that SQLExecDirect() would return SQL_NEED_DATA if it found a
parameter marker. Instead, we have to bind the parameters before
calling SQLExecDirect() and it reads them from the bound location as
required. In addition an older version of the ODBC spec required
that the cbColDef value never exceed SQL_MAX_MESSAGE_LENGTH, however
this was defined to be 512 bytes which meant that we couldn't add
most certs of any real complexity or with keys > 1K bits. The
workaround was to pass in the actual data length here instead. This
worked for all ODBC drivers tested.
For any newer Win32 ODBC 3.x drivers this isn't a problem any more,
so we use the mechanism described in the docs, leaving the older
alternative as an option if it's ever needed */
status = bindParameters( hStmt, boundData, boundDataLength,
boundDate, &timeStamp, &lengthInfo,
dbmsInfo, FALSE );
if( cryptStatusError( status ) )
return( status );
#ifdef ODBC1x
if( boundData != NULL )
{
dbmsInfo->cbBlobLength = SQL_LEN_DATA_AT_EXEC( boundDataLength );
SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
dbmsInfo->hasBinaryBlobs ? SQL_C_BINARY : SQL_C_CHAR,
dbmsInfo->blobType, boundDataLength, 0,
( SQLPOINTER ) 6, 0, &dbmsInfo->cbBlobLength );
}
#endif /* ODBC1x */
/* Execute the command/hStmt as appropriate */
if( command == NULL )
sqlStatus = SQLExecute( hStmt );
else
{
char query[ MAX_SQL_QUERY_SIZE ];
convertQuery( dbmsInfo, query, command );
sqlStatus = SQLExecDirect( hStmt, query, SQL_NTS );
}
#ifdef ODBC1x
if( sqlStatus == SQL_NEED_DATA )
{
SQLPOINTER pToken;
/* Add the key data and perform a dummy SQLParamData() call to tell
the ODBC driver that we've finished with the operation */
SQLParamData( hStmt, &pToken );
sqlStatus = SQLPutData( hStmt, ( SQLPOINTER ) boundData,
boundDataLength );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLParamData( hStmt, &pToken );
}
#endif /* ODBC1x */
if( !sqlStatusOK( sqlStatus ) )
{
/* The return status from a delete operation can be reported in
several ways at the whim of the driver. Some drivers always
report success even though nothing was found to delete (more
common in ODBC 2.x drivers, see the code further on for the
handling for this). Others report a failure to delete anything
with an SQL_NO_DATA status (more common in ODBC 3.x drivers).
For this case we convert the overall status to a
CRYPT_ERROR_NOTFOUND and update the sqlStatus as required if we
need to continue */
if( sqlStatus == SQL_NO_DATA && command != NULL && \
!strCompare( command, "DELETE", 6 ) )
{
status = CRYPT_ERROR_NOTFOUND;
if( updateType != DBMS_UPDATE_COMMIT )
return( status );
}
else
{
/* If we hit an error at this point we can only exit if we're
not finishing a transaction. If we are, the commit turns
into an abort further down */
status = getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_WRITE );
if( updateType != DBMS_UPDATE_COMMIT )
return( status );
}
}
else
/* If we're performing a delete, the operation will succeed even
though nothing was found to delete, so we make sure that we
actually changed something */
if( command != NULL && !strCompare( command, "DELETE", 6 ) )
{
SQLUINTEGER rowCount;
SQLRowCount( hStmt, &rowCount );
if( rowCount <= 0 )
status = CRYPT_ERROR_NOTFOUND;
}
/* If it's the end of a transaction, commit the transaction and turn
autocommit on again */
if( updateType == DBMS_UPDATE_COMMIT )
{
SQLRETURN sqlStatus;
/* If we've had a failure before this point, abort, otherwise
commit. The SQLSMALLINT cast is necessary in some development
environments (although spurious) */
sqlStatus = SQLEndTran( SQL_HANDLE_DBC, dbmsInfo->hDbc,
( SQLSMALLINT ) \
( cryptStatusError( status ) ? \
SQL_ROLLBACK : SQL_COMMIT ) );
if( dbmsInfo->transactIsDestructive )
{
int i;
/* If transactions are destructive for this back-end, invalidate
all prepared statements */
for( i = 0; i < NO_CACHED_QUERIES; i++ )
dbmsInfo->hStmtPrepared[ i ] = FALSE;
}
SQLSetConnectAttr( dbmsInfo->hDbc, SQL_ATTR_AUTOCOMMIT,
( SQLPOINTER ) SQL_AUTOCOMMIT_ON,
SQL_IS_UINTEGER );
if( cryptStatusOK( status ) && !sqlStatusOK( sqlStatus ) )
status = getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_WRITE );
}
return( status );
}
#ifndef USE_RPCAPI
int initDispatchODBC( DBMS_INFO *dbmsInfo )
{
dbmsInfo->openDatabaseBackend = openDatabase;
dbmsInfo->closeDatabaseBackend = closeDatabase;
dbmsInfo->performUpdateBackend = performUpdate;
dbmsInfo->performQueryBackend = performQuery;
dbmsInfo->performErrorQueryBackend = performErrorQuery;
return( CRYPT_OK );
}
#else
/* Pull in the shared database RPC routines, renaming the generic dispatch
function to the ODBC-specific one which is called directly by the
marshalling code */
#define processCommand( stateInfo, buffer ) \
odbcProcessCommand( stateInfo, buffer )
#include "dbx_rpc.c"
#endif /* !USE_RPCAPI */
#endif /* USE_ODBC */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -