//////////////////////////////////////////////////////////////////////////////
// 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;
}
