/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    route.c

Abstract:

    This module implements the inbound routing rules.

Author:

    Wesley Witt (wesw) 1-Apr-1997

Revision History:

--*/

#include "faxsvc.h"
#include "tiff.h"
#pragma hdrstop



BOOL
BuildRouteInfo(
    LPWSTR              TiffFileName,
    PROUTE_FAILURE_INFO RouteFailure,
    DWORD               RouteFailureCount,
    LPWSTR              ReceiverName,
    LPWSTR              ReceiverNumber,
    LPWSTR              DeviceName,
    LPWSTR              Tsid,
    LPWSTR              Csid,
    LPWSTR              CallerId,
    LPWSTR              RoutingInfo,
    DWORDLONG           ElapsedTime
    );

extern DWORD FaxPrinters;

LPVOID InboundProfileInfo;
LPTSTR InboundProfileName;
LIST_ENTRY RoutingExtensions;
LIST_ENTRY RoutingMethods;
DWORD CountRoutingMethods;
CRITICAL_SECTION CsRouting;
BOOL RoutingIsInitialized = FALSE;

LONG WINAPI
FaxRouteAddFile(
    IN DWORD JobId,
    IN LPCWSTR FileName,
    IN GUID *Guid
    )
{
    PJOB_QUEUE JobQueueEntry;
    PFAX_ROUTE_FILE FaxRouteFile;
    WCHAR FullPathName[MAX_PATH];
    LPWSTR fnp;
    DWORD Count;
    WCHAR RouteGuid[MAX_GUID_STRING_LEN];

    StringFromGUID2( Guid, RouteGuid, MAX_GUID_STRING_LEN );

    if (!JobId || !Guid || !FileName) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return -1;
    }

    JobQueueEntry = FindJobQueueEntry( JobId );
    if (!JobQueueEntry) {
        SetLastError( ERROR_INVALID_DATA );
        return -1;
    }

    if ((!IsEqualGUID(Guid,&FaxSvcGuid)) && (!FindRoutingMethodByGuid(RouteGuid))) {
        SetLastError( ERROR_INVALID_DATA );
        return -1;
    }

    if (!GetFullPathName( FileName, sizeof(FullPathName)/sizeof(WCHAR), FullPathName, &fnp )) {
        return -1;
    }

    FaxRouteFile = (PFAX_ROUTE_FILE) MemAlloc( sizeof(FAX_ROUTE_FILE) );
    if (!FaxRouteFile) {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return -1;
    }

    FaxRouteFile->FileName = StringDup( FullPathName );

    CopyMemory( &FaxRouteFile->Guid, Guid, sizeof(GUID) );

    EnterCriticalSection( &JobQueueEntry->CsFileList );

    InsertTailList( &JobQueueEntry->FaxRouteFiles, &FaxRouteFile->ListEntry );

    LeaveCriticalSection( &JobQueueEntry->CsFileList );

    //
    // increment file count
    //
    EnterCriticalSection( &CsJob );
        EnterCriticalSection( &CsQueue );
            JobQueueEntry->CountFaxRouteFiles += 1;
            Count = JobQueueEntry->CountFaxRouteFiles;
        LeaveCriticalSection( &CsQueue );
    LeaveCriticalSection( &CsJob );



    return Count;
}


LONG WINAPI
FaxRouteDeleteFile(
    IN DWORD JobId,
    IN LPCWSTR FileName
    )
{
    PJOB_QUEUE JobQueueEntry;
    PFAX_ROUTE_FILE FaxRouteFile;
    PLIST_ENTRY Next;
    LONG Index = 1;

    if (!FileName) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return -1;
    }

    JobQueueEntry = FindJobQueueEntry( JobId );
    if (!JobQueueEntry) {
        SetLastError( ERROR_INVALID_DATA );
        return -1;
    }

    Next = JobQueueEntry->FaxRouteFiles.Flink;
    if (Next == &JobQueueEntry->FaxRouteFiles) {
        SetLastError( ERROR_NO_MORE_FILES );
        return -1;
    }

    EnterCriticalSection( &JobQueueEntry->CsFileList );

    while ((ULONG_PTR)Next != (ULONG_PTR)&JobQueueEntry->FaxRouteFiles) {
        FaxRouteFile = CONTAINING_RECORD( Next, FAX_ROUTE_FILE, ListEntry );
        Next = FaxRouteFile->ListEntry.Flink;
        if (_wcsicmp( FileName, FaxRouteFile->FileName ) == 0) {
            //
            // the initial file is read-only for all extensions
            //
            if (Index == 1) {
                SetLastError( ERROR_INVALID_DATA );
                LeaveCriticalSection( &JobQueueEntry->CsFileList );
                return -1;
            }

            //
            // remove from list, delete the file, cleanup memory
            //
            RemoveEntryList( &FaxRouteFile->ListEntry );
            DeleteFile( FaxRouteFile->FileName );
            MemFree ( FaxRouteFile->FileName ) ;
            MemFree ( FaxRouteFile );

            //
            // decrement file count
            //
            LeaveCriticalSection( &JobQueueEntry->CsFileList );
            EnterCriticalSection( &CsJob );
                EnterCriticalSection( &CsQueue );
                    JobQueueEntry->CountFaxRouteFiles -= 1;
                LeaveCriticalSection( &CsQueue );
            LeaveCriticalSection( &CsJob );

            return Index;
        }
        Index += 1;
    }

    LeaveCriticalSection( &JobQueueEntry->CsFileList );
    SetLastError( ERROR_FILE_NOT_FOUND );
    return -1;

}


BOOL WINAPI
FaxRouteGetFile(
    IN DWORD JobId,
    IN DWORD FileNumber,
    OUT LPWSTR FileNameBuffer,
    OUT LPDWORD RequiredSize
    )
{
    PJOB_QUEUE JobQueueEntry;
    PFAX_ROUTE_FILE FaxRouteFile;
    PLIST_ENTRY Next;
    ULONG Index = 1;

    if (RequiredSize == NULL) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    JobQueueEntry = FindJobQueueEntry( JobId );
    if (!JobQueueEntry) {
        SetLastError( ERROR_INVALID_DATA );
        return FALSE;
    }

    if (JobQueueEntry->CountFaxRouteFiles < Index) {
        SetLastError( ERROR_INVALID_DATA );
    }

    Next = JobQueueEntry->FaxRouteFiles.Flink;
    //
    // make sure list isn't empty
    //
    if (Next == &JobQueueEntry->FaxRouteFiles) {
        SetLastError( ERROR_NO_MORE_FILES );
        return FALSE;
    }

    EnterCriticalSection( &JobQueueEntry->CsFileList );

    while ((ULONG_PTR)Next != (ULONG_PTR)&JobQueueEntry->FaxRouteFiles) {
        FaxRouteFile = CONTAINING_RECORD( Next, FAX_ROUTE_FILE, ListEntry );
        Next = FaxRouteFile->ListEntry.Flink;
        if (Index ==  FileNumber) {
            if (*RequiredSize < (wcslen(FaxRouteFile->FileName)+1)*sizeof(WCHAR)) {
                if (FileNameBuffer == NULL) {
                    *RequiredSize = (wcslen(FaxRouteFile->FileName) + 1)*sizeof(WCHAR);
                }
                SetLastError( ERROR_INSUFFICIENT_BUFFER );
                LeaveCriticalSection( &JobQueueEntry->CsFileList );
                return FALSE;
            } else if (FileNameBuffer) {
                wcscpy( FileNameBuffer, FaxRouteFile->FileName );
                LeaveCriticalSection( &JobQueueEntry->CsFileList );
                return TRUE;
            } else {
                LeaveCriticalSection( &JobQueueEntry->CsFileList );
                SetLastError( ERROR_INVALID_PARAMETER );
                return TRUE;
            }
        }
        Index += 1;
    }

    LeaveCriticalSection( &JobQueueEntry->CsFileList );
    SetLastError( ERROR_NO_MORE_FILES );

    return FALSE;
}


BOOL WINAPI
FaxRouteEnumFiles(
    IN DWORD JobId,
    IN GUID *Guid,
    IN PFAXROUTEENUMFILE FileEnumerator,
    IN PVOID Context
    )
{
    PJOB_QUEUE JobQueueEntry;
    PFAX_ROUTE_FILE FaxRouteFile;
    PLIST_ENTRY Next;

    if (!FileEnumerator) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    JobQueueEntry = FindJobQueueEntry( JobId );
    if (!JobQueueEntry) {
        SetLastError( ERROR_INVALID_DATA );
        return FALSE;
    }

    Next = JobQueueEntry->FaxRouteFiles.Flink;
    if (Next == &JobQueueEntry->FaxRouteFiles) {
        SetLastError( ERROR_NO_MORE_FILES );
        return FALSE;
    }

    EnterCriticalSection( &JobQueueEntry->CsFileList );

    while ((ULONG_PTR)Next != (ULONG_PTR)&JobQueueEntry->FaxRouteFiles) {
        FaxRouteFile = CONTAINING_RECORD( Next, FAX_ROUTE_FILE, ListEntry );
        Next = FaxRouteFile->ListEntry.Flink;
        if (!FileEnumerator( JobId, &FaxRouteFile->Guid, Guid, FaxRouteFile->FileName, Context )) {
            LeaveCriticalSection( &JobQueueEntry->CsFileList );
            return FALSE;
        }
    }

    LeaveCriticalSection( &JobQueueEntry->CsFileList );

    SetLastError( ERROR_NO_MORE_FILES );
    return TRUE;
}


PROUTING_METHOD
FindRoutingMethodByGuid(
    IN LPCWSTR RoutingGuidString
    )
{
    PLIST_ENTRY         NextMethod;
    PROUTING_METHOD     RoutingMethod;
    GUID                RoutingGuid;


    IIDFromString( (LPWSTR)RoutingGuidString, &RoutingGuid );

    EnterCriticalSection( &CsRouting );

    NextMethod = RoutingMethods.Flink;
    if (NextMethod == NULL) {
        LeaveCriticalSection( &CsRouting );
        return NULL;
    }

    while ((ULONG_PTR)NextMethod != (ULONG_PTR)&RoutingMethods) {
        RoutingMethod = CONTAINING_RECORD( NextMethod, ROUTING_METHOD, ListEntryMethod );
        NextMethod = RoutingMethod->ListEntryMethod.Flink;
        if (IsEqualGUID( &RoutingGuid, &RoutingMethod->Guid )) {
            LeaveCriticalSection( &CsRouting );
            return RoutingMethod;
        }
    }

    LeaveCriticalSection( &CsRouting );

    return NULL;
}


DWORD
EnumerateRoutingMethods(
    IN PFAXROUTEMETHODENUM Enumerator,
    IN LPVOID Context
    )
{
    PLIST_ENTRY         NextMethod;
    PROUTING_METHOD     RoutingMethod;
    DWORD               Count = 0;


    EnterCriticalSection( &CsRouting );

    __try {

        NextMethod = RoutingMethods.Flink;
        if (NextMethod == NULL) {
            LeaveCriticalSection( &CsRouting );
            return Count;
        }


        while ((ULONG_PTR)NextMethod != (ULONG_PTR)&RoutingMethods) {
            RoutingMethod = CONTAINING_RECORD( NextMethod, ROUTING_METHOD, ListEntryMethod );
            NextMethod = RoutingMethod->ListEntryMethod.Flink;
            if (!Enumerator( RoutingMethod, Context )) {
                LeaveCriticalSection( &CsRouting );
                return Count;
            }
            Count += 1;
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {
          DebugPrint(( TEXT("EnumerateRoutingMethods crashed, ec = %x\n"), GetExceptionCode() ));
    }

    LeaveCriticalSection( &CsRouting );

    return Count;
}


BOOL
FaxRouteModifyRoutingData(
    DWORD JobId,
    LPCWSTR RoutingGuid,
    LPBYTE RoutingData,
    DWORD RoutingDataSize
    )
{
    PJOB_QUEUE JobQueueEntry = NULL;
    PROUTING_METHOD RoutingMethod = NULL;
    PROUTING_DATA_OVERRIDE RoutingDataOverride = NULL;


    if (JobId == 0 || RoutingGuid == NULL || RoutingData == NULL || RoutingDataSize == 0) {
        SetLastError( ERROR_INVALID_PARAMETER );
        return FALSE;
    }

    JobQueueEntry = FindJobQueueEntry( JobId );
    if (!JobQueueEntry) {
        SetLastError( ERROR_INVALID_DATA );
        return FALSE;
    }

    RoutingMethod = FindRoutingMethodByGuid( RoutingGuid );
    if (RoutingMethod == NULL) {
        SetLastError( ERROR_INVALID_DATA );
        return FALSE;
    }

    RoutingDataOverride = (PROUTING_DATA_OVERRIDE) MemAlloc( sizeof(ROUTING_DATA_OVERRIDE) );
    if (RoutingDataOverride == NULL) {
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return FALSE;
    }

    RoutingDataOverride->RoutingData = (LPBYTE)MemAlloc( RoutingDataSize );
    if (RoutingDataOverride->RoutingData == NULL) {
        MemFree( RoutingDataOverride );
        SetLastError( ERROR_NOT_ENOUGH_MEMORY );
        return FALSE;
    }

    RoutingDataOverride->RoutingDataSize = RoutingDataSize;
    RoutingDataOverride->RoutingMethod = RoutingMethod;

    CopyMemory( RoutingDataOverride->RoutingData, RoutingData, RoutingDataSize );

    EnterCriticalSection( &JobQueueEntry->CsRoutingDataOverride );
    InsertTailList( &JobQueueEntry->RoutingDataOverride, &RoutingDataOverride->ListEntry );
    LeaveCriticalSection( &JobQueueEntry->CsRoutingDataOverride );

    return TRUE;
}


int
__cdecl
MethodPriorityCompare(
    const void *arg1,
    const void *arg2
    )
{
    if (((PMETHOD_SORT)arg1)->Priority < ((PMETHOD_SORT)arg2)->Priority) {
        return -1;
    }
    if (((PMETHOD_SORT)arg1)->Priority > ((PMETHOD_SORT)arg2)->Priority) {
        return 1;
    }
    return 0;
}


BOOL
SortMethodPriorities(
    VOID
    )
{
    PLIST_ENTRY Next;
    PROUTING_METHOD RoutingMethod;
    PMETHOD_SORT MethodSort;
    DWORD i;


    EnterCriticalSection( &CsRouting );

    Next = RoutingMethods.Flink;
    if (Next == NULL) {
        LeaveCriticalSection( &CsRouting );
        return FALSE;
    }

    MethodSort = (PMETHOD_SORT) MemAlloc( CountRoutingMethods * sizeof(METHOD_SORT) );
    if (MethodSort == NULL) {
        LeaveCriticalSection( &CsRouting );
        return FALSE;
    }

    i = 0;

    while ((ULONG_PTR)Next != (ULONG_PTR)&RoutingMethods) {
        RoutingMethod = CONTAINING_RECORD( Next, ROUTING_METHOD, ListEntryMethod );
        Next = RoutingMethod->ListEntryMethod.Flink;
        MethodSort[i].Priority = RoutingMethod->Priority;
        MethodSort[i].RoutingMethod = RoutingMethod;
        i += 1;
    }

    qsort(
        (PVOID)MethodSort,
        (int)CountRoutingMethods,
        sizeof(METHOD_SORT),
        MethodPriorityCompare
        );

    InitializeListHead( &RoutingMethods );

    for (i=0; i<CountRoutingMethods; i++) {
        MethodSort[i].RoutingMethod->Priority = i + 1;
        MethodSort[i].RoutingMethod->ListEntryMethod.Flink = NULL;
        MethodSort[i].RoutingMethod->ListEntryMethod.Blink = NULL;
        InsertTailList( &RoutingMethods, &MethodSort[i].RoutingMethod->ListEntryMethod );
    }

    MemFree( MethodSort );

    LeaveCriticalSection( &CsRouting );

    return TRUE;
}

BOOL
CommitMethodChanges(
    VOID
    )
/*++

Routine Description:

    sticks changes to routing into the registry

Arguments:

    NONE

Return Value:

    TRUE for success

--*/
{
    PLIST_ENTRY Next;
    PROUTING_METHOD RoutingMethod;
    TCHAR StrGuid[100];

__try {
    EnterCriticalSection(&CsRouting);

    Next = RoutingMethods.Flink;

    while ((UINT_PTR)Next != (UINT_PTR)&RoutingMethods) {

        RoutingMethod = CONTAINING_RECORD( Next, ROUTING_METHOD , ListEntryMethod );
        Next = RoutingMethod->ListEntryMethod.Flink;

        StringFromGUID2( &RoutingMethod->Guid,
                         StrGuid,
                         sizeof(StrGuid)/sizeof(TCHAR)
                        );

        SetFaxRoutingInfo( RoutingMethod->RoutingExtension->InternalName,
                           RoutingMethod->InternalName,
                           StrGuid,
                           RoutingMethod->Priority,
                           RoutingMethod->FunctionName,
                           RoutingMethod->FriendlyName
                        );
    }

    LeaveCriticalSection(&CsRouting);
} __except (EXCEPTION_EXECUTE_HANDLER) {
    LeaveCriticalSection(&CsRouting);
}

return TRUE;

}

BOOL
InitializeRouting(
    PREG_FAX_SERVICE FaxReg
    )

/*++

Routine Description:

    Initializes routing

Arguments:

    NONE

Return Value:

    NONE

--*/

{
    DWORD i,j;
    HMODULE hModule;
    PROUTING_EXTENSION RoutingExtension;
    PROUTING_METHOD RoutingMethod;
    LPSTR ProcName;
    FAX_ROUTE_CALLBACKROUTINES Callbacks;
    BOOL InitializationSuccess;


    FaxMapiInitialize( FaxSvcHeapHandle, ServiceMessageBox, ServiceDebug );

    InitializeListHead( &RoutingExtensions );
    InitializeListHead( &RoutingMethods );

    Callbacks.SizeOfStruct              = sizeof(FAX_ROUTE_CALLBACKROUTINES);
    Callbacks.FaxRouteAddFile           = FaxRouteAddFile;
    Callbacks.FaxRouteDeleteFile        = FaxRouteDeleteFile;
    Callbacks.FaxRouteGetFile           = FaxRouteGetFile;
    Callbacks.FaxRouteEnumFiles         = FaxRouteEnumFiles;
    Callbacks.FaxRouteModifyRoutingData = FaxRouteModifyRoutingData;

    InitializationSuccess = TRUE;
    for (i=0; i<FaxReg->RoutingExtensionsCount; i++) {


        hModule = LoadLibrary( FaxReg->RoutingExtensions[i].ImageName );
        if (!hModule) {
            DebugStop(( L"LoadLibrary() failed: [%s], ec=%d", FaxReg->RoutingExtensions[i].ImageName, GetLastError() ));
            goto InitializationFailed;
        }

        RoutingExtension = (PROUTING_EXTENSION) MemAlloc( sizeof(ROUTING_EXTENSION) );
        if (!RoutingExtension) {
            FreeLibrary( hModule );
            DebugStop(( L"Could not allocate memory for routing extension %s", FaxReg->RoutingExtensions[i].ImageName ));
            goto InitializationFailed;
        }

        RoutingExtension->hModule = hModule;

        wcscpy( RoutingExtension->FriendlyName, FaxReg->RoutingExtensions[i].FriendlyName );
        wcscpy( RoutingExtension->ImageName,    FaxReg->RoutingExtensions[i].ImageName    );
        wcscpy( RoutingExtension->InternalName, FaxReg->RoutingExtensions[i].InternalName );

        if (wcscmp( RoutingExtension->FriendlyName, FAX_EXTENSION_NAME ) == 0) {
            RoutingExtension->MicrosoftExtension = TRUE;
        }

        RoutingExtension->FaxRouteInitialize = (PFAXROUTEINITIALIZE) GetProcAddress(
            hModule,
            "FaxRouteInitialize"
            );

        RoutingExtension->FaxRouteGetRoutingInfo = (PFAXROUTEGETROUTINGINFO) GetProcAddress(
            hModule,
            "FaxRouteGetRoutingInfo"
            );

        RoutingExtension->FaxRouteSetRoutingInfo = (PFAXROUTESETROUTINGINFO) GetProcAddress(
            hModule,
            "FaxRouteSetRoutingInfo"
            );

        RoutingExtension->FaxRouteDeviceEnable = (PFAXROUTEDEVICEENABLE) GetProcAddress(
            hModule,
            "FaxRouteDeviceEnable"
            );

        RoutingExtension->FaxRouteDeviceChangeNotification = (PFAXROUTEDEVICECHANGENOTIFICATION) GetProcAddress(
            hModule,
            "FaxRouteDeviceChangeNotification"
            );

        if (RoutingExtension->FaxRouteInitialize == NULL ||
            RoutingExtension->FaxRouteGetRoutingInfo == NULL ||
            RoutingExtension->FaxRouteSetRoutingInfo == NULL ||
            RoutingExtension->FaxRouteDeviceChangeNotification == NULL ||
            RoutingExtension->FaxRouteDeviceEnable == NULL)
        {
            //
            // the routing extension dll does not have a complete export list
            //

            MemFree( RoutingExtension );
            FreeLibrary( hModule );
            DebugStop(( L"Routing extension FAILED to initialized [%s]", FaxReg->RoutingExtensions[i].FriendlyName ));
            goto InitializationFailed;

        }

        //
        // create the routing extension's heap and add it to the list
        //

        RoutingExtension->HeapHandle = RoutingExtension->MicrosoftExtension ? FaxSvcHeapHandle : HeapCreate( 0, 1024*100, 1024*1024*2 );
        if (!RoutingExtension->HeapHandle) {

            FreeLibrary( hModule );
            MemFree( RoutingExtension );
            goto InitializationFailed;

        } else {

            __try {
                if (RoutingExtension->FaxRouteInitialize( RoutingExtension->HeapHandle, &Callbacks )) {

                    InsertTailList( &RoutingExtensions, &RoutingExtension->ListEntry );
                    InitializeListHead( &RoutingExtension->RoutingMethods );
                    for (j=0; j<FaxReg->RoutingExtensions[i].RoutingMethodsCount; j++) {

                        RoutingMethod = (PROUTING_METHOD) MemAlloc( sizeof(ROUTING_METHOD) );
                        if (!RoutingMethod) {
                            DebugStop(( L"Could not allocate memory for routing method %s", FaxReg->RoutingExtensions[i].RoutingMethods[j].FunctionName ));
                            goto InitializationFailed;
                        }

                        RoutingMethod->RoutingExtension = RoutingExtension;

                        RoutingMethod->Priority = FaxReg->RoutingExtensions[i].RoutingMethods[j].Priority;

                        RoutingMethod->FriendlyName = StringDup( FaxReg->RoutingExtensions[i].RoutingMethods[j].FriendlyName );
                        if (!RoutingMethod->FriendlyName) {
                            DebugStop(( L"Could not create routing function name [%s]", FaxReg->RoutingExtensions[i].RoutingMethods[j].FunctionName ));
                            MemFree( RoutingMethod );
                            goto InitializationFailed;
                        }

                        RoutingMethod->FunctionName = StringDup( FaxReg->RoutingExtensions[i].RoutingMethods[j].FunctionName );
                        if (!RoutingMethod->FunctionName) {
                            DebugStop(( L"Could not create routing function name [%s]", FaxReg->RoutingExtensions[i].RoutingMethods[j].FunctionName ));
                            MemFree( RoutingMethod );
                            goto InitializationFailed;
                        }

                        RoutingMethod->InternalName = StringDup( FaxReg->RoutingExtensions[i].RoutingMethods[j].InternalName );
                        if (!RoutingMethod->InternalName) {
                            DebugStop(( L"Could not create routing internal name [%s]", FaxReg->RoutingExtensions[i].RoutingMethods[j].InternalName ));
                            MemFree( RoutingMethod );
                            goto InitializationFailed;
                        }

                        ProcName = UnicodeStringToAnsiString( RoutingMethod->FunctionName );
                        if (!ProcName) {
                            DebugStop(( L"Could not create routing function name [%s]", RoutingMethod->FunctionName ));
                            MemFree( RoutingMethod );
                            goto InitializationFailed;
                        }

                        if (IIDFromString( FaxReg->RoutingExtensions[i].RoutingMethods[j].Guid, &RoutingMethod->Guid ) != S_OK) {
                            DebugStop(( L"Invalid GUID string [%s]", FaxReg->RoutingExtensions[i].RoutingMethods[j].Guid ));
                            MemFree( RoutingMethod );
                            goto InitializationFailed;
                        }

                        RoutingMethod->FaxRouteMethod = (PFAXROUTEMETHOD) GetProcAddress(
                            hModule,
                            ProcName
                            );
                        if (!RoutingMethod->FaxRouteMethod) {

                            DebugStop(( L"Could not get function address [%s]", ProcName ));
                            MemFree( RoutingMethod );
                            MemFree( ProcName );

                        } else {

                            MemFree( ProcName );
                            InsertTailList( &RoutingExtension->RoutingMethods, &RoutingMethod->ListEntry );
                            InsertTailList( &RoutingMethods, &RoutingMethod->ListEntryMethod );
                            CountRoutingMethods += 1;

                        }
                    }

                } else {
                    FreeLibrary( hModule );
                    MemFree( RoutingExtension );
                }
            } __except (EXCEPTION_EXECUTE_HANDLER) {
                DebugStop(( L"FaxRouteInitialize() faulted: 0x%08x", GetExceptionCode() ));
                goto InitializationFailed;
            }
        }
        //
        // success initializing this extension
        //
        goto next;

InitializationFailed:
        FaxLog(
                FAXLOG_CATEGORY_INIT,
                FAXLOG_LEVEL_NONE,
                2,
                MSG_ROUTE_INIT_FAILED,
                FaxReg->RoutingExtensions[i].FriendlyName,
                FaxReg->RoutingExtensions[i].ImageName
              );

next:
        ;
    }

    if (FaxReg->InboundProfile && FaxReg->InboundProfile[0]) {
        InboundProfileName = StringDup( FaxReg->InboundProfile );
        InboundProfileInfo = AddNewMapiProfile( FaxReg->InboundProfile, TRUE, TRUE );
        if (!InboundProfileInfo) {
            DebugStop(( L"Could not initialize inbound mapi profile [%s]", FaxReg->InboundProfile ));
        }
    }

    SortMethodPriorities();

    RoutingIsInitialized = TRUE;

    return TRUE;
}


LPWSTR
TiffFileNameToRouteFileName(
    LPWSTR  TiffFileName
    )

/*++

Routine Description:

    Convert a tiff file name to a routing information file name.  The call MUST free
    the memory allocated by this routine.

Arguments:

    TiffFileName    - pointer to tiff file name

Return value:

    A pointer to a routing information file name on success, NULL on fail.

--*/

{
    LPWSTR RouteFileName;
    LPWSTR Ext;

    RouteFileName = StringDup( TiffFileName );

    if (!RouteFileName) {
        return NULL;
    }

    Ext = wcsrchr( RouteFileName, L'.' );

    if (Ext) {

        wcscpy( Ext, L".rte" );
        return RouteFileName;

    } else {

        return NULL;

    }
}


BOOL
FaxRoute(
    PJOB_QUEUE          JobQueueEntry,
    LPTSTR              TiffFileName,
    PFAX_ROUTE          FaxRoute,
    PROUTE_FAILURE_INFO *RouteFailureInfo,
    LPDWORD             RouteFailureCount
    )

/*++

Routine Description:

    Routes a FAX.

Arguments:

    JobQueueEntry           - the job queue entry for the job
    TiffFileName            - filename of the received fax
    FaxRoute                - struct describing received FAX
    RouteFailureInfo        - pointer to receive pointr to eceive buffer ROUTE_FAILURE_INFO structures
    RouteFailureCount       - receives the total number of route failures recorded

Return Value:

    ignored for now

--*/

{
    LPWSTR                  FullPath = NULL;
    LPWSTR                  RouteFileName = TiffFileNameToRouteFileName( TiffFileName );
    LPJOB_INFO_2            JobInfo = NULL;
    PLIST_ENTRY             NextMethod;
    PROUTING_METHOD         RoutingMethod;
    DWORD                   FailureCount = 0;
    PROUTE_FAILURE_INFO     RouteFailure;
    PLIST_ENTRY             NextRoutingOverride;
    PROUTING_DATA_OVERRIDE  RoutingDataOverride;
    BOOL                    RetVal = TRUE;

    *RouteFailureInfo = NULL;
    *RouteFailureCount = 0;

    //
    // if the tiff file has been deleted, delete the routing info file and return
    //

    if (GetFileAttributes( TiffFileName ) == 0xffffffff) {
        DeleteFile( RouteFileName );
        MemFree( RouteFileName );
        return FALSE;
    }

    EnterCriticalSection( &CsRouting );

    NextMethod = RoutingMethods.Flink;
    if (NextMethod) {

        //
        // allocate memory to record the GUIDs of the failed routing methods
        //

        RouteFailure = (PROUTE_FAILURE_INFO) MemAlloc( CountRoutingMethods * sizeof(ROUTE_FAILURE_INFO) );
        if (RouteFailure == NULL) {
            MemFree( RouteFileName );
            LeaveCriticalSection( &CsRouting );
            return FALSE;
        }

        //
        // add the tiff file as the first file
        // in the file name list, the owner is the fax service
        //

        if (FaxRouteAddFile( FaxRoute->JobId, TiffFileName, &FaxSvcGuid ) < 1) {
            LeaveCriticalSection( &CsRouting );
            return FALSE;
        }

        //
        // walk thru all of the routing methods and call them
        //

        while ((ULONG_PTR)NextMethod != (ULONG_PTR)&RoutingMethods) {
            RoutingMethod = CONTAINING_RECORD( NextMethod, ROUTING_METHOD, ListEntryMethod );
            NextMethod = RoutingMethod->ListEntryMethod.Flink;

            __try {

                FaxRoute->RoutingInfoData = NULL;
                FaxRoute->RoutingInfoDataSize = 0;

                EnterCriticalSection( &JobQueueEntry->CsRoutingDataOverride );

                NextRoutingOverride = JobQueueEntry->RoutingDataOverride.Flink;
                if (NextRoutingOverride != NULL) {
                    while ((ULONG_PTR)NextRoutingOverride != (ULONG_PTR)&JobQueueEntry->RoutingDataOverride) {
                        RoutingDataOverride = CONTAINING_RECORD( NextRoutingOverride, ROUTING_DATA_OVERRIDE, ListEntry );
                        NextRoutingOverride = RoutingDataOverride->ListEntry.Flink;
                        if (RoutingDataOverride->RoutingMethod == RoutingMethod) {
                            FaxRoute->RoutingInfoData = RoutingDataOverride->RoutingData;
                            FaxRoute->RoutingInfoDataSize = RoutingDataOverride->RoutingDataSize;
                        }
                    }
                }

                LeaveCriticalSection( &JobQueueEntry->CsRoutingDataOverride );

                RouteFailure[FailureCount].FailureData = NULL;
                RouteFailure[FailureCount].FailureSize = 0;

                if (!RoutingMethod->FaxRouteMethod(
                        FaxRoute,
                        &RouteFailure[FailureCount].FailureData,
                        &RouteFailure[FailureCount].FailureSize ))
                {
                    StringFromGUID2(
                        &RoutingMethod->Guid,
                        RouteFailure[FailureCount++].GuidString,
                        MAX_GUID_STRING_LEN
                        );
                    RetVal = FALSE;
                }

            } __except (EXCEPTION_EXECUTE_HANDLER) {

                DebugStop(( L"FaxRouteProcess() faulted: 0x%08x", GetExceptionCode() ));
                StringFromGUID2(
                    &RoutingMethod->Guid,
                    RouteFailure[FailureCount++].GuidString,
                    MAX_GUID_STRING_LEN
                    );

            }
        }
    }

    if (FailureCount == 0) {

        DeleteFile( TiffFileName );
        MemFree( RouteFailure );

    } else {
        *RouteFailureInfo = RouteFailure;
        *RouteFailureCount = FailureCount;
    }

    MemFree( JobInfo );
    MemFree( RouteFileName );

    LeaveCriticalSection( &CsRouting );

    return RetVal;
}


BOOL
LoadRouteInfo(
    IN  LPWSTR              RouteFileName,
    OUT PROUTE_INFO         *RouteInfo,
    OUT PROUTE_FAILURE_INFO *RouteFailure,
    OUT LPDWORD             RouteFailureCount
    )

/*++

Routine Description:

    Load routing information from a routing information file.

Arguments:

    RouteFileName - Name of routing information file.

Return value:

    Pointer to routing information structure if success.  NULL if fail.

--*/

{
#if 0
    HANDLE RouteHandle;
    PROUTE_INFO pRouteInfo = NULL;
    DWORD FileSize;
    DWORD BytesRead;
    LPBYTE Buffer;
    PROUTE_FAILURE_INFO pRouteFailure = NULL;
    DWORD pRouteFailureCount = 0;


    RouteHandle = CreateFile(
        RouteFileName,
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (RouteHandle == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    //
    // the size of the file is the size of the structure
    //

    FileSize = GetFileSize( RouteHandle, NULL );
    if (FileSize == 0xffffffff) {
        CloseHandle( RouteHandle );
        return FALSE;
    }

    Buffer = MemAlloc( FileSize );
    pRouteInfo = (PROUTE_INFO) Buffer;
    if (Buffer == NULL) {
        CloseHandle( RouteHandle );
        return FALSE;
    }

    if (!ReadFile( RouteHandle, Buffer, FileSize, &BytesRead, NULL) || BytesRead != FileSize ) {
        CloseHandle( RouteHandle );
        return FALSE;
    }

    CloseHandle( RouteHandle );

    if (pRouteInfo->Signature != ROUTING_SIGNATURE) {
        CloseHandle( RouteHandle );
        return FALSE;
    }

    pRouteInfo->TiffFileName   = OffsetToString( pRouteInfo->TiffFileName,   Buffer );
    pRouteInfo->ReceiverName   = OffsetToString( pRouteInfo->ReceiverName,   Buffer );
    pRouteInfo->ReceiverNumber = OffsetToString( pRouteInfo->ReceiverNumber, Buffer );
    pRouteInfo->Csid           = OffsetToString( pRouteInfo->Csid,           Buffer );
    pRouteInfo->CallerId       = OffsetToString( pRouteInfo->CallerId,       Buffer );
    pRouteInfo->RoutingInfo    = OffsetToString( pRouteInfo->RoutingInfo,    Buffer );
    pRouteInfo->DeviceName     = OffsetToString( pRouteInfo->DeviceName,     Buffer );
    pRouteInfo->Tsid           = OffsetToString( pRouteInfo->Tsid,           Buffer );


    //
    // return the data
    //

    RouteInfo = pRouteInfo;

#endif
    return TRUE;
}


BOOL
BuildRouteInfo(
    LPWSTR              TiffFileName,
    PROUTE_FAILURE_INFO RouteFailure,
    DWORD               RouteFailureCount,
    LPWSTR              ReceiverName,
    LPWSTR              ReceiverNumber,
    LPWSTR              DeviceName,
    LPWSTR              Tsid,
    LPWSTR              Csid,
    LPWSTR              CallerId,
    LPWSTR              RoutingInfo,
    DWORDLONG           ElapsedTime
    )

/*++

Routine Description:

    Build a routing info structure.  The caller MUST free the memory allocated by this routine.

Arguments:

    TiffFileName        - File containing the fax tiff data
    ReceiverName        - Receiver's name
    ReceiverNumber      - Receiver's fax number
    DeviceName          - Device name on which the fax was received
    Tsid                - Transmitting station identifier
    Csid                - Calling station's identifier
    CallerId            - Caller id data, if any
    RoutingInfo         - Routing info, such as DID, T.30 subaddress, etc

Return value:

    A pointer to a ROUTE_INFO struct on success, NULL on failure.

--*/

{
    BOOL Rval = FALSE;
    LPWSTR RouteFileName = NULL;
    HANDLE RouteHandle = INVALID_HANDLE_VALUE;
    DWORD StringSize = 0;
    DWORD FailureSize = 0;
    LPBYTE Buffer = NULL;
    ULONG_PTR Offset = 0;
    PROUTE_INFO RouteInfo = NULL;
    DWORD i = 0;


    StringSize += StringSize( TiffFileName );
    StringSize += StringSize( ReceiverName );
    StringSize += StringSize( ReceiverNumber );

    StringSize += StringSize( Csid );
    StringSize += StringSize( CallerId );
    StringSize += StringSize( RoutingInfo );

    StringSize += StringSize( DeviceName );
    StringSize += StringSize( Tsid );

    for (i=0; i<RouteFailureCount; i++) {
        FailureSize += RouteFailure[i].FailureSize;
        FailureSize += MAX_GUID_STRING_LEN;
    }

    Buffer = MemAlloc( sizeof(ROUTE_INFO) + StringSize + FailureSize );
    if (!Buffer) {
        goto exit;
    }

    RouteInfo = (PROUTE_INFO) Buffer;

    RouteInfo->Signature = ROUTING_SIGNATURE;
    RouteInfo->StringSize = StringSize;
    RouteInfo->FailureSize = FailureSize;
    RouteInfo->ElapsedTime = ElapsedTime;

    Offset = sizeof(ROUTE_INFO);

    //
    // convert string pointers to offsets
    // and store the strings at the end of the buffer
    //

    StoreString(
        TiffFileName,
        (PULONG_PTR) &RouteInfo->TiffFileName,
        Buffer,
        &Offset
        );

    StoreString(
        ReceiverName,
        (PULONG_PTR) &RouteInfo->ReceiverName,
        Buffer,
        &Offset
        );

    StoreString(
        ReceiverNumber,
        (PULONG_PTR) &RouteInfo->ReceiverNumber,
        Buffer,
        &Offset
        );

    StoreString(
        Csid,
        (PULONG_PTR) &RouteInfo->Csid,
        Buffer,
        &Offset
        );

    StoreString(
        Tsid,
        (PULONG_PTR) &RouteInfo->Tsid,
        Buffer,
        &Offset
        );

    StoreString(
        CallerId,
        (PULONG_PTR) &RouteInfo->CallerId,
        Buffer,
        &Offset
        );

    StoreString(
        RoutingInfo,
        (PULONG_PTR) &RouteInfo->RoutingInfo,
        Buffer,
        &Offset
        );

    StoreString(
        DeviceName,
        (PULONG_PTR) &RouteInfo->DeviceName,
        Buffer,
        &Offset
        );

    //
    // store the routing failure data
    //

    *(LPDWORD) (Buffer + Offset) = RouteFailureCount;
    Offset += sizeof(DWORD);

    for (i=0; i<RouteFailureCount; i++) {
        CopyMemory( Buffer+Offset, RouteFailure[i].FailureData, RouteFailure[i].FailureSize );
        Offset += RouteFailure[i].FailureSize;
    }


    RouteFileName = TiffFileNameToRouteFileName( TiffFileName );

    RouteHandle = CreateFile(
        RouteFileName,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (RouteHandle == INVALID_HANDLE_VALUE) {
        goto exit;
    }

    WriteFile(
        RouteHandle,
        Buffer,
        sizeof(ROUTE_INFO) + StringSize + FailureSize,
        &i,
        NULL
        );

    Rval = TRUE;

exit:

    if (RouteHandle != INVALID_HANDLE_VALUE) {
        CloseHandle( RouteHandle );
    }

    MemFree( Buffer );
    MemFree( RouteFileName );

    return Rval;
}

BOOL
FaxRouteRetry(
    PFAX_ROUTE FaxRoute,
    PROUTE_FAILURE_INFO RouteFailureInfo
    )
{
    PROUTING_METHOD         RoutingMethod;
    DWORD                   FailureCount = 0;
    BOOL                    RetVal = TRUE;


    //
    // in this case, we've already retried this method and it succeeded.
    //
    if (!*RouteFailureInfo->GuidString) {
       return TRUE;
    }

    RoutingMethod = FindRoutingMethodByGuid( RouteFailureInfo->GuidString );

    if (RoutingMethod) {
        __try {

          if (!RoutingMethod->FaxRouteMethod(
                  FaxRoute,
                  &RouteFailureInfo->FailureData,
                  &RouteFailureInfo->FailureSize ))
          {
              RetVal = FALSE;
          } else {
             //
             // set the routing guid to zero so we don't try to route this guy again.  He is
             // deallocated when we delete the queue entry.
             //
             ZeroMemory(RouteFailureInfo->GuidString, MAX_GUID_STRING_LEN*sizeof(WCHAR) );
          }

        } __except (EXCEPTION_EXECUTE_HANDLER) {

          DebugStop(( L"FaxRouteProcess() faulted: 0x%08x", GetExceptionCode() ));

        }
    } else {
        return FALSE;
    }

    return RetVal;
}

PFAX_ROUTE
SerializeFaxRoute(
    PFAX_ROUTE FaxRoute,
    LPDWORD Size
    )
{
    DWORD ByteCount = sizeof(FAX_ROUTE);
    DWORD_PTR Offset;
    PFAX_ROUTE SerFaxRoute;             // the serialized version


    *Size = 0;

    // Add the size of the strings

    ByteCount += StringSize( FaxRoute->Csid );
    ByteCount += StringSize( FaxRoute->Tsid );
    ByteCount += StringSize( FaxRoute->CallerId );
    ByteCount += StringSize( FaxRoute->RoutingInfo );
    ByteCount += StringSize( FaxRoute->ReceiverName );
    ByteCount += StringSize( FaxRoute->ReceiverNumber );
    ByteCount += StringSize( FaxRoute->DeviceName );

    ByteCount += FaxRoute->RoutingInfoDataSize;

    SerFaxRoute = (PFAX_ROUTE) MemAlloc( ByteCount );


    if (SerFaxRoute == NULL) {
        return NULL;
    }

    *Size = ByteCount;

    CopyMemory( (PVOID) SerFaxRoute, (PVOID) FaxRoute, sizeof(FAX_ROUTE) );

    Offset = sizeof( FAX_ROUTE );

    StoreString( FaxRoute->Csid, (PDWORD_PTR)&SerFaxRoute->Csid, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->Tsid, (PDWORD_PTR)&SerFaxRoute->Tsid, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->CallerId, (PDWORD_PTR)&SerFaxRoute->CallerId, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->RoutingInfo, (PDWORD_PTR)&SerFaxRoute->RoutingInfo, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->ReceiverName, (PDWORD_PTR)&SerFaxRoute->ReceiverName, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->ReceiverNumber, (PDWORD_PTR)&SerFaxRoute->ReceiverNumber, (LPBYTE) SerFaxRoute, &Offset );
    StoreString( FaxRoute->DeviceName, (PDWORD_PTR)&SerFaxRoute->DeviceName, (LPBYTE) SerFaxRoute, &Offset );

    FaxRoute->RoutingInfoData = (LPBYTE) Offset;
    Offset += FaxRoute->RoutingInfoDataSize;

    CopyMemory(
        (PVOID) ((LPBYTE) &SerFaxRoute + Offset),
        (PVOID) FaxRoute->RoutingInfoData,
        FaxRoute->RoutingInfoDataSize
        );

    return SerFaxRoute;
}

PFAX_ROUTE
DeSerializeFaxRoute(
    PFAX_ROUTE FaxRoute
    )
{
    PFAX_ROUTE NewFaxRoute;

    FixupString( FaxRoute, FaxRoute->Csid );
    FixupString( FaxRoute, FaxRoute->Tsid );
    FixupString( FaxRoute, FaxRoute->CallerId );
    FixupString( FaxRoute, FaxRoute->RoutingInfo );
    FixupString( FaxRoute, FaxRoute->ReceiverName );
    FixupString( FaxRoute, FaxRoute->DeviceName );
    FixupString( FaxRoute, FaxRoute->ReceiverNumber );

    FaxRoute->RoutingInfoData = (LPBYTE) FaxRoute + (ULONG_PTR) FaxRoute->RoutingInfoData;

    //
    // Make a copy where each item is individually malloced so it can be freed properly
    //

    NewFaxRoute = MemAlloc( sizeof( FAX_ROUTE ) );

    if (NewFaxRoute) {

        CopyMemory( (LPBYTE) NewFaxRoute, (LPBYTE) FaxRoute, sizeof(FAX_ROUTE) );

        NewFaxRoute->Csid = StringDup( FaxRoute->Csid );
        NewFaxRoute->Tsid = StringDup( FaxRoute->Tsid );
        NewFaxRoute->CallerId = StringDup( FaxRoute->CallerId );
        NewFaxRoute->RoutingInfo = StringDup( FaxRoute->RoutingInfo );
        NewFaxRoute->ReceiverName = StringDup( FaxRoute->ReceiverName );
        NewFaxRoute->DeviceName = StringDup( FaxRoute->DeviceName );
        NewFaxRoute->ReceiverNumber = StringDup( FaxRoute->ReceiverNumber );

        NewFaxRoute->RoutingInfoData = MemAlloc( FaxRoute->RoutingInfoDataSize );

        if (NewFaxRoute->RoutingInfoData) {
            CopyMemory( NewFaxRoute->RoutingInfoData, FaxRoute->RoutingInfoData, FaxRoute->RoutingInfoDataSize );
        }

    }

    MemFree( FaxRoute );

    return NewFaxRoute;
}