|
|
// PMSPservice.cpp
#include "NTServApp.h"
#include "PMSPservice.h"
#include "svchost.h"
#define BUFSIZE 256
#define PIPE_TIMEOUT 2000
#define NUM_BYTES_PER_READ_REQUEST (sizeof(MEDIA_SERIAL_NUMBER_DATA))
#define INACTIVE_TIMEOUT_SHUTDOWN (5*60*1000) // in millisec -- 5 minutes
#include "serialid.h"
#include "aclapi.h"
#include <crtdbg.h>
LPTSTR g_lpszPipename = "\\\\.\\pipe\\WMDMPMSPpipe";
// static member variables
const DWORD CPMSPService::m_dwMaxConsecutiveConnectErrors = 5;
static DWORD CheckDriveType(HANDLE hPipe, LPCWSTR pwszDrive) { // On XP, as a result of the impersonation, we use the
// client's drive letter namespace for the GetDriveType call.
// When we CreateFile the drive letter, we use the LocalSystem
// drive namespace.
if (ImpersonateNamedPipeClient(hPipe) == 0) { return GetLastError(); }
DWORD dwDriveType = GetDriveTypeW(pwszDrive);
RevertToSelf();
if (dwDriveType != DRIVE_FIXED && dwDriveType != DRIVE_REMOVABLE) { return ERROR_INVALID_PARAMETER; } return ERROR_SUCCESS; }
static VOID GetAnswerToRequest(HANDLE hPipe, LPBYTE szBufIn, DWORD dwSizeIn, LPBYTE szBufOut, DWORD dwBufSizeOut, LPDWORD pdwNumBytesWritten) { WCHAR wcsDeviceName[]=L"A:\\"; WMDMID stMSN; DWORD dwDriveNum; HRESULT hr=E_FAIL; PMEDIA_SERIAL_NUMBER_DATA pMSNIn = (PMEDIA_SERIAL_NUMBER_DATA)szBufIn; PMEDIA_SERIAL_NUMBER_DATA pMSNOut = (PMEDIA_SERIAL_NUMBER_DATA)szBufOut;
if (!hPipe || !szBufIn || !szBufOut || !pdwNumBytesWritten || dwBufSizeOut < sizeof(MEDIA_SERIAL_NUMBER_DATA)) { _ASSERTE(0); return; }
// For all errors, we send back (and write to the pipe) the
// entire MEDIA_SERIAL_NUMBER_DATA struct. On successful returns,
// the number of bytes written may be more or less than
// sizeof(MEDIA_SERIAL_NUMBER_DATA) depnding on the length of
// the serial number.
ZeroMemory(szBufOut, dwBufSizeOut); *pdwNumBytesWritten = sizeof(MEDIA_SERIAL_NUMBER_DATA); if (dwSizeIn >= NUM_BYTES_PER_READ_REQUEST) { dwDriveNum = pMSNIn->Reserved[1]; if (dwDriveNum < 26) { wcsDeviceName[0] = L'A' + (USHORT)dwDriveNum; CPMSPService::DebugMsg("Getting serial number for %c", 'A' + (USHORT) (wcsDeviceName[0] - 'A'));
DWORD dwErr = CheckDriveType(hPipe, wcsDeviceName); CPMSPService::DebugMsg("CheckDriveType returns %u", dwErr);
if (dwErr == ERROR_SUCCESS) { hr = UtilGetSerialNumber(wcsDeviceName, &stMSN, FALSE);
CPMSPService::DebugMsg("hr = %x\n", hr); CPMSPService::DebugMsg("serial = %c %c %c %c ...\n", stMSN.pID[0], stMSN.pID[1], stMSN.pID[2], stMSN.pID[3]);
if (hr == S_OK) { // Note that dwNumBytesToTransfer could actually be less than sizeof(MEDIA_SERIAL_NUMBER_DATA)
DWORD dwNumBytesToTransfer = FIELD_OFFSET(MEDIA_SERIAL_NUMBER_DATA, SerialNumberData) + stMSN.SerialNumberLength; if (dwNumBytesToTransfer > dwBufSizeOut) { pMSNOut->Result = ERROR_INSUFFICIENT_BUFFER; } else { CopyMemory(pMSNOut->SerialNumberData, stMSN.pID, stMSN.SerialNumberLength); *pdwNumBytesWritten = dwNumBytesToTransfer; pMSNOut->SerialNumberLength = stMSN.SerialNumberLength; pMSNOut->Reserved[1] = stMSN.dwVendorID; pMSNOut->Result = ERROR_SUCCESS; } } else { pMSNOut->Result = 0xFFFF & hr; } } else { pMSNOut->Result = dwErr; } } else { pMSNOut->Result = ERROR_INVALID_PARAMETER; } } else { // This should never happen because this function is called only after
// reading NUM_BYTES_PER_READ_REQUEST or more bytes.
_ASSERTE(m_PipeState[i].dwNumBytesRead >= NUM_BYTES_PER_READ_REQUEST); pMSNOut->Result = ERROR_INVALID_PARAMETER; } }
CPMSPService::CPMSPService(DWORD& dwLastError) :CNTService() { ZeroMemory(&m_PipeState, MAX_PIPE_INSTANCES * sizeof(PIPE_STATE));
m_hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // unsignalled manual reset event
m_dwNumClients = 0;
dwLastError = m_hStopEvent? ERROR_SUCCESS : GetLastError(); }
CPMSPService::~CPMSPService() { CPMSPService::DebugMsg("~CPMSPService, last error %u, num clients: %u", m_Status.dwWin32ExitCode, m_dwNumClients );
if (m_hStopEvent) { CloseHandle(m_hStopEvent); }
DWORD i; DWORD dwRet;
for (i = 0; i < MAX_PIPE_INSTANCES; i++) { if (m_PipeState[i].state == PIPE_STATE::CONNECT_PENDING || m_PipeState[i].state == PIPE_STATE::READ_PENDING || m_PipeState[i].state == PIPE_STATE::WRITE_PENDING) { BOOL bDisconnect = 0;
_ASSERTE(m_PipeState[i].hPipe); _ASSERTE(m_PipeState[i].overlapped.hEvent); CancelIo(m_PipeState[i].hPipe);
CPMSPService::DebugMsg("~CPMSPService client %u's state: %u", i, m_PipeState[i].state);
if (m_PipeState[i].state == PIPE_STATE::CONNECT_PENDING) { dwRet = WaitForSingleObject(m_PipeState[i].overlapped.hEvent, 0); _ASSERTE(dwRet != WAIT_FAILED); if (dwRet == WAIT_OBJECT_0) { bDisconnect = 1; } } else { bDisconnect = 1; _ASSERTE(m_dwNumClients > 0); m_dwNumClients--; }
// Note that we do not call FlushFileBuffers. That is
// a sync call and a malicious client can prevent us from
// progressing by not reading bytes from a pipe. That would
// prevent the service from stopping.
//
// In normal circumstances we disconnect the pipe only after
// the client tells us it is done (by closing its end of the
// pipe), so these is no need to flush.
if (bDisconnect) { DisconnectNamedPipe(m_PipeState[i].hPipe); } } if (m_PipeState[i].overlapped.hEvent) { CloseHandle(m_PipeState[i].overlapped.hEvent); } if (m_PipeState[i].hPipe) { CloseHandle(m_PipeState[i].hPipe); } } _ASSERTE(m_dwNumClients == 0); }
BOOL CPMSPService::OnInit(DWORD& dwLastError) { BOOL bRet = FALSE; PSID pAuthUserSID = NULL; PSID pAdminSID = NULL; PACL pACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL;
__try { DWORD i; DWORD dwRet;
EXPLICIT_ACCESS ea[2]; // SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY; SECURITY_ATTRIBUTES sa;
// Create a well-known SID for interactive users
if (!AllocateAndInitializeSid(&SIDAuthNT, 1, SECURITY_INTERACTIVE_RID, 0, 0, 0, 0, 0, 0, 0, &pAuthUserSID)) { dwLastError = GetLastError(); DebugMsg("AllocateAndInitializeSid Error %u - auth users\n", dwLastError); __leave; }
// Initialize an EXPLICIT_ACCESS structure for an ACE.
// The ACE will allow authenticated users read access to the key.
ZeroMemory(ea, 2 * sizeof(EXPLICIT_ACCESS));
// Was: ea[0].grfAccessPermissions = GENERIC_WRITE | GENERIC_READ;
// Disallow non admins from creating pipe instances. Don't know if
// GENERIC_WRITE enables that, but the replacement below is safer.
// Following leaves DELETE access turned on; what effect does this have for named pipes?
// ea[0].grfAccessPermissions = (FILE_ALL_ACCESS & ~(FILE_CREATE_PIPE_INSTANCE | WRITE_OWNER | WRITE_DAC));
// Following is same as above except that DELETE access is not given
ea[0].grfAccessPermissions = (FILE_GENERIC_READ | FILE_GENERIC_WRITE) & ~(FILE_CREATE_PIPE_INSTANCE); ea[0].grfAccessMode = SET_ACCESS; ea[0].grfInheritance= NO_INHERITANCE; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.ptstrName = (LPTSTR) pAuthUserSID;
// Create a SID for the BUILTIN\Administrators group.
if (!AllocateAndInitializeSid(&SIDAuthNT, 2, // 3,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, // DOMAIN_ALIAS_RID_POWER_USERS,
0, 0, 0, 0, 0, &pAdminSID)) { dwLastError = GetLastError(); DebugMsg("AllocateAndInitializeSid Error %u - Domain, Power, Admins\n", dwLastError); __leave; }
// Initialize an EXPLICIT_ACCESS structure for an ACE.
// The ACE will allow the Administrators group full access to the key.
ea[1].grfAccessPermissions = GENERIC_ALL; ea[1].grfAccessMode = SET_ACCESS; ea[1].grfInheritance= NO_INHERITANCE; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea[1].Trustee.ptstrName = (LPTSTR) pAdminSID;
// Create a new ACL that contains the new ACEs.
dwRet = SetEntriesInAcl(2, ea, NULL, &pACL); if (ERROR_SUCCESS != dwRet) { dwLastError = dwRet; DebugMsg("SetEntriesInAcl Error %u\n", dwLastError); __leave; }
// Initialize a security descriptor.
pSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); if (pSD == NULL) { dwLastError = GetLastError(); DebugMsg("LocalAlloc Error %u\n", dwLastError); __leave; }
if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) { dwLastError = GetLastError(); DebugMsg("InitializeSecurityDescriptor Error %u\n", dwLastError); __leave; }
// Add the ACL to the security descriptor.
if (!SetSecurityDescriptorDacl(pSD, TRUE, // fDaclPresent flag
pACL, FALSE)) // not a default DACL
{ dwLastError = GetLastError(); DebugMsg("SetSecurityDescriptorDacl Error %u\n", dwLastError); __leave; } // Bump up the check point
SetStatus(SERVICE_START_PENDING);
// Initialize a security attributes structure.
sa.nLength = sizeof (SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = pSD; sa.bInheritHandle = FALSE;
for (i = 0; i < MAX_PIPE_INSTANCES; i++) { // Note that if i == 0, we supply FILE_FLAG_FIRST_PIPE_INSTANCE
// to this function. This causes the call to fail if an instance
// of the named pipe is already open. That can happen in 2
// cases: 1. Another instance of this dll is running or 2. We have
// a name clash with another app (benign or malicious).
// @@@@ Note: Apparently FILE_FLAG_FIRST_PIPE_INSTANCE is supported
// only with Win2K SP2 and up. To do: (a) Confirm this (b) What is
// the effect of setting this flag on Win2K gold and SP1?
m_PipeState[i].hPipe = CreateNamedPipe( g_lpszPipename, // pipe name
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | (i == 0? FILE_FLAG_FIRST_PIPE_INSTANCE : 0), // read/write access
PIPE_TYPE_BYTE | // byte type pipe
PIPE_READMODE_BYTE | // byte-read mode
PIPE_WAIT, // blocking mode
MAX_PIPE_INSTANCES, // max. instances
BUFSIZE, // output buffer size
BUFSIZE, // input buffer size
PIPE_TIMEOUT, // client time-out
&sa); // no security attribute
if (m_PipeState[i].hPipe == INVALID_HANDLE_VALUE) { // Note that we bail out if we fail to create ANY pipe instance,
// not just the first one. We expect to create all pipe instances;
// failure to do so could mean that another app (benign or malicious)
// is creating pipe instances. This is possible only if the other
// app has the FILE_CREATE_PIPE_INSTANCE access right.
dwLastError = GetLastError(); m_PipeState[i].hPipe = NULL; DebugMsg("CreateNamedPipe Error %u, instance = %u\n", dwLastError, i); __leave; }
m_PipeState[i].overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // unsignalled manual reset event
if (m_PipeState[i].overlapped.hEvent == NULL) { dwLastError = GetLastError(); DebugMsg("CreateEvent Error %u, instance = %u\n", dwLastError, i); __leave; }
// Errors connecting ot the client are sdtashed away in
// m_PipeState[i].dwLastIOCallError. Let CPMSPService::Run
// deal with the error. We'll just continue to start up the
// service here.
ConnectToClient(i);
// Bump up the check point
SetStatus(SERVICE_START_PENDING); }
bRet = TRUE; dwLastError = ERROR_SUCCESS; CPMSPService::DebugMsg("OnInit succeeded"); } __finally { if (pAuthUserSID) { FreeSid(pAuthUserSID); } if (pAdminSID) { FreeSid(pAdminSID); } if (pACL) { LocalFree(pACL); } if (pSD) { LocalFree(pSD); } }
return bRet; }
// This routine initiates a connection to a client on pipe instance i.
// Success/error status is saved away in m_PipeState[i].dwLastIOCallError
void CPMSPService::ConnectToClient(DWORD i) { m_PipeState[i].state = PIPE_STATE::CONNECT_PENDING;
m_PipeState[i].overlapped.Offset = m_PipeState[i].overlapped.OffsetHigh = 0; m_PipeState[i].dwNumBytesTransferredByLastIOCall = 0; m_PipeState[i].dwNumBytesRead = 0; m_PipeState[i].dwNumBytesToWrite = m_PipeState[i].dwNumBytesWritten = 0; m_PipeState[i].dwLastIOCallError = 0;
DWORD dwRet = ConnectNamedPipe(m_PipeState[i].hPipe, &m_PipeState[i].overlapped); if (dwRet) { // The event should be signalled already, but just in case:
SetEvent(m_PipeState[i].overlapped.hEvent);
m_PipeState[i].dwLastIOCallError = ERROR_SUCCESS; } else { m_PipeState[i].dwLastIOCallError = GetLastError(); if (m_PipeState[i].dwLastIOCallError == ERROR_PIPE_CONNECTED) { // The event should be signalled already, but just in case:
SetEvent(m_PipeState[i].overlapped.hEvent); } else if (m_PipeState[i].dwLastIOCallError == ERROR_IO_PENDING) { // Do nothing
} else { // Set tbe event so that CPMSPService::Run deals with the error
// in the next iteration of its main loop
SetEvent(m_PipeState[i].overlapped.hEvent); } } }
// This routine initiates a read on pipe instance i.
// Success/error status is saved away in m_PipeState[i].dwLastIOCallError
void CPMSPService::Read(DWORD i) { DWORD dwRet;
m_PipeState[i].state = PIPE_STATE::READ_PENDING;
m_PipeState[i].overlapped.Offset = m_PipeState[i].overlapped.OffsetHigh = 0;
CPMSPService::DebugMsg("Read(): client %u has %u unprocessed bytes in read buffer", i, m_PipeState[i].dwNumBytesRead);
if (m_PipeState[i].dwNumBytesRead >= NUM_BYTES_PER_READ_REQUEST) { // We already have another complete request; process it.
dwRet = 1; m_PipeState[i].dwNumBytesTransferredByLastIOCall = 0; } else { dwRet = ReadFile(m_PipeState[i].hPipe, m_PipeState[i].readBuf + m_PipeState[i].dwNumBytesRead, sizeof(m_PipeState[i].readBuf)- m_PipeState[i].dwNumBytesRead, &m_PipeState[i].dwNumBytesTransferredByLastIOCall, &m_PipeState[i].overlapped); } if (dwRet) { // The event should be signalled already if we issued a ReadFile,
// but it won't be signalled in other cases
SetEvent(m_PipeState[i].overlapped.hEvent);
m_PipeState[i].dwLastIOCallError = ERROR_SUCCESS; } else { m_PipeState[i].dwLastIOCallError = GetLastError(); if (m_PipeState[i].dwLastIOCallError == ERROR_IO_PENDING) { // Do nothing
} else { // Set tbe event so that CPMSPService::Run deals with the error
// in the next iteration of its main loop. (Note that this may
// not be an error condition - e.g., it could be EOF)
SetEvent(m_PipeState[i].overlapped.hEvent); } } }
// This routine initiates a write on pipe instance i.
// Success/error status is saved away in m_PipeState[i].dwLastIOCallError
void CPMSPService::Write(DWORD i) { DWORD dwRet;
m_PipeState[i].state = PIPE_STATE::WRITE_PENDING;
m_PipeState[i].overlapped.Offset = m_PipeState[i].overlapped.OffsetHigh = 0;
dwRet = WriteFile(m_PipeState[i].hPipe, m_PipeState[i].writeBuf + m_PipeState[i].dwNumBytesWritten, m_PipeState[i].dwNumBytesToWrite - m_PipeState[i].dwNumBytesWritten, &m_PipeState[i].dwNumBytesTransferredByLastIOCall, &m_PipeState[i].overlapped); if (dwRet) { // The event should be signalled already, but just in case:
SetEvent(m_PipeState[i].overlapped.hEvent);
m_PipeState[i].dwLastIOCallError = ERROR_SUCCESS; } else { m_PipeState[i].dwLastIOCallError = GetLastError(); if (m_PipeState[i].dwLastIOCallError == ERROR_IO_PENDING) { // Do nothing
} else { // Set tbe event so that CPMSPService::Run deals with the error
// in the next iteration of its main loop. (Note that this may
// not be an error condition - e.g., it could be EOF)
SetEvent(m_PipeState[i].overlapped.hEvent); } } }
void CPMSPService::Run() { DWORD i; DWORD dwRet; HANDLE hWaitArray[MAX_PIPE_INSTANCES+1];
SetStatus(SERVICE_RUNNING);
hWaitArray[0] = m_hStopEvent; for (i = 0; i < MAX_PIPE_INSTANCES; i++) { hWaitArray[i+1] = m_PipeState[i].overlapped.hEvent; }
do { DWORD dwTimeout = (m_dwNumClients == 0)? INACTIVE_TIMEOUT_SHUTDOWN : INFINITE; dwRet = WaitForMultipleObjects( sizeof(hWaitArray)/sizeof(hWaitArray[0]), hWaitArray, FALSE, // wait for any one to be signalled
dwTimeout);
if (dwRet == WAIT_FAILED) { m_Status.dwWin32ExitCode = GetLastError(); CPMSPService::DebugMsg("Wait failed, last error %u", m_Status.dwWin32ExitCode ); break; } if (dwRet == WAIT_OBJECT_0) { // Service has been stopped
CPMSPService::DebugMsg("Service stopped"); break; } if (dwRet == WAIT_TIMEOUT) { _ASSERTE(m_dwNumClients == 0); CPMSPService::DebugMsg("Service timed out - stopping"); OnStop(); continue; } _ASSERTE(dwRet >= WAIT_OBJECT_0 + 1);
i = dwRet - WAIT_OBJECT_0 - 1;
_ASSERTE(i < MAX_PIPE_INSTANCES);
CPMSPService::DebugMsg("Service woken up by client %u in state %u", i, m_PipeState[i].state);
// Although it's likely that all Win32 I/O calls do this at the
// start of an I/O, we need to do this anyway. Our destructor
// uses the state of this event to determine whether to disconnect
// the pipe.
ResetEvent(m_PipeState[i].overlapped.hEvent);
_ASSERTE(m_PipeState[i].state != PIPE_STATE::NO_IO_PENDING);
if (m_PipeState[i].dwLastIOCallError == ERROR_IO_PENDING) { if (!GetOverlappedResult(m_PipeState[i].hPipe, &m_PipeState[i].overlapped, &m_PipeState[i].dwNumBytesTransferredByLastIOCall, FALSE)) { m_PipeState[i].dwLastIOCallError = GetLastError();
// The following assertion should not fail because our event was
// signaled.
_ASSERTE(m_PipeState[i].dwLastIOCallError != ERROR_IO_INCOMPLETE); } else { m_PipeState[i].dwLastIOCallError = ERROR_SUCCESS; } }
switch (m_PipeState[i].state) { case PIPE_STATE::NO_IO_PENDING: // This should not happen.
// We have asserted m_PipeState[i].state != NO_IO_PENDING above.
break;
case PIPE_STATE::CONNECT_PENDING: if (m_PipeState[i].dwLastIOCallError == ERROR_SUCCESS || m_PipeState[i].dwLastIOCallError == ERROR_PIPE_CONNECTED) { // A client has connected; issue a read
m_dwNumClients++; CPMSPService::DebugMsg("Client %u connected, num clients is now: %u", i, m_dwNumClients); Read(i);
// Reset error counter
m_PipeState[i].dwConsecutiveConnectErrors = 0; } else { CPMSPService::DebugMsg("Client %u connect failed, error %u, # consecutive errors %u", i, m_PipeState[i].dwLastIOCallError, m_PipeState[i].dwConsecutiveConnectErrors+1); if (++m_PipeState[i].dwConsecutiveConnectErrors == m_dwMaxConsecutiveConnectErrors) { // We are done with this instance of the pipe, don't
// attempt to connect any more
// @@@@ We should break out of the loop and stop the service if all pipe instances
// are hosed?
m_PipeState[i].state = PIPE_STATE::NO_IO_PENDING; } else { // Connect to next client
ConnectToClient(i); } } break;
case PIPE_STATE::READ_PENDING: if (m_PipeState[i].dwLastIOCallError == ERROR_SUCCESS) { // We read something. We may have read only a part of
// a request or more than one request (if the client wrote
// two requests to pipe before our read completed).
//
// We have assumed that a request always has NUM_BYTES_PER_READ_REQUEST
// bytes. Otherwise, we can't handle cases where the client writes
// two requests at once (before our read completes) or writes part of
// requests or writes the whole request but ReadFile returns with some
// of the bytes that the client wrote (this is unlikely to happen in
// practice).
m_PipeState[i].dwNumBytesRead += m_PipeState[i].dwNumBytesTransferredByLastIOCall;
CPMSPService::DebugMsg("Client %u read %u bytes; total bytes read: %u", i, m_PipeState[i].dwNumBytesTransferredByLastIOCall, m_PipeState[i].dwNumBytesRead);
if (m_PipeState[i].dwNumBytesRead >= NUM_BYTES_PER_READ_REQUEST) { GetAnswerToRequest(m_PipeState[i].hPipe, m_PipeState[i].readBuf, m_PipeState[i].dwNumBytesRead, m_PipeState[i].writeBuf, sizeof(m_PipeState[i].writeBuf), &m_PipeState[i].dwNumBytesToWrite); // Remove the read request that has been processed from the read buffer
m_PipeState[i].dwNumBytesRead -= NUM_BYTES_PER_READ_REQUEST; MoveMemory(m_PipeState[i].readBuf, m_PipeState[i].readBuf + NUM_BYTES_PER_READ_REQUEST, m_PipeState[i].dwNumBytesRead);
// Write response to the request that was just processed
Write(i); } else { Read(i); } } else { // If (m_PipeState[i].dwLastIOCallError == ERROR_HANDLE_EOF),
// the reader's done and gone. So we can connect to another
// client. For all other errors, we bail out on the client,
// and connect to another client. Note that we do not call
// FlushFileBuffers here. When the client's gone (we read EOF),
// this is not necessary. In other cases, the client may lose
// the response to its last request - too bad. In any case the
// client has to be able to handle the server's abrupt disconnect.
//
// Calling FlushFileBuffers opens us up to DOS attacks (and could
// prevent the service from stopping) because the call is synchronous
// and does not return till the client has read the stuff we wrote to
// the pipe.
CPMSPService::DebugMsg("Client %u read failed, error %u, num clients left: %u", i, m_PipeState[i].dwLastIOCallError, m_dwNumClients-1); DisconnectNamedPipe(m_PipeState[i].hPipe); m_dwNumClients--;
// Connect to another client
ConnectToClient(i); } break;
case PIPE_STATE::WRITE_PENDING: if (m_PipeState[i].dwLastIOCallError == ERROR_SUCCESS) { m_PipeState[i].dwNumBytesWritten += m_PipeState[i].dwNumBytesTransferredByLastIOCall;
_ASSERTE(m_PipeState[i].dwNumBytesWritten <= m_PipeState[i].dwNumBytesToWrite);
CPMSPService::DebugMsg("Wrote %u of %u bytes to client %u", m_PipeState[i].dwNumBytesWritten, m_PipeState[i].dwNumBytesToWrite, i); // >= is only a safety net. == should suffice in view of the assert above.
if (m_PipeState[i].dwNumBytesWritten >= m_PipeState[i].dwNumBytesToWrite) { // We are done with this request, read the next one
m_PipeState[i].dwNumBytesWritten = m_PipeState[i].dwNumBytesToWrite = 0; Read(i); } else { // We wrote only a part of what we were asked to write. Write the rest.
// This is very unlikely to happen since our buffers are small.
Write(i); } } else { // For all errors, we bail out on the client,
// and connect to another client. Note that we do not call
// FlushFileBuffers here. The client may lose
// the response to its last request - too bad. In any case the
// client has to be able to handle the server's abrupt disconnect.
//
// Calling FlushFileBuffers opens us up to DOS attacks (and could
// prevent the service from stopping) because the call is synchronous
// and does not return till the client has read the stuff we wrote to
// the pipe.
CPMSPService::DebugMsg("Client %u write failed, error %u, num clients left: %u", i, m_PipeState[i].dwLastIOCallError, m_dwNumClients-1); m_PipeState[i].dwNumBytesWritten = m_PipeState[i].dwNumBytesToWrite = 0; DisconnectNamedPipe(m_PipeState[i].hPipe); m_dwNumClients--;
// Connect to another client
ConnectToClient(i); } break;
} // switch m_PipeState[i].state)
} while (1);
return; }
// Process user control requests
BOOL CPMSPService::OnUserControl(DWORD dwOpcode) { // switch (dwOpcode)
// {
// case SERVICE_CONTROL_USER + 0:
// // Save the current status in the registry
// SaveStatus();
// return TRUE;
// default:
// break;
// }
return FALSE; // say not handled
}
void CPMSPService::OnStop() { SetStatus(SERVICE_STOP_PENDING); if (m_hStopEvent) { SetEvent(m_hStopEvent); } else { _ASSERTE(m_hStopEvent); } }
void CPMSPService::OnShutdown() { OnStop(); }
|