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.
833 lines
31 KiB
833 lines
31 KiB
// 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();
|
|
}
|