📄 odbc.c
字号:
/* Get information on an ODBC error. The statement handle is specified as a
distinct parameter because it may be an ephemeral handle not part of the
state info data */
static int getErrorInfo( DBMS_STATE_INFO *dbmsInfo, const int errorLevel,
SQLHSTMT hStmt, const int defaultStatus )
{
#ifdef ODBC1x
SQLHDBC hdbc = ( errorLevel < 1 ) ? SQL_NULL_HDBC : dbmsInfo->hDbc;
SQLHSTMT hstmt = ( errorLevel < 2 ) ? SQL_NULL_HSTMT : dbmsInfo->hStmt;
#else
const SQLSMALLINT handleType = ( errorLevel == SQL_ERRLVL_STMT ) ? \
SQL_HANDLE_STMT : \
( errorLevel == SQL_ERRLVL_DBC ) ? \
SQL_HANDLE_DBC : SQL_HANDLE_ENV;
const SQLHANDLE handle = ( errorLevel == SQL_ERRLVL_STMT ) ? \
hStmt : \
( errorLevel == SQL_ERRLVL_DBC ) ? \
dbmsInfo->hDbc : dbmsInfo->hEnv;
#endif /* ODBC1x */
char szSqlState[ SQL_SQLSTATE_SIZE ];
SQLUINTEGER dwNativeError = 0;
SQLSMALLINT dummy;
SQLRETURN sqlStatus;
#ifdef ODBC1x
/* Get the initial ODBC error info. Some of the information returned by
SQLError() is pretty odd. It usually returns an ANSI SQL2 error
state in SQLSTATE, but also returns a native error code in NativeError.
However the NativeError codes aren't documented anywhere, so we rely
on SQLSTATE having a useful value. We pre-set the native error codes
to zero because they sometimes aren't set by SQLError() */
sqlStatus = SQLError( dbmsInfo->hEnv, hdbc, hstmt, szSqlState,
&dwNativeError, dbmsInfo->errorMessage,
MAX_ERRMSG_SIZE - 1, &dummy );
dbmsInfo->errorCode = ( int ) dwNativeError; /* Usually 0 */
if( !strncmp( szSqlState, "01004", 5 ) )
{
/* Work around a bug in ODBC 2.0 drivers (still present on older
NT 4 machines) in which the primary error is some bizarre
nonsense value (string data right truncated, even though there's
no output data to truncate) and the actual error is present at
the second level, obtained by calling SQLError() a second time */
dwNativeError = 0;
sqlStatus = SQLError( dbmsInfo->hEnv, hdbc, hstmt, szSqlState,
&dwNativeError, dbmsInfo->errorMessage,
MAX_ERRMSG_SIZE - 1, &dummy );
}
#else
/* Get the ODBC error info at the most detailed level we can manage */
sqlStatus = SQLGetDiagRec( handleType, handle, 1, szSqlState,
&dwNativeError, dbmsInfo->errorMessage,
MAX_ERRMSG_SIZE - 1, &dummy );
if( !sqlStatusOK( sqlStatus ) && errorLevel == SQL_ERRLVL_STMT )
/* If we couldn't get info at the statement-handle level, try again
at the connection handle level */
sqlStatus = SQLGetDiagRec( SQL_HANDLE_DBC, dbmsInfo->hDbc, 1,
szSqlState, &dwNativeError,
dbmsInfo->errorMessage,
MAX_ERRMSG_SIZE - 1, &dummy );
if( !sqlStatusOK( sqlStatus ) )
{
assert( NOTREACHED ); /* Catch this if it ever occurs */
strcpy( dbmsInfo->errorMessage, "Couldn't get error information "
"from database backend" );
return( CRYPT_ERROR_FAILED );
}
#endif /* ODBC1x */
/* Check for a not-found error status. We can also get an sqlStatus of
SQL_NO_DATA with SQLSTATE set to "00000" and the error message string
empty in some cases, in which case we provide our own error string */
if( !strncmp( szSqlState, "S0002", 5 ) || /* ODBC 2.x */
!strncmp( szSqlState, "42S02", 5 ) || /* ODBC 3.x */
( !strncmp( szSqlState, "00000", 5 ) && \
sqlStatus == SQL_NO_DATA ) )
{
/* Make sure that the caller gets a sensible error message if they
try to examine the extended error information */
if( !*dbmsInfo->errorMessage )
strcpy( dbmsInfo->errorMessage, "No data found" );
return( CRYPT_ERROR_NOTFOUND );
}
/* When we're trying to create a new keyset, there may already be one
present giving an S0001 (table already exists) or S0011 (index
already exists) error . We could check for the table by doing a
dummy read, but it's easier to just try the update anyway and convert
the error code to the correct value here if there's a problem */
if( !strncmp( szSqlState, "S0001", 5 ) ||
!strncmp( szSqlState, "S0011", 5 ) || /* ODBC 2.x */
!strncmp( szSqlState, "42S01", 5 ) ||
!strncmp( szSqlState, "42S11", 5 ) ) /* ODBX 3.x */
return( CRYPT_ERROR_DUPLICATE );
/* This one is a bit odd: An integrity constraint violation occurred,
which means (among other things) that an attempt was made to write a
duplicate value to a column constrained to contain unique values. It
can also include things like writing a NULL value to a column
constrained to be NOT NULL, but this wouldn't normally happen so we
can convert this one to a duplicate data error */
if( !strncmp( szSqlState, "23000", 5 ) )
return( CRYPT_ERROR_DUPLICATE );
return( defaultStatus );
}
/* Rewrite the SQL query to handle the back-end specific blob and date type,
and work around problems with some back-end types (and we're specifically
talking Access here) */
static void convertQuery( DBMS_STATE_INFO *dbmsInfo, char *query,
const char *command )
{
SQLRETURN sqlStatus;
SQLSMALLINT bufLen;
char *keywordPtr, buffer[ 128 ];
assert( command != NULL );
strcpy( query, command );
/* If it's a CREATE TABLE command, rewrite the blob and date types to
the appropriate values for the database backend */
if( !strncmp( command, "CREATE TABLE", 12 ) )
{
char *placeholderPtr;
if( ( placeholderPtr = strstr( query, " BLOB" ) ) != NULL )
{
const int nameLen = strlen( dbmsInfo->blobName );
/* Open up a gap and replace the blob name placeholder with the
actual blob name */
memmove( placeholderPtr + 1 + nameLen, placeholderPtr + 5,
strlen( placeholderPtr + 5 ) + 1 );
memcpy( placeholderPtr + 1, dbmsInfo->blobName, nameLen );
}
if( ( placeholderPtr = strstr( query, " DATETIME" ) ) != NULL )
{
const int nameLen = strlen( dbmsInfo->dateTimeName );
/* Open up a gap and replace the date name placeholder with the
actual date name */
memmove( placeholderPtr + 1 + nameLen, placeholderPtr + 9,
strlen( placeholderPtr + 9 ) + 1 );
memcpy( placeholderPtr + 1, dbmsInfo->dateTimeName, nameLen );
}
}
/* If it's not a SELECT/DELETE with wildcards used, there's nothing to
do */
if( ( strncmp( query, "SELECT", 6 ) && strncmp( query, "DELETE", 6 ) ) || \
strstr( query, " LIKE " ) == NULL )
return;
/* It's a potential problem command, check for the presence of Access */
sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_DBMS_NAME, buffer,
sizeof( buffer ), &bufLen );
if( sqlStatusOK( sqlStatus ) && \
strCompare( buffer, "Access", 6 ) )
return;
/* Unlike everything else in the known universe, Access uses * and ?
instead of the standard SQL wildcards so if we find a LIKE ... %
we rewrite the % as a * */
if( ( keywordPtr = strstr( query, " LIKE " ) ) != NULL )
{
int i;
/* Search up to 6 characters ahead for a wildcard and replace it
with the one needed by Access if we find it. We search 6 chars
ahead because the higher-level SQL code uses expressions like
"SELECT .... WHERE foo LIKE '--%'", which is 5 chars plus one as
a safety margin */
for( i = 7; i < 11 && keywordPtr[ i ]; i++ )
if( keywordPtr[ i ] == '%' )
keywordPtr[ i ] = '*';
}
}
/* Get data type info for this data source. Since SQLGetTypeInfo() returns
a variable (and arbitrary) length result set, we have to call
SQLCloseCursor() after each fetch before performing a new query */
static int getBlobInfo( DBMS_STATE_INFO *dbmsInfo, const SQLSMALLINT type )
{
const SQLHSTMT hStmt = dbmsInfo->hStmt[ 0 ];
SQLRETURN sqlStatus;
SQLUINTEGER length;
SQLINTEGER count;
/* Check for support for the requested blob type and get the results of
the transaction. If the database doesn't support this, we'll get an
SQL_NO_DATA status */
sqlStatus = SQLGetTypeInfo( hStmt, type );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLFetch( hStmt );
if( !sqlStatusOK( sqlStatus ) )
return( CRYPT_ERROR );
/* Get the type name (result column 1) and column size (= maximum
possible field length, result column 3). We only check the second
return code since they both apply to the same row */
SQLGetData( hStmt, 1, SQL_C_CHAR, dbmsInfo->blobName,
CRYPT_MAX_TEXTSIZE, &length );
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG, &count,
sizeof( SQLINTEGER ), &length );
SQLCloseCursor( hStmt );
if( !sqlStatusOK( sqlStatus ) )
return( CRYPT_ERROR );
/* We've got the blob type, remember the details */
if( type == SQL_LONGVARBINARY )
dbmsInfo->hasBinaryBlobs = TRUE;
dbmsInfo->blobType = type;
return( count );
}
static int getDatatypeInfo( DBMS_STATE_INFO *dbmsInfo, int *featureFlags )
{
const SQLHSTMT hStmt = dbmsInfo->hStmt[ 0 ];
SQLRETURN sqlStatus;
SQLSMALLINT bufLen;
SQLUSMALLINT transactBehaviour;
SQLINTEGER attrLength;
SQLUINTEGER privileges;
char buffer[ 8 ];
int count;
/* First we see what the back-end's blob data type is. Usually it'll
be binary blobs, if that doesn't work we try for char blobs */
count = getBlobInfo( dbmsInfo, SQL_LONGVARBINARY );
if( cryptStatusError( count ) )
count = getBlobInfo( dbmsInfo, SQL_LONGVARCHAR );
if( cryptStatusError( count ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_OPEN ) );
if( dbmsInfo->hasBinaryBlobs )
*featureFlags |= DBMS_HAS_BINARYBLOBS;
/* If we couldn't get a blob type or the type is too short to use,
report it back as a database open failure */
if( count < MAX_ENCODED_CERT_SIZE )
{
sprintf( dbmsInfo->errorMessage, "Database blob type can only "
"store %d bytes, we need at least %d", count,
MAX_ENCODED_CERT_SIZE );
return( CRYPT_ERROR_OPEN );
}
/* Sanity check, make sure that the source can return the required
amount of data. A number of data sources don't support this
attribute (it's mostly meant to be set by applications rather than
being read, and is intended to be used to reduce network traffic)
so we don't worry if it's not available. In addition to the maximum-
size check we also have to perform a minimum-size check, since a
value of zero is used to indicate no length limit */
sqlStatus = SQLGetStmtAttr( hStmt, SQL_ATTR_MAX_LENGTH,
( SQLPOINTER ) &attrLength, SQL_IS_INTEGER,
NULL );
if( sqlStatusOK( sqlStatus ) && \
attrLength > 0 && attrLength < MAX_SQL_QUERY_SIZE )
{
sprintf( dbmsInfo->errorMessage, "Database back-end can only "
"transmit %d bytes per message, we need at least %d",
attrLength, MAX_SQL_QUERY_SIZE );
return( CRYPT_ERROR_OPEN );
}
/* Now do the same thing for the date+time data type. This changed from
SQL_TIMESTAMP in ODBC 2.x to SQL_TYPE_TIMESTAMP in ODBC 3.x, since 3.x
will be more common we try the 3.x version first and if that fails
fall back to 2.x */
sqlStatus = SQLGetTypeInfo( hStmt, SQL_TYPE_TIMESTAMP );
if( !sqlStatusOK( sqlStatus ) )
sqlStatus = SQLGetTypeInfo( hStmt, SQL_TIMESTAMP );
if( sqlStatusOK( sqlStatus ) )
{
SQLUINTEGER length;
/* Fetch the results of the transaction and get the type name (result
column 1) and column size (result column 3). The column size
argument is quite problematic because although some back-ends
have a fixed size for this (and usually ignore the column-size
parameter), others allow multiple time representations and
require an explicit column-size indicator to decide which one
they should use. The ODBC standard representation for example
uses 19 chars (yyyy-mm-dd hh:mm:ss) for the full date+time that
we use here, but also allows a 16-char version without the seconds
and a 20+n-char version for n digits of fractional seconds. The
back-end however may use a completely different value, for
example Oracle encodes the full date+time as 7 bytes (century,
year, month, day, hour, minute, second). To get around this we
get the first column-size value (which is usually the only one
available), if this is the same as the ODBC standard minimum-size
column we try for more results to see if the full date+time form
is available, and use that if it is */
sqlStatus = SQLFetch( hStmt );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLGetData( hStmt, 1, SQL_C_CHAR,
dbmsInfo->dateTimeName,
CRYPT_MAX_TEXTSIZE, &length );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG,
&dbmsInfo->dateTimeNameColSize,
sizeof( SQLINTEGER ), &length );
if( sqlStatusOK( sqlStatus ) && \
dbmsInfo->dateTimeNameColSize == 16 )
{
SQLINTEGER columnSize;
/* Some back-ends allow multiple formats for the date+time
column, if the back-end reports the short (no-seconds) ODBC-
default format see whether it'll support the longer (with
seconds) format instead */
sqlStatus = SQLFetch( hStmt );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG,
&columnSize, sizeof( SQLINTEGER ),
&length );
if( sqlStatusOK( sqlStatus ) && columnSize == 19 )
dbmsInfo->dateTimeNameColSize = columnSize;
}
SQLCloseCursor( hStmt );
}
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_OPEN ) );
#if 0 /* Not needed, we always supply the length at bind time */
/* Determine whether we can supply the length of blob data at
parameter bind time (result = 'Y') or we have to defer it to
statement execution time (result = 'N') */
sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_NEED_LONG_DATA_LEN,
buffer, sizeof( buffer ), &bufLen );
if( sqlStatusOK( sqlStatus ) )
dbmsInfo->needLongLength = ( *buffer == 'Y' ) ? TRUE : FALSE;
else
dbmsInfo->needLongLength = TRUE; /* Make a paranoid guess */
#endif /* 0 */
/* Determine whether we can write to the database (result = 'Y') or not
(result = 'N') */
sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_DATA_SOURCE_READ_ONLY,
buffer, sizeof( buffer ), &bufLen );
if( sqlStatusOK( sqlStatus ) && *buffer == 'Y' )
*featureFlags |= DBMS_HAS_NOWRITE;
/* Determine whether GRANT/REVOKE capabilities are available. This gets
a bit messy because it only specifies which extended GRANT/REVOKE
options are available, rather than whether GRANT/REVOKE is available
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_HAS_PRIVILEGES;
sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_SQL92_REVOKE,
( SQLPOINTER ) &privileges,
sizeof( SQLUINTEGER ), &bufLen );
if( sqlStatusOK( sqlStatus ) && privileges )
*featureFlags |= DBMS_HAS_PRIVILEGES;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -