[vidalia/alpha] Improve the bootstrap procedure to play better with the async TorControl

commit decef1ded8096ecdf15c3e2c140b315e09b4cc61 Author: Tomás Touceda <chiiph@torproject.org> Date: Thu Mar 15 11:10:46 2012 -0300 Improve the bootstrap procedure to play better with the async TorControl --- src/torcontrol/ControlConnection.cpp | 42 +++--- src/torcontrol/TorControl.cpp | 89 ++++++++++- src/torcontrol/TorControl.h | 45 ++++-- src/torcontrol/TorProcess.cpp | 28 ++-- src/vidalia/MainWindow.cpp | 295 +++++++++++++++++++++------------- src/vidalia/MainWindow.h | 16 +- 6 files changed, 345 insertions(+), 170 deletions(-) diff --git a/src/torcontrol/ControlConnection.cpp b/src/torcontrol/ControlConnection.cpp index 21cb527..af3d2d3 100644 --- a/src/torcontrol/ControlConnection.cpp +++ b/src/torcontrol/ControlConnection.cpp @@ -1,10 +1,10 @@ /* ** This file is part of Vidalia, and is subject to the license terms in the -** LICENSE file, found in the top level directory of this distribution. If +** LICENSE file, found in the top level directory of this distribution. If ** you did not receive the LICENSE file with this file, you may obtain it ** from the Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ @@ -44,6 +44,9 @@ ControlConnection::~ControlConnection() { /* Clean up after the send waiter */ delete _sendWaiter; + delete _sock; + delete _connectTimer; + _sock = 0; } /** Connect to the specified Tor control interface. */ @@ -75,7 +78,7 @@ ControlConnection::connect(const QString &addr) "control thread is already running."); return; } - + _path = addr; _connectAttempt = 0; setStatus(Connecting); @@ -92,7 +95,7 @@ ControlConnection::connect() _connectAttempt++; tc::debug("Connecting to Tor (Attempt %1 of %2)").arg(_connectAttempt) .arg(MAX_CONNECT_ATTEMPTS); - + switch(_method) { case ControlMethod::Socket: _sock->connectToServer(_path); @@ -115,7 +118,7 @@ ControlConnection::disconnect() while (!_recvQueue.isEmpty()) { ReceiveWaiter *w = _recvQueue.dequeue(); - w->setResult(false, ControlReply(), + w->setResult(false, ControlReply(), tr("Control socket is not connected.")); QCoreApplication::processEvents(QEventLoop::AllEvents, 1000); @@ -135,9 +138,6 @@ ControlConnection::disconnect() } _sock->disconnect(this); - delete _sock; - delete _connectTimer; - _sock = 0; } /** Called when the control socket is connected. This method checks that the @@ -278,10 +278,10 @@ bool ControlConnection::send(const ControlCommand &cmd, QString *errmsg) { if (!_sock || !_sock->isConnected()) { - return err(errmsg, tr("Control socket is not connected.")); + return err(errmsg, tr("Control socket is not connected.")); } QCoreApplication::postEvent(_sock, new SendCommandEvent(cmd, _sendWaiter)); - + return _sendWaiter->getResult(errmsg); } @@ -291,21 +291,21 @@ ControlConnection::onReadyRead() { ReceiveWaiter *waiter; QString errmsg; - + while (_sock->canReadLine()) { ControlReply reply; if (_sock->readReply(reply, &errmsg)) { if (reply.getStatus() == "650") { /* Asynchronous event message */ tc::debug("Control Event: %1").arg(reply.toString()); - + if (_events) { _events->handleEvent(reply); } } else { /* Response to a previous command */ tc::debug("Control Reply: %1").arg(reply.toString()); - + if (!_recvQueue.isEmpty()) { waiter = _recvQueue.dequeue(); waiter->setResult(true, reply); @@ -327,14 +327,14 @@ ControlConnection::run() _connectTimer = new QTimer(); _connectTimer->setSingleShot(true); - + QObject::connect(_sock, SIGNAL(readyRead()), this, SLOT(onReadyRead()), Qt::DirectConnection); QObject::connect(_sock, SIGNAL(disconnected()), this, SLOT(onDisconnected()), Qt::DirectConnection); QObject::connect(_sock, SIGNAL(connected()), this, SLOT(onConnected()), Qt::DirectConnection); - QObject::connect(_sock, SIGNAL(error(QAbstractSocket::SocketError)), + QObject::connect(_sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)), Qt::DirectConnection); QObject::connect(_connectTimer, SIGNAL(timeout()), this, SLOT(connect()), @@ -349,8 +349,8 @@ ControlConnection::run() * ControlConnection::ReceiveWaiter */ /** Waits for and gets the reply from a control command. */ -bool -ControlConnection::ReceiveWaiter::getResult(ControlReply *reply, +bool +ControlConnection::ReceiveWaiter::getResult(ControlReply *reply, QString *errmsg) { while(_status == Waiting) @@ -364,9 +364,9 @@ ControlConnection::ReceiveWaiter::getResult(ControlReply *reply, } /** Sets the result and reply from a control command. */ -void -ControlConnection::ReceiveWaiter::setResult(bool success, - const ControlReply &reply, +void +ControlConnection::ReceiveWaiter::setResult(bool success, + const ControlReply &reply, const QString &errmsg) { _status = (success ? Success : Failed); diff --git a/src/torcontrol/TorControl.cpp b/src/torcontrol/TorControl.cpp index 9cdf6ad..0ad9db6 100644 --- a/src/torcontrol/TorControl.cpp +++ b/src/torcontrol/TorControl.cpp @@ -3,8 +3,8 @@ ** LICENSE file, found in the top level directory of this distribution. If ** you did not receive the LICENSE file with this file, you may obtain it ** from the Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ @@ -26,7 +26,7 @@ /** Default constructor */ TorControl::TorControl(ControlMethod::Method method) -{ + : QObject(), _shouldContinue(true), _reason("") { #define RELAY_SIGNAL(src, sig) \ QObject::connect((src), (sig), this, (sig)) @@ -83,6 +83,12 @@ TorControl::TorControl(ControlMethod::Method method) QObject::connect(_torProcess, SIGNAL(log(QString, QString)), this, SLOT(onLogStdout(QString, QString))); + QObject::connect(_torProcess, SIGNAL(startFailed(QString)), + this, SLOT(torStartFailed(QString))); + QObject::connect(_controlConn, SIGNAL(connectFailed(QString)), + this, SLOT(torConnectFailed(QString))); + + #if defined(Q_OS_WIN32) _torService = new TorService(this); RELAY_SIGNAL(_torService, SIGNAL(started())); @@ -127,6 +133,33 @@ TorControl::start(const QString &tor, const QStringList &args) } } +/** Returns true if the process is running */ +bool +TorControl::torStarted() +{ + return _torProcess->state() == QProcess::Running; +} + +/** Called when starting the tor process failed */ +void +TorControl::torStartFailed(QString errmsg) +{ + _shouldContinue = false; + _reason = tr("Start failed: %1").arg(errmsg); +} + +/** Returns true if the bootstrap should continue. If it returns + * false, it also sets the errmsg to the last error */ +bool +TorControl::shouldContinue(QString *errmsg) +{ + if(not errmsg) + errmsg = new QString(); + + *errmsg = _reason; + return _shouldContinue; +} + /** Stop the Tor process. */ bool TorControl::stop(QString *errmsg) @@ -157,10 +190,26 @@ TorControl::onStopped(int exitCode, QProcess::ExitStatus exitStatus) if (_controlConn->status() == ControlConnection::Connecting) _controlConn->cancelConnect(); + if (exitStatus == QProcess::CrashExit || exitCode != 0) { + _shouldContinue = false; + _reason = tr("Process finished: ExitCode=%1").arg(exitCode); + } + emit stopped(); + disconnect(); emit stopped(exitCode, exitStatus); } +/** Returns true if the tor process has finished, along with its exit + * code and status */ +bool +TorControl::finished(int *exitCode, QProcess::ExitStatus *exitStatus) +{ + *exitCode = _torProcess->exitCode(); + *exitStatus = _torProcess->exitStatus(); + return _torProcess->state() == QProcess::NotRunning; +} + /** Detects if the Tor process is running under Vidalia. Returns true if * Vidalia owns the Tor process, or false if it was an independent Tor. */ bool @@ -209,6 +258,14 @@ TorControl::connect(const QString &path) _controlConn->connect(path); } +/** Called when the connection to tor has failed */ +void +TorControl::torConnectFailed(QString errmsg) +{ + _shouldContinue = false; + _reason = tr("Connection failed: %1").arg(errmsg); +} + /** Disconnect from Tor's control port */ void TorControl::disconnect() @@ -217,6 +274,7 @@ TorControl::disconnect() _controlConn->disconnect(); } +/** Emits the proper bootstrapStatusChanged */ void TorControl::getBootstrapPhase() { @@ -257,6 +315,9 @@ TorControl::onDisconnected() /* Tor isn't running, so it has no version */ _torVersion = QString(); + _shouldContinue = false; + _reason = tr("Disconnected"); + /* Let interested parties know we lost our control connection */ emit disconnected(); } @@ -307,6 +368,9 @@ TorControl::authenticate(const QByteArray cookie, QString *errmsg) if (!send(cmd, reply, &str)) { emit authenticationFailed(str); + _shouldContinue = false; + _reason = str; + return err(errmsg, str); } onAuthenticated(); @@ -326,6 +390,9 @@ TorControl::authenticate(const QString &password, QString *errmsg) if (!send(cmd, reply, &str)) { emit authenticationFailed(str); + _shouldContinue = false; + _reason = str; + return err(errmsg, str); } onAuthenticated(); @@ -403,6 +470,7 @@ TorControl::useFeature(const QString &feature, QString *errmsg) return send(cmd, errmsg); } +/** Returns the current BootstrapStatus */ BootstrapStatus TorControl::bootstrapStatus(QString *errmsg) { @@ -906,7 +974,7 @@ TorControl::loadConf(const QString &contents, QString *errmsg) { ControlCommand cmd("+LOADCONF"); ControlReply reply; - + cmd.addArgument(contents + "."); return send(cmd, reply, errmsg); } @@ -963,6 +1031,7 @@ TorControl::resetConf(QString key, QString *errmsg) return resetConf(QStringList() << key, errmsg); } +/** Returns true if microdescriptors are used */ bool TorControl::useMicrodescriptors(QString *errmsg) { @@ -1131,8 +1200,8 @@ TorControl::closeStream(const StreamId &streamId, QString *errmsg) return send(cmd, errmsg); } - /** Gets a list of address mappings of the type specified by <b>type</b> - * (defaults to <i>AddressMapAll</i>. */ +/** Gets a list of address mappings of the type specified by <b>type</b> + * (defaults to <i>AddressMapAll</i>. */ AddressMap TorControl::getAddressMap(AddressMap::AddressMapType type, QString *errmsg) { @@ -1180,3 +1249,11 @@ TorControl::takeOwnership(QString *errmsg) ControlCommand cmd("TAKEOWNERSHIP"); return send(cmd, errmsg); } + +/** Clear the error state for shouldContinue(errmsg) */ +void +TorControl::clearErrState() +{ + _shouldContinue = true; + _reason = ""; +} diff --git a/src/torcontrol/TorControl.h b/src/torcontrol/TorControl.h index 96c7bd2..6432c83 100644 --- a/src/torcontrol/TorControl.h +++ b/src/torcontrol/TorControl.h @@ -1,14 +1,14 @@ /* ** This file is part of Vidalia, and is subject to the license terms in the -** LICENSE file, found in the top level directory of this distribution. If +** LICENSE file, found in the top level directory of this distribution. If ** you did not receive the LICENSE file with this file, you may obtain it ** from the Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ -/* +/* ** \file TorControl.h ** \brief Object for interacting with the Tor process and control interface */ @@ -49,7 +49,7 @@ typedef QHash<QString,QString> DescriptorAnnotations; class TorControl : public QObject { Q_OBJECT - + public: /** Default constructor */ TorControl(ControlMethod::Method method = ControlMethod::Port); @@ -58,6 +58,9 @@ public: /** Start the Tor process */ void start(const QString &tor, const QStringList &args); + /** Returns true if the process is running */ + bool torStarted(); + /** Stop the Tor process */ bool stop(QString *errmsg = 0); /** Detect if the Tor process is running */ @@ -71,6 +74,7 @@ public: /** Connect to Tor's control socket */ void connect(const QHostAddress &address, quint16 port); void connect(const QString &path); + /** Disconnect from Tor's control socket */ void disconnect(); /** Check if we're connected to Tor's control socket */ @@ -79,14 +83,14 @@ public: bool authenticate(const QByteArray cookie, QString *errmsg = 0); /** Sends an authentication password to Tor. */ bool authenticate(const QString &password = QString(), QString *errmsg = 0); - + /** Sends a PROTOCOLINFO command to Tor and parses the response. */ ProtocolInfo protocolInfo(QString *errmsg = 0); /** Returns the Tor software's current bootstrap phase and status. */ BootstrapStatus bootstrapStatus(QString *errmsg = 0); - /** Returns true if Tor either has an open circuit or (on Tor >= + /** Returns true if Tor either has an open circuit or (on Tor >= * 0.2.0.1-alpha) has previously decided it's able to establish a circuit. */ bool isCircuitEstablished(); @@ -106,11 +110,11 @@ public: /** Sends a signal to Tor */ bool signal(TorSignal::Signal sig, QString *errmsg = 0); - + /** Returns an address on which Tor is listening for application * requests. If none are available, a null QHostAddress is returned. */ QHostAddress getSocksAddress(QString *errmsg = 0); - /** Returns a (possibly empty) list of all currently configured + /** Returns a (possibly empty) list of all currently configured * SocksListenAddress entries. */ QStringList getSocksAddressList(QString *errmsg = 0); /** Returns a valid SOCKS port for Tor, or 0 if Tor is not accepting @@ -162,7 +166,7 @@ public: /** Sends a GETCONF message to Tor with the single key and returns a QString * containing the value returned by Tor */ QString getHiddenServiceConf(const QString &key, QString *errmsg = 0); - + /** Asks Tor to save the current configuration to its torrc */ bool saveConf(QString *errmsg = 0); /** Tells Tor to reset the given configuration keys back to defaults. */ @@ -200,7 +204,7 @@ public: CircuitList getCircuits(QString *errmsg = 0); /** Gets a list of current streams. */ StreamList getStreams(QString *errmsg = 0); - + /** Gets a list of address mappings of the type specified by <b>type</b> * (defaults to <i>AddressMapAll</i>. */ AddressMap getAddressMap( @@ -216,6 +220,15 @@ public: /** Takes ownership of the tor process it's communicating to */ bool takeOwnership(QString *errmsg); + /** Clear the error state for shouldContinue(errmsg) */ + void clearErrState(); + /** Returns true if the bootstrap should continue. If it returns + * false, it also sets the errmsg to the last error */ + bool shouldContinue(QString *errmsg = 0); + /** Returns true if the tor process has finished, along with its exit + * code and status */ + bool finished(int *exitCode, QProcess::ExitStatus *exitStatus); + public slots: /** Closes the circuit specified by <b>circId</b>. If <b>ifUnused</b> is * true, then the circuit will not be closed unless it is unused. */ @@ -313,7 +326,7 @@ signals: /** Emitted when Tor decides the client's external IP address has changed * to <b>ip</b>. If <b>hostname</b> is non-empty, Tor obtained the new - * value for <b>ip</b> by resolving <b>hostname</b>. + * value for <b>ip</b> by resolving <b>hostname</b>. */ void externalAddressChanged(const QHostAddress &ip, const QString &hostname); @@ -338,7 +351,7 @@ signals: */ void dnsUseless(); - /** Indicates Tor has started testing the reachability of its OR port + /** Indicates Tor has started testing the reachability of its OR port * using the IP address <b>ip</b> and port <b>port</b>. */ void checkingOrPortReachability(const QHostAddress &ip, quint16 port); @@ -396,6 +409,9 @@ private: TorService* _torService; #endif + bool _shouldContinue; + QString _reason; + /** Send a message to Tor and read the response */ bool send(ControlCommand cmd, ControlReply &reply, QString *errmsg = 0); /** Send a message to Tor and discard the response */ @@ -412,6 +428,9 @@ private slots: void onDisconnected(); void onLogStdout(const QString &severity, const QString &message); void onAuthenticated(); + + void torStartFailed(QString errmsg); + void torConnectFailed(QString errmsg); }; #endif diff --git a/src/torcontrol/TorProcess.cpp b/src/torcontrol/TorProcess.cpp index 7275f7a..84dfcb3 100644 --- a/src/torcontrol/TorProcess.cpp +++ b/src/torcontrol/TorProcess.cpp @@ -1,14 +1,14 @@ /* ** This file is part of Vidalia, and is subject to the license terms in the -** LICENSE file, found in the top level directory of this distribution. If +** LICENSE file, found in the top level directory of this distribution. If ** you did not receive the LICENSE file with this file, you may obtain it ** from the Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ -/* +/* ** \file TorProcess.cpp ** \brief Starts and stops a Tor process */ @@ -31,9 +31,9 @@ TorProcess::TorProcess(QObject *parent) : QProcess(parent) { openStdout(); - connect(this, SIGNAL(readyReadStandardOutput()), + connect(this, SIGNAL(readyReadStandardOutput()), this, SLOT(onReadyRead())); - connect(this, SIGNAL(error(QProcess::ProcessError)), + connect(this, SIGNAL(error(QProcess::ProcessError)), this, SLOT(onError(QProcess::ProcessError))); } @@ -53,7 +53,7 @@ TorProcess::formatArguments(const QStringList &args) * signal started() will be emitted. If Tor fails to start, * startFailed(errmsg) will be emitted, with an appropriate error message. */ void -TorProcess::start(const QString &app, const QStringList &args) +TorProcess::start(const QString &app, const QStringList &args) { QString exe = app; #if defined(Q_OS_WIN32) @@ -61,12 +61,12 @@ TorProcess::start(const QString &app, const QStringList &args) * quoted before being passed to it. */ exe = "\"" + exe + "\""; #endif - + /* Attempt to start Tor with the given command-line arguments */ QStringList env = QProcess::systemEnvironment(); #if !defined(Q_OS_WIN32) /* Add "/usr/sbin" to an existing $PATH - * XXX What if they have no path? Would always just making one with + * XXX What if they have no path? Would always just making one with * "/usr/sbin" smart? Should we add anything else? */ for (int i = 0; i < env.size(); i++) { QString envVar = env.at(i); @@ -93,7 +93,7 @@ TorProcess::stop(QString *errmsg) tc::debug("Stopping the Tor process."); /* Tell the process to stop */ #if defined(Q_OS_WIN32) - /* Tor on Windows doesn't understand a WM_CLOSE message (which is what + /* Tor on Windows doesn't understand a WM_CLOSE message (which is what * QProcess::terminate() sends it), so we have to kill it harshly. */ kill(); #else @@ -103,7 +103,7 @@ TorProcess::stop(QString *errmsg) if (!waitForFinished(5000)) { tc::error("Tor failed to stop: %1").arg(errorString()); if (errmsg) { - *errmsg = + *errmsg = tr("Process %1 failed to stop. [%2]").arg(pid()).arg(errorString()); } return false; @@ -149,7 +149,7 @@ TorProcess::onReadyRead() { int i, j; QString line; - + while (canReadLine()) { line = readLine(); if (!line.isEmpty()) { @@ -171,11 +171,11 @@ TorProcess::onError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { tc::error("The Tor process failed to start: %1").arg(errorString()); - /* Tor didn't start, so let everyone know why. */ - emit startFailed(errorString()); } else { tc::error("Tor process error: %1").arg(errorString()); } + /* Tor didn't start, so let everyone know why. */ + emit startFailed(errorString()); } /** Returns the version reported by the Tor executable specified in diff --git a/src/vidalia/MainWindow.cpp b/src/vidalia/MainWindow.cpp index 4307f05..0861b0c 100644 --- a/src/vidalia/MainWindow.cpp +++ b/src/vidalia/MainWindow.cpp @@ -3,8 +3,8 @@ ** LICENSE file, found in the top level directory of this distribution. If you ** did not receive the LICENSE file with this file, you may obtain it from the ** Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ @@ -105,10 +105,12 @@ void qt_mac_set_dock_menu(QMenu *menu); MainWindow::MainWindow() : VidaliaWindow("MainWindow") { + _pressedStop = false; + _startedWithPrevious = false; /* Create a new TorControl object, used to communicate with Tor */ - _torControl = Vidalia::torControl(); + _torControl = Vidalia::torControl(); _engine = new PluginEngine(); @@ -239,9 +241,9 @@ MainWindow::createToolBar() tool->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } -/** Creates a QMenu object that contains QActions which compose the system +/** Creates a QMenu object that contains QActions which compose the system * tray menu. */ -QMenu* +QMenu* MainWindow::createTrayMenu() { QMenu *menu = new QMenu(this); @@ -255,7 +257,7 @@ MainWindow::createTrayMenu() menu->addAction(_actionShowControlPanel); menu->addMenu(&_reattachMenu); - + #if !defined(Q_WS_MAC) /* These aren't added to the dock menu on Mac, since they are in the * standard Mac locations in the menu bar. */ @@ -398,18 +400,6 @@ MainWindow::createConnections() connect(vApp, SIGNAL(running()), this, SLOT(running())); connect(vApp, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit())); - connect(_torControl, SIGNAL(started()), this, SLOT(started())); - connect(_torControl, SIGNAL(startFailed(QString)), - this, SLOT(startFailed(QString))); - connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)), - this, SLOT(stopped(int, QProcess::ExitStatus))); - connect(_torControl, SIGNAL(connected()), this, SLOT(connected())); - connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected())); - connect(_torControl, SIGNAL(connectFailed(QString)), - this, SLOT(connectFailed(QString))); - connect(_torControl, SIGNAL(authenticated()), this, SLOT(authenticated())); - connect(_torControl, SIGNAL(authenticationFailed(QString)), - this, SLOT(authenticationFailed(QString))); connect(_torControl, SIGNAL(clockSkewed(int, QString)), this, SLOT(clockSkewed(int, QString))); @@ -422,8 +412,6 @@ MainWindow::createConnections() _torControl->setEvent(TorEvents::ClientStatus); connect(_torControl, SIGNAL(bootstrapStatusChanged(BootstrapStatus)), this, SLOT(bootstrapStatusChanged(BootstrapStatus))); - connect(_torControl, SIGNAL(circuitEstablished()), - this, SLOT(circuitEstablished())); connect(_torControl, SIGNAL(dangerousPort(quint16, bool)), this, SLOT(warnDangerousPort(quint16, bool))); @@ -458,7 +446,7 @@ MainWindow::createConnections() this, SLOT(upnpError(UPNPControl::UPNPError))); #endif - connect(_engine, SIGNAL(pluginTab(VidaliaTab *)), + connect(_engine, SIGNAL(pluginTab(VidaliaTab *)), this, SLOT(addTab(VidaliaTab *))); } @@ -468,16 +456,17 @@ MainWindow::createConnections() void MainWindow::close() { + _pressedStop = true; + QCoreApplication::processEvents(); + if (_torControl->isVidaliaRunningTor()) { /* If we're running a server currently, ask if we want to do a delayed * shutdown. If we do, then close Vidalia only when Tor stops. Otherwise, * kill Tor and bail now. */ ServerSettings settings(_torControl); if (_torControl->isConnected() && settings.isServerEnabled()) { - connect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit())); - if (!stop()) - QObject::disconnect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit())); - return; + if(!stop()) + return; } } vApp->quit(); @@ -545,7 +534,7 @@ MainWindow::aboutToQuit() vNotice("Cleaning up before exiting."); if (_torControl->isVidaliaRunningTor()) { - /* Kill our Tor process now */ + /* Kill our Tor process now */ _torControl->stop(); } @@ -559,12 +548,14 @@ MainWindow::aboutToQuit() /** Attempts to start Tor. If Tor fails to start, then startFailed() will be * called with an error message containing the reason. */ -void +void MainWindow::start() { TorSettings settings; QStringList args; + _torControl->clearErrState(); + updateTorStatus(Starting); // Disable autoconfiguration if there are missing config data @@ -579,12 +570,12 @@ MainWindow::start() if(settings.getControlMethod() == ControlMethod::Port) { if(!settings.autoControlPort() && net_test_connect(settings.getControlAddress(), settings.getControlPort())) { - started(); + connectToTor(); return; } } else { if (socket_test_connect(settings.getSocketPath())) { - started(); + connectToTor(); return; } } @@ -602,7 +593,7 @@ MainWindow::start() } } } - + if(_torControl->getTorVersion() >= 0x020309) { if (!torrc_defaults.isEmpty()) { args << "--defaults-torrc" << torrc_defaults; @@ -619,7 +610,7 @@ MainWindow::start() /* Specify Tor's data directory, if different from the default */ QString dataDirectory = settings.getDataDirectory(); QString expDataDirectory = expand_filename(dataDirectory); - + if(settings.getControlMethod() == ControlMethod::Port) { if(settings.autoControlPort()) { QString portconf = QString("%1/port.conf").arg(expDataDirectory); @@ -637,8 +628,9 @@ MainWindow::start() } } - args << "__OwningControllerProcess" << QString::number(QCoreApplication::applicationPid()); - + if (_torControl->getTorVersion() < 0x02021c) + args << "__OwningControllerProcess" << QString::number(QCoreApplication::applicationPid()); + /* Add the control port authentication arguments */ switch (settings.getAuthenticationMethod()) { case TorSettings::PasswordAuth: @@ -664,15 +656,36 @@ MainWindow::start() _isIntentionalExit = true; /* Kick off the Tor process */ _torControl->start(settings.getExecutable(), args); + + QString errmsg; + while(not _torControl->torStarted()) { + QCoreApplication::processEvents(); + if(not _torControl->shouldContinue(&errmsg) and not _pressedStop) { + startFailed(errmsg); + + int exitCode; + QProcess::ExitStatus exitStatus; + + if(_torControl->finished(&exitCode, &exitStatus)) + stopped(exitCode, exitStatus); + + return; + } else if(_pressedStop) { + return; + } + } + connectToTor(); } /** Slot: Called when the Tor process is started. It will connect the control * socket and set the icons and tooltips accordingly. */ -void -MainWindow::started() +void +MainWindow::connectToTor() { TorSettings settings; + _torControl->clearErrState(); + updateTorStatus(Started); /* Now that Tor is running, we want to know if it dies when we didn't want @@ -741,6 +754,26 @@ MainWindow::started() Vidalia::torrc()->apply(Vidalia::torControl()); } setStartupProgress(STARTUP_PROGRESS_CONNECTING, tr("Connecting to Tor")); + + QString errmsg; + while(not _torControl->isConnected()) { + QCoreApplication::processEvents(); + if(not _torControl->shouldContinue(&errmsg) and not _pressedStop) { + connectFailed(errmsg); + + int exitCode; + QProcess::ExitStatus exitStatus; + + if(_torControl->finished(&exitCode, &exitStatus)) + stopped(exitCode, exitStatus); + + return; + } else if(_pressedStop) { + return; + } + } + + authenticate(); } /** Disconnects the control socket and stops the Tor process. */ @@ -753,6 +786,9 @@ MainWindow::stop() bool rc; VidaliaSettings settings; + _pressedStop = true; + QCoreApplication::processEvents(); + /* If we're running a server, give users the option of terminating * gracefully so clients have time to find new servers. */ if (server.isServerEnabled() && !_delayedShutdownStarted) { @@ -765,8 +801,8 @@ MainWindow::stop() "open connections from clients.\n\n" "Would you like to shutdown gracefully and " "give clients time to find a new relay?"), - VMessageBox::Yes|VMessageBox::Default, - VMessageBox::No, + VMessageBox::Yes|VMessageBox::Default, + VMessageBox::No, VMessageBox::Cancel|VMessageBox::Escape, "Remember this answer", &settings, SETTING_REMEMBER_SHUTDOWN); } @@ -775,8 +811,8 @@ MainWindow::stop() else if (response == VMessageBox::Cancel) return false; } - - prevStatus = updateTorStatus(Stopping); + + prevStatus = updateTorStatus(Stopping); if (_delayedShutdownStarted) { /* Start a delayed shutdown */ rc = _torControl->signal(TorSignal::Shutdown, &errmsg); @@ -785,15 +821,22 @@ MainWindow::stop() _isIntentionalExit = true; rc = _torControl->stop(&errmsg); } - + + int exitCode; + QProcess::ExitStatus exitStatus; + + while(not _torControl->finished(&exitCode, &exitStatus)) { + QCoreApplication::processEvents(); + } + if (!rc) { /* We couldn't tell Tor to stop, for some reason. */ int response = VMessageBox::warning(this, tr("Error Shutting Down"), - p(tr("Vidalia was unable to stop the Tor software.")) + p(tr("Vidalia was unable to stop the Tor software.")) + p(errmsg), - VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, + VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, VMessageBox::Help); - + if (response == VMessageBox::Help) { /* Show some troubleshooting help */ showHelpDialog("troubleshooting.stop"); @@ -803,12 +846,17 @@ MainWindow::stop() _delayedShutdownStarted = false; updateTorStatus(prevStatus); } + + stopped(exitCode, exitStatus); + + _pressedStop = false; + return rc; } /** Slot: Called when the Tor process has exited. It will adjust the tray * icons and tooltips accordingly. */ -void +void MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus) { updateTorStatus(Stopped); @@ -825,7 +873,7 @@ MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus) "unexpectedly.\n\n" "Please check the message log for recent " "warning or error messages."), - VMessageBox::Ok|VMessageBox::Escape, + VMessageBox::Ok|VMessageBox::Escape, VMessageBox::ShowLog|VMessageBox::Default, VMessageBox::Help); if (ret == VMessageBox::ShowLog) @@ -834,6 +882,9 @@ MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus) showHelpDialog("troubleshooting.torexited"); } } + QObject::disconnect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)), + this, SLOT(stopped(int, QProcess::ExitStatus))); + QObject::disconnect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected())); } /** Called when the Tor process fails to start, for example, because the path @@ -857,7 +908,7 @@ MainWindow::startFailed(QString errmsg) start(); return; } - + updateTorStatus(Stopped); /* Display an error message and see if the user wants some help */ @@ -879,22 +930,15 @@ MainWindow::startFailed(QString errmsg) } } -/** Called when the control socket has successfully connected to Tor. */ -void -MainWindow::connected() -{ - authenticate(); -} - /** Called when the connection to the control socket fails. The reason will be * given in the errmsg parameter. */ void MainWindow::connectFailed(QString errmsg) { /* Ok, ok. It really isn't going to connect. I give up. */ - int response = VMessageBox::warning(this, + int response = VMessageBox::warning(this, tr("Connection Error"), p(errmsg), - VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, + VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, VMessageBox::Retry, VMessageBox::Help); @@ -917,6 +961,9 @@ void MainWindow::authenticated() { TorSettings settings; + + _torControl->clearErrState(); + if(settings.autoControlPort()) { // We want to remember the ports if it's on auto QString control_str = "", socks_str = ""; @@ -930,7 +977,7 @@ MainWindow::authenticated() if(socks_parts.size() > 1) socks_str = socks_parts[1]; } - + _previousControlPort = control_str; _previousSocksPort = socks_str; } else { @@ -943,14 +990,14 @@ MainWindow::authenticated() QString errmsg; updateTorStatus(Authenticated); - + /* If Tor doesn't have bootstrapping events, then update the current * status string and bump the progress bar along a bit. */ if (_torControl->getTorVersion() < 0x020101) { setStartupProgress(STARTUP_PROGRESS_CIRCUITBUILD, tr("Connecting to the Tor network")); } - + /* Let people click on their beloved "New Circuit" button */ _actionNewIdentity->setEnabled(true); @@ -970,10 +1017,6 @@ MainWindow::authenticated() /* Configure UPnP port forwarding if needed */ serverSettings.configurePortForwarding(); - /* Check if Tor has a circuit established */ - if (_torControl->isCircuitEstablished()) { - circuitEstablished(); - } /* Check the status of Tor's version */ if (_torControl->getTorVersion() >= 0x020001) checkTorVersion(); @@ -982,6 +1025,25 @@ MainWindow::authenticated() if (status.isValid()) bootstrapStatusChanged(status); } + + /* Check if Tor has a circuit established */ + while(not _torControl->isCircuitEstablished()) { + QCoreApplication::processEvents(); + if(not _torControl->shouldContinue(&errmsg) and not _pressedStop) { + startFailed(errmsg); + + int exitCode; + QProcess::ExitStatus exitStatus; + + if(_torControl->finished(&exitCode, &exitStatus)) + stopped(exitCode, exitStatus); + return; + } else if(_pressedStop) { + return; + } + } + + circuitEstablished(); } /** Called when Vidalia fails to authenticate to Tor. The failure reason is @@ -990,7 +1052,7 @@ void MainWindow::authenticationFailed(QString errmsg) { bool retry = false; - + vWarn("Authentication failed: %1").arg(errmsg); /* Parsing log messages is evil, but we're left with little option */ @@ -1009,7 +1071,7 @@ MainWindow::authenticationFailed(QString errmsg) break; } } - + dlg.setResetEnabled(torPid > 0); int ret = dlg.exec(); @@ -1026,9 +1088,9 @@ MainWindow::authenticationFailed(QString errmsg) } } } - + if (_torControl->isRunning()) - if (_isVidaliaRunningTor) + if (_isVidaliaRunningTor) stop(); else disconnect(); @@ -1093,7 +1155,7 @@ void MainWindow::bootstrapStatusChanged(const BootstrapStatus &bs) { int percentComplete = STARTUP_PROGRESS_BOOTSTRAPPING + bs.percentComplete(); - bool warn = (bs.severity() == tc::WarnSeverity && + bool warn = (bs.severity() == tc::WarnSeverity && bs.recommendedAction() != BootstrapStatus::RecommendIgnore); QString description; @@ -1190,6 +1252,9 @@ MainWindow::circuitEstablished() } } #endif + connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)), + this, SLOT(stopped(int, QProcess::ExitStatus))); + connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected())); } /** Called when Tor thinks the user has tried to connect to a port that @@ -1217,7 +1282,7 @@ MainWindow::warnDangerousPort(quint16 port, bool rejected) case 109: case 110: case 143: - application = tr("(probably an email client)"); + application = tr("(probably an email client)"); break; default: @@ -1284,18 +1349,20 @@ MainWindow::warnDangerousPort(quint16 port, bool rejected) /** Attempts to authenticate to Tor's control port, depending on the * authentication method specified in TorSettings::getAuthenticationMethod(). */ -bool +void MainWindow::authenticate() { TorSettings::AuthenticationMethod authMethod; TorSettings settings; ProtocolInfo pi; - + + _torControl->clearErrState(); + updateTorStatus(Authenticating); setStartupProgress(STARTUP_PROGRESS_AUTHENTICATING, tr("Authenticating to Tor")); - authMethod = settings.getAuthenticationMethod(); + authMethod = settings.getAuthenticationMethod(); pi = _torControl->protocolInfo(); QStringList authMethods; if (!pi.isEmpty()) { @@ -1308,30 +1375,40 @@ MainWindow::authenticate() authMethod = TorSettings::NullAuth; } + QString errmsg; + if (authMethod == TorSettings::CookieAuth) { - if(!tryCookie(pi)) { - if(authMethods.contains("HASHEDPASSWORD") and !tryHashed()) { - goto cancel; - } else { - return true; - } + if(tryCookie(pi)) { + authenticated(); + return; } else { - return true; + if(authMethods.contains("HASHEDPASSWORD")) { + if(tryHashed()) { + authenticated(); + return; + } + } + _torControl->shouldContinue(&errmsg); + authenticationFailed(errmsg); + return; } } else if (authMethod == TorSettings::PasswordAuth) { - return tryHashed(); + if(tryHashed()) { + authenticated(); + return; + } + _torControl->shouldContinue(&errmsg); + authenticationFailed(errmsg); + return; } /* No authentication. Send an empty password. */ vNotice("Authenticating using 'null' authentication."); - return _torControl->authenticate(QString("")); - -cancel: - vWarn("Cancelling control authentication attempt."); - if (_isVidaliaRunningTor) - stop(); - else - disconnect(); - return false; + if(_torControl->authenticate(QString(""))) + authenticated(); + else { + _torControl->shouldContinue(&errmsg); + authenticationFailed(errmsg); + } } bool @@ -1351,7 +1428,7 @@ MainWindow::tryCookie(const ProtocolInfo &pi) "'control_auth_cookie' yourself?")), VMessageBox::Browse|VMessageBox::Default, VMessageBox::Cancel|VMessageBox::Escape); - + if (ret == VMessageBox::Cancel) return false; QString cookieDir = QFileDialog::getOpenFileName(this, @@ -1425,14 +1502,14 @@ MainWindow::loadControlCookie(QString cookiePath) QString dataDir = settings.getDataDirectory(); if (!dataDir.isEmpty()) pathList << dataDir; - + #if defined(Q_WS_WIN) pathList << expand_filename("%APPDATA%\\Tor"); #else pathList << expand_filename("~/.tor"); #endif } - + /* Search for the cookie file */ foreach (QString path, pathList) { QString cookieFile = QFileInfo(path).isFile() ? @@ -1440,7 +1517,7 @@ MainWindow::loadControlCookie(QString cookiePath) vDebug("Checking for authentication cookie in '%1'").arg(cookieFile); if (!QFileInfo(cookieFile).exists()) continue; - + authCookie.setFileName(cookieFile); if (authCookie.open(QIODevice::ReadOnly)) { vInfo("Reading authentication cookie from '%1'").arg(cookieFile); @@ -1462,7 +1539,7 @@ MainWindow::updateTorStatus(TorStatus status) QString statusText, actionText; QString trayIconFile, statusIconFile; TorStatus prevStatus = _status; - + vNotice("Tor status changed from '%1' to '%2'.") .arg(toString(prevStatus)).arg(toString(status)); _status = status; @@ -1488,14 +1565,14 @@ MainWindow::updateTorStatus(TorStatus status) _actionRestartTor->setEnabled(false); _actionReloadConfig->setEnabled(false); if (_delayedShutdownStarted) { - statusText = tr("Your relay is shutting down.\n" + statusText = tr("Your relay is shutting down.\n" "Click 'Stop' again to stop your relay now."); } else { statusText = tr("Tor is shutting down"); } trayIconFile = IMG_TOR_STOPPING; statusIconFile = IMG_TOR_STOPPING_48; - + } else if (status == Started) { actionText = tr("Stop Tor"); _actionRestartTor->setEnabled(true); @@ -1503,7 +1580,7 @@ MainWindow::updateTorStatus(TorStatus status) _actionStartStopTor->setEnabled(true); _actionStartStopTor->setIcon(QIcon(IMG_STOP_TOR_16)); _actionStartStopTor->setText(actionText); - + /* XXX: This might need to be smarter if we ever start connecting other * slots to these triggered() and clicked() signals. */ QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0); @@ -1594,7 +1671,7 @@ MainWindow::sighup() if (!rc) { int response = VMessageBox::warning(this, tr("Error reloading configuration"), - p(tr("Vidalia was unable to reload Tor's configuration.")) + p(tr("Vidalia was unable to reload Tor's configuration.")) + p(errmsg), VMessageBox::Ok); } @@ -1635,7 +1712,7 @@ MainWindow::disconnected() * connection as "Tor is stopped". */ updateTorStatus(Stopped); } - + /*XXX We should warn here if we get disconnected when we didn't intend to */ _actionNewIdentity->setEnabled(false); _isVidaliaRunningTor = false; @@ -1659,7 +1736,7 @@ MainWindow::newIdentity() QString errmsg; /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM - * is successful, we will show the result as a balloon. Otherwise, we'll + * is successful, we will show the result as a balloon. Otherwise, we'll * just use a message box. */ if (_torControl->signal(TorSignal::NewNym, &errmsg)) { /* NEWNYM signal was successful */ @@ -1670,7 +1747,7 @@ MainWindow::newIdentity() /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */ _actionNewIdentity->setEnabled(false); - QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, + QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, this, SLOT(enableNewIdentity())); if (QSystemTrayIcon::supportsMessages()) @@ -1679,7 +1756,7 @@ MainWindow::newIdentity() VMessageBox::information(this, title, message, VMessageBox::Ok); } else { /* NEWNYM signal failed */ - VMessageBox::warning(this, + VMessageBox::warning(this, tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok); } } @@ -1788,8 +1865,8 @@ MainWindow::addTab(VidaliaTab *tab) return; /** Exception for tabs that need to be always created */ - if (tab != _messageLog && - tab != &_statusTab && + if (tab != _messageLog && + tab != &_statusTab && tab != &_netViewer && tab != _graph) tab->deleteLater(); @@ -1832,8 +1909,8 @@ MainWindow::delTab(int index) VidaliaTab *tab = qobject_cast<VidaliaTab*>(ui.tabWidget->widget(index)); // if it isn't one of the tabs that's supposed to be open at every moment - if (tab != _messageLog && - tab != &_statusTab && + if (tab != _messageLog && + tab != &_statusTab && tab != &_netViewer && tab != _graph) { QObject::disconnect(ui.tabWidget->widget(index), 0, 0, 0); @@ -1850,13 +1927,13 @@ MainWindow::showStatusTab() addTab(&_statusTab); } -void +void MainWindow::showMessageLogTab() { addTab(_messageLog); } -void +void MainWindow::showBandwidthTab() { addTab(_graph); @@ -1888,7 +1965,7 @@ MainWindow::showHelpDialog(const QString &topic) helpBrowser->showWindow(topic); } -void +void MainWindow::showNetViewerTab() { addTab(&_netViewer); @@ -2051,7 +2128,7 @@ MainWindow::installUpdatesFailed(const QString &errmsg) VMessageBox::warning(this, tr("Installation Failed"), p(tr("Vidalia was unable to install your software updates.")) - + p(tr("The following error occurred:")) + + p(tr("The following error occurred:")) + p(errmsg), VMessageBox::Ok); diff --git a/src/vidalia/MainWindow.h b/src/vidalia/MainWindow.h index 7cd86d7..6e60bb7 100644 --- a/src/vidalia/MainWindow.h +++ b/src/vidalia/MainWindow.h @@ -3,8 +3,8 @@ ** LICENSE file, found in the top level directory of this distribution. If you ** did not receive the LICENSE file with this file, you may obtain it from the ** Vidalia source package distributed by the Vidalia Project at -** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, -** including this file, may be copied, modified, propagated, or distributed +** http://www.torproject.org/projects/vidalia.html. No part of Vidalia, +** including this file, may be copied, modified, propagated, or distributed ** except according to the terms described in the LICENSE file. */ @@ -73,13 +73,13 @@ private slots: /** Called when the Tor process fails to start. */ void startFailed(QString errmsg); /** Called when the Tor process has successfully started. */ - void started(); + void connectToTor(); /** Called when the user selects "Stop" form the menu. */ bool stop(); /** Called when the Tor process has exited, either expectedly or not. */ void stopped(int errorCode, QProcess::ExitStatus exitStatus); /** Called when the control socket has connected to Tor. */ - void connected(); + //void connected(); /** Called when the control connection fails. */ void connectFailed(QString errmsg); /** Called when Vidalia wants to disconnect from a Tor it did not start. */ @@ -145,7 +145,7 @@ private slots: /** Displays the debug output dialog for plugins */ void showDebugDialog(); - + /** Adds a new tab to the MainWindow */ void addTab(VidaliaTab *tab); /** Deletes the tab at index if it exists and it isn't the Status tab */ @@ -224,7 +224,7 @@ private: /** Converts a TorStatus enum value to a string for debug logging purposes. */ QString toString(TorStatus status); /** Authenticates Vidalia to Tor's control port. */ - bool authenticate(); + void authenticate(); /** Searches for and attempts to load the control authentication cookie. * This assumes the cookie is named 'control_auth_cookie'. If * <b>cookiePath</b> is empty, this method will search some default locations @@ -305,10 +305,12 @@ private: PluginEngine *_engine; QStringList _tabMap; /**< Map to handle opened tabs */ QStringList _detachedTabMap; /**< Map to handle detached tabs */ - + bool _startedWithPrevious; /**< True if Vidalia tried to start Tor with the previous ports */ QString _previousControlPort; /**< Holds the previous controlport used */ QString _previousSocksPort; /**< Holds the previous socksport used */ + + bool _pressedStop; /**< True if the user has pressed the Stop Tor button */ }; #endif
participants (1)
-
chiiph@torproject.org