I need to write code to download a file and the requirements are as follows. If the application is configured to use a proxy, attempt to download via the proxy. If that fails, attempt a direct connection. If no proxy is configured, attempt a direct connection. High-level pseudo-code:
if(ProxyEnabled)
if(!DownloadWithProxy())
DownloadWithoutProxy()
else
DownloadWithoutProxy()
I'm using WinHTTP since this code will be running in a service. The actual downloading is straightforward but I'm having issues with the proxy settings. My current pseudo-code is as follows:
hSession = WinHttpOpen(..., WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, ...)
hConnect = WinHttpConnect(hSession, ...)
hRequest = WinHttpOpenRequest(hConnect, ...)
if(Proxy.Enabled)
{
// set proxy server
WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, ServerName)
// set proxy credentials
WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY, UserName, Password)
if(!DownloadFile(hRequest))
{
// reset proxy server
WinHttpSetOption(hRequest, WINHTTP_OPTION_PROXY, NULL)
// reset proxy credentials
WinHttpSetCredentials(hRequest, WINHTTP_AUTH_TARGET_PROXY, NULL, NULL)
DownloadFile(hRequest)
}
}
else
{
DownloadFile(hRequest)
}
Where DownloadFile() does the standard WinHttpSendRequest() and WinHttpReceiveResponse() sequence. Everything works fine except in the case where the download via the proxy fails. When that happens the call to WinHttpSetCredentials() to reset the credentials fails (with ERROR_INVALID_PARAMETER), and as a result the second call to DownloadFile() still tries to use the proxy (even though I reset it) and the credentials. NOTE: In this specific scenario I am using a valid proxy server but invalid proxy credentials to trigger the failure.
So I guess my question is what is the best approach? As far as I can tell it is not possible to reset the credentials set via WinHttpSetCredentials(), so I guess I should just recreate the request for each call to DownloadFile() rather than trying to be "clever" by reusing a single request object.
WinHttpSetCredentials() takes 6 parameters, one of which is the auth scheme. You have only shown 4 parameters, and do not say which auth scheme you are using. Overall, your sequence for authenticating is different than what MSDN suggests:
Authentication in WinHTTP
A typical WinHTTP application completes the following steps in order to handle authentication.
•Request a resource with WinHttpOpenRequest and WinHttpSendRequest.
•Check the response headers with WinHttpQueryHeaders.
•If a 401 or 407 status code is returned indicating that authentication is required, call WinHttpQueryAuthSchemes to find an acceptable scheme.
•Set the authentication scheme, username, and password with WinHttpSetCredentials.
•Resend the request with the same request handle by calling WinHttpSendRequest.
Also take note of this condition:
The credentials set by WinHttpSetCredentials are only used for one request. WinHTTP does not cache the credentials to use in other requests, which means that applications must be written that can respond to multiple requests. If an authenticated connection is re-used, other requests may not be challenged, but your code should be able to respond to a request at any time.
The documentation linked above includes the following code example for how to use WinHTTPSetCredentials() correctly:
#include <windows.h>
#include <winhttp.h>
#include <stdio.h>
#pragma comment(lib, "winhttp.lib")
DWORD ChooseAuthScheme( DWORD dwSupportedSchemes )
{
// It is the server's responsibility only to accept
// authentication schemes that provide a sufficient
// level of security to protect the servers resources.
//
// The client is also obligated only to use an authentication
// scheme that adequately protects its username and password.
//
// Thus, this sample code does not use Basic authentication
// becaus Basic authentication exposes the client's username
// and password to anyone monitoring the connection.
if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE )
return WINHTTP_AUTH_SCHEME_NEGOTIATE;
else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NTLM )
return WINHTTP_AUTH_SCHEME_NTLM;
else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT )
return WINHTTP_AUTH_SCHEME_PASSPORT;
else if( dwSupportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST )
return WINHTTP_AUTH_SCHEME_DIGEST;
else
return 0;
}
struct SWinHttpSampleGet
{
LPCWSTR szServer;
LPCWSTR szPath;
BOOL fUseSSL;
LPCWSTR szServerUsername;
LPCWSTR szServerPassword;
LPCWSTR szProxyUsername;
LPCWSTR szProxyPassword;
};
void WinHttpAuthSample( IN SWinHttpSampleGet *pGetRequest )
{
DWORD dwStatusCode = 0;
DWORD dwSupportedSchemes;
DWORD dwFirstScheme;
DWORD dwSelectedScheme;
DWORD dwTarget;
DWORD dwLastStatus = 0;
DWORD dwSize = sizeof(DWORD);
BOOL bResults = FALSE;
BOOL bDone = FALSE;
DWORD dwProxyAuthScheme = 0;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen( L"WinHTTP Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0 );
INTERNET_PORT nPort = ( pGetRequest->fUseSSL ) ?
INTERNET_DEFAULT_HTTPS_PORT :
INTERNET_DEFAULT_HTTP_PORT;
// Specify an HTTP server.
if( hSession )
hConnect = WinHttpConnect( hSession,
pGetRequest->szServer,
nPort, 0 );
// Create an HTTP request handle.
if( hConnect )
hRequest = WinHttpOpenRequest( hConnect,
L"GET",
pGetRequest->szPath,
NULL,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
( pGetRequest->fUseSSL ) ?
WINHTTP_FLAG_SECURE : 0 );
// Continue to send a request until status code
// is not 401 or 407.
if( hRequest == NULL )
bDone = TRUE;
while( !bDone )
{
// If a proxy authentication challenge was responded to, reset
// those credentials before each SendRequest, because the proxy
// may require re-authentication after responding to a 401 or
// to a redirect. If you don't, you can get into a
// 407-401-407-401- loop.
if( dwProxyAuthScheme != 0 )
bResults = WinHttpSetCredentials( hRequest,
WINHTTP_AUTH_TARGET_PROXY,
dwProxyAuthScheme,
pGetRequest->szProxyUsername,
pGetRequest->szProxyPassword,
NULL );
// Send a request.
bResults = WinHttpSendRequest( hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS,
0,
WINHTTP_NO_REQUEST_DATA,
0,
0,
0 );
// End the request.
if( bResults )
bResults = WinHttpReceiveResponse( hRequest, NULL );
// Resend the request in case of
// ERROR_WINHTTP_RESEND_REQUEST error.
if( !bResults && GetLastError( ) == ERROR_WINHTTP_RESEND_REQUEST)
continue;
// Check the status code.
if( bResults )
bResults = WinHttpQueryHeaders( hRequest,
WINHTTP_QUERY_STATUS_CODE |
WINHTTP_QUERY_FLAG_NUMBER,
NULL,
&dwStatusCode,
&dwSize,
NULL );
if( bResults )
{
switch( dwStatusCode )
{
case 200:
// The resource was successfully retrieved.
// You can use WinHttpReadData to read the
// contents of the server's response.
printf( "The resource was successfully retrieved.\n" );
bDone = TRUE;
break;
case 401:
// The server requires authentication.
printf(" The server requires authentication. Sending credentials...\n" );
// Obtain the supported and preferred schemes.
bResults = WinHttpQueryAuthSchemes( hRequest,
&dwSupportedSchemes,
&dwFirstScheme,
&dwTarget );
// Set the credentials before resending the request.
if( bResults )
{
dwSelectedScheme = ChooseAuthScheme( dwSupportedSchemes);
if( dwSelectedScheme == 0 )
bDone = TRUE;
else
bResults = WinHttpSetCredentials( hRequest,
dwTarget,
dwSelectedScheme,
pGetRequest->szServerUsername,
pGetRequest->szServerPassword,
NULL );
}
// If the same credentials are requested twice, abort the
// request. For simplicity, this sample does not check
// for a repeated sequence of status codes.
if( dwLastStatus == 401 )
bDone = TRUE;
break;
case 407:
// The proxy requires authentication.
printf( "The proxy requires authentication. Sending credentials...\n" );
// Obtain the supported and preferred schemes.
bResults = WinHttpQueryAuthSchemes( hRequest,
&dwSupportedSchemes,
&dwFirstScheme,
&dwTarget );
// Set the credentials before resending the request.
if( bResults )
dwProxyAuthScheme = ChooseAuthScheme(dwSupportedSchemes);
// If the same credentials are requested twice, abort the
// request. For simplicity, this sample does not check
// for a repeated sequence of status codes.
if( dwLastStatus == 407 )
bDone = TRUE;
break;
default:
// The status code does not indicate success.
printf("Error. Status code %d returned.\n", dwStatusCode);
bDone = TRUE;
}
}
// Keep track of the last status code.
dwLastStatus = dwStatusCode;
// If there are any errors, break out of the loop.
if( !bResults )
bDone = TRUE;
}
// Report any errors.
if( !bResults )
{
DWORD dwLastError = GetLastError( );
printf( "Error %d has occurred.\n", dwLastError );
}
// Close any open handles.
if( hRequest ) WinHttpCloseHandle( hRequest );
if( hConnect ) WinHttpCloseHandle( hConnect );
if( hSession ) WinHttpCloseHandle( hSession );
}
You just have to inject your WinHttpSetOption(WINHTTP_OPTION_PROXY) where appropriate, and be sure that you are handling authentication requests for both proxy and non-proxy connections (in case the target HTTP server requires its own authentication).
Related
I've been trying to modify outgoing DNS packets via the DATAGRAM_DATA layer in WFP, however i get blue screen errors when rewriting the destination ip in the outgoing packet. What am i doing wrong?
I admit i found the parameters for FwpsInjectTransportSendAsync a bit confusing, and was unsure exactly what to put in for the sendParams arg - though i think what i have looks right.
RtlIpv4StringToAddressExW(
L"1.1.1.1", // hard-coding the new (rewritten) dns server for now
FALSE,
&sin4.sin_addr,
&sin4.sin_port);
RtlIpv4StringToAddressExW(
L"8.8.8.8", // hard-coding the original dns server for now
FALSE,
&origSin4.sin_addr,
&origSin4.sin_port);
if ((Direction == FWP_DIRECTION_OUTBOUND) && (PacketInjectionState == FWPS_PACKET_NOT_INJECTED) && (RemotePort == 53) && (RemoteAddress == origSin4.sin_addr.S_un.S_addr))
{
UINT32 IpHeaderSize = inMetaValues->ipHeaderSize;
UINT32 TransportHeaderSize = inMetaValues->transportHeaderSize;
UINT64 endpointHandle = inMetaValues->transportEndpointHandle;
PNET_BUFFER NetBuffer = NET_BUFFER_LIST_FIRST_NB((PNET_BUFFER_LIST)layerData);
NdisRetreatNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, 0, NULL);
PNET_BUFFER_LIST NetBufferList = NULL;
NTSTATUS Status = FwpsAllocateCloneNetBufferList(layerData, NULL, NULL, 0, &NetBufferList);
if (!NT_SUCCESS(Status))
{
return;
}
NdisAdvanceNetBufferDataStart(NetBuffer, IpHeaderSize + TransportHeaderSize, FALSE, NULL);
if (!NetBufferList)
{
return;
}
NetBuffer = NET_BUFFER_LIST_FIRST_NB(NetBufferList);
PIPV4_HEADER IpHeader = NdisGetDataBuffer(NetBuffer, sizeof(IPV4_HEADER), NULL, 1, 0);
// Rewriting the dest ip
IpHeader->DestinationAddress = sin4.sin_addr.S_un.S_addr;
// Updating the IP checksum
UpdateIpv4HeaderChecksum(IpHeader, sizeof(IPV4_HEADER));
// not 100% sure the sendParams argument is setup correctly, the docs are slightly unclear
FWPS_TRANSPORT_SEND_PARAMS sendParams = {
.remoteAddress = (UCHAR*)IpHeader->DestinationAddress,
.remoteScopeId = inMetaValues->remoteScopeId,
.controlData = inMetaValues->controlData,
.controlDataLength = inMetaValues->controlDataLength,
.headerIncludeHeader = inMetaValues->headerIncludeHeader,
.headerIncludeHeaderLength = inMetaValues->headerIncludeHeaderLength
};
Status = FwpsInjectTransportSendAsync(g_InjectionHandle, NULL, endpointHandle, 0, &sendParams, AF_INET, inMetaValues->compartmentId, NetBufferList, DriverDatagramDataInjectComplete, NULL);
if (!NT_SUCCESS(Status))
{
FwpsFreeCloneNetBufferList(NetBufferList, 0);
}
classifyOut->actionType = FWP_ACTION_BLOCK;
classifyOut->rights &= ~FWPS_RIGHT_ACTION_WRITE;
classifyOut->flags |= FWPS_CLASSIFY_OUT_FLAG_ABSORB;
}
Two things stand out to me, both in the sendParams.
First, remoteAddress is incorrect. It needs to a pointer to the address, so it should be (UCHAR*)&IpHeader->DestinationAddress.
Second, FwpsInjectTransportSendAsync() is asynchronous so any parameters you pass to it need to stay valid until it completes which may be after your calling function returns. Typically you allocate some context structure that contains sendParams and deep copies of relevant members (remoteAddress and controlData). You pass this as the context to the completion routine where you free it.
I am writing a multithreaded client that uses an IO Completion Port.
I create and connect the socket that has the WSA_FLAG_OVERLAPPED attribute set.
if ((m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
throw std::exception("Failed to create socket.");
}
if (WSAConnectByName(m_socket, L"server.com", L"80", &localAddressLength, reinterpret_cast<sockaddr*>(&localAddress), &remoteAddressLength, &remoteAddress, NULL, NULL) == FALSE)
{
throw std::exception("Failed to connect.");
}
I associate the IO Completion Port with the socket.
if ((m_hIOCP = CreateIoCompletionPort(reinterpret_cast<HANDLE>(m_socket), m_hIOCP, NULL, 8)) == NULL)
{
throw std::exception("Failed to create IOCP object.");
}
All appears to go well until I try to send some data over the socket.
SocketData* socketData = new SocketData;
socketData->hEvent = 0;
DWORD bytesSent = 0;
if (WSASend(m_socket, socketData->SetBuffer(socketData->GenerateLoginRequestHeader()), 1, &bytesSent, NULL, reinterpret_cast<OVERLAPPED*>(socketData), NULL) == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING)
{
throw std::exception("Failed to send data.");
}
Instead of returning SOCKET_ERROR with the last error set to WSA_IO_PENDING, WSASend returns immediately.
I need the IO to pend and for it's completion to be handled in my thread function which is also my worker thread.
unsigned int __stdcall MyClass::WorkerThread(void* lpThis)
{
}
I've done this before but I don't know what is going wrong in this case, I'd greatly appreciate any efforts in helping me fix this problem.
It's not a problem unless you make it so.
As long as you're not calling SetFileCompletionNotificationModes() and setting the flag to skip completion port processing on success then even if WSARecv (or whatever) returns SUCCESS an IO Completion Packet is queued to the IOCP the same as if ERROR_IO_PENDING was returned. Thus you need no special handling for the non error return case.
See http://support.microsoft.com/default.aspx?scid=kb;en-us;Q192800 for details.
First of all break the call into more clear logic:
int nRet = WSASend(m_socket, socketData->SetBuffer(socketData->GenerateLoginRequestHeader()), 1, NULL, NULL, reinterpret_cast<OVERLAPPED*>(socketData), NULL);
if (nRet == SOCKET_ERROR)
{
if ((WSAGetLastError()) == WSA_IO_PENDING)
nRet = 0; // ok
else
throw std::exception("Failed to send data."); // failed
}
Also, as you can see in my code, you should NOT pass the "&bytesSent" parameter according to WSASend:
Use NULL for this parameter if the
lpOverlapped parameter is not NULL to
avoid potentially erroneous results.
Besides that your call to WSASend() looks fine.
I am writing a windows filesystem minifilter driver that must fail I/O Request Packets (IRP's) in a preoperation callback based on their type (read/write).
How can I find out from the callback parameters (or elsewhere?) if the operation is read-like ( only reads data ) or it's write-like ( modifies data on the disk - write, delete, format etc ) ?
Here is a list of major IRP codes.
I'm thinking on stuff like:
Data->Iopb->TargetFileObject->ReadAccess
Data->Iopb->TargetFileObject->WriteAccess
But I'm not sure, I think these are available only in postoperation callback. The documentation is really cumbersome.
Code sample for further clarification:
FLT_PREOP_CALLBACK_STATUS
Fail (
__inout PFLT_CALLBACK_DATA Data,
__in PCFLT_RELATED_OBJECTS FltObjects,
__deref_out_opt PVOID *CompletionContext
)
{
FLT_PREOP_CALLBACK_STATUS status = FLT_PREOP_SUCCESS_NO_CALLBACK;
//********************************************************************
if ( IS_WRITE_LIKE(Data, FltObjects) ) { // ??? HOW DO I FIND OUT ???
//********************************************************************
if( FLT_IS_FASTIO_OPERATION(Data) ){
status = FLT_PREOP_DISALLOW_FASTIO;
} else {
status = FLT_PREOP_COMPLETE;
}
Data->IoStatus.Status = STATUS_ACCESS_DENIED;
Data->IoStatus.Information = 0;
return status;
}
return status;
}
Here is an euristic list I got after a lot of trial and error:
write-like:
IRP_MJ_SET_EA
IRP_MJ_SET_INFORMATION
IRP_MJ_SET_QUOTA
IRP_MJ_SET_SECURITY
IRP_MJ_SET_VOLUME_INFORMATION
IRP_MJ_WRITE
read-like:
IRP_MJ_CREATE
IRP_MJ_DEVICE_CONTROL
IRP_MJ_FILE_SYSTEM_CONTROL
IRP_MJ_FLUSH_BUFFERS
IRP_MJ_INTERNAL_DEVICE_CONTROL
IRP_MJ_QUERY_EA
IRP_MJ_QUERY_INFORMATION
IRP_MJ_QUERY_QUOTA
IRP_MJ_QUERY_SECURITY
IRP_MJ_QUERY_VOLUME_INFORMATION
IRP_MJ_READ
IRP_MJ_OPERATION_END
The following piece of test code runs under Windows Mobile.
It's objective is to seek out the default message store so I can get the proper account name for programmatically compiling an email.
IMAPISession *mapiSession;
HRESULT hr = S_OK;
MAPIInitialize (NULL);
IMAPITable *msgTable;
SRowSet *pRows;
IMsgStore *msgStore;
if (MAPILogonEx(0,NULL,NULL,0,&mapiSession) != S_OK)
{
// MessageBox(g_hWnd,_T("Failed to logon"),_T("Error"),0);
}
else
{
SizedSPropTagArray(3, PropTagArr) = {3,{PR_DISPLAY_NAME,
PR_ENTRYID,
PR_DEFAULT_STORE}};
hr = mapiSession->GetMsgStoresTable(MAPI_UNICODE,&msgTable);
hr = msgTable->SetColumns((LPSPropTagArray)&PropTagArr, 0);
if (!hr)
{
do
{
hr = msgTable->QueryRows(1,0,&pRows);
LPSPropValue lpProp;
lpProp = &pRows->aRow[0].lpProps[0];
// if(_tcscmp( lpProp->Value.LPSZ, _T("SMS") ) == 0 )
// break;
lpProp = &pRows->aRow[0].lpProps[0];
if (lpProp->ulPropTag == PR_DEFAULT_STORE)
break;
lpProp = &pRows->aRow[0].lpProps[1];
if (lpProp->ulPropTag == PR_DEFAULT_STORE)
break;
lpProp = &pRows->aRow[0].lpProps[2];
if (lpProp->ulPropTag == PR_DEFAULT_STORE)
break;
FreeProws(pRows);
pRows = NULL;
}while (!hr);
hr = mapiSession->OpenMsgStore (0,
pRows->aRow[0].lpProps[1].Value.bin.cb,
(ENTRYID*)pRows->aRow[0].lpProps[1].Value.bin.lpb,
NULL,
MDB_NO_DIALOG | MAPI_BEST_ACCESS,
&msgStore);
... BUT, fails to get the PR_DEFAULT_STORE property on a Windows Mobile device. I'm guessing Microsoft didn't implement it accurately. And so, lpProp->ulPropTag will never == PR_DEFAULT_STORE. It's always 0000.
Has anyone had success getting PR_DEFAULT_STORE using MAPI under Windows Mobile?
Is there another way of the determining the default message store?
I use VS6 and ATL with CServiceModule to implement a custom windows service. In case of a fatal error service should shut itself down. Since CServiceModule is available via _Module variable in all files I thought of something like this to cause CServiceModule::Run to stop pumping messages and shut itself down
PostThreadMessage(_Module.dwThreadID, WM_QUIT, 0, 0);
Is this correct or you have better idea ?
For self shutdown you send command to Service Manager. Try this sample :
BOOL StopServiceCmd ( const char * szServiceName )
{
SC_HANDLE schService;
SC_HANDLE schSCManager;
SERVICE_STATUS ssStatus; // current status of the service
BOOL bRet;
int iCont=0;
schSCManager = OpenSCManager(
NULL, // machine (NULL == local)
NULL, // database (NULL == default)
SC_MANAGER_ALL_ACCESS // access required
);
if ( schSCManager )
{
schService = OpenService(schSCManager, szServiceName, SERVICE_ALL_ACCESS);
if (schService)
{
// try to stop the service
if ( ControlService( schService, SERVICE_CONTROL_STOP, &ssStatus ) )
{
Sleep( 1000 );
while( QueryServiceStatus( schService, &ssStatus ) )
{
iCont++;
if ( ssStatus.dwCurrentState == SERVICE_STOP_PENDING )
{
Sleep( 1000 );
if ( iCont > 4 ) break;
}
else
break;
}
if ( ssStatus.dwCurrentState == SERVICE_STOPPED )
bRet = TRUE;
else
bRet = FALSE;
}
CloseServiceHandle(schService);
}
else
bRet = FALSE;
CloseServiceHandle(schSCManager);
}
else
bRet = FALSE;
return bRet;
}
I believe that if you do this, then the service manager will think that your service has crashed and if the user ever sets it up to auto-restart, it will.
In .NET, you use the ServiceController to signal your service to shut down. I expect it is similar in Win32 since most of this stuff in .NET is just wrappers. Sorry, I don't have C++ code handy to shut down the service, but here is the .NET code. This will hopefully help you Google the info you need, or find the docs in MSDN.
This is from some test suite code, thus the style of error checking ;) You will need to put this code in a thread so that the shutdown message gets handled.
private void stopPLService( bool close )
{
if ( m_serviceController == null )
{
m_serviceController = new ServiceController( "PLService" );
}
WriteLine( "StopPLService" );
if ( m_serviceController != null )
{
try
{
m_serviceController.Stop();
}
catch
{
// Probably just means that it wasn't running or installed, ignore
}
// Wait up to 30 seconds for the service to stop
try
{
m_serviceController.WaitForStatus( ServiceControllerStatus.Stopped, new TimeSpan( 0, 0, 30 ) );
}
catch ( System.ServiceProcess.TimeoutException )
{
Assert.Fail( "Timeout waiting for PLService to stop" );
}
catch
{
// Not installed, we only care in the start
}
if ( close )
{
m_serviceController.Close();
m_serviceController = null;
}
}
}
You probably want to use the ControlService or ControlServiceEx methods to shutdown your service. You should be able to get the required handle from the CServiceModule.