/*++

Copyright (c) 1996  Microsoft Corporation

Module Name:

    faxrpc.c

Abstract:

    This module contains the functions that are dispatched
    as a result of an rpc call.

Author:

    Wesley Witt (wesw) 16-Jan-1996


Revision History:

--*/

#include "faxsvc.h"
#pragma hdrstop

//
// version defines
//

#define WINFAX_MAJOR_VERSION        1803
#define WINFAX_MINOR_VERSION        1

#define WINFAX_VERSION              ((WINFAX_MINOR_VERSION<<16) | WINFAX_MAJOR_VERSION)

LIST_ENTRY          ClientsListHead;
CRITICAL_SECTION    CsClients;
LONG                ConnectionCount = 0;        // Represents the number of active rpc connections plus the number
                                                // of devices with receive enabled.   If > zero, the service will not
                                                // shut itself down.



void *
MIDL_user_allocate(
    IN size_t NumBytes
    )
{
    return MemAlloc( NumBytes );
}


void
MIDL_user_free(
    IN void *MemPointer
    )
{
    MemFree( MemPointer );
}


VOID
StoreString(
    LPCTSTR String,
    PULONG_PTR DestString,
    LPBYTE Buffer,
    PULONG_PTR Offset
    )
{
    if (String) {
        _tcscpy( (LPTSTR) (Buffer+*Offset), String );
        *DestString = *Offset;
        *Offset += StringSize( String );
    } else {
        *DestString = 0;
    }

}

error_status_t
FAX_ConnectionRefCount(
    handle_t FaxHandle,
    LPHANDLE FaxConHandle,
    DWORD Connect,
    LPDWORD CanShare
    )
/*++

Routine Description:

    Called on connect.  Maintains an active connection count.  Client unbind rpc and
    the counter is decremented in the rundown routine.  Returns a context handle to the client.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    FaxConHandle    - Context handle
    Connect         - 1 if connecting, 0 if disconnecting
    CanShare        - non-zero if sharing is allowed, zero otherwise

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/
{
    PHANDLE_ENTRY HandleEntry;
    error_status_t Rval = 0;
    static int Sharing = -1;

    if (Sharing == -1) {
        Sharing = IsProductSuite() ? 1 : 0 ;        // If running on SBS or Comm Server, sharing is allowed.
    }


    __try {

        *CanShare = Sharing;

        if (Connect == 0) {

            HandleEntry = (PHANDLE_ENTRY) *FaxConHandle;

            *FaxConHandle = NULL;

            CloseFaxHandle( HandleEntry );

            return 0;
        }

        HandleEntry = CreateNewConnectionHandle( FaxHandle );

        if (!HandleEntry) {
            Rval = ERROR_INVALID_HANDLE;
            _leave;
        }

        *FaxConHandle = (HANDLE) HandleEntry;

        InterlockedIncrement( &ConnectionCount );



    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        Rval = GetExceptionCode();

    }


    return Rval;
}


VOID
RPC_FAX_SVC_HANDLE_rundown(
    IN HANDLE FaxConnectionHandle
    )
{
    PHANDLE_ENTRY HandleEntry = (PHANDLE_ENTRY) FaxConnectionHandle;


    __try {

        DebugPrint(( TEXT("RPC_FAX_SVC_HANDLE_rundown() running for connection handle 0x%08x"), FaxConnectionHandle ));

        CloseFaxHandle( HandleEntry );

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        DebugPrint(( TEXT("RPC_FAX_SVC_HANDLE_rundown() crashed, ec=0x%08x"), GetExceptionCode() ));

    }

    return;
}

error_status_t
FAX_GetVersion(
    handle_t FaxHandle,
    LPDWORD Version
    )

/*++

Routine Description:

    Gets the FAX dll's version number.  This
    API is really only used as a ping API.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    Version         - Version number.


Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    if (!Version) {
        return ERROR_INVALID_PARAMETER;
    }
    *Version = WINFAX_VERSION;
    return 0;
}


error_status_t
FAX_GetInstallType(
    IN  handle_t    FaxHandle,
    OUT LPDWORD     InstallType,
    OUT LPDWORD     InstalledPlatforms,
    OUT LPDWORD     ProductType
    )

/*++

Routine Description:

    Gets the FAX dll's version number.  This
    API is really only used as a ping API.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    Version         - Version number.


Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    DWORD Installed;


    if ((!GetInstallationInfo( &Installed, InstallType, InstalledPlatforms, ProductType )) || (!Installed)) {
        return ERROR_INVALID_FUNCTION;
    }

    return 0;
}


error_status_t
FAX_OpenPort(
    handle_t            FaxHandle,
    DWORD               DeviceId,
    DWORD               Flags,
    LPHANDLE            FaxPortHandle
    )

/*++

Routine Description:

    Opens a fax port for subsequent use in other fax APIs.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    DeviceId        - Requested device id
    FaxPortHandle   - The resulting FAX port handle.

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    error_status_t Rval = 0;
    PLINE_INFO LineInfo;
    PHANDLE_ENTRY HandleEntry;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!FaxPortHandle) {
        return ERROR_INVALID_PARAMETER;
    }

    EnterCriticalSection( &CsLine );

    __try {

        LineInfo = GetTapiLineFromDeviceId( DeviceId );
        if (LineInfo) {
            if (Flags & PORT_OPEN_MODIFY) {
                //
                // the client wants to open the port for modify
                // access so we must make sure that no other
                // client already has this port open for modify access
                //
                if (IsPortOpenedForModify( LineInfo )) {
                    Rval = ERROR_INVALID_HANDLE;
                    _leave;
                }
            }

            HandleEntry = CreateNewPortHandle( FaxHandle, LineInfo, Flags );
            if (!HandleEntry) {
                Rval = ERROR_INVALID_HANDLE;
                _leave;
            }

            *FaxPortHandle = (HANDLE) HandleEntry;
        } else {
            Rval = ERROR_BAD_UNIT;
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        Rval = GetExceptionCode();

    }

    LeaveCriticalSection( &CsLine );

    return Rval;
}


error_status_t
FAX_ClosePort(
    OUT LPHANDLE    FaxPortHandle
    )

/*++

Routine Description:

    Closes an open FAX port.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    FaxPortHandle   - FAX port handle obtained from FaxOpenPort.


Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    error_status_t Rval = 0;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    __try {

        CloseFaxHandle( (PHANDLE_ENTRY) *FaxPortHandle );

        *FaxPortHandle = NULL;

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        Rval = GetExceptionCode();

    }

    return Rval;
}

error_status_t
FAX_SendDocument(
    IN handle_t FaxHandle,
    IN LPCWSTR FileName,
    IN const FAX_JOB_PARAMW *JobParams,
    OUT LPDWORD FaxJobId
    )

/*++

Routine Description:

    Sends a FAX document to the specified recipient.
    This is an asychronous operation.  Use FaxReportStatus
    to determine when the send is completed.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    FileName        - File containing the TIFF-F FAX document.
    JobParams       - pointer to FAX_JOB_PARAM structure describing transmission
    FaxJobId        - receives job id for this transmission.


Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    PJOB_QUEUE JobQueue = NULL, JobQueueEntry = NULL;
    LPCWSTR UserName;
    WCHAR TifFileName[MAX_PATH];
    DWORD rc = ERROR_SUCCESS;

    //
    // do a security check
    //

    if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_SUBMIT )) {
        return ERROR_ACCESS_DENIED;
    }

    //
    // argument validation
    //
    if (!JobParams ||
        !FileName  ||
        !FaxJobId  ||
        (wcslen(FileName)+wcslen(FaxQueueDir)+2 > MAX_PATH)) {
        return ERROR_INVALID_PARAMETER;
    }

    if (JobParams->Reserved[0] == 0xfffffffe) {

        if (JobParams->Reserved[1] == 2) {
            if (JobParams->RecipientNumber == NULL) {
                return ERROR_INVALID_PARAMETER;
            }
        } else if (JobParams->Reserved[1] == 1) {
            if (FileName == NULL) {
                return ERROR_INVALID_PARAMETER;
            }
        }

    } else if (JobParams->CallHandle != 0) {

        if (FileName == NULL || JobParams->RecipientNumber == NULL) {
            return ERROR_INVALID_PARAMETER;
        }
    }

    //
    // get the client's user name
    //

    UserName = GetClientUserName();
    if (!UserName) {
        return GetLastError();
    }

    //
    // create a full path to the file
    //

    swprintf( TifFileName, L"%s\\%s", FaxQueueDir, FileName );

    // 
    // validate the tiff file
    //
    rc =  ValidateTiffFile(TifFileName);
    if (rc != ERROR_SUCCESS) {
        MemFree( (LPBYTE) UserName );
        return rc;
    }
    
    //
    // add the job to the queue
    //

    JobQueueEntry = AddJobQueueEntry(
        JT_SEND,
        TifFileName,
        JobParams,
        UserName,
        TRUE,
        NULL
        );

    MemFree( (LPBYTE) UserName );

    if (!JobQueueEntry) {
        return ERROR_INVALID_PARAMETER;
    }

    EnterCriticalSection( &CsJob) ;
    EnterCriticalSection( &CsQueue );
    __try {
        JobQueue = FindJobQueueEntryByJobQueueEntry(JobQueueEntry);

        if (!JobQueue) {
            __leave;
        }

        if (JobParams->Reserved[0] == 0xffffffff) {
            CreateFaxEvent( (DWORD)JobParams->Reserved[1], FEI_JOB_QUEUED, JobQueue->JobId );
        } else {
            CreateFaxEvent( 0, FEI_JOB_QUEUED, JobQueue->JobId );
        }

        *FaxJobId = JobQueue->JobId;
        rc = ERROR_SUCCESS;

    } __except (EXCEPTION_EXECUTE_HANDLER) {
        rc = GetExceptionCode();
        DebugPrint(( TEXT("FAX_SendDocument() crashed, ec=0x%08x"), rc ));        
    }

    LeaveCriticalSection( &CsQueue );
    LeaveCriticalSection( &CsJob );

    return(rc);
}


error_status_t
FAX_GetQueueFileName(
    IN  handle_t FaxHandle,
    OUT LPTSTR FileName,
    IN DWORD FileNameSize
    )
{
    WCHAR QueueFileName[MAX_PATH];
    LPWSTR p;
    RPC_STATUS ec;

    ec = RpcImpersonateClient(FaxHandle);

    if (ec != RPC_S_OK) {
        DebugPrint(( TEXT("RpcImpersonateClient failed, ec = %d\n"),ec ));
        return ec;
    }

    GenerateUniqueFileName( FaxQueueDir, TEXT("tif"), QueueFileName, sizeof(QueueFileName)/sizeof(WCHAR) );

    RpcRevertToSelf();

    p = wcsrchr( QueueFileName, L'\\' );
    if (p) {
        p += 1;
    } else {
        p = QueueFileName;
    }

    wcsncpy( FileName, p , FileNameSize );

    return 0;
}



error_status_t
FAX_EnumJobs(
    IN handle_t FaxHandle,
    OUT LPBYTE *Buffer,
    OUT LPDWORD BufferSize,
    OUT LPDWORD JobsReturned
    )

/*++

Routine Description:

    Enumerates jobs.

Arguments:

    FaxHandle   - FAX handle obtained from FaxConnectFaxServer.
    Buffer      - Buffer to hold the job information
    BufferSize  - Total size of the job info buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    PLIST_ENTRY Next;
    PJOB_QUEUE JobQueue;
    DWORD rVal = 0;
    ULONG_PTR Offset = 0;
    DWORD Size = 0;
    DWORD Count = 0;
    PFAX_JOB_ENTRYW JobEntry;


    if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!Buffer || !BufferSize || !JobsReturned)
        return ERROR_INVALID_PARAMETER;

    EnterCriticalSection( &CsJob) ;
    EnterCriticalSection( &CsQueue );

    Next = QueueListHead.Flink;
    while ((ULONG_PTR)Next != (ULONG_PTR)&QueueListHead) {
        JobQueue = CONTAINING_RECORD( Next, JOB_QUEUE, ListEntry );
        Next = JobQueue->ListEntry.Flink;
        // don't include broadcast owner jobs, we don't want user to see these
        if (!( JobQueue->BroadcastJob && JobQueue->BroadcastOwner == NULL ) ) {
            Count += 1;
            Size += sizeof(FAX_JOB_ENTRYW);
            Size += StringSize( JobQueue->UserName );
            Size += StringSize( JobQueue->JobParams.RecipientNumber );
            Size += StringSize( JobQueue->JobParams.RecipientName );
            Size += StringSize( JobQueue->JobParams.Tsid );
            Size += StringSize( JobQueue->JobParams.SenderName );
            Size += StringSize( JobQueue->JobParams.SenderCompany );
            Size += StringSize( JobQueue->JobParams.SenderDept );
            Size += StringSize( JobQueue->JobParams.BillingCode );
            Size += StringSize( JobQueue->JobParams.DeliveryReportAddress );
            Size += StringSize( JobQueue->JobParams.DocumentName );
        }
    }

    *BufferSize = Size;
    *Buffer = (LPBYTE) MemAlloc( Size );
    if (*Buffer == NULL) {
        LeaveCriticalSection( &CsQueue );
        LeaveCriticalSection( &CsJob );
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    Offset = sizeof(FAX_JOB_ENTRYW) * Count;
    JobEntry = (PFAX_JOB_ENTRYW) *Buffer;

    Next = QueueListHead.Flink;
    while ((ULONG_PTR)Next != (ULONG_PTR)&QueueListHead) {

        JobQueue = CONTAINING_RECORD( Next, JOB_QUEUE, ListEntry );
        Next = JobQueue->ListEntry.Flink;
        // don't include broadcast owner jobs, we don't want user to see these
        if (!( JobQueue->BroadcastJob && JobQueue->BroadcastOwner == NULL ) ) {

            JobEntry->SizeOfStruct = sizeof(FAX_JOB_ENTRYW);
            JobEntry->JobId                  = JobQueue->JobId;
            JobEntry->JobType                = JobQueue->JobType;
            JobEntry->QueueStatus            = JobQueue->JobStatus;

            if (JobQueue->JobEntry && JobQueue->JobEntry->LineInfo) {
                JobEntry->Status             = JobQueue->JobEntry->LineInfo->State;
            } else {
                JobEntry->Status             = 0;
            }
            JobEntry->ScheduleAction         = JobQueue->JobParams.ScheduleAction;
            JobEntry->DeliveryReportType     = JobQueue->DeliveryReportType;
            FileTimeToSystemTime((LPFILETIME) &JobQueue->ScheduleTime, &JobEntry->ScheduleTime);
            JobEntry->PageCount              = JobQueue->PageCount;
            JobEntry->Size                   = JobQueue->FileSize;

            StoreString(
                JobQueue->UserName,
                (PULONG_PTR)&JobEntry->UserName,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.RecipientNumber,
                (PULONG_PTR)&JobEntry->RecipientNumber,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.RecipientName,
                (PULONG_PTR)&JobEntry->RecipientName,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.DocumentName,
                (PULONG_PTR)&JobEntry->DocumentName,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.Tsid,
                (PULONG_PTR)&JobEntry->Tsid,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.SenderName,
                (PULONG_PTR)&JobEntry->SenderName,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.SenderCompany,
                (PULONG_PTR)&JobEntry->SenderCompany,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.SenderDept,
                (PULONG_PTR)&JobEntry->SenderDept,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.BillingCode,
                (PULONG_PTR)&JobEntry->BillingCode,
                *Buffer,
                &Offset
                );

            StoreString(
                JobQueue->JobParams.DeliveryReportAddress,
                (PULONG_PTR)&JobEntry->DeliveryReportAddress,
                *Buffer,
                &Offset
                );

            JobEntry += 1;
        }
    }

    LeaveCriticalSection( &CsQueue );
    LeaveCriticalSection( &CsJob );

    *JobsReturned = Count;

    return 0;
}

DWORD
GetJobSize(
    PJOB_QUEUE JobQueue
    )
{
    DWORD Size;


    Size = sizeof(FAX_JOB_ENTRYW);
    Size += StringSize( JobQueue->UserName );
    Size += StringSize( JobQueue->JobParams.RecipientNumber );
    Size += StringSize( JobQueue->JobParams.RecipientName );
    Size += StringSize( JobQueue->JobParams.Tsid );
    Size += StringSize( JobQueue->JobParams.SenderName );
    Size += StringSize( JobQueue->JobParams.SenderCompany );
    Size += StringSize( JobQueue->JobParams.SenderDept );
    Size += StringSize( JobQueue->JobParams.BillingCode );
    Size += StringSize( JobQueue->DeliveryReportAddress );
    Size += StringSize( JobQueue->JobParams.DocumentName );

    return Size;
}


VOID
GetJobData(
    LPBYTE JobBuffer,
    PFAX_JOB_ENTRYW FaxJobEntry,
    PJOB_QUEUE JobQueue,
    PULONG_PTR Offset
    )
{

    FaxJobEntry->SizeOfStruct           = sizeof (FAX_JOB_ENTRYW);
    FaxJobEntry->JobId                  = JobQueue->JobId;
    FaxJobEntry->JobType                = JobQueue->JobType;
    FaxJobEntry->QueueStatus            = JobQueue->JobStatus;
    FaxJobEntry->PageCount              = JobQueue->PageCount;
    FaxJobEntry->Size                   = JobQueue->FileSize;
    FaxJobEntry->ScheduleAction         = JobQueue->JobParams.ScheduleAction;
    FaxJobEntry->DeliveryReportType     = JobQueue->DeliveryReportType;

    //
    // copy the schedule time that the user orginally requested
    //
    FileTimeToSystemTime((LPFILETIME) &JobQueue->ScheduleTime, &FaxJobEntry->ScheduleTime);

    //
    // get the device status, this job might not be scheduled yet, though.
    //
    EnterCriticalSection(&CsJob);

    __try {
        if (JobQueue->JobEntry && JobQueue->JobEntry->LineInfo) {
            FaxJobEntry->Status = JobQueue->JobEntry->LineInfo->State;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        LeaveCriticalSection(&CsJob);
    }

    LeaveCriticalSection(&CsJob);


    StoreString( JobQueue->UserName,                  (PULONG_PTR)&FaxJobEntry->UserName,              JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.RecipientNumber, (PULONG_PTR)&FaxJobEntry->RecipientNumber,       JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.RecipientName,   (PULONG_PTR)&FaxJobEntry->RecipientName,         JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.Tsid,            (PULONG_PTR)&FaxJobEntry->Tsid,                  JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.SenderName,      (PULONG_PTR)&FaxJobEntry->SenderName,            JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.SenderCompany,   (PULONG_PTR)&FaxJobEntry->SenderCompany,         JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.SenderDept,      (PULONG_PTR)&FaxJobEntry->SenderDept,            JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.BillingCode,     (PULONG_PTR)&FaxJobEntry->BillingCode,           JobBuffer,  Offset );
    StoreString( JobQueue->DeliveryReportAddress,     (PULONG_PTR)&FaxJobEntry->DeliveryReportAddress, JobBuffer,  Offset );
    StoreString( JobQueue->JobParams.DocumentName,    (PULONG_PTR)&FaxJobEntry->DocumentName,          JobBuffer,  Offset );

    return;
}



error_status_t
FAX_GetJob(
    IN handle_t FaxHandle,
    IN DWORD JobId,
    OUT LPBYTE *Buffer,
    OUT LPDWORD BufferSize
    )
{
    PJOB_QUEUE JobQueue;
    ULONG_PTR Offset = sizeof(FAX_JOB_ENTRYW);
    DWORD Rval = 0;

    if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    EnterCriticalSection( &CsJob );
    EnterCriticalSection( &CsQueue );

    JobQueue = FindJobQueueEntry( JobId );
    // don't include broadcast owner jobs, we don't want user to see these
    if (!JobQueue || (JobQueue->BroadcastJob && JobQueue->BroadcastOwner == NULL) ) {
        Rval = ERROR_INVALID_PARAMETER;
        goto exit;
    }

    __try {
        *BufferSize = GetJobSize(JobQueue);

        *Buffer = MemAlloc( *BufferSize );
        if (!*Buffer) {
            Rval = ERROR_NOT_ENOUGH_MEMORY;
            goto exit;
        }

        GetJobData(*Buffer,(PFAX_JOB_ENTRYW) *Buffer,JobQueue,&Offset);

    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        Rval = GetExceptionCode();
    }

exit:
    LeaveCriticalSection( &CsQueue );
    LeaveCriticalSection( &CsJob );
    return Rval;

}

BOOL
UserOwnsJob(
    PJOB_QUEUE JobQueue
    )
{
    LPWSTR UserName = GetClientUserName();
    BOOL RetVal = FALSE;

    if (JobQueue && JobQueue->UserName && (wcscmp(UserName,JobQueue->UserName)==0) ) {
        RetVal = TRUE;
    }

    MemFree( UserName );

    return RetVal;
}



error_status_t
FAX_SetJob(
    IN handle_t FaxHandle,
    IN DWORD JobId,
    IN DWORD Command,
    IN const FAX_JOB_ENTRYW *JobEntry
    )
{
    PJOB_QUEUE JobQueue;
    DWORD Rval = 0;
    BOOL bAccess = TRUE;

    if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_MANAGE )) {
        bAccess = FALSE;
    }

    if (!JobEntry) {
        return ERROR_INVALID_PARAMETER;
    }

    //
    // handle abort case up here because we aquire must aquire additional critical sections to avoid deadlock
    //
    if (Command == JC_DELETE) {
        Rval = FAX_Abort(FaxHandle,JobId);
    } else {

        EnterCriticalSection( &CsQueue );

        JobQueue = FindJobQueueEntry( JobId );
        // don't include broadcast owner jobs, we don't want user to see these
        if (!JobQueue || (JobQueue->BroadcastJob && JobQueue->BroadcastOwner == NULL) ) {
            Rval = ERROR_INVALID_PARAMETER;
            goto exit;
        }

        if (!bAccess && !UserOwnsJob( JobQueue ) ) {
            Rval = ERROR_ACCESS_DENIED;
            goto exit;
        }

        switch (Command) {
            case JC_UNKNOWN:
                Rval = ERROR_INVALID_PARAMETER;
                goto exit;
                break;

/*
 * This case is handled above...
 *           case JC_DELETE:
 *               Rval = FAX_Abort(FaxHandle,JobId);
 *               break;
 */
            case JC_PAUSE:
                PauseJobQueueEntry( JobQueue );
                break;

            case JC_RESUME:
                ResumeJobQueueEntry( JobQueue );
                break;

            default:
                Rval = ERROR_INVALID_PARAMETER;
                goto exit;
                break;
        }

exit:
        LeaveCriticalSection( &CsQueue );
    }

    return Rval;
}


error_status_t
FAX_GetPageData(
    IN handle_t FaxHandle,
    IN DWORD JobId,
    OUT LPBYTE *Buffer,
    OUT LPDWORD BufferSize,
    OUT LPDWORD ImageWidth,
    OUT LPDWORD ImageHeight
    )
{
    PJOB_QUEUE JobQueue;
    LPBYTE TiffBuffer;

    if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_QUERY )) {
       return ERROR_ACCESS_DENIED;
   }

    if (!Buffer || !BufferSize || !ImageWidth || !ImageHeight) {
        return ERROR_INVALID_PARAMETER;
    }

    EnterCriticalSection( &CsQueue );

    JobQueue = FindJobQueueEntry( JobId );
    if (!JobQueue) {
        LeaveCriticalSection( &CsQueue );
        return ERROR_INVALID_PARAMETER;
    }

    if (JobQueue->JobType != JT_SEND) {
        LeaveCriticalSection( &CsQueue );
        return ERROR_INVALID_DATA;
    }

    TiffExtractFirstPage(
        JobQueue->FileName,
        &TiffBuffer,
        BufferSize,
        ImageWidth,
        ImageHeight
        );

    LeaveCriticalSection( &CsQueue );

    *Buffer = (LPBYTE) MemAlloc( *BufferSize );
    if (*Buffer == NULL) {
        VirtualFree( TiffBuffer, *BufferSize, MEM_RELEASE);
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    CopyMemory( *Buffer, TiffBuffer, *BufferSize );

    VirtualFree( TiffBuffer, *BufferSize, MEM_RELEASE);

    return 0;
}


error_status_t
FAX_GetDeviceStatus(
    IN HANDLE FaxPortHandle,
    OUT LPBYTE *StatusBuffer,
    OUT LPDWORD BufferSize
    )

/*++

Routine Description:

    Obtains a status report for the specified FAX job.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    StatusBuffer    - receives FAX_DEVICE_STATUS pointer
    BufferSize      - Pointer to the size of this structure

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    DWORD rVal = 0;
    ULONG_PTR Offset;
    PFAX_DEVICE_STATUS FaxStatus;
    PLINE_INFO LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    __try {

        EnterCriticalSection( &CsJob );
        EnterCriticalSection( &CsLine );

        //
        // count the number of bytes required
        //

        *BufferSize  = sizeof(FAX_DEVICE_STATUS);
        *BufferSize += StringSize( LineInfo->DeviceName );
        *BufferSize += StringSize( LineInfo->Csid );

        if (LineInfo->JobEntry) {

            *BufferSize += StringSize( LineInfo->JobEntry->PhoneNumber );
            *BufferSize += StringSize( LineInfo->JobEntry->FaxStatus.CallerId );
            *BufferSize += StringSize( LineInfo->JobEntry->FaxStatus.RoutingInfo );
            *BufferSize += StringSize( LineInfo->JobEntry->FaxStatus.CSI );
            *BufferSize += StringSize( LineInfo->JobEntry->JobParam.SenderName );
            *BufferSize += StringSize( LineInfo->JobEntry->JobParam.RecipientName );
            *BufferSize += StringSize( LineInfo->JobEntry->UserName );

        }

        *StatusBuffer = (LPBYTE) MemAlloc( *BufferSize );
        if (*StatusBuffer == NULL) {
            rVal = ERROR_NOT_ENOUGH_MEMORY;
            goto exit;
        }

        FaxStatus = (PFAX_DEVICE_STATUS) *StatusBuffer;
        Offset = sizeof(FAX_DEVICE_STATUS);

        FaxStatus->SizeOfStruct = sizeof(FAX_DEVICE_STATUS);
        FaxStatus->Status       = LineInfo->State;
        FaxStatus->DeviceId     = LineInfo->PermanentLineID;
        FaxStatus->StatusString = NULL;

        StoreString(
            LineInfo->DeviceName,
            (PULONG_PTR)&FaxStatus->DeviceName,
            *StatusBuffer,
            &Offset
            );

        StoreString(
            LineInfo->Csid,
            (PULONG_PTR)&FaxStatus->Csid,
            *StatusBuffer,
            &Offset
            );

        if (LineInfo->JobEntry) {

            FaxStatus->JobType        = LineInfo->JobEntry->JobType;
            FaxStatus->TotalPages     = LineInfo->JobEntry->PageCount;
            FaxStatus->Size           = FaxStatus->JobType == JT_SEND ?
                                        LineInfo->JobEntry->FileSize :
                                        0; //meaningful for an outbound job only
            FaxStatus->DocumentName   = NULL;

            ZeroMemory( &FaxStatus->SubmittedTime, sizeof(FILETIME) );

            StoreString(
                LineInfo->JobEntry->JobParam.SenderName,
                (PULONG_PTR)&FaxStatus->SenderName,
                *StatusBuffer,
                &Offset
                );

            StoreString(
                LineInfo->JobEntry->JobParam.RecipientName,
                (PULONG_PTR)&FaxStatus->RecipientName,
                *StatusBuffer,
                &Offset
                );

            FaxStatus->CurrentPage = LineInfo->JobEntry->FaxStatus.PageCount;

			CopyMemory(&FaxStatus->StartTime, &LineInfo->JobEntry->StartTime, sizeof(FILETIME));

            StoreString(
                LineInfo->JobEntry->PhoneNumber,
                (PULONG_PTR)&FaxStatus->PhoneNumber,
                *StatusBuffer,
                &Offset
                );

            StoreString(
                LineInfo->JobEntry->FaxStatus.CallerId,
                (PULONG_PTR)&FaxStatus->CallerId,
                *StatusBuffer,
                &Offset
                );

            StoreString(
                LineInfo->JobEntry->FaxStatus.RoutingInfo,
                (PULONG_PTR)&FaxStatus->RoutingString,
                *StatusBuffer,
                &Offset
                );

            StoreString(
                LineInfo->JobEntry->FaxStatus.CSI,
                (PULONG_PTR)&FaxStatus->Tsid,
                *StatusBuffer,
                &Offset
                );

            StoreString(
                LineInfo->JobEntry->UserName,
                (PULONG_PTR)&FaxStatus->UserName,
                *StatusBuffer,
                &Offset
                );

        } else {

            FaxStatus->PhoneNumber    = NULL;
            FaxStatus->CallerId       = NULL;
            FaxStatus->RoutingString  = NULL;
            FaxStatus->CurrentPage    = 0;
            FaxStatus->JobType        = 0;
            FaxStatus->TotalPages     = 0;
            FaxStatus->Size           = 0;
            FaxStatus->DocumentName   = NULL;
            FaxStatus->SenderName     = NULL;
            FaxStatus->RecipientName  = NULL;
            FaxStatus->Tsid           = NULL;

            ZeroMemory( &FaxStatus->SubmittedTime, sizeof(FILETIME) );
            ZeroMemory( &FaxStatus->StartTime,     sizeof(FILETIME) );

        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

exit:
    LeaveCriticalSection( &CsLine );
    LeaveCriticalSection( &CsJob );
    return rVal;
}


error_status_t
FAX_Abort(
   IN handle_t hBinding,
   IN DWORD JobId
   )

/*++

Routine Description:

    Abort the specified FAX job.  All outstanding FAX
    operations are terminated.

Arguments:

    hBinding        - FAX handle obtained from FaxConnectFaxServer.
    JobId           - FAX job Id

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
   PJOB_QUEUE JobQueueEntry;
   BOOL bAccess = TRUE;
   DWORD Rval;


   if (!FaxSvcAccessCheck( SEC_JOB_SET, FAX_JOB_MANAGE )) {
       bAccess = FALSE;
   }

   EnterCriticalSection( &CsJob) ;
   EnterCriticalSection( &CsQueue );

   JobQueueEntry = FindJobQueueEntry( JobId );
   if (!JobQueueEntry) {
      Rval = ERROR_INVALID_PARAMETER;
      goto exit;
   }

   // don't include broadcast owner jobs, we don't want user to see these
   if (!JobQueueEntry || (JobQueueEntry->BroadcastJob && JobQueueEntry->BroadcastOwner == NULL) ) {
       Rval = ERROR_INVALID_PARAMETER;
       goto exit;
   }

   if (!bAccess && !UserOwnsJob( JobQueueEntry ) ) {
       Rval = ERROR_ACCESS_DENIED;
       goto exit;
   }

   //
   // abort the job if it's in progress
   //
   if (((JobQueueEntry->JobStatus & JS_INPROGRESS) == JS_INPROGRESS)  &&
       ( JobQueueEntry->JobType == JT_SEND ||
         JobQueueEntry->JobType == JT_RECEIVE )) {

      __try {
          // signal the event we may be waiting on
          if (JobQueueEntry->JobEntry->hCallHandleEvent) {
              JobQueueEntry->JobEntry->LineInfo->HandoffCallHandle = 0;
              SetEvent(JobQueueEntry->JobEntry->hCallHandleEvent);
          }
          JobQueueEntry->JobEntry->Aborting = TRUE;
          JobQueueEntry->JobStatus = JS_DELETING;
          CreateFaxEvent(JobQueueEntry->JobEntry->LineInfo->PermanentLineID,
                         FEI_ABORTING,
                         JobId);

          DebugPrint(( TEXT("Attempting FaxDevAbort for job\n") ));
          JobQueueEntry->JobEntry->LineInfo->Provider->FaxDevAbortOperation(
              (HANDLE) JobQueueEntry->JobEntry->InstanceData );
      }
      __except (EXCEPTION_EXECUTE_HANDLER) {
          JobQueueEntry->JobEntry->ErrorCode = GetExceptionCode();
      }



   } else {
       RemoveJobQueueEntry( JobQueueEntry );
   }

   Rval = 0;

exit:
   LeaveCriticalSection( &CsQueue );
   LeaveCriticalSection( &CsJob );

   return Rval;

}


error_status_t
FAX_GetConfiguration(
    IN  handle_t FaxHandle,
    OUT LPBYTE *Buffer,
    IN  LPDWORD BufferSize
    )

/*++

Routine Description:

    Retrieves the FAX configuration from the FAX server.
    The SizeOfStruct in the FaxConfig argument MUST be
    set to a value == sizeof(FAX_CONFIGURATION).  If the BufferSize
    is not big enough, return an error and set BytesNeeded to the
    required size.

Arguments:

    FaxHandle   - FAX handle obtained from FaxConnectFaxServer.
    Buffer      - Pointer to a FAX_CONFIGURATION structure.
    BufferSize  - Size of Buffer
    BytesNeeded - Number of bytes needed

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    error_status_t rVal = ERROR_SUCCESS;
    PFAX_CONFIGURATION FaxConfig;
    ULONG_PTR Offset;


    if (!FaxSvcAccessCheck( SEC_CONFIG_QUERY, FAX_CONFIG_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!Buffer || !BufferSize)
        return ERROR_INVALID_PARAMETER;

    //
    // count up the number of bytes needed
    //

    *BufferSize = sizeof(FAX_CONFIGURATION);
    Offset = sizeof(FAX_CONFIGURATION);

    if (InboundProfileName) {
        *BufferSize += StringSize( InboundProfileName );
    }

    if (ArchiveDirectory) {
        *BufferSize += StringSize( ArchiveDirectory );
    }

    *Buffer = MemAlloc( *BufferSize );
    if (*Buffer == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    FaxConfig = (PFAX_CONFIGURATION)*Buffer;

    FaxConfig->SizeOfStruct          = sizeof(FAX_CONFIGURATION);
    FaxConfig->Retries               = FaxSendRetries;
    FaxConfig->RetryDelay            = FaxSendRetryDelay;
    FaxConfig->DirtyDays             = FaxDirtyDays;
    FaxConfig->Branding              = FaxUseBranding;
    FaxConfig->UseDeviceTsid         = FaxUseDeviceTsid;
    FaxConfig->ServerCp              = ServerCp;
    FaxConfig->StartCheapTime.Hour   = StartCheapTime.Hour;
    FaxConfig->StartCheapTime.Minute = StartCheapTime.Minute;
    FaxConfig->StopCheapTime.Hour    = StopCheapTime.Hour;
    FaxConfig->StopCheapTime.Minute  = StopCheapTime.Minute;
    FaxConfig->ArchiveOutgoingFaxes  = ArchiveOutgoingFaxes;
    FaxConfig->PauseServerQueue      = QueuePaused;

    StoreString(
        ArchiveDirectory,
        (PULONG_PTR)&FaxConfig->ArchiveDirectory,
        *Buffer,
        &Offset
        );

    StoreString(
        InboundProfileName,
        (PULONG_PTR)&FaxConfig->InboundProfile,
        *Buffer,
        &Offset
        );

    return rVal;
}



error_status_t
FAX_SetConfiguration(
    IN handle_t FaxHandle,
    IN const FAX_CONFIGURATION *FaxConfig
    )

/*++

Routine Description:

    Changes the FAX configuration on the FAX server.
    The SizeOfStruct in the FaxConfig argument MUST be
    set to a value == sizeof(FAX_CONFIGURATION).

Arguments:

    FaxHandle   - FAX handle obtained from FaxConnectFaxServer.
    Buffer      - Pointer to a FAX_CONFIGURATION structure.
    BufferSize  - Size of Buffer

Return Value:

    TRUE    - Success
    FALSE   - Failure, call GetLastError() for more error information.

--*/

{
    error_status_t rVal = ERROR_SUCCESS;
    LPTSTR s;


    if (!FaxSvcAccessCheck( SEC_CONFIG_SET, FAX_CONFIG_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!FaxConfig || FaxConfig->SizeOfStruct != sizeof(FAX_CONFIGURATION)) {
        return ERROR_INVALID_PARAMETER;
    }

    if (FaxConfig->ArchiveOutgoingFaxes) {
       //
       // make sure they give us something valid for a path if they want us to archive
       //
       if (!FaxConfig->ArchiveDirectory) {
          return ERROR_INVALID_PARAMETER;
       }
    }

    __try {
        if (FaxConfig->InboundProfile) {
            if (!InboundProfileName ||
                wcscmp(FaxConfig->InboundProfile,InboundProfileName) != 0) {
                //
                // profile has changed, let's use the new one.
                //
                InboundProfileInfo = AddNewMapiProfile( FaxConfig->InboundProfile, TRUE, FALSE );
                if (!InboundProfileInfo) {
                    return ERROR_INVALID_DATA;
                }
            }
        }

        s = (LPTSTR) InterlockedExchangePointer(
            (LPVOID *)&InboundProfileName,
            FaxConfig->InboundProfile ? (PVOID)StringDup( FaxConfig->InboundProfile ) : NULL
            );
        if (s) {
            MemFree( s );
        }

        //
        // change the values that the server is currently using
        //

        InterlockedExchange( &FaxUseDeviceTsid,      FaxConfig->UseDeviceTsid );
        InterlockedExchange( &FaxUseBranding,        FaxConfig->Branding );
        InterlockedExchange( &ServerCp,              FaxConfig->ServerCp );
        InterlockedExchange( &ArchiveOutgoingFaxes,  FaxConfig->ArchiveOutgoingFaxes );
        InterlockedExchange( &FaxSendRetries,        FaxConfig->Retries );
        InterlockedExchange( &FaxDirtyDays,          FaxConfig->DirtyDays );
        InterlockedExchange( &FaxSendRetryDelay,     FaxConfig->RetryDelay );

        if ( (MAKELONG(StartCheapTime.Hour,StartCheapTime.Minute) != MAKELONG(FaxConfig->StartCheapTime.Hour,FaxConfig->StartCheapTime.Minute)) ||
             (MAKELONG(StopCheapTime.Hour,StopCheapTime.Minute)  != MAKELONG(FaxConfig->StopCheapTime.Hour, FaxConfig->StopCheapTime.Minute )) ) {
            InterlockedExchange( (LPLONG)&StartCheapTime, MAKELONG(FaxConfig->StartCheapTime.Hour,FaxConfig->StartCheapTime.Minute) );
            InterlockedExchange( (LPLONG)&StopCheapTime, MAKELONG(FaxConfig->StopCheapTime.Hour,FaxConfig->StopCheapTime.Minute) );
            SortJobQueue();
        }

        s = (LPTSTR) InterlockedExchangePointer(
            (LPVOID *)&ArchiveDirectory,
            FaxConfig->ArchiveDirectory ? (PVOID)StringDup( FaxConfig->ArchiveDirectory ) : NULL
            );
        if (s) {
            MemFree( s );
        }

        if (FaxConfig->PauseServerQueue) {
            PauseServerQueue();
        } else {
            ResumeServerQueue();
        }

        //
        // change the values in the registry
        //

        SetFaxGlobalsRegistry( (PFAX_CONFIGURATION) FaxConfig );

    } __except (EXCEPTION_EXECUTE_HANDLER) {
          return GetExceptionCode();
    }

    return ERROR_SUCCESS;
}


DWORD
GetPortSize(
    PLINE_INFO LineInfo
    )
{
    DWORD Size;


    Size = sizeof(FAX_PORT_INFOW);
    Size += StringSize( LineInfo->DeviceName );
    Size += StringSize( LineInfo->Tsid );
    Size += StringSize( LineInfo->Csid );

    return Size;
}


VOID
GetPortData(
    LPBYTE PortBuffer,
    PFAX_PORT_INFOW PortInfo,
    PLINE_INFO LineInfo,
    PULONG_PTR Offset
    )
{
    PortInfo->SizeOfStruct = sizeof(FAX_PORT_INFOW);
    PortInfo->DeviceId   = LineInfo->PermanentLineID;
    PortInfo->State      = LineInfo->State;
    PortInfo->Flags      = LineInfo->Flags & 0x0fffffff;
    PortInfo->Rings      = LineInfo->RingsForAnswer;
    PortInfo->Priority   = LineInfo->Priority;

    StoreString( LineInfo->DeviceName,  (PULONG_PTR)&PortInfo->DeviceName,  PortBuffer, Offset );
    StoreString( LineInfo->Tsid,        (PULONG_PTR)&PortInfo->Tsid,        PortBuffer, Offset );
    StoreString( LineInfo->Csid,        (PULONG_PTR)&PortInfo->Csid,        PortBuffer, Offset );

    return;
}


error_status_t
FAX_EnumPorts(
    handle_t    FaxHandle,
    LPBYTE      *PortBuffer,
    LPDWORD     BufferSize,
    LPDWORD     PortsReturned
    )

/*++

Routine Description:

    Enumerates all of the FAX devices attached to the
    FAX server.  The port state information is returned
    for each device.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer
    PortBuffer      - Buffer to hold the port information
    BufferSize      - Total size of the port info buffer
    PortsReturned   - The number of ports in the buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    DWORD rVal = 0;
    PLIST_ENTRY Next;
    PLINE_INFO LineInfo;
    DWORD i;
    ULONG_PTR Offset;
    DWORD FaxDevices;
    PFAX_PORT_INFOW PortInfo;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    __try {

        EnterCriticalSection( &CsLine );

        if (!PortsReturned) {
            rVal = ERROR_INVALID_PARAMETER;
            goto exit;
        }

        if (!TapiLinesListHead.Flink) {
            rVal = ERROR_INVALID_PARAMETER;
            goto exit;
        }

        Next = TapiLinesListHead.Flink;

        *PortsReturned = 0;
        *BufferSize = 0;
        FaxDevices = 0;

        //
        // count the number of bytes required
        //

        *BufferSize = 0;

        while ((ULONG_PTR)Next != (ULONG_PTR)&TapiLinesListHead) {

            LineInfo = CONTAINING_RECORD( Next, LINE_INFO, ListEntry );
            Next = LineInfo->ListEntry.Flink;

            if (LineInfo->PermanentLineID && LineInfo->DeviceName) {
                *BufferSize += sizeof(PFAX_PORT_INFOW);
                *BufferSize += GetPortSize( LineInfo );
                FaxDevices += 1;
            }

        }

        *PortBuffer = (LPBYTE) MemAlloc( *BufferSize );
        if (*PortBuffer == NULL) {
            rVal = ERROR_NOT_ENOUGH_MEMORY;
            goto exit;
        }

        PortInfo = (PFAX_PORT_INFOW) *PortBuffer;
        Offset = FaxDevices * sizeof(FAX_PORT_INFOW);

        Next = TapiLinesListHead.Flink;
        i = 0;

        while ((ULONG_PTR)Next != (ULONG_PTR)&TapiLinesListHead) {

            LineInfo = CONTAINING_RECORD( Next, LINE_INFO, ListEntry );
            Next = LineInfo->ListEntry.Flink;

            if (LineInfo->PermanentLineID && LineInfo->DeviceName) {

                GetPortData(
                    *PortBuffer,
                    &PortInfo[i],
                    LineInfo,
                    &Offset
                    );
            }
            i++;
        }

        //
        // set the device count
        //

        *PortsReturned = FaxDevices;

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

exit:
    LeaveCriticalSection( &CsLine );
    return rVal;
}


error_status_t
FAX_GetPort(
    HANDLE FaxPortHandle,
    LPBYTE *PortBuffer,
    LPDWORD BufferSize
    )

/*++

Routine Description:

    Returns port status information for a requested port.
    The device id passed in should be optained from FAXEnumPorts.

Arguments:

    FaxHandle   - FAX handle obtained from FaxConnectFaxServer.
    DeviceId    - TAPI device id
    PortBuffer  - Buffer to hold the port information
    BufferSize  - Total size of the port info buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    PLINE_INFO LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    DWORD rVal = 0;
    ULONG_PTR Offset;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    EnterCriticalSection( &CsLine );

    __try {

        //
        // calculate the required buffer size
        //

        *BufferSize = GetPortSize( LineInfo );

        *PortBuffer = (LPBYTE) MemAlloc( *BufferSize );
        if (*PortBuffer == NULL) {
            rVal = ERROR_NOT_ENOUGH_MEMORY;
            _leave;
        }

        Offset = sizeof(FAX_PORT_INFOW);

        GetPortData(
            *PortBuffer,
            (PFAX_PORT_INFO)*PortBuffer,
            LineInfo,
            &Offset
            );

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

    LeaveCriticalSection( &CsLine );
    return rVal;
}


error_status_t
FAX_SetPort(
    HANDLE FaxPortHandle,
    const FAX_PORT_INFOW *PortInfo
    )

/*++

Routine Description:

    Changes the port capability mask.  This allows the caller to
    enable or disable sending & receiving on a port basis.

Arguments:

    FaxHandle   - FAX handle obtained from FaxConnectFaxServer.
    PortBuffer  - Buffer to hold the port information
    BufferSize  - Total size of the port info buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    DWORD rVal = 0;
    DWORD flags = 0;
    PLINE_INFO LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    DWORD totalDevices;
    BOOL SendEnabled = FALSE;


    if (!FaxSvcAccessCheck( SEC_PORT_SET, FAX_PORT_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    EnterCriticalSection( &CsJob );
    EnterCriticalSection( &CsLine );

    __try {

        if (PortInfo->SizeOfStruct != sizeof(FAX_PORT_INFOW)) {
            rVal = ERROR_INVALID_PARAMETER;
            _leave;
        }

        //
        // HACK: we allow the ring count to be set even if the line is in use so that systray will work.  we don't allow
        //  the user to change things like CSID/TSID or tapi related information since that cannot change until the call
        //  transaction is complete.
        //
        LineInfo->RingsForAnswer = PortInfo->Rings;

        if (LineInfo->JobEntry) {

            //
            // changing a line while there is an outstanding
            // job is not allowed
            //

            rVal = ERROR_DEVICE_IN_USE;
            _leave;
        }

        if (LineInfo->Flags & 0x80000000) {
            _leave;
        }

        flags = PortInfo->Flags & (FPF_RECEIVE | FPF_SEND | FPF_VIRTUAL);

        //
        // first change the real time data that the server is using
        //

        if ((!(LineInfo->Flags & FPF_RECEIVE)) && (flags & FPF_RECEIVE)) {
            if (!OpenTapiLine( LineInfo )) {
                DebugPrint(( TEXT("Could not get an open tapi line, FAX_SetPort() failed") ));
            } else {
                InterlockedIncrement( &ConnectionCount );
            }
        } else if ((LineInfo->Flags & FPF_RECEIVE) && (!(flags & FPF_RECEIVE))) {
            EnterCriticalSection( &CsLine );
            if (LineInfo->hLine) {
                lineClose( LineInfo->hLine );
                LineInfo->hLine = 0;
                InterlockedDecrement( &ConnectionCount );
            }
            LeaveCriticalSection( &CsLine );
        }

        if (!(LineInfo->Flags & FPF_SEND) && (flags & FPF_SEND)) {
            SendEnabled = TRUE;
        }

        LineInfo->Flags = (LineInfo->Flags & ~FPF_CLIENT_BITS) | flags;
        LineInfo->RingsForAnswer = PortInfo->Rings;
        //
        // make sure the user sets a reasonable priority
        //
        totalDevices = GetFaxDeviceCount();
        if (PortInfo->Priority <= totalDevices) {
            LineInfo->Priority = PortInfo->Priority ;
        }

        if (PortInfo->Tsid) {
            MemFree( LineInfo->Tsid );
            LineInfo->Tsid = StringDup( PortInfo->Tsid );
        }
        if (PortInfo->Csid) {
            MemFree( LineInfo->Csid );
            LineInfo->Csid = StringDup( PortInfo->Csid );
        }

        SortDevicePriorities();

        //
        // now change the registry so it sticks
        // (need to change all devices, since the priority may have changed)
        //
        CommitDeviceChanges();

        //
        // update virtual devices if they changed
        //
        UpdateVirtualDevices();

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

    LeaveCriticalSection( &CsLine );
    LeaveCriticalSection( &CsJob );

    if (SendEnabled && JobQueueSemaphore) {
        ReleaseSemaphore( JobQueueSemaphore, 1, NULL );
    }

    return rVal;
}


typedef struct _ENUM_CONTEXT {
    DWORD               Function;
    DWORD               Size;
    ULONG_PTR            Offset;
    PLINE_INFO          LineInfo;
    PFAX_ROUTING_METHOD RoutingInfoMethod;
} ENUM_CONTEXT, *PENUM_CONTEXT;


BOOL CALLBACK
RoutingMethodEnumerator(
    PROUTING_METHOD RoutingMethod,
    PENUM_CONTEXT EnumContext
    )
{
    LPWSTR GuidString;

    //
    // we only access read-only static data in the LINE_INFO structure.
    // make sure that this access is protected if you access dynamic
    // data in the future.
    //

    if (EnumContext->Function == 1) {

        EnumContext->Size += sizeof(FAX_ROUTING_METHOD);

        StringFromIID( &RoutingMethod->Guid, &GuidString );

        EnumContext->Size += StringSize( GuidString );
        EnumContext->Size += StringSize( EnumContext->LineInfo->DeviceName );
        EnumContext->Size += StringSize( RoutingMethod->FunctionName );
        EnumContext->Size += StringSize( RoutingMethod->FriendlyName );
        EnumContext->Size += StringSize( RoutingMethod->RoutingExtension->ImageName );
        EnumContext->Size += StringSize( RoutingMethod->RoutingExtension->FriendlyName );

        CoTaskMemFree( GuidString );

        return TRUE;
    }

    if (EnumContext->Function == 2) {

        StringFromIID( &RoutingMethod->Guid, &GuidString );

        EnumContext->RoutingInfoMethod[EnumContext->Size].SizeOfStruct = sizeof(FAX_ROUTING_METHOD);
        EnumContext->RoutingInfoMethod[EnumContext->Size].DeviceId = EnumContext->LineInfo->PermanentLineID;

        __try {
            EnumContext->RoutingInfoMethod[EnumContext->Size].Enabled =
                RoutingMethod->RoutingExtension->FaxRouteDeviceEnable( GuidString, EnumContext->LineInfo->PermanentLineID, QUERY_STATUS );
        } __except (EXCEPTION_EXECUTE_HANDLER) {
            EnumContext->RoutingInfoMethod[EnumContext->Size].Enabled = FALSE;
        }

        StoreString(
            EnumContext->LineInfo->DeviceName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].DeviceName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            GuidString,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].Guid,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->FriendlyName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].FriendlyName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->FunctionName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].FunctionName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->RoutingExtension->ImageName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].ExtensionImageName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->RoutingExtension->FriendlyName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].ExtensionFriendlyName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        EnumContext->Size += 1;
        CoTaskMemFree( GuidString );

        return TRUE;
    }

    return FALSE;
}


error_status_t
FAX_EnumRoutingMethods(
    IN HANDLE FaxPortHandle,
    OUT LPBYTE *RoutingInfoBuffer,
    OUT LPDWORD RoutingInfoBufferSize,
    OUT LPDWORD MethodsReturned
    )
{
    PLINE_INFO      LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    ENUM_CONTEXT    EnumContext;
    DWORD           CountMethods;


    //
    // verify that the client as access rights
    //

    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!RoutingInfoBuffer || !RoutingInfoBufferSize || !MethodsReturned) {
        return ERROR_INVALID_PARAMETER;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    //
    // note that the called routines are protected so we don't have any protection here
    //

    //
    // compute the required size of the buffer
    //

    EnumContext.Function = 1;
    EnumContext.Size = 0;
    EnumContext.Offset = 0;
    EnumContext.LineInfo = LineInfo;
    EnumContext.RoutingInfoMethod = NULL;

    CountMethods = EnumerateRoutingMethods( RoutingMethodEnumerator, &EnumContext );
    if (CountMethods == 0) {
        return ERROR_INVALID_FUNCTION;
    }

    //
    // allocate the buffer
    //

    *RoutingInfoBufferSize = EnumContext.Size;
    *RoutingInfoBuffer = (LPBYTE) MemAlloc( *RoutingInfoBufferSize );
    if (*RoutingInfoBuffer == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // fill the buffer with the data
    //

    EnumContext.Function = 2;
    EnumContext.Size = 0;
    EnumContext.Offset = sizeof(FAX_ROUTING_METHODW) * CountMethods;
    EnumContext.LineInfo = LineInfo;
    EnumContext.RoutingInfoMethod = (PFAX_ROUTING_METHOD) *RoutingInfoBuffer;

    if (!EnumerateRoutingMethods( RoutingMethodEnumerator, &EnumContext )) {
        MemFree( *RoutingInfoBuffer );
        *RoutingInfoBuffer = NULL;
        *RoutingInfoBufferSize = 0;
        return ERROR_INVALID_FUNCTION;
    }

    *MethodsReturned = CountMethods;


    return 0;
}


error_status_t
FAX_EnableRoutingMethod(
    IN HANDLE FaxPortHandle,
    IN LPCWSTR RoutingGuidString,
    IN BOOL Enabled
    )
{
    extern CRITICAL_SECTION CsRouting;
    error_status_t  ec = 0;
    PLINE_INFO      LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    PROUTING_METHOD RoutingMethod;


    //
    // verify that the client as access rights
    //

    if (!FaxSvcAccessCheck( SEC_PORT_SET, FAX_PORT_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    if (!RoutingGuidString)
        return ERROR_INVALID_PARAMETER;

    EnterCriticalSection( &CsRouting );

    //
    // get the routing method
    //

    RoutingMethod = FindRoutingMethodByGuid( RoutingGuidString );
    if (!RoutingMethod) {
        LeaveCriticalSection( &CsRouting );
        return ERROR_INVALID_DATA;
    }

    //
    // enable/disable the routing method for this device
    //

    __try {
        RoutingMethod->RoutingExtension->FaxRouteDeviceEnable(
                                             (LPWSTR)RoutingGuidString,
                                             LineInfo->PermanentLineID,
                                             Enabled ? STATUS_ENABLE : STATUS_DISABLE);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        ec = GetExceptionCode();
    }

    LeaveCriticalSection( &CsRouting );

    return ec;
}


typedef struct _ENUM_GLOBALCONTEXT {
    DWORD               Function;
    DWORD               Size;
    ULONG_PTR            Offset;
    PFAX_GLOBAL_ROUTING_INFO RoutingInfoMethod;
} ENUM_GLOBALCONTEXT, *PENUM_GLOBALCONTEXT;


BOOL CALLBACK
GlobalRoutingInfoMethodEnumerator(
    PROUTING_METHOD RoutingMethod,
    PENUM_GLOBALCONTEXT EnumContext
    )
{
    LPWSTR GuidString;


    if (EnumContext->Function == 1) {

        EnumContext->Size += sizeof(FAX_GLOBAL_ROUTING_INFO);

        StringFromIID( &RoutingMethod->Guid, &GuidString );

        EnumContext->Size += StringSize( GuidString );
        EnumContext->Size += StringSize( RoutingMethod->FunctionName );
        EnumContext->Size += StringSize( RoutingMethod->FriendlyName );
        EnumContext->Size += StringSize( RoutingMethod->RoutingExtension->ImageName );
        EnumContext->Size += StringSize( RoutingMethod->RoutingExtension->FriendlyName );

        CoTaskMemFree( GuidString );

        return TRUE;
    }

    if (EnumContext->Function == 2) {

        StringFromIID( &RoutingMethod->Guid, &GuidString );

        EnumContext->RoutingInfoMethod[EnumContext->Size].SizeOfStruct = sizeof(FAX_GLOBAL_ROUTING_INFO);

        EnumContext->RoutingInfoMethod[EnumContext->Size].Priority = RoutingMethod->Priority;


        StoreString(
            GuidString,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].Guid,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->FriendlyName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].FriendlyName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->FunctionName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].FunctionName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->RoutingExtension->ImageName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].ExtensionImageName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        StoreString(
            RoutingMethod->RoutingExtension->FriendlyName,
            (PULONG_PTR)&EnumContext->RoutingInfoMethod[EnumContext->Size].ExtensionFriendlyName,
            (LPBYTE)EnumContext->RoutingInfoMethod,
            &EnumContext->Offset
            );

        EnumContext->Size += 1;
        CoTaskMemFree( GuidString );

        return TRUE;
    }

    return FALSE;
}



error_status_t
FAX_EnumGlobalRoutingInfo(
    IN handle_t FaxHandle ,
    OUT LPBYTE *RoutingInfoBuffer,
    OUT LPDWORD RoutingInfoBufferSize,
    OUT LPDWORD MethodsReturned
    )
{

    DWORD           CountMethods;
    ENUM_GLOBALCONTEXT EnumContext;


    //
    // verify that the client as access rights
    //

    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_CONFIG_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!RoutingInfoBuffer || !RoutingInfoBufferSize || !MethodsReturned)
        return ERROR_INVALID_PARAMETER;

    //
    // compute the required size of the buffer
    //

    EnumContext.Function = 1;
    EnumContext.Size = 0;
    EnumContext.Offset = 0;
    EnumContext.RoutingInfoMethod = NULL;

    CountMethods = EnumerateRoutingMethods( GlobalRoutingInfoMethodEnumerator, &EnumContext );
    if (CountMethods == 0) {
        return ERROR_INVALID_FUNCTION;
    }

    //
    // allocate the buffer
    //

    *RoutingInfoBufferSize = EnumContext.Size;
    *RoutingInfoBuffer = (LPBYTE) MemAlloc( *RoutingInfoBufferSize );
    if (*RoutingInfoBuffer == NULL) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    //
    // fill the buffer with the data
    //

    EnumContext.Function = 2;
    EnumContext.Size = 0;
    EnumContext.Offset = sizeof(FAX_GLOBAL_ROUTING_INFOW) * CountMethods;
    EnumContext.RoutingInfoMethod = (PFAX_GLOBAL_ROUTING_INFO) *RoutingInfoBuffer;

    if (!EnumerateRoutingMethods( GlobalRoutingInfoMethodEnumerator, &EnumContext )) {
        MemFree( *RoutingInfoBuffer );
        *RoutingInfoBuffer = NULL;
        *RoutingInfoBufferSize = 0;
        return ERROR_INVALID_FUNCTION;
    }

    *MethodsReturned = CountMethods;

    return 0;
}


error_status_t
FAX_SetGlobalRoutingInfo(
    IN HANDLE FaxHandle,
    IN const FAX_GLOBAL_ROUTING_INFOW *RoutingInfo
    )
{
    extern CRITICAL_SECTION CsRouting;
    error_status_t  ec = 0;

    PROUTING_METHOD RoutingMethod;

    //
    // verify that the client as access rights
    //

    if (!FaxSvcAccessCheck( SEC_CONFIG_SET, FAX_CONFIG_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!RoutingInfo) {
        return ERROR_INVALID_PARAMETER;
    }

    __try {
        if (RoutingInfo->SizeOfStruct != sizeof(FAX_GLOBAL_ROUTING_INFOW)) {
            return ERROR_INVALID_PARAMETER;
        }

        EnterCriticalSection( &CsRouting );

        //
        // get the routing method
        //

        RoutingMethod = FindRoutingMethodByGuid( RoutingInfo->Guid );
        if (!RoutingMethod) {
            LeaveCriticalSection( &CsRouting );
            return ERROR_INVALID_DATA;
        }

        //
        // change the priority
        //

        RoutingMethod->Priority = RoutingInfo->Priority;
        SortMethodPriorities();
        CommitMethodChanges();
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        ec = GetExceptionCode();

    }

    LeaveCriticalSection( &CsRouting );

    return ec;
}


error_status_t
FAX_GetRoutingInfo(
    IN HANDLE FaxPortHandle,
    IN LPCWSTR RoutingGuidString,
    OUT LPBYTE *RoutingInfoBuffer,
    OUT LPDWORD RoutingInfoBufferSize
    )
{
    PLINE_INFO          LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    PROUTING_METHOD     RoutingMethod;
    LPBYTE              RoutingInfo = NULL;
    DWORD               RoutingInfoSize = 0;


    if (!FaxSvcAccessCheck( SEC_PORT_QUERY, FAX_PORT_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!RoutingGuidString || !RoutingInfoBuffer || !RoutingInfoBufferSize) {
        return ERROR_INVALID_PARAMETER;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    RoutingMethod = FindRoutingMethodByGuid( RoutingGuidString );
    if (!RoutingMethod) {
        return ERROR_INVALID_DATA;
    }

    __try {

        //
        // first check to see how big the buffer needs to be
        //

        if (RoutingMethod->RoutingExtension->FaxRouteGetRoutingInfo(
                (LPWSTR) RoutingGuidString,
                LineInfo->PermanentLineID,
                NULL,
                &RoutingInfoSize ))
        {

            //
            // allocate a client buffer
            //

            RoutingInfo = (LPBYTE) MemAlloc( RoutingInfoSize );
            if (RoutingInfo == NULL) {
                return ERROR_NOT_ENOUGH_MEMORY;
            }

            //
            // get the routing data
            //

            if (RoutingMethod->RoutingExtension->FaxRouteGetRoutingInfo(
                    RoutingGuidString,
                    LineInfo->PermanentLineID,
                    RoutingInfo,
                    &RoutingInfoSize ))
            {

                //
                // move the data to the return buffer
                //

                *RoutingInfoBuffer = RoutingInfo;
                *RoutingInfoBufferSize = RoutingInfoSize;

                return ERROR_SUCCESS;

            } else {

                return ERROR_INVALID_DATA;

            }

        } else {

            return ERROR_INVALID_DATA;

        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        return GetExceptionCode();

    }

    return ERROR_INVALID_FUNCTION;
}


error_status_t
FAX_SetRoutingInfo(
    IN HANDLE FaxPortHandle,
    IN LPCWSTR RoutingGuidString,
    IN const BYTE *RoutingInfoBuffer,
    IN DWORD RoutingInfoBufferSize
    )
{
    PLINE_INFO          LineInfo = ((PHANDLE_ENTRY)FaxPortHandle)->LineInfo;
    PROUTING_METHOD     RoutingMethod;


    if (!FaxSvcAccessCheck( SEC_PORT_SET, FAX_PORT_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!RoutingGuidString || !RoutingInfoBuffer || !RoutingInfoBufferSize) {
        return ERROR_INVALID_PARAMETER;
    }

    if (!LineInfo) {
        return ERROR_INVALID_DATA;
    }

    RoutingMethod = FindRoutingMethodByGuid( RoutingGuidString );
    if (!RoutingMethod) {
        return ERROR_INVALID_DATA;
    }

    __try {

        if (RoutingMethod->RoutingExtension->FaxRouteSetRoutingInfo(
                RoutingGuidString,
                LineInfo->PermanentLineID,
                RoutingInfoBuffer,
                RoutingInfoBufferSize ))
        {

            return ERROR_SUCCESS;

        } else {

            return ERROR_INVALID_DATA;

        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        return GetExceptionCode();

    }

    return ERROR_INVALID_FUNCTION;
}


error_status_t
FAX_GetTapiLocations(
    IN  handle_t    FaxHandle,
    OUT LPBYTE      *Buffer,
    OUT LPDWORD     LocationSize
    )

/*++

Routine Description:

    Queries the TAPI location information for the server

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    NumLocations    - Returned number of locations
    LocationSize    - Size of the TapiLocations buffer
    BytesNeeded     - Size required
    TapiLocations   - Data buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    LPLINETRANSLATECAPS LineTransCaps = NULL;
    LPLINELOCATIONENTRY LineLocation = NULL;
    LPTSTR s,p;
    DWORD i,l;
    LONG rVal = ERROR_SUCCESS;
    ULONG_PTR Offset;
    PFAX_TAPI_LOCATION_INFO TapiLocationInfo;


    if (!FaxSvcAccessCheck( SEC_CONFIG_QUERY, FAX_CONFIG_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!Buffer || !LocationSize)
        return ERROR_INVALID_PARAMETER;

    __try {

        //
        // get the toll lists
        //

        rVal = MyLineGetTransCaps( &LineTransCaps );
        if (rVal != ERROR_SUCCESS) {
            goto exit;
        }

        *LocationSize = sizeof(FAX_TAPI_LOCATION_INFO) + 32;

        if (LineTransCaps->dwLocationListSize && LineTransCaps->dwLocationListOffset) {
            LineLocation = (LPLINELOCATIONENTRY) ((LPBYTE)LineTransCaps + LineTransCaps->dwLocationListOffset);
            for (i=0; i<LineTransCaps->dwNumLocations; i++) {
                *LocationSize += sizeof(FAX_TAPI_LOCATIONS);
                if (LineLocation[i].dwTollPrefixListSize && LineLocation[i].dwTollPrefixListOffset) {
                    *LocationSize += LineLocation[i].dwLocationNameSize;
                    *LocationSize += LineLocation[i].dwTollPrefixListSize;
                }
            }
        }

        *Buffer = MemAlloc( *LocationSize );
        if (*Buffer == NULL) {
            rVal = ERROR_NOT_ENOUGH_MEMORY;
            goto exit;
        }

        TapiLocationInfo = (PFAX_TAPI_LOCATION_INFO) *Buffer;

        Offset = sizeof(FAX_TAPI_LOCATION_INFO);

        TapiLocationInfo->CurrentLocationID = LineTransCaps->dwCurrentLocationID;
        TapiLocationInfo->NumLocations = LineTransCaps->dwNumLocations;
        TapiLocationInfo->TapiLocations = (PFAX_TAPI_LOCATIONS) ((LPBYTE) TapiLocationInfo + Offset);

        Offset += (LineTransCaps->dwNumLocations * sizeof(FAX_TAPI_LOCATIONS));

        if (LineTransCaps->dwLocationListSize && LineTransCaps->dwLocationListOffset) {

            LineLocation = (LPLINELOCATIONENTRY) ((LPBYTE)LineTransCaps + LineTransCaps->dwLocationListOffset);

            for (i=0; i<LineTransCaps->dwNumLocations; i++) {

                TapiLocationInfo->TapiLocations[i].PermanentLocationID = LineLocation[i].dwPermanentLocationID;
                TapiLocationInfo->TapiLocations[i].CountryCode = LineLocation[i].dwCountryCode;
                TapiLocationInfo->TapiLocations[i].NumTollPrefixes = 0;

                if (LineLocation[i].dwCityCodeSize && LineLocation[i].dwCityCodeOffset) {
                    TapiLocationInfo->TapiLocations[i].AreaCode =
                        _ttoi( (LPTSTR) ((LPBYTE)LineTransCaps + LineLocation[i].dwCityCodeOffset) );
                } else {
                    TapiLocationInfo->TapiLocations[i].AreaCode = 0;
                }

                if (LineLocation[i].dwTollPrefixListSize && LineLocation[i].dwTollPrefixListOffset) {
                    s = (LPTSTR) ((LPBYTE)LineTransCaps + LineLocation[i].dwTollPrefixListOffset);
                    if (!*s) {
                        TapiLocationInfo->TapiLocations[i].TollPrefixes = NULL;
                    } else {
                        TapiLocationInfo->TapiLocations[i].TollPrefixes =
                            (LPTSTR) ((LPBYTE) TapiLocationInfo + Offset);
                        Offset += LineLocation[i].dwTollPrefixListSize;
                        s = (LPTSTR) ((LPBYTE)LineTransCaps + LineLocation[i].dwTollPrefixListOffset);
                        if (*s == TEXT(',')) {
                            s += 1;
                        }
                        l = _tcslen(s);
                        if (l && s[l-1] == TEXT(',')) {
                            s[l-1] = 0;
                        }
                        _tcscpy(
                            (LPTSTR)TapiLocationInfo->TapiLocations[i].TollPrefixes,
                            s
                            );
                        //
                        // count the number of toll prefixes
                        //
                        s = (LPTSTR)TapiLocationInfo->TapiLocations[i].TollPrefixes;
                        while (*s) {
                            p = _tcschr( s, TEXT(',') );
                            s = (p) ? p + 1 : s + _tcslen( s );
                            TapiLocationInfo->TapiLocations[i].NumTollPrefixes += 1;
                        }
                    }
                } else {
                    TapiLocationInfo->TapiLocations[i].TollPrefixes = NULL;
                }

                if (LineLocation[i].dwLocationNameSize && LineLocation[i].dwLocationNameOffset) {
                    TapiLocationInfo->TapiLocations[i].LocationName =
                        (LPTSTR) ((LPBYTE) TapiLocationInfo + Offset);
                    Offset += LineLocation[i].dwLocationNameSize;
                    _tcscpy(
                        (LPTSTR)TapiLocationInfo->TapiLocations[i].LocationName,
                        (LPTSTR) ((LPBYTE)LineTransCaps + LineLocation[i].dwLocationNameOffset)
                        );
                } else {
                    TapiLocationInfo->TapiLocations[i].LocationName = NULL;
                }
            }
        }

        for (i=0; i<TapiLocationInfo->NumLocations; i++) {
            if (TapiLocationInfo->TapiLocations[i].LocationName) {
                TapiLocationInfo->TapiLocations[i].LocationName =
                    (LPWSTR) ((ULONG_PTR)TapiLocationInfo->TapiLocations[i].LocationName - (ULONG_PTR)TapiLocationInfo);
            }
            if (TapiLocationInfo->TapiLocations[i].TollPrefixes) {
                TapiLocationInfo->TapiLocations[i].TollPrefixes =
                    (LPWSTR) ((ULONG_PTR)TapiLocationInfo->TapiLocations[i].TollPrefixes - (ULONG_PTR)TapiLocationInfo);
            }
        }

        TapiLocationInfo->TapiLocations = (PFAX_TAPI_LOCATIONS) ((LPBYTE)TapiLocationInfo->TapiLocations - (ULONG_PTR)TapiLocationInfo);

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

exit:
    MemFree( LineTransCaps );
    return rVal;
}


error_status_t
FAX_SetTapiLocations(
    IN  handle_t    FaxHandle,
    IN  LPBYTE      Buffer,
    IN  DWORD       BufferSize
    )

/*++

Routine Description:

    Queries the TAPI location information for the server

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    NumLocations    - Number of locations in the TapiLocations buffer
    TapiLocations   - Data buffer

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    #define TOLL_MASK (LINETRANSLATERESULT_NOTINTOLLLIST|LINETRANSLATERESULT_INTOLLLIST)
    #define SetBit(_bitmap,_bit) (_bitmap[((_bit)>>5)]|=(1<<((_bit)-(((_bit)>>5)*32))))
    #define IsBit(_bitmap,_bit) (_bitmap[((_bit)>>5)]&(1<<((_bit)-(((_bit)>>5)*32))))

    PFAX_TAPI_LOCATION_INFO TapiLocationInfo = (PFAX_TAPI_LOCATION_INFO) Buffer;
    LPLINETRANSLATECAPS LineTransCaps = NULL;
    LPLINELOCATIONENTRY LineLocation = NULL;
    DWORD BitMap[32];
    DWORD BitMapCurr[32];
    LPTSTR s,p;
    DWORD i,j;
    LONG rVal = ERROR_SUCCESS;
    TCHAR Address[32];
    DWORD TollListOption;


    if (!FaxSvcAccessCheck( SEC_CONFIG_SET, FAX_CONFIG_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    __try {

        if (!TapiLocationInfo) {
            rVal = ERROR_INVALID_PARAMETER;
            goto exit;
        }

        TapiLocationInfo->TapiLocations = (PFAX_TAPI_LOCATIONS) (Buffer+ (ULONG_PTR) TapiLocationInfo->TapiLocations);

        for (i=0; i<TapiLocationInfo->NumLocations; i++) {
            TapiLocationInfo->TapiLocations[i].LocationName = (LPWSTR) FixupString(Buffer,TapiLocationInfo->TapiLocations[i].LocationName);
            TapiLocationInfo->TapiLocations[i].TollPrefixes = (LPWSTR) FixupString(Buffer,TapiLocationInfo->TapiLocations[i].TollPrefixes);
        }

        //
        // get the toll lists
        //

        rVal = MyLineGetTransCaps( &LineTransCaps );
        if (rVal != ERROR_SUCCESS) {
            goto exit;
        }

        LineLocation = (LPLINELOCATIONENTRY) ((LPBYTE)LineTransCaps + LineTransCaps->dwLocationListOffset);
        for (i=0; i<TapiLocationInfo->NumLocations; i++) {

            //
            // match the location id for this location entry with
            // one that tapi knows about.
            //

            for (j=0; j<LineTransCaps->dwNumLocations; j++) {
                if (LineLocation[j].dwPermanentLocationID == TapiLocationInfo->TapiLocations[i].PermanentLocationID) {
                    break;
                }
            }
            if (j == LineTransCaps->dwNumLocations) {
                //
                // we got a bogus location id
                //
                continue;
            }

            //
            // set the bitmap for the toll prefixes that
            // tapi location is using.
            //

            ZeroMemory( BitMapCurr, sizeof(BitMapCurr) );

            if (LineLocation[j].dwTollPrefixListOffset) {
                s = (LPTSTR) ((LPBYTE)LineTransCaps + LineLocation[j].dwTollPrefixListOffset);
                if (*s == TEXT(',')) {
                    s += 1;
                }
                while( s && *s ) {
                    p = _tcschr( s, TEXT(',') );
                    if (p) {
                        *p = 0;
                    }
                    SetBit( BitMapCurr, min(_ttoi(s),999) );
                    if (p) {
                        s = p + 1;
                    } else {
                        s += _tcslen( s );
                    }
                }
            }

            //
            // set the bitmap for the toll prefixes
            // that this location is using.
            //

            s = (LPTSTR) TapiLocationInfo->TapiLocations[i].TollPrefixes;

            ZeroMemory( BitMap, sizeof(BitMap) );

            while( s && *s ) {
                p = _tcschr( s, TEXT(',') );
                if (p) {
                    *p = 0;
                }
                SetBit( BitMap, min(_ttoi(s),999) );
                if (p) {
                    s = p + 1;
                } else {
                    s += _tcslen( s );
                }
            }

            //
            // set the current location so that the toll prefix
            // changes affect the correct location.
            //

            rVal = lineSetCurrentLocation( hLineApp, TapiLocationInfo->TapiLocations[i].PermanentLocationID );
            if (rVal != ERROR_SUCCESS) {
                DebugPrint(( TEXT("lineSetCurrentLocation() failed, ec=%08x"), rVal ));
                continue;
            }

            //
            // change the toll list
            //

            for (j=200; j<999; j++) {

                TollListOption = 0;

                if (!IsBit( BitMapCurr, j )) {
                    if (IsBit(BitMap,j)) {
                        TollListOption = LINETOLLLISTOPTION_ADD;
                    }
                } else {
                    if (!IsBit(BitMap,j)) {
                        TollListOption = LINETOLLLISTOPTION_REMOVE;
                    }
                }

                if (TollListOption) {
                    wsprintf(
                        Address,
                        TEXT("+%d (%d) %03d-0000"),
                        TapiLocationInfo->TapiLocations[i].CountryCode,
                        TapiLocationInfo->TapiLocations[i].AreaCode,
                        j
                        );
                    rVal = lineSetTollList( hLineApp, 0, Address, TollListOption );
                    if (rVal != ERROR_SUCCESS) {
                        DebugPrint(( TEXT("lineSetTollList() failed, address=%s, ec=%08x"), Address, rVal ));
                        continue;
                    }
                }

            }

        }

        //
        // reset the current location
        //

        rVal = lineSetCurrentLocation( hLineApp, TapiLocationInfo->CurrentLocationID );
        if (rVal != ERROR_SUCCESS) {
            DebugPrint(( TEXT("lineSetCurrentLocation() failed, ec=%08x"), rVal ));
        }

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        //
        // for some reason we crashed, so return the exception code
        //

        rVal = GetExceptionCode();

    }

exit:
    MemFree( LineTransCaps );
    return rVal;
}


error_status_t
FAX_GetMapiProfiles(
    IN  handle_t FaxHandle,
    OUT LPBYTE *MapiProfiles,
    OUT LPDWORD BufferSize
    )

/*++

Routine Description:

    Returns a list of MAPI profiles.

Arguments:

    FaxHandle       - FAX handle obtained from FaxConnectFaxServer.
    MapiProfiles    - Multi-SZ string containing all MAPI profiles
    ProfileSize     - Size of the MapiProfiles array

Return Value:

    ERROR_SUCCESS for success, otherwise a WIN32 error code.

--*/

{
    error_status_t rVal;


    if (!FaxSvcAccessCheck( SEC_CONFIG_QUERY, FAX_CONFIG_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!MapiProfiles || !BufferSize)
        return ERROR_INVALID_PARAMETER;

    __try {

        rVal = (error_status_t) GetMapiProfiles( (LPWSTR*) MapiProfiles, BufferSize );

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        rVal = GetExceptionCode();

    }

    return rVal;
}

error_status_t
FAX_GetLoggingCategories(
    IN handle_t hBinding,
    OUT LPBYTE *Buffer,
    OUT LPDWORD BufferSize,
    OUT LPDWORD NumberCategories
    )
{
    PFAX_LOG_CATEGORY Categories;
    REG_FAX_LOGGING FaxRegLogging;
    DWORD i;
    ULONG_PTR Offset;


    if (!FaxSvcAccessCheck( SEC_CONFIG_QUERY, FAX_CONFIG_QUERY )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!Buffer || !BufferSize || !NumberCategories) {
        return ERROR_INVALID_PARAMETER;
    }


    GetLoggingCategoriesRegistry( &FaxRegLogging );

    *BufferSize = sizeof(FAX_LOG_CATEGORY) * FaxRegLogging.LoggingCount;
    Offset = *BufferSize;

    for (i=0; i<FaxRegLogging.LoggingCount; i++) {
        *BufferSize += StringSize( FaxRegLogging.Logging[i].CategoryName );
    }

    *Buffer = (LPBYTE) MemAlloc( *BufferSize );
    if (!*Buffer) {
        *BufferSize = 0;
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    *NumberCategories = FaxRegLogging.LoggingCount;
    Categories = (PFAX_LOG_CATEGORY) *Buffer;

    for (i=0; i<FaxRegLogging.LoggingCount; i++) {

        StoreString(
            FaxRegLogging.Logging[i].CategoryName,
            (PULONG_PTR)&Categories[i].Name,
            *Buffer,
            &Offset
            );

        Categories[i].Category  = FaxRegLogging.Logging[i].Number;
        Categories[i].Level     = FaxRegLogging.Logging[i].Level;
    }

    return 0;
}


error_status_t
FAX_SetLoggingCategories(
    IN handle_t hBinding,
    IN const LPBYTE Buffer,
    IN DWORD BufferSize,
    IN DWORD NumberCategories
    )
{
    REG_FAX_LOGGING FaxRegLogging;
    DWORD i;


    if (!FaxSvcAccessCheck( SEC_CONFIG_QUERY, FAX_CONFIG_SET )) {
        return ERROR_ACCESS_DENIED;
    }

    if (!Buffer || !BufferSize)
        return ERROR_INVALID_PARAMETER;

    //
    // setup the data
    //
    FaxRegLogging.LoggingCount = NumberCategories;
    FaxRegLogging.Logging = (PREG_CATEGORY) Buffer;

    for (i=0; i<FaxRegLogging.LoggingCount; i++) {
        FaxRegLogging.Logging[i].CategoryName = (LPWSTR) FixupString(Buffer,FaxRegLogging.Logging[i].CategoryName);
    }

    //
    // first change the real time data that the server is using
    //
    RefreshEventLog( &FaxRegLogging );

    //
    // now change the registry so it sticks
    //
    return SetLoggingCategoriesRegistry( &FaxRegLogging ) ? 0 : GetLastError();
}


error_status_t
FAX_RegisterEventWindow(
   IN  handle_t hBinding,
   IN  ULONG64 hWnd,
   IN  UINT MessageStart,
   IN  LPCWSTR WindowStation,
   IN  LPCWSTR Desktop,
   OUT LPDWORD FaxSvcProcessId
   )
{
    PFAX_CLIENT_DATA ClientData,Current;
    PLIST_ENTRY Next;
    BOOL EntryExists = FALSE;
    RPC_STATUS ec;
    HANDLE hToken;
    HANDLE hThread;

    if (!hWnd || !FaxSvcProcessId)
        return ERROR_INVALID_PARAMETER;

    ClientData = (PFAX_CLIENT_DATA) MemAlloc( sizeof(FAX_CLIENT_DATA) );
    if (!ClientData) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    ClientData->hBinding        = hBinding;
    ClientData->MachineName     = NULL;
    ClientData->ClientName      = NULL;
    ClientData->Context         = 0;
    ClientData->hWnd            = (HWND)hWnd;
    ClientData->MessageStart    = MessageStart;
    ClientData->StartedMsg      = FALSE;

    if (MessageStart != 0 ) {

        ec = RpcImpersonateClient(hBinding);
        if (ec != RPC_S_OK) {
            DebugPrint(( TEXT("RpcImpersonateClient failed, ec = %d\n"), ec ));
            goto e0;
        }

        //
        //  need to cross threads, so duplicate thread psuedohandle?
        //
        if (!DuplicateHandle(GetCurrentProcess(),
                            GetCurrentThread(),
                            GetCurrentProcess(),
                            &hThread,
                            THREAD_ALL_ACCESS,
                            FALSE,
                            0 )) {
            ec = GetLastError();
            DebugPrint(( TEXT("DuplicateHandle() failed, ec=%d"), ec ));
            goto e1;
        }

        //
        // different thread will be impersonating so I better open then duplicate the token
        //
        if (!OpenThreadToken(hThread,
                       TOKEN_QUERY | TOKEN_IMPERSONATE | TOKEN_DUPLICATE,
                       FALSE,
                       &hToken
                      ) ) {
            ec = GetLastError();
            DebugPrint((TEXT("Couldn't OpenThreadToken, ec = %d.\n"), ec ));
            goto e2;
        }

        if (!DuplicateToken( hToken,
                            SecurityImpersonation,
                            &ClientData->hClientToken )) {
          ec = GetLastError();
          DebugPrint((TEXT("Couldn't DuplicateToken, ec = %d.\n"), ec ));
          goto e3;
        }

        CloseHandle( hToken );
        CloseHandle( hThread );
        RpcRevertToSelf();

        ClientData->WindowStation = StringDup( WindowStation );
        ClientData->Desktop       = StringDup( Desktop );

    }

    __try {
        EnterCriticalSection( &CsClients );

        Next = ClientsListHead.Flink;
        if (Next) {
            while ((ULONG_PTR)Next != (ULONG_PTR)&ClientsListHead) {
                Current = CONTAINING_RECORD( Next, FAX_CLIENT_DATA, ListEntry );

                Next = Current->ListEntry.Flink;

                if (Current->hWnd == ClientData->hWnd 
                    && !lstrcmpi(Current->WindowStation,ClientData->WindowStation)
                    && !lstrcmpi(Current->Desktop,ClientData->Desktop) ) {
                    DebugPrint((TEXT("Already have window handle %d registered.\n"),Current->hWnd));
                    EntryExists = TRUE;
                    if (MessageStart == 0) {
                       //
                       // This means that we have already registered this client.
                       // To allow this client to logoff, we should close the impersonation token
                       // we have for their desktop
                       //
                       CloseHandle( Current->hClientToken );
                       RemoveEntryList( &Current->ListEntry );
                       MemFree( (LPBYTE) Current->WindowStation );
                       MemFree( (LPBYTE) Current->Desktop );
                       MemFree( Current );
                    }
                }
            }
        }

        if (!EntryExists) {
            InsertTailList( &ClientsListHead, &ClientData->ListEntry );
        } else {
            if ( ClientData ) {
               if ( ClientData->WindowStation ) MemFree ( (LPBYTE) ClientData->WindowStation ) ;
               if ( ClientData->Desktop )       MemFree ( (LPBYTE) ClientData->Desktop ) ;
               CloseHandle( ClientData->hClientToken );
               MemFree( ClientData );
            }

            LeaveCriticalSection( &CsClients );

            return 0;

        }

        LeaveCriticalSection( &CsClients );
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        DebugPrint((TEXT("FAX_RegisterEventWindow exception, ec = %d.\n"),GetExceptionCode() ));

        if ( ClientData->WindowStation ) MemFree ( (LPBYTE) ClientData->WindowStation ) ;

        if ( ClientData->Desktop )       MemFree ( (LPBYTE) ClientData->Desktop ) ;

        if ( ClientData ) {

            if ( ClientData->hClientToken) {
                CloseHandle( ClientData->hClientToken );
            }

            MemFree( ClientData );
        }

        LeaveCriticalSection( &CsClients );

        return GetExceptionCode();

    }

    CreateFaxEvent( 0, FEI_FAXSVC_STARTED, 0xffffffff );

    if (FaxSvcProcessId) {
        *FaxSvcProcessId = GetCurrentProcessId();
    }

    return 0;

e3:
    CloseHandle( hToken );
e2:
    CloseHandle( hThread );
e1:
    RpcRevertToSelf();
e0:
    MemFree(ClientData);
    return ec;
}


error_status_t
FAX_StartClientServer(
   IN handle_t   hBinding,
   IN LPCTSTR    MachineName,
   IN LPCTSTR    ClientName,
   IN ULONG64    Context
   )
{
    DWORD Error;
    PFAX_CLIENT_DATA ClientData,Current;
    PLIST_ENTRY Next;
    BOOL EntryExists = FALSE;


    ClientData = (PFAX_CLIENT_DATA) MemAlloc( sizeof(FAX_CLIENT_DATA) );
    if (!ClientData) {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    ClientData->hBinding        = hBinding;
    ClientData->MachineName     = StringDup( MachineName );
    ClientData->ClientName      = StringDup( ClientName );
    ClientData->Context         = Context;
    ClientData->hWnd            = NULL;
    ClientData->MessageStart    = 0;
    ClientData->StartedMsg      = FALSE;

    __try {
        EnterCriticalSection( &CsClients );

        //
        // make sure we don't register a client twice
        //
        Next = ClientsListHead.Flink;
        if (Next) {
            while ((ULONG_PTR)Next != (ULONG_PTR)&ClientsListHead) {
                Current = CONTAINING_RECORD( Next, FAX_CLIENT_DATA, ListEntry );
                Next = Current->ListEntry.Flink;

                //
                // make sure we're looking at io event-based clients
                //
                if (!Current->hWnd) {

                    if ((_wcsicmp(Current->ClientName ,ClientData->ClientName ) == 0) &&
                        ( ((!Current->MachineName) && (!ClientData->MachineName)) ||
                         (_wcsicmp(Current->MachineName,ClientData->MachineName) == 0) )) {
                        DebugPrint((TEXT("Already have client %s on %s registered.\n"),
                                   Current->ClientName  ? Current->ClientName  : L"NULL",
                                   Current->MachineName ? Current->MachineName : L"NULL" ));
                        EntryExists = TRUE;
                    }
                }
            }
        }

        if (!EntryExists) {
            Error = RpcpBindRpc( MachineName, ClientName, L"Security=identification static true", &ClientData->FaxHandle );
            if (Error) {
                MemFree( ClientData );
                LeaveCriticalSection( &CsClients );
                return Error;
            }
            InsertTailList( &ClientsListHead, &ClientData->ListEntry );
        }

        LeaveCriticalSection( &CsClients );
    } __except (EXCEPTION_EXECUTE_HANDLER) {
        MemFree( ClientData);
        LeaveCriticalSection( &CsClients );
        return (GetExceptionCode() );
    }

    CreateFaxEvent( 0, FEI_FAXSVC_STARTED, 0xffffffff );

    return 0;
}

error_status_t
FAX_AccessCheck(
   IN handle_t  hBinding,
   IN DWORD     AccessMask,
   OUT LPDWORD  fAccess
   )
{
    if (!hBinding || !fAccess) {
        return ERROR_INVALID_PARAMETER;
    }

    //
    // we only have one security descriptor, so the first parameter is meaningless
    //
    *fAccess = FaxSvcAccessCheck( SEC_CONFIG_QUERY, AccessMask);

    return 0;
}

VOID
RPC_FAX_PORT_HANDLE_rundown(
    IN HANDLE FaxPortHandle
    )
{
    PHANDLE_ENTRY PortHandleEntry = (PHANDLE_ENTRY) FaxPortHandle;
    PLIST_ENTRY Next;
    PFAX_CLIENT_DATA ClientData;


    EnterCriticalSection( &CsLine );
    EnterCriticalSection( &CsClients );

    __try {

        DebugPrint(( TEXT("RPC_FAX_PORT_HANDLE_rundown() running for port handle 0x%08x"), FaxPortHandle ));

        Next = ClientsListHead.Flink;
        if (Next) {
            while ((ULONG_PTR)Next != (ULONG_PTR)&ClientsListHead) {
                ClientData = CONTAINING_RECORD( Next, FAX_CLIENT_DATA, ListEntry );
                Next = ClientData->ListEntry.Flink;
                if (ClientData->hBinding == PortHandleEntry->hBinding) {
                    RemoveEntryList( &ClientData->ListEntry );
                    MemFree(ClientData);
                }
            }
        }

        CloseFaxHandle( PortHandleEntry );

    } __except (EXCEPTION_EXECUTE_HANDLER) {

        DebugPrint(( TEXT("RPC_FAX_PORT_HANDLE_rundown() crashed, ec=0x%08x"), GetExceptionCode() ));

    }

    LeaveCriticalSection( &CsClients );
    LeaveCriticalSection( &CsLine );

    return;
}