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