summaryrefslogtreecommitdiff
path: root/qpid/cpp/src/windows/SCM.cpp
blob: 2eeb143427eeac3448503d5eeeed46afb398a384 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*
 *
 * 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<ENUM_SERVICE_STATUS> 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<ENUM_SERVICE_STATUS>& 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