/****************************************************************************
*																			*
*						Network Stream I/O Functions						*
*						Copyright Peter Gutmann 1993-2005					*
*																			*
****************************************************************************/

#if defined( INC_ALL )
  #include "stream.h"
#else
  #include "io/stream.h"
#endif /* Compiler-specific includes */

#ifdef USE_TCP

/* Network streams can work on multiple levels.  At the lowest level we have
   the raw network I/O layer, handled by calling setAccessMethodXXX(), which
   hooks up the transport-level I/O functions.  If there's a requirement to
   replace the built-in network I/O, it can be done by replacing the
   functionality at this level.

   Layered on top of the transport-level I/O via setStreamLayerXXX() is an
   optional higher layer protocol such as HTTP, which is added by calling
   the appropriate function to layer the higher-level protocol over the
   transport-level I/O.  Alternatively, we can use setStreamLayerDirect()
   to just pass the call straight down to the transport layer.

   In addition to these two layers, the higher level read requires an extra
   buffering layer in order to avoid making many calls to the transport-
   level I/O function, which is a particular problem for HTTP which has to
   take input a character at a time.  To avoid this problem, we use the
   bufferedRead layer which reads ahead as far as it can and then feeds the
   buffered result back to the caller as required.  We also need to use write
   buffering to avoid potential problems with interactions with some
   transport layers, details are given in the comment for the buffered write
   function.

   The layering looks as follows:

	--- httpRead ---+-- bufferedRead ---+--- tcpRead
		cmpRead							|
										+--- clibRead
										|
	------------------------------------+--- otherRead

	--- httpWrite --+-- bufferedWrite --+---- tcpWrite
		cmpWrite						|
										+---- clibWrite
										|
	------------------------------------+---- otherWrite

   When we allocate the readahead/write buffers we try and make them an
   optimal size to minimise unnecessary copying and not negatively affect
   network I/O.  If we make them too big, we'll have to move too much data
   around when we partially empty them.  If we make them too small, the
   buffering effect is suboptimal.  Since what we're buffering is PKI
   traffic, a 4K buffer should get most messages in one go.  This also
   matches many network stacks that use 4K I/O buffers, the BSD default */

#define NETWORK_BUFFER_SIZE		4096

/****************************************************************************
*																			*
*								Utility Functions							*
*																			*
****************************************************************************/

/* Copy error information from a cryptlib transport-layer session into a
   stream */

static int getSessionErrorInfo( STREAM *stream, const int errorStatus )
	{
	MESSAGE_DATA msgData;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	status = krnlSendMessage( stream->iTransportSession,
							  IMESSAGE_GETATTRIBUTE, 
							  &stream->errorInfo->errorCode,
							  CRYPT_ATTRIBUTE_INT_ERRORCODE );
	if( cryptStatusError( status ) )
		stream->errorInfo->errorCode = CRYPT_OK;
	setMessageData( &msgData, stream->errorInfo->errorString, 
					MAX_ERRMSG_SIZE );
	krnlSendMessage( stream->iTransportSession, IMESSAGE_GETATTRIBUTE,
					 &msgData, CRYPT_ATTRIBUTE_INT_ERRORMESSAGE );
	return( errorStatus );
	}

#if defined( __WIN32__ ) && !defined( NDEBUG ) && 0

/* Code to test HTTP header parsing, call from just before the 
   openConnection() call in completeConnect() */

static int transportFileFunction( STREAM *stream, void *buffer,
								  const int length, const int flags )
	{
	FILE *filePtr = ( FILE * ) stream->callbackFunction;

	return( fread( buffer, 1, length, filePtr ) );
	}

static void testHttp( STREAM *stream )
	{
	STREAM streamCopy;
	ERROR_INFO errorInfoCopy;
	FILE *reportFile = stdout;
	void *buffer;
	int i, status;

	stream->protocol = STREAM_PROTOCOL_HTTP_TRANSACTION;
	stream->transportReadFunction = transportFileFunction;
	if( ( buffer = clAlloc( "", 16384 ) ) == NULL )
		{
		puts( "Out of memory." );
		return;
		}
#if 1
	reportFile = fopen( "r:/http_report.txt", "w" );
#endif
	if( reportFile == NULL )
		{
		printf( "Couldn't open file for report, hit a key." );
		getchar();
		putchar( '\n' );
		exit( EXIT_FAILURE );
		}
//	for( i = 1455; i <= 2000; i++ )
//	for( i = 0; i < 1000; i++ )
//	for( i = 1000; i < 1999; i++ )
//	for( i = 2000; i < 2999; i++ )
//	for( i = 3000; i < 3999; i++ )
	for( i = 0; i <= 3965; i++ )
		{
		FILE *filePtr;
		char fileName[ 128 ];

		sprintf( fileName, "d:/tmp/testcases/%08d", i );
		filePtr = fopen( fileName, "rb" );
		if( filePtr == NULL )
			{
			printf( "Failed to open file #%d, hit a key.", i );
			getchar();
			putchar( '\n' );
			continue;
			}
		memcpy( &streamCopy, stream, sizeof( STREAM ) );
		memcpy( &errorInfoCopy, stream->errorInfo, sizeof( ERROR_INFO ) );
		stream->callbackFunction = ( CALLBACKFUNCTION ) filePtr;/* Kludge */
		fprintf( reportFile, "%04d: ", i );
		if( reportFile != stdout )
			{
			if( !( i % 10 ) )
				putchar( '\n' );
			printf( "%04d ", i );
			}
		status = sread( stream, buffer, 16384 );
		fclose( filePtr );
		if( !cryptStatusError( status ) )
			fprintf( reportFile, 
					 "%d: cryptlib error: HTTP error not detected.\n", 
					 status );
		else
			fprintf( reportFile, "%d %s.\n", status, 
					stream->errorInfo->errorString );
		fflush( reportFile );
		memcpy( stream, &streamCopy, sizeof( STREAM ) );
		memcpy( stream->errorInfo, &errorInfoCopy, sizeof( ERROR_INFO ) );
		}
	if( reportFile != stdout )
		fclose( reportFile );
	putchar( '\n' );
	}
#else
  #define testHttp( stream )
#endif /* Win32 debug build only */

/****************************************************************************
*																			*
*							URL Processing Functions						*
*																			*
****************************************************************************/

/* Parse a URI into:

	<schema>://[<user>@]<host>[:<port>]/<path>[?<query>] components

   This function is intended for use from the internal interface (i.e. to
   parse URLs supplied by the caller to the cryptlib API), and not so much
   for the external interface (i.e. URLs supplied by remote systems for
   processing by cryptlib).  Because of this it's rather more liberal with
   what it'll accept than a generic URL parser would be */

static int parseURL( URL_INFO *urlInfo, const char *url, const int urlLen,
					 const int defaultPort, const URL_TYPE urlTypeHint )
	{
	typedef struct {
		const char *schema;
		const int schemaLength;
		const URL_TYPE type;
		} URL_SCHEMA_INFO;
	static const URL_SCHEMA_INFO FAR_BSS urlSchemaInfo[] = {
		{ "http://", 7, URL_TYPE_HTTP },
		{ "https://", 8, URL_TYPE_HTTPS },
		{ "ssh://", 6, URL_TYPE_SSH },
		{ "scp://", 6, URL_TYPE_SSH },
		{ "sftp://", 7, URL_TYPE_SSH },
		{ "cmp://", 6, URL_TYPE_CMP },
		{ "tsp://", 6, URL_TYPE_TSP },
		{ NULL, 0, URL_TYPE_NONE }, { NULL, 0, URL_TYPE_NONE }
		};
	char *strPtr;
	int offset, length;

	assert( isWritePtr( urlInfo, sizeof( URL_INFO ) ) );
	assert( isReadPtr( url, urlLen ) );
	assert( defaultPort == CRYPT_UNUSED || \
			( defaultPort >= 22 && defaultPort <= 65536 ) );
	assert( urlTypeHint >= URL_TYPE_NONE && urlTypeHint < URL_TYPE_LAST );

	/* Clear return values */
	memset( urlInfo, 0, sizeof( URL_INFO ) );
	if( defaultPort != CRYPT_UNUSED )
		urlInfo->port = defaultPort;

	/* Skip leading and trailing whitespace and syntactic sugar */
	length = strStripWhitespace( &strPtr, url, urlLen );
	if( length <= 0 )
		return( CRYPT_ERROR_BADDATA );
	if( length >= MAX_URL_SIZE )
		return( CRYPT_ERROR_OVERFLOW );
	if( ( offset = strFindStr( strPtr, length, "://", 3 ) ) >= 0 )
		{
		int i;

		/* Extract the URI schema */
		urlInfo->schema = strPtr;
		urlInfo->schemaLen = offset + 3;
		length -= offset + 3;
		if( length <= 0 )
			return( CRYPT_ERROR_BADDATA );
		strPtr += offset + 3;
		length = strStripWhitespace( &strPtr, strPtr, length );
		if( length <= 0 )
			return( CRYPT_ERROR_BADDATA );

		/* Check whether the schema is one that we recognise */
		for( i = 0; 
			 urlSchemaInfo[ i ].type != URL_TYPE_NONE && \
				i < FAILSAFE_ARRAYSIZE( urlSchemaInfo, URL_SCHEMA_INFO ); 
			 i++ )
			{
			if( urlSchemaInfo[ i ].schemaLength == urlInfo->schemaLen && \
				!strCompare( urlSchemaInfo[ i ].schema, urlInfo->schema,
							 urlInfo->schemaLen ) )
				break;
			}
		if( i >= FAILSAFE_ARRAYSIZE( urlSchemaInfo, URL_SCHEMA_INFO ) )
			retIntError();
		urlInfo->type = urlSchemaInfo[ i ].type;

		/* If there's a URL hint given, make sure that the URL type matches */
		if( urlTypeHint != URL_TYPE_NONE )
			{
			switch( urlTypeHint )
				{
				case URL_TYPE_HTTP:
					/* An explicit HTTP URL must really be HTTP and not 
					   just a generic HTTP/HTTPS mix */
					if( urlInfo->type != URL_TYPE_HTTP )
						return( CRYPT_ERROR_BADDATA );
					break;

				case URL_TYPE_HTTPS:
					/* A requirement for an HTTPS URL can also match an HTTP
					   URL - this type is used for SSL, where the use of 
					   HTTPS is implied by the fact that an SSL session is 
					   being used even if it's a straight HTTP URL */
					if( urlInfo->type != URL_TYPE_HTTP && \
						urlInfo->type != URL_TYPE_HTTPS )
						return( CRYPT_ERROR_BADDATA );
					break;

				case URL_TYPE_SSH:
					if( urlInfo->type != URL_TYPE_SSH )
						return( CRYPT_ERROR_BADDATA );
					break;

				case URL_TYPE_CMP:
					if( urlInfo->type != URL_TYPE_CMP )
						return( CRYPT_ERROR_BADDATA );
					break;

				case URL_TYPE_TSP:
					if( urlInfo->type != URL_TYPE_TSP )
						return( CRYPT_ERROR_BADDATA );
					break;

				default:
					retIntError();
				}
			}
		}

	/* Check for user info before an '@' sign */
	if( ( offset = strFindCh( strPtr, length, '@' ) ) >= 0 )
		{
		/* Extract the user info */
		urlInfo->userInfoLen = \
					strStripWhitespace( ( char ** ) &urlInfo->userInfo,
										strPtr, offset );
		length -= offset + 1;
		if( length <= 0 || urlInfo->userInfoLen <= 0 )
			return( CRYPT_ERROR_BADDATA );
		strPtr += offset + 1;
		length = strStripWhitespace( &strPtr, strPtr, length );
		if( length <= 0 )
			return( CRYPT_ERROR_BADDATA );
		}

	/* IPv6 addresses use colons in their string representation, RFC 2732
	   requires that IPv6 addresses in URLs be delimited by square brackets
	   so if we find one at the start of the URI we treat it as an IPv6
	   address */
	if( *strPtr == '[' && \
		( length != 12 || strCompareZ( strPtr, "[Autodetect]" ) ) )
		{
		/* Strip the leading '[' delimiter */
		length = strStripWhitespace( &strPtr, strPtr + 1, length - 1 );
		if( length <= 0 )
			return( CRYPT_ERROR_BADDATA );

		/* Locate the end of the RFC 2732 IPv6 address.  Trailing whitespace
		   will be stripped later */
		if( ( offset = strFindCh( strPtr, length, ']' ) ) <= 0 )
			return( CRYPT_ERROR_BADDATA );
		urlInfo->host = strPtr;
		urlInfo->hostLen = offset;
		strPtr += offset + 1;
		length -= offset + 1;
		}
	else
		{
		int offset2;

		/* It's a non-IPv6 host name, check whether there's anything
		   following the name */
		urlInfo->host = strPtr;
		offset = strFindCh( strPtr, length, ':' );
		offset2 = strFindCh( strPtr, length, '/' );
		if( offset < 0 )
			offset = offset2;
		else
			{
			assert( offset >= 0 );
			if( offset2 >= 0 )
				offset = min( offset, offset2 );
			}
		if( offset <= 0 )
			{
			/* It's a standalone server name, we're done */
			urlInfo->hostLen = length;
			return( CRYPT_OK );
			}

		/* There's port/location info following the server name.  Trailing
		   whitespace will be stripped later */
		urlInfo->hostLen = offset;
		strPtr += offset;
		length -= offset;
		}
	urlInfo->hostLen = strStripWhitespace( ( char ** ) &urlInfo->host,
										   urlInfo->host, urlInfo->hostLen );
	if( urlInfo->hostLen <= 0 )
		return( CRYPT_ERROR_BADDATA );

	/* If there's nothing beyond the host name, we're done */
	if( length <= 0 )
		return( CRYPT_OK );
	length = strStripWhitespace( &strPtr, strPtr, length );
	if( length <= 0 )
		return( CRYPT_ERROR_BADDATA );

	/* Parse the remainder of the URI into port/location */
	if( *strPtr == ':' )
		{
		char portBuffer[ 16 + 8 ];
		const int portStrLen = min( length - 1, 15 );
		int port;

		/* Get the port to connect to.  If it's an invalid port we ignore it
		   and use the default one, which was set earlier */
		if( portStrLen <= 0 )
			return( CRYPT_ERROR_BADDATA );
		memcpy( portBuffer, strPtr + 1, portStrLen );
		portBuffer[ portStrLen ] = '\0';
		port = atoi( portBuffer );
		if( port >= 22 && port < 65535 )
			urlInfo->port = port;
		}
	if( ( offset = strFindCh( strPtr, length, '/' ) ) >= 0 )
		{
		const int locationLength = length - offset;

		if( locationLength <= 0 )
			return( CRYPT_ERROR_BADDATA );
		urlInfo->locationLen = \
					strStripWhitespace( ( char ** ) &urlInfo->location,
										strPtr + offset, locationLength );
		if( urlInfo->locationLen <= 0 )
			return( CRYPT_ERROR_BADDATA );
		}

	return( CRYPT_OK );
	}

/* Copy parsed URL info to a stream structure */

static int copyUrlToStream( STREAM *stream, const URL_INFO *urlInfo )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( urlInfo, sizeof( URL_INFO ) ) );

	if( ( stream->host = clAlloc( "copyUrlToStream", \
								  urlInfo->hostLen ) ) == NULL )
		return( CRYPT_ERROR_MEMORY );
	memcpy( stream->host, urlInfo->host, urlInfo->hostLen );
	stream->hostLen = urlInfo->hostLen;
	if( urlInfo->location != NULL )
		{
		if( ( stream->path = \
				clAlloc( "copyUrlToStream", urlInfo->locationLen ) ) == NULL )
			{
			clFree( "copyUrlToStream", stream->host );
			return( CRYPT_ERROR_MEMORY );
			}
		memcpy( stream->path, urlInfo->location, urlInfo->locationLen );
		stream->pathLen = urlInfo->locationLen;
		}
	stream->port = urlInfo->port;

	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Transport-layer Functions						*
*																			*
****************************************************************************/

/* Map the upper-layer I/O functions directly to the transport-layer
   equivalent.  This is used if we're performing raw I/O without any
   intermediate protocol layers or buffering */

static int transportDirectReadFunction( STREAM *stream, void *buffer,
										const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( buffer, length ) );

	return( stream->transportReadFunction( stream, buffer, length,
										   TRANSPORT_FLAG_NONE ) );
	}

static int transportDirectWriteFunction( STREAM *stream, const void *buffer,
										 const int length )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( buffer, length ) );

	return( stream->transportWriteFunction( stream, buffer, length,
											TRANSPORT_FLAG_NONE ) );
	}

static int setStreamLayerDirect( STREAM *stream )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	stream->writeFunction = transportDirectWriteFunction;
	stream->readFunction = transportDirectReadFunction;

	return( CRYPT_OK );
	}

/* Send and receive data with a cryptlib session as the transport layer */

static int transportSessionConnectFunction( STREAM *stream,
											const char *host, const int hostLen,
											const int port )
	{
	int isActive, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( host == NULL && hostLen == 0 );
	assert( port == 0 );

	/* If the transport session hasn't been activated yet, activate it now */
	status = krnlSendMessage( stream->iTransportSession,
							  IMESSAGE_GETATTRIBUTE, &isActive,
							  CRYPT_SESSINFO_ACTIVE );
	if( cryptStatusOK( status ) && isActive )
		return( CRYPT_OK );
	status = krnlSendMessage( stream->iTransportSession,
							  IMESSAGE_SETATTRIBUTE, MESSAGE_VALUE_TRUE,
							  CRYPT_SESSINFO_ACTIVE );
	if( cryptStatusError( status ) )
		return( getSessionErrorInfo( stream, status ) );
	return( CRYPT_OK );
	}

static void transportSessionDisconnectFunction( STREAM *stream,
												const BOOLEAN fullDisconnect )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	krnlSendNotifier( stream->iTransportSession, IMESSAGE_DECREFCOUNT );
	}

static BOOLEAN transportSessionOKFunction( void )
	{
	return( TRUE );
	}

static int transportSessionReadFunction( STREAM *stream, BYTE *buffer,
										 const int length, const int flags )
	{
	MESSAGE_DATA msgData;
	int newTimeout = CRYPT_UNUSED, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( buffer, length ) );

	/* Read data from the session, overriding the timeout handling if
	   requested */
	if( ( flags & TRANSPORT_FLAG_NONBLOCKING ) && stream->timeout > 0 )
		newTimeout = 0;
	else
		if( ( flags & TRANSPORT_FLAG_BLOCKING ) && stream->timeout == 0 )
			newTimeout = 30;
	if( newTimeout != CRYPT_UNUSED )
		krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
						 &newTimeout, CRYPT_OPTION_NET_READTIMEOUT );
	setMessageData( &msgData, buffer, length );
	status = krnlSendMessage( stream->iTransportSession, IMESSAGE_ENV_POPDATA,
							  &msgData, 0 );
	if( newTimeout != CRYPT_UNUSED )
		krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
						 &stream->timeout, CRYPT_OPTION_NET_READTIMEOUT );
	if( cryptStatusError( status ) )
		return( getSessionErrorInfo( stream, status ) );
	if( msgData.length < length )
		retExt( STREAM_ERRINFO, CRYPT_ERROR_READ,
				"Only read %d out of %d bytes via cryptlib session object", 
				msgData.length, length );
	return( length );
	}

static int transportSessionWriteFunction( STREAM *stream, const BYTE *buffer,
										  const int length, const int flags )
	{
	MESSAGE_DATA msgData;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( buffer, length ) );

	setMessageData( &msgData, ( void * ) buffer, length );
	status = krnlSendMessage( stream->iTransportSession,
							  IMESSAGE_ENV_PUSHDATA, &msgData, 0 );
	if( cryptStatusOK( status ) )
		{
		setMessageData( &msgData, NULL, 0 );
		status = krnlSendMessage( stream->iTransportSession,
								  IMESSAGE_ENV_PUSHDATA, &msgData, 0 );
		}
	if( cryptStatusError( status ) )
		return( getSessionErrorInfo( stream, status ) );
	return( CRYPT_OK );
	}

/****************************************************************************
*																			*
*							Proxy Management Functions						*
*																			*
****************************************************************************/

/* Open a connection through a Socks proxy.  This is currently disabled
   since it doesn't appear to be used by anyone */

#if 0

static int connectViaSocksProxy( STREAM *stream )
	{
	MESSAGE_DATA msgData;
	BYTE socksBuffer[ 64 + CRYPT_MAX_TEXTSIZE + 8 ], *bufPtr = socksBuffer;
	char userName[ CRYPT_MAX_TEXTSIZE + 8 ];
	int length, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	/* Get the SOCKS user name, defaulting to "cryptlib" if there's none
	   set */
	setMessageData( &msgData, userName, CRYPT_MAX_TEXTSIZE );
	status = krnlSendMessage( DEFAULTUSER_OBJECT_HANDLE,
							  IMESSAGE_GETATTRIBUTE_S, &msgData,
							  CRYPT_OPTION_NET_SOCKS_USERNAME );
	if( cryptStatusOK( status ) )
		userName[ msgData.length ] = '\0';
	else
		strlcpy_s( userName, CRYPT_MAX_TEXTSIZE, "cryptlib" );

	/* Build up the socks request string:
		BYTE: version = 4
		BYTE: command = 1 (connect)
		WORD: port
		LONG: IP address
		STRING: userName + '\0' */
	*bufPtr++ = 4; *bufPtr++ = 1;
	mputWord( bufPtr, stream->port );
	status = getIPAddress( stream, bufPtr, stream->host );
	strlcpy_s( bufPtr + 4, CRYPT_MAX_TEXTSIZE, userName );
	length = 1 + 1 + 2 + 4 + strlen( userName ) + 1;
	if( cryptStatusError( status ) )
		{
		stream->transportDisconnectFunction( stream, TRUE );
		return( status );
		}

	/* Send the data to the server and read back the reply */
	status = stream->transportWriteFunction( stream, socksBuffer, length,
											 TRANSPORT_FLAG_FLUSH );
	if( cryptStatusOK( status ) )
		status = stream->transportReadFunction( stream, socksBuffer, 8,
												TRANSPORT_FLAG_BLOCKING );
	if( cryptStatusError( status ) )
		{
		/* The involvement of a proxy complicates matters somewhat because
		   we can usually connect to the proxy OK but may run into problems
		   going from the proxy to the remote server, so if we get an error
		   at this stage (which will typically show up as a read error from
		   the proxy) we report it as an open error instead */
		if( status == CRYPT_ERROR_READ || status == CRYPT_ERROR_COMPLETE )
			status = CRYPT_ERROR_OPEN;
		stream->transportDisconnectFunction( stream, TRUE );
		return( status );
		}

	/* Make sure that everything is OK, the second returned byte should be
	   90 */
	if( socksBuffer[ 1 ] != 90 )
		{
		int i;

		stream->transportDisconnectFunction( stream, TRUE );
		strlcpy_s( stream->errorInfo->errorString, MAX_ERRMSG_SIZE, 
				  "Socks proxy returned" );
		for( i = 0; i < 8; i++ )
			sprintf_s( stream->errorInfo->errorString + 20 + ( i * 3 ),
					   MAX_ERRMSG_SIZE - ( 20 + ( i * 3 ) ), " %02X", 
					   socksBuffer[ i ] );
		strlcat_s( stream->errorInfo->errorString, MAX_ERRMSG_SIZE, "." );
		stream->errorCode = socksBuffer[ 1 ];
		return( CRYPT_ERROR_OPEN );
		}

	return( CRYPT_OK );
	}
#endif /* 0 */

static int connectViaHttpProxy( STREAM *stream, ERROR_INFO *errorInfo )
	{
	BYTE buffer[ 64 + 8 ];
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	/* Open the connection via the proxy.  To do this we temporarily layer
	   HTTP I/O over the TCP I/O, then once the proxy messaging has been
	   completely we re-set the stream to pure TCP I/O and clear any stream
	   flags that were set during the proxying */
	setStreamLayerHTTP( stream );
	status = stream->writeFunction( stream, "", 0 );
	if( cryptStatusOK( status ) )
		status = stream->readFunction( stream, buffer, 64 );
	setStreamLayerDirect( stream );
	stream->flags = 0;
	if( cryptStatusError( status ) )
		{
		/* The involvement of a proxy complicates matters somewhat because
		   we can usually connect to the proxy OK but may run into problems
		   going from the proxy to the remote server, so if we get an error
		   at this stage (which will typically show up as a read error from
		   the proxy) we report it as an open error instead */
		if( status == CRYPT_ERROR_READ || status == CRYPT_ERROR_COMPLETE )
			status = CRYPT_ERROR_OPEN;
		errorInfo->errorCode = stream->errorInfo->errorCode;
		strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE, 
				   stream->errorInfo->errorString );
		stream->transportDisconnectFunction( stream, TRUE );
		}
	return( status );
	}

/* Try and auto-detect HTTP proxy information */

#if defined( __WIN32__ )

/* The autoproxy functions were only documented in WinHTTP 5.1, so we have to
   provide the necessary defines and structures ourselves */

#ifndef WINHTTP_ACCESS_TYPE_DEFAULT_PROXY

#define HINTERNET	HANDLE

typedef struct {
	DWORD dwFlags;
	DWORD dwAutoDetectFlags;
	LPCWSTR lpszAutoConfigUrl;
	LPVOID lpvReserved;
	DWORD dwReserved;
	BOOL fAutoLogonIfChallenged;
	} WINHTTP_AUTOPROXY_OPTIONS;

typedef struct {
	DWORD dwAccessType;
	LPWSTR lpszProxy;
	LPWSTR lpszProxyBypass;
	} WINHTTP_PROXY_INFO;

typedef struct {
	BOOL fAutoDetect;
	LPWSTR lpszAutoConfigUrl;
	LPWSTR lpszProxy;
	LPWSTR lpszProxyBypass;
	} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;

#define WINHTTP_AUTOPROXY_AUTO_DETECT	1
#define WINHTTP_AUTO_DETECT_TYPE_DHCP	1
#define WINHTTP_AUTO_DETECT_TYPE_DNS_A	2
#define WINHTTP_ACCESS_TYPE_NO_PROXY	1
#define WINHTTP_NO_PROXY_NAME			NULL
#define WINHTTP_NO_PROXY_BYPASS			NULL

#endif /* WinHTTP 5.1 defines and structures */

typedef HINTERNET ( *WINHTTPOPEN )( LPCWSTR pwszUserAgent, DWORD dwAccessType,
									LPCWSTR pwszProxyName, LPCWSTR pwszProxyBypass,
									DWORD dwFlags );
typedef BOOL ( *WINHTTPGETDEFAULTPROXYCONFIGURATION )( WINHTTP_PROXY_INFO* pProxyInfo );
typedef BOOL ( *WINHTTPGETIEPROXYCONFIGFORCURRENTUSER )(
								WINHTTP_CURRENT_USER_IE_PROXY_CONFIG *pProxyConfig );
typedef BOOL ( *WINHTTPGETPROXYFORURL )( HINTERNET hSession, LPCWSTR lpcwszUrl,
										 WINHTTP_AUTOPROXY_OPTIONS *pAutoProxyOptions,
										 WINHTTP_PROXY_INFO *pProxyInfo );
typedef BOOL ( *WINHTTPCLOSEHANDLE )( HINTERNET hInternet );

static int findProxyUrl( char *proxy, const int proxyMaxLen, 
						 const char *url, const int urlLen )
	{
	static HMODULE hWinHTTP = NULL;
	static WINHTTPOPEN pWinHttpOpen = NULL;
	static WINHTTPGETDEFAULTPROXYCONFIGURATION pWinHttpGetDefaultProxyConfiguration = NULL;
	static WINHTTPGETIEPROXYCONFIGFORCURRENTUSER pWinHttpGetIEProxyConfigForCurrentUser = NULL;
	static WINHTTPGETPROXYFORURL pWinHttpGetProxyForUrl = NULL;
	static WINHTTPCLOSEHANDLE pWinHttpCloseHandle = NULL;
	WINHTTP_AUTOPROXY_OPTIONS autoProxyOptions = \
			{ WINHTTP_AUTOPROXY_AUTO_DETECT,
			  WINHTTP_AUTO_DETECT_TYPE_DHCP | WINHTTP_AUTO_DETECT_TYPE_DNS_A,
			  NULL, NULL, 0, FALSE };
	WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxyInfo;
	WINHTTP_PROXY_INFO proxyInfo;
	HINTERNET hSession;
	char urlBuffer[ MAX_DNS_SIZE + 1 + 8 ];
	wchar_t unicodeURL[ MAX_DNS_SIZE + 1 + 8 ];
	size_t unicodeUrlLen, proxyLen;
	int offset = 0, proxyStatus;

	assert( isWritePtr( proxy, proxyMaxLen ) );
	assert( isReadPtr( url, urlLen ) );

	/* Under Win2K SP3, XP and 2003 (or at least Windows versions with
	   WinHTTP 5.1 installed in some way, it officially shipped with the
	   versions mentioned earlier) we can use WinHTTP AutoProxy support,
	   which implements the Web Proxy Auto-Discovery (WPAD) protocol from
	   an internet draft that expired in May 2001.  Under older versions of
	   Windows we have to use the WinINet InternetGetProxyInfo, however this
	   consists of a ghastly set of kludges that were never meant to be
	   exposed to the outside world (they were only crowbarred out of MS
	   as part of the DoJ consent decree), and user experience with them is
	   that they don't really work except in the one special way in which
	   MS-internal code calls them.  Since we don't know what this is, we
	   use the WinHTTP functions instead */
	if( hWinHTTP == NULL )
		{
		if( ( hWinHTTP = LoadLibrary( "WinHTTP.dll" ) ) == NULL )
			return( CRYPT_ERROR_NOTFOUND );

		pWinHttpOpen = ( WINHTTPOPEN ) \
						GetProcAddress( hWinHTTP, "WinHttpOpen" );
		pWinHttpGetDefaultProxyConfiguration = ( WINHTTPGETDEFAULTPROXYCONFIGURATION ) \
						GetProcAddress( hWinHTTP, "WinHttpGetDefaultProxyConfiguration" );
		pWinHttpGetIEProxyConfigForCurrentUser = ( WINHTTPGETIEPROXYCONFIGFORCURRENTUSER ) \
						GetProcAddress( hWinHTTP, "WinHttpGetIEProxyConfigForCurrentUser" );
		pWinHttpGetProxyForUrl = ( WINHTTPGETPROXYFORURL ) \
						GetProcAddress( hWinHTTP, "WinHttpGetProxyForUrl" );
		pWinHttpCloseHandle = ( WINHTTPCLOSEHANDLE ) \
						GetProcAddress( hWinHTTP, "WinHttpCloseHandle" );
		if( pWinHttpOpen == NULL || pWinHttpGetProxyForUrl == NULL || \
			pWinHttpCloseHandle == NULL )
			{
			FreeLibrary( hWinHTTP );
			return( CRYPT_ERROR_NOTFOUND );
			}
		}

	/* Autoproxy discovery using WinHttpGetProxyForUrl() can be awfully slow,
	   often taking several seconds, since it requires probing for proxy info
	   first using DHCP and then if that fails using DNS.  Since this is done
	   via a blocking call, everything blocks while it's in progress.  To
	   help mitigate this, we try for proxy info direct from the registry if
	   it's available, avoiding the lengthy autodiscovery process.  This also
	   means that discovery will work if no auto-discovery support is present,
	   for example on servers where the admin has set the proxy config
	   directly with ProxyCfg.exe */
	if( pWinHttpGetDefaultProxyConfiguration != NULL && \
		pWinHttpGetDefaultProxyConfiguration( &proxyInfo ) && \
		proxyInfo.lpszProxy != NULL )
		{
		proxyStatus = wcstombs_s( &proxyLen, proxy, proxyMaxLen,
								  proxyInfo.lpszProxy, MAX_DNS_SIZE );
		GlobalFree( proxyInfo.lpszProxy );
		if( proxyInfo.lpszProxyBypass != NULL )
			GlobalFree( proxyInfo.lpszProxy );
		if( proxyStatus == 0 )
			return( proxyLen );
		}

	/* The next fallback is to get the proxy info from MSIE.  This is also
	   usually much quicker than WinHttpGetProxyForUrl(), although sometimes
	   it seems to fall back to that, based on the longish delay involved.
	   Another issue with this is that it won't work in a service process
	   that isn't impersonating an interactive user (since there isn't a
	   current user), but in that case we just fall back to
	   WinHttpGetProxyForUrl() */
	if( pWinHttpGetIEProxyConfigForCurrentUser != NULL && \
		pWinHttpGetIEProxyConfigForCurrentUser( &ieProxyInfo ) )
		{
		proxyStatus = wcstombs_s( &proxyLen, proxy, proxyMaxLen,
								  ieProxyInfo.lpszProxy, MAX_DNS_SIZE );
		if( ieProxyInfo.lpszAutoConfigUrl != NULL )
			GlobalFree( ieProxyInfo.lpszAutoConfigUrl );
		if( ieProxyInfo.lpszProxy != NULL )
			GlobalFree( ieProxyInfo.lpszProxy );
		if( ieProxyInfo.lpszProxyBypass != NULL )
			GlobalFree( ieProxyInfo.lpszProxyBypass );
		if( proxyStatus == 0 )
			return( proxyLen );
		}

	/* WinHttpGetProxyForUrl() requires a schema for the URL that it's
	   performing a lookup on, if the URL doesn't contain one we use a
	   default value of "http://".  In addition we need to convert the
	   raw octet string into a null-terminated string for the mbstowcs()
	   Unicode conversion and following WinHttpGetProxyForUrl() lookup */
	if( strFindStr( url, urlLen, "://", 3 ) < 0 )
		{
		strlcpy_s( urlBuffer, MAX_DNS_SIZE, "http://" );
		offset = 7;
		}
	memcpy( urlBuffer + offset, url, min( urlLen, MAX_DNS_SIZE - offset ) );
	urlBuffer[ offset + min( urlLen, MAX_DNS_SIZE - offset ) ] = '\0';

	/* Locate the proxy used for accessing the resource at the supplied URL.
	   We have to convert to and from Unicode because the WinHTTP functions
	   all take Unicode strings as args.  Note that we use the libc widechar
	   functions rather than the Windows ones since the latter aren't
	   present in Win95 or Win98.

	   WinHttpGetProxyForUrl() can be rather flaky, in some cases it'll fail
	   instantly (without even trying auto-discovery) with GetLastError() =
	   87 (parameter error), but then calling it again some time later works
	   fine.  Because of this we leave it as the last resort after trying
	   all of the other get-proxy mechanisms */
	hSession = pWinHttpOpen( L"cryptlib/1.0",
							 WINHTTP_ACCESS_TYPE_NO_PROXY,
							 WINHTTP_NO_PROXY_NAME,
							 WINHTTP_NO_PROXY_BYPASS, 0 );
	if( hSession == NULL )
		return( CRYPT_ERROR_NOTFOUND );
	if( mbstowcs_s( &unicodeUrlLen, unicodeURL, MAX_DNS_SIZE,
					urlBuffer, MAX_DNS_SIZE ) != 0 )
		{
		pWinHttpCloseHandle( hSession );
		return( CRYPT_ERROR_NOTFOUND );
		}
	unicodeURL[ unicodeUrlLen ] = L'\0';
	proxyStatus = 0;
	memset( &proxyInfo, 0, sizeof( WINHTTP_PROXY_INFO ) );
	if( pWinHttpGetProxyForUrl( hSession, unicodeURL, &autoProxyOptions,
								&proxyInfo ) == TRUE )
		{
		proxyStatus = wcstombs_s( &proxyLen, proxy, proxyMaxLen,
								  proxyInfo.lpszProxy, MAX_DNS_SIZE );
		GlobalFree( proxyInfo.lpszProxy );
		if( proxyInfo.lpszProxyBypass != NULL )
			GlobalFree( proxyInfo.lpszProxy );
		}
	pWinHttpCloseHandle( hSession );
	if( proxyStatus != 0 )
		return( CRYPT_ERROR_NOTFOUND );
	return( proxyLen );
	}

#if 0

typedef BOOL ( WINAPI *INTERNETGETPROXYINFO )( LPCSTR lpszUrl, DWORD dwUrlLength,
							LPSTR lpszUrlHostName, DWORD dwUrlHostNameLength,
							LPSTR* lplpszProxyHostName,
							LPDWORD lpdwProxyHostNameLength );
typedef BOOL ( WINAPI *INTERNETINITIALIZEAUTOPROXYDLL )( DWORD dwVersion,
							LPSTR lpszDownloadedTempFile, LPSTR lpszMime,
							AutoProxyHelperFunctions* lpAutoProxyCallbacks,
							LPAUTO_PROXY_SCRIPT_BUFFER lpAutoProxyScriptBuffer );

static int findProxyUrl( char *proxy, const int proxyMaxLen, 
						 const char *url, const int urlLen )
	{
	static INTERNETGETPROXYINFO pInternetGetProxyInfo = NULL;
	static INTERNETINITIALIZEAUTOPROXYDLL pInternetInitializeAutoProxyDll = NULL;
	URL_INFO urlInfo;
	char urlHost[ MAX_DNS_SIZE + 8 ];
	char *proxyHost = NULL;
	int proxyHostLen, status;

	/* This gets somewhat complicated, under Win2K SP3, XP and 2003 (or at
	   least Windows versions with WinHTTP 5.1 installed in some way, it
	   officially shipped with the versions mentioned earlier) we can use
	   WinHTTP AutoProxy support, which implements the Web Proxy Auto-
	   Discovery (WPAD) protocol from an internet draft that expired in May
	   2001.  Under older versions of Windows we have to use the WinINet
	   InternetGetProxyInfo.

	   These functions were never meant to be used by the general public
	   (see the comment below), so they work in an extremely peculiar way
	   and only with the exact calling sequence that's used by MS code - it
	   looks like they were only intended as components of Windows-internal
	   implementation of proxy support, since they require manual handling
	   of proxy config script downloading, parsing, and all manner of other
	   stuff that really doesn't concern us.  Because of the extreme
	   difficulty in doing anything with these functions, we use the WinHTTP
	   approach instead */
	if( pInternetGetProxyInfo == NULL )
		{
		HMODULE hModJS;

		if( ( hModJS = LoadLibrary( "JSProxy.dll" ) ) == NULL )
			return( CRYPT_ERROR_NOTFOUND );

		pInternetGetProxyInfo = ( INTERNETGETPROXYINFO ) \
					GetProcAddress( hModJS, "InternetGetProxyInfo" );
		pInternetInitializeAutoProxyDll = ( INTERNETINITIALIZEAUTOPROXYDLL ) \
					GetProcAddress( hModJS, "InternetInitializeAutoProxyDll" );
		if( pInternetGetProxyInfo == NULL || \
			pInternetInitializeAutoProxyDll == NULL )
			{
			FreeLibrary( hModJS );
			return( CRYPT_ERROR_NOTFOUND );
			}

		pInternetInitializeAutoProxyDll( 0, TempFile, NULL,
										 &HelperFunctions, NULL )
		}

	/* InternetGetProxyInfo() is a somewhat screwball undocumented function
	   that was crowbarred out of MS as part of the DoJ consent decree.  It
	   takes as input four parameters that do the work of a single
	   parameter, the null-terminated target URL string.  The documentation
	   for the function was initially wrong, but has been partially
	   corrected in places after user complaints, there are still missing
	   parts, as well as possible errors (why is it necessary to specify a
	   length for a supposedly null-terminated string?).  In order to meet
	   the strange input-parameter requirements, we have to pre-parse the
	   target URL in order to provide the various bits and pieces that
	   InternetGetProxyInfo() requires */
	status = parseURL( &urlInfo, url, strlen( url ), 80, URL_TYPE_HTTP );
	if( cryptStatusError( status ) )
		return( status );
	if( urlInfo.hostLen > MAX_DNS_SIZE )
		return( CRYPT_ERROR_OVERFLOW );
	memcpy( urlHost, urlInfo.host, urlInfo.hostLen );
	urlHost[ urlInfo.hostLen ] = '\0';
	if( !pInternetGetProxyInfo( url, strlen( url ), urlHost, urlInfo.hostLen,
								&proxyHost, &proxyHostLen ) )
		return( CRYPT_ERROR_NOTFOUND );
	memcpy( proxy, proxyHost, proxyHostLen );
	proxy[ proxyHostLen ] = '\0';
	GlobalFree( proxyHost );
	return( CRYPT_OK );
	}
#endif

#else
  #define findProxyUrl( proxy, proxyMaxLen, url, urlLen )	CRYPT_ERROR_NOTFOUND
#endif /* __WIN32__ */

/****************************************************************************
*																			*
*								Buffering Functions							*
*																			*
****************************************************************************/

/* Buffered transport-layer read function.  This sits on top of the
   transport-layer read function and performs speculative read-ahead
   buffering to improve performance in protocols such as HTTP that have to
   read a byte at a time in places:

		   bPos		   bEnd
			|			|
			v			v
	+-------+-----------+-------+
	|		|///////////|		|
	+-------+-----------+-------+
			 -- Read -->

   We fill the buffer to bEnd, then empty it advancing bPos until there isn't
   enough data left to satisfy the read, whereupon we move the data down and
   refill from bEnd:

   bPos		   bEnd
	|			|
	v			v
	+-----------+---------------+
	|///////////|				|
	+-----------+---------------+
				 -- Write -->	  */

static int bufferedTransportReadFunction( STREAM *stream, BYTE *buffer,
										  const int length, const int flags )
	{
	const int bytesLeft = stream->bufEnd - stream->bufPos;
	int bytesToRead, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isWritePtr( buffer, length ) );
	assert( length > 0 );
	assert( bytesLeft >= 0 );

	/* If there's enough data in the buffer to satisfy the request, return it
	   directly */
	if( length <= bytesLeft )
		{
		if( length == 1 )
			/* Optimisation for char-at-a-time HTTP header reads */
			*buffer = stream->buffer[ stream->bufPos++ ];
		else
			{
			memcpy( buffer, stream->buffer + stream->bufPos, length );
			stream->bufPos += length;
			}
		assert( stream->bufPos <= stream->bufEnd );
		return( length );
		}

	/* We're about to refill the buffer, if there's a gap at the start move
	   everything down to make room for the new data */
	if( stream->bufPos > 0 )
		{
		if( bytesLeft > 0 )
			memmove( stream->buffer, stream->buffer + stream->bufPos,
					 bytesLeft );
		stream->bufEnd = bytesLeft;
		stream->bufPos = 0;
		}

	assert( stream->bufPos == 0 );
	assert( length > bytesLeft );

	/* If there's more room in the buffer, refill it */
	if( stream->bufEnd < stream->bufSize )
		{
		int bytesRead;

		/* Perform an explicitly blocking read of as many bytes as we can/are
		   asked for.  Since there may be data already present from an
		   earlier speculative read, we only read as much as we need to
		   fulfill the request */
		bytesRead = stream->transportReadFunction( stream,
										stream->buffer + stream->bufEnd,
										min( length - bytesLeft, \
											 stream->bufSize - stream->bufEnd ),
										TRANSPORT_FLAG_BLOCKING );
		if( cryptStatusError( bytesRead ) )
			return( bytesRead );
		stream->bufEnd += bytesRead;

		/* If there's room for more, perform a second, nonblocking read for
		   whatever might still be there.  An error at this point isn't
		   fatal since this was only a speculative read  */
		if( stream->bufEnd < stream->bufSize )
			{
			bytesRead = stream->transportReadFunction( stream,
										stream->buffer + stream->bufEnd,
										stream->bufSize - stream->bufEnd,
										TRANSPORT_FLAG_NONBLOCKING );
			if( !cryptStatusError( bytesRead ) )
				stream->bufEnd += bytesRead;
			}
		}
	assert( stream->bufEnd <= stream->bufSize );

	/* Read as much as we can from the buffer */
	bytesToRead = min( length, stream->bufEnd );
	memcpy( buffer, stream->buffer, bytesToRead );
	stream->bufPos += bytesToRead;
	assert( stream->bufPos <= stream->bufEnd );

	/* If we could satisfy the read from the buffer, we're done */
	if( length <= bytesToRead )
		return( length );

	/* We're drained the stream buffer and there's more to go, read it
	   directly into the caller's buffer */
	status = stream->transportReadFunction( stream,
								buffer + bytesToRead, length - bytesToRead,
								TRANSPORT_FLAG_BLOCKING );
	return( cryptStatusError( status ) ? status : status + bytesToRead );
	}

/* Buffered transport-layer write function.  This sits on top of the
   transport-layer write function and combines two (or more, although in
   practice only two ever occur) writes into a single write.  The reason for
   this is that when using TCP transport the delayed-ACK handling means
   that performing two writes followed by a read (typical for HTTP and CMP
   messages) leads to very poor performance, usually made even worse by TCP
   slow-start.

   The reason for this is that the TCP MSS is typically 1460 bytes on a LAN
   (Ethernet) or 512/536 bytes on a WAN, while HTTP headers are ~200-300
   bytes, far less than the MSS.  When an HTTP message is first sent, the
   TCP congestion window begins at one segment, with the TCP slow-start then
   doubling its size for each ACK.  Sending the headers separately will
   send one short segment and a second MSS-size segment, whereupon the TCP
   stack will wait for the responder's ACK before continuing.  The responder
   gets both segments, then delays its ACK for 200ms in the hopes of
   piggybacking it on responder data, which is never sent since it's still
   waiting for the rest of the HTTP body from the initiator.  As a result,
   this results in a 200ms (+ assorted RTT) delay in each message sent.

   There is a somewhat related situation that occurs as a result of TCP
   slow-start and that can't be avoided programmatically in which we can't
   send more than a single request initially, however most BSD-derived
   implementations set the server's congestion window to two segments in
   response to receiving the TCP handshake ACK, so for the initial message
   exchange the client can send a request of 1MSS and the server a response
   of 2MSS without running into congestion-control problems.

   A related problem is the fact that many TCP implementations will reset the
   congestion window after one retransmission timeout period if all data sent
   at that point has been acked, which means that both sides now restart with
   a congestion window of size 1.  Unfortunately there's nothing that can be
   done about this, however hopefully at some point TCP implementations will
   start to fall into line with RFC 3390 and allow initial windows of ~4K,
   which will fix this particular problem */

static int bufferedTransportWriteFunction( STREAM *stream, const BYTE *buffer,
										   const int length, const int flags )
	{
	const BYTE *bufPtr = buffer;
	int byteCount = length, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( isReadPtr( buffer, length ) );
	assert( length > 0 );

	/* If it's not a flush and the buffer can absorb the data, copy it in and
	   exit */
	if( !( flags & TRANSPORT_FLAG_FLUSH ) && \
		stream->writeBufEnd + length <= stream->writeBufSize )
		{
		memcpy( stream->writeBuffer + stream->writeBufEnd, buffer, length );
		stream->writeBufEnd += length;
		assert( stream->writeBufEnd <= stream->writeBufSize );

		return( length );
		}

	/* It's a flush or too much data to buffer, assemble a complete buffer
	   and write it */
	if( stream->writeBufEnd > 0 )
		{
		const int bytesToCopy = min( byteCount, \
									 stream->writeBufSize - stream->writeBufEnd );
		const int bytesToWrite = stream->writeBufEnd + bytesToCopy;

		if( bytesToCopy > 0 )
			memcpy( stream->writeBuffer + stream->writeBufEnd, buffer,
					bytesToCopy );
		status = stream->transportWriteFunction( stream, stream->writeBuffer,
												 bytesToWrite,
												 TRANSPORT_FLAG_FLUSH );
		if( cryptStatusError( status ) || status < bytesToWrite )
			return( status );
		stream->writeBufEnd = 0;
		bufPtr += bytesToCopy;
		byteCount -= bytesToCopy;
		if( byteCount <= 0 )
			/* We've written everything, exit */
			return( length );
		}

	/* Write anything that's left directly */
	status = stream->transportWriteFunction( stream, bufPtr, byteCount,
											 TRANSPORT_FLAG_FLUSH );
	if( cryptStatusError( status ) || status < byteCount )
		return( status );
	return( length );
	}

/****************************************************************************
*																			*
*							Network Stream Functions						*
*																			*
****************************************************************************/

/* Initialise the network stream */

static int initStream( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
					   const NET_CONNECT_INFO *connectInfo,
					   const BOOLEAN isServer )
	{
	int timeout;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( protocol > STREAM_PROTOCOL_NONE && \
			protocol < STREAM_PROTOCOL_LAST );
	assert( isReadPtr( connectInfo, sizeof( NET_CONNECT_INFO ) ) );

	/* Set up the basic network stream info */
	memset( stream, 0, sizeof( STREAM ) );
	stream->type = STREAM_TYPE_NETWORK;
	stream->protocol = protocol;
	stream->port = connectInfo->port;
	stream->netSocket = stream->listenSocket = CRYPT_ERROR;
	stream->iTransportSession = CRYPT_ERROR;
	if( isServer )
		stream->flags = STREAM_NFLAG_ISSERVER;

	/* Set up the stream timeout information.  While we're connecting, the
	   stream timeout is the connect timeout.  Once we've connected it's set
	   to the data transfer timeout, so initially we set the stream timeout
	   to the connect timeout and the saved timeout to the data transfer
	   timeout */
	if( connectInfo->connectTimeout != CRYPT_ERROR )
		/* There's an explicit timeout specified, use that */
		timeout = connectInfo->connectTimeout;
	else
		/* Get the default timeout from the user object */
		if( cryptStatusError( \
				krnlSendMessage( connectInfo->iUserObject, IMESSAGE_GETATTRIBUTE,
								 &timeout, CRYPT_OPTION_NET_CONNECTTIMEOUT ) ) )
			timeout = 30;
	if( timeout < 5 )
		{
		/* Enforce the same minimum connect timeout as the kernel ACLs */
		assert( NOTREACHED );
		timeout = 5;
		}
	stream->timeout = timeout;
	if( connectInfo->timeout != CRYPT_ERROR )
		/* There's an explicit timeout specified, use that */
		timeout = connectInfo->timeout;
	else
		/* Get the default timeout from the user object */
		if( cryptStatusError( \
				krnlSendMessage( connectInfo->iUserObject, IMESSAGE_GETATTRIBUTE,
								 &timeout, CRYPT_OPTION_NET_READTIMEOUT ) ) )
			timeout = 30;
	stream->savedTimeout = timeout;

	return( CRYPT_OK );
	}

/* Connect a stream */

static int openConnection( STREAM *stream,
						   const NET_OPTION_TYPE options,
						   const char *proxyUrl, const int proxyUrlLen )
	{
	URL_INFO urlInfo;
	char urlBuffer[ MAX_DNS_SIZE + 8 ];
	const char *url = proxyUrl;
	int urlLen = proxyUrlLen, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( ( proxyUrl == NULL && proxyUrlLen == 0 ) || \
			isReadPtr( proxyUrl, proxyUrlLen ) );

	/* If we're using an already-active network socket supplied by the
	   user, there's nothing to do */
	if( stream->flags & STREAM_NFLAG_USERSOCKET )
		{
		/* If it's a dummy open to check parameters that can't be validated
		   at a higher level, pass the info on down to the low-level
		   checking routines */
		if( options == NET_OPTION_NETWORKSOCKET_DUMMY )
			return( stream->transportCheckFunction( stream ) );

		return( CRYPT_OK );
		}

	/* If we're not going via a proxy, perform a direct open */
	if( proxyUrl == NULL )
		return( stream->transportConnectFunction( stream, stream->host, 
												  stream->hostLen,
												  stream->port ) );

	/* We're going via a proxy.  If the user has specified automatic proxy
	   detection, try and locate the proxy information */
	if( !strCompareZ( proxyUrl, "[Autodetect]" ) )
		{
		status = findProxyUrl( urlBuffer, MAX_DNS_SIZE, stream->host, 
							   stream->hostLen );
		if( cryptStatusError( status ) )
			{
			/* The proxy URL was invalid, provide more information for the
			   caller */
			stream->errorInfo->errorCode = CRYPT_ERROR_NOTFOUND;
			strlcpy_s( stream->errorInfo->errorString, MAX_ERRMSG_SIZE,
					   "Couldn't auto-detect HTTP proxy" );
			return( CRYPT_ERROR_OPEN );
			}
		url = urlBuffer;
		urlLen = status;
		}

	/* Process the proxy details.  Since this is an HTTP proxy, we specify
	   the default port as port 80 */
	status = parseURL( &urlInfo, url, urlLen, 80, URL_TYPE_HTTP );
	if( cryptStatusError( status ) )
		{
		/* The proxy URL was invalid, provide more information for the
		   caller */
		stream->errorInfo->errorCode = CRYPT_ERROR_BADDATA;
		strlcpy_s( stream->errorInfo->errorString, MAX_ERRMSG_SIZE,
				   "Invalid HTTP proxy URL" );
		return( CRYPT_ERROR_OPEN );
		}

	/* Since we're going via a proxy, open the connection to the proxy
	   rather than directly to the target system.  */
	return( stream->transportConnectFunction( stream, urlInfo.host, 
											  urlInfo.hostLen, urlInfo.port ) );
	}

/* Clean up a stream to shut it down */

static void cleanupStream( STREAM *stream, const BOOLEAN cleanupTransport,
						   const BOOLEAN cleanupBuffers )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( stream->type == STREAM_TYPE_NETWORK );

	/* Clean up the transport system if necessary */
	if( cleanupTransport && !( stream->flags & STREAM_NFLAG_USERSOCKET ) )
		stream->transportDisconnectFunction( stream, TRUE );

	/* Clean up stream-related buffers if necessary */
	if( cleanupBuffers )
		{
		assert( stream->errorInfo != NULL );

		if( stream->bufSize > 0 )
			{
			zeroise( stream->buffer, stream->bufSize );
			clFree( "cleanupStream", stream->buffer );
			}
		if( stream->writeBufSize > 0 )
			{
			zeroise( stream->writeBuffer, stream->writeBufSize );
			clFree( "cleanupStream", stream->writeBuffer );
			}
		clFree( "cleanupStream", stream->errorInfo );
		}

	/* Clean up static stream data */
	if( stream->host != NULL )
		clFree( "cleanupStream", stream->host );
	if( stream->path != NULL )
		clFree( "cleanupStream", stream->path );
	if( stream->query != NULL )
		clFree( "cleanupStream", stream->query );

	zeroise( stream, sizeof( STREAM ) );
	}

/* Check for the use of a proxy when opening a stream */

static int checkForProxy( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
						  const NET_CONNECT_INFO *connectInfo,
						  char *proxyUrlBuffer, const int proxyUrlMaxLen )
	{
	MESSAGE_DATA msgData;
	int status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( protocol > STREAM_PROTOCOL_NONE && \
			protocol < STREAM_PROTOCOL_LAST );
	assert( isReadPtr( connectInfo, sizeof( NET_CONNECT_INFO ) ) );
	assert( isWritePtr( proxyUrlBuffer, proxyUrlMaxLen ) );

	/* Check for a local connection, which always bypasses the proxy.  We
	   only use the case-insensitive string compares for the text-format
	   host names, since the numeric forms don't need this. */
	if( ( stream->hostLen == 9 && \
		  !memcmp( stream->host, "127.0.0.1", 9 ) ) || \
		( stream->hostLen == 3 && \
		  !memcmp( stream->host, "::1", 3 ) ) || \
		( stream->hostLen == 9 && \
		  !strCompare( stream->host, "localhost", 9 ) ) || \
		( stream->hostLen == 10 && \
		  !strCompare( stream->host, "localhost.", 10 ) ) )	/* Are you local? */
		/* This is a local socket! We'll have no proxies here! */
		return( 0 );

	/* Check to see whether we're going through a proxy.  First we check for
	   a protocol-specific HTTP proxy (if appropriate), if there's none we
	   check for the more generic case of a SOCKS proxy.  In addition to the
	   obvious use of an HTTP proxy for HTTP, we also check for an HTTP URL
	   specified for use with other protocols (specifcally SSL/TLS), since
	   these can also go via a proxy even if the they're not an explicit use
	   of HTTP */
	if( ( protocol == STREAM_PROTOCOL_HTTP || \
		  protocol == STREAM_PROTOCOL_HTTP_TRANSACTION || \
		  connectInfo->options == NET_OPTION_HOSTNAME_TUNNEL ) )
		{
		/* Check whether there's an HTTP proxy configured */
		setMessageData( &msgData, proxyUrlBuffer, proxyUrlMaxLen );
		status = krnlSendMessage( connectInfo->iUserObject,
								  IMESSAGE_GETATTRIBUTE_S, &msgData,
								  CRYPT_OPTION_NET_HTTP_PROXY );
		if( cryptStatusOK( status ) )
			{
			stream->flags |= \
				( connectInfo->options == NET_OPTION_HOSTNAME ) ? \
				STREAM_NFLAG_HTTPPROXY : STREAM_NFLAG_HTTPTUNNEL;
			return( msgData.length );
			}
		}

	/* Check whether there's a SOCKS proxy configured */
	setMessageData( &msgData, proxyUrlBuffer, proxyUrlMaxLen );
	status = krnlSendMessage( connectInfo->iUserObject,
							  IMESSAGE_GETATTRIBUTE_S, &msgData,
							  CRYPT_OPTION_NET_SOCKS_SERVER );
	if( cryptStatusOK( status ) )
		return( msgData.length );

	/* There's no proxy configured */
	return( 0 );
	}

/* Complete a network connection after the client- or server-specific
   portions have been handled */

static int completeConnect( STREAM *stream,
							const STREAM_PROTOCOL_TYPE protocol,
							const NET_OPTION_TYPE options,
							const char *proxyUrl, const int proxyUrlLen,
							const CRYPT_USER iUserObject, 
							ERROR_INFO *errorInfo )
	{
	const BOOLEAN useTransportBuffering = \
						( options == NET_OPTION_TRANSPORTSESSION || \
						  protocol == STREAM_PROTOCOL_TCPIP ) ? \
						FALSE : TRUE;
	int status = CRYPT_OK;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( protocol > STREAM_PROTOCOL_NONE && \
			protocol < STREAM_PROTOCOL_LAST );
	assert( options > NET_OPTION_NONE && options < NET_OPTION_LAST );
	assert( ( proxyUrl == NULL && proxyUrlLen == 0 ) || \
			isReadPtr( proxyUrl, proxyUrlLen ) );
	assert( ( iUserObject == DEFAULTUSER_OBJECT_HANDLE ) || \
			isHandleRangeValid( iUserObject ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	/* Set up the access method pointers.  We can use either direct TCP/IP
	   access or a cryptlib stream for transport, and layered over that
	   either HTTP, the CMP socket protocol, or direct access to the
	   transport layer */
	if( options == NET_OPTION_TRANSPORTSESSION )
		{
		stream->transportConnectFunction = transportSessionConnectFunction;
		stream->transportDisconnectFunction = transportSessionDisconnectFunction;
		stream->transportWriteFunction = transportSessionWriteFunction;
		stream->transportReadFunction = transportSessionReadFunction;
		stream->transportOKFunction = transportSessionOKFunction;
		}
	else
		setAccessMethodTCP( stream );
	switch( protocol )
		{
		case STREAM_PROTOCOL_HTTP:
		case STREAM_PROTOCOL_HTTP_TRANSACTION:
#ifdef USE_HTTP
			setStreamLayerHTTP( stream );
#else
			return( CRYPT_ERROR_NOTAVAIL );
#endif /* USE_HTTP */
			break;

		case STREAM_PROTOCOL_CMP:
#ifdef USE_CMP_TRANSPORT
			setStreamLayerCMP( stream );
#else
			return( CRYPT_ERROR_NOTAVAIL );
#endif /* USE_CMP_TRANSPORT */
			break;

		case STREAM_PROTOCOL_TCPIP:
			setStreamLayerDirect( stream );
			break;

		default:
			retIntError();
		}
	if( useTransportBuffering )
		{
		stream->bufferedTransportReadFunction = bufferedTransportReadFunction;
		stream->bufferedTransportWriteFunction = bufferedTransportWriteFunction;
		}
	else
		{
		stream->bufferedTransportReadFunction = stream->transportReadFunction;
		stream->bufferedTransportWriteFunction = stream->transportWriteFunction;
		}

	/* If we're running over a cryptlib session, make sure that we wait around
	   for a minimum amount of time during network comms in case the user has
	   specified nonblocking behaviour or quick timeouts */
	if( options == NET_OPTION_TRANSPORTSESSION )
		{
		static const int fixedTimeout = 30;
		int timeout;

		status = krnlSendMessage( iUserObject, IMESSAGE_GETATTRIBUTE,
								  &timeout, CRYPT_OPTION_NET_CONNECTTIMEOUT );
		if( cryptStatusOK( status ) && timeout < fixedTimeout )
			krnlSendMessage( stream->iTransportSession,
							 IMESSAGE_SETATTRIBUTE, ( void * ) &fixedTimeout,
							 CRYPT_OPTION_NET_CONNECTTIMEOUT );
		status = krnlSendMessage( iUserObject, IMESSAGE_GETATTRIBUTE,
								  &timeout, CRYPT_OPTION_NET_READTIMEOUT );
		if( cryptStatusOK( status ) && timeout < fixedTimeout )
			krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
							 ( void * ) &fixedTimeout,
							 CRYPT_OPTION_NET_READTIMEOUT );
		status = krnlSendMessage( iUserObject, IMESSAGE_GETATTRIBUTE,
								  &timeout, CRYPT_OPTION_NET_WRITETIMEOUT );
		if( cryptStatusOK( status ) && timeout < fixedTimeout )
			krnlSendMessage( stream->iTransportSession, IMESSAGE_SETATTRIBUTE,
							 ( void * ) &fixedTimeout,
							 CRYPT_OPTION_NET_WRITETIMEOUT );
		status = CRYPT_OK;	/* Reset status from above checks */
		}

	/* Wait for any async network driver binding to complete and make sure
	   that the network interface has been initialised */
	if( !krnlWaitSemaphore( SEMAPHORE_DRIVERBIND ) || \
		!stream->transportOKFunction() )
		{
		/* Provide more information on the nature of the problem */
		strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE,
				   "Networking subsystem not available" );

		/* Clean up */
		cleanupStream( stream, FALSE, FALSE );
		return( CRYPT_ERROR_NOTINITED );
		}

	/* Allocate room for the I/O buffers and error messages returned from the
	   lower-level networking code */
	if( ( stream->errorInfo = clAlloc( "completeConnect", \
									   sizeof( ERROR_INFO ) ) ) == NULL )
		{
		cleanupStream( stream, FALSE, FALSE );
		return( CRYPT_ERROR_MEMORY );
		}
	if( useTransportBuffering )
		{
		if( ( stream->buffer = clAlloc( "completeConnect", \
										NETWORK_BUFFER_SIZE ) ) != NULL )
			{
			stream->bufSize = NETWORK_BUFFER_SIZE;
			if( ( stream->writeBuffer = \
					clAlloc( "completeConnect", NETWORK_BUFFER_SIZE ) ) != NULL )
				stream->writeBufSize = NETWORK_BUFFER_SIZE;
			}
		if( stream->writeBufSize <= 0 )
			{
			cleanupStream( stream, FALSE, TRUE );
			return( CRYPT_ERROR_MEMORY );
			}
		}
	memset( stream->errorInfo, 0, sizeof( ERROR_INFO ) );
	status = openConnection( stream, options, proxyUrl, proxyUrlLen );
	if( cryptStatusError( status ) )
		{
		/* Copy back the error information to the caller */
		errorInfo->errorCode = stream->errorInfo->errorCode;
		strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE, 
				   stream->errorInfo->errorString );

		/* Clean up */
		cleanupStream( stream, FALSE, TRUE );
		return( status );
		}

	/* If we're not going through a proxy, we're done */
	if( proxyUrl == NULL )
		return( CRYPT_OK );

	/* Complete the connect via the appropriate proxy type */
	return( connectViaHttpProxy( stream, errorInfo ) );
	}

/* Open and close a network connection.  This parses a location string
   (usually a URL) into <scheme>://<host>[:<port>]/<path>[?<query>]
   components and opens a connection to the host for non-stateless
   protocols */

int sNetConnect( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
				 const NET_CONNECT_INFO *connectInfo, ERROR_INFO *errorInfo )
	{
	URL_INFO urlInfo;
	char proxyUrlBuffer[ MAX_DNS_SIZE + 8 ], *proxyURL = NULL;
	int proxyUrlLen = 0, status;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( protocol == STREAM_PROTOCOL_TCPIP || \
			protocol == STREAM_PROTOCOL_HTTP || \
			protocol == STREAM_PROTOCOL_HTTP_TRANSACTION || \
			protocol == STREAM_PROTOCOL_CMP );
	assert( isReadPtr( connectInfo, sizeof( NET_CONNECT_INFO ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( ( connectInfo->options != NET_OPTION_HOSTNAME && \
			  connectInfo->options != NET_OPTION_HOSTNAME_TUNNEL ) || \
			( ( connectInfo->options == NET_OPTION_HOSTNAME || \
				connectInfo->options == NET_OPTION_HOSTNAME_TUNNEL ) && \
			  isReadPtr( connectInfo->name, connectInfo->nameLength ) && \
			  connectInfo->iCryptSession == CRYPT_ERROR && \
			  connectInfo->networkSocket == CRYPT_ERROR ) );
	assert( connectInfo->options != NET_OPTION_TRANSPORTSESSION || \
			( connectInfo->options == NET_OPTION_TRANSPORTSESSION && \
			  connectInfo->name == NULL && connectInfo->nameLength == 0 && \
			  connectInfo->iCryptSession != CRYPT_ERROR && \
			  connectInfo->networkSocket == CRYPT_ERROR ) );
	assert( ( connectInfo->options != NET_OPTION_NETWORKSOCKET && \
			  connectInfo->options != NET_OPTION_NETWORKSOCKET_DUMMY ) || \
			( ( connectInfo->options == NET_OPTION_NETWORKSOCKET || \
				connectInfo->options == NET_OPTION_NETWORKSOCKET_DUMMY ) && \
			  connectInfo->name == NULL && connectInfo->nameLength == 0 && \
			  connectInfo->iCryptSession == CRYPT_ERROR && \
			  connectInfo->networkSocket != CRYPT_ERROR ) );
	assert( connectInfo->iUserObject >= DEFAULTUSER_OBJECT_HANDLE &&
			connectInfo->iUserObject < MAX_OBJECTS );

	/* Clear the return values */
	memset( errorInfo, 0, sizeof( ERROR_INFO ) );

	/* Initialise the network stream info */
	initStream( stream, protocol, connectInfo, FALSE );
	switch( connectInfo->options )
		{
		case NET_OPTION_HOSTNAME:
		case NET_OPTION_HOSTNAME_TUNNEL:
			/* If we're using standard HTTP then only an HTTP GET is
			   possible, use of POST requires the HTTP_TRANSACTION variant */
			if( protocol == STREAM_PROTOCOL_HTTP )
				stream->flags = STREAM_FLAG_READONLY;

			/* Parse the URI into its various components */
			status = parseURL( &urlInfo, connectInfo->name,
						connectInfo->nameLength, connectInfo->port,
						( protocol == STREAM_PROTOCOL_HTTP || \
						  protocol == STREAM_PROTOCOL_HTTP_TRANSACTION ) ? \
							URL_TYPE_HTTP : 
						( protocol == STREAM_PROTOCOL_CMP ) ? \
							URL_TYPE_HTTP : URL_TYPE_NONE );
			if( cryptStatusError( status ) )
				{
				/* There's an error in the URL format, provide more
				   information to the caller */
				strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE,
						   "Invalid host name/URL" );
				return( CRYPT_ERROR_OPEN );
				}
			status = copyUrlToStream( stream, &urlInfo );
			if( cryptStatusError( status ) )
				return( status );

			/* Check for the use of a proxy to establish the connection */
			status = checkForProxy( stream, protocol, connectInfo, 
									proxyUrlBuffer, MAX_DNS_SIZE );
			if( status > 0 )
				{
				proxyURL = proxyUrlBuffer;
				proxyUrlLen = status;
				}
			break;

		case NET_OPTION_TRANSPORTSESSION:
			stream->iTransportSession = connectInfo->iCryptSession;
			break;

		case NET_OPTION_NETWORKSOCKET:
		case NET_OPTION_NETWORKSOCKET_DUMMY:
			stream->netSocket = connectInfo->networkSocket;
			stream->flags |= STREAM_NFLAG_USERSOCKET;
			break;

		default:
			retIntError();
		}

	/* Set up access mechanisms and complete the connection */
	return( completeConnect( stream, protocol, connectInfo->options, 
							 proxyURL, proxyUrlLen, 
							 connectInfo->iUserObject, errorInfo ) );
	}

int sNetListen( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
				const NET_CONNECT_INFO *connectInfo, ERROR_INFO *errorInfo )
	{
	URL_INFO urlInfo;

	assert( isWritePtr( stream, sizeof( STREAM ) ) );
	assert( protocol == STREAM_PROTOCOL_TCPIP || \
			protocol == STREAM_PROTOCOL_HTTP_TRANSACTION || \
			protocol == STREAM_PROTOCOL_CMP );
	assert( isReadPtr( connectInfo, sizeof( NET_CONNECT_INFO ) ) );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );
	assert( connectInfo->options != NET_OPTION_HOSTNAME || \
			( connectInfo->options == NET_OPTION_HOSTNAME && \
			  connectInfo->iCryptSession == CRYPT_ERROR && \
			  connectInfo->networkSocket == CRYPT_ERROR ) );
	assert( connectInfo->options != NET_OPTION_TRANSPORTSESSION || \
			( connectInfo->options == NET_OPTION_TRANSPORTSESSION && \
			  connectInfo->name == NULL && connectInfo->nameLength == 0 && \
			  connectInfo->iCryptSession != CRYPT_ERROR && \
			  connectInfo->networkSocket == CRYPT_ERROR ) );
	assert( ( connectInfo->options != NET_OPTION_NETWORKSOCKET && \
			  connectInfo->options != NET_OPTION_NETWORKSOCKET_DUMMY ) || \
			( ( connectInfo->options == NET_OPTION_NETWORKSOCKET || \
				connectInfo->options == NET_OPTION_NETWORKSOCKET_DUMMY ) || \
			  connectInfo->name == NULL && connectInfo->nameLength == 0 &&  \
			  connectInfo->iCryptSession == CRYPT_ERROR && \
			  connectInfo->networkSocket != CRYPT_ERROR ) );
	assert( connectInfo->iUserObject >= DEFAULTUSER_OBJECT_HANDLE &&
			connectInfo->iUserObject < MAX_OBJECTS );

	/* Clear the return values */
	memset( errorInfo, 0, sizeof( ERROR_INFO ) );

	/* Initialise the network stream info */
	initStream( stream, protocol, connectInfo, TRUE );
	switch( connectInfo->options )
		{
		case NET_OPTION_HOSTNAME:
			if( connectInfo->name != NULL )
				{
				int status;

				/* Parse the interface URI into its various components */
				status = parseURL( &urlInfo, connectInfo->name,
							connectInfo->nameLength, connectInfo->port,
							( protocol == STREAM_PROTOCOL_HTTP || \
							  protocol == STREAM_PROTOCOL_HTTP_TRANSACTION || \
							  protocol == STREAM_PROTOCOL_CMP ) ? \
								URL_TYPE_HTTP : URL_TYPE_NONE );
				if( cryptStatusError( status ) )
					{
					/* There's an error in the format, provide more
					   information to the caller */
					strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE,
							   "Invalid interface name" );
					return( CRYPT_ERROR_OPEN );
					}
				status = copyUrlToStream( stream, &urlInfo );
				if( cryptStatusError( status ) )
					return( status );
				}
			break;

		case NET_OPTION_TRANSPORTSESSION:
			stream->iTransportSession = connectInfo->iCryptSession;
			break;

		case NET_OPTION_NETWORKSOCKET:
		case NET_OPTION_NETWORKSOCKET_DUMMY:
			stream->netSocket = connectInfo->networkSocket;
			stream->flags |= STREAM_NFLAG_USERSOCKET;
			break;

		default:
			retIntError();
		}

	/* Set up access mechanisms and complete the connection */
	return( completeConnect( stream, protocol, connectInfo->options, NULL, 0,
							 connectInfo->iUserObject, errorInfo ) );
	}

int sNetDisconnect( STREAM *stream )
	{
	assert( isWritePtr( stream, sizeof( STREAM ) ) );

	cleanupStream( stream, TRUE, TRUE );

	return( CRYPT_OK );
	}

/* Parse a URL into its various components */

int sNetParseURL( URL_INFO *urlInfo, const char *url, const int urlLen,
				  const URL_TYPE urlTypeHint )
	{
	assert( isWritePtr( urlInfo, sizeof( URL_INFO ) ) );
	assert( isReadPtr( url, urlLen ) );
	assert( urlTypeHint >= URL_TYPE_NONE && urlTypeHint < URL_TYPE_LAST );

	return( parseURL( urlInfo, url, urlLen, CRYPT_UNUSED, urlTypeHint ) );
	}

/* Get extended information about an error status on a network connection */

void sNetGetErrorInfo( STREAM *stream, ERROR_INFO *errorInfo )
	{
	assert( isReadPtr( stream, sizeof( STREAM ) ) );
	assert( stream->type == STREAM_TYPE_NETWORK );
	assert( isWritePtr( errorInfo, sizeof( ERROR_INFO ) ) );

	/* Remember the error code and message.  If we're running over a
	   cryptlib transport session we have to first pull the info up from the
	   session */
	if( stream->iTransportSession != CRYPT_ERROR )
		getSessionErrorInfo( stream, CRYPT_OK );
	errorInfo->errorCode = stream->errorInfo->errorCode;
	strlcpy_s( errorInfo->errorString, MAX_ERRMSG_SIZE, 
			   stream->errorInfo->errorString );
	}

#else

/****************************************************************************
*																			*
*							Network Stream Stubs							*
*																			*
****************************************************************************/

/* If there's no networking support present, we replace the network access
   routines with dummy ones that always return an error */

int sNetConnect( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
				 const NET_CONNECT_INFO *connectInfo, ERROR_INFO *errorInfo )
	{
	memset( stream, 0, sizeof( STREAM ) );
	memset( errorInfo, 0, sizeof( ERROR_INFO ) );
	return( CRYPT_ERROR_OPEN );
	}

int sNetListen( STREAM *stream, const STREAM_PROTOCOL_TYPE protocol,
				const NET_CONNECT_INFO *connectInfo, ERROR_INFO *errorInfo )
	{
	memset( stream, 0, sizeof( STREAM ) );
	memset( errorInfo, 0, sizeof( ERROR_INFO ) );
	return( CRYPT_ERROR_OPEN );
	}

int sNetDisconnect( STREAM *stream )
	{
	UNUSED( stream );

	return( CRYPT_OK );
	}

int sNetParseURL( URL_INFO *urlInfo, const char *url, const int urlLen,
				  const URL_TYPE urlTypeHint )
	{
	memset( urlInfo, 0, sizeof( URL_INFO ) );

	return( CRYPT_ERROR_BADDATA );
	}

void sNetGetErrorInfo( STREAM *stream, ERROR_INFO *errorInfo )
	{
	UNUSED( stream );

	memset( errorInfo, 0, sizeof( ERROR_INFO ) );
	}
#endif /* USE_TCP */
