/*++ Copyright (c) 1995 Microsoft Corporation Module Name: api.c Abstract: Exported routines to transports for automatic connection management. Author: Anthony Discolo (adiscolo) 17-Apr-1995 Environment: Kernel Mode Revision History: --*/ #include //#include #include #include #include #include #include #include #include "acdapi.h" #include "acddefs.h" #include "request.h" #include "mem.h" #include "debug.h" PACD_DISABLED_ADDRESSES pDisabledAddressesG; // // Driver enabled mode. The automatic // connection system service sets // this depending on whether a user // has logged in, and whether there's // general network connectivity. // BOOLEAN fAcdEnabledG; // // Spin lock for this module. // KSPIN_LOCK AcdSpinLockG; // // Event signaled when the AcdNotificationRequestThread // thread has a notification to process. // KEVENT AcdRequestThreadEventG; // // This is a list of one irp representing // a user-space process waiting to create a // new network connection given an address. // LIST_ENTRY AcdNotificationQueueG; // // This is a list of ACD_CONNECTION blocks representing // requests from transports about unsuccessful connection // attempts. There may be multiple ACD_COMPLETION block // linked onto the same ACD_CONNECTION, grouped by // address. // LIST_ENTRY AcdConnectionQueueG; // // This is a list of ACD_COMPLETION blocks representing // other requests from transports. // LIST_ENTRY AcdCompletionQueueG; // // The list of drivers that have binded // with us. // LIST_ENTRY AcdDriverListG; // // Count of outstanding irps - we need to maintain this // to limit the number of outstanding requests to acd // ow there is a potential of running out of non-paged // pool memory. // LONG lOutstandingRequestsG = 0; // ULONG count = 0; #define MAX_ACD_REQUESTS 100 // // BOOLEAN that enables autoconnect notifications // from redir/CSC. // extern BOOLEAN fAcdEnableRedirNotifs; // // Statistics // typedef struct _ACD_STATS { ULONG ulConnects; // connection attempts ULONG ulCancels; // connection cancels } ACD_STATS; ACD_STATS AcdStatsG[ACD_ADDR_MAX]; // // Forward declarations // VOID AcdPrintAddress( IN PACD_ADDR pAddr ); VOID ClearRequests( IN KIRQL irql ); // // External variables // extern ULONG ulAcdOpenCountG; VOID SetDriverMode( IN BOOLEAN fEnable ) /*++ DESCRIPTION Set the global driver mode value, and inform all bound transports of the change. Note: this call assumes AcdSpinLockG is already acquired. ARGUMENTS fEnable: the new driver mode value RETURN VALUE None. --*/ { KIRQL dirql; PLIST_ENTRY pEntry; PACD_DRIVER pDriver; // // Set the new global driver mode value. // fAcdEnabledG = fEnable; // // Inform all the drivers that have binded // with us of the new enable mode. // for (pEntry = AcdDriverListG.Flink; pEntry != &AcdDriverListG; pEntry = pEntry->Flink) { pDriver = CONTAINING_RECORD(pEntry, ACD_DRIVER, ListEntry); KeAcquireSpinLock(&pDriver->SpinLock, &dirql); pDriver->fEnabled = fEnable; KeReleaseSpinLock(&pDriver->SpinLock, dirql); } } // SetDriverMode NTSTATUS AcdEnable( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Set the enable mode for the driver. This determines which notifications it will pass up to the automatic connection system service. ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_BUFFER_TOO_SMALL: the supplied user buffer is too small to hold an ACD_ENABLE_MODE value. STATUS_SUCCESS: if the enabled bit was set successfully. --*/ { KIRQL irql; BOOLEAN fEnable; // // Verify the input buffer is sufficient to hold // a BOOLEAN structure. // if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (BOOLEAN)) { return STATUS_BUFFER_TOO_SMALL; } KeAcquireSpinLock(&AcdSpinLockG, &irql); fEnable = *(BOOLEAN *)pIrp->AssociatedIrp.SystemBuffer; SetDriverMode(fEnable); // // Clear all pending requests if // we are disabling the driver. // if (!fEnable) { ClearRequests(irql); if(pDisabledAddressesG->ulNumAddresses > 1) { PLIST_ENTRY pEntry; PACD_DISABLED_ADDRESS pDisabledAddress; while(pDisabledAddressesG->ulNumAddresses > 1) { pEntry = pDisabledAddressesG->ListEntry.Flink; RemoveEntryList( pDisabledAddressesG->ListEntry.Flink); pDisabledAddress = CONTAINING_RECORD(pEntry, ACD_DISABLED_ADDRESS, ListEntry); FREE_MEMORY(pDisabledAddress); pDisabledAddressesG->ulNumAddresses -= 1; } } } KeReleaseSpinLock(&AcdSpinLockG, irql); return STATUS_SUCCESS; } // AcdEnable VOID CancelNotification( IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp ) /*++ DESCRIPTION Generic cancel routine for irps on the AcdNotificationQueueG. ARGUMENTS pDeviceObject: unused pIrp: pointer to the irp to be cancelled. RETURN VALUE None. --*/ { KIRQL irql; UNREFERENCED_PARAMETER(pDeviceObject); // // Mark this irp as cancelled. // pIrp->IoStatus.Status = STATUS_CANCELLED; pIrp->IoStatus.Information = 0; // // Remove it from our list. // KeAcquireSpinLock(&AcdSpinLockG, &irql); RemoveEntryList(&pIrp->Tail.Overlay.ListEntry); KeReleaseSpinLock(&AcdSpinLockG, irql); // // Release the spinlock Io Subsystem acquired. // IoReleaseCancelSpinLock(pIrp->CancelIrql); // // Complete the request. // IoCompleteRequest(pIrp, IO_NO_INCREMENT); } // CancelNotification VOID AcdCancelNotifications() /*++ DESCRIPTION Cancel all irps on the AcdNotification queue. Although technically more than one user address space can be waiting for these notifications, we allow only one at this time. ARGUMENTS None. RETURN VALUE None. --*/ { KIRQL irql; PLIST_ENTRY pHead; PIRP pIrp; PIO_STACK_LOCATION pIrpSp; // // Complete all the irps in the list. // while ((pHead = ExInterlockedRemoveHeadList( &AcdNotificationQueueG, &AcdSpinLockG)) != NULL) { pIrp = CONTAINING_RECORD(pHead, IRP, Tail.Overlay.ListEntry); // // Mark this irp as cancelled. // pIrp->IoStatus.Status = STATUS_CANCELLED; pIrp->IoStatus.Information = 0; // // Complete the irp. // IoCompleteRequest(pIrp, IO_NO_INCREMENT); } } // AcdCancelNotifications NTSTATUS AcdWaitForNotification( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Enqueue an connection notification irp. This is done done by the automatic connection system service. ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_BUFFER_TOO_SMALL: the supplied user buffer is too small to hold an ACD_NOTIFICATION structure. STATUS_PENDING: if the ioctl was successfully enqueued STATUS_SUCCESS: if there is a notification already available --*/ { KIRQL irql, irql2; PLIST_ENTRY pHead; PACD_COMPLETION pCompletion; PACD_NOTIFICATION pNotification; PEPROCESS pProcess; // // Verify the output buffer is sufficient to hold // an ACD_NOTIFICATION structure - note that this // should only be called from rasuato service which // is a 64 bit process on win64. This should never // be called from a 32 bit process so no thunking is // done. // if (pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof (ACD_NOTIFICATION)) { return STATUS_BUFFER_TOO_SMALL; } IoAcquireCancelSpinLock(&irql); KeAcquireSpinLock(&AcdSpinLockG, &irql2); // // There is no notification available. // Mark the irp as pending and wait for one. // pIrp->IoStatus.Status = STATUS_PENDING; IoMarkIrpPending(pIrp); // // Set the irp's cancel routine. // IoSetCancelRoutine(pIrp, CancelNotification); // // Append the irp at the end of the // connection notification list. // InsertTailList(&AcdNotificationQueueG, &pIrp->Tail.Overlay.ListEntry); // // Signal the request thread there is // work to do. // KeSetEvent(&AcdRequestThreadEventG, 0, FALSE); KeReleaseSpinLock(&AcdSpinLockG, irql2); IoReleaseCancelSpinLock(irql); return STATUS_PENDING; } // AcdWaitForNotification BOOLEAN EqualAddress( IN PACD_ADDR p1, IN PACD_ADDR p2 ) { ULONG i; if (p1->fType != p2->fType) return FALSE; switch (p1->fType) { case ACD_ADDR_IP: return (p1->ulIpaddr == p2->ulIpaddr); case ACD_ADDR_IPX: return (BOOLEAN)RtlEqualMemory( &p1->cNode, &p2->cNode, ACD_ADDR_IPX_LEN); case ACD_ADDR_NB: IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(( "EqualAddress: NB: (%15s,%15s) result=%d\n", p1->cNetbios, p2->cNetbios, RtlEqualMemory(&p1->cNetbios, &p2->cNetbios, ACD_ADDR_NB_LEN - 1))); } return (BOOLEAN)RtlEqualMemory( &p1->cNetbios, &p2->cNetbios, ACD_ADDR_NB_LEN - 1); case ACD_ADDR_INET: for (i = 0; i < ACD_ADDR_INET_LEN; i++) { if (p1->szInet[i] != p2->szInet[i]) return FALSE; if (p1->szInet[i] == '\0' || p2->szInet[i] == '\0') break; } return TRUE; default: ASSERT(FALSE); break; } return FALSE; } // EqualAddress PACD_CONNECTION FindConnection( IN PACD_ADDR pAddr ) /*++ DESCRIPTION Search for a connection block with the specified address. ARGUMENTS pAddr: a pointer to the target ACD_ADDR RETURN VALUE A PACD_CONNECTION with the specified address, if found; otherwise NULL. --*/ { PLIST_ENTRY pEntry; PACD_CONNECTION pConnection; PACD_COMPLETION pCompletion; for (pEntry = AcdConnectionQueueG.Flink; pEntry != &AcdConnectionQueueG; pEntry = pEntry->Flink) { pConnection = CONTAINING_RECORD(pEntry, ACD_CONNECTION, ListEntry); pCompletion = CONTAINING_RECORD(pConnection->CompletionList.Flink, ACD_COMPLETION, ListEntry); if (EqualAddress(pAddr, &pCompletion->notif.addr)) return pConnection; } return NULL; } // FindConnection NTSTATUS AcdConnectionInProgress( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Refresh the progress indicator for the connection attempt. If the progress indicator is not updated by the user ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_INVALID_CONNECTION: if there is no connection attempt in progress. STATUS_SUCCESS --*/ { KIRQL irql; PACD_STATUS pStatus; PACD_CONNECTION pConnection; // // Verify the input buffer is sufficient to hold // a BOOLEAN structure. // if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (ACD_STATUS)) { return STATUS_BUFFER_TOO_SMALL; } // // Get the success code from the // connection attempt and pass it // to the completion routine. // pStatus = (PACD_STATUS)pIrp->AssociatedIrp.SystemBuffer; KeAcquireSpinLock(&AcdSpinLockG, &irql); pConnection = FindConnection(&pStatus->addr); if (pConnection != NULL) pConnection->fProgressPing = TRUE; KeReleaseSpinLock(&AcdSpinLockG, irql); return (pConnection != NULL) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; } // AcdConnectionInProgress BOOLEAN AddCompletionToConnection( IN PACD_COMPLETION pCompletion ) { PACD_CONNECTION pConnection; pConnection = FindConnection(&pCompletion->notif.addr); // // If the connection already exists, then add // the completion request to its list. // if (pConnection != NULL) { InsertTailList(&pConnection->CompletionList, &pCompletion->ListEntry); return TRUE; } // // This is a connection to a new address. // Create the connection block, enqueue it, // and start the connection timer. // ALLOCATE_CONNECTION(pConnection); if (pConnection == NULL) { // DbgPrint("AddCompletionToConnection: ExAllocatePool failed\n"); return FALSE; } pConnection->fNotif = FALSE; pConnection->fProgressPing = FALSE; pConnection->fCompleting = FALSE; pConnection->ulTimerCalls = 0; pConnection->ulMissedPings = 0; InitializeListHead(&pConnection->CompletionList); InsertHeadList(&pConnection->CompletionList, &pCompletion->ListEntry); InsertTailList(&AcdConnectionQueueG, &pConnection->ListEntry); return TRUE; } // AddCompletionToConnection BOOLEAN AddCompletionBlock( IN ULONG ulDriverId, IN PACD_ADDR pAddr, IN ULONG ulFlags, IN PACD_ADAPTER pAdapter, IN ACD_CONNECT_CALLBACK pProc, IN USHORT nArgs, IN PVOID *pArgs ) /*++ DESCRIPTION Create a block that represents an outstanding transport request waiting for an automatic connection. Link this block into the global list of outstanding transport requests. ARGUMENTS ulDriverId: a unique value for the transport driver pAddr: the network address of the connection ulFlags: connection flags pAdapter: pointer to adapter identifier pProc: a completion callback procedure nArgs: the number of parameters passed in pArgs pArgs: the parameters to pProc RETURN VALUE TRUE if successful, FALSE otherwise --*/ { PACD_COMPLETION pCompletion; ULONG i; if(lOutstandingRequestsG >= MAX_ACD_REQUESTS) { /* if(0 == (count % 5)) { count += 1; } */ return FALSE; } ALLOCATE_MEMORY( sizeof (ACD_COMPLETION) + ((nArgs - 1) * sizeof (PVOID)), pCompletion); if (pCompletion == NULL) { // DbgPrint("AcdAddCompletionBlock: ExAllocatePool failed\n"); return FALSE; } // // Copy the arguments into the information block. // pCompletion->ulDriverId = ulDriverId; pCompletion->fCanceled = FALSE; pCompletion->fCompleted = FALSE; RtlCopyMemory(&pCompletion->notif.addr, pAddr, sizeof (ACD_ADDR)); pCompletion->notif.Pid = PsGetCurrentProcessId(); // DbgPrint("ACD: request by Process %lx\n", // pCompletion->notif.Pid); pCompletion->notif.ulFlags = ulFlags; if (pAdapter != NULL) { RtlCopyMemory( &pCompletion->notif.adapter, pAdapter, sizeof (ACD_ADAPTER)); } else RtlZeroMemory(&pCompletion->notif.adapter, sizeof (ACD_ADAPTER)); pCompletion->pProc = pProc; pCompletion->nArgs = nArgs; for (i = 0; i < nArgs; i++) pCompletion->pArgs[i] = pArgs[i]; // // If this is a unsuccessful connection request, // then insert it onto the connection queue for // that address; Otherwise, insert it into the list // for all other requests. // if (ulFlags & ACD_NOTIFICATION_SUCCESS) { InsertTailList(&AcdCompletionQueueG, &pCompletion->ListEntry); } else { if (!AddCompletionToConnection(pCompletion)) { FREE_MEMORY(pCompletion); return FALSE; } } lOutstandingRequestsG++; // // Inform the request thread // there is new work to do. // KeSetEvent(&AcdRequestThreadEventG, 0, FALSE); return TRUE; } // AddCompletionBlock VOID AcdNewConnection( IN PACD_ADDR pAddr, IN PACD_ADAPTER pAdapter ) { KIRQL irql; IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdNewConnection: ")); AcdPrintAddress(pAddr); AcdPrint(("\n")); } // // If the driver is disabled, then fail // all requests. // if (!fAcdEnabledG) { IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdNewConnection: driver disabled\n")); } return; } // // Acquire our spin lock. // KeAcquireSpinLock(&AcdSpinLockG, &irql); // // Allocate a new completion block. // AddCompletionBlock( 0, pAddr, ACD_NOTIFICATION_SUCCESS, pAdapter, NULL, 0, NULL); // // Release the spin lock. // KeReleaseSpinLock(&AcdSpinLockG, irql); } // AcdNewConnection BOOLEAN AcdStartConnection( IN ULONG ulDriverId, IN PACD_ADDR pAddr, IN ULONG ulFlags, IN ACD_CONNECT_CALLBACK pProc, IN USHORT nArgs, IN PVOID *pArgs ) /*++ DESCRIPTION Create a new connection completion block, and enqueue it on the list of network requests to be completed when a new network connection has been created. ARGUMENTS ulDriverId: a unique value for the transport driver pAddr: the address of the connection attempt ulFlags: connection flags pProc: the transport callback to be called when a new connection has been created. nArgs: the number of arguments to pProc. pArgs: a pointer to an array of pProc's parameters RETURN VALUE TRUE if successful, FALSE otherwise. --*/ { BOOLEAN fSuccess = FALSE, fFound; KIRQL irql; ULONG ulAttributes = 0; PACD_COMPLETION pCompletion; PCHAR psz, pszOrg; ACD_ADDR szOrgAddr; IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdStartConnection: ")); AcdPrintAddress(pAddr); AcdPrint((", ulFlags=0x%x\n", ulFlags)); } // // If the driver is disabled, then fail // all requests. // if (!fAcdEnabledG) { IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdStartConnection: driver disabled\n")); } return FALSE; } // // Validate the address type. // if ((ULONG)pAddr->fType >= ACD_ADDR_MAX) { AcdPrint(("AcdStartConnection: bad address type (%d)\n", pAddr->fType)); return FALSE; } // // Acquire our spin lock. // KeAcquireSpinLock(&AcdSpinLockG, &irql); // // Update statistics. // AcdStatsG[pAddr->fType].ulConnects++; // // Allocate a new completion block. // fSuccess = AddCompletionBlock( ulDriverId, pAddr, ulFlags, NULL, pProc, nArgs, pArgs); // // Release the spin lock. // KeReleaseSpinLock(&AcdSpinLockG, irql); return fSuccess; } // AcdStartConnection BOOLEAN AcdCancelConnection( IN ULONG ulDriverId, IN PACD_ADDR pAddr, IN ACD_CANCEL_CALLBACK pProc, IN PVOID pArg ) /*++ DESCRIPTION Remove a previously enqueued connection information block from the list. ARGUMENTS ulDriverId: a unique value for the transport driver pAddr: the address of the connection attempt pProc: the enumerator procecdure pArg: the enumerator procedure argument RETURN VALUE None. --*/ { BOOLEAN fCanceled = FALSE; KIRQL irql; PLIST_ENTRY pEntry; PACD_CONNECTION pConnection; PACD_COMPLETION pCompletion; IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdCancelConnection: ulDriverId=0x%x, ")); AcdPrintAddress(pAddr); AcdPrint(("\n")); } KeAcquireSpinLock(&AcdSpinLockG, &irql); // // Enumerate the list looking for // the information block with the // supplied parameters. // pConnection = FindConnection(pAddr); if (pConnection != NULL) { for (pEntry = pConnection->CompletionList.Flink; pEntry != &pConnection->CompletionList; pEntry = pEntry->Flink) { pCompletion = CONTAINING_RECORD(pEntry, ACD_COMPLETION, ListEntry); // // If we have a match, remove it from // the list and free the information block. // if (pCompletion->ulDriverId == ulDriverId && !pCompletion->fCanceled && !pCompletion->fCompleted) { IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(( "AcdCancelConnection: pCompletion=0x%x\n", pCompletion)); } if ((*pProc)( pArg, pCompletion->notif.ulFlags, pCompletion->pProc, pCompletion->nArgs, pCompletion->pArgs)) { pCompletion->fCanceled = TRUE; fCanceled = TRUE; // // Update statistics. // AcdStatsG[pAddr->fType].ulCancels++; break; } } } } KeReleaseSpinLock(&AcdSpinLockG, irql); return fCanceled; } // AcdCancelConnection VOID ConnectAddressComplete( BOOLEAN fSuccess, PVOID *pArgs ) { PIRP pIrp = pArgs[0]; PIO_STACK_LOCATION pIrpSp = pArgs[1]; KIRQL irql; // // Complete the request. // pIrp->IoStatus.Status = fSuccess ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; pIrp->IoStatus.Information = 0; IoAcquireCancelSpinLock(&irql); IoSetCancelRoutine(pIrp, NULL); IoReleaseCancelSpinLock(irql); IoCompleteRequest(pIrp, IO_NO_INCREMENT); } // ConnectAddressComplete BOOLEAN CancelConnectAddressCallback( IN PVOID pArg, IN ULONG ulFlags, IN ACD_CONNECT_CALLBACK pProc, IN USHORT nArgs, IN PVOID *pArgs ) { return (nArgs == 2 && pArgs[0] == pArg); } // CancelConnectAddressCallback VOID CancelConnectAddress( PDEVICE_OBJECT pDevice, PIRP pIrp ) { KIRQL irql; PACD_NOTIFICATION pNotification; ASSERT(pIrp->Cancel); // // Remove our outstanding request. // pNotification = (PACD_NOTIFICATION)pIrp->AssociatedIrp.SystemBuffer; // // If we can't find the request on the connection // list, then it has already been completed. // if (!AcdCancelConnection( 0, &pNotification->addr, CancelConnectAddressCallback, pIrp)) { IoReleaseCancelSpinLock(pIrp->CancelIrql); return; } // // Mark this irp as cancelled. // pIrp->IoStatus.Status = STATUS_CANCELLED; pIrp->IoStatus.Information = 0; IoSetCancelRoutine(pIrp, NULL); // // Release the spin lock the I/O system acquired. // IoReleaseCancelSpinLock(pIrp->CancelIrql); // // Complete the I/O request. // IoCompleteRequest(pIrp, IO_NO_INCREMENT); } // CancelConnectAddress BOOLEAN FDisabledAddress( IN ACD_ADDR *pAddr ) { BOOLEAN bRet = FALSE; KIRQL irql; PACD_DISABLED_ADDRESS pDisabledAddress; PLIST_ENTRY pEntry; KeAcquireSpinLock(&AcdSpinLockG, &irql); if(!fAcdEnabledG) { KeReleaseSpinLock(&AcdSpinLockG, irql); return FALSE; } for (pEntry = pDisabledAddressesG->ListEntry.Flink; pEntry != &pDisabledAddressesG->ListEntry; pEntry = pEntry->Flink) { pDisabledAddress = CONTAINING_RECORD(pEntry, ACD_DISABLED_ADDRESS, ListEntry); if(pDisabledAddress->EnableAddress.fDisable && RtlEqualMemory( pDisabledAddress->EnableAddress.addr.szInet, pAddr->szInet, ACD_ADDR_INET_LEN)) { bRet = TRUE; } } KeReleaseSpinLock(&AcdSpinLockG, irql); //DbgPrint("FDisabledAddress: Address %s. Disabled=%d\n", // pAddr->szInet, bRet); return bRet; } NTSTATUS AcdConnectAddress( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Manufacture a call to ourselves to simulate a transport requesting an automatic connection. This allows a user address space to initiate an automatic connection. ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_BUFFER_TOO_SMALL: the supplied user buffer is too small to hold an ACD_NOTIFICATION structure. STATUS_UNSUCCESSFUL: an error occurred initiating the automatic connection. STATUS_PENDING: success --*/ { NTSTATUS status = STATUS_UNSUCCESSFUL; KIRQL irql; PACD_NOTIFICATION pNotification; PVOID pArgs[2]; ACD_ADDR *pAddr; ACD_ADAPTER *pAdapter; ULONG ulFlags; // // Verify the input buffer is sufficient to hold // an ACD_NOTIFICATION (_32) structure. // #if defined (_WIN64) ACD_NOTIFICATION_32 *pNotification32; if(IoIs32bitProcess(pIrp)) { if(pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(ACD_NOTIFICATION_32)) { return STATUS_BUFFER_TOO_SMALL; } } else #endif if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (ACD_NOTIFICATION)) { return STATUS_BUFFER_TOO_SMALL; } // // Doing the whole 32 bit stuff for correctness. The code will // work even if left alone i.e casting the systembuffer to // ACD_NOTIFICATION * [raos]. // #if defined (_WIN64) if(IoIs32bitProcess(pIrp)) { pNotification32 = (PACD_NOTIFICATION_32) pIrp->AssociatedIrp.SystemBuffer; pAddr = &pNotification32->addr; pAdapter = &pNotification32->adapter; ulFlags = pNotification32->ulFlags; } else #endif { pNotification = (PACD_NOTIFICATION)pIrp->AssociatedIrp.SystemBuffer; pAddr = &pNotification->addr; pAdapter = &pNotification->adapter; ulFlags = pNotification->ulFlags; } if(FDisabledAddress(pAddr)) { //DbgPrint("AcdConnectAddress: returning because address is disabled\n"); return status; } pArgs[0] = pIrp; pArgs[1] = pIrpSp; // // Start the connection. // IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdConnectAddress: ")); AcdPrintAddress(pAddr); AcdPrint((", ulFlags=0x%x\n", ulFlags)); } if (ulFlags & ACD_NOTIFICATION_SUCCESS) { AcdNewConnection( pAddr, pAdapter); status = STATUS_SUCCESS; } else { IoAcquireCancelSpinLock(&irql); if (AcdStartConnection( 0, pAddr, ulFlags, ConnectAddressComplete, 2, pArgs)) { // // We enqueued the request successfully. // Mark the irp as pending. // IoSetCancelRoutine(pIrp, CancelConnectAddress); IoMarkIrpPending(pIrp); status = STATUS_PENDING; } IoReleaseCancelSpinLock(irql); } return status; } // AcdConnectAddress NTSTATUS AcdQueryState( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) { KIRQL irql; if(pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(BOOLEAN)) { return STATUS_BUFFER_TOO_SMALL; } KeAcquireSpinLock(&AcdSpinLockG, &irql); if(fAcdEnableRedirNotifs) { *(BOOLEAN *)pIrp->AssociatedIrp.SystemBuffer = fAcdEnabledG; } else { *(BOOLEAN *)pIrp->AssociatedIrp.SystemBuffer = FALSE; } pIrp->IoStatus.Information = sizeof(BOOLEAN); KeReleaseSpinLock(&AcdSpinLockG, irql); // KdPrint(("AcdQueryState: returned %d\n", // *(BOOLEAN *)pIrp->AssociatedIrp.SystemBuffer)); return STATUS_SUCCESS; } VOID AcdSignalCompletionCommon( IN PACD_CONNECTION pConnection, IN BOOLEAN fSuccess ) { KIRQL irql; PLIST_ENTRY pEntry; PACD_COMPLETION pCompletion; BOOLEAN fFound; IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(( "AcdSignalCompletionCommon: pConnection=0x%x, fCompleting=%d\n", pConnection, pConnection->fCompleting)); } again: fFound = FALSE; // // Acquire our lock and look for // the next uncompleted request. // KeAcquireSpinLock(&AcdSpinLockG, &irql); for (pEntry = pConnection->CompletionList.Flink; pEntry != &pConnection->CompletionList; pEntry = pEntry->Flink) { pCompletion = CONTAINING_RECORD(pEntry, ACD_COMPLETION, ListEntry); IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(( "AcdSignalCompletionCommon: pCompletion=0x%x, fCanceled=%d, fCompleted=%d\n", pCompletion, pCompletion->fCanceled, pCompletion->fCompleted)); } // // Only complete this request if it // hasn't already been completed // or canceled. // if (!pCompletion->fCanceled && !pCompletion->fCompleted) { pCompletion->fCompleted = TRUE; fFound = TRUE; break; } } // // If there are no more requests to // complete then remove this connection // from the connection list and free its // memory. // if (!fFound) { RemoveEntryList(&pConnection->ListEntry); while (!IsListEmpty(&pConnection->CompletionList)) { pEntry = RemoveHeadList(&pConnection->CompletionList); pCompletion = CONTAINING_RECORD(pEntry, ACD_COMPLETION, ListEntry); FREE_MEMORY(pCompletion); lOutstandingRequestsG--; } FREE_CONNECTION(pConnection); // // Signal the request thread that // the connection list has changed. // KeSetEvent(&AcdRequestThreadEventG, 0, FALSE); } // // Release our lock. // KeReleaseSpinLock(&AcdSpinLockG, irql); // // If we found a request, then // call its completion proc. // if (fFound) { IF_ACDDBG(ACD_DEBUG_CONNECTION) { AcdPrint(("AcdSignalCompletionCommon: pCompletion=0x%x, ", pCompletion)); AcdPrintAddress(&pCompletion->notif.addr); AcdPrint(("\n")); } (*pCompletion->pProc)(fSuccess, pCompletion->pArgs); // // Look for another request. // goto again; } } // AcdSignalCompletionCommon NTSTATUS AcdSignalCompletion( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION For each thread waiting on the AcdCompletionQueueG, call the transport-dependent callback to retry the connection attempt and complete the irp. ARGUMENTS pIrp: unused pIrpSp: unused RETURN VALUE STATUS_SUCCESS --*/ { KIRQL irql; PACD_STATUS pStatus; PACD_CONNECTION pConnection; BOOLEAN fFound = FALSE; // // Verify the input buffer is sufficient to hold // a BOOLEAN structure. // if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (ACD_STATUS)) { return STATUS_BUFFER_TOO_SMALL; } // // Get the success code from the // connection attempt and pass it // to the completion routine. // pStatus = (PACD_STATUS)pIrp->AssociatedIrp.SystemBuffer; KeAcquireSpinLock(&AcdSpinLockG, &irql); pConnection = FindConnection(&pStatus->addr); if (pConnection != NULL && !pConnection->fCompleting) { // // Set the completion-in-progress flag so // this request cannot be timed-out after // we release the spin lock. // pConnection->fCompleting = TRUE; fFound = TRUE; } KeReleaseSpinLock(&AcdSpinLockG, irql); // // If we didn't find the connection block, // or the completion was already in progress, // then return an error. // if (!fFound) return STATUS_UNSUCCESSFUL; AcdSignalCompletionCommon(pConnection, pStatus->fSuccess); return STATUS_SUCCESS; } // AcdSignalCompletion NTSTATUS AcdpEnableAddress(PACD_ENABLE_ADDRESS pEnableAddress) { PLIST_ENTRY pEntry = NULL; PACD_DISABLED_ADDRESS pDisabledAddress = NULL; NTSTATUS status = STATUS_SUCCESS; KIRQL irql; KeAcquireSpinLock(&AcdSpinLockG, &irql); ASSERT(pDisabledAddressesG->ulNumAddresses >= 1); if(pDisabledAddressesG->ulNumAddresses == 1) { pDisabledAddress = CONTAINING_RECORD(pDisabledAddressesG->ListEntry.Flink, ACD_DISABLED_ADDRESS, ListEntry); RtlZeroMemory(&pDisabledAddress->EnableAddress, sizeof(ACD_ENABLE_ADDRESS)); //DbgPrint("AcdEnableAddress: reenabling\n"); } else if(pDisabledAddressesG->ulNumAddresses > 1) { for (pEntry = pDisabledAddressesG->ListEntry.Flink; pEntry != &pDisabledAddressesG->ListEntry; pEntry = pEntry->Flink) { pDisabledAddress = CONTAINING_RECORD(pEntry, ACD_DISABLED_ADDRESS, ListEntry); if(RtlEqualMemory( pDisabledAddress->EnableAddress.addr.szInet, pEnableAddress->addr.szInet, ACD_ADDR_INET_LEN)) { break; } } if(pEntry != &pDisabledAddressesG->ListEntry) { //DbgPrint("AcdEnableAddress: removing %s (%p) from disabled list\n", // pDisabledAddress->EnableAddress.addr.szInet, // pDisabledAddress); RemoveEntryList(pEntry); pDisabledAddressesG->ulNumAddresses -= 1; } else { pEntry = NULL; } } KeReleaseSpinLock(&AcdSpinLockG, irql); if(pEntry != NULL) { FREE_MEMORY(pDisabledAddress); } return status; } NTSTATUS AcdpDisableAddress(PACD_ENABLE_ADDRESS pEnableAddress) { PACD_DISABLED_ADDRESS pDisabledAddress; NTSTATUS status = STATUS_SUCCESS; KIRQL irql; KeAcquireSpinLock(&AcdSpinLockG, &irql); ASSERT(pDisabledAddressesG->ulNumAddresses >= 1); pDisabledAddress = CONTAINING_RECORD(pDisabledAddressesG->ListEntry.Flink, ACD_DISABLED_ADDRESS, ListEntry); if(!pDisabledAddress->EnableAddress.fDisable) { RtlCopyMemory(&pDisabledAddress->EnableAddress, pEnableAddress, sizeof(ACD_ENABLE_ADDRESS)); KeReleaseSpinLock(&AcdSpinLockG, irql); } else if(pDisabledAddressesG->ulNumAddresses < pDisabledAddressesG->ulMaxAddresses) { KeReleaseSpinLock(&AcdSpinLockG, irql); ALLOCATE_MEMORY(sizeof(ACD_DISABLED_ADDRESS), pDisabledAddress); if(pDisabledAddress != NULL) { RtlCopyMemory(&pDisabledAddress->EnableAddress, pEnableAddress, sizeof(ACD_ENABLE_ADDRESS)); //DbgPrint("AcdEnableAddress: Adding %p to list \n", // pDisabledAddress) ; KeAcquireSpinLock(&AcdSpinLockG, &irql); InsertTailList(&pDisabledAddressesG->ListEntry, &pDisabledAddress->ListEntry); pDisabledAddressesG->ulNumAddresses += 1; KeReleaseSpinLock(&AcdSpinLockG, irql); } else { status = STATUS_INSUFFICIENT_RESOURCES; } } //DbgPrint("AcdDisableAddress: Disabling %s, status=0x%x\n", // pEnableAddress->addr.szInet, status); return status; } NTSTATUS AcdEnableAddress( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) { PACD_ENABLE_ADDRESS pEnableAddress; KIRQL irql; PACD_DISABLED_ADDRESS pDisabledAddress = NULL; NTSTATUS Status = STATUS_SUCCESS; if(pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof(ACD_ENABLE_ADDRESS)) { return STATUS_BUFFER_TOO_SMALL; } if(!fAcdEnabledG) { return STATUS_UNSUCCESSFUL; } pEnableAddress = (PACD_ENABLE_ADDRESS)pIrp->AssociatedIrp.SystemBuffer; if(pEnableAddress->fDisable) { Status = AcdpDisableAddress(pEnableAddress); } else { Status = AcdpEnableAddress(pEnableAddress); } //DbgPrint("AcdEnableAddress. status=0x%x\n", Status); return Status; } VOID ClearRequests( IN KIRQL irql ) /*++ DESCRIPTION Complete all pending requests with failure status. This call assumes the AcdSpinLockG is already held, and it returns with it held. ARGUMENTS None. RETURN VALUE None. --*/ { PLIST_ENTRY pHead, pEntry; PACD_COMPLETION pCompletion; PACD_CONNECTION pConnection; again: // // Complete all pending connections with // an error. // for (pEntry = AcdConnectionQueueG.Flink; pEntry != &AcdConnectionQueueG; pEntry = pEntry->Flink) { pConnection = CONTAINING_RECORD(pEntry, ACD_CONNECTION, ListEntry); if (!pConnection->fCompleting) { pConnection->fCompleting = TRUE; // // We need to release our lock to // complete the request. // KeReleaseSpinLock(&AcdSpinLockG, irql); // // Complete the request. // AcdSignalCompletionCommon(pConnection, FALSE); // // Check for more uncompleted requests. // KeAcquireSpinLock(&AcdSpinLockG, &irql); goto again; } } // // Clear out all other pending requests. // while (!IsListEmpty(&AcdCompletionQueueG)) { pHead = RemoveHeadList(&AcdCompletionQueueG); pCompletion = CONTAINING_RECORD(pHead, ACD_COMPLETION, ListEntry); FREE_MEMORY(pCompletion); lOutstandingRequestsG--; } } // ClearRequests VOID AcdReset() /*++ DESCRIPTION Complete all pending requests with failure status. This is called when the reference count on the driver object goes to zero, and prevents stale requests from being presented to the system service if it is restarted when there are pending completion requests. ARGUMENTS None. RETURN VALUE None. --*/ { KIRQL irql; PLIST_ENTRY pHead, pEntry; PACD_COMPLETION pCompletion; PACD_CONNECTION pConnection; KeAcquireSpinLock(&AcdSpinLockG, &irql); // // Reset the notification mode to disabled. // SetDriverMode(FALSE); // // Complete all pending connections with // an error. // ClearRequests(irql); KeReleaseSpinLock(&AcdSpinLockG, irql); } // AcdReset NTSTATUS AcdBind( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Return the list of entry points to a client transport driver. ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_BUFFER_TOO_SMALL if the supplied SystemBuffer is too small. STATUS_SUCCESS otherwise. --*/ { NTSTATUS status; PACD_DRIVER *ppDriver, pDriver; KIRQL irql, dirql; // // Verify the input buffer a pointer to // the driver's ACD_DRIVER structure. // if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (PACD_DRIVER)) { return STATUS_BUFFER_TOO_SMALL; } ppDriver = (PACD_DRIVER *)pIrp->AssociatedIrp.SystemBuffer; pDriver = *ppDriver; #if DBG // // Selectively bind with some transports. // switch (pDriver->ulDriverId) { case 'Nbf ': break; case 'Tcp ': #ifdef notdef DbgPrint("AcdBind: ignoring Tcp\n"); pDriver->fEnabled = FALSE; pIrp->IoStatus.Information = 0; return STATUS_SUCCESS; #endif break; case 'Nbi ': #ifdef notdef DbgPrint("AcdBind: ignoring Nbi\n"); pDriver->fEnabled = FALSE; pIrp->IoStatus.Information = 0; return STATUS_SUCCESS; #endif break; } #endif // // Fill in the entry point structure. // pDriver->lpfnNewConnection = AcdNewConnection; pDriver->lpfnStartConnection = AcdStartConnection; pDriver->lpfnCancelConnection = AcdCancelConnection; // // Insert this block into our driver list. // KeAcquireSpinLock(&AcdSpinLockG, &irql); KeAcquireSpinLock(&pDriver->SpinLock, &dirql); pDriver->fEnabled = fAcdEnabledG; KeReleaseSpinLock(&pDriver->SpinLock, dirql); InsertTailList(&AcdDriverListG, &pDriver->ListEntry); KeReleaseSpinLock(&AcdSpinLockG, irql); // // No data should be copied back. // pIrp->IoStatus.Information = 0; return STATUS_SUCCESS; } // AcdBind NTSTATUS AcdUnbind( IN PIRP pIrp, IN PIO_STACK_LOCATION pIrpSp ) /*++ DESCRIPTION Unbind a client transport driver. ARGUMENTS pIrp: a pointer to the irp to be enqueued. pIrpSp: a pointer to the current irp stack. RETURN VALUE STATUS_BUFFER_TOO_SMALL if the supplied SystemBuffer is too small. STATUS_SUCCESS otherwise. --*/ { KIRQL irql, dirql; PLIST_ENTRY pEntry, pEntry2; PACD_DRIVER *ppDriver, pDriver; PACD_CONNECTION pConnection; PACD_COMPLETION pCompletion; // // Verify the input buffer a pointer to // the driver's ACD_DRIVER structure. // if (pIrpSp->Parameters.DeviceIoControl.InputBufferLength < sizeof (PACD_DRIVER)) { return STATUS_BUFFER_TOO_SMALL; } ppDriver = (PACD_DRIVER *)pIrp->AssociatedIrp.SystemBuffer; pDriver = *ppDriver; KeAcquireSpinLock(&AcdSpinLockG, &irql); // // Enumerate the list looking for // any connection request initiated by the // specified driver. // for (pEntry = AcdConnectionQueueG.Flink; pEntry != &AcdConnectionQueueG; pEntry = pEntry->Flink) { pConnection = CONTAINING_RECORD(pEntry, ACD_CONNECTION, ListEntry); for (pEntry2 = pConnection->CompletionList.Flink; pEntry2 != &pConnection->CompletionList; pEntry2 = pEntry2->Flink) { pCompletion = CONTAINING_RECORD(pEntry2, ACD_COMPLETION, ListEntry); // // If we have a match, cancel it. // if (pCompletion->ulDriverId == pDriver->ulDriverId) pCompletion->fCanceled = TRUE; } } // // Set this driver's enable mode to ACD_ENABLE_NONE. // KeAcquireSpinLock(&pDriver->SpinLock, &dirql); pDriver->fEnabled = FALSE; KeReleaseSpinLock(&pDriver->SpinLock, dirql); // // Remove this driver from the list. // RemoveEntryList(&pDriver->ListEntry); KeReleaseSpinLock(&AcdSpinLockG, irql); // // No data should be copied back. // pIrp->IoStatus.Information = 0; return STATUS_SUCCESS; } // AcdUnbind VOID AcdPrintAddress( IN PACD_ADDR pAddr ) { #if DBG PUCHAR puc; switch (pAddr->fType) { case ACD_ADDR_IP: puc = (PUCHAR)&pAddr->ulIpaddr; AcdPrint(("IP: %d.%d.%d.%d", puc[0], puc[1], puc[2], puc[3])); break; case ACD_ADDR_IPX: AcdPrint(( "IPX: %02x:%02x:%02x:%02x:%02x:%02x", pAddr->cNode[0], pAddr->cNode[1], pAddr->cNode[2], pAddr->cNode[3], pAddr->cNode[4], pAddr->cNode[5])); break; case ACD_ADDR_NB: AcdPrint(("NB: %15.15s", pAddr->cNetbios)); break; case ACD_ADDR_INET: AcdPrint(("INET: %s", pAddr->szInet)); break; default: AcdPrint(("UNKNOWN: ????")); break; } #endif } // AcdPrintAddress