TorService.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If 
00004 **  you did not receive the LICENSE file with this file, you may obtain it
00005 **  from the Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to
00008 **  the terms described in the LICENSE file.
00009 */
00010 
00011 /* 
00012 ** \file TorService.cpp
00013 ** \version $Id: TorService.cpp 3735 2009-04-28 20:28:01Z edmanm $
00014 ** \brief Starts, stops, installs, and uninstalls a Tor service (Win32).
00015 */
00016 
00017 #include "TorService.h"
00018 #include "tcglobal.h"
00019 
00020 #include <QLibrary>
00021 
00022 /** Returned by TorService::exitCode() when we are unable to determine the
00023  * actual exit code of the service (unless, of course, Tor returns -999999). */
00024 #define UNKNOWN_EXIT_CODE     -999999
00025 
00026 /** List of dynamically loaded NT service functions. */
00027 ServiceFunctions TorService::_service_fns = 
00028   { false,
00029     NULL, NULL, NULL, NULL, NULL,
00030     NULL, NULL, NULL, NULL, NULL
00031   };
00032 
00033 
00034 /** Default ctor. */
00035 TorService::TorService(QObject *parent)
00036   : QObject(parent)
00037 {
00038   _scm = openSCM();
00039 }
00040 
00041 /** Default dtor. */
00042 TorService::~TorService()
00043 {
00044   closeHandle(_scm);
00045 }
00046 
00047 /** Returns true if services are supported. */
00048 bool
00049 TorService::isSupported()
00050 {
00051   return (QSysInfo::WindowsVersion & QSysInfo::WV_NT_based);
00052 }
00053 
00054 /** Dyanmically loads NT service related functions from advapi32.dll. This
00055  * function is adapted from Tor's nt_service_load_library() function. See
00056  * LICENSE for details on Tor's license. */
00057 bool
00058 TorService::loadServiceFunctions()
00059 {
00060 #define LOAD_SERVICE_FN(f) do {                                         \
00061   void *fn;                                                             \
00062   if (!((fn = QLibrary::resolve("advapi32", #f)))) {                    \
00063       return false;                                                     \
00064     } else {                                                            \
00065       _service_fns.f = (f ## _fn) fn;                                   \
00066     }                                                                   \
00067   } while (0)
00068 
00069   if (!isSupported()) {
00070     _service_fns.loaded = false;
00071   } else if (!_service_fns.loaded) {
00072     LOAD_SERVICE_FN(ChangeServiceConfig2A);
00073     LOAD_SERVICE_FN(CloseServiceHandle);
00074     LOAD_SERVICE_FN(ControlService);
00075     LOAD_SERVICE_FN(CreateServiceA);
00076     LOAD_SERVICE_FN(DeleteService);
00077     LOAD_SERVICE_FN(OpenSCManagerA);
00078     LOAD_SERVICE_FN(OpenServiceA);
00079     LOAD_SERVICE_FN(QueryServiceStatus);
00080     LOAD_SERVICE_FN(SetServiceStatus);
00081     LOAD_SERVICE_FN(StartServiceA);
00082     _service_fns.loaded = true;
00083   }
00084   return _service_fns.loaded;
00085 }
00086 
00087 /** Opens a handle to the Tor service. Returns NULL on error. */
00088 SC_HANDLE
00089 TorService::openService()
00090 {
00091   if (!loadServiceFunctions())
00092     return NULL;
00093   if (!_scm)
00094     return NULL;
00095   return _service_fns.OpenServiceA(_scm, 
00096                                    (LPCTSTR)TOR_SERVICE_NAME, 
00097                                    TOR_SERVICE_ACCESS);
00098 }
00099 
00100 /** Opens a handle to the service control manager. Returns NULL on error. */
00101 SC_HANDLE
00102 TorService::openSCM()
00103 {
00104   if (!loadServiceFunctions())
00105     return NULL;
00106   return _service_fns.OpenSCManagerA(NULL, NULL, SC_MANAGER_ALL_ACCESS);
00107 }
00108 
00109 /** Closes the service <b>handle</b>. */
00110 void
00111 TorService::closeHandle(SC_HANDLE handle)
00112 {
00113   if (!loadServiceFunctions())
00114     return;
00115   _service_fns.CloseServiceHandle(handle);
00116 }
00117 
00118 /** Returns true if the Tor service is installed. */
00119 bool
00120 TorService::isInstalled()
00121 {
00122   bool installed;
00123   SC_HANDLE service = openService();
00124   installed = (service != NULL);
00125   closeHandle(service);
00126   return installed;
00127 }
00128 
00129 /** Returns true if the Tor service is running. */
00130 bool
00131 TorService::isRunning()
00132 {
00133   return (status() == SERVICE_RUNNING);
00134 }
00135 
00136 /** Starts Tor service. */
00137 void
00138 TorService::start()
00139 {
00140   SC_HANDLE service = openService();
00141 
00142   if (!service) {
00143     tc::error("Bug: We tried to start the Tor service, but it is not installed.");
00144     emit startFailed(tr("The Tor service is not installed."));
00145     return;
00146   }
00147 
00148   /* Starting a service can take up to 30 seconds! */
00149   if (status() != SERVICE_RUNNING) {
00150     int tries = 0;
00151     tc::debug("Starting the Tor service.");
00152     _service_fns.StartServiceA(service, 0, NULL);
00153 
00154     while ((status() != SERVICE_RUNNING) && ++tries <= 5)
00155       Sleep(1000);
00156   }
00157 
00158   if (status() == SERVICE_RUNNING) {
00159     emit started();
00160   } else {
00161     tc::error("Unable to start the Tor service.");
00162     emit startFailed(tr("Unable to start the Tor service."));
00163   }
00164   closeHandle(service);
00165 }
00166 
00167 /** Stops Tor service. */
00168 bool
00169 TorService::stop()
00170 {
00171   SC_HANDLE service = openService();
00172 
00173   if (!service)
00174     return false;
00175 
00176   if (status() != SERVICE_STOPPED) {
00177     SERVICE_STATUS stat;
00178     stat.dwCurrentState = SERVICE_RUNNING;
00179     tc::debug("Stopping the Tor service.");
00180     if (_service_fns.ControlService(service, SERVICE_CONTROL_STOP, &stat)) {
00181       /* XXX Five seconds isn't long enough to wait when we're stopping a Tor
00182        * that is running as a server, but we don't want to block for 30
00183        * seconds. It would be nice if we could get an async notification when
00184        * the service stops or fails to stop. */
00185       int tries = 0;
00186       while ((status() != SERVICE_STOPPED) && (++tries <= 5))
00187         Sleep(1000);
00188     }
00189   }
00190   closeHandle(service);
00191 
00192   /* Find out if the service really stopped and return the result */
00193   if (status() == SERVICE_STOPPED) {
00194     emit finished(exitCode(), exitStatus());
00195     return true;
00196   }
00197   /* XXX This needs an actual reason message. */
00198   tc::error("Unable to stop the Tor service.");
00199   return false;
00200 }
00201 
00202 /** Returns the exit code of the last Tor service that finished. */
00203 int
00204 TorService::exitCode()
00205 {
00206   SC_HANDLE service;
00207   int exitCode = UNKNOWN_EXIT_CODE;
00208   
00209   service = openService();
00210   if (service) {
00211     SERVICE_STATUS s;
00212     if (_service_fns.QueryServiceStatus(service, &s)) {
00213       /* Services return one exit code, but it could be in one of two
00214        * variables. Fun. */
00215       exitCode = (int)(s.dwWin32ExitCode == ERROR_SERVICE_SPECIFIC_ERROR
00216                                             ? s.dwServiceSpecificExitCode
00217                                             : s.dwWin32ExitCode);
00218     }
00219     closeHandle(service);
00220   }
00221   return exitCode;
00222 }
00223 
00224 /** Returns the exit status of the last Tor service that finished. */
00225 QProcess::ExitStatus
00226 TorService::exitStatus()
00227 {
00228   /* NT services don't really have an equivalent to QProcess::CrashExit, so 
00229    * this just returns QProcess::NormalExit. Tor _could_ set
00230    * dwServiceSpecificExitCode to some magic value when it starts and then
00231    * set it to the real exit code when Tor exits. Then we would know if the
00232    * service crashed when dwServiceSpecificExitCode is still the magic value.
00233    * However, I don't care and it doesn't really matter anyway. */
00234   return QProcess::NormalExit;
00235 }
00236 
00237 /** Installs the Tor service. Returns true if the service was successfully
00238  * installed or already exists. */
00239 bool
00240 TorService::install(const QString &torPath, const QString &torrc,
00241                     quint16 controlPort)
00242 {
00243   SC_HANDLE service;
00244   
00245   if (!_scm)
00246     return false;
00247  
00248   service = openService();
00249   if (!service) {
00250     QString command = QString("\"%1\" --nt-service -f \"%2\" ControlPort %3")
00251                                                  .arg(torPath)
00252                                                  .arg(torrc)
00253                                                  .arg(controlPort);
00254 
00255     tc::debug("Installing the Tor service using the command line '%1'")
00256                                                           .arg(command);
00257     service = _service_fns.CreateServiceA(_scm, 
00258                               (LPCTSTR)TOR_SERVICE_NAME, (LPCTSTR)TOR_SERVICE_DISP,
00259                               TOR_SERVICE_ACCESS, SERVICE_WIN32_OWN_PROCESS,
00260                               SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
00261                               (LPCTSTR)command.toAscii().data(), NULL, NULL, NULL, 
00262                               NULL, NULL);
00263     if (!service) {
00264       /* XXX This needs an actual reason message. */
00265       tc::error("Failed to install the Tor service.");
00266       return false;
00267     }
00268 
00269     SERVICE_DESCRIPTION desc;
00270     desc.lpDescription = TOR_SERVICE_DESC;
00271     _service_fns.ChangeServiceConfig2A(service, 
00272                                        SERVICE_CONFIG_DESCRIPTION, &desc);
00273     closeHandle(service);
00274   }
00275   return true;
00276 }
00277 
00278 /** Removes the Tor service. Returns true if the service was removed
00279  * successfully or does not exist. */
00280 bool
00281 TorService::remove()
00282 {
00283   bool removed = true;
00284   SC_HANDLE service = openService();
00285 
00286   if (service) {
00287     stop();
00288     tc::debug("Removing the Tor service.");
00289     removed = _service_fns.DeleteService(service);
00290     closeHandle(service);
00291   }
00292   if (!removed) {
00293     /* XXX This needs an actual reason message. */
00294     tc::error("Failed to remove the Tor service.");
00295   }
00296   return removed;
00297 }
00298 
00299 /** Gets the status of the Tor service. */
00300 DWORD
00301 TorService::status()
00302 {
00303   SC_HANDLE service;
00304   SERVICE_STATUS s;
00305   DWORD stat = SERVICE_ERROR;
00306   
00307   service = openService();
00308   if (service && _service_fns.QueryServiceStatus(service, &s))
00309     stat = s.dwCurrentState;
00310   closeHandle(service);
00311   return stat;
00312 }
00313 

Generated on 31 Mar 2010 for Vidalia by  doxygen 1.6.1