1926 lines
51 KiB
C++
1926 lines
51 KiB
C++
//////////////////////////////////////////////////////////////////////////////
|
|
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <stdio.h>
|
|
#include <conio.h>
|
|
#include "SecureService.h"
|
|
#include <strsafe.h>
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// print_result - Displays the HRESULT
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
void print_result( HRESULT hr )
|
|
{
|
|
if( hr == S_OK )
|
|
{
|
|
_cwprintf(L"[S_OK]\r\n");
|
|
}
|
|
else
|
|
{
|
|
_cwprintf(L"[ERROR: %x]\r\n", hr);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// StripCbPath - Strips path info from a filename
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT StripCbPath(
|
|
_Out_writes_bytes_(cbDst) LPWSTR pszDst,
|
|
size_t cbDst,
|
|
LPCWSTR pszSrc)
|
|
{
|
|
LPCWSTR pszBackSlash = NULL;
|
|
LPCWSTR pszSlash = NULL;
|
|
LPCWSTR psz = NULL;
|
|
|
|
if( NULL == pszSrc )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
pszBackSlash = wcsrchr(pszSrc, '\\');
|
|
if( NULL != pszBackSlash )
|
|
{
|
|
pszBackSlash++;
|
|
}
|
|
|
|
pszSlash = wcsrchr(pszSrc, '/');
|
|
if( NULL != pszSlash )
|
|
{
|
|
pszSlash++;
|
|
}
|
|
|
|
psz = (pszBackSlash > pszSlash ? pszBackSlash : pszSlash);
|
|
if( NULL == psz )
|
|
{
|
|
psz = pszSrc;
|
|
}
|
|
|
|
return ::StringCbCopyW(pszDst, cbDst, psz);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CloneString - Allocates memory (via WSD Linked Memory) and copies a string
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CloneString(
|
|
LPCWSTR pszSrc,
|
|
LPCWSTR *ppszDst)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
LPWSTR pszDst = NULL;
|
|
size_t len = 0;
|
|
size_t cchDst = 0;
|
|
|
|
if( NULL == pszSrc )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if( NULL == ppszDst )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
*ppszDst = NULL;
|
|
|
|
hr = ::StringCchLengthW( pszSrc, MAX_PATH, &len );
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
cchDst = len + 1;
|
|
pszDst = (LPWSTR)WSDAllocateLinkedMemory( NULL,
|
|
cchDst * sizeof(WCHAR) );
|
|
|
|
if( NULL == pszDst )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
hr = ::StringCchCopyW( pszDst, cchDst, pszSrc );
|
|
}
|
|
|
|
// cleanup
|
|
if( S_OK != hr )
|
|
{
|
|
if( NULL != pszDst )
|
|
{
|
|
WSDFreeLinkedMemory( pszDst );
|
|
pszDst = NULL;
|
|
}
|
|
}
|
|
|
|
*ppszDst = pszDst;
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CreateStringList - Allocates a string list object for a string
|
|
// The resulting list object will eventually be used in a linked list,
|
|
// but this routine does not chain the objects together.
|
|
//
|
|
// The outparam ppList will contain the following members:
|
|
// Element (cloned from pszItem, and linked to ppList)
|
|
// Next (NULL, but will be modified by caller to point to next
|
|
// PWCHAR_LIST in the linked list)
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CreateStringList(
|
|
LPCWSTR pszItem,
|
|
PWCHAR_LIST **ppList)
|
|
{
|
|
PWCHAR_LIST* pList = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
if( NULL == pszItem )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if( NULL == ppList )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
*ppList = NULL;
|
|
|
|
// Allocate and zero the linked-list container structure first
|
|
if( S_OK == hr )
|
|
{
|
|
pList = (PWCHAR_LIST*)WSDAllocateLinkedMemory( NULL, sizeof(PWCHAR_LIST) );
|
|
|
|
if( NULL == pList )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
// NULL the Next field and clone a string into the Element field
|
|
if( S_OK == hr )
|
|
{
|
|
pList->Next = NULL;
|
|
hr = CloneString( pszItem, &pList->Element );
|
|
}
|
|
|
|
// Attach the Element item to pList
|
|
if( S_OK == hr )
|
|
{
|
|
WSDAttachLinkedMemory( (void*)pList, (void*)pList->Element );
|
|
}
|
|
|
|
// If all was successful, set outparam and clear local variable
|
|
if( S_OK == hr )
|
|
{
|
|
*ppList = pList;
|
|
pList = NULL;
|
|
}
|
|
|
|
// cleanup
|
|
if( NULL != pList )
|
|
{
|
|
WSDFreeLinkedMemory( pList );
|
|
pList = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
typedef enum _UserTokenMode
|
|
{
|
|
CertificateBased,
|
|
HttpBased
|
|
} UserTokenMode;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// GetUserToken - Given a WSD_EVENT, retrieve the user token present in the
|
|
// event. The function retrieves a certificate-based user token if
|
|
// tokenMode is CertificateBased. Otherwise, the function retrieves
|
|
// an Negotiate HTTP Authenticated user token.
|
|
//
|
|
// Returns one of the following:
|
|
// - S_OK along with the user token if the retrieval is
|
|
// successful.
|
|
// - S_FALSE if no user token is present in the event.
|
|
// - E_INVALIDARG if wsdEvent is NULL.
|
|
// - E_POINTER if phUserToken is NULL.
|
|
// - E_FAIL or other appropriate code if a failure occured while
|
|
// attempting to retrieve the user token.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT GetUserToken(
|
|
WSD_EVENT* wsdEvent,
|
|
UserTokenMode tokenMode,
|
|
HANDLE* phUserToken)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
IWSDMessageParameters* pMessageParams = NULL; // shallow copy
|
|
IWSDMessageParameters* pLowerParams = NULL;
|
|
IWSDHttpMessageParameters* pHttpParams = NULL;
|
|
IWSDSSLClientCertificate* pClientCert = NULL;
|
|
IWSDHttpAuthParameters* pHttpAuthParams = NULL;
|
|
HANDLE hUserToken = NULL;
|
|
|
|
if ( NULL == wsdEvent )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else if ( NULL == phUserToken )
|
|
{
|
|
hr = E_POINTER;
|
|
}
|
|
else
|
|
{
|
|
*phUserToken = NULL;
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
pMessageParams = wsdEvent->MessageParameters;
|
|
|
|
if ( NULL == pMessageParams )
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
hr = pMessageParams->GetLowerParameters( &pLowerParams );
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// The user token is going to be available in the HTTP message
|
|
// parameters. Since the traffic arrived via HTTP or HTTPS, we
|
|
// expect such parameters to be available.
|
|
hr = pLowerParams->QueryInterface(
|
|
__uuidof(IWSDHttpMessageParameters),
|
|
(void**)&pHttpParams);
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
if ( CertificateBased == tokenMode )
|
|
{
|
|
// Certificate-based user token will be available under
|
|
// IWSDSSLClientCertificate. If this interface is not
|
|
// available, then no such user token is present.
|
|
|
|
hr = pHttpParams->QueryInterface(
|
|
__uuidof(IWSDSSLClientCertificate),
|
|
(void**)&pClientCert);
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
hr = pClientCert->GetMappedAccessToken( &hUserToken );
|
|
}
|
|
else if ( E_NOINTERFACE == hr )
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
else // if ( HttpBased == tokenMode )
|
|
{
|
|
// HTTP-based user token will be available under
|
|
// IWSDHttpAuthParameters. If this interface is not
|
|
// available, then no such user token is present.
|
|
|
|
hr = pHttpParams->QueryInterface(
|
|
__uuidof(IWSDHttpAuthParameters),
|
|
(void**)&pHttpAuthParams);
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
hr = pHttpAuthParams->GetClientAccessToken( &hUserToken );
|
|
}
|
|
else if ( E_NOINTERFACE == hr )
|
|
{
|
|
hr = S_FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// Outside pointer now owns the token.
|
|
*phUserToken = hUserToken;
|
|
hUserToken = NULL;
|
|
}
|
|
|
|
if ( NULL != hUserToken )
|
|
{
|
|
CloseHandle( hUserToken );
|
|
hUserToken = NULL;
|
|
}
|
|
|
|
if ( NULL != pHttpAuthParams )
|
|
{
|
|
pHttpAuthParams->Release();
|
|
pHttpAuthParams = NULL;
|
|
}
|
|
|
|
if ( NULL != pClientCert )
|
|
{
|
|
pClientCert->Release();
|
|
pClientCert = NULL;
|
|
}
|
|
|
|
if ( NULL != pHttpParams )
|
|
{
|
|
pHttpParams->Release();
|
|
pHttpParams = NULL;
|
|
}
|
|
|
|
if ( NULL != pLowerParams )
|
|
{
|
|
pLowerParams->Release();
|
|
pLowerParams = NULL;
|
|
}
|
|
|
|
pMessageParams = NULL; // shallow copy
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService Class
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
CFileServiceSecureService::CFileServiceSecureService()
|
|
: m_cRef(1), m_bIsAcceptCertAuth(FALSE), m_bIsAcceptHttpAuth(FALSE)
|
|
{
|
|
}
|
|
|
|
HRESULT CFileServiceSecureService::Init(
|
|
LPCWSTR pszFileDirectory,
|
|
BOOL bIsAcceptCertAuth,
|
|
BOOL bIsAcceptHttpAuth)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
// validate pszFileDirectory
|
|
if( NULL == pszFileDirectory )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if( 0 == wcscmp( pszFileDirectory, L"" ) )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
hr = ::StringCbCopyW( m_szFileDirectory, sizeof(m_szFileDirectory),
|
|
pszFileDirectory );
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
m_bIsAcceptCertAuth = bIsAcceptCertAuth;
|
|
m_bIsAcceptHttpAuth = bIsAcceptHttpAuth;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService::QueryInterface
|
|
// Implementation of IUnknown-related methods.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT STDMETHODCALLTYPE CFileServiceSecureService::QueryInterface(
|
|
REFIID riid,
|
|
void** ppvObject)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if ( NULL == ppvObject )
|
|
{
|
|
hr = E_POINTER;
|
|
}
|
|
else
|
|
{
|
|
*ppvObject = NULL;
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
if ( __uuidof(IFileServiceSecureService) == riid )
|
|
{
|
|
*ppvObject = (IFileServiceSecureService *)this;
|
|
}
|
|
else if ( __uuidof(IUnknown) == riid )
|
|
{
|
|
*ppvObject = (IUnknown *)this;
|
|
}
|
|
else
|
|
{
|
|
hr = E_NOINTERFACE;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
((LPUNKNOWN) *ppvObject)->AddRef();
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService::AddRef
|
|
// Implementation of IUnknown-related methods.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
ULONG STDMETHODCALLTYPE CFileServiceSecureService::AddRef()
|
|
{
|
|
ULONG ulNewRefCount = (ULONG)InterlockedIncrement( (LONG *)&m_cRef );
|
|
return ulNewRefCount;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService::Release
|
|
// Implementation of IUnknown-related methods.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
ULONG STDMETHODCALLTYPE CFileServiceSecureService::Release()
|
|
{
|
|
ULONG ulNewRefCount = (ULONG)InterlockedDecrement( (LONG *)&m_cRef );
|
|
|
|
if( 0 == ulNewRefCount )
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
return ulNewRefCount;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService::GetFile
|
|
// Service method which returns the contents of the file specified
|
|
// from the service's files directory as an attachment back to the
|
|
// client.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT STDMETHODCALLTYPE CFileServiceSecureService::GetFile(
|
|
WSD_EVENT* wsdEvent,
|
|
GET_FILE_REQUEST* pParameters,
|
|
GET_FILE_RESPONSE** ppResponse)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HRESULT localHr = S_OK;
|
|
BOOL bIsImpersonated = TRUE;
|
|
WCHAR szRoot[MAX_PATH];
|
|
WCHAR szFile[MAX_PATH];
|
|
HANDLE hUserToken = NULL;
|
|
HANDLE hFile = NULL;
|
|
IWSDOutboundAttachment* pAttachment = NULL;
|
|
CSendAttachmentThread* pSendAttachmentThread = NULL;
|
|
GET_FILE_RESPONSE* pGetFileResponse = NULL;
|
|
|
|
if( NULL == pParameters->filePath || NULL == wsdEvent )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else if( NULL == ppResponse )
|
|
{
|
|
hr = E_POINTER;
|
|
}
|
|
else
|
|
{
|
|
*ppResponse = NULL;
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Client is requesting file '%s'\r\n", pParameters->filePath);
|
|
_cwprintf(L" Assembling path... ");
|
|
|
|
// for security, strip path information from the filename that was
|
|
// sent from the client
|
|
hr = StripCbPath( szRoot, sizeof(szRoot), pParameters->filePath );
|
|
|
|
if ( S_OK != hr )
|
|
{
|
|
print_result( hr );
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// concatenate server file directory and root file
|
|
hr = ::StringCbCopyW( szFile, sizeof(szFile), m_szFileDirectory );
|
|
|
|
if ( S_OK != hr )
|
|
{
|
|
print_result( hr );
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
hr = ::StringCbCatW( szFile, sizeof(szFile), szRoot );
|
|
print_result( hr );
|
|
}
|
|
|
|
if ( S_OK == hr && ( m_bIsAcceptCertAuth || m_bIsAcceptHttpAuth ) )
|
|
{
|
|
// Certificate or HTTP authentication is enabled for this service.
|
|
// Attempt to retrieve the requested credentials beginning with the
|
|
// certificate authentication.
|
|
|
|
if ( m_bIsAcceptCertAuth )
|
|
{
|
|
_cwprintf(L" Retrieving certificate-based user token... ");
|
|
|
|
localHr = GetUserToken( wsdEvent, CertificateBased, &hUserToken );
|
|
print_result( localHr );
|
|
}
|
|
|
|
if ( NULL == hUserToken && m_bIsAcceptHttpAuth )
|
|
{
|
|
_cwprintf(L" Retrieving HTTP-based user token... ");
|
|
|
|
localHr = GetUserToken( wsdEvent, HttpBased, &hUserToken );
|
|
print_result( localHr );
|
|
}
|
|
|
|
if ( NULL == hUserToken )
|
|
{
|
|
// No user tokens available. Access denied.
|
|
_cwprintf(L" Failed to retrieve a usable user token. "
|
|
L"Denying access to this request.\r\n");
|
|
|
|
hr = E_ACCESSDENIED;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr && NULL != hUserToken )
|
|
{
|
|
// Attempt to impersonate the user retrieved from the event. Any
|
|
// file operations that happen below will be executed through the
|
|
// impersonated user.
|
|
|
|
_cwprintf(L" Attempting to impersonate this user... ");
|
|
bIsImpersonated = ImpersonateLoggedOnUser( hUserToken );
|
|
|
|
if ( !bIsImpersonated )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
// Check to see if file exists
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L" Checking if file exists... ");
|
|
hFile = ::CreateFileW( szFile, FILE_READ_DATA, 0, NULL,
|
|
OPEN_EXISTING, 0, NULL );
|
|
|
|
if( INVALID_HANDLE_VALUE == hFile )
|
|
{
|
|
// File doesn't exist
|
|
hr = E_FAIL;
|
|
hFile = NULL;
|
|
}
|
|
print_result( hr );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// File exists, discard handle since we don't need it anymore
|
|
CloseHandle( hFile );
|
|
hFile = NULL;
|
|
}
|
|
|
|
//
|
|
// Generate response structure to pass back as an outparam. The
|
|
// attachment will be linked inside, and WSDAPI will read from
|
|
// this attachment as data is written to it inside the
|
|
// CSendAttachmentThread.
|
|
//
|
|
if( S_OK == hr )
|
|
{
|
|
pGetFileResponse = (GET_FILE_RESPONSE*)
|
|
WSDAllocateLinkedMemory(NULL, sizeof(GET_FILE_RESPONSE));
|
|
|
|
if( NULL == pGetFileResponse )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
// Create an outbound attachment object for the file
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L" Creating outbound attachment object... ");
|
|
hr = WSDCreateOutboundAttachment( &pAttachment );
|
|
print_result( hr );
|
|
}
|
|
|
|
// Create worker thread to send the attachment
|
|
if( S_OK == hr )
|
|
{
|
|
pSendAttachmentThread = new CSendAttachmentThread();
|
|
|
|
if( NULL == pSendAttachmentThread )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
hr = pSendAttachmentThread->Init( szFile, pAttachment );
|
|
}
|
|
|
|
// Start the worker thread. The worker thread will run under the
|
|
// impersonated user context.
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L" Starting thread to send %s as attachment... ",
|
|
szFile);
|
|
hr = pSendAttachmentThread->Start();
|
|
|
|
//
|
|
// Once Start has been called, the thread may delete itself of its
|
|
// own volition. Set our reference to NULL so we don't access
|
|
// it after it has deleted itself.
|
|
//
|
|
pSendAttachmentThread = NULL;
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// Hand our reference off to the response
|
|
pGetFileResponse->Attachment = pAttachment;
|
|
pAttachment = NULL;
|
|
|
|
*ppResponse = pGetFileResponse;
|
|
pGetFileResponse = NULL;
|
|
}
|
|
|
|
// cleanup
|
|
if( NULL != pGetFileResponse )
|
|
{
|
|
WSDFreeLinkedMemory( pGetFileResponse );
|
|
pGetFileResponse = NULL;
|
|
}
|
|
|
|
if( NULL != pSendAttachmentThread )
|
|
{
|
|
delete pSendAttachmentThread;
|
|
pSendAttachmentThread = NULL;
|
|
}
|
|
|
|
if( NULL != pAttachment )
|
|
{
|
|
pAttachment->Release();
|
|
pAttachment = NULL;
|
|
}
|
|
|
|
if ( NULL != hFile )
|
|
{
|
|
CloseHandle( hFile );
|
|
hFile = NULL;
|
|
}
|
|
|
|
// If we have previously impersonated the user, we need to revert back
|
|
// to the original user before proceeding.
|
|
|
|
if ( bIsImpersonated )
|
|
{
|
|
if ( !RevertToSelf() )
|
|
{
|
|
_cwprintf(L"Failed to revert to self: 0x%x\r\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
if ( NULL != hUserToken )
|
|
{
|
|
CloseHandle( hUserToken );
|
|
hUserToken = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileServiceSecureService::GetFileList (a.k.a. 'dir' functionality)
|
|
// Service method which returns the list of files that exists in the
|
|
// directory specified on the command lined.
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT STDMETHODCALLTYPE CFileServiceSecureService::GetFileList(
|
|
WSD_EVENT* wsdEvent,
|
|
GET_FILE_LIST_RESPONSE** ppResponse)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HRESULT localHr = S_OK;
|
|
BOOL bIsImpersonated = TRUE;
|
|
WCHAR szFilter[MAX_PATH];
|
|
GET_FILE_LIST_RESPONSE* pResponse = NULL;
|
|
PWCHAR_LIST** ppCursor = NULL;
|
|
WIN32_FIND_DATAW findFileData;
|
|
HANDLE hFind = NULL;
|
|
DWORD dwErr = 0;
|
|
HANDLE hUserToken = NULL;
|
|
|
|
if( NULL == ppResponse )
|
|
{
|
|
return E_POINTER;
|
|
}
|
|
|
|
*ppResponse = NULL;
|
|
|
|
_cwprintf(L"Client is requesting file list:\r\n");
|
|
|
|
_cwprintf(L" Building directory filter... ");
|
|
// Build wildcard directory filter
|
|
hr = ::StringCbCopyW( szFilter, sizeof(szFilter), m_szFileDirectory );
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
hr = ::StringCbCatW( szFilter, sizeof(szFilter), (LPCWSTR)L"*.*" );
|
|
}
|
|
|
|
print_result( hr );
|
|
|
|
// Allocate and zero response structure
|
|
if( S_OK == hr )
|
|
{
|
|
//
|
|
// pResponse will get returned as an outparameter, and our
|
|
// configuration states that the Deallocator for outparams is
|
|
// WSDFreeLinkedMemory.
|
|
//
|
|
pResponse = (GET_FILE_LIST_RESPONSE*)
|
|
WSDAllocateLinkedMemory(NULL, sizeof(GET_FILE_LIST_RESPONSE));
|
|
|
|
if (NULL == pResponse)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
::ZeroMemory(pResponse, sizeof(GET_FILE_LIST_RESPONSE));
|
|
ppCursor = &pResponse->FileList;
|
|
}
|
|
|
|
if ( S_OK == hr && ( m_bIsAcceptCertAuth || m_bIsAcceptHttpAuth ) )
|
|
{
|
|
// Certificate or HTTP authentication is enabled for this service.
|
|
// Attempt to retrieve the requested credentials beginning with the
|
|
// certificate authentication.
|
|
|
|
if ( m_bIsAcceptCertAuth )
|
|
{
|
|
_cwprintf(L" Retrieving certificate-based user token... ");
|
|
|
|
localHr = GetUserToken( wsdEvent, CertificateBased, &hUserToken );
|
|
print_result( localHr );
|
|
}
|
|
|
|
if ( NULL == hUserToken && m_bIsAcceptHttpAuth )
|
|
{
|
|
_cwprintf(L" Retrieving HTTP-based user token... ");
|
|
|
|
localHr = GetUserToken( wsdEvent, HttpBased, &hUserToken );
|
|
print_result( localHr );
|
|
}
|
|
|
|
if ( NULL == hUserToken )
|
|
{
|
|
// No user tokens available. Access denied.
|
|
_cwprintf(L" Failed to retrieve a usable user token. "
|
|
L"Denying access to this request.\r\n");
|
|
|
|
hr = E_ACCESSDENIED;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr && NULL != hUserToken )
|
|
{
|
|
// Attempt to impersonate the user retrieved from the event. Any
|
|
// file operations that happen below will be executed through the
|
|
// impersonated user.
|
|
|
|
_cwprintf(L" Attempting to impersonate this user... ");
|
|
bIsImpersonated = ImpersonateLoggedOnUser( hUserToken );
|
|
|
|
if ( !bIsImpersonated )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
// Seek to first file in directory list
|
|
if( S_OK == hr )
|
|
{
|
|
hFind = ::FindFirstFileW( szFilter, &findFileData );
|
|
|
|
if( INVALID_HANDLE_VALUE == hFind )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
hFind = NULL;
|
|
}
|
|
}
|
|
|
|
// Iterate through the files in the directory
|
|
while( S_OK == hr )
|
|
{
|
|
bool bAddFile = true;
|
|
|
|
// Skip '.' and '..'
|
|
if( 0 == wcscmp( findFileData.cFileName, L"." ) ||
|
|
0 == wcscmp( findFileData.cFileName, L".." ) )
|
|
{
|
|
bAddFile = false;
|
|
}
|
|
|
|
if( bAddFile )
|
|
{
|
|
_cwprintf(L" Adding file '%s' to list... ",
|
|
findFileData.cFileName );
|
|
// Build list entry out of current file
|
|
hr = CreateStringList( findFileData.cFileName, ppCursor );
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// Add new list entry to end of list
|
|
WSDAttachLinkedMemory( pResponse, *ppCursor );
|
|
|
|
// Advance cursor
|
|
ppCursor = &((*ppCursor)->Next);
|
|
}
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
// If we hit the end of the directory, bail out
|
|
if( S_OK == hr && 0 == ::FindNextFileW( hFind, &findFileData ) )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
*ppResponse = pResponse;
|
|
pResponse = NULL;
|
|
|
|
_cwprintf(L" List has been prepared.\r\n");
|
|
}
|
|
|
|
_cwprintf(L" Service method exit code: ");
|
|
print_result( hr );
|
|
|
|
// cleanup
|
|
if( NULL != hFind )
|
|
{
|
|
::FindClose( hFind );
|
|
hFind = NULL;
|
|
}
|
|
|
|
if( NULL != pResponse )
|
|
{
|
|
WSDFreeLinkedMemory( pResponse );
|
|
pResponse = NULL;
|
|
}
|
|
|
|
// If we have previously impersonated the user, we need to revert back
|
|
// to the original user before proceeding.
|
|
|
|
if ( bIsImpersonated )
|
|
{
|
|
if ( !RevertToSelf() )
|
|
{
|
|
_cwprintf(L"Failed to revert to self: 0x%x\r\n", GetLastError());
|
|
}
|
|
}
|
|
|
|
if ( NULL != hUserToken )
|
|
{
|
|
CloseHandle( hUserToken );
|
|
hUserToken = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// CFileChangeNotificationThread Class
|
|
// Performs worker thread that monitors the file system for changes
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
CFileChangeNotificationThread::CFileChangeNotificationThread()
|
|
: m_pHost(NULL)
|
|
, m_hThread(NULL)
|
|
, m_hWait(NULL)
|
|
, m_eventSource(NULL)
|
|
{
|
|
}
|
|
|
|
CFileChangeNotificationThread::~CFileChangeNotificationThread()
|
|
{
|
|
// m_hWait and m_hThread are cleaned up in Stop
|
|
if( NULL != m_eventSource )
|
|
{
|
|
m_eventSource->Release();
|
|
m_eventSource = NULL;
|
|
}
|
|
|
|
if( NULL != m_pHost )
|
|
{
|
|
m_pHost->Release();
|
|
m_pHost = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT CFileChangeNotificationThread::Init(
|
|
LPCWSTR pszDirectory,
|
|
LPCWSTR pszServiceId,
|
|
IWSDDeviceHost* pHost)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if( NULL == pszDirectory )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if( NULL == pszServiceId )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
if( NULL == pHost )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_pHost = pHost;
|
|
m_pHost->AddRef();
|
|
|
|
// Copy directory and serviceId parameters
|
|
hr = ::StringCbCopyW( m_szDirectory, sizeof(m_szDirectory),
|
|
pszDirectory);
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
hr = ::StringCbCopyW( m_szServiceId, sizeof(m_szServiceId),
|
|
pszServiceId );
|
|
}
|
|
|
|
// Allocate and initialize event source object so we can send events to
|
|
// clients when we are notified of file system changes
|
|
if( S_OK == hr )
|
|
{
|
|
hr = CreateCFileServiceSecureEventSource(
|
|
m_pHost,
|
|
m_szServiceId,
|
|
&m_eventSource );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Start - Starts the file change notification thread
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CFileChangeNotificationThread::Start()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwErr = 0;
|
|
|
|
//
|
|
// Set up handle to shut down the notification thread
|
|
// Parameters to CreateEvent:
|
|
// lpEventAttributes (NULL) Ignored, must be NULL
|
|
// bManualReset (TRUE) Event must be manually reset
|
|
// bInitialState (FALSE) Event starts in unsignalled state
|
|
// lpName (NULL) Event is unnamed
|
|
//
|
|
m_hWait = ::CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
if( NULL == m_hWait )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
|
|
//
|
|
// Create thread to listen for file change notifications
|
|
//
|
|
// Use CreateThread here because this thread should be persistent
|
|
// for the lifetime of the executable.
|
|
//
|
|
if( S_OK == hr )
|
|
{
|
|
m_hThread = ::CreateThread( 0, 0, StaticThreadProc, this, 0, 0 );
|
|
|
|
// Failure code for CreateThread is NULL
|
|
if( NULL == m_hThread )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Stop - Stops the file change notification thread
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CFileChangeNotificationThread::Stop()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
DWORD dwErr = 0;
|
|
|
|
// Signal thread to exit
|
|
::SetEvent( m_hWait );
|
|
|
|
// Wait for thread to exit
|
|
if( S_OK == hr )
|
|
{
|
|
if( WAIT_FAILED == ::WaitForSingleObject( m_hThread, INFINITE ) )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
}
|
|
|
|
// Clean up handles
|
|
if( NULL != m_hThread )
|
|
{
|
|
::CloseHandle( m_hThread );
|
|
m_hThread = NULL;
|
|
}
|
|
|
|
if( NULL != m_hWait )
|
|
{
|
|
::CloseHandle( m_hWait );
|
|
m_hWait = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// ThreadProc - Performs the file change notification work
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CFileChangeNotificationThread::ThreadProc()
|
|
{
|
|
DWORD dwInfoSize = 0;
|
|
FILE_NOTIFY_INFORMATION *pInfo = NULL;
|
|
HRESULT hr = S_OK;
|
|
WCHAR szFileName[MAX_PATH];
|
|
HANDLE hDir = NULL;
|
|
OVERLAPPED overlapped = { 0 };
|
|
HANDLE waitHandles[2];
|
|
DWORD dwErr = 0;
|
|
|
|
const DWORD dwShareMode =
|
|
FILE_SHARE_READ |
|
|
FILE_SHARE_WRITE |
|
|
FILE_SHARE_DELETE;
|
|
|
|
const DWORD dwFlags =
|
|
FILE_FLAG_BACKUP_SEMANTICS |
|
|
FILE_FLAG_OVERLAPPED;
|
|
|
|
const DWORD dwNotifyFilter =
|
|
FILE_NOTIFY_CHANGE_FILE_NAME |
|
|
FILE_NOTIFY_CHANGE_LAST_WRITE |
|
|
FILE_NOTIFY_CHANGE_SIZE |
|
|
FILE_NOTIFY_CHANGE_ATTRIBUTES;
|
|
|
|
// allocate a FILE_NOTIFY_INFORMATION object to receive notifications
|
|
dwInfoSize = sizeof(FILE_NOTIFY_INFORMATION) + MAX_PATH;
|
|
|
|
pInfo = (FILE_NOTIFY_INFORMATION*)malloc(dwInfoSize);
|
|
|
|
if( NULL == pInfo )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
|
|
// Open directory handle
|
|
if( S_OK == hr )
|
|
{
|
|
hDir = ::CreateFileW( m_szDirectory, GENERIC_READ,
|
|
dwShareMode, NULL, OPEN_EXISTING, dwFlags, NULL );
|
|
|
|
if( INVALID_HANDLE_VALUE == hDir )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
}
|
|
|
|
// Set up the OVERLAPPED structure for receiving async notifications
|
|
if( S_OK == hr )
|
|
{
|
|
::ZeroMemory( &overlapped, sizeof(OVERLAPPED) );
|
|
|
|
overlapped.hEvent = ::CreateEvent( NULL, TRUE, FALSE, NULL );
|
|
|
|
if( NULL == overlapped.hEvent )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
}
|
|
|
|
// Loop for change notifications
|
|
while( S_OK == hr )
|
|
{
|
|
DWORD dwBytesReturned = 0;
|
|
ZeroMemory( pInfo, dwInfoSize );
|
|
|
|
//
|
|
// Get information that describes the most recent file change
|
|
// This call will return immediately and will set
|
|
// overlapped.hEvent when it has completed
|
|
//
|
|
if( 0 == ::ReadDirectoryChangesW(hDir, pInfo, dwInfoSize, FALSE,
|
|
dwNotifyFilter, &dwBytesReturned, &overlapped, NULL))
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
|
|
//
|
|
// Set up a wait on the overlapped handle and on the handle used
|
|
// to shut down this thread
|
|
//
|
|
if( S_OK == hr )
|
|
{
|
|
DWORD waitResult = 0;
|
|
|
|
//
|
|
// WaitForMultipleObjects will wait on both event handles in
|
|
// waitHandles. If either one of these events is signalled,
|
|
// WaitForMultipleObjects will return and its return code
|
|
// can be used to tell which event was set.
|
|
//
|
|
waitHandles[0] = m_hWait;
|
|
waitHandles[1] = overlapped.hEvent;
|
|
|
|
waitResult = ::WaitForMultipleObjects( 2, waitHandles, FALSE,
|
|
INFINITE );
|
|
|
|
if( WAIT_FAILED == waitResult )
|
|
{
|
|
// Hit an error--bail out from this thread
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
else if( WAIT_OBJECT_0 == waitResult )
|
|
{
|
|
// Stop was called, and this thread should be shut down
|
|
hr = S_FALSE;
|
|
_cwprintf(L"Change notification thread shutting down.\r\n");
|
|
}
|
|
// Otherwise, a change notification occured, and we should
|
|
// continue through the loop
|
|
}
|
|
|
|
// Retrieve result from overlapped structure
|
|
if( S_OK == hr )
|
|
{
|
|
DWORD dwBytesTransferred = 0;
|
|
|
|
if( 0 == ::GetOverlappedResult( hDir, &overlapped,
|
|
&dwBytesTransferred, TRUE ) )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// Copy the file name into a message structure
|
|
hr = ::StringCchCopyNW( szFileName, MAX_PATH,
|
|
pInfo->FileName, pInfo->FileNameLength );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// Notify all clients subscribed for FileChange events
|
|
NotifyClient( szFileName, pInfo->Action );
|
|
}
|
|
}
|
|
|
|
// cleanup
|
|
if( NULL != pInfo )
|
|
{
|
|
free( pInfo );
|
|
pInfo = NULL;
|
|
}
|
|
|
|
if( NULL != overlapped.hEvent )
|
|
{
|
|
::CloseHandle( overlapped.hEvent );
|
|
overlapped.hEvent = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// NotifyClient - Notifies the client via the FileChangeEvent
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
void CFileChangeNotificationThread::NotifyClient(
|
|
LPCWSTR pszFileName,
|
|
DWORD dwAction)
|
|
{
|
|
LPCWSTR pszEventType = NULL;
|
|
FILE_CHANGE_EVENT changeEvent;
|
|
HRESULT hr = S_OK;
|
|
|
|
//
|
|
// First, select which file change type we want to send to the clients.
|
|
// This will be packaged as a parameter for the message.
|
|
//
|
|
switch (dwAction)
|
|
{
|
|
case FILE_ACTION_ADDED:
|
|
pszEventType = FileChangeEventType_Added;
|
|
break;
|
|
case FILE_ACTION_REMOVED:
|
|
pszEventType = FileChangeEventType_Removed;
|
|
break;
|
|
case FILE_ACTION_MODIFIED:
|
|
pszEventType = FileChangeEventType_Modified;
|
|
break;
|
|
case FILE_ACTION_RENAMED_OLD_NAME:
|
|
pszEventType = FileChangeEventType_RenamedOldName;
|
|
break;
|
|
case FILE_ACTION_RENAMED_NEW_NAME:
|
|
pszEventType = FileChangeEventType_RenamedNewName;
|
|
break;
|
|
}
|
|
|
|
_cwprintf(L"Noticed file change: %s %s\r\n",
|
|
pszEventType, pszFileName);
|
|
|
|
//
|
|
// Second, build the parameter structure and send the message to all
|
|
// subscribed clients
|
|
//
|
|
_cwprintf(L"Sending event to clients... ");
|
|
|
|
changeEvent.EventType = pszEventType;
|
|
changeEvent.FileName = pszFileName;
|
|
|
|
hr = m_eventSource->FileChangeEvent(&changeEvent);
|
|
|
|
print_result( hr );
|
|
|
|
// No return code--signalling events is best-effort
|
|
}
|
|
|
|
DWORD WINAPI CFileChangeNotificationThread::StaticThreadProc(LPVOID pParams)
|
|
{
|
|
if( NULL != pParams )
|
|
{
|
|
// Ignore result
|
|
(void)((CFileChangeNotificationThread*)pParams)->ThreadProc();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
CSendAttachmentThread::CSendAttachmentThread()
|
|
: m_pAttachment(NULL)
|
|
{
|
|
}
|
|
|
|
CSendAttachmentThread::~CSendAttachmentThread()
|
|
{
|
|
if( NULL != m_pAttachment )
|
|
{
|
|
m_pAttachment->Release();
|
|
m_pAttachment = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT CSendAttachmentThread::Init(
|
|
LPCWSTR pszFilePath,
|
|
IWSDOutboundAttachment* pAttachment)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
if( NULL == pAttachment )
|
|
{
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
m_pAttachment = pAttachment;
|
|
m_pAttachment->AddRef();
|
|
|
|
hr = ::StringCbCopyW( m_szFilePath, sizeof(m_szFilePath), pszFilePath );
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Start - Starts the send-attachment thread via thread-pool thread
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CSendAttachmentThread::Start()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
BOOL bIsSuccess = TRUE;
|
|
|
|
//
|
|
// Immediately start a new thread to send the attachment
|
|
//
|
|
// Use QueueUserWorkItem here because the attachment threads are created
|
|
// often and die quickly. Also, the threadpool cap will help keep from
|
|
// spawning too many threads.
|
|
//
|
|
bIsSuccess = QueueUserWorkItem(
|
|
StaticThreadProc,
|
|
(LPVOID*)this,
|
|
WT_EXECUTELONGFUNCTION | WT_TRANSFER_IMPERSONATION );
|
|
|
|
if ( !bIsSuccess )
|
|
{
|
|
hr = HRESULT_FROM_WIN32( GetLastError() );
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// ThreadProc - Performs the send attachment work
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT CSendAttachmentThread::ThreadProc()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
HANDLE hFile = NULL;
|
|
BYTE buffer[8192];
|
|
DWORD dwBytesRead = 0;
|
|
DWORD dwBytesLeft = 0;
|
|
DWORD dwErr = 0;
|
|
|
|
_cwprintf(L"Sending file '%s' as attachment...\r\n", m_szFilePath);
|
|
|
|
_cwprintf(L" Opening file...");
|
|
// Open the file to send
|
|
hFile = ::CreateFileW(m_szFilePath, FILE_READ_DATA, 0,
|
|
NULL, OPEN_EXISTING, 0, NULL);
|
|
|
|
if( INVALID_HANDLE_VALUE == hFile )
|
|
{
|
|
hFile = NULL;
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
|
|
print_result( hr );
|
|
|
|
// Loop through data in file until finished
|
|
while( S_OK == hr )
|
|
{
|
|
_cwprintf(L" Reading from file... ");
|
|
// Read a block from the file
|
|
if( 0 == ::ReadFile(hFile, buffer, sizeof(buffer), &dwBytesRead, 0) )
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
|
|
dwBytesLeft = dwBytesRead;
|
|
|
|
if( 0 == dwBytesLeft )
|
|
{
|
|
// No data left--time to bail out
|
|
_cwprintf(L" done.\r\n");
|
|
break;
|
|
}
|
|
|
|
print_result( hr );
|
|
|
|
while( S_OK == hr && 0 < dwBytesLeft )
|
|
{
|
|
DWORD dwBytesWritten = 0;
|
|
|
|
_cwprintf(L" Writing to attachment... ");
|
|
// Write multiple times until this block has been consumed
|
|
hr = m_pAttachment->Write(
|
|
buffer + (dwBytesRead - dwBytesLeft),
|
|
dwBytesLeft,
|
|
&dwBytesWritten);
|
|
|
|
print_result( hr );
|
|
|
|
dwBytesLeft -= dwBytesWritten;
|
|
}
|
|
}
|
|
|
|
// cleanup
|
|
if( NULL != hFile )
|
|
{
|
|
::CloseHandle( hFile );
|
|
hFile = NULL;
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L" Finished reading file. Closing attachment... ");
|
|
hr = m_pAttachment->Close();
|
|
print_result( hr );
|
|
}
|
|
else
|
|
{
|
|
_cwprintf(L" Error reading file. Aborting attachment... ");
|
|
m_pAttachment->Abort();
|
|
print_result( hr );
|
|
}
|
|
|
|
_cwprintf(L" Attachment thread exiting.\r\n");
|
|
|
|
delete this;
|
|
|
|
return hr;
|
|
}
|
|
|
|
DWORD WINAPI CSendAttachmentThread::StaticThreadProc(LPVOID pParams)
|
|
{
|
|
if( NULL != pParams )
|
|
{
|
|
// Ignore result
|
|
(void)((CSendAttachmentThread*)pParams)->ThreadProc();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Argument parsing function
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
HRESULT ParseArgs(
|
|
int argc,
|
|
_In_reads_(argc) LPWSTR* argv,
|
|
BOOL* pbIsCertAuth,
|
|
BOOL* pbIsHttpAuth,
|
|
BOOL* pbIsCertOrHttpAuth,
|
|
_In_reads_(cchFileDirectoryBufferSize) LPWSTR pszFileDirectory,
|
|
size_t cchFileDirectoryBufferSize,
|
|
_In_reads_(cchDeviceAddressBufferSize) LPWSTR pszDeviceAddress,
|
|
size_t cchDeviceAddressBufferSize)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
BOOL bIsHttpAuth = FALSE;
|
|
BOOL bIsCertAuth = FALSE;
|
|
BOOL bIsCertOrHttpAuth = FALSE;
|
|
LPWSTR pszTempFileDirectory = NULL; // shallow copy
|
|
LPWSTR pszTempDeviceAddress = NULL; // shallow copy
|
|
|
|
size_t cchFileDirectoryLength = 0;
|
|
UUID uuid = {0};
|
|
|
|
if ( argc < 2 || argc > 6 || NULL == argv )
|
|
{
|
|
// argv[0] = executable name
|
|
//
|
|
// one additional argument required at the minimum for the
|
|
// files directory
|
|
//
|
|
// maximum is (1 exe name) + (all 5 args) = 6
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else if ( NULL == pbIsCertAuth || NULL == pbIsHttpAuth
|
|
|| NULL == pbIsCertOrHttpAuth )
|
|
{
|
|
hr = E_POINTER;
|
|
}
|
|
else if ( NULL == pszFileDirectory || cchFileDirectoryBufferSize < 1
|
|
|| NULL == pszDeviceAddress || cchDeviceAddressBufferSize < 1 )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
else
|
|
{
|
|
*pbIsCertAuth = FALSE;
|
|
*pbIsHttpAuth = FALSE;
|
|
*pbIsCertOrHttpAuth = FALSE;
|
|
|
|
::ZeroMemory( pszFileDirectory,
|
|
cchFileDirectoryBufferSize * sizeof(WCHAR) );
|
|
|
|
::ZeroMemory( pszDeviceAddress,
|
|
cchDeviceAddressBufferSize * sizeof(WCHAR) );
|
|
}
|
|
|
|
for ( int i = 1; i < argc && S_OK == hr; i++ ) // skip argv[0] for exe name
|
|
{
|
|
if ( 0 == _wcsicmp( argv[i], L"-CertAuth" ) )
|
|
{
|
|
bIsCertAuth= TRUE;
|
|
}
|
|
else if ( 0 == _wcsicmp( argv[i], L"-HttpAuth" ) )
|
|
{
|
|
bIsHttpAuth = TRUE;
|
|
}
|
|
else if ( 0 == _wcsicmp( argv[i], L"-CertOrHttpAuth" ) )
|
|
{
|
|
bIsCertOrHttpAuth = TRUE;
|
|
}
|
|
else if ( NULL == pszTempFileDirectory )
|
|
{
|
|
pszTempFileDirectory = argv[i];
|
|
}
|
|
else if ( NULL == pszTempDeviceAddress )
|
|
{
|
|
pszTempDeviceAddress = argv[i];
|
|
}
|
|
else
|
|
{
|
|
// Unknown argument
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr && NULL == pszTempFileDirectory )
|
|
{
|
|
// File Directory is required.
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// Check to see that the file directory is not an empty string
|
|
hr = StringCchLengthW(
|
|
pszTempFileDirectory,
|
|
cchFileDirectoryBufferSize,
|
|
&cchFileDirectoryLength );
|
|
|
|
if ( S_OK == hr && cchFileDirectoryLength < 1 )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
// Determine if there is enough buffer space to hold the
|
|
// directory string, including the ending backslash if one is not
|
|
// already present.
|
|
if ( '\\' != pszTempFileDirectory[cchFileDirectoryLength - 1] )
|
|
{
|
|
// Backslash required at the end.
|
|
|
|
if ( cchFileDirectoryBufferSize < (cchFileDirectoryLength + 2) )
|
|
{
|
|
// + 1 for slash, + 1 for NULL char
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Backslash is already present.
|
|
if ( cchFileDirectoryBufferSize < (cchFileDirectoryLength + 1) )
|
|
{
|
|
// + 1 for NULL char
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the variables to the caller.
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
if ( NULL == pszTempDeviceAddress )
|
|
{
|
|
// Device address not specified.
|
|
// Generate a UUID and return that as the device address.
|
|
|
|
RPC_STATUS st = UuidCreate( &uuid );
|
|
|
|
if( RPC_S_OK != st )
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
else
|
|
{
|
|
hr = StringCbPrintfW(
|
|
pszDeviceAddress,
|
|
cchDeviceAddressBufferSize * sizeof(WCHAR),
|
|
L"urn:uuid:%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
uuid.Data1, uuid.Data2, uuid.Data3,
|
|
uuid.Data4[0], uuid.Data4[1], uuid.Data4[2],
|
|
uuid.Data4[3], uuid.Data4[4], uuid.Data4[5],
|
|
uuid.Data4[6], uuid.Data4[7]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Device address specified. Copy the string into the buffer.
|
|
hr = StringCbPrintfW(
|
|
pszDeviceAddress,
|
|
cchDeviceAddressBufferSize * sizeof(WCHAR),
|
|
L"%s", pszTempDeviceAddress );
|
|
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
if ( '\\' != pszTempFileDirectory[cchFileDirectoryLength - 1] )
|
|
{
|
|
// Backslash required at the end.
|
|
hr = StringCbPrintfW(
|
|
pszFileDirectory,
|
|
cchFileDirectoryBufferSize * sizeof(WCHAR),
|
|
L"%s\\", pszTempFileDirectory );
|
|
}
|
|
else
|
|
{
|
|
// Backslash is already present.
|
|
hr = StringCbPrintfW(
|
|
pszFileDirectory,
|
|
cchFileDirectoryBufferSize * sizeof(WCHAR),
|
|
L"%s", pszTempFileDirectory );
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
*pbIsCertAuth = bIsCertAuth;
|
|
*pbIsHttpAuth = bIsHttpAuth;
|
|
*pbIsCertOrHttpAuth = bIsCertOrHttpAuth;
|
|
}
|
|
|
|
pszTempFileDirectory = NULL; // shallow copy
|
|
pszTempDeviceAddress = NULL; // shallow copy
|
|
|
|
return hr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Usage
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
void Usage()
|
|
{
|
|
_cwprintf(L"FileServiceSecureService.exe [-CertAuth] [-HttpAuth] "
|
|
L"[-CertOrHttpAuth] <files-directory> [<device-address>]\r\n");
|
|
_cwprintf(L"\r\n");
|
|
_cwprintf(L"See the ReadMe file for a detailed usage description.\r\n");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Main Entry Point
|
|
// argv[0] = executable name
|
|
// argv[1+] = application arguments
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
int _cdecl wmain(
|
|
int argc,
|
|
_In_reads_(argc) LPWSTR* argv)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
WCHAR szFileDirectory[MAX_PATH] = {0};
|
|
WCHAR szDeviceAddress[MAX_PATH] = {0};
|
|
BOOL bIsCertAuth = FALSE;
|
|
BOOL bIsHttpAuth = FALSE;
|
|
BOOL bIsCertOrHttpAuth = FALSE;
|
|
HANDLE hDir = NULL;
|
|
CFileChangeNotificationThread* pFileChangeThread = NULL;
|
|
CFileServiceSecureService* pFileService = NULL;
|
|
IFileServiceSecureService* pIFileService = NULL;
|
|
IWSDDeviceHost* pHost = NULL;
|
|
DWORD dwErr = 0;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Validate command-line parameters
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if( argc <= 1 )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
|
|
// check first argument for /? or -?
|
|
if( S_OK == hr )
|
|
{
|
|
if( 0 == wcscmp( argv[1], L"/?") || 0 == wcscmp( argv[1], L"-?" ) )
|
|
{
|
|
hr = E_INVALIDARG;
|
|
}
|
|
}
|
|
|
|
if ( S_OK == hr )
|
|
{
|
|
hr = ParseArgs( argc, argv,
|
|
&bIsCertAuth, &bIsHttpAuth, &bIsCertOrHttpAuth,
|
|
szFileDirectory, MAX_PATH,
|
|
szDeviceAddress, MAX_PATH );
|
|
}
|
|
|
|
if( S_OK != hr )
|
|
{
|
|
Usage();
|
|
return -1;
|
|
}
|
|
|
|
// Check if files-directory actually exists
|
|
if( S_OK == hr )
|
|
{
|
|
hDir = ::CreateFileW(szFileDirectory, GENERIC_READ, FILE_SHARE_READ,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
|
|
if (hDir == INVALID_HANDLE_VALUE)
|
|
{
|
|
hDir = NULL;
|
|
hr = E_INVALIDARG;
|
|
|
|
_cwprintf(L"The files directory %s does not exist.\r\n",
|
|
szFileDirectory);
|
|
}
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// If the handle could be opened, we have no more use for it
|
|
if( 0 == ::CloseHandle( hDir ))
|
|
{
|
|
dwErr = ::GetLastError();
|
|
hr = HRESULT_FROM_WIN32( dwErr );
|
|
}
|
|
|
|
hDir = NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Build our FileService service
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Creating the file service... ");
|
|
pFileService = new CFileServiceSecureService();
|
|
|
|
if( NULL == pFileService )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
print_result( hr );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Initializing the file service... ");
|
|
|
|
hr = pFileService->Init(
|
|
szFileDirectory,
|
|
bIsCertAuth || bIsCertOrHttpAuth,
|
|
bIsHttpAuth || bIsCertOrHttpAuth );
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
// Retrieve an IFileService pointer to pass into CreateFileServiceHost
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Querying file service for service interface... ");
|
|
hr = pFileService->QueryInterface( __uuidof(IFileServiceSecureService),
|
|
(void**)&pIFileService );
|
|
print_result( hr );
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Build the host
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
// Create host
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Creating file service host with ID %s... ",
|
|
szDeviceAddress );
|
|
|
|
hr = CreateFileServiceSecureHost(
|
|
bIsCertAuth,
|
|
bIsHttpAuth,
|
|
bIsCertOrHttpAuth,
|
|
szDeviceAddress,
|
|
&thisDeviceMetadata,
|
|
pIFileService,
|
|
&pHost,
|
|
NULL );
|
|
|
|
print_result( hr );
|
|
}
|
|
|
|
// Start host
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Starting Host... ");
|
|
hr = pHost->Start( 0, NULL, NULL );
|
|
print_result( hr );
|
|
}
|
|
|
|
// Launch thread to listen for file change notifications and send events
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Creating file change notification thread... ");
|
|
pFileChangeThread = new CFileChangeNotificationThread();
|
|
|
|
if( NULL == pFileChangeThread )
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
}
|
|
print_result( hr );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
// Use first hosted metadata service ID as serviceID
|
|
_cwprintf(L"Initializing file change notification thread... ");
|
|
hr = pFileChangeThread->Init( szFileDirectory,
|
|
hostMetadata.Hosted->Element->ServiceId, pHost );
|
|
print_result( hr );
|
|
}
|
|
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Starting file change notification thread... ");
|
|
hr = pFileChangeThread->Start();
|
|
print_result( hr );
|
|
}
|
|
|
|
// Let service run
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L">>> Service running. Press any key to stop service. <<<\r\n");
|
|
// Ignore character returned from prompt
|
|
(void)::_getch();
|
|
}
|
|
|
|
// Stop event notification thread first
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Stopping file change notification thread... ");
|
|
hr = pFileChangeThread->Stop();
|
|
print_result( hr );
|
|
}
|
|
|
|
// Stop host
|
|
if( S_OK == hr )
|
|
{
|
|
_cwprintf(L"Stopping host... ");
|
|
hr = pHost->Stop();
|
|
print_result(hr);
|
|
}
|
|
|
|
_cwprintf(L"Cleaning up resources... ");
|
|
|
|
// cleanup
|
|
if( NULL != pIFileService )
|
|
{
|
|
pIFileService->Release();
|
|
pIFileService = NULL;
|
|
}
|
|
|
|
if( NULL != pFileService )
|
|
{
|
|
pFileService->Release();
|
|
pFileService = NULL;
|
|
}
|
|
|
|
if( NULL != pFileChangeThread )
|
|
{
|
|
// pFileChangeThread is not a refcounted object
|
|
delete pFileChangeThread;
|
|
pFileChangeThread = NULL;
|
|
}
|
|
|
|
if( NULL != pHost )
|
|
{
|
|
// Terminating the host is nonoptional, so it's done in the cleanup
|
|
// block
|
|
hr = pHost->Terminate();
|
|
pHost->Release();
|
|
pHost = NULL;
|
|
}
|
|
|
|
_cwprintf(L"finished.\r\n");
|
|
|
|
if( SUCCEEDED(hr) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return -1;
|
|
}
|