📄 dbms.c
字号:
/****************************************************************************
* *
* cryptlib DBMS Backend Interface *
* Copyright Peter Gutmann 1996-2007 *
* *
****************************************************************************/
#include <ctype.h>
#include <stdarg.h>
#if defined( INC_ALL )
#include "crypt.h"
#include "keyset.h"
#include "dbms.h"
#include "rpc.h"
#else
#include "crypt.h"
#include "keyset/keyset.h"
#include "keyset/dbms.h"
#include "misc/rpc.h"
#endif /* Compiler-specific includes */
#ifdef USE_DBMS
/****************************************************************************
* *
* Backend Database Access Functions *
* *
****************************************************************************/
/* Dispatch functions for various database types. ODBC is the native keyset
for Windows and (frequently) Unix, a cryptlib-native plugin is the
fallback for Unix, and the rest are only accessible via database network
plugins */
#ifdef USE_ODBC
int initDispatchODBC( DBMS_INFO *dbmsInfo );
#else
#define initDispatchODBC( dbmsInfo ) CRYPT_ERROR
#endif /* USE_ODBC */
#if defined( USE_DATABASE )
int initDispatchDatabase( DBMS_INFO *dbmsInfo );
#else
#define initDispatchDatabase( dbmsInfo ) CRYPT_ERROR
#endif /* General database interface */
#ifdef USE_DATABASE_PLUGIN
int initDispatchNet( DBMS_INFO *dbmsInfo );
#else
#define initDispatchNet( dbmsInfo ) CRYPT_ERROR
#endif /* USE_DATABASE_PLUGIN */
/* Database access functions */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2, 5 ) ) \
static int openDatabase( INOUT DBMS_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_FEATURE ) int *featureFlags )
{
DBMS_STATE_INFO *dbmsStateInfo = dbmsInfo->stateInfo;
int status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_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 value */
*featureFlags = DBMS_FEATURE_FLAG_NONE;
status = dbmsInfo->openDatabaseBackend( dbmsStateInfo, name, nameLen,
options, featureFlags );
if( cryptStatusError( status ) )
return( status );
/* Make long-term information returned as a back-end interface-specific
feature flags persistent if necessary */
if( *featureFlags & DBMS_FEATURE_FLAG_BINARYBLOBS )
dbmsInfo->flags |= DBMS_FLAG_BINARYBLOBS;
return( CRYPT_OK );
}
STDC_NONNULL_ARG( ( 1 ) ) \
static void closeDatabase( INOUT DBMS_INFO *dbmsInfo )
{
DBMS_STATE_INFO *dbmsStateInfo = dbmsInfo->stateInfo;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
dbmsInfo->closeDatabaseBackend( dbmsStateInfo );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int performUpdate( INOUT DBMS_INFO *dbmsInfo,
IN_STRING_OPT const char *command,
IN_ARRAY_OPT( BOUND_DATA_MAXITEMS ) \
const BOUND_DATA *boundData,
IN_ENUM( DBMS_UPDATE ) \
const DBMS_UPDATE_TYPE updateType )
{
DBMS_STATE_INFO *dbmsStateInfo = dbmsInfo->stateInfo;
const int commandLength = ( command != NULL ) ? strlen( command ) : 0;
int status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
assert( ( command == NULL && commandLength == 0 && \
updateType == DBMS_UPDATE_ABORT ) || \
isReadPtr( command, commandLength ) );
assert( ( boundData == NULL ) || \
isReadPtr( boundData, \
sizeof( BOUND_DATA ) * BOUND_DATA_MAXITEMS ) );
REQUIRES( ( updateType == DBMS_UPDATE_ABORT && \
command == NULL && commandLength == 0 ) || \
( updateType != DBMS_UPDATE_ABORT && \
command != NULL && \
commandLength > 0 && commandLength < MAX_INTLENGTH_SHORT ) );
REQUIRES( updateType > DBMS_UPDATE_NONE && \
updateType < DBMS_UPDATE_LAST );
/* If we're trying to abort a transaction that was never begun, don't
do anything */
if( updateType == DBMS_UPDATE_ABORT && \
!( dbmsInfo->flags & DBMS_FLAG_UPDATEACTIVE ) )
return( CRYPT_OK );
/* Process the update */
status = dbmsInfo->performUpdateBackend( dbmsStateInfo, command,
commandLength, boundData,
updateType );
if( cryptStatusOK( status ) )
{
/* If we're starting or ending an update, record the update state */
if( updateType == DBMS_UPDATE_BEGIN )
dbmsInfo->flags |= DBMS_FLAG_UPDATEACTIVE;
if( updateType == DBMS_UPDATE_COMMIT || \
updateType == DBMS_UPDATE_ABORT )
dbmsInfo->flags &= ~DBMS_FLAG_UPDATEACTIVE;
}
return( status );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int performStaticUpdate( INOUT DBMS_INFO *dbmsInfo,
IN_STRING const char *command )
{
return( performUpdate( dbmsInfo, command, NULL, DBMS_UPDATE_NORMAL ) );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int performQuery( INOUT DBMS_INFO *dbmsInfo,
IN_STRING_OPT const char *command,
OUT_BUFFER_OPT( dataMaxLength, *dataLength ) \
char *data,
IN_LENGTH_SHORT_Z const int dataMaxLength,
OUT_LENGTH_SHORT_Z int *dataLength,
IN_ARRAY_OPT( BOUND_DATA_MAXITEMS ) \
const BOUND_DATA *boundData,
IN_ENUM_OPT( DBMS_CACHEDQUERY ) \
const DBMS_CACHEDQUERY_TYPE queryEntry,
IN_ENUM( DBMS_QUERY ) const DBMS_QUERY_TYPE queryType )
{
DBMS_STATE_INFO *dbmsStateInfo = dbmsInfo->stateInfo;
const int commandLength = ( command != NULL ) ? strlen( command ) : 0;
int status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_INFO ) ) );
assert( ( command == NULL && commandLength == 0 && \
( queryType == DBMS_QUERY_CONTINUE || \
queryType == DBMS_QUERY_CANCEL ) ) || \
isReadPtr( command, commandLength ) );
assert( ( data == NULL && dataLength == NULL ) || \
isWritePtr( data, MAX_QUERY_RESULT_SIZE ) );
assert( ( boundData == NULL ) || \
isReadPtr( boundData, \
sizeof( BOUND_DATA ) * BOUND_DATA_MAXITEMS ) );
REQUIRES( ( ( queryType == DBMS_QUERY_CONTINUE || \
queryType == DBMS_QUERY_CANCEL ) && \
command == NULL && commandLength == 0 ) || \
( ( queryType == DBMS_QUERY_START || \
queryType == DBMS_QUERY_CHECK || \
queryType == DBMS_QUERY_NORMAL ) && \
command != NULL && \
commandLength > 0 && commandLength < MAX_INTLENGTH_SHORT ) );
REQUIRES( ( data == NULL && dataMaxLength == 0 && \
dataLength == NULL ) || \
( data != NULL && dataMaxLength >= 16 && \
dataMaxLength < MAX_INTLENGTH_SHORT && \
dataLength != NULL ) );
REQUIRES( queryEntry >= DBMS_CACHEDQUERY_NONE && \
queryEntry < DBMS_CACHEDQUERY_LAST );
REQUIRES( queryType > DBMS_QUERY_NONE && \
queryType < DBMS_QUERY_LAST );
/* Additional state checks: If we're starting a new query or performing
a point query there can't already be one active and if we're
continuing or cancelling an existing query there has to be one
already active */
REQUIRES( ( ( queryType == DBMS_QUERY_START || \
queryType == DBMS_QUERY_CHECK || \
queryType == DBMS_QUERY_NORMAL ) && \
!( dbmsInfo->flags & DBMS_FLAG_QUERYACTIVE ) ) ||
( ( queryType == DBMS_QUERY_CONTINUE || \
queryType == DBMS_QUERY_CANCEL ) && \
( dbmsInfo->flags & DBMS_FLAG_QUERYACTIVE ) ) );
/* Clear return value */
if( data != NULL )
{
memset( data, 0, min( 16, dataMaxLength ) );
*dataLength = 0;
}
/* Process the query */
status = dbmsInfo->performQueryBackend( dbmsStateInfo, command,
commandLength, data, dataMaxLength,
dataLength, boundData,
queryEntry, queryType );
if( cryptStatusError( status ) )
return( status );
/* Sanity-check the result data from the back-end */
if( dataLength != NULL && \
( *dataLength <= 0 || *dataLength > MAX_QUERY_RESULT_SIZE ) )
{
assert( DEBUG_WARN );
memset( data, 0, min( 16, dataMaxLength ) );
*dataLength = 0;
return( CRYPT_ERROR_BADDATA );
}
/* Update the state information based on the query that we've just
performed */
if( queryType == DBMS_QUERY_START )
dbmsInfo->flags |= DBMS_FLAG_QUERYACTIVE;
if( queryType == DBMS_QUERY_CANCEL )
dbmsInfo->flags &= ~DBMS_FLAG_QUERYACTIVE;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int performStaticQuery( INOUT DBMS_INFO *dbmsInfo,
IN_STRING_OPT const char *command,
IN_ENUM_OPT( DBMS_CACHEDQUERY ) \
const DBMS_CACHEDQUERY_TYPE queryEntry,
IN_ENUM( DBMS_QUERY ) \
const DBMS_QUERY_TYPE queryType )
{
return( performQuery( dbmsInfo, command, NULL, 0, NULL, NULL,
queryEntry, queryType ) );
}
/****************************************************************************
* *
* SQL Rewrite Routines *
* *
****************************************************************************/
/* In order to allow general certificate database queries we have to be able
to process user-supplied query strings. The cryptlib manual contains
strong warnings about the correct way to do this (if it's done at all),
the best that we can do is apply assorted safety checks of the query data
to try and reduce the chances of SQL injection. Unfortunately this can
get arbitrarily complicated:
'; The standard SQL-injection method, used with values like
'foo; DROP TABLE bar', or '1=1' to return all entries in a table.
-- Comment delimiter (other values also exist, e.g. MySQL's '#') to
truncate queries beyond the end of the injected SQL.
char(0xNN) Bypass the first level of filtering, e.g. char(0x41)
produces the banned character '.
One additional check that we could do is to try and explicitly strip
SQL keywords from queries but this is somewhat problematic because apart
from the usual trickery (e.g. embedding one SQL keyword inside another so
that stripping SELECT from SELSELECTECT will still leave the outer
SELECT, requiring recursive stripping, or taking advantage of the fact
that VARBINARY values are implicitly cast to VARCHARS so that 0x42434445
would turn into ABCD, or further escaping the encoding with values like
'sel'+'ect') there are also any number of backend-specific custom
keywords and ways of escaping keywords that we can't know about and
therefore can't easily strip */
#define SQL_ESCAPE '\''
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
static int copyChar( OUT_BUFFER( bufMaxLen, *bufPos ) char *buffer,
IN_LENGTH_SHORT const int bufMaxLen,
OUT_LENGTH_SHORT_Z int *bufPos,
IN_BYTE const int ch, const BOOLEAN escapeQuotes )
{
int position = 0;
assert( isWritePtr( buffer, bufMaxLen ) );
assert( isWritePtr( bufPos, sizeof( int ) ) );
REQUIRES( bufMaxLen > 0 && bufMaxLen < MAX_INTLENGTH_SHORT );
REQUIRES( ch >= 0 && ch <= 0xFF );
/* Clear return value */
*bufPos = 0;
/* If it's a control character, skip it */
if( ( ch & 0x7F ) < ' ' )
return( CRYPT_OK );
/* Escape metacharacters that could be misused in queries. We catch the
obvious ' and ; as well as the less obvious %, which could be used to
hide other metacharacters, and \, used by some databases (e.g. MySQL)
as an escape. Note that none of these characters are valid in base64,
which makes it safe to escape them in the few instances where they do
occur */
if( ( ch == '\'' && escapeQuotes ) || \
ch == '\\' || ch == ';' || ch == '%' )
{
/* Escape the character */
buffer[ position++ ] = SQL_ESCAPE;
if( position >= bufMaxLen )
return( CRYPT_ERROR_OVERFLOW );
}
/* Bypass various dangerous SQL "enhancements". For Windows ODBC the
driver will execute anything delimited by '|'s as an expression (an
example being '|shell("cmd /c echo " & chr(124) & " format c:")|'),
because of this we strip gazintas. Since ODBC uses '{' and '}' as
escape delimiters we also strip these */
if( ch != '|' && ch != '{' && ch != '}' )
buffer[ position++ ] = ch;
/* Make sure that we haven't overflowed the output buffer. This
overflowing can be done deliberately, for example by using large
numbers of escape chars (which are in turn escaped) to force
truncation of the query beyond the injected SQL if the processing
simply stops at a given point */
if( position >= bufMaxLen )
return( CRYPT_ERROR_OVERFLOW );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -