Vidalia  0.2.21
TorService.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If
4 ** you did not receive the LICENSE file with this file, you may obtain it
5 ** from the Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file TorService.cpp
13 ** \brief Starts, stops, installs, and uninstalls a Tor service (Win32).
14 */
15 
16 #include "TorService.h"
17 #include "tcglobal.h"
18 
19 #include <QLibrary>
20 
21 /** Returned by TorService::exitCode() when we are unable to determine the
22  * actual exit code of the service (unless, of course, Tor returns -999999). */
23 #define UNKNOWN_EXIT_CODE -999999
24 
25 /** List of dynamically loaded NT service functions. */
27  { false,
28  NULL, NULL, NULL, NULL, NULL,
29  NULL, NULL, NULL, NULL, NULL
30  };
31 
32 
33 /** Default ctor. */
34 TorService::TorService(QObject *parent)
35  : QObject(parent)
36 {
37  _scm = openSCM();
38 }
39 
40 /** Default dtor. */
42 {
44 }
45 
46 /** Returns true if services are supported. */
47 bool
49 {
50  return (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based);
51 }
52 
53 /** Dyanmically loads NT service related functions from advapi32.dll. This
54  * function is adapted from Tor's nt_service_load_library() function. See
55  * LICENSE for details on Tor's license. */
56 bool
58 {
59 #define LOAD_SERVICE_FN(f) do { \
60  void *fn; \
61  if (!((fn = QLibrary::resolve("advapi32", #f)))) { \
62  return false; \
63  } else { \
64  _service_fns.f = (f ## _fn) fn; \
65  } \
66  } while (0)
67 
68  if (!isSupported()) {
69  _service_fns.loaded = false;
70  } else if (!_service_fns.loaded) {
71  LOAD_SERVICE_FN(ChangeServiceConfig2A);
72  LOAD_SERVICE_FN(CloseServiceHandle);
73  LOAD_SERVICE_FN(ControlService);
74  LOAD_SERVICE_FN(CreateServiceA);
75  LOAD_SERVICE_FN(DeleteService);
76  LOAD_SERVICE_FN(OpenSCManagerA);
77  LOAD_SERVICE_FN(OpenServiceA);
78  LOAD_SERVICE_FN(QueryServiceStatus);
79  LOAD_SERVICE_FN(SetServiceStatus);
80  LOAD_SERVICE_FN(StartServiceA);
81  _service_fns.loaded = true;
82  }
83  return _service_fns.loaded;
84 }
85 
86 /** Opens a handle to the Tor service. Returns NULL on error. */
87 SC_HANDLE
89 {
90  if (!loadServiceFunctions())
91  return NULL;
92  if (!_scm)
93  return NULL;
95  (LPCTSTR)TOR_SERVICE_NAME,
97 }
98 
99 /** Opens a handle to the service control manager. Returns NULL on error. */
100 SC_HANDLE
102 {
103  if (!loadServiceFunctions())
104  return NULL;
105  return _service_fns.OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
106 }
107 
108 /** Closes the service <b>handle</b>. */
109 void
110 TorService::closeHandle(SC_HANDLE handle)
111 {
112  if (!loadServiceFunctions())
113  return;
115 }
116 
117 /** Returns true if the Tor service is installed. */
118 bool
120 {
121  bool installed;
122  SC_HANDLE service = openService();
123  installed = (service != NULL);
124  closeHandle(service);
125  return installed;
126 }
127 
128 /** Returns true if the Tor service is running. */
129 bool
131 {
132  return (status() == SERVICE_RUNNING);
133 }
134 
135 /** Starts Tor service. */
136 void
138 {
139  SC_HANDLE service = openService();
140 
141  if (!service) {
142  tc::error("Bug: We tried to start the Tor service, but it is not installed.");
143  emit startFailed(tr("The Tor service is not installed."));
144  return;
145  }
146 
147  /* Starting a service can take up to 30 seconds! */
148  if (status() != SERVICE_RUNNING) {
149  int tries = 0;
150  tc::debug("Starting the Tor service.");
151  _service_fns.StartServiceA(service, 0, NULL);
152 
153  while ((status() != SERVICE_RUNNING) && ++tries <= 5)
154  Sleep(1000);
155  }
156 
157  if (status() == SERVICE_RUNNING) {
158  emit started();
159  } else {
160  tc::error("Unable to start the Tor service.");
161  emit startFailed(tr("Unable to start the Tor service."));
162  }
163  closeHandle(service);
164 }
165 
166 /** Stops Tor service. */
167 bool
169 {
170  SC_HANDLE service = openService();
171 
172  if (!service)
173  return false;
174 
175  if (status() != SERVICE_STOPPED) {
176  SERVICE_STATUS stat;
177  stat.dwCurrentState = SERVICE_RUNNING;
178  tc::debug("Stopping the Tor service.");
179  if (_service_fns.ControlService(service, SERVICE_CONTROL_STOP, &stat)) {
180  /* XXX Five seconds isn't long enough to wait when we're stopping a Tor
181  * that is running as a server, but we don't want to block for 30
182  * seconds. It would be nice if we could get an async notification when
183  * the service stops or fails to stop. */
184  int tries = 0;
185  while ((status() != SERVICE_STOPPED) && (++tries <= 5))
186  Sleep(1000);
187  }
188  }
189  closeHandle(service);
190 
191  /* Find out if the service really stopped and return the result */
192  if (status() == SERVICE_STOPPED) {
193  emit finished(exitCode(), exitStatus());
194  return true;
195  }
196  /* XXX This needs an actual reason message. */
197  tc::error("Unable to stop the Tor service.");
198  return false;
199 }
200 
201 /** Returns the exit code of the last Tor service that finished. */
202 int
204 {
205  SC_HANDLE service;
207 
208  service = openService();
209  if (service) {
210  SERVICE_STATUS s;
211  if (_service_fns.QueryServiceStatus(service, &s)) {
212  /* Services return one exit code, but it could be in one of two
213  * variables. Fun. */
214  exitCode = (int)(s.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR
215  ? s.dwServiceSpecificExitCode
216  : s.dwWin32ExitCode);
217  }
218  closeHandle(service);
219  }
220  return exitCode;
221 }
222 
223 /** Returns the exit status of the last Tor service that finished. */
224 QProcess::ExitStatus
226 {
227  /* NT services don't really have an equivalent to QProcess::CrashExit, so
228  * this just returns QProcess::NormalExit. Tor _could_ set
229  * dwServiceSpecificExitCode to some magic value when it starts and then
230  * set it to the real exit code when Tor exits. Then we would know if the
231  * service crashed when dwServiceSpecificExitCode is still the magic value.
232  * However, I don't care and it doesn't really matter anyway. */
233  return QProcess::NormalExit;
234 }
235 
236 /** Installs the Tor service. Returns true if the service was successfully
237  * installed or already exists. */
238 bool
239 TorService::install(const QString &torPath, const QString &torrc,
240  quint16 controlPort)
241 {
242  SC_HANDLE service;
243 
244  if (!_scm)
245  return false;
246 
247  service = openService();
248  if (!service) {
249  QString command = QString("\"%1\" --nt-service -f \"%2\" ControlPort %3")
250  .arg(torPath)
251  .arg(torrc)
252  .arg(controlPort);
253 
254  tc::debug("Installing the Tor service using the command line '%1'")
255  .arg(command);
256  service = _service_fns.CreateServiceA(_scm,
257  (LPCTSTR)TOR_SERVICE_NAME, (LPCTSTR)TOR_SERVICE_DISP,
258  TOR_SERVICE_ACCESS, SERVICE_WIN32_OWN_PROCESS,
259  SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
260  (LPCTSTR)command.toAscii().data(), NULL, NULL, NULL,
261  NULL, NULL);
262  if (!service) {
263  /* XXX This needs an actual reason message. */
264  tc::error("Failed to install the Tor service.");
265  return false;
266  }
267 
268  SERVICE_DESCRIPTION desc;
269  desc.lpDescription = TOR_SERVICE_DESC;
271  SERVICE_CONFIG_DESCRIPTION, &desc);
272  closeHandle(service);
273  }
274  return true;
275 }
276 
277 /** Removes the Tor service. Returns true if the service was removed
278  * successfully or does not exist. */
279 bool
281 {
282  bool removed = true;
283  SC_HANDLE service = openService();
284 
285  if (service) {
286  stop();
287  tc::debug("Removing the Tor service.");
288  removed = _service_fns.DeleteService(service);
289  closeHandle(service);
290  }
291  if (!removed) {
292  /* XXX This needs an actual reason message. */
293  tc::error("Failed to remove the Tor service.");
294  }
295  return removed;
296 }
297 
298 /** Gets the status of the Tor service. */
299 DWORD
301 {
302  SC_HANDLE service;
303  SERVICE_STATUS s;
304  DWORD stat = SERVICE_ERROR;
305 
306  service = openService();
307  if (service && _service_fns.QueryServiceStatus(service, &s))
308  stat = s.dwCurrentState;
309  closeHandle(service);
310  return stat;
311 }
312