// 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 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(); }