/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ #include "qpid/log/Statement.h" #include "qpid/sys/windows/check.h" #include "SCM.h" #pragma comment(lib, "advapi32.lib") namespace qpid { namespace windows { namespace { // Container that will close a SC_HANDLE upon destruction. class AutoServiceHandle { public: AutoServiceHandle(SC_HANDLE h_ = NULL) : h(h_) {} ~AutoServiceHandle() { if (h != NULL) ::CloseServiceHandle(h); } void release() { h = NULL; } void reset(SC_HANDLE newHandle) { if (h != NULL) ::CloseServiceHandle(h); h = newHandle; } operator SC_HANDLE() const { return h; } private: SC_HANDLE h; }; } SCM::SCM() : scmHandle(NULL) { } SCM::~SCM() { if (NULL != scmHandle) ::CloseServiceHandle(scmHandle); } /** * Install this executable as a service */ void SCM::install(const string& serviceName, const string& serviceDesc, const string& args, DWORD startType, const string& account, const string& password, const string& depends) { // Handle dependent service name list; Windows wants a set of nul-separated // names ending with a double nul. string depends2 = depends; if (!depends2.empty()) { // CDL to null delimiter w/ trailing double null size_t p = 0; while ((p = depends2.find_first_of( ',', p)) != string::npos) depends2.replace(p, 1, 1, '\0'); depends2.push_back('\0'); depends2.push_back('\0'); } #if 0 // I'm nervous about adding a user/password check here. Is this a // potential attack vector, letting users check passwords without // control? -Steve Huston, Feb 24, 2011 // Validate account, password HANDLE hToken = NULL; bool logStatus = false; if (!account.empty() && !password.empty() && !(logStatus = ::LogonUserA(account.c_str(), "", password.c_str(), LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &hToken ) != 0)) std::cout << "warning: supplied account & password failed with LogonUser." << std::endl; if (logStatus) ::CloseHandle(hToken); #endif // Get fully qualified .exe name char myPath[MAX_PATH]; DWORD myPathLength = ::GetModuleFileName(NULL, myPath, MAX_PATH); QPID_WINDOWS_CHECK_NOT(myPathLength, 0); string imagePath(myPath, myPathLength); if (!args.empty()) imagePath += " " + args; // Ensure there's a handle to the SCM database. openSvcManager(); // Create the service SC_HANDLE svcHandle; svcHandle = ::CreateService(scmHandle, // SCM database serviceName.c_str(), // name of service serviceDesc.c_str(), // name to display SERVICE_ALL_ACCESS, // desired access SERVICE_WIN32_OWN_PROCESS, // service type startType, // start type SERVICE_ERROR_NORMAL, // error cntrl type imagePath.c_str(), // path to service's binary w/ optional arguments NULL, // no load ordering group NULL, // no tag identifier depends2.empty() ? NULL : depends2.c_str(), account.empty() ? NULL : account.c_str(), // account name, or NULL for LocalSystem password.empty() ? NULL : password.c_str()); // password, or NULL for none QPID_WINDOWS_CHECK_NULL(svcHandle); ::CloseServiceHandle(svcHandle); QPID_LOG(info, "Service installed successfully"); } /** * */ void SCM::uninstall(const string& serviceName) { // Ensure there's a handle to the SCM database. openSvcManager(); AutoServiceHandle svc(::OpenService(scmHandle, serviceName.c_str(), DELETE)); QPID_WINDOWS_CHECK_NULL((SC_HANDLE)svc); QPID_WINDOWS_CHECK_NOT(::DeleteService(svc), 0); QPID_LOG(info, "Service deleted successfully."); } /** * Attempt to start the service. */ void SCM::start(const string& serviceName) { // Ensure we have a handle to the SCM database. openSvcManager(); // Get a handle to the service. AutoServiceHandle svc(::OpenService(scmHandle, serviceName.c_str(), SERVICE_ALL_ACCESS)); QPID_WINDOWS_CHECK_NULL(svc); // Check the status in case the service is not stopped. DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); if (state == SERVICE_STOP_PENDING) throw qpid::Exception("Timed out waiting for running service to stop."); // Attempt to start the service. QPID_WINDOWS_CHECK_NOT(::StartService(svc, 0, NULL), 0); QPID_LOG(info, "Service start pending..."); // Check the status until the service is no longer start pending. state = waitForStateChangeFrom(svc, SERVICE_START_PENDING); // Determine whether the service is running. if (state == SERVICE_RUNNING) { QPID_LOG(info, "Service started successfully"); } else { throw qpid::Exception(QPID_MSG("Service not yet running; state now " << state)); } } /** * */ void SCM::stop(const string& serviceName) { // Ensure a handle to the SCM database. openSvcManager(); // Get a handle to the service. AutoServiceHandle svc(::OpenService(scmHandle, serviceName.c_str(), SERVICE_STOP | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS)); QPID_WINDOWS_CHECK_NULL(svc); // Make sure the service is not already stopped; if it's stop-pending, // wait for it to finalize. DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); if (state == SERVICE_STOPPED) { QPID_LOG(info, "Service is already stopped"); return; } // If the service is running, dependencies must be stopped first. std::auto_ptr deps; DWORD numDeps = getDependentServices(svc, deps); for (DWORD i = 0; i < numDeps; i++) stop(deps.get()[i].lpServiceName); // Dependents stopped; send a stop code to the service. SERVICE_STATUS_PROCESS ssp; if (!::ControlService(svc, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp)) throw qpid::Exception(QPID_MSG("Stopping " << serviceName << ": " << qpid::sys::strError(::GetLastError()))); // Wait for the service to stop. state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING); if (state == SERVICE_STOPPED) QPID_LOG(info, QPID_MSG("Service " << serviceName << " stopped successfully.")); } /** * */ void SCM::openSvcManager() { if (NULL != scmHandle) return; scmHandle = ::OpenSCManager(NULL, // local computer NULL, // ServicesActive database SC_MANAGER_ALL_ACCESS); // Rights QPID_WINDOWS_CHECK_NULL(scmHandle); } DWORD SCM::waitForStateChangeFrom(SC_HANDLE svc, DWORD originalState) { SERVICE_STATUS_PROCESS ssStatus; DWORD bytesNeeded; DWORD waitTime; if (!::QueryServiceStatusEx(svc, // handle to service SC_STATUS_PROCESS_INFO, // information level (LPBYTE)&ssStatus, // address of structure sizeof(ssStatus), // size of structure &bytesNeeded)) // size needed if buffer is too small throw QPID_WINDOWS_ERROR(::GetLastError()); // Save the tick count and initial checkpoint. DWORD startTickCount = ::GetTickCount(); DWORD oldCheckPoint = ssStatus.dwCheckPoint; // Wait for the service to change out of the noted state. while (ssStatus.dwCurrentState == originalState) { // Do not wait longer than the wait hint. A good interval is // one-tenth of the wait hint but not less than 1 second // and not more than 10 seconds. waitTime = ssStatus.dwWaitHint / 10; if (waitTime < 1000) waitTime = 1000; else if (waitTime > 10000) waitTime = 10000; ::Sleep(waitTime); // Check the status until the service is no longer stop pending. if (!::QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO, (LPBYTE) &ssStatus, sizeof(ssStatus), &bytesNeeded)) throw QPID_WINDOWS_ERROR(::GetLastError()); if (ssStatus.dwCheckPoint > oldCheckPoint) { // Continue to wait and check. startTickCount = ::GetTickCount(); oldCheckPoint = ssStatus.dwCheckPoint; } else { if ((::GetTickCount() - startTickCount) > ssStatus.dwWaitHint) break; } } return ssStatus.dwCurrentState; } /** * Get the services that depend on @arg svc. All dependent service info * is returned in an array of ENUM_SERVICE_STATUS structures via @arg deps. * * @retval The number of dependent services. */ DWORD SCM::getDependentServices(SC_HANDLE svc, std::auto_ptr& deps) { DWORD bytesNeeded; DWORD numEntries; // Pass a zero-length buffer to get the required buffer size. if (::EnumDependentServices(svc, SERVICE_ACTIVE, 0, 0, &bytesNeeded, &numEntries)) { // If the Enum call succeeds, then there are no dependent // services, so do nothing. return 0; } if (::GetLastError() != ERROR_MORE_DATA) throw QPID_WINDOWS_ERROR((::GetLastError())); // Allocate a buffer for the dependencies. deps.reset((LPENUM_SERVICE_STATUS)(new char[bytesNeeded])); // Enumerate the dependencies. if (!::EnumDependentServices(svc, SERVICE_ACTIVE, deps.get(), bytesNeeded, &bytesNeeded, &numEntries)) throw QPID_WINDOWS_ERROR((::GetLastError())); return numEntries; } } } // namespace qpid::windows