⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 odbc.c

📁 cryptlib安全工具包
💻 C
📖 第 1 页 / 共 5 页
字号:
			   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 + -