/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    autodial.c

Abstract:

    This file provides routines for interacting
    with the automatic connection driver (acd.sys).

Author:

    Anthony Discolo (adiscolo)  9-6-95

Revision History:

--*/
#include "precomp.h"   // procedure headings

#ifdef RASAUTODIAL

#ifndef VXD
#include <acd.h>
#include <acdapi.h>
#endif

//*******************  Pageable Routine Declarations ****************
#ifdef ALLOC_PRAGMA
#pragma CTEMakePageable(INIT, NbtAcdBind)
#pragma CTEMakePageable(PAGE, NbtAcdUnbind)
#endif
//*******************  Pageable Routine Declarations ****************


//
// Automatic connection global variables.
//
BOOLEAN fAcdLoadedG;
ACD_DRIVER AcdDriverG;
ULONG ulDriverIdG = 'Nbt ';

//
// Imported routines.
//
VOID
CleanUpPartialConnection(
    IN NTSTATUS             status,
    IN tCONNECTELE          *pConnEle,
    IN tDGRAM_SEND_TRACKING *pTracker,
    IN PIRP                 pClientIrp,
    IN CTELockHandle        irqlJointLock,
    IN CTELockHandle        irqlConnEle
    );

NTSTATUS
NbtConnectCommon(
    IN  TDI_REQUEST                 *pRequest,
    IN  PVOID                       pTimeout,
    IN  PTDI_CONNECTION_INFORMATION pCallInfo,
    IN  PIRP                        pIrp
    );

NTSTATUS
NbtpConnectCompletionRoutine(
    PDEVICE_OBJECT  pDeviceObject,
    PIRP            pIrp,
    PVOID           pCompletionContext
    );


VOID
NbtRetryPreConnect(
    IN BOOLEAN fSuccess,
    IN PVOID *pArgs
    )

/*++

Routine Description:

    This routine is called indirectly by the automatic
    connection driver to continue the connection process
    after an automatic connection has been made.

Arguments:

    fSuccess - TRUE if the connection attempt was successful.

    pArgs - a pointer to the argument vector

Return Value:

    None.

--*/

{
    NTSTATUS                    status;
    tCONNECTELE                 *pConnEle = pArgs[0];
    PVOID                       pTimeout = pArgs[1];
    PTDI_CONNECTION_INFORMATION pCallInfo = pArgs[2];
    PIRP                        pIrp = pArgs[3];
    TDI_REQUEST                 request;
    KIRQL                       irql;
    CTELockHandle               OldIrq;
    tDEVICECONTEXT              *pDeviceContext = pConnEle->pDeviceContext;

    IF_DBG(NBT_DEBUG_NAME)
        KdPrint(("Nbt.NbtRetryPreConnect: fSuccess=%d, pIrp=0x%x, pIrp->Cancel=%d, pConnEle=0x%x\n",
            fSuccess, pIrp, pIrp->Cancel, pConnEle));

    request.Handle.ConnectionContext = pConnEle;
    status = NbtCancelCancelRoutine (pIrp);
    if (status != STATUS_CANCELLED)
    {
        //
        // We're done with the connection progress,
        // so clear the fAutoConnecting flag.  We
        // set the fAutoConnected flag to prevent us
        // from re-attempting another automatic
        // connection on this connection.
        //
        CTESpinLock(pConnEle,OldIrq);
        pConnEle->fAutoConnecting = FALSE;
        pConnEle->fAutoConnected = TRUE;
        CTESpinFree(pConnEle,OldIrq);

        status = fSuccess ? NbtConnectCommon (&request, pTimeout, pCallInfo, pIrp) :
                            STATUS_BAD_NETWORK_PATH;
        //
        // We are responsible for completing
        // the irp.
        //
        if (status != STATUS_PENDING)
        {
            //
            // Clear out the Irp pointer in the Connection object so that we dont try to
            // complete it again when we clean up the connection. Do this under the connection
            // lock.
            //
            CTESpinLock(pConnEle,OldIrq);
            pConnEle->pIrp = NULL;
            CTESpinFree(pConnEle,OldIrq);

            pIrp->IoStatus.Status = status;
            IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        }

        NBT_DEREFERENCE_DEVICE (pDeviceContext, REF_DEV_AUTODIAL, FALSE);
    }
} // NbtRetryPreConnect



BOOLEAN
NbtCancelAutoDialRequest(
    IN PVOID pArg,
    IN ULONG ulFlags,
    IN ACD_CONNECT_CALLBACK pProc,
    IN USHORT nArgs,
    IN PVOID *pArgs
    )
{
    if (nArgs != 5)
        return FALSE;

    return (pArgs[4] == pArg);
} // NbtCancelAutoDialRequest



BOOLEAN
NbtCancelPreConnect(
    IN PDEVICE_OBJECT pDeviceObject,
    IN PIRP pIrp
    )
{
    NTSTATUS            status;
    PIO_STACK_LOCATION  pIrpSp;
    tCONNECTELE         *pConnEle;
    KIRQL               irql;
    ACD_ADDR            *pAddr;
    BOOLEAN             fCancelled;
    CTELockHandle       OldIrq;

    UNREFERENCED_PARAMETER(pDeviceObject);

    pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
    pConnEle = (tCONNECTELE *) pIrpSp->FileObject->FsContext;
    if ((!pConnEle) ||
        (!NBT_VERIFY_HANDLE2 (pConnEle, NBT_VERIFY_CONNECTION, NBT_VERIFY_CONNECTION_DOWN)) ||
        (!(pAddr = (ACD_ADDR *) NbtAllocMem(sizeof(ACD_ADDR),NBT_TAG('A')))))
    {
        IoReleaseCancelSpinLock(pIrp->CancelIrql);
        ASSERTMSG ("Nbt.NbtCancelPreConnect: ERROR - Invalid Connection Handle\n", 0);
        return FALSE;
    }

    IF_DBG(NBT_DEBUG_NAME)
        KdPrint(("NbtCancelPreConnect: pIrp=0x%x, pConnEle=0x%x\n", pIrp, pConnEle));
    //
    // Get the address of the connection.
    //
    pAddr->fType = ACD_ADDR_NB;
    RtlCopyMemory(&pAddr->cNetbios, pConnEle->RemoteName, 16);
    //
    // Cancel the autodial request.
    //
    fCancelled = (*AcdDriverG.lpfnCancelConnection) (ulDriverIdG, pAddr, NbtCancelAutoDialRequest, pIrp);
    if (fCancelled)
    {
        IoSetCancelRoutine(pIrp, NULL);
    }
    IoReleaseCancelSpinLock(pIrp->CancelIrql);

    CTEMemFree(pAddr);

    //
    // If the request could not be found
    // in the driver, then it has already
    // been completed, so we simply return.
    //
    if (!fCancelled)
    {
        return FALSE;
    }

    KeRaiseIrql(DISPATCH_LEVEL, &irql);
    pIrp->IoStatus.Status = STATUS_CANCELLED;
    pIrp->IoStatus.Information = 0;

    NBT_DEREFERENCE_DEVICE (pConnEle->pDeviceContext, REF_DEV_AUTODIAL, FALSE);

    //
    // Clear out the Irp pointer in the Connection object so that we dont try to
    // complete it again when we clean up the connection. Do this under the connection
    // lock.
    //
    // This should not be needed since before we call NbtConnectCommon, the Cancel routine
    // is NULLed out, so it cannot happen that the pIrp ptr in the connection is set to the
    // Irp, and this cancel routine is called.
    //

    CTESpinLock(pConnEle,OldIrq);
    pConnEle->pIrp = NULL;
    CTESpinFree(pConnEle,OldIrq);

    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    KeLowerIrql(irql);

    return TRUE;
} // NbtCancelPreConnect


BOOLEAN
NbtCancelPostConnect(
    IN PIRP pIrp
    )
{
    PIO_STACK_LOCATION  pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
    tCONNECTELE         *pConnEle = (tCONNECTELE *)pIrpSp->FileObject->FsContext;
    ACD_ADDR            *pAddr;
    BOOLEAN             fCancelled;

    if ((!pConnEle) ||
        (!NBT_VERIFY_HANDLE2 (pConnEle, NBT_VERIFY_CONNECTION, NBT_VERIFY_CONNECTION_DOWN)) ||
        (!(pAddr = (ACD_ADDR *) NbtAllocMem(sizeof(ACD_ADDR),NBT_TAG('A')))))
    {
        ASSERTMSG ("Nbt.NbtCancelPostConnect: ERROR - Invalid Connection Handle\n", 0);
        return FALSE;
    }

    IF_DBG(NBT_DEBUG_NAME)
        KdPrint(("Nbt.NbtCancelPostConnect: pIrp=0x%x, pConnEle=0x%x\n", pIrp, pConnEle));
    //
    // Get the address of the connection.
    //
    pAddr->fType = ACD_ADDR_NB;
    RtlCopyMemory(&pAddr->cNetbios, pConnEle->RemoteName, 15);

    //
    // Cancel the autodial request.
    //
    fCancelled = (*AcdDriverG.lpfnCancelConnection) (ulDriverIdG, pAddr, NbtCancelAutoDialRequest, pIrp);
    if (fCancelled)
    {
        NBT_DEREFERENCE_DEVICE (pConnEle->pDeviceContext, REF_DEV_AUTODIAL, FALSE);
    }

    CTEMemFree(pAddr);
    return (fCancelled);
} // NbtCancelPostConnect



BOOLEAN
NbtAttemptAutoDial(
    IN  tCONNECTELE                 *pConnEle,
    IN  PVOID                       pTimeout,
    IN  PTDI_CONNECTION_INFORMATION pCallInfo,
    IN  PIRP                        pIrp,
    IN  ULONG                       ulFlags,
    IN  ACD_CONNECT_CALLBACK        pProc
    )

/*++

Routine Description:

    Call the automatic connection driver to attempt an
    automatic connection.  The first five parameters are
    used in the call to NbtConnect after the connection
    completes successfully.

Arguments:

    ...

    ulFlags - automatic connection flags

    pProc - callback procedure when the automatic connection completes

Return Value:

    TRUE if the automatic connection was started successfully,
    FALSE otherwise.

--*/

{
    NTSTATUS    status;
    BOOLEAN     fSuccess;
    ACD_ADDR    *pAddr = NULL;
    KIRQL       irql;
    PVOID       pArgs[4];
    PCHAR       pName;
    ULONG       ulcbName;
    LONG        lNameType;
    TDI_ADDRESS_NETBT_INTERNAL  TdiAddr;
    PIO_STACK_LOCATION pIrpSp;

    ASSERT(pCallInfo);

    //
    // If this connection has already been through the
    // automatic connection process, don't do it again.
    //
    if ((pConnEle->fAutoConnected)) {
        return FALSE;
    }

    pIrpSp = IoGetCurrentIrpStackLocation(pIrp);
    if (pIrpSp->CompletionRoutine != NbtpConnectCompletionRoutine) {
        status = GetNetBiosNameFromTransportAddress((PTRANSPORT_ADDRESS) pCallInfo->RemoteAddress,
                                                          pCallInfo->RemoteAddressLength, &TdiAddr);
    } else {
        ASSERT(((PTRANSPORT_ADDRESS)pCallInfo->RemoteAddress)->Address[0].AddressType == TDI_ADDRESS_TYPE_UNSPEC);
        CTEMemCopy(&TdiAddr,
                (PTDI_ADDRESS_NETBT_INTERNAL)((PTRANSPORT_ADDRESS)pCallInfo->RemoteAddress)->Address[0].Address,
                sizeof(TdiAddr));
        status = STATUS_SUCCESS;
    }
    if (status != STATUS_SUCCESS || (!NBT_REFERENCE_DEVICE (pConnEle->pDeviceContext, REF_DEV_AUTODIAL, FALSE)) ||
        (!(pAddr = (ACD_ADDR *) NbtAllocMem(sizeof(ACD_ADDR),NBT_TAG('A'))))) {
        if (pAddr) {
            CTEMemFree(pAddr);
        }

        return FALSE;
    }

    pName = TdiAddr.OEMRemoteName.Buffer;
    ulcbName = TdiAddr.OEMRemoteName.Length;
    lNameType = TdiAddr.NameType;

    //
    // Save the address for pre-connect attempts,
    // because if we have to cancel this irp,
    // it is not saved anywhere else.
    //
    CTESpinLock(pConnEle, irql);
    pConnEle->fAutoConnecting = TRUE;
    CTEMemCopy(pConnEle->RemoteName, pName, NETBIOS_NAME_SIZE);
    CTESpinFree(pConnEle, irql);
    pAddr->fType = ACD_ADDR_NB;
    RtlCopyMemory(&pAddr->cNetbios, pName, NETBIOS_NAME_SIZE);

    IF_DBG(NBT_DEBUG_NAME)
        KdPrint(("Nbt.NbtAttemptAutodial: szAddr=%-15.15s\n", pName));
    //
    // Enqueue this request on the network
    // connection pending queue.
    //
    pArgs[0] = pConnEle;
    pArgs[1] = pTimeout;
    pArgs[2] = pCallInfo;
    pArgs[3] = pIrp;
    fSuccess = (*AcdDriverG.lpfnStartConnection) (ulDriverIdG, pAddr, ulFlags, pProc, 4, pArgs);

    //
    // If fSuccess is TRUE, then it means that the NetBT proc has
    // already been called to setup the connection, and hence the
    // data in pConnEle may not be valid now
    //
    // In the case it is FALSE, then pProc has not been called, and
    // we should set the fAutoConnecting flag to FALSE also
    //
    if (!fSuccess)
    {
        NBT_DEREFERENCE_DEVICE (pConnEle->pDeviceContext, REF_DEV_AUTODIAL, FALSE);
        CTESpinLock(pConnEle, irql);
        pConnEle->fAutoConnecting = FALSE;
        CTESpinFree(pConnEle, irql);
    }

    CTEMemFree(pAddr);
    return fSuccess;
} // NbtAttemptAutoDial



VOID
NbtNoteNewConnection(
    IN tNAMEADDR    *pNameAddr,
    IN  ULONG       IpAddress
    )

/*++

Routine Description:

    Inform the automatic connection driver of a
    successful new connection.

Arguments:

    pNameAddr - a pointer to the remote name
    IpAddress - Source IP address of the connection

Return Value:
    None.

--*/

{
    ACD_ADDR        *pAddr = NULL;
    ACD_ADAPTER     *pAdapter = NULL;

    //
    // Notify the AcdDriver only if we have a valid Source address
    //
    // We can end up blowing the stack if we pre-allocate ACD_ADDR
    // and ACD_ADAPTER on the stack -- so allocate them dynamically!
    //
    if ((IpAddress) &&
        (pAddr = (ACD_ADDR *) NbtAllocMem(sizeof(ACD_ADDR),NBT_TAG('A'))) &&
        (pAdapter = (ACD_ADAPTER *) NbtAllocMem(sizeof(ACD_ADAPTER),NBT_TAG('A'))))
    {
        pAddr->fType = ACD_ADDR_NB;
        RtlCopyMemory(&pAddr->cNetbios, pNameAddr->Name, 15);

        pAdapter->fType = ACD_ADAPTER_IP;
        pAdapter->ulIpaddr = htonl(IpAddress);  // Get the source IP address of the connection.

        (*AcdDriverG.lpfnNewConnection) (pAddr, pAdapter);
    }

    if (pAddr)
    {
        CTEMemFree(pAddr);
    }

    if (pAdapter)
    {
        CTEMemFree(pAdapter);
    }
} // NbtNoteNewConnection



VOID
NbtAcdBind()
{
    NTSTATUS status;
    UNICODE_STRING nameString;
    IO_STATUS_BLOCK ioStatusBlock;
    PIRP pIrp;
    PFILE_OBJECT pAcdFileObject;
    PDEVICE_OBJECT pAcdDeviceObject;
    PACD_DRIVER pDriver = &AcdDriverG;

    //
    // Initialize the name of the automatic
    // connection device.
    //
    RtlInitUnicodeString(&nameString, ACD_DEVICE_NAME);
    //
    // Get the file and device objects for the
    // device.
    //
    status = IoGetDeviceObjectPointer(
               &nameString,
               SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE,
               &pAcdFileObject,
               &pAcdDeviceObject);
    if (status != STATUS_SUCCESS)
        return;
    //
    // Reference the device object.
    //
    ObReferenceObject(pAcdDeviceObject);
    //
    // Remove the reference IoGetDeviceObjectPointer()
    // put on the file object.
    //
    ObDereferenceObject(pAcdFileObject);
    //
    // Initialize our part of the ACD_DRIVER
    // structure.
    //
    KeInitializeSpinLock(&AcdDriverG.SpinLock);
    AcdDriverG.ulDriverId = ulDriverIdG;
    AcdDriverG.fEnabled = FALSE;
    //
    // Build a request to get the automatic
    // connection driver entry points.
    //
    pIrp = IoBuildDeviceIoControlRequest (IOCTL_INTERNAL_ACD_BIND,
                                          pAcdDeviceObject,
                                          (PVOID)&pDriver,
                                          sizeof (pDriver),
                                          NULL,
                                          0,
                                          TRUE,
                                          NULL,
                                          &ioStatusBlock);
    if (pIrp == NULL)
    {
        ObDereferenceObject(pAcdDeviceObject);
        return;
    }
    //
    // Submit the request to the
    // automatic connection driver.
    //
    status = IoCallDriver(pAcdDeviceObject, pIrp);
    fAcdLoadedG = (status == STATUS_SUCCESS);
    //
    // Close the device.
    //
    ObDereferenceObject(pAcdDeviceObject);
} // NbtAcdBind



VOID
NbtAcdUnbind()
{
    NTSTATUS status;
    UNICODE_STRING nameString;
    IO_STATUS_BLOCK ioStatusBlock;
    PIRP pIrp;
    PFILE_OBJECT pAcdFileObject;
    PDEVICE_OBJECT pAcdDeviceObject;
    PACD_DRIVER pDriver = &AcdDriverG;

    //
    // Don't bother to unbind if we
    // didn't successfully bind in the
    // first place.
    //
    if (!fAcdLoadedG)
    {
        return;
    }

    fAcdLoadedG = FALSE;

    //
    // Initialize the name of the automatic
    // connection device.
    //
    RtlInitUnicodeString(&nameString, ACD_DEVICE_NAME);
    //
    // Get the file and device objects for the
    // device.
    //
    status = IoGetDeviceObjectPointer (&nameString,
                                       SYNCHRONIZE|GENERIC_READ|GENERIC_WRITE,
                                       &pAcdFileObject,
                                       &pAcdDeviceObject);
    if (status != STATUS_SUCCESS)
    {
        return;
    }

    //
    // Reference the device object.
    //
    ObReferenceObject(pAcdDeviceObject);
    //
    // Remove the reference IoGetDeviceObjectPointer()
    // put on the file object.
    //
    ObDereferenceObject(pAcdFileObject);
    //
    // Build a request to unbind from
    // the automatic connection driver.
    //
    pIrp = IoBuildDeviceIoControlRequest (IOCTL_INTERNAL_ACD_UNBIND,
                                          pAcdDeviceObject,
                                          (PVOID)&pDriver,
                                          sizeof (pDriver),
                                          NULL,
                                          0,
                                          TRUE,
                                          NULL,
                                          &ioStatusBlock);
    if (pIrp == NULL)
    {
        ObDereferenceObject(pAcdDeviceObject);
        return;
    }

    //
    // Submit the request to the
    // automatic connection driver.
    //
    status = IoCallDriver(pAcdDeviceObject, pIrp);
    //
    // Close the device.
    //
    ObDereferenceObject(pAcdDeviceObject);
} // NbtAcdUnbind

#endif // RASAUTODIAL