📄 odbc.c
字号:
and SQL Server) may support multiple time representations and
require an explicit length indicator to decide which one they
should use. This means that we have to provide an explicit
length value as a hint to the driver, see the comment in
getDatatypeInfo() for how this is obtained */
memset( timestampStorage, 0, sizeof( SQL_TIMESTAMP_STRUCT ) );
timestampStorage->year = timeInfoPtr->tm_year + 1900;
timestampStorage->month = timeInfoPtr->tm_mon + 1;
timestampStorage->day = timeInfoPtr->tm_mday;
timestampStorage->hour = timeInfoPtr->tm_hour;
timestampStorage->minute = timeInfoPtr->tm_min;
timestampStorage->second = timeInfoPtr->tm_sec;
sqlStatus = SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
SQL_C_TYPE_TIMESTAMP, SQL_TYPE_TIMESTAMP,
dbmsInfo->dateTimeNameColSize, 0,
timestampStorage, 0, NULL );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_BADDATA ) );
continue;
}
assert( ( boundDataPtr->dataLength == 0 ) || \
isReadPtr( boundDataPtr->data, boundDataPtr->dataLength ) );
REQUIRES( boundDataPtr->type == BOUND_DATA_STRING || \
boundDataPtr->type == BOUND_DATA_BLOB );
REQUIRES( dbmsInfo->hasBinaryBlobs || \
( !dbmsInfo->hasBinaryBlobs && \
boundDataPtr->type == BOUND_DATA_STRING ) );
REQUIRES( ( boundDataPtr == NULL ) || \
( boundDataPtr != NULL && \
( boundDataPtr->data == NULL && \
boundDataPtr->dataLength == 0 ) || \
( boundDataPtr->data != NULL && \
boundDataPtr->dataLength > 0 &&
boundDataPtr->dataLength < MAX_INTLENGTH_SHORT ) ) );
/* Bound data of { NULL, 0 } denotes a null parameter */
/* Null parameters have to be handled specially. Note that we have
to set the ColumnSize parameter (no.6) to a nonzero value (even
though it's ignored, since this is a null parameter) otherwise
some drivers will complain about an "Invalid precision value" */
if( boundDataPtr->dataLength <= 0 )
{
static const SQLINTEGER nullDataValue = SQL_NULL_DATA;
sqlStatus = SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_C_CHAR, 1, 0, NULL, 0,
( SQLINTEGER * ) &nullDataValue );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_BADDATA ) );
continue;
}
/* Bind in the query data */
if( boundDataPtr->type == BOUND_DATA_BLOB )
{
valueType = SQL_C_BINARY;
parameterType = dbmsInfo->blobType;
}
else
valueType = parameterType = SQL_C_CHAR;
boundDataState->lengthStorage[ i ] = boundDataPtr->dataLength;
sqlStatus = SQLBindParameter( hStmt, paramNo++, SQL_PARAM_INPUT,
valueType, parameterType,
boundDataPtr->dataLength, 0,
( SQLPOINTER ) boundDataPtr->data,
boundDataPtr->dataLength,
&boundDataState->lengthStorage[ i ] );
if( !sqlStatusOK( sqlStatus ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_BADDATA ) );
}
ENSURES( i < BOUND_DATA_MAXITEMS );
return( CRYPT_OK );
}
/****************************************************************************
* *
* Get Database Back-end Information *
* *
****************************************************************************/
/* Get data type information 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 */
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3 ) ) \
static int getBlobInfo( INOUT DBMS_STATE_INFO *dbmsInfo,
const SQLSMALLINT type,
OUT_LENGTH_SHORT_Z int *maxFieldSize )
{
const SQLHSTMT hStmt = dbmsInfo->hStmt[ DBMS_CACHEDQUERY_NONE ];
SQLRETURN sqlStatus;
SQLUINTEGER blobNameLength, dummy;
SQLINTEGER count;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
/* Clear return value */
*maxFieldSize = 0;
/* Check for support for the requested 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, &blobNameLength );
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG, &count,
sizeof( SQLINTEGER ), &dummy );
SQLCloseCursor( hStmt );
if( !sqlStatusOK( sqlStatus ) )
return( CRYPT_ERROR );
dbmsInfo->blobNameLength = ( int ) blobNameLength;
#ifdef __UNIX__
if( dummy != sizeof( SQLINTEGER ) )
{
fprintf( stderr, "\ncryptlib: The ODBC driver is erroneously "
"returning a %d-byte integer value\n when a "
"%d-byte SQLINTEGER value is requested, which will "
"overwrite\n adjacent memory locations. To fix "
"this you need to recompile\n with whatever "
"preprocessor options your ODBC header files require\n"
" to force the use of 64-bit ODBC data types (and "
"report this issue\n to the ODBC driver vendor so "
"that they can sync the driver and\n headers)."
"\n\n", dummy, sizeof( SQLINTEGER ) );
}
#endif /* __UNIX__ */
*maxFieldSize = count;
/* We've got the type information, remember the details. Postgres has
problems handling blobs via ODBC (or even in general) since it uses
its own BYTEA (byte array) type that's not really usable as an SQL
blob type because of weird escaping requirements when
sending/receiving data. In addition it requires other Postgres-
specific oddities like specifying 'ByteaAsLongVarBinary=1' in the
connect string. So even though the back-end sort-of supports blobs
we can't actually use them */
if( ( type == SQL_LONGVARBINARY ) && \
( dbmsInfo->backendType != DBMS_POSTGRES ) )
dbmsInfo->hasBinaryBlobs = TRUE;
dbmsInfo->blobType = type;
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
static int getDateTimeInfo( INOUT DBMS_STATE_INFO *dbmsInfo )
{
const SQLHSTMT hStmt = dbmsInfo->hStmt[ DBMS_CACHEDQUERY_NONE ];
SQLRETURN sqlStatus;
SQLUINTEGER dateTimeNameLength, dummy;
SQLINTEGER columnSize;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
/* The Postgres driver doesn't correctly detect the date/time type used
by the back-end so we have to hard-code in the actual value */
if( dbmsInfo->backendType == DBMS_POSTGRES )
{
memcpy( dbmsInfo->dateTimeName, "TIMESTAMP", 9 );
dbmsInfo->dateTimeNameLength = 9;
dbmsInfo->dateTimeNameColSize = 16;
return( CRYPT_OK );
}
/* Get information on the back-end's 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 ) )
{
assert( DEBUG_WARN ); /* Warn of absenceof ODBC 3.0 types */
sqlStatus = SQLGetTypeInfo( hStmt, SQL_TIMESTAMP );
}
if( !sqlStatusOK( sqlStatus ) )
return( CRYPT_ERROR );
/* Fetch the results of the transaction and get the type name (result
column 1) and column size (result column 3) */
sqlStatus = SQLFetch( hStmt );
if( !sqlStatusOK( sqlStatus ) )
{
SQLCloseCursor( hStmt );
return( CRYPT_ERROR );
}
sqlStatus = SQLGetData( hStmt, 1, SQL_C_CHAR, dbmsInfo->dateTimeName,
CRYPT_MAX_TEXTSIZE, &dateTimeNameLength );
if( sqlStatusOK( sqlStatus ) )
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG,
&dbmsInfo->dateTimeNameColSize,
sizeof( SQLINTEGER ), &dummy );
if( !sqlStatusOK( sqlStatus ) )
{
SQLCloseCursor( hStmt );
return( CRYPT_ERROR );
}
dbmsInfo->dateTimeNameLength = ( int ) dateTimeNameLength;
/* 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) and 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 */
if( dbmsInfo->dateTimeNameColSize != 16 )
{
/* This isn't a potentially problematic column size, we're done */
SQLCloseCursor( hStmt );
return( CRYPT_OK );
}
/* If the back-end has reported the short (no-seconds) ODBC-default
format, see whether it'll support the longer (with seconds) format
instead */
sqlStatus = SQLFetch( hStmt );
if( !sqlStatusOK( sqlStatus ) )
{
SQLCloseCursor( hStmt );
return( CRYPT_ERROR );
}
sqlStatus = SQLGetData( hStmt, 3, SQL_C_SLONG, &columnSize,
sizeof( SQLINTEGER ), &dummy );
if( sqlStatusOK( sqlStatus ) && columnSize == 19 )
dbmsInfo->dateTimeNameColSize = columnSize;
SQLCloseCursor( hStmt );
return( CRYPT_OK );
}
CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
static int getDatatypeInfo( INOUT DBMS_STATE_INFO *dbmsInfo,
OUT_FLAGS_Z( DBMS ) int *featureFlags )
{
ERROR_INFO *errorInfo = &dbmsInfo->errorInfo;
const SQLHSTMT hStmt = dbmsInfo->hStmt[ DBMS_CACHEDQUERY_NONE ];
SQLRETURN sqlStatus;
SQLSMALLINT bufLen;
SQLUSMALLINT transactBehaviour;
SQLINTEGER attrLength;
SQLUINTEGER privileges;
char buffer[ 8 + 8 ];
int maxBlobSize, status;
assert( isWritePtr( dbmsInfo, sizeof( DBMS_STATE_INFO ) ) );
assert( isWritePtr( featureFlags, sizeof( int ) ) );
/* Clear return value */
*featureFlags = DBMS_FEATURE_FLAG_NONE;
/* 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 */
status = getBlobInfo( dbmsInfo, SQL_LONGVARBINARY, &maxBlobSize );
if( cryptStatusError( status ) )
status = getBlobInfo( dbmsInfo, SQL_LONGVARCHAR, &maxBlobSize );
if( cryptStatusError( status ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_OPEN ) );
if( dbmsInfo->hasBinaryBlobs )
*featureFlags |= DBMS_FEATURE_FLAG_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( maxBlobSize < MAX_ENCODED_CERT_SIZE )
{
char errorMessage[ 128 + 8 ];
int errorMessageLength;
errorMessageLength = \
sprintf_s( errorMessage, 128,
"Database blob type can only store %d bytes, we need "
"at least %d", maxBlobSize, MAX_ENCODED_CERT_SIZE );
setErrorString( errorInfo, errorMessage, errorMessageLength );
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) and
in addition the maximum query size is pretty short (the longest is a
few hundred bytes for the table creation commands) 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 )
{
char errorMessage[ 128 + 8 ];
int errorMessageLength;
errorMessageLength = \
sprintf_s( errorMessage, 128,
"Database back-end can only transmit %d bytes per "
"message, we need at least %d", attrLength,
MAX_SQL_QUERY_SIZE );
setErrorString( errorInfo, errorMessage, errorMessageLength );
return( CRYPT_ERROR_OPEN );
}
/* Now do the same thing for the date+time data type */
status = getDateTimeInfo( dbmsInfo );
if( cryptStatusError( status ) )
return( getErrorInfo( dbmsInfo, SQL_ERRLVL_STMT, hStmt,
CRYPT_ERROR_OPEN ) );
/* Determine whether we can write to the database (result = 'Y') or not
(result = 'N') */
sqlStatus = SQLGetInfo( dbmsInfo->hDbc, SQL_DATA_SOURCE_READ_ONLY,
buffer, 8, &bufLen );
if( sqlStatusOK( sqlStatus ) && *buffer == 'Y' )
*featureFlags |= DBMS_FEATURE_FLAG_READONLY;
/* 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -