mirror of https://github.com/lianthony/NT4.0
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2317 lines
52 KiB
2317 lines
52 KiB
/*++
|
|
|
|
Copyright (c) 1992 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
Simpsvc.c
|
|
|
|
Abstract:
|
|
|
|
Supports several simple TCP/IP services in a single thread: TCP Echo,
|
|
UDP Echo, Daytime, Null, Chargen.
|
|
|
|
Author:
|
|
|
|
David Treadwell (davidtr) 3-Mar-1993
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#define FD_SETSIZE 65536
|
|
|
|
#include "simptcp.h"
|
|
|
|
#define MAX_UDP_CHARGEN_RESPONSE 7030
|
|
|
|
DWORD IoBufferSize = 4096;
|
|
PCHAR IoBuffer = NULL;
|
|
|
|
WSADATA WsaData;
|
|
|
|
RTL_CRITICAL_SECTION CriticalSection;
|
|
BOOL InitializedCriticalSection = FALSE;
|
|
|
|
typedef struct _TCP_CLIENT_INFO {
|
|
SOCKET SocketHandle;
|
|
SOCKADDR_IN RemoteAddress;
|
|
HANDLE ThreadHandle;
|
|
SHORT ServicePort;
|
|
} TCP_CLIENT_INFO, *PTCP_CLIENT_INFO;
|
|
|
|
#define MAX_TCP_CLIENTS 1000
|
|
PTCP_CLIENT_INFO TcpClients = NULL;
|
|
|
|
#define LISTEN_BACKLOG 5
|
|
|
|
#define MAX_IDLE_TICKS 10 * 60 * 1000 // 10 minutes
|
|
#define SELECT_TIMEOUT 5 * 60 // 5 minutes
|
|
|
|
DWORD MaxTcpClients = MAX_TCP_CLIENTS;
|
|
DWORD MaxIdleTicks = MAX_IDLE_TICKS;
|
|
DWORD SelectTimeout = SELECT_TIMEOUT;
|
|
|
|
PFD_SET ReadfdsStore, Readfds, WritefdsStore, Writefds;
|
|
|
|
SHORT TcpEchoPort;
|
|
SHORT UdpEchoPort;
|
|
SHORT TcpDiscardPort;
|
|
SHORT UdpDiscardPort;
|
|
SHORT TcpChargenPort;
|
|
SHORT UdpChargenPort;
|
|
SHORT TcpDaytimePort;
|
|
SHORT UdpDaytimePort;
|
|
SHORT TcpQotdPort;
|
|
SHORT UdpQotdPort;
|
|
|
|
#define INVALID_PORT 0
|
|
|
|
BOOL DoTcpEcho = TRUE;
|
|
BOOL DoUdpEcho = TRUE;
|
|
BOOL DoTcpDiscard = TRUE;
|
|
BOOL DoUdpDiscard = TRUE;
|
|
BOOL DoTcpChargen = TRUE;
|
|
BOOL DoUdpChargen = TRUE;
|
|
BOOL DoTcpDaytime = TRUE;
|
|
BOOL DoUdpDaytime = TRUE;
|
|
BOOL DoTcpQotd = TRUE;
|
|
BOOL DoUdpQotd = TRUE;
|
|
|
|
struct {
|
|
PBOOL Boolean;
|
|
PWSTR ValueName;
|
|
} RegistryBooleans[] = {
|
|
&DoTcpEcho, L"EnableTcpEcho",
|
|
&DoUdpEcho, L"EnableUdpEcho",
|
|
&DoTcpDiscard, L"EnableTcpDiscard",
|
|
&DoUdpDiscard, L"EnableUdpDiscard",
|
|
&DoTcpChargen, L"EnableTcpChargen",
|
|
&DoUdpChargen, L"EnableUdpChargen",
|
|
&DoTcpDaytime, L"EnableTcpDaytime",
|
|
&DoUdpDaytime, L"EnableUdpDaytime",
|
|
&DoTcpQotd, L"EnableTcpQotd",
|
|
&DoUdpQotd, L"EnableUdpQotd",
|
|
NULL, NULL
|
|
};
|
|
|
|
struct {
|
|
PDWORD Dword;
|
|
PWSTR ValueName;
|
|
} RegistryDwords[] = {
|
|
&MaxTcpClients, L"MaxTcpClients",
|
|
&MaxIdleTicks, L"MaxIdleTicks",
|
|
&SelectTimeout, L"SelectTimeout",
|
|
&IoBufferSize, L"IoBufferSize",
|
|
NULL, NULL
|
|
};
|
|
|
|
SERVICE_STATUS SimpServiceStatus;
|
|
SERVICE_STATUS_HANDLE SimpServiceStatusHandle;
|
|
|
|
HANDLE SimpPauseEvent;
|
|
SOCKET SimpQuitSocket;
|
|
|
|
BOOL SimpServiceExit = FALSE;
|
|
|
|
PVOID ChargenBuffer = NULL;
|
|
DWORD ChargenBufferSize;
|
|
|
|
PVOID QotdBuffer = NULL;
|
|
DWORD QotdQuoteCount;
|
|
struct {
|
|
DWORD QuoteLength;
|
|
PCHAR Quote;
|
|
} *QotdStrings = NULL;
|
|
PWSTR QotdFileName = NULL;
|
|
HANDLE QotdFileHandle = NULL;
|
|
HANDLE QotdFileMapping = NULL;
|
|
|
|
VOID
|
|
AnnounceServiceStatus (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
ControlResponse(
|
|
DWORD opCode
|
|
);
|
|
|
|
VOID
|
|
AbortTcpClient (
|
|
IN SOCKET Socket
|
|
);
|
|
|
|
INT
|
|
AcceptTcpClient (
|
|
IN SOCKET ListenSocket,
|
|
IN SHORT Port
|
|
);
|
|
|
|
VOID
|
|
DeleteTcpClient (
|
|
IN DWORD ArraySlot,
|
|
IN BOOLEAN Graceful
|
|
);
|
|
|
|
VOID
|
|
DoSimpleServices (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
DoSingleClient (
|
|
IN SOCKET s,
|
|
IN USHORT port
|
|
);
|
|
|
|
VOID
|
|
FormatDaytimeResponse (
|
|
IN PCHAR Buffer,
|
|
IN PDWORD BufferLength
|
|
);
|
|
|
|
VOID
|
|
FormatQotdResponse (
|
|
IN PCHAR Buffer,
|
|
IN PDWORD BufferLength
|
|
);
|
|
|
|
SHORT
|
|
GetServicePort (
|
|
IN PCHAR Service,
|
|
IN PCHAR Protocol
|
|
);
|
|
|
|
INT
|
|
InitializeChargen (
|
|
VOID
|
|
);
|
|
|
|
INT
|
|
InitializeQotdQuotes (
|
|
VOID
|
|
);
|
|
|
|
INT
|
|
ReadRegistry (
|
|
VOID
|
|
);
|
|
|
|
SOCKET
|
|
OpenTcpSocket (
|
|
IN SHORT Port
|
|
);
|
|
|
|
SOCKET
|
|
OpenUdpSocket (
|
|
IN SHORT Port
|
|
);
|
|
|
|
INT
|
|
SimpInitializeEventLog (
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
SimpTerminateEventLog(
|
|
VOID
|
|
);
|
|
|
|
VOID
|
|
SimpLogEvent(
|
|
DWORD Message,
|
|
WORD SubStringCount,
|
|
CHAR *SubStrings[],
|
|
DWORD ErrorCode
|
|
);
|
|
|
|
DWORD
|
|
ThreadEntry (
|
|
LPVOID lpThreadParameter
|
|
);
|
|
|
|
|
|
VOID
|
|
ServiceEntry (
|
|
IN DWORD argc,
|
|
IN LPWSTR argv[],
|
|
IN PTCPSVCS_GLOBAL_DATA pGlobalData
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the "main" routine for the simple TCP/IP services. The
|
|
containing process will call this routine when we're supposed to
|
|
start up.
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
SOCKET tcpEcho = INVALID_SOCKET;
|
|
SOCKET udpEcho = INVALID_SOCKET;
|
|
SOCKET tcpDaytime = INVALID_SOCKET;
|
|
SOCKET udpDaytime = INVALID_SOCKET;
|
|
SOCKET tcpDiscard = INVALID_SOCKET;
|
|
SOCKET udpDiscard = INVALID_SOCKET;
|
|
SOCKET tcpChargen = INVALID_SOCKET;
|
|
SOCKET udpChargen = INVALID_SOCKET;
|
|
SOCKET tcpQotd = INVALID_SOCKET;
|
|
SOCKET udpQotd = INVALID_SOCKET;
|
|
INT err;
|
|
TIMEVAL timeout;
|
|
INT i;
|
|
SOCKADDR_IN remoteAddr;
|
|
INT remoteAddrLength;
|
|
u_long one = 1;
|
|
DWORD maxFdSetSize;
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// Initialize all the status fields so that subsequent calls to
|
|
// SetServiceStatus need to only update fields that changed.
|
|
//
|
|
|
|
SimpServiceStatus.dwServiceType = SERVICE_WIN32;
|
|
SimpServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
|
SimpServiceStatus.dwControlsAccepted = 0;
|
|
SimpServiceStatus.dwCheckPoint = 1;
|
|
SimpServiceStatus.dwWaitHint = 30000; // 30 seconds
|
|
|
|
SimpServiceStatus.dwWin32ExitCode = NO_ERROR;
|
|
SimpServiceStatus.dwServiceSpecificExitCode = NO_ERROR;
|
|
|
|
//
|
|
// Initialize server to receive service requests by registering the
|
|
// control handler.
|
|
//
|
|
|
|
SimpServiceStatusHandle = RegisterServiceCtrlHandler(
|
|
TEXT("SimpTcp"),
|
|
ControlResponse
|
|
);
|
|
|
|
if ( SimpServiceStatusHandle == 0 ) {
|
|
err = GetLastError();
|
|
goto exit;
|
|
}
|
|
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Initialize our critical section.
|
|
//
|
|
|
|
status = RtlInitializeCriticalSection( &CriticalSection );
|
|
if ( !NT_SUCCESS(status) ) {
|
|
goto exit;
|
|
}
|
|
|
|
InitializedCriticalSection = TRUE;
|
|
|
|
//
|
|
// Initialize the eventlog.
|
|
//
|
|
|
|
err = SimpInitializeEventLog( );
|
|
ASSERT( err == NO_ERROR );
|
|
|
|
//
|
|
// Read all registry information.
|
|
//
|
|
|
|
err = ReadRegistry( );
|
|
if ( err != NO_ERROR ) {
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Allocate memory for the IO buffer.
|
|
//
|
|
|
|
IoBuffer = RtlAllocateHeap( RtlProcessHeap( ), 0, IoBufferSize );
|
|
|
|
//
|
|
// Allocate memory for the array of TCP clients.
|
|
//
|
|
|
|
TcpClients = RtlAllocateHeap(
|
|
RtlProcessHeap( ),
|
|
0,
|
|
MaxTcpClients * sizeof(TcpClients[0])
|
|
);
|
|
if ( TcpClients == NULL ) {
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Initialize the chargen data buffer.
|
|
//
|
|
|
|
if ( DoTcpChargen || DoUdpChargen ) {
|
|
err = InitializeChargen( );
|
|
if ( err != NO_ERROR ) {
|
|
DoUdpChargen = FALSE;
|
|
DoTcpChargen = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize the quote of the day quotes.
|
|
//
|
|
|
|
if ( DoTcpQotd || DoUdpQotd ) {
|
|
err = InitializeQotdQuotes( );
|
|
if ( err != NO_ERROR ) {
|
|
DoUdpQotd = FALSE;
|
|
DoTcpQotd = FALSE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize client socket array.
|
|
//
|
|
|
|
for ( i = 0; (DWORD)i < MaxTcpClients; i++ ) {
|
|
TcpClients[i].SocketHandle = INVALID_SOCKET;
|
|
TcpClients[i].ThreadHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// Determine how large our FD_SET structures must be, then allocate
|
|
// space for them.
|
|
//
|
|
|
|
maxFdSetSize = 4 + ( (20 + MaxTcpClients) * sizeof(SOCKET) );
|
|
|
|
|
|
Readfds = RtlAllocateHeap( RtlProcessHeap( ), 0, maxFdSetSize );
|
|
ReadfdsStore = RtlAllocateHeap( RtlProcessHeap( ), 0, maxFdSetSize );
|
|
Writefds = RtlAllocateHeap( RtlProcessHeap( ), 0, maxFdSetSize );
|
|
WritefdsStore = RtlAllocateHeap( RtlProcessHeap( ), 0, maxFdSetSize );
|
|
|
|
if ( Readfds == NULL || ReadfdsStore == NULL ||
|
|
Writefds == NULL || WritefdsStore == NULL ) {
|
|
err = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Initialize the pause event. We use this event to stop activity
|
|
// when the service is paused.
|
|
//
|
|
|
|
SimpPauseEvent = CreateEvent( NULL, TRUE, TRUE, NULL );
|
|
if ( SimpPauseEvent == NULL ) {
|
|
err = GetLastError( );
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Initialize the Windows Sockets DLL.
|
|
//
|
|
|
|
err = WSAStartup( 0x0101, &WsaData );
|
|
if ( err == SOCKET_ERROR ) {
|
|
err = GetLastError( );
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// Initialize the FD sets we'll use.
|
|
//
|
|
|
|
FD_ZERO( ReadfdsStore );
|
|
FD_ZERO( WritefdsStore );
|
|
|
|
//
|
|
// Open the "quit" socket. We close this socket when we need to
|
|
// shut down in order to wake up the main thread from it's select()
|
|
// and begin shutdown.
|
|
//
|
|
|
|
SimpQuitSocket = socket( AF_INET, SOCK_DGRAM, 0 );
|
|
if ( SimpQuitSocket != INVALID_SOCKET ) {
|
|
FD_SET( SimpQuitSocket, ReadfdsStore );
|
|
} else {
|
|
err = GetLastError( );
|
|
goto exit;
|
|
}
|
|
|
|
//
|
|
// First find the port numbers for all our services.
|
|
//
|
|
|
|
TcpEchoPort = GetServicePort( "echo", "tcp" );
|
|
if ( TcpEchoPort == INVALID_PORT && DoTcpEcho ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_TCP_ECHO_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoTcpEcho = FALSE;
|
|
}
|
|
|
|
UdpEchoPort = GetServicePort( "echo", "udp" );
|
|
if ( UdpEchoPort == INVALID_PORT && DoUdpEcho ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_UDP_ECHO_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoUdpEcho = FALSE;
|
|
}
|
|
|
|
TcpDiscardPort = GetServicePort( "discard", "tcp" );
|
|
if ( TcpDiscardPort == INVALID_PORT && DoTcpDiscard ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_TCP_DISCARD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoTcpDiscard = FALSE;
|
|
}
|
|
|
|
UdpDiscardPort = GetServicePort( "discard", "udp" );
|
|
if ( UdpDiscardPort == INVALID_PORT && DoUdpDiscard ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_UDP_DISCARD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoUdpDiscard = FALSE;
|
|
}
|
|
|
|
TcpDaytimePort = GetServicePort( "daytime", "tcp" );
|
|
if ( TcpDaytimePort == INVALID_PORT && DoTcpDaytime ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_TCP_DAYTIME_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoTcpDaytime = FALSE;
|
|
}
|
|
|
|
UdpDaytimePort = GetServicePort( "daytime", "udp" );
|
|
if ( UdpDaytimePort == INVALID_PORT && DoUdpDaytime ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_UDP_DAYTIME_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoUdpDaytime = FALSE;
|
|
}
|
|
|
|
TcpChargenPort = GetServicePort( "chargen", "tcp" );
|
|
if ( TcpChargenPort == INVALID_PORT && DoTcpChargen ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_TCP_CHARGEN_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoTcpChargen = FALSE;
|
|
}
|
|
|
|
UdpChargenPort = GetServicePort( "chargen", "udp" );
|
|
if ( UdpChargenPort == INVALID_PORT && DoUdpChargen ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_UDP_CHARGEN_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoUdpChargen = FALSE;
|
|
}
|
|
|
|
TcpQotdPort = GetServicePort( "qotd", "tcp" );
|
|
if ( TcpQotdPort == INVALID_PORT && DoTcpQotd ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_TCP_QOTD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoTcpQotd = FALSE;
|
|
}
|
|
|
|
UdpQotdPort = GetServicePort( "qotd", "udp" );
|
|
if ( UdpQotdPort == INVALID_PORT && DoUdpQotd ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_FIND_UDP_QOTD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
|
|
DoUdpQotd = FALSE;
|
|
}
|
|
|
|
//
|
|
// Open, bind, and listen on the necessary ports.
|
|
//
|
|
|
|
if ( DoTcpEcho ) {
|
|
tcpEcho = OpenTcpSocket( TcpEchoPort );
|
|
if ( tcpEcho != INVALID_SOCKET ) {
|
|
FD_SET( tcpEcho, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_TCP_ECHO_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoUdpEcho ) {
|
|
udpEcho = OpenUdpSocket( UdpEchoPort );
|
|
if ( udpEcho != INVALID_SOCKET ) {
|
|
FD_SET( udpEcho, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_UDP_ECHO_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoTcpDiscard ) {
|
|
tcpDiscard = OpenTcpSocket( TcpDiscardPort );
|
|
if ( tcpDiscard != INVALID_SOCKET ) {
|
|
FD_SET( tcpDiscard, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_TCP_DISCARD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoUdpDiscard ) {
|
|
udpDiscard = OpenUdpSocket( UdpDiscardPort );
|
|
if ( udpDiscard != INVALID_SOCKET ) {
|
|
FD_SET( udpDiscard, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_UDP_DISCARD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoTcpDaytime ) {
|
|
tcpDaytime = OpenTcpSocket( TcpDaytimePort );
|
|
if ( tcpDaytime != INVALID_SOCKET ) {
|
|
FD_SET( tcpDaytime, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_TCP_DAYTIME_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoUdpDaytime ) {
|
|
udpDaytime = OpenUdpSocket( UdpDaytimePort );
|
|
if ( udpDaytime != INVALID_SOCKET ) {
|
|
FD_SET( udpDaytime, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_UDP_DAYTIME_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoTcpChargen ) {
|
|
tcpChargen = OpenTcpSocket( TcpChargenPort );
|
|
if ( tcpChargen != INVALID_SOCKET ) {
|
|
FD_SET( tcpChargen, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_TCP_CHARGEN_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoUdpChargen ) {
|
|
udpChargen = OpenUdpSocket( UdpChargenPort );
|
|
if ( udpChargen != INVALID_SOCKET ) {
|
|
FD_SET( udpChargen, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_UDP_CHARGEN_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoTcpQotd ) {
|
|
tcpQotd = OpenTcpSocket( TcpQotdPort );
|
|
if ( tcpQotd != INVALID_SOCKET ) {
|
|
FD_SET( tcpQotd, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_TCP_QOTD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( DoUdpQotd ) {
|
|
udpQotd = OpenUdpSocket( UdpQotdPort );
|
|
if ( udpQotd != INVALID_SOCKET ) {
|
|
FD_SET( udpQotd, ReadfdsStore );
|
|
} else {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_UDP_QOTD_PORT,
|
|
0,
|
|
NULL,
|
|
WSAGetLastError( )
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Announce that we have successfully started.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
|
SimpServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
|
|
SERVICE_ACCEPT_PAUSE_CONTINUE;
|
|
SimpServiceStatus.dwCheckPoint = 0;
|
|
SimpServiceStatus.dwWaitHint = 0;
|
|
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Loop waiting for connect attempts or datagrams, and service them
|
|
// when they arrive.
|
|
//
|
|
|
|
while ( TRUE ) {
|
|
|
|
//
|
|
// First initialize the FD sets we'll actually use for select().
|
|
//
|
|
|
|
RtlCopyMemory( Readfds, ReadfdsStore, maxFdSetSize );
|
|
RtlCopyMemory( Writefds, WritefdsStore, maxFdSetSize );
|
|
|
|
//
|
|
// Now wait for something to happen. Timeout occaisonally
|
|
// so that we can kill idle TCP clients.
|
|
//
|
|
|
|
timeout.tv_sec = SelectTimeout;
|
|
timeout.tv_usec = 0;
|
|
|
|
err = select( 0, Readfds, Writefds, NULL, &timeout );
|
|
|
|
//
|
|
// If the service is shutting down, stop processing requests
|
|
// and exit.
|
|
//
|
|
|
|
if ( SimpServiceExit ) {
|
|
err = NO_ERROR;
|
|
goto exit;
|
|
}
|
|
|
|
if ( err == SOCKET_ERROR ) {
|
|
|
|
//
|
|
// This is bad. We should do something intelligent here.
|
|
//
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// If the service is paused, wait for it to become unpaused.
|
|
//
|
|
|
|
err = WaitForSingleObject( SimpPauseEvent, INFINITE );
|
|
ASSERT( err != WAIT_FAILED );
|
|
|
|
//
|
|
// Figure out what happened and act accordingly.
|
|
//
|
|
|
|
if ( tcpEcho != INVALID_SOCKET && FD_ISSET( tcpEcho, Readfds ) ) {
|
|
i = AcceptTcpClient( tcpEcho, TcpEchoPort );
|
|
if ( i != -1 ) {
|
|
FD_SET( TcpClients[i].SocketHandle, ReadfdsStore );
|
|
}
|
|
}
|
|
|
|
if ( tcpDiscard != INVALID_SOCKET && FD_ISSET( tcpDiscard, Readfds ) ) {
|
|
i = AcceptTcpClient( tcpDiscard, TcpDiscardPort );
|
|
if ( i != -1 ) {
|
|
FD_SET( TcpClients[i].SocketHandle, ReadfdsStore );
|
|
}
|
|
}
|
|
|
|
if ( tcpDaytime != INVALID_SOCKET && FD_ISSET( tcpDaytime, Readfds ) ) {
|
|
|
|
SOCKET acceptSocket;
|
|
DWORD length;
|
|
|
|
//
|
|
// A client is making a TCP daytime request. First accept
|
|
// the connection, then send the current time-of-day string
|
|
// to the client, then close the socket.
|
|
//
|
|
|
|
acceptSocket = accept( tcpDaytime, NULL, NULL );
|
|
|
|
if ( acceptSocket != INVALID_SOCKET ) {
|
|
FormatDaytimeResponse( IoBuffer, &length );
|
|
send( acceptSocket, IoBuffer, length, 0 );
|
|
err = closesocket( acceptSocket );
|
|
ASSERT( err != SOCKET_ERROR );
|
|
}
|
|
}
|
|
|
|
if ( tcpChargen != INVALID_SOCKET && FD_ISSET( tcpChargen, Readfds ) ) {
|
|
i = AcceptTcpClient( tcpChargen, TcpChargenPort );
|
|
if ( i != -1 ) {
|
|
FD_SET( TcpClients[i].SocketHandle, ReadfdsStore );
|
|
FD_SET( TcpClients[i].SocketHandle, WritefdsStore );
|
|
one = 1;
|
|
err = ioctlsocket( TcpClients[i].SocketHandle, FIONBIO, &one );
|
|
if ( err == SOCKET_ERROR ) {
|
|
DeleteTcpClient( i, FALSE );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( tcpQotd != INVALID_SOCKET && FD_ISSET( tcpQotd, Readfds ) ) {
|
|
|
|
SOCKET acceptSocket;
|
|
DWORD length;
|
|
|
|
//
|
|
// A client is making a TCP Qotd request. First accept
|
|
// the connection, then send the current time-of-day string
|
|
// to the client, then close the socket.
|
|
//
|
|
|
|
acceptSocket = accept( tcpQotd, NULL, NULL );
|
|
|
|
if ( acceptSocket != INVALID_SOCKET ) {
|
|
FormatQotdResponse( IoBuffer, &length );
|
|
send( acceptSocket, IoBuffer, length, 0 );
|
|
err = closesocket( acceptSocket );
|
|
ASSERT( err != SOCKET_ERROR );
|
|
}
|
|
}
|
|
|
|
if ( udpEcho != INVALID_SOCKET && FD_ISSET( udpEcho, Readfds ) ) {
|
|
|
|
remoteAddrLength = sizeof(remoteAddr);
|
|
|
|
err = recvfrom(
|
|
udpEcho,
|
|
IoBuffer,
|
|
IoBufferSize,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
&remoteAddrLength
|
|
);
|
|
|
|
if ( err != SOCKET_ERROR ) {
|
|
err = sendto(
|
|
udpEcho,
|
|
IoBuffer,
|
|
err,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
remoteAddrLength
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( udpDiscard != INVALID_SOCKET && FD_ISSET( udpDiscard, Readfds ) ) {
|
|
err = recvfrom(
|
|
udpDiscard,
|
|
IoBuffer,
|
|
IoBufferSize,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
ASSERT( err != SOCKET_ERROR );
|
|
}
|
|
|
|
if ( udpDaytime != INVALID_SOCKET && FD_ISSET( udpDaytime, Readfds ) ) {
|
|
|
|
DWORD length;
|
|
|
|
remoteAddrLength = sizeof(remoteAddr);
|
|
|
|
err = recvfrom(
|
|
udpDaytime,
|
|
IoBuffer,
|
|
IoBufferSize,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
&remoteAddrLength
|
|
);
|
|
|
|
FormatDaytimeResponse( IoBuffer, &length );
|
|
|
|
err = sendto(
|
|
udpDaytime,
|
|
IoBuffer,
|
|
length,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
remoteAddrLength
|
|
);
|
|
}
|
|
|
|
if ( udpChargen != INVALID_SOCKET && FD_ISSET( udpChargen, Readfds ) ) {
|
|
|
|
DWORD length;
|
|
|
|
remoteAddrLength = sizeof(remoteAddr);
|
|
|
|
err = recvfrom(
|
|
udpChargen,
|
|
IoBuffer,
|
|
IoBufferSize,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
&remoteAddrLength
|
|
);
|
|
|
|
srand( GetTickCount( ) );
|
|
|
|
length = (rand( ) * MAX_UDP_CHARGEN_RESPONSE) / RAND_MAX;
|
|
|
|
err = sendto(
|
|
udpChargen,
|
|
ChargenBuffer,
|
|
length,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
remoteAddrLength
|
|
);
|
|
}
|
|
|
|
if ( udpQotd != INVALID_SOCKET && FD_ISSET( udpQotd, Readfds ) ) {
|
|
|
|
DWORD length;
|
|
|
|
remoteAddrLength = sizeof(remoteAddr);
|
|
|
|
err = recvfrom(
|
|
udpQotd,
|
|
IoBuffer,
|
|
IoBufferSize,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
&remoteAddrLength
|
|
);
|
|
|
|
FormatQotdResponse( IoBuffer, &length );
|
|
|
|
err = sendto(
|
|
udpQotd,
|
|
IoBuffer,
|
|
length,
|
|
0,
|
|
(PSOCKADDR)&remoteAddr,
|
|
remoteAddrLength
|
|
);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
|
|
//
|
|
// Announce that we're going down.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
|
SimpServiceStatus.dwCheckPoint = 1;
|
|
SimpServiceStatus.dwWaitHint = 20000; // 20 seconds
|
|
|
|
SimpServiceStatus.dwWin32ExitCode = err;
|
|
SimpServiceStatus.dwServiceSpecificExitCode = err;
|
|
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Delete our critical section.
|
|
//
|
|
|
|
if ( InitializedCriticalSection ) {
|
|
InitializedCriticalSection = FALSE;
|
|
RtlDeleteCriticalSection( &CriticalSection );
|
|
}
|
|
|
|
//
|
|
// Close all opened listening sockets.
|
|
//
|
|
|
|
if ( tcpEcho != INVALID_SOCKET ) {
|
|
closesocket( tcpEcho );
|
|
}
|
|
if ( udpEcho != INVALID_SOCKET ) {
|
|
closesocket( udpEcho );
|
|
}
|
|
if ( tcpDiscard != INVALID_SOCKET ) {
|
|
closesocket( tcpDiscard );
|
|
}
|
|
if ( udpDiscard != INVALID_SOCKET ) {
|
|
closesocket( udpDiscard );
|
|
}
|
|
if ( tcpDaytime != INVALID_SOCKET ) {
|
|
closesocket( tcpDaytime );
|
|
}
|
|
if ( udpDaytime != INVALID_SOCKET ) {
|
|
closesocket( udpDaytime );
|
|
}
|
|
if ( tcpChargen != INVALID_SOCKET ) {
|
|
closesocket( tcpChargen );
|
|
}
|
|
if ( udpChargen != INVALID_SOCKET ) {
|
|
closesocket( udpChargen );
|
|
}
|
|
|
|
//
|
|
// Close all connected TCP sockets.
|
|
//
|
|
|
|
for ( i = 0; TcpClients != NULL && (DWORD)i < MaxTcpClients; i++ ) {
|
|
|
|
if ( TcpClients[i].SocketHandle != INVALID_SOCKET ) {
|
|
AbortTcpClient( TcpClients[i].SocketHandle );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Should wait here for all threads to exit!
|
|
//
|
|
|
|
//
|
|
// Deinitialize the eventlog.
|
|
//
|
|
|
|
SimpTerminateEventLog( );
|
|
|
|
//
|
|
// Free allocated memory.
|
|
//
|
|
|
|
if ( IoBuffer != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, IoBuffer );
|
|
}
|
|
|
|
if ( TcpClients != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, TcpClients );
|
|
}
|
|
|
|
if ( Readfds != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, Readfds );
|
|
}
|
|
|
|
if ( ReadfdsStore != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, ReadfdsStore );
|
|
}
|
|
|
|
if ( Writefds != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, Writefds );
|
|
}
|
|
|
|
if ( WritefdsStore != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, WritefdsStore );
|
|
}
|
|
|
|
if ( ChargenBuffer != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, ChargenBuffer );
|
|
}
|
|
|
|
if ( QotdBuffer != NULL ) {
|
|
UnmapViewOfFile( QotdBuffer );
|
|
}
|
|
|
|
if ( QotdFileMapping != NULL ) {
|
|
CloseHandle( QotdFileMapping );
|
|
}
|
|
|
|
if ( QotdFileHandle != NULL ) {
|
|
CloseHandle( QotdFileHandle );
|
|
}
|
|
|
|
if ( QotdFileName != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, QotdFileName );
|
|
}
|
|
|
|
if ( QotdStrings != NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, QotdStrings );
|
|
}
|
|
|
|
//
|
|
// Announce that we're down.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
|
SimpServiceStatus.dwControlsAccepted = 0;
|
|
SimpServiceStatus.dwCheckPoint = 0;
|
|
SimpServiceStatus.dwWaitHint = 0;
|
|
|
|
SimpServiceStatus.dwWin32ExitCode = err;
|
|
SimpServiceStatus.dwServiceSpecificExitCode = err;
|
|
|
|
AnnounceServiceStatus( );
|
|
|
|
return;
|
|
|
|
} // ServiceEntry
|
|
|
|
|
|
SOCKET
|
|
OpenTcpSocket (
|
|
IN SHORT Port
|
|
)
|
|
{
|
|
SOCKET s;
|
|
SOCKADDR_IN localAddr;
|
|
INT err;
|
|
INT one = 1;
|
|
|
|
s = socket( AF_INET, SOCK_STREAM, 0 );
|
|
if ( s == INVALID_SOCKET ) {
|
|
return s;
|
|
}
|
|
|
|
RtlZeroMemory( &localAddr, sizeof(localAddr) );
|
|
localAddr.sin_port = Port;
|
|
localAddr.sin_family = AF_INET;
|
|
|
|
err = bind( s, (PSOCKADDR)&localAddr, sizeof(localAddr) );
|
|
if ( err ==SOCKET_ERROR ) {
|
|
closesocket(s);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
err = listen( s, LISTEN_BACKLOG );
|
|
if ( err == SOCKET_ERROR ) {
|
|
closesocket(s);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
err = setsockopt( s, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one) );
|
|
if ( err == INVALID_SOCKET ) {
|
|
closesocket(s);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
return s;
|
|
|
|
} // OpenTcpSocket
|
|
|
|
|
|
SOCKET
|
|
OpenUdpSocket (
|
|
IN SHORT Port
|
|
)
|
|
{
|
|
SOCKET s;
|
|
SOCKADDR_IN localAddr;
|
|
INT err;
|
|
|
|
s = socket( AF_INET, SOCK_DGRAM, 0 );
|
|
if ( s == INVALID_SOCKET ) {
|
|
return s;
|
|
}
|
|
|
|
RtlZeroMemory( &localAddr, sizeof(localAddr) );
|
|
localAddr.sin_port = Port;
|
|
localAddr.sin_family = AF_INET;
|
|
|
|
err = bind( s, (PSOCKADDR)&localAddr, sizeof(localAddr) );
|
|
if ( err == SOCKET_ERROR ) {
|
|
closesocket(s);
|
|
return INVALID_SOCKET;
|
|
}
|
|
|
|
return s;
|
|
|
|
} // OpenUdpSocket
|
|
|
|
|
|
INT
|
|
AcceptTcpClient (
|
|
IN SOCKET ListenSocket,
|
|
IN SHORT Port
|
|
)
|
|
{
|
|
SOCKADDR_IN remoteSockaddr;
|
|
INT remoteSockaddrLength;
|
|
DWORD i;
|
|
SOCKET acceptSocket;
|
|
DWORD threadId;
|
|
NTSTATUS status;
|
|
|
|
//
|
|
// Always accept the socket first.
|
|
//
|
|
|
|
remoteSockaddrLength = sizeof(remoteSockaddr);
|
|
|
|
acceptSocket =
|
|
accept( ListenSocket, (PSOCKADDR)&remoteSockaddr, &remoteSockaddrLength );
|
|
if ( acceptSocket == INVALID_SOCKET ) {
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Use a critical section to protect access to our database of
|
|
// TCP clients.
|
|
//
|
|
|
|
status = RtlEnterCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
|
|
//
|
|
// Attempt to find a TCP client slot.
|
|
//
|
|
|
|
for ( i = 0; i < MaxTcpClients; i++ ) {
|
|
if ( TcpClients[i].SocketHandle == INVALID_SOCKET ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we're at the max count of TCP sockets, abort this new
|
|
// socket.
|
|
//
|
|
|
|
if ( i >= MaxTcpClients ) {
|
|
AbortTcpClient( acceptSocket );
|
|
status = RtlLeaveCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// Initialize info about this client.
|
|
//
|
|
|
|
TcpClients[i].SocketHandle = acceptSocket;
|
|
RtlCopyMemory(
|
|
&TcpClients[i].RemoteAddress,
|
|
&remoteSockaddr,
|
|
sizeof(remoteSockaddr)
|
|
);
|
|
TcpClients[i].ServicePort = Port;
|
|
|
|
//
|
|
// We're in multi-threaded mode, so we'll create a separate thread
|
|
// to handle this client.
|
|
//
|
|
|
|
TcpClients[i].ThreadHandle = CreateThread(
|
|
NULL,
|
|
0,
|
|
ThreadEntry,
|
|
(PVOID)i,
|
|
0,
|
|
&threadId
|
|
);
|
|
if ( TcpClients[i].ThreadHandle == NULL ) {
|
|
AbortTcpClient( acceptSocket );
|
|
TcpClients[i].SocketHandle = INVALID_SOCKET;
|
|
status = RtlLeaveCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
return -1;
|
|
}
|
|
|
|
//
|
|
// The created thread will handle the connected client.
|
|
//
|
|
|
|
status = RtlLeaveCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
|
|
return -1;
|
|
|
|
} // AcceptTcpClient
|
|
|
|
|
|
VOID
|
|
AbortTcpClient (
|
|
IN SOCKET Socket
|
|
)
|
|
{
|
|
LINGER lingerInfo;
|
|
INT err;
|
|
|
|
//
|
|
// First set the linger timeout on the socket to 0. This will cause
|
|
// the connection to be reset.
|
|
//
|
|
|
|
lingerInfo.l_onoff = 1;
|
|
lingerInfo.l_linger = 0;
|
|
|
|
err = setsockopt(
|
|
Socket,
|
|
SOL_SOCKET,
|
|
SO_LINGER,
|
|
(char *)&lingerInfo,
|
|
sizeof(lingerInfo)
|
|
);
|
|
|
|
if ( err == SOCKET_ERROR ) {
|
|
|
|
//
|
|
// There's not too much we can do. Just close the socket.
|
|
//
|
|
|
|
ASSERT(FALSE);
|
|
closesocket( Socket );
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Now close the socket.
|
|
//
|
|
|
|
err = closesocket( Socket );
|
|
ASSERT( err != SOCKET_ERROR );
|
|
|
|
return;
|
|
|
|
} // AbortTcpClient
|
|
|
|
|
|
VOID
|
|
DeleteTcpClient (
|
|
IN DWORD ArraySlot,
|
|
IN BOOLEAN Graceful
|
|
)
|
|
{
|
|
INT err;
|
|
NTSTATUS status;
|
|
|
|
status = RtlEnterCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
|
|
ASSERT( TcpClients[ArraySlot].SocketHandle != INVALID_SOCKET );
|
|
|
|
//
|
|
// If the socket is in any of the primary FD sets, remove it.
|
|
//
|
|
|
|
if ( FD_ISSET( TcpClients[ArraySlot].SocketHandle, ReadfdsStore ) ) {
|
|
FD_CLR( TcpClients[ArraySlot].SocketHandle, ReadfdsStore );
|
|
}
|
|
|
|
if ( FD_ISSET( TcpClients[ArraySlot].SocketHandle, WritefdsStore ) ) {
|
|
FD_CLR( TcpClients[ArraySlot].SocketHandle, WritefdsStore );
|
|
}
|
|
|
|
//
|
|
// If this is to be an abortive disconnect, reset the connection.
|
|
// Otherwise just close it normally.
|
|
//
|
|
|
|
if ( !Graceful ) {
|
|
|
|
AbortTcpClient( TcpClients[ArraySlot].SocketHandle );
|
|
|
|
} else {
|
|
|
|
LINGER lingerInfo;
|
|
INT one;
|
|
|
|
//
|
|
// Set the socket to blocking.
|
|
//
|
|
|
|
one = 0;
|
|
ioctlsocket( TcpClients[ArraySlot].SocketHandle, FIONBIO, &one );
|
|
|
|
//
|
|
// Set the socket to linger no more than 60 seconds.
|
|
//
|
|
|
|
lingerInfo.l_onoff = 1;
|
|
lingerInfo.l_linger = 60;
|
|
|
|
setsockopt( TcpClients[ArraySlot].SocketHandle, SOL_SOCKET,
|
|
SO_LINGER, (char *)&lingerInfo, sizeof(lingerInfo) );
|
|
|
|
err = closesocket( TcpClients[ArraySlot].SocketHandle );
|
|
ASSERT( err != SOCKET_ERROR );
|
|
}
|
|
|
|
//
|
|
// Close the thread handle, if appropriate.
|
|
//
|
|
|
|
if ( TcpClients[ArraySlot].ThreadHandle != NULL ) {
|
|
CloseHandle( TcpClients[ArraySlot].ThreadHandle );
|
|
TcpClients[ArraySlot].ThreadHandle = NULL;
|
|
}
|
|
|
|
//
|
|
// Set the handle in the TCP clients array to INVALID_SOCKET so that we
|
|
// know that it is free.
|
|
//
|
|
|
|
TcpClients[ArraySlot].SocketHandle = INVALID_SOCKET;
|
|
|
|
status = RtlLeaveCriticalSection( &CriticalSection );
|
|
ASSERT( NT_SUCCESS(status) );
|
|
|
|
return;
|
|
|
|
} // DeleteTcpClient
|
|
|
|
PCHAR Months[] =
|
|
{
|
|
"January",
|
|
"Febuary",
|
|
"March",
|
|
"April",
|
|
"May",
|
|
"June",
|
|
"July",
|
|
"August",
|
|
"September",
|
|
"October",
|
|
"November",
|
|
"December"
|
|
};
|
|
|
|
PCHAR Days[] =
|
|
{
|
|
"Sunday",
|
|
"Monday",
|
|
"Tuesday",
|
|
"Wednesday",
|
|
"Thursday",
|
|
"Friday",
|
|
"Saturday"
|
|
};
|
|
|
|
|
|
VOID
|
|
FormatDaytimeResponse (
|
|
IN PCHAR Buffer,
|
|
IN PDWORD BufferLength
|
|
)
|
|
{
|
|
SYSTEMTIME timeStruct;
|
|
|
|
GetLocalTime( &timeStruct );
|
|
|
|
*BufferLength = sprintf( Buffer, "%s, %s %d, %d %d:%02d:%02d\n",
|
|
Days[timeStruct.wDayOfWeek],
|
|
Months[timeStruct.wMonth-1],
|
|
timeStruct.wDay,
|
|
timeStruct.wYear,
|
|
timeStruct.wHour,
|
|
timeStruct.wMinute,
|
|
timeStruct.wSecond ) + 1;
|
|
return;
|
|
|
|
} // FormatDaytimeResponse
|
|
|
|
|
|
VOID
|
|
FormatQotdResponse (
|
|
IN PCHAR Buffer,
|
|
IN PDWORD BufferLength
|
|
)
|
|
{
|
|
INT index;
|
|
|
|
//
|
|
// Choose a random quote index.
|
|
//
|
|
|
|
index = (rand( ) * QotdQuoteCount) / RAND_MAX;
|
|
|
|
//
|
|
// Copy the quote into the output buffer.
|
|
//
|
|
|
|
strncpy( Buffer, QotdStrings[index].Quote, QotdStrings[index].QuoteLength );
|
|
*BufferLength = QotdStrings[index].QuoteLength;
|
|
|
|
return;
|
|
|
|
} // FormatDaytimeResponse
|
|
|
|
|
|
INT
|
|
InitializeQotdQuotes (
|
|
VOID
|
|
)
|
|
{
|
|
|
|
BY_HANDLE_FILE_INFORMATION fileInformation;
|
|
PCHAR buffer;
|
|
DWORD i;
|
|
|
|
if ( QotdFileName == NULL ) {
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Open the file containing quote information.
|
|
//
|
|
|
|
QotdFileHandle = CreateFileW(
|
|
QotdFileName,
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
NULL
|
|
);
|
|
if ( QotdFileHandle == NULL ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_QUOTE_FILE,
|
|
0,
|
|
NULL,
|
|
GetLastError( )
|
|
);
|
|
return GetLastError( );
|
|
}
|
|
|
|
//
|
|
// Determine the size of the QOTD file.
|
|
//
|
|
|
|
if ( !GetFileInformationByHandle( QotdFileHandle, &fileInformation ) ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_QUOTE_FILE,
|
|
0,
|
|
NULL,
|
|
GetLastError( )
|
|
);
|
|
return GetLastError( );
|
|
}
|
|
|
|
//
|
|
// Create a file mapping for the quotes file and map it into
|
|
// the address space of this process.
|
|
//
|
|
|
|
QotdFileMapping = CreateFileMapping(
|
|
QotdFileHandle,
|
|
NULL,
|
|
PAGE_READONLY,
|
|
0,
|
|
0,
|
|
NULL
|
|
);
|
|
if ( QotdFileMapping == NULL ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_QUOTE_FILE,
|
|
0,
|
|
NULL,
|
|
GetLastError( )
|
|
);
|
|
return GetLastError( );
|
|
}
|
|
|
|
QotdBuffer = MapViewOfFile(
|
|
QotdFileMapping,
|
|
FILE_MAP_READ,
|
|
0,
|
|
0,
|
|
0
|
|
);
|
|
if ( QotdBuffer == NULL ) {
|
|
SimpLogEvent(
|
|
SIMPTCP_CANT_OPEN_QUOTE_FILE,
|
|
0,
|
|
NULL,
|
|
GetLastError( )
|
|
);
|
|
return GetLastError( );
|
|
}
|
|
|
|
//
|
|
// Count the number of lines in the file. The number of lines
|
|
// corresponds to the number of quotes.
|
|
//
|
|
|
|
QotdQuoteCount = 0;
|
|
buffer = (PCHAR)QotdBuffer;
|
|
QotdQuoteCount = 1;
|
|
|
|
for ( i = 0; i < fileInformation.nFileSizeLow; i++ ) {
|
|
if ( *buffer++ == '%' ) {
|
|
QotdQuoteCount++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Allocate a buffer to hold the quote array.
|
|
//
|
|
|
|
QotdStrings = RtlAllocateHeap(
|
|
RtlProcessHeap( ),
|
|
0,
|
|
sizeof(QotdStrings[0]) * QotdQuoteCount
|
|
);
|
|
|
|
//
|
|
// Initialize the quote array.
|
|
//
|
|
|
|
buffer = (PCHAR)QotdBuffer;
|
|
|
|
for ( i = 0; i < QotdQuoteCount; i++ ) {
|
|
|
|
QotdStrings[i].Quote = buffer;
|
|
|
|
while ( (DWORD)buffer < (DWORD)QotdBuffer +
|
|
fileInformation.nFileSizeLow &&
|
|
*buffer++ != '%' );
|
|
|
|
QotdStrings[i].QuoteLength =
|
|
(DWORD)buffer - (DWORD)QotdStrings[i].Quote - 1;
|
|
buffer += 2;
|
|
|
|
//
|
|
// If this quote if longer than the IO buffer size, skip over
|
|
// it. We can't use it.
|
|
//
|
|
|
|
if ( QotdStrings[i].QuoteLength > IoBufferSize ) {
|
|
QotdQuoteCount--;
|
|
i--;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Initialize the random-number generator.
|
|
//
|
|
|
|
srand( GetTickCount( ) );
|
|
|
|
return NO_ERROR;
|
|
|
|
} // InitializeQotdQuotes
|
|
|
|
|
|
#define CHARGEN_LINE_LENGTH 72
|
|
#define CHARGEN_REAL_LINE_LENGTH (CHARGEN_LINE_LENGTH + 2)
|
|
#define CHARGEN_MIN_CHAR ' '
|
|
#define CHARGEN_MAX_CHAR '~'
|
|
#define CHARGEN_DIFFERENCE (CHARGEN_MAX_CHAR - CHARGEN_MIN_CHAR)
|
|
#define CHARGEN_LINE_COUNT (CHARGEN_DIFFERENCE)
|
|
#define CHARGEN_BUFFER_LENGTH ((CHARGEN_LINE_LENGTH + 2) * (CHARGEN_LINE_COUNT))
|
|
|
|
|
|
INT
|
|
InitializeChargen (
|
|
VOID
|
|
)
|
|
{
|
|
DWORD line;
|
|
BYTE startChar = 0;
|
|
DWORD i;
|
|
|
|
//
|
|
// Allocate a buffer for the chargen data.
|
|
//
|
|
|
|
ChargenBufferSize = CHARGEN_BUFFER_LENGTH;
|
|
|
|
ChargenBuffer = RtlAllocateHeap(
|
|
RtlProcessHeap( ),
|
|
0,
|
|
ChargenBufferSize
|
|
);
|
|
if ( ChargenBuffer == NULL ) {
|
|
return ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
|
|
//
|
|
// Fill in the buffer with the required pattern.
|
|
//
|
|
|
|
for ( line = 0; line < CHARGEN_LINE_COUNT; line++ ) {
|
|
|
|
for ( i = 0; i < CHARGEN_LINE_LENGTH; i++ ) {
|
|
|
|
*((PCHAR)ChargenBuffer + (line * CHARGEN_REAL_LINE_LENGTH) + i) =
|
|
(CHAR)( ((startChar + i) % CHARGEN_DIFFERENCE) + CHARGEN_MIN_CHAR);
|
|
}
|
|
|
|
*((PCHAR)ChargenBuffer + (line * CHARGEN_REAL_LINE_LENGTH) + i) = 0x0D;
|
|
*((PCHAR)ChargenBuffer + (line * CHARGEN_REAL_LINE_LENGTH) + i + 1) = 0x0A;
|
|
|
|
startChar++;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
|
|
} // InitializeQotdQuotes
|
|
|
|
|
|
VOID
|
|
DoSingleClient (
|
|
IN SOCKET s,
|
|
IN USHORT port
|
|
)
|
|
{
|
|
INT err;
|
|
|
|
if ( port == TcpEchoPort ) {
|
|
|
|
while ( TRUE ) {
|
|
|
|
err = recv( s, IoBuffer, IoBufferSize, 0 );
|
|
if ( err == SOCKET_ERROR ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the remote closed gracefully, close the socket.
|
|
//
|
|
|
|
if ( err == 0 ) {
|
|
|
|
closesocket( s );
|
|
return;
|
|
|
|
} else {
|
|
|
|
err = send( s, IoBuffer, err, 0 );
|
|
if ( err == SOCKET_ERROR ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if ( port == TcpDiscardPort ) {
|
|
|
|
while ( TRUE ) {
|
|
|
|
err = recv( s, IoBuffer, IoBufferSize, 0 );
|
|
if ( err == SOCKET_ERROR ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// If the remote closed gracefully, close the socket.
|
|
//
|
|
|
|
if ( err == 0 ) {
|
|
closesocket( s );
|
|
return;
|
|
} else if ( err == SOCKET_ERROR ) {
|
|
closesocket( s );
|
|
return;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Something bad has happened. Internal data
|
|
// structures are corrupt.
|
|
//
|
|
|
|
ASSERT( FALSE );
|
|
}
|
|
|
|
} // DoSingleClient
|
|
|
|
|
|
SHORT
|
|
GetServicePort (
|
|
IN PCHAR Service,
|
|
IN PCHAR Protocol
|
|
)
|
|
{
|
|
PSERVENT serviceEntry;
|
|
|
|
//
|
|
// Get a servent structure for the specified service.
|
|
//
|
|
|
|
serviceEntry = getservbyname( Service, Protocol );
|
|
|
|
if ( serviceEntry == NULL ) {
|
|
// log an error!
|
|
return INVALID_PORT;
|
|
}
|
|
|
|
//
|
|
// Return the port for the specified service.
|
|
//
|
|
|
|
return serviceEntry->s_port;
|
|
|
|
} // GetServicePort
|
|
|
|
|
|
VOID
|
|
AnnounceServiceStatus (
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Announces the service's status to the service controller.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
None.
|
|
|
|
--*/
|
|
|
|
{
|
|
//
|
|
// Service status handle is NULL if RegisterServiceCtrlHandler failed.
|
|
//
|
|
|
|
if ( SimpServiceStatusHandle == 0 ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Call SetServiceStatus, ignoring any errors.
|
|
//
|
|
|
|
SetServiceStatus(SimpServiceStatusHandle, &SimpServiceStatus);
|
|
|
|
} // AnnounceServiceStatus
|
|
|
|
|
|
VOID
|
|
ControlResponse(
|
|
DWORD opCode
|
|
)
|
|
|
|
{
|
|
BOOL announce = TRUE;
|
|
BOOL err;
|
|
|
|
//
|
|
// Determine the type of service control message and modify the
|
|
// service status, if necessary.
|
|
//
|
|
|
|
switch( opCode ) {
|
|
|
|
case SERVICE_CONTROL_STOP:
|
|
|
|
//
|
|
// Announce that we are in the process of stopping.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Remember that we're stopping.
|
|
//
|
|
|
|
SimpServiceExit = TRUE;
|
|
|
|
//
|
|
// Close a socket that the main select()er thread is
|
|
// waiting on. This will cause the select to wake up
|
|
// and shutdown processing to commence.
|
|
//
|
|
|
|
closesocket( SimpQuitSocket );
|
|
|
|
//
|
|
// Let the main thread announce when the stop is done.
|
|
//
|
|
|
|
announce = FALSE;
|
|
|
|
break;
|
|
|
|
case SERVICE_CONTROL_PAUSE:
|
|
|
|
//
|
|
// Announce that we are in the process of pausing.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_PAUSE_PENDING;
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Remember that we're paused.
|
|
//
|
|
|
|
err = ResetEvent( SimpPauseEvent );
|
|
ASSERT( err );
|
|
|
|
//
|
|
// Announce that we're now paused.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_PAUSED;
|
|
|
|
break;
|
|
|
|
case SERVICE_CONTROL_CONTINUE:
|
|
|
|
//
|
|
// Announce that continue is pending.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_CONTINUE_PENDING;
|
|
AnnounceServiceStatus( );
|
|
|
|
//
|
|
// Remember that we're no longer paused.
|
|
//
|
|
|
|
err = SetEvent( SimpPauseEvent );
|
|
ASSERT( err );
|
|
|
|
//
|
|
// Announce that we're active now.
|
|
//
|
|
|
|
SimpServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
|
|
|
break;
|
|
|
|
case SERVICE_CONTROL_INTERROGATE:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
if ( announce ) {
|
|
AnnounceServiceStatus( );
|
|
}
|
|
|
|
} // ControlResponse
|
|
|
|
|
|
INT
|
|
ReadRegistry (
|
|
VOID
|
|
)
|
|
{
|
|
HKEY simptcpKey = NULL;
|
|
ULONG error;
|
|
ULONG i;
|
|
DWORD dwordBuffer;
|
|
DWORD bufferLength;
|
|
DWORD type;
|
|
DWORD qotdFileNameLength;
|
|
PWSTR fileName;
|
|
|
|
//
|
|
// First open our parameters key.
|
|
//
|
|
|
|
error = RegOpenKeyExW(
|
|
HKEY_LOCAL_MACHINE,
|
|
L"SYSTEM\\CurrentControlSet\\Services\\SimpTcp\\Parameters",
|
|
0,
|
|
MAXIMUM_ALLOWED,
|
|
&simptcpKey
|
|
);
|
|
if ( error != NO_ERROR ) {
|
|
return error;
|
|
}
|
|
|
|
//
|
|
// Read BOOLEANs from the registry.
|
|
//
|
|
|
|
for ( i = 0; RegistryBooleans[i].Boolean != NULL; i++ ) {
|
|
|
|
bufferLength = sizeof(dwordBuffer);
|
|
|
|
error = RegQueryValueExW(
|
|
simptcpKey,
|
|
RegistryBooleans[i].ValueName,
|
|
NULL,
|
|
&type,
|
|
(PVOID)&dwordBuffer,
|
|
&bufferLength
|
|
);
|
|
|
|
//
|
|
// If we fail to read one of these for some reason, just skip it
|
|
// and move on to the next one.
|
|
//
|
|
|
|
if ( error != NO_ERROR ) {
|
|
continue;
|
|
}
|
|
|
|
if ( dwordBuffer == 0 ) {
|
|
*RegistryBooleans[i].Boolean = FALSE;
|
|
} else {
|
|
*RegistryBooleans[i].Boolean = TRUE;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Read DWORDs from the registry.
|
|
//
|
|
|
|
for ( i = 0; RegistryDwords[i].Dword != NULL; i++ ) {
|
|
|
|
bufferLength = sizeof(RegistryDwords[i].Dword);
|
|
|
|
RegQueryValueExW(
|
|
simptcpKey,
|
|
RegistryDwords[i].ValueName,
|
|
NULL,
|
|
&type,
|
|
(PVOID)RegistryDwords[i].Dword,
|
|
&bufferLength
|
|
);
|
|
}
|
|
|
|
//
|
|
// Read other known values from the registry. Determine the size
|
|
// of the QOTD file name. We need this so that we can allocate
|
|
// enough memory to hold it.
|
|
//
|
|
|
|
qotdFileNameLength = 0;
|
|
|
|
error = RegQueryValueExW(
|
|
simptcpKey,
|
|
L"QotdFileName",
|
|
NULL,
|
|
&type,
|
|
NULL,
|
|
&qotdFileNameLength
|
|
);
|
|
|
|
if ( error == ERROR_MORE_DATA || error == NO_ERROR ) {
|
|
|
|
fileName = RtlAllocateHeap(
|
|
RtlProcessHeap( ),
|
|
0,
|
|
qotdFileNameLength
|
|
);
|
|
if ( fileName == NULL ) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
error = RegQueryValueExW(
|
|
simptcpKey,
|
|
L"QotdFileName",
|
|
NULL,
|
|
&type,
|
|
(PVOID)fileName,
|
|
&qotdFileNameLength
|
|
);
|
|
if ( error != NO_ERROR ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, fileName );
|
|
return NO_ERROR;
|
|
}
|
|
|
|
//
|
|
// Expand the file name.
|
|
//
|
|
|
|
qotdFileNameLength = ExpandEnvironmentStringsW( fileName, NULL, 0 );
|
|
|
|
QotdFileName = RtlAllocateHeap(
|
|
RtlProcessHeap( ),
|
|
0,
|
|
qotdFileNameLength * 2
|
|
);
|
|
if ( QotdFileName == NULL ) {
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, fileName );
|
|
return NO_ERROR;
|
|
}
|
|
|
|
ExpandEnvironmentStringsW( fileName, QotdFileName, qotdFileNameLength );
|
|
}
|
|
|
|
return NO_ERROR;
|
|
|
|
} // ReadRegistry
|
|
|
|
|
|
DWORD
|
|
ThreadEntry (
|
|
LPVOID lpThreadParameter
|
|
)
|
|
{
|
|
DWORD i = (DWORD)lpThreadParameter;
|
|
PVOID ioBuffer;
|
|
INT err;
|
|
BOOLEAN graceful = TRUE;
|
|
|
|
//
|
|
// First, set the send and receive timeouts for the socket. This
|
|
// prevents a dead client from tying up our resources for too long.
|
|
//
|
|
|
|
err = setsockopt( TcpClients[i].SocketHandle, SOL_SOCKET, SO_SNDTIMEO,
|
|
(char *)&MaxIdleTicks, sizeof(MaxIdleTicks) );
|
|
if ( err == SOCKET_ERROR ) {
|
|
DeleteTcpClient( i, FALSE );
|
|
return 0;
|
|
}
|
|
|
|
err = setsockopt( TcpClients[i].SocketHandle, SOL_SOCKET, SO_RCVTIMEO,
|
|
(char *)&MaxIdleTicks, sizeof(MaxIdleTicks) );
|
|
if ( err == SOCKET_ERROR ) {
|
|
DeleteTcpClient( i, FALSE );
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Get a buffer to use locally for IO on the socket.
|
|
//
|
|
|
|
ioBuffer = RtlAllocateHeap( RtlProcessHeap( ), 0, IoBufferSize );
|
|
if ( ioBuffer == NULL ) {
|
|
DeleteTcpClient( i, FALSE );
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Now service the client as appropriate.
|
|
//
|
|
|
|
if ( TcpClients[i].ServicePort == TcpEchoPort ) {
|
|
|
|
//
|
|
// If there is data on a client's echo socket,
|
|
// receive some data and send it back.
|
|
//
|
|
|
|
do {
|
|
|
|
err = recv(
|
|
TcpClients[i].SocketHandle,
|
|
ioBuffer,
|
|
IoBufferSize,
|
|
0
|
|
);
|
|
if ( err == SOCKET_ERROR ) {
|
|
graceful = FALSE;
|
|
}
|
|
|
|
if ( err > 0 ) {
|
|
|
|
err = send(
|
|
TcpClients[i].SocketHandle,
|
|
ioBuffer,
|
|
err,
|
|
0
|
|
);
|
|
if ( err == SOCKET_ERROR ) {
|
|
graceful = FALSE;
|
|
}
|
|
|
|
} else if ( err < 0 ) {
|
|
|
|
graceful = FALSE;
|
|
}
|
|
|
|
} while ( err > 0 );
|
|
|
|
} else if ( TcpClients[i].ServicePort == TcpChargenPort ) {
|
|
|
|
INT one;
|
|
INT error;
|
|
TIMEVAL timeout;
|
|
|
|
//
|
|
// Set the socket to nonblocking.
|
|
//
|
|
|
|
one = 1;
|
|
err = ioctlsocket( TcpClients[i].SocketHandle, FIONBIO, &one );
|
|
if ( err == SOCKET_ERROR ) {
|
|
graceful = FALSE;
|
|
}
|
|
|
|
//
|
|
// Calculate the select() timeout.
|
|
//
|
|
|
|
timeout.tv_sec = MaxIdleTicks / 1000;
|
|
timeout.tv_usec = MaxIdleTicks % 1000;
|
|
|
|
//
|
|
// Loop sending data.
|
|
//
|
|
|
|
do {
|
|
|
|
err = send(
|
|
TcpClients[i].SocketHandle,
|
|
ChargenBuffer,
|
|
ChargenBufferSize,
|
|
0
|
|
);
|
|
|
|
if ( err == SOCKET_ERROR ) {
|
|
|
|
error = GetLastError( );
|
|
|
|
if ( error != WSAEWOULDBLOCK ) {
|
|
|
|
graceful = FALSE;
|
|
|
|
} else {
|
|
|
|
struct {
|
|
INT Count;
|
|
SOCKET Handle;
|
|
} readfds = { 0, 0 };
|
|
struct {
|
|
INT Count;
|
|
SOCKET Handle;
|
|
} writefds = { 0, 0 };
|
|
|
|
//
|
|
// The socket's send queue is blocked. Wait for it to
|
|
// become unblocked.
|
|
//
|
|
|
|
FD_SET( TcpClients[i].SocketHandle, (PFD_SET)&readfds );
|
|
FD_SET( TcpClients[i].SocketHandle, (PFD_SET)&writefds );
|
|
|
|
err = select(
|
|
1,
|
|
(PFD_SET)&readfds,
|
|
(PFD_SET)&writefds,
|
|
NULL,
|
|
&timeout
|
|
);
|
|
if ( err <= 0 ) {
|
|
graceful = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
err = recv(
|
|
TcpClients[i].SocketHandle,
|
|
ioBuffer,
|
|
IoBufferSize,
|
|
0
|
|
);
|
|
if ( err == SOCKET_ERROR ) {
|
|
if ( WSAGetLastError( ) != WSAEWOULDBLOCK ) {
|
|
graceful = FALSE;
|
|
} else {
|
|
err = 1;
|
|
}
|
|
}
|
|
|
|
} while ( err > 0 );
|
|
|
|
} else if ( TcpClients[i].ServicePort == TcpDiscardPort ) {
|
|
|
|
//
|
|
// If there is data on a client's socket, just
|
|
// receive some data and discard it.
|
|
//
|
|
|
|
do {
|
|
|
|
err = recv(
|
|
TcpClients[i].SocketHandle,
|
|
ioBuffer,
|
|
IoBufferSize,
|
|
0
|
|
);
|
|
if ( err == SOCKET_ERROR ) {
|
|
graceful = FALSE;
|
|
}
|
|
|
|
} while ( err > 0 );
|
|
|
|
} else {
|
|
|
|
//
|
|
// Something bad has happened. Internal data
|
|
// structures are corrupt.
|
|
//
|
|
|
|
ASSERT( FALSE );
|
|
}
|
|
|
|
//
|
|
// Free the socket and the IO buffer and return.
|
|
//
|
|
|
|
DeleteTcpClient( i, graceful );
|
|
RtlFreeHeap( RtlProcessHeap( ), 0, ioBuffer );
|
|
|
|
return 0;
|
|
|
|
} // ThreadEntry
|
|
|