/*++ Copyright (c) 2000 Microsoft Corporation Module Name: ftpio.c Abstract: This module contains code for the FTP transparent proxy's network I/O completion routines. Author: Qiang Wang (qiangw) 10-Apr-2000 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include "ftpmsg.h" VOID FtpAcceptCompletionRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ) /*++ Routine Description: This routine is invoked upon completion of an accept operation on a FTP transparent proxy stream socket. Arguments: ErrorCode - Win32 status code for the I/O operation BytesTransferred - number of bytes in 'Bufferp' Bufferp - holds the local and remote IP address and port for the connection. Return Value: none. Environment: Runs in the context of a worker-thread which has just dequeued an I/O completion packet from the common I/O completion port with which our stream sockets are associated. A reference to the component will have been made on our behalf by 'NhAcceptStreamSocket'. A reference to the interface will have been made on our behalf by whoever issued the I/O request. --*/ { SOCKET AcceptedSocket; PFTP_CONNECTION Connectionp; ULONG Error; PFTP_INTERFACE Interfacep; SOCKET ListeningSocket; PROFILE("FtpAcceptCompletionRoutine"); do { AcceptedSocket = (SOCKET)Bufferp->Socket; Interfacep = (PFTP_INTERFACE)Bufferp->Context; ListeningSocket = (SOCKET)Bufferp->Context2; // // Acquire three additional references to the interface // for the followup requests that we will issue below, // and lock the interface. // EnterCriticalSection(&FtpInterfaceLock); if (!FTP_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&FtpInterfaceLock); NhReleaseBuffer(Bufferp); NhDeleteStreamSocket(AcceptedSocket); break; } FTP_REFERENCE_INTERFACE(Interfacep); FTP_REFERENCE_INTERFACE(Interfacep); LeaveCriticalSection(&FtpInterfaceLock); ACQUIRE_LOCK(Interfacep); // // Process the accept-completion. // First look for an error code. If an error occurred // and the interface is no longer active, end the completion-handling. // Otherwise, attempt to reissue the accept-request. // if (ErrorCode) { NhTrace( TRACE_FLAG_IO, "FtpAcceptCompletionRoutine: error %d for interface %d", ErrorCode, Interfacep->Index ); // // See if the interface is still active and, if so, reissue // the accept-request. Since we will not be creating an active // endpoint, we won't need the second reference to the interface. // FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); if (!FTP_INTERFACE_ACTIVE(Interfacep)) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhDeleteStreamSocket(AcceptedSocket); } else { // // Reissue the accept-request. Note that the callee is now // responsible for the reference we made to the interface. // Error = FtpAcceptConnectionInterface( Interfacep, ListeningSocket, AcceptedSocket, Bufferp, NULL ); RELEASE_LOCK(Interfacep); if (Error) { NhReleaseBuffer(Bufferp); NhDeleteStreamSocket(AcceptedSocket); NhTrace( TRACE_FLAG_IO, "FtpAcceptCompletionRoutine: error %d reissuing accept", Error ); } } break; } // // Now see if the interface is operational. // If it isn't, we need to destroy the accepted socket // and return control. // if (!FTP_INTERFACE_ACTIVE(Interfacep)) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhDeleteStreamSocket(AcceptedSocket); NhTrace( TRACE_FLAG_IO, "FtpAcceptCompletionRoutine: interface %d inactive", Interfacep->Index ); InterlockedIncrement( reinterpret_cast(&FtpStatistics.ConnectionsDropped) ); break; } // // We now create a 'FTP_CONNECTION' for the new connection, // in the process launching operations for the connection. // The connection management module will handle the accepted socket // from here onward, and is responsible for the references to the // interface that were made above. // NhTrace( TRACE_FLAG_IO, "FtpAcceptCompletionRoutine: socket %d accepting connection", ListeningSocket ); Error = FtpCreateConnection( Interfacep, ListeningSocket, AcceptedSocket, Bufferp->Buffer, &Connectionp ); if (Error) { InterlockedIncrement( reinterpret_cast(&FtpStatistics.ConnectionsDropped) ); } else { InterlockedIncrement( reinterpret_cast(&FtpStatistics.ConnectionsAccepted) ); } // // Finally, issue an accept operation for the next connection-request // on the listening socket. Note that the callee is responsible // for releasing the reference to the interface in case of a failure. // Error = FtpAcceptConnectionInterface( Interfacep, ListeningSocket, INVALID_SOCKET, Bufferp, NULL ); RELEASE_LOCK(Interfacep); if (Error) { NhReleaseBuffer(Bufferp); } } while(FALSE); FTP_DEREFERENCE_INTERFACE(Interfacep); DEREFERENCE_FTP(); } // FtpAcceptCompletionRoutine VOID FtpCloseEndpointNotificationRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ) /*++ Routine Description: This routine is invoked upon notification of a close operation on a FTP transparent proxy stream socket. Arguments: ErrorCode - Win32 status code for the I/O operation BytesTransferred - number of bytes in 'Bufferp' Bufferp - holds context information for the closed socket. Note that we are not allowed to release this buffer here. Return Value: none. Environment: Runs in the context of a wait-thread. A reference to the component will have been made on our behalf by 'NhAcceptStreamSocket' or 'NhConnectStreamSocket'. A reference to the interface will have been made on our behalf by whoever issued the I/O request. Both of these references are released here. --*/ { SOCKET ClosedSocket; ULONG EndpointId; PFTP_INTERFACE Interfacep; PROFILE("FtpCloseEndpointNotificationRoutine"); do { ClosedSocket = (SOCKET)Bufferp->Socket; Interfacep = (PFTP_INTERFACE)Bufferp->Context; EndpointId = PtrToUlong(Bufferp->Context2); NhTrace( TRACE_FLAG_IO, "FtpCloseEndpointNotificationRoutine: endpoint %d socket %d " "closed, error %d", EndpointId, ClosedSocket, ErrorCode ); #if 0 PFTP_ENDPOINT Endpointp; // // Lock the interface, and retrieve the endpoint whose socket has // been closed. // ACQUIRE_LOCK(Interfacep); Endpointp = FtpLookupInterfaceEndpoint(Interfacep, EndpointId, NULL); if (Endpointp) { FtpCloseActiveEndpoint(Endpointp, ClosedSocket); } RELEASE_LOCK(Interfacep); #endif } while(FALSE); FTP_DEREFERENCE_INTERFACE(Interfacep); DEREFERENCE_FTP(); } // FtpCloseEndpointNotificationRoutine VOID FtpConnectEndpointCompletionRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ) /*++ Routine Description: This routine is invoked upon completion of a connect operation on a FTP transparent proxy stream socket. Arguments: ErrorCode - Win32 status code for the I/O operation BytesTransferred - number of bytes in 'Bufferp' Bufferp - holds the context information for the endpoint. Note that we are not allowed to release this buffer here. Return Value: none. Environment: Runs in the context of a wait-thread. A reference to the component will have been made on our behalf by 'NhConnectStreamSocket'. A reference to the interface will have been made on our behalf by whoever issued the I/O request. Neither of these references may be released here; they are both released in the close-notification routine, which we are guaranteed will be invoked. (Eventually.) --*/ { SOCKET ConnectedSocket; ULONG EndpointId; PFTP_ENDPOINT Endpointp; ULONG Error; PFTP_INTERFACE Interfacep; PROFILE("FtpConnectEndpointCompletionRoutine"); do { ConnectedSocket = (SOCKET)Bufferp->Socket; Interfacep = (PFTP_INTERFACE)Bufferp->Context; EndpointId = PtrToUlong(Bufferp->Context2); // // Acquire two additional references to the interface // for the endpoint-activation that we will initiate below, // lock the interface, and retrieve the endpoint. // EnterCriticalSection(&FtpInterfaceLock); if (!FTP_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&FtpInterfaceLock); break; } FTP_REFERENCE_INTERFACE(Interfacep); LeaveCriticalSection(&FtpInterfaceLock); ACQUIRE_LOCK(Interfacep); Endpointp = FtpLookupInterfaceEndpoint(Interfacep, EndpointId, NULL); // // First look for an error code. // If an error occurred and the interface is still active, // destroy the endpoint. // If the interface is inactive, we're done, since the endpoint // will have already been destroyed. // If the interface is active but the endpoint has already // been destroyed, end this connection-attempt. // if (ErrorCode) { if (Endpointp) { NhTrace( TRACE_FLAG_IO, "FtpConnectEndpointCompletionRoutine: deleting endpoint %d " "on error %d", EndpointId, ErrorCode ); FtpDeleteActiveEndpoint(Endpointp); } RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); break; } else if (!FTP_INTERFACE_ACTIVE(Interfacep)) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpConnectEndpointCompletionRoutine: interface %d inactive", Interfacep->Index ); break; } else if (!Endpointp) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpConnectEndpointCompletionRoutine: endpoint %d removed", EndpointId ); break; } // // We now activate the endpoint, beginning data transfer. // Note that it is the caller's responsibility to release // the two new references to the interface if an error occurs. // NhTrace( TRACE_FLAG_IO, "FtpConnectEndpointCompletionRoutine: endpoint %d socket %d " "connected", EndpointId, ConnectedSocket ); Error = FtpActivateActiveEndpoint(Interfacep, Endpointp); RELEASE_LOCK(Interfacep); } while(FALSE); } // FtpConnectEndpointCompletionRoutine VOID FtpReadEndpointCompletionRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ) /*++ Routine Description: This routine is invoked upon completion of a read operation on a FTP transparent proxy stream socket. The contexts for all reads are the interface and endpoint-identifier corresponding to the socket, stored in 'Context' and 'Context2', respectively. Arguments: ErrorCode - Win32 status code for the I/O operation BytesTransferred - number of bytes in 'Bufferp' Bufferp - holds data read from the socket Return Value: none. Environment: Runs in the context of a worker-thread which has just dequeued an I/O completion packet from the common I/O completion port with which our stream sockets are associated. A reference to the component will have been made on our behalf by 'NhReadStreamSocket'. A reference to the interface will have been made on our behalf by whoever issued the I/O request. --*/ { ULONG EndpointId; PFTP_ENDPOINT Endpointp; ULONG Error; PFTP_INTERFACE Interfacep; PROFILE("FtpReadEndpointCompletionRoutine"); do { Interfacep = (PFTP_INTERFACE)Bufferp->Context; EndpointId = PtrToUlong(Bufferp->Context2); // // Acquire two additional references to the interface // for the followup requests that we will issue below, // lock the interface, and retrieve the endpoint. // EnterCriticalSection(&FtpInterfaceLock); if (!FTP_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&FtpInterfaceLock); NhReleaseBuffer(Bufferp); break; } FTP_REFERENCE_INTERFACE(Interfacep); LeaveCriticalSection(&FtpInterfaceLock); ACQUIRE_LOCK(Interfacep); Endpointp = FtpLookupInterfaceEndpoint(Interfacep, EndpointId, NULL); // // Process the read-completion. First we look for an error-code, // and if we find one, we decide whether to re-issue the read-request. // If the interface is still active, the error-code is non-fatal, and // the endpoint still exists, we reissue the read. // if (ErrorCode) { // // We won't be needing the second reference to the interface, // since we won't be calling 'FtpProcessMessage. // FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: error %d for endpoint %d", ErrorCode, EndpointId ); if (!FTP_INTERFACE_ACTIVE(Interfacep) || !Endpointp) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); } else if (NhIsFatalSocketError(ErrorCode)) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: deleting endpoint %d " "on fatal read-error %d", EndpointId, ErrorCode ); } else { // // We need to repost the buffer for another read operation, // so we now reissue a read for the same number of bytes as // before. // Error = NhReadStreamSocket( &FtpComponentReference, Bufferp->Socket, Bufferp, Bufferp->BytesToTransfer, Bufferp->TransferOffset, FtpReadEndpointCompletionRoutine, Bufferp->Context, Bufferp->Context2 ); if (Error) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: deleting endpoint " "%d, NhReadStreamSocket=%d", EndpointId, Error ); if (Error != ERROR_NETNAME_DELETED) { NhWarningLog( IP_FTP_LOG_RECEIVE_FAILED, Error, "%I", NhQueryAddressSocket(Bufferp->Socket) ); } NhReleaseBuffer(Bufferp); break; } RELEASE_LOCK(Interfacep); } break; } else if (!BytesTransferred) { // // Zero bytes were read from the endpoint's socket. // This indicates that the sender has closed the socket. // We now propagate the closure to the alternate socket // for the endpoint. When the 'other' sender is done, // this endpoint will be removed altogether. // NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: endpoint %d socket %d " "closed", EndpointId, Bufferp->Socket ); if (Endpointp) { FtpCloseActiveEndpoint(Endpointp, Bufferp->Socket); } RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); break; } // // The original request completed successfully. // Now see if the interface and endpoint are operational and, // if not, return control. // if (!FTP_INTERFACE_ACTIVE(Interfacep)) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: interface %d inactive", Interfacep->Index ); break; } else if (!Endpointp) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: endpoint %d not found", EndpointId ); break; } // // Record the number of bytes read, and issue a read-request // for the remainder if necessary. Otherwise, process the completed // message. // NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: endpoint %d socket %d read %d " "bytes", EndpointId, Bufferp->Socket, BytesTransferred ); ASSERT(BytesTransferred <= Bufferp->BytesToTransfer); Bufferp->BytesToTransfer -= BytesTransferred; Bufferp->TransferOffset += BytesTransferred; if (Bufferp->BytesToTransfer > 0 && FtpIsFullMessage( reinterpret_cast(Bufferp->Buffer), Bufferp->TransferOffset ) == NULL) { // // Read the remainder of the message, after releasing // the second reference to the interface, which is needed // only when we call 'FtpProcessMessage'. // FTP_DEREFERENCE_INTERFACE(Interfacep); Error = NhReadStreamSocket( &FtpComponentReference, Bufferp->Socket, Bufferp, Bufferp->BytesToTransfer, Bufferp->TransferOffset, FtpReadEndpointCompletionRoutine, Bufferp->Context, Bufferp->Context2 ); if (Error) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpReadEndpointCompletionRoutine: deleting endpoint " "%d, NhReadStreamSocket=%d", EndpointId, Error ); if (Error != ERROR_NETNAME_DELETED) { NhWarningLog( IP_FTP_LOG_RECEIVE_FAILED, Error, "%I", NhQueryAddressSocket(Bufferp->Socket) ); } NhReleaseBuffer(Bufferp); break; } } else { // // We've finished reading something. Process it. // FtpProcessMessage(Interfacep, Endpointp, Bufferp); } RELEASE_LOCK(Interfacep); } while(FALSE); FTP_DEREFERENCE_INTERFACE(Interfacep); DEREFERENCE_FTP(); } // FtpReadEndpointCompletionRoutine VOID FtpWriteEndpointCompletionRoutine( ULONG ErrorCode, ULONG BytesTransferred, PNH_BUFFER Bufferp ) /*++ Routine Description: This routine is invoked upon completion of a write-operation on a stream socket for a FTP control-channel connection. The contexts for all writes are the interface and endpoint-identifier corresponding to the socket, stored in 'Context' and 'Context2', respectively. Arguments: ErrorCode - Win32 status code for the I/O operation BytesTransferred - number of bytes in 'Bufferp' Bufferp - holds data read from the stream socket Return Value: none. Environment: Runs in the context of a worker-thread which has just dequeued an I/O completion packet from the common I/O completion port with which our stream sockets are associated. A reference to the component will have been made on our behalf by 'NhWriteStreamSocket'. A reference to the interface will have been made on our behalf by whoever issued the I/O request. --*/ { ULONG Error; ULONG EndpointId; PFTP_ENDPOINT Endpointp; PFTP_INTERFACE Interfacep; PROFILE("FtpWriteEndpointCompletionRoutine"); do { Interfacep = (PFTP_INTERFACE)Bufferp->Context; EndpointId = PtrToUlong(Bufferp->Context2); // // Acquire an additional reference to the interface // for the followup requests that we will issue below, // lock the interface, and retrieve the endpoint. // EnterCriticalSection(&FtpInterfaceLock); if (!FTP_REFERENCE_INTERFACE(Interfacep)) { LeaveCriticalSection(&FtpInterfaceLock); NhReleaseBuffer(Bufferp); break; } LeaveCriticalSection(&FtpInterfaceLock); ACQUIRE_LOCK(Interfacep); Endpointp = FtpLookupInterfaceEndpoint(Interfacep, EndpointId, NULL); // // Process the write-completion. First we look for an error-code, // and if we find one, we decide whether to re-issue the write-request. // If the interface is still active, the error-code is non-fatal, and // the endpoint still exists, we reissue the write. // if (ErrorCode) { NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: error %d for endpoint %d", ErrorCode, EndpointId ); if (!FTP_INTERFACE_ACTIVE(Interfacep) || !Endpointp) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); } else if (NhIsFatalSocketError(ErrorCode)) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: deleting endpoint %d " "on fatal write-error %d", EndpointId, ErrorCode ); } else { // // We need to repost the buffer for another write operation, // so we now reissue a write for the same number of bytes // as before. // Error = NhWriteStreamSocket( &FtpComponentReference, Bufferp->Socket, Bufferp, Bufferp->BytesToTransfer, Bufferp->TransferOffset, FtpWriteEndpointCompletionRoutine, Bufferp->Context, Bufferp->Context2 ); if (Error) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: deleting endpoint " "%d, NhWriteStreamSocket=%d", EndpointId, Error ); NhWarningLog( IP_FTP_LOG_SEND_FAILED, Error, "%I", NhQueryAddressSocket(Bufferp->Socket) ); NhReleaseBuffer(Bufferp); break; } RELEASE_LOCK(Interfacep); } break; } // // The original request completed successfully. // Now see if the interface and endpoint are operational and, // if not, return control. // if (!FTP_INTERFACE_ACTIVE(Interfacep)) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: interface %d inactive", Interfacep->Index ); break; } else if (!Endpointp) { RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhReleaseBuffer(Bufferp); NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: endpoint %d not found", EndpointId ); break; } // // Record the number of bytes written, and issue a write-request // for the remainder if necessary. Otherwise, we are done, // and we return to reading from the 'other' socket for the // control-channel. // NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: endpoint %d socket %d wrote %d " "bytes", EndpointId, Bufferp->Socket, BytesTransferred ); ASSERT(BytesTransferred <= Bufferp->BytesToTransfer); Bufferp->BytesToTransfer -= BytesTransferred; Bufferp->TransferOffset += BytesTransferred; if (Bufferp->BytesToTransfer) { // // Write the remainder of the message // Error = NhWriteStreamSocket( &FtpComponentReference, Bufferp->Socket, Bufferp, Bufferp->BytesToTransfer, Bufferp->TransferOffset, FtpWriteEndpointCompletionRoutine, Bufferp->Context, Bufferp->Context2 ); if (Error) { FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); FTP_DEREFERENCE_INTERFACE(Interfacep); NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: deleting endpoint %d, " "NhWriteStreamSocket=%d", EndpointId, Error ); NhWarningLog( IP_FTP_LOG_SEND_FAILED, Error, "%I", NhQueryAddressSocket(Bufferp->Socket) ); NhReleaseBuffer(Bufferp); break; } } else { SOCKET Socket; ULONG UserFlags; // // We now go back to reading from the other socket of the // endpoint, by issuing the next read on the endpoint's other // socket. Note that it is the responsibility of the callee // to release the reference to the interface if a failure occurs. // UserFlags = Bufferp->UserFlags; if (UserFlags & FTP_BUFFER_FLAG_FROM_ACTUAL_CLIENT) { Socket = Endpointp->HostSocket; UserFlags &= ~(ULONG)FTP_BUFFER_FLAG_CONTINUATION; UserFlags |= FTP_BUFFER_FLAG_FROM_ACTUAL_CLIENT; } else { Socket = Endpointp->ClientSocket; UserFlags &= ~(ULONG)FTP_BUFFER_FLAG_CONTINUATION; UserFlags |= FTP_BUFFER_FLAG_FROM_ACTUAL_HOST; } NhReleaseBuffer(Bufferp); Error = FtpReadActiveEndpoint( Interfacep, Endpointp, Socket, UserFlags ); if (Error) { NhTrace( TRACE_FLAG_IO, "FtpWriteEndpointCompletionRoutine: deleting endpoint %d, " "FtpReadActiveEndpoint=%d", EndpointId, Error ); FtpDeleteActiveEndpoint(Endpointp); RELEASE_LOCK(Interfacep); NhWarningLog( IP_FTP_LOG_RECEIVE_FAILED, Error, "%I", NhQueryAddressSocket(Socket) ); break; } } RELEASE_LOCK(Interfacep); } while(FALSE); FTP_DEREFERENCE_INTERFACE(Interfacep); DEREFERENCE_FTP(); } // FtpWriteEndpointCompletionRoutine