Vidalia 0.2.10
|
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