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

📄 odbc.c

📁 cryptlib是功能强大的安全工具集。允许开发人员快速在自己的软件中集成加密和认证服务。
💻 C
📖 第 1 页 / 共 4 页
字号:
/* 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 + -