/****************************************************************************
*																			*
*						 Network Stream Proxy Management					*
*						Copyright Peter Gutmann 1993-2025					*
*																			*
****************************************************************************/

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

#ifdef USE_TCP

/****************************************************************************
*																			*
*							SOCKS Proxy Management							*
*																			*
****************************************************************************/

/* Open a connection through a SOCKSv4 proxy.  This was added in cryptlib 
   3.0 in 2002 but disabled in cryptlib 3.1 in 2003 since it didn't appear 
   to be used by anyone, since then there haven't been any requests to 
   enable it in response to the config option being present so there's 
   presumably no need for it */

#if 0

CHECK_RETVAL STDC_NONNULL_ARG( ( 1 ) ) \
int connectViaSocksProxy( INOUT_PTR 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 ) ) );

	REQUIRES_S( stream->type == STREAM_TYPE_NETWORK );

	/* 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 SOCKSv4 request string:

		BYTE: version = 4
		BYTE: command = 1 (connect)
		WORD: port
		LONG: IP address
		STRING: userName + '\0'

	   Note that this has a potential problem in that it requires a DNS 
	   lookup by the client, which can lead to problems if the client
	   can't get DNS requests out because only SOCKSified access is allowed.
	   A related problem occurs when SOCKS is being used as a tunnelling
	   interface because the DNS lookup will communicate data about the 
	   client to an observer outside the tunnel.

	   To work around this there's a so-called SOCKSv4a protocol that has 
	   the SOCKS proxy perform the lookup:

		BYTE: version = 4
		BYTE: command = 1 (connect)
		WORD: port
		LONG: IP address = 0x00 0x00 0x00 0xFF
		STRING: userName + '\0'
		STRING: FQDN + '\0'

	   Unfortunately there's no way to tell whether a SOCKS server supports
	   4a or only 4, but in any case since SOCKS support is currently 
	   disabled we leave the poke-and-hope 4a detection until such time as
	   someone actually requests it */
	*bufPtr++ = 4; *bufPtr++ = 1;
	mputWord( bufPtr, netStream->port );
	status = getIPAddress( stream, bufPtr, netStream->host );
	strlcpy_s( bufPtr + 4, CRYPT_MAX_TEXTSIZE, userName );
	length = 1 + 1 + 2 + 4 + strnlen_s( userName, CRYPT_MAX_TEXTSIZE ) + 1;
	if( cryptStatusError( status ) )
		{
		netStream->transportDisconnectFunction( stream, TRUE );
		return( status );
		}

	/* Send the data to the server and read back the reply */
	status = netStream->transportWriteFunction( stream, socksBuffer, length,
												TRANSPORT_FLAG_FLUSH );
	if( cryptStatusOK( status ) )
		status = netStream->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;
		netStream->transportDisconnectFunction( stream, TRUE );
		return( status );
		}

	/* Make sure that everything is OK:

		BYTE: null = 0
		BYTE: status = 90 (OK)
		WORD: port
		LONG: IP address */
	if( socksBuffer[ 1 ] != 90 )
		{
		LOOP_INDEX i;

		netStream->transportDisconnectFunction( stream, TRUE );
		strlcpy_s( netStream->errorInfo->errorString, MAX_ERRMSG_SIZE, 
				   "Socks proxy returned" );
		LOOP_SMALL( i = 0, i < 8, i++ )
			{
			int result;

			ENSURES( LOOP_INVARIANT_SMALL( i, 0, 7 ) );

			result = sprintf_s( netStream->errorInfo->errorString + 20 + ( i * 3 ),
								MAX_ERRMSG_SIZE - ( 20 + ( i * 3 ) ), " %02X", 
								socksBuffer[ i ] );
			ENSURES( rangeCheck( result, 2, \
								 MAX_ERRMSG_SIZE - ( 20 + ( i * 3 ) + 1 ) ) );
			}
		ENSURES( LOOP_BOUND_OK );
		strlcat_s( netStream->errorInfo->errorString, MAX_ERRMSG_SIZE, "." );
		netStream->errorCode = socksBuffer[ 1 ];
		return( CRYPT_ERROR_OPEN );
		}

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

/****************************************************************************
*																			*
*							HTTP Proxy Management							*
*																			*
****************************************************************************/

#ifdef USE_HTTP

/* Open a connection via an HTTP proxy */

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 2 ) ) \
int connectViaHttpProxy( INOUT_PTR STREAM *stream, 
						 INOUT_PTR ERROR_INFO *errorInfo )
	{
	NET_STREAM_INFO *netStream = DATAPTR_GET( stream->netStream );
	STM_WRITE_FUNCTION writeFunction;
	STM_READ_FUNCTION readFunction;
	STM_TRANSPORTDISCONNECT_FUNCTION transportDisconnectFunction;
	HTTP_DATA_INFO httpDataInfo;
	BYTE ALIGN_STACK_DATA buffer[ SAFEBUFFER_SIZE( 512 ) + 8 ];
	int length, status;

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

	REQUIRES_S( netStream != NULL && sanityCheckNetStream( netStream ) );
	REQUIRES_S( stream->type == STREAM_TYPE_NETWORK );

	/* Set up the function pointers.  We have to do this after the netStream
	   check otherwise we'd potentially be dereferencing a NULL pointer */
	writeFunction = ( STM_WRITE_FUNCTION ) \
					FNPTR_GET( netStream->writeFunction );
	readFunction = ( STM_READ_FUNCTION ) \
				   FNPTR_GET( netStream->readFunction );
	transportDisconnectFunction = ( STM_TRANSPORTDISCONNECT_FUNCTION ) \
					FNPTR_GET( netStream->transportDisconnectFunction );
	REQUIRES_S( writeFunction != NULL );
	REQUIRES_S( readFunction != NULL );
	REQUIRES_S( transportDisconnectFunction != NULL );

	/* 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
	   completed we re-set the stream to pure TCP I/O and clear any stream
	   flags that were set during the proxying */
	safeBufferInit( SAFEBUFFER_PTR( buffer ), 512 );
	setStreamLayerHTTP( netStream );
	status = initHttpInfoWrite( &httpDataInfo, SAFEBUFFER_PTR( buffer ), 
								512, 512 );
	if( cryptStatusOK( status ) )
		{
		status = writeFunction( stream, &httpDataInfo, 
								sizeof( HTTP_DATA_INFO ), &length );
		}
	if( cryptStatusOK( status ) )
		{
		status = initHttpInfoRead( &httpDataInfo, 
								   SAFEBUFFER_PTR( buffer ), 512 );
		}
	if( cryptStatusOK( status ) )
		{
		status = readFunction( stream, &httpDataInfo, 
							   sizeof( HTTP_DATA_INFO ), &length );
		}
	setStreamLayerDirect( netStream );
	INIT_FLAGS( stream->flags, STREAM_FLAG_NONE );
	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;
		copyErrorInfo( errorInfo, NETSTREAM_ERRINFO );
		transportDisconnectFunction( netStream, TRUE );
		}

	return( status );
	}
#endif /* USE_HTTP */

/****************************************************************************
*																			*
*							Proxy Autoconfig Management						*
*																			*
****************************************************************************/

/* 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 );

CHECK_RETVAL STDC_NONNULL_ARG( ( 1, 3, 4 ) ) \
int findProxyUrl( OUT_BUFFER( proxyMaxLen, *proxyLen ) char *proxy, 
				  IN_LENGTH_DNS const int proxyMaxLen, 
				  OUT_LENGTH_BOUNDED_Z( proxyMaxLen ) int *proxyLen,
				  IN_BUFFER( urlLen ) const char *url, 
				  IN_LENGTH_DNS 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, wcsProxyLen DUMMY_INIT;
	int offset, proxyStatus = -1;

	assert( isWritePtrDynamic( proxy, proxyMaxLen ) );
	assert( isWritePtr( proxyLen, sizeof( int ) ) );
	assert( isReadPtrDynamic( url, urlLen ) );

	REQUIRES( proxyMaxLen >= 10 && proxyMaxLen <= MAX_DNS_SIZE );
	REQUIRES( urlLen > 0 && urlLen <= MAX_DNS_SIZE );

	/* Clear return values */
	memset( proxy, 0, min( 16, proxyMaxLen ) );
	*proxyLen = 0;
	
	/* Under Win2K SP3 and Windows XP and newer (2003, Vista, etc), 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 = DynamicLoad( "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 )
			{
			DynamicUnload( 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 ) )
		{
		if( proxyInfo.lpszProxy != NULL )
			{
			proxyStatus = wcstombs_s( &wcsProxyLen, proxy, proxyMaxLen,
									  proxyInfo.lpszProxy, MAX_DNS_SIZE );
			GlobalFree( proxyInfo.lpszProxy );
			}
		if( proxyInfo.lpszProxyBypass != NULL )
			GlobalFree( proxyInfo.lpszProxyBypass );
		if( proxyStatus == 0 )
			{
			*proxyLen = wcsProxyLen - 1;	/* Exclude '\0' */
			return( CRYPT_OK );
			}
		}

	/* The next fallback is to get the proxy info from MSIE, which is used 
	   if WPAD isn't available.  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 ) )
		{
		if( ieProxyInfo.lpszProxy != NULL )
			{
			proxyStatus = wcstombs_s( &wcsProxyLen, proxy, proxyMaxLen,
									  ieProxyInfo.lpszProxy, MAX_DNS_SIZE );
			GlobalFree( ieProxyInfo.lpszProxy );
			}
		if( ieProxyInfo.lpszAutoConfigUrl != NULL )
			GlobalFree( ieProxyInfo.lpszAutoConfigUrl );
		if( ieProxyInfo.lpszProxyBypass != NULL )
			GlobalFree( ieProxyInfo.lpszProxyBypass );
		if( proxyStatus == 0 )
			{
			*proxyLen = wcsProxyLen - 1;	/* Exclude '\0' */
			return( CRYPT_OK );
			}
		}

	/* 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_s()
	   Unicode conversion and following WinHttpGetProxyForUrl() lookup */
	if( strFindStr( url, urlLen, "://", 3 ) < 0 )
		{
		strlcpy_s( urlBuffer, MAX_DNS_SIZE, "http://" );
		offset = 7;
		}
	else
		{
		/* There's already a schema present, not need to manually add one */
		offset = 0;
		}
	REQUIRES( boundsCheck( offset, urlLen, MAX_DNS_SIZE ) );
	memcpy( urlBuffer + offset, url, urlLen );
	REQUIRES( !checkOverflowAdd( offset, urlLen ) );
	urlBuffer[ offset + urlLen ] = '\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.

	   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 );
		}
	memset( &proxyInfo, 0, sizeof( WINHTTP_PROXY_INFO ) );
	if( pWinHttpGetProxyForUrl( hSession, unicodeURL, &autoProxyOptions,
								&proxyInfo ) != TRUE )
		{
		pWinHttpCloseHandle( hSession );
		return( CRYPT_ERROR_NOTFOUND );
		}
	if( proxyInfo.lpszProxy != NULL )
		{
		proxyStatus = wcstombs_s( &wcsProxyLen, proxy, proxyMaxLen,
								  proxyInfo.lpszProxy, MAX_DNS_SIZE );
		GlobalFree( proxyInfo.lpszProxy );
		}
	if( proxyInfo.lpszProxyBypass != NULL )
		GlobalFree( proxyInfo.lpszProxyBypass );
	pWinHttpCloseHandle( hSession );
	if( proxyStatus != 0 )
		return( CRYPT_ERROR_NOTFOUND );
	*proxyLen = wcsProxyLen - 1;		/* Exclude '\0' */

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

#endif /* USE_TCP */
