mainwindow.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 you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  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 the
00008 **  terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file mainwindow.cpp
00013 ** \version $Id: mainwindow.cpp 3900 2009-06-26 23:20:39Z edmanm $
00014 ** \brief Main (hidden) window. Creates tray menu and child windows
00015 **
00016 ** Implements the main window. The main window is a hidden window that serves
00017 ** as the parent of the tray icon and popup menu, as well as other application
00018 ** dialogs.
00019 */
00020 
00021 #include <QtGui>
00022 #include <QTimer>
00023 #include <vidalia.h>
00024 #include <file.h>
00025 #include <html.h>
00026 #include <stringutil.h>
00027 #include <net.h>
00028 #include <clientstatusevent.h>
00029 #include <dangerousversionevent.h>
00030 #include <vmessagebox.h>
00031 #include <procutil.h>
00032 
00033 #include "procutil.h"
00034 
00035 #include "mainwindow.h"
00036 #include "controlpasswordinputdialog.h"
00037 
00038 #define IMG_BWGRAPH        ":/images/16x16/utilities-system-monitor.png"
00039 #define IMG_CONTROL_PANEL  ":/images/16x16/system-run.png"
00040 #define IMG_MESSAGELOG     ":/images/16x16/format-justify-fill.png"
00041 #define IMG_CONFIG         ":/images/16x16/preferences-system.png"
00042 #define IMG_IDENTITY       ":/images/16x16/view-media-artist.png"
00043 #define IMG_HELP           ":/images/16x16/system-help.png"
00044 #define IMG_ABOUT          ":/images/16x16/help-about.png"
00045 #define IMG_EXIT           ":/images/16x16/application-exit.png"
00046 #define IMG_NETWORK        ":/images/16x16/applications-internet.png"
00047 
00048 #define IMG_START_TOR_16     ":/images/16x16/media-playback-start.png"
00049 #define IMG_STOP_TOR_16      ":/images/16x16/media-playback-stop.png"
00050 #define IMG_START_TOR_48     ":/images/48x48/media-playback-start.png"
00051 #define IMG_STOP_TOR_48      ":/images/48x48/media-playback-stop.png"
00052 #define IMG_TOR_STOPPED_48   ":/images/48x48/tor-off.png"
00053 #define IMG_TOR_RUNNING_48   ":/images/48x48/tor-on.png"
00054 #define IMG_TOR_STARTING_48  ":/images/48x48/tor-starting.png"
00055 #define IMG_TOR_STOPPING_48  ":/images/48x48/tor-stopping.png"
00056 
00057 /* Decide which of our four sets of tray icons to use. */
00058 #if defined(Q_WS_WIN)
00059 /* QSystemTrayIcon on Windows wants 16x16 .png files */
00060 #define IMG_TOR_STOPPED  ":/images/16x16/tor-off.png"
00061 #define IMG_TOR_RUNNING  ":/images/16x16/tor-on.png"
00062 #define IMG_TOR_STARTING ":/images/16x16/tor-starting.png"
00063 #define IMG_TOR_STOPPING ":/images/16x16/tor-stopping.png"
00064 #elif defined(Q_WS_MAC)
00065 /* On Mac, we always go straight to Carbon to load our dock images 
00066  * from .icns files */
00067 #define IMG_TOR_STOPPED    "tor-off"
00068 #define IMG_TOR_RUNNING    "tor-on"
00069 #define IMG_TOR_STARTING   "tor-starting"
00070 #define IMG_TOR_STOPPING   "tor-stopping"
00071 #else
00072 /* On X11, we just use always the 22x22 .png files */
00073 #define IMG_TOR_STOPPED    ":/images/22x22/tor-off.png"
00074 #define IMG_TOR_RUNNING    ":/images/22x22/tor-on.png"
00075 #define IMG_TOR_STARTING   ":/images/22x22/tor-starting.png"
00076 #define IMG_TOR_STOPPING   ":/images/22x22/tor-stopping.png"
00077 #endif
00078 
00079 /** Only allow 'New Identity' to be clicked once every 10 seconds. */
00080 #define MIN_NEWIDENTITY_INTERVAL   (10*1000)
00081 
00082 /* Startup progress milestones */
00083 #define STARTUP_PROGRESS_STARTING          0
00084 #define STARTUP_PROGRESS_CONNECTING       10
00085 #define STARTUP_PROGRESS_AUTHENTICATING   20
00086 #define STARTUP_PROGRESS_BOOTSTRAPPING    30
00087 #define STARTUP_PROGRESS_CIRCUITBUILD     75
00088 #define STARTUP_PROGRESS_MAXIMUM          (STARTUP_PROGRESS_BOOTSTRAPPING+100)
00089 
00090 
00091 /** Default constructor. It installs an icon in the system tray area and
00092  * creates the popup menu associated with that icon. */
00093 MainWindow::MainWindow()
00094 : VidaliaWindow("MainWindow")
00095 {
00096   VidaliaSettings settings;
00097 
00098   ui.setupUi(this);
00099 
00100   /* Pressing 'Esc' or 'Ctrl+W' will close the window */
00101   Vidalia::createShortcut("Ctrl+W", this, ui.btnHide, SLOT(click()));
00102   Vidalia::createShortcut("Esc", this, ui.btnHide, SLOT(click()));
00103 
00104   /* Create all the dialogs of which we only want one instance */
00105   _messageLog     = new MessageLog();
00106   _bandwidthGraph = new BandwidthGraph();
00107   _netViewer      = new NetViewer();
00108   _configDialog   = new ConfigDialog();
00109   connect(_messageLog, SIGNAL(helpRequested(QString)),
00110           this, SLOT(showHelpDialog(QString)));
00111   connect(_netViewer, SIGNAL(helpRequested(QString)),
00112           this, SLOT(showHelpDialog(QString)));
00113   connect(_configDialog, SIGNAL(helpRequested(QString)),
00114           this, SLOT(showHelpDialog(QString)));
00115 
00116   /* Create the actions that will go in the tray menu */
00117   createActions();
00118   /* Creates a tray icon with a context menu and adds it to the system's
00119    * notification area. */
00120   createTrayIcon();
00121   /* Start with Tor initially stopped */
00122   _status = Unset;
00123   updateTorStatus(Stopped);
00124   
00125   /* Create a new TorControl object, used to communicate with Tor */
00126   _torControl = Vidalia::torControl(); 
00127   connect(_torControl, SIGNAL(started()), this, SLOT(started()));
00128   connect(_torControl, SIGNAL(startFailed(QString)),
00129                  this,   SLOT(startFailed(QString)));
00130   connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)),
00131                  this,   SLOT(stopped(int, QProcess::ExitStatus)));
00132   connect(_torControl, SIGNAL(connected()), this, SLOT(connected()));
00133   connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected()));
00134   connect(_torControl, SIGNAL(connectFailed(QString)), 
00135                  this,   SLOT(connectFailed(QString)));
00136   connect(_torControl, SIGNAL(authenticated()), this, SLOT(authenticated()));
00137   connect(_torControl, SIGNAL(authenticationFailed(QString)),
00138                  this,   SLOT(authenticationFailed(QString)));
00139   _torControl->setEvent(TorEvents::ClientStatus,  this, true);
00140   _torControl->setEvent(TorEvents::GeneralStatus, this, true);
00141 
00142   /* Create a new HelperProcess object, used to start the web browser */
00143   _browserProcess = new HelperProcess(this);
00144   connect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
00145            this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
00146   connect(_browserProcess, SIGNAL(startFailed(QString)),
00147            this, SLOT(onBrowserFailed(QString)));
00148 
00149   /* Create a new HelperProcess object, used to start the IM client */
00150   _imProcess = new HelperProcess(this);
00151   connect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
00152            this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
00153   connect(_imProcess, SIGNAL(startFailed(QString)),
00154            this, SLOT(onIMFailed(QString)));
00155 
00156   /* Create a new HelperProcess object, used to start the proxy server */
00157   _proxyProcess = new HelperProcess(this);
00158   connect(_proxyProcess, SIGNAL(startFailed(QString)),
00159            this, SLOT(onProxyFailed(QString)));
00160 
00161   /* Catch signals when the application is running or shutting down */
00162   connect(vApp, SIGNAL(running()), this, SLOT(running()));
00163   connect(vApp, SIGNAL(shutdown()), this, SLOT(shutdown()));
00164 
00165 #if defined(USE_MINIUPNPC)
00166   /* Catch UPnP-related signals */
00167   connect(UPNPControl::instance(), SIGNAL(error(UPNPControl::UPNPError)),
00168          this, SLOT(upnpError(UPNPControl::UPNPError)));
00169 #endif
00170 
00171   ui.chkShowOnStartup->setChecked(settings.showMainWindowAtStart());
00172   if (ui.chkShowOnStartup->isChecked())
00173     show(); 
00174   /* Optimistically hope that the tray icon gets added. */
00175   _trayIcon.show();
00176 }
00177 
00178 /** Destructor. */
00179 MainWindow::~MainWindow()
00180 {
00181   _trayIcon.hide();
00182   delete _messageLog;
00183   delete _bandwidthGraph;
00184   delete _netViewer;
00185   delete _configDialog;
00186 }
00187 
00188 void
00189 MainWindow::setVisible(bool visible)
00190 {
00191   if (visible) {
00192     /* In Gnome, will hide buttons if Vidalia is run on startup. */
00193     if (!TrayIcon::isTrayIconSupported()) {
00194       /* Don't let people hide the main window, since that's all they have. */
00195       ui.chkShowOnStartup->hide();
00196       ui.btnHide->hide();
00197       /* Causes window to not appear in Enlightenment. */
00198       //setMinimumHeight(height()-ui.btnHide->height());
00199       //setMaximumHeight(height()-ui.btnHide->height());
00200     }
00201   }
00202   VidaliaWindow::setVisible(visible);
00203 }
00204 
00205 /** Catches and processes Tor client and general status events. */
00206 void
00207 MainWindow::customEvent(QEvent *event)
00208 {
00209   if (event->type() == CustomEventType::ClientStatusEvent) {
00210     ClientStatusEvent *cse = dynamic_cast<ClientStatusEvent *>(event);
00211     if (!cse)
00212       return;
00213 
00214     if (cse->status() == ClientStatusEvent::CircuitEstablished) {
00215       circuitEstablished();
00216       cse->accept();
00217     } else if (cse->status() == ClientStatusEvent::Bootstrap) {
00218       BootstrapStatusEvent *bse = dynamic_cast<BootstrapStatusEvent *>(cse);
00219       if (bse)
00220         bootstrapStatusChanged(bse->status());
00221       cse->accept();
00222     }
00223   } else if (event->type() == CustomEventType::GeneralStatusEvent) {
00224     GeneralStatusEvent *gse = dynamic_cast<GeneralStatusEvent *>(event);
00225     if (!gse)
00226       return;
00227 
00228     if (gse->status() == GeneralStatusEvent::DangerousTorVersion) {
00229       DangerousVersionEvent *dve = dynamic_cast<DangerousVersionEvent *>(gse);
00230       if (dve && (dve->reason() == DangerousVersionEvent::ObsoleteVersion
00231            || dve->reason() == DangerousVersionEvent::UnrecommendedVersion)) {
00232         dangerousTorVersion();
00233       }
00234       gse->accept();
00235     }
00236   }
00237 }
00238 
00239 /** Called when the application has started and the main event loop is
00240  * running. */
00241 void
00242 MainWindow::running()
00243 {
00244   VidaliaSettings settings;
00245  
00246   /* Initialize _useSavedPassword to true. If Tor is already running when
00247    * Vidalia starts, then there is no point in generating a random password.
00248    * If Tor is not already running, then this will be set according to the
00249    * current configuration in the start() method.
00250    */
00251   _useSavedPassword = true;
00252  
00253   if (settings.runTorAtStart()) {
00254     /* If we're supposed to start Tor when Vidalia starts, then do it now */
00255     start();
00256   }
00257 
00258   /* Start the proxy server, if configured */
00259   if (settings.runProxyAtStart())
00260     startProxy();
00261 }
00262 
00263 /** Terminate the Tor process if it is being run under Vidalia, disconnect all
00264  * TorControl signals, and exit Vidalia. */
00265 void
00266 MainWindow::shutdown()
00267 {
00268   if (_torControl->isVidaliaRunningTor()) {
00269     /* Kill our Tor process now */ 
00270     _torControl->stop();
00271   }
00272 
00273   /* Disable port forwarding */
00274   ServerSettings settings(_torControl);
00275   settings.cleanupPortForwarding();
00276 
00277   if (_proxyProcess->state() != QProcess::NotRunning) {
00278     /* Close the proxy server (Polipo ignores the WM_CLOSE event sent by
00279      * terminate() so we have to kill() it) */
00280     _proxyProcess->kill();
00281   }
00282 
00283   /* Kill the browser and IM client if using the new launcher */
00284   VidaliaSettings vidalia_settings;
00285 
00286   if (! vidalia_settings.getBrowserDirectory().isEmpty()) {
00287     /* Disconnect the finished signals so that we won't try to exit Vidalia again */
00288     QObject::disconnect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
00289     QObject::disconnect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
00290 
00291     /* Use QProcess terminate function */
00292     if (_browserProcess->state() == QProcess::Running)
00293       _browserProcess->terminate();
00294 
00295 #if defined(Q_OS_WIN)
00296     /* Kill any processes which might have been forked off */
00297     win32_end_process_by_filename(vidalia_settings.getBrowserExecutable());
00298 #endif
00299 
00300     if (_imProcess->state() == QProcess::Running)
00301       _imProcess->terminate();    
00302   }
00303 
00304   /* Disconnect all of the TorControl object's signals */
00305   QObject::disconnect(_torControl, 0, 0, 0);
00306 
00307   /* And then quit for real */
00308   QCoreApplication::quit();
00309 }
00310 
00311 /** Called when the application is closing, by selecting "Exit" from the tray
00312  * menu. If we're running a Tor server, then ask if we want to kill Tor now,
00313  * or do a delayed shutdown. */
00314 void
00315 MainWindow::close()
00316 {
00317   if (_torControl->isVidaliaRunningTor()) {
00318     /* If we're running a server currently, ask if we want to do a delayed
00319      * shutdown. If we do, then close Vidalia only when Tor stops. Otherwise,
00320      * kill Tor and bail now. */
00321     ServerSettings settings(_torControl);
00322     if (_torControl->isConnected() && settings.isServerEnabled()) {
00323       connect(_torControl, SIGNAL(stopped()), this, SLOT(shutdown()));
00324       if (!stop())
00325         QObject::disconnect(_torControl, SIGNAL(stopped()), this, SLOT(shutdown()));
00326       return;
00327     }
00328   }
00329   /* Shut down Tor (if necessary) and exit Vidalia */
00330   shutdown();
00331 }
00332 
00333 /** Create and bind actions to events. Setup for initial
00334  * tray menu configuration. */
00335 void 
00336 MainWindow::createActions()
00337 {
00338   _startStopAct = new QAction(tr("Start Tor"), this);
00339   connect(_startStopAct, SIGNAL(triggered()), this, SLOT(start()));
00340 
00341   _exitAct = new QAction(tr("Exit"), this);
00342   connect(_exitAct, SIGNAL(triggered()), this, SLOT(close()));
00343 
00344   _bandwidthAct = new QAction(tr("Bandwidth Graph"), this);
00345   connect(_bandwidthAct, SIGNAL(triggered()), 
00346           _bandwidthGraph, SLOT(showWindow()));
00347   connect(ui.lblBandwidthGraph, SIGNAL(clicked()),
00348           _bandwidthGraph, SLOT(showWindow()));
00349 
00350   _messageAct = new QAction(tr("Message Log"), this);
00351   connect(_messageAct, SIGNAL(triggered()),
00352           _messageLog, SLOT(showWindow()));
00353   connect(ui.lblMessageLog, SIGNAL(clicked()),
00354           _messageLog, SLOT(showWindow()));
00355 
00356   _networkAct = new QAction(tr("Network Map"), this);
00357   connect(_networkAct, SIGNAL(triggered()), 
00358           _netViewer, SLOT(showWindow()));
00359   connect(ui.lblViewNetwork, SIGNAL(clicked()),
00360           _netViewer, SLOT(showWindow()));
00361 
00362   _controlPanelAct = new QAction(tr("Control Panel"), this);
00363   connect(_controlPanelAct, SIGNAL(triggered()), this, SLOT(show()));
00364 
00365   _configAct = new QAction(tr("Settings"), this);
00366   connect(_configAct, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
00367   
00368   _aboutAct = new QAction(tr("About"), this);
00369   connect(_aboutAct, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
00370 
00371   _helpAct = new QAction(tr("Help"), this);
00372   connect(_helpAct, SIGNAL(triggered()), this, SLOT(showHelpDialog()));
00373   connect(ui.lblHelpBrowser, SIGNAL(clicked()), this, SLOT(showHelpDialog()));
00374 
00375   _newIdentityAct = new QAction(tr("New Identity"), this);
00376   _newIdentityAct->setEnabled(false);
00377   connect(_newIdentityAct, SIGNAL(triggered()), this, SLOT(newIdentity()));
00378 
00379 #if !defined(Q_WS_MAC)
00380   /* Don't give the menu items icons on OS X, since they end up in the
00381    * application menu bar. Menu bar items on OS X typically do not have
00382    * icons. */
00383   _startStopAct->setIcon(QIcon(IMG_START_TOR_16));
00384   _exitAct->setIcon(QIcon(IMG_EXIT));
00385   _bandwidthAct->setIcon(QIcon(IMG_BWGRAPH));
00386   _messageAct->setIcon(QIcon(IMG_MESSAGELOG));
00387   _networkAct->setIcon(QIcon(IMG_NETWORK));
00388   _controlPanelAct->setIcon(QIcon(IMG_CONTROL_PANEL));
00389   _configAct->setIcon(QIcon(IMG_CONFIG));
00390   _aboutAct->setIcon(QIcon(IMG_ABOUT));
00391   _helpAct->setIcon(QIcon(IMG_HELP));
00392   _newIdentityAct->setIcon(QIcon(IMG_IDENTITY));
00393 #endif
00394 }
00395 
00396 /** Creates a tray icon with a context menu and adds it to the system
00397  * notification area. On Mac, we also set up an application menubar. */
00398 void
00399 MainWindow::createTrayIcon()
00400 {
00401   /* Create the default menu bar (Mac) */
00402   createMenuBar();
00403   /* Create a tray menu and add it to the tray icon */
00404   _trayIcon.setContextMenu(createTrayMenu());
00405   connect(&_trayIcon, SIGNAL(doubleClicked()), this, SLOT(show()));
00406 }
00407 
00408 /** Creates a QMenu object that contains QActions which compose the system 
00409  * tray menu. */
00410 QMenu* 
00411 MainWindow::createTrayMenu()
00412 {
00413   QMenu *menu = new QMenu(this);
00414   menu->addAction(_startStopAct);
00415   menu->addSeparator();
00416   menu->addAction(_bandwidthAct);
00417   menu->addAction(_messageAct);
00418   menu->addAction(_networkAct);
00419   menu->addAction(_newIdentityAct);
00420   menu->addSeparator();
00421   menu->addAction(_controlPanelAct);
00422   
00423 #if !defined(Q_WS_MAC)
00424   /* These aren't added to the dock menu on Mac, since they are in the
00425    * standard Mac locations in the menu bar. */
00426   menu->addAction(_configAct);
00427   menu->addAction(_helpAct);
00428   menu->addAction(_aboutAct);
00429   menu->addSeparator();
00430   menu->addAction(_exitAct);
00431 #endif
00432   return menu;
00433 }
00434 
00435 /** Creates a new menubar with no parent, so Qt will use this as the "default
00436  * menubar" on Mac. This adds on to the existing actions from the createMens()
00437  * method. */
00438 void
00439 MainWindow::createMenuBar()
00440 {
00441 #if defined(Q_WS_MAC)
00442   /* Mac users sure like their shortcuts. Actions NOT mentioned below
00443    * don't explicitly need shortcuts, since they are merged to the default
00444    * menubar and get the default shortcuts anyway. */
00445   _startStopAct->setShortcut(tr("Ctrl+T"));
00446   _bandwidthAct->setShortcut(tr("Ctrl+B"));
00447   _messageAct->setShortcut(tr("Ctrl+L"));
00448   _networkAct->setShortcut(tr("Ctrl+N"));
00449   _helpAct->setShortcut(tr("Ctrl+?"));
00450   _newIdentityAct->setShortcut(tr("Ctrl+I"));
00451   _controlPanelAct->setShortcut(tr("Ctrl+P"));
00452 
00453   /* Force Qt to put merge the Exit, Configure, and About menubar options into
00454    * the default menu, even if Vidalia is currently not speaking English. */
00455   _exitAct->setText("exit");
00456   _configAct->setText("config");
00457   _aboutAct->setText("about");
00458   
00459   /* The File, Help, and Configure menus will get merged into the application
00460    * menu by Qt. */
00461   QMenuBar *menuBar = new QMenuBar(0);
00462   QMenu *fileMenu = menuBar->addMenu(tr("File"));
00463   fileMenu->addAction(_exitAct);
00464   
00465   QMenu *torMenu = menuBar->addMenu(tr("Tor"));
00466   torMenu->addAction(_startStopAct);
00467   torMenu->addSeparator();
00468   torMenu->addAction(_newIdentityAct);
00469 
00470   QMenu *viewMenu = menuBar->addMenu(tr("View"));
00471   viewMenu->addAction(_controlPanelAct);
00472   viewMenu->addSeparator();
00473   viewMenu->addAction(_bandwidthAct);
00474   viewMenu->addAction(_messageAct);
00475   viewMenu->addAction(_networkAct);
00476   viewMenu->addAction(_configAct);
00477   
00478   QMenu *helpMenu = menuBar->addMenu(tr("Help"));
00479   _helpAct->setText(tr("Vidalia Help"));
00480   helpMenu->addAction(_helpAct);
00481   helpMenu->addAction(_aboutAct);
00482 #endif
00483 }
00484 
00485 /** Start a web browser when given the directory containing the executable and profile */
00486 void
00487 MainWindow::launchBrowserFromDirectory()
00488 {
00489   VidaliaSettings settings;
00490 
00491   QString browserDirectory = settings.getBrowserDirectory();
00492   QString browserDirectoryFilename = settings.getBrowserExecutable();
00493 
00494   /* Set TZ=UTC (to stop leaking timezone information) and
00495    * MOZ_NO_REMOTE=1 (to allow multiple instances of Firefox */
00496   QStringList env = QProcess::systemEnvironment();
00497   env << "TZ=UTC";
00498   env << "MOZ_NO_REMOTE=1";
00499   _browserProcess->setEnvironment(env);
00500 
00501   /* The browser is in <browserDirectory>/App/Firefox/<browserDirectoryFilename> */
00502   QString browserExecutable =
00503     QDir::toNativeSeparators(browserDirectory + "/App/Firefox/" + browserDirectoryFilename);
00504   /* The profile is in <browserDirectory>/Data/profile */
00505   QString profileDir =
00506     QDir::toNativeSeparators(browserDirectory + "/Data/profile");
00507 
00508   /* Copy the profile directory if it's not already there */
00509   QDir browserDirObj = QDir(browserDirectory);
00510 
00511   /* Copy the profile directory if it's not already there */
00512   if (!browserDirObj.exists("Data/profile")) {
00513     browserDirObj.mkdir("Data/profile");
00514     copy_dir(browserDirectory + "/App/DefaultData/profile", browserDirectory + "/Data/profile");
00515   }
00516 
00517   /* Copy the plugins directory if it's not already there */
00518   if (!browserDirObj.exists("Data/plugins")) {
00519     browserDirObj.mkdir("Data/plugins");
00520     copy_dir(browserDirectory + "/App/DefaultData/plugins", browserDirectory + "/Data/plugins");
00521   }
00522 
00523   /* Build the command line arguments */
00524   QStringList commandLine;
00525   // Is this better or worse than MOZ_NO_REMOTE?
00526   //commandLine << "-no-remote";
00527   commandLine << "-profile";
00528   commandLine << profileDir;
00529 
00530   /* Launch the browser */
00531   _browserProcess->start(browserExecutable, commandLine);
00532 }
00533 
00534 /** Starts the web browser and IM client, if appropriately configured */
00535 void
00536 MainWindow::startSubprocesses()
00537 {
00538   VidaliaSettings settings;
00539   QString subprocess;
00540 
00541   /* Launch the web browser */
00542   if (!(subprocess = settings.getBrowserDirectory()).isEmpty()) {
00543     /* The user has set BrowserDirectory; use this */
00544     launchBrowserFromDirectory();
00545   } else if (!(subprocess = settings.getBrowserExecutable()).isEmpty()) {
00546     /* BrowserDirectory is not set, but BrowserExecutable is; use this */
00547     _browserProcess->setEnvironment(QProcess::systemEnvironment() << "TZ=UTC");
00548     _browserProcess->start(subprocess, QStringList());
00549   }
00550 
00551   /* Launch the IM client */
00552   subprocess = settings.getIMExecutable();
00553 
00554   if (!subprocess.isEmpty())
00555     _imProcess->start(subprocess, QStringList());
00556   
00557 }
00558 
00559 /** Called when browser or IM client have exited */
00560 void
00561 MainWindow::onSubprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
00562 {
00563   Q_UNUSED(exitCode)
00564   Q_UNUSED(exitStatus)
00565 
00566   /* Get path to browser and IM client */
00567   VidaliaSettings settings;
00568   QString browserExecutable = settings.getBrowserExecutable();
00569   QString browserDirectory = settings.getBrowserDirectory();
00570   QString imExecutable = settings.getIMExecutable();
00571 
00572   /* A subprocess is finished if it successfully exited or was never asked to start */
00573   bool browserDone = (browserExecutable.isEmpty() && browserDirectory.isEmpty()) || _browserProcess->isDone();
00574   bool imDone = imExecutable.isEmpty() || _imProcess->isDone();
00575 
00576   /* Exit if both subprocesses are finished */
00577   if (browserDone && imDone) {
00578     if (browserDirectory.isEmpty()) {
00579       /* We are using the standard launcher, exit immediately */
00580       shutdown();
00581     } else {
00582       /* We are using the alternate launcher, wait until the browser has really died */
00583       QTimer *browserWatcher = new QTimer(this);
00584       connect(browserWatcher, SIGNAL(timeout()), this, SLOT(onCheckForBrowser()));
00585       browserWatcher->start(2000);
00586     }
00587   }
00588 }
00589 
00590 /** Called periodically to check if the browser is running. If it is not,
00591  * exit Vidalia cleanly */
00592 void
00593 MainWindow::onCheckForBrowser()
00594 {
00595 /* This only works on Windows for now */
00596 #if defined(Q_OS_WIN)
00597 
00598   VidaliaSettings settings;
00599   QString browserDirectoryFilename = settings.getBrowserExecutable();
00600 
00601   /* Get list of running processes */
00602   QHash<qint64, QString> procList = win32_process_list();
00603 
00604   /* On old versions of Windows win32_process_list() will return
00605      an empty list. In this case, just keep Vidalia open */
00606   if (procList.isEmpty()) {
00607     return;
00608   }
00609 
00610   /* Loop over all processes or until we find <browserDirectoryFilename> */
00611   QHashIterator<qint64, QString> i(procList);
00612   while (i.hasNext()) {
00613     i.next();
00614     if (i.value().toLower() == browserDirectoryFilename) {
00615       /* The browser is still running, so Vidalia should keep running too */
00616       return;
00617     }
00618   }
00619 
00620   /* The browser isn't running, exit Vidalia */
00621   shutdown();
00622 #endif  
00623 }
00624 
00625 /** Called when the web browser failed to start, for example, because the path
00626  * specified to the web browser executable didn't lead to an executable. */
00627 void
00628 MainWindow::onBrowserFailed(QString errmsg)
00629 {
00630   Q_UNUSED(errmsg);
00631  
00632   /* Display an error message and see if the user wants some help */
00633   VMessageBox::warning(this, tr("Error starting web browser"),
00634               tr("Vidalia was unable to start the configured web browser"),
00635               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00636 }
00637 
00638 /** Called when the IM client failed to start, for example, because the path
00639  * specified to the IM client executable didn't lead to an executable. */
00640 void
00641 MainWindow::onIMFailed(QString errmsg)
00642 {
00643   Q_UNUSED(errmsg);
00644  
00645   /* Display an error message and see if the user wants some help */
00646   VMessageBox::warning(this, tr("Error starting IM client"),
00647               tr("Vidalia was unable to start the configured IM client"),
00648               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00649 }
00650 
00651 /** Starts the proxy server, if appropriately configured */
00652 void
00653 MainWindow::startProxy()
00654 {
00655   VidaliaSettings settings;
00656   QString executable = settings.getProxyExecutable();
00657   _proxyProcess->start(executable, settings.getProxyExecutableArguments());
00658 }
00659 
00660 /** Called when the proxy server fails to start, for example, because
00661  * the path specified didn't lead to an executable. */
00662 void
00663 MainWindow::onProxyFailed(QString errmsg)
00664 {
00665   Q_UNUSED(errmsg);
00666  
00667   /* Display an error message and see if the user wants some help */
00668   VMessageBox::warning(this, tr("Error starting proxy server"),
00669               tr("Vidalia was unable to start the configured proxy server"),
00670               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00671 }
00672 
00673 /** Called when Tor's bootstrapping status changes. <b>bse</b> represents
00674  * Tor's current estimate of its bootstrapping progress. */
00675 void
00676 MainWindow::bootstrapStatusChanged(const BootstrapStatus &bs)
00677 {
00678   int percentComplete = STARTUP_PROGRESS_BOOTSTRAPPING + bs.percentComplete();
00679   bool warn = (bs.severity() == tc::SeverityWarn && 
00680                bs.recommendedAction() != BootstrapStatus::RecommendIgnore);
00681 
00682   QString description;
00683   switch (bs.status()) {
00684     case BootstrapStatus::ConnectingToDirMirror:
00685       description = tr("Connecting to a relay directory");
00686       break;
00687     case BootstrapStatus::HandshakingWithDirMirror:
00688     case BootstrapStatus::CreatingOneHopCircuit:
00689       description = tr("Establishing an encrypted directory connection");
00690       break;
00691     case BootstrapStatus::RequestingNetworkStatus:
00692       description = tr("Retrieving network status");
00693       break;
00694     case BootstrapStatus::LoadingNetworkStatus:
00695       description = tr("Loading network status");
00696       break;
00697     case BootstrapStatus::LoadingAuthorityCertificates:
00698       description = tr("Loading authority certificates");
00699       break;
00700     case BootstrapStatus::RequestingDescriptors:
00701       description = tr("Requesting relay information");
00702       break;
00703     case BootstrapStatus::LoadingDescriptors:
00704       description = tr("Loading relay information");
00705       break;
00706     case BootstrapStatus::ConnectingToEntryGuard:
00707       description = tr("Connecting to the Tor network");
00708       break;
00709     case BootstrapStatus::HandshakingWithEntryGuard:
00710     case BootstrapStatus::EstablishingCircuit:
00711       description = tr("Establishing a Tor circuit");
00712       break;
00713     case BootstrapStatus::BootstrappingDone:
00714       description = tr("Connected to the Tor network!");
00715       warn = false; /* probably false anyway */
00716       break;
00717     default:
00718       description = tr("Unrecognized startup status");
00719   }
00720   if (warn) {
00721     QString reason;
00722     /* Is it really a good idea to translate these? */
00723     switch (bs.reason()) {
00724       case tc::MiscellaneousReason:
00725         reason = tr("miscellaneous");
00726         break;
00727       case tc::IdentityMismatch:
00728         reason = tr("identity mismatch");
00729         break;
00730       case tc::ConnectionDone:
00731         reason = tr("done");
00732         break;
00733       case tc::ConnectionRefused:
00734         reason = tr("connection refused");
00735         break;
00736       case tc::ConnectionTimeout:
00737         reason = tr("connection timeout");
00738         break;
00739       case tc::ConnectionIoError:
00740         reason = tr("read/write error");
00741         break;
00742       case tc::NoRouteToHost:
00743         reason = tr("no route to host");
00744         break;
00745       case tc::ResourceLimitReached:
00746         reason = tr("insufficient resources");
00747         break;
00748       default:
00749         reason = tr("unknown");
00750     }
00751     description += tr(" failed (%1)").arg(reason);
00752   }
00753   setStartupProgress(percentComplete, description);
00754 }
00755 
00756 /** Updates the UI to reflect Tor's current <b>status</b>. Returns the
00757  * previously set TorStatus value.*/
00758 MainWindow::TorStatus
00759 MainWindow::updateTorStatus(TorStatus status)
00760 {
00761   QString statusText, actionText;
00762   QString trayIconFile, statusIconFile;
00763   TorStatus prevStatus = _status;
00764  
00765   vNotice("Tor status changed from '%1' to '%2'.")
00766     .arg(toString(prevStatus)).arg(toString(status));
00767   _status = status;
00768 
00769   if (status == Stopped) {
00770       statusText = tr("Tor is not running");
00771       actionText = tr("Start Tor");
00772       trayIconFile = IMG_TOR_STOPPED;
00773       statusIconFile = IMG_TOR_STOPPED_48;
00774       _startStopAct->setEnabled(true);
00775       _startStopAct->setText(actionText);
00776       _startStopAct->setIcon(QIcon(IMG_START_TOR_16));
00777       ui.lblStartStopTor->setEnabled(true);
00778       ui.lblStartStopTor->setText(actionText);
00779       ui.lblStartStopTor->setPixmap(QPixmap(IMG_START_TOR_48));
00780       ui.lblStartStopTor->setStatusTip(actionText);
00781 
00782       /* XXX: This might need to be smarter if we ever start connecting other
00783        * slots to these triggered() and clicked() signals. */
00784       QObject::disconnect(_startStopAct, SIGNAL(triggered()), this, 0);
00785       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00786       connect(_startStopAct, SIGNAL(triggered()), this, SLOT(start()));
00787       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(start()));
00788       setStartupProgressVisible(false);
00789   } else if (status == Stopping) {
00790       if (_delayedShutdownStarted) {
00791         statusText = tr("Your relay is shutting down.\n" 
00792                         "Click 'Stop' again to stop your relay now.");
00793       } else {
00794         statusText = tr("Tor is shutting down");
00795       }
00796       trayIconFile = IMG_TOR_STOPPING;
00797       statusIconFile = IMG_TOR_STOPPING_48;
00798       
00799       ui.lblStartStopTor->setStatusTip(tr("Stop Tor Now"));
00800   } else if (status == Started) {
00801       actionText = tr("Stop Tor");
00802       _startStopAct->setEnabled(true);
00803       _startStopAct->setText(actionText);
00804       _startStopAct->setIcon(QIcon(IMG_STOP_TOR_16));
00805       ui.lblStartStopTor->setEnabled(true);
00806       ui.lblStartStopTor->setText(actionText);
00807       ui.lblStartStopTor->setPixmap(QPixmap(IMG_STOP_TOR_48));
00808       ui.lblStartStopTor->setStatusTip(actionText);
00809             
00810       /* XXX: This might need to be smarter if we ever start connecting other
00811        * slots to these triggered() and clicked() signals. */
00812       QObject::disconnect(_startStopAct, SIGNAL(triggered()), this, 0);
00813       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00814       connect(_startStopAct, SIGNAL(triggered()), this, SLOT(stop()));
00815       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(stop()));
00816   } else if (status == Starting)  {
00817       statusText = tr("Starting the Tor software");
00818       trayIconFile = IMG_TOR_STARTING;
00819       statusIconFile = IMG_TOR_STARTING_48;
00820       _startStopAct->setEnabled(false);
00821       ui.lblStartStopTor->setText(tr("Starting Tor"));
00822       ui.lblStartStopTor->setEnabled(false);
00823       ui.lblStartStopTor->setStatusTip(statusText);
00824       setStartupProgressVisible(true);
00825       setStartupProgress(STARTUP_PROGRESS_STARTING, statusText);
00826   } else if (status == CircuitEstablished) {
00827       statusText = tr("Connected to the Tor network!");
00828       trayIconFile = IMG_TOR_RUNNING;
00829       statusIconFile = IMG_TOR_RUNNING_48;
00830       setStartupProgressVisible(false);
00831   }
00832 
00833   /* Update the tray icon */
00834   if (!trayIconFile.isEmpty()) {
00835     _trayIcon.setIcon(trayIconFile);
00836   }
00837   /* Update the status banner on the control panel */
00838   if (!statusIconFile.isEmpty())
00839     ui.lblTorStatusImg->setPixmap(QPixmap(statusIconFile));
00840   if (!statusText.isEmpty()) {
00841     _trayIcon.setToolTip(statusText);
00842     ui.lblTorStatus->setText(statusText);
00843   }
00844   return prevStatus;
00845 }
00846 
00847 /** Called when the "show on startup" checkbox is toggled. */
00848 void
00849 MainWindow::toggleShowOnStartup(bool checked)
00850 {
00851   VidaliaSettings settings;
00852   settings.setShowMainWindowAtStart(checked);
00853 }
00854 
00855 /** Sets the visibility of the startup status description and progress bar to
00856  * <b>visible</b>. */
00857 void
00858 MainWindow::setStartupProgressVisible(bool visible)
00859 {
00860   /* XXX: We force a repaint() to make sure the progress bar and onion status
00861    * icon don't overlap briefly. This is pretty hacktastic. */
00862   if (visible) {
00863     ui.lblTorStatus->setVisible(false);
00864     ui.lblTorStatusImg->setVisible(false);
00865     repaint(ui.grpStatus->rect());
00866     ui.lblStartupProgress->setVisible(true);
00867     ui.progressBar->setVisible(true);
00868   } else {
00869     ui.lblStartupProgress->setVisible(false);
00870     ui.progressBar->setVisible(false);
00871     repaint(ui.grpStatus->rect());
00872     ui.lblTorStatus->setVisible(true);
00873     ui.lblTorStatusImg->setVisible(true);
00874   }
00875 }
00876 
00877 /** Sets the progress bar completion value to <b>progressValue</b> and sets
00878  * the status text to <b>description</b>. */
00879 void
00880 MainWindow::setStartupProgress(int progressValue,
00881                                const QString &description)
00882 {
00883   ui.progressBar->setValue(progressValue);
00884   ui.lblStartupProgress->setText(description);
00885   _trayIcon.setToolTip(description);
00886 }
00887 
00888 /** Attempts to start Tor. If Tor fails to start, then startFailed() will be
00889  * called with an error message containing the reason. */
00890 void 
00891 MainWindow::start()
00892 {
00893   TorSettings settings;
00894   QStringList args;
00895 
00896   updateTorStatus(Starting);
00897 
00898   /* Check if Tor is already running separately */
00899   if (net_test_connect(settings.getControlAddress(),
00900                        settings.getControlPort())) {
00901     started();
00902     return;
00903   }
00904 
00905   /* Make sure the torrc we want to use really exists. */
00906   QString torrc = settings.getTorrc();
00907   if (!torrc.isEmpty()) {
00908     if (!QFileInfo(torrc).exists())
00909       touch_file(torrc, true);
00910     args << "-f" << torrc;
00911   }
00912 
00913   /* Specify Tor's data directory, if different from the default */
00914   QString dataDirectory = settings.getDataDirectory();
00915   if (!dataDirectory.isEmpty())
00916     args << "DataDirectory" << expand_filename(dataDirectory);
00917   
00918   /* Add the intended control port value */
00919   quint16 controlPort = settings.getControlPort();
00920   if (controlPort)
00921     args << "ControlPort" << QString::number(controlPort);
00922   
00923   /* Add the control port authentication arguments */
00924   switch (settings.getAuthenticationMethod()) {
00925     case TorSettings::PasswordAuth:
00926       if (settings.useRandomPassword()) {
00927         _controlPassword = TorSettings::randomPassword();
00928         _useSavedPassword = false;
00929       } else {
00930         _controlPassword = settings.getControlPassword();
00931         _useSavedPassword = true;
00932       }
00933       args << "HashedControlPassword"
00934            << TorSettings::hashPassword(_controlPassword)
00935            << "CookieAuthentication"  << "0";
00936       break;
00937     case TorSettings::CookieAuth:
00938       args << "CookieAuthentication"  << "1"
00939            << "HashedControlPassword" << "";
00940       break;
00941     default:
00942       args << "CookieAuthentication"  << "0"
00943            << "HashedControlPassword" << "";
00944   }
00945 
00946   /* This doesn't get set to false until Tor is actually up and running, so we
00947    * don't yell at users twice if their Tor doesn't even start, due to the fact
00948    * that QProcess::stopped() is emitted even if the process didn't even
00949    * start. */
00950   _isIntentionalExit = true;
00951   /* Kick off the Tor process */
00952   _torControl->start(settings.getExecutable(), args);
00953 }
00954 
00955 /** Called when the Tor process fails to start, for example, because the path
00956  * specified to the Tor executable didn't lead to an executable. */
00957 void
00958 MainWindow::startFailed(QString errmsg)
00959 {
00960   /* We don't display the error message for now, because the error message
00961    * that Qt gives us in this instance is almost always "Unknown Error". That
00962    * will make users sad. */
00963   Q_UNUSED(errmsg);
00964  
00965   updateTorStatus(Stopped);
00966 
00967   /* Display an error message and see if the user wants some help */
00968   int response = VMessageBox::warning(this, tr("Error Starting Tor"),
00969                    tr("Vidalia was unable to start Tor. Check your settings "
00970                         "to ensure the correct name and location of your Tor "
00971                         "executable is specified."),
00972                    VMessageBox::ShowSettings|VMessageBox::Default,
00973                    VMessageBox::Cancel|VMessageBox::Escape,
00974                    VMessageBox::Help);
00975 
00976   if (response == VMessageBox::ShowSettings) {
00977     /* Show the settings dialog so the user can make sure they're pointing to
00978      * the correct Tor. */
00979      showConfigDialog();
00980   } else if (response == VMessageBox::Help) {
00981     /* Show troubleshooting information about starting Tor */
00982     showHelpDialog("troubleshooting.start");
00983   }
00984 }
00985 
00986 /** Slot: Called when the Tor process is started. It will connect the control
00987  * socket and set the icons and tooltips accordingly. */
00988 void 
00989 MainWindow::started()
00990 {
00991   TorSettings settings;
00992 
00993   updateTorStatus(Started);
00994 
00995   /* Now that Tor is running, we want to know if it dies when we didn't want
00996    * it to. */
00997   _isIntentionalExit = false;
00998   /* We haven't started a delayed shutdown yet. */
00999   _delayedShutdownStarted = false;
01000   /* Remember whether we started Tor or not */
01001   _isVidaliaRunningTor = _torControl->isVidaliaRunningTor();
01002   /* Try to connect to Tor's control port */
01003   _torControl->connect(settings.getControlAddress(),
01004                        settings.getControlPort());
01005   setStartupProgress(STARTUP_PROGRESS_CONNECTING, tr("Connecting to Tor"));
01006 }
01007 
01008 /** Called when the connection to the control socket fails. The reason will be
01009  * given in the errmsg parameter. */
01010 void
01011 MainWindow::connectFailed(QString errmsg)
01012 {
01013   /* Ok, ok. It really isn't going to connect. I give up. */
01014   int response = VMessageBox::warning(this, 
01015                    tr("Connection Error"), p(errmsg),
01016                    VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01017                    VMessageBox::Retry, VMessageBox::Help);
01018 
01019 
01020   if (response == VMessageBox::Retry) {
01021     /* Let's give it another try. */
01022     TorSettings settings;
01023     _torControl->connect(settings.getControlAddress(),
01024                          settings.getControlPort());
01025   } else {
01026     /* Show the help browser (if requested) */
01027     if (response == VMessageBox::Help)
01028       showHelpDialog("troubleshooting.connect");
01029     /* Since Vidalia can't connect, we can't really do much, so stop Tor. */
01030     _torControl->stop();
01031   }
01032 }
01033 
01034 /** Disconnects the control socket and stops the Tor process. */
01035 bool
01036 MainWindow::stop()
01037 {
01038   ServerSettings server(_torControl);
01039   QString errmsg;
01040   TorStatus prevStatus;
01041   bool rc;
01042 
01043   /* If we're running a server, give users the option of terminating
01044    * gracefully so clients have time to find new servers. */
01045   if (server.isServerEnabled() && !_delayedShutdownStarted) {
01046     /* Ask the user if they want to shutdown nicely. */
01047     int response = VMessageBox::question(this, tr("Relaying is Enabled"),
01048                      tr("You are currently running a relay. "
01049                         "Terminating your relay will interrupt any "
01050                         "open connections from clients.\n\n"
01051                         "Would you like to shutdown gracefully and "
01052                         "give clients time to find a new relay?"),
01053                         VMessageBox::Yes|VMessageBox::Default, 
01054                         VMessageBox::No, 
01055                         VMessageBox::Cancel|VMessageBox::Escape);
01056     if (response == VMessageBox::Yes)
01057       _delayedShutdownStarted = true;
01058     else if (response == VMessageBox::Cancel)
01059       return false;
01060   }
01061   
01062   prevStatus = updateTorStatus(Stopping);  
01063   if (_delayedShutdownStarted) {
01064     /* Start a delayed shutdown */
01065     rc = _torControl->signal(TorSignal::Shutdown, &errmsg);
01066   } else {
01067     /* We want Tor to stop now, regardless of whether we're a server. */
01068     _isIntentionalExit = true;
01069     rc = _torControl->stop(&errmsg);
01070   }
01071   
01072   if (!rc) {
01073     /* We couldn't tell Tor to stop, for some reason. */
01074     int response = VMessageBox::warning(this, tr("Error Shutting Down"),
01075                      p(tr("Vidalia was unable to stop the Tor software.")) 
01076                        + p(errmsg),
01077                      VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01078                      VMessageBox::Help);
01079       
01080     if (response == VMessageBox::Help) {
01081       /* Show some troubleshooting help */
01082       showHelpDialog("troubleshooting.stop");
01083     }
01084     /* Tor is still running since stopping failed */
01085     _isIntentionalExit = false;
01086     _delayedShutdownStarted = false;
01087     updateTorStatus(prevStatus);
01088   }
01089   return rc;
01090 }
01091 
01092 /** Slot: Called when the Tor process has exited. It will adjust the tray
01093  * icons and tooltips accordingly. */
01094 void 
01095 MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus)
01096 {
01097   updateTorStatus(Stopped);
01098 
01099   /* If we didn't intentionally close Tor, then check to see if it crashed or
01100    * if it closed itself and returned an error code. */
01101   if (!_isIntentionalExit) {
01102     /* A quick overview of Tor's code tells me that if it catches a SIGTERM or
01103      * SIGINT, Tor will exit(0). We might need to change this warning message
01104      * if this turns out to not be the case. */
01105     if (exitStatus == QProcess::CrashExit || exitCode != 0) {
01106       int ret = VMessageBox::warning(this, tr("Unexpected Error"),
01107                   tr("Vidalia detected that the Tor software exited "
01108                      "unexpectedly.\n\n"
01109                      "Please check the message log for recent "
01110                      "warning or error messages."),
01111                   VMessageBox::Ok|VMessageBox::Escape, 
01112                   VMessageBox::ShowLog|VMessageBox::Default,
01113                   VMessageBox::Help);
01114       if (ret == VMessageBox::ShowLog)
01115         _messageLog->showWindow();  
01116       else if (ret == VMessageBox::Help)
01117         showHelpDialog("troubleshooting.torexited");
01118     }
01119   }
01120 }
01121 
01122 /** Called when the control socket has successfully connected to Tor. */
01123 void
01124 MainWindow::connected()
01125 {
01126   authenticate();
01127 }
01128 
01129 /** Called when Vidalia wants to disconnect from a Tor it did not start. */
01130 void
01131 MainWindow::disconnect()
01132 {
01133   _torControl->disconnect();
01134 }
01135 
01136 /** Called when the control socket has been disconnected. */
01137 void
01138 MainWindow::disconnected()
01139 {
01140   if (!_isVidaliaRunningTor) {
01141     /* If we didn't start our own Tor process, interpret losing the control
01142      * connection as "Tor is stopped". */
01143     updateTorStatus(Stopped);
01144   }
01145   
01146   /*XXX We should warn here if we get disconnected when we didn't intend to */
01147   _newIdentityAct->setEnabled(false);
01148   ui.lblNewIdentity->setEnabled(false);
01149   _isVidaliaRunningTor = false;
01150 }
01151 
01152 /** Attempts to authenticate to Tor's control port, depending on the
01153  * authentication method specified in TorSettings::getAuthenticationMethod().
01154  */
01155 bool
01156 MainWindow::authenticate()
01157 {
01158   TorSettings::AuthenticationMethod authMethod;
01159   TorSettings settings;
01160   ProtocolInfo pi;
01161   
01162   updateTorStatus(Authenticating);
01163   setStartupProgress(STARTUP_PROGRESS_AUTHENTICATING,
01164                      tr("Authenticating to Tor"));
01165 
01166   authMethod = settings.getAuthenticationMethod(); 
01167   pi = _torControl->protocolInfo();
01168   if (!pi.isEmpty()) {
01169     QStringList authMethods = pi.authMethods();
01170     if (authMethods.contains("COOKIE"))
01171       authMethod = TorSettings::CookieAuth;
01172     else if (authMethods.contains("HASHEDPASSWORD"))
01173       authMethod = TorSettings::PasswordAuth;
01174     else if (authMethods.contains("NULL"))
01175       authMethod = TorSettings::NullAuth;
01176   }
01177   
01178   if (authMethod == TorSettings::CookieAuth) {
01179     /* Try to load an auth cookie and send it to Tor */
01180     QByteArray cookie = loadControlCookie(pi.cookieAuthFile());
01181     while (cookie.isEmpty()) {
01182       /* Prompt the user to find their control_auth_cookie */
01183       int ret = VMessageBox::question(this,
01184                   tr("Cookie Authentication Required"),
01185                   p(tr("The Tor software requires Vidalia to send the "
01186                        "contents of an authentication cookie, but Vidalia "
01187                        "was unable to find one."))
01188                   + p(tr("Would you like to browse for the file "
01189                          "'control_auth_cookie' yourself?")),
01190                 VMessageBox::Browse|VMessageBox::Default,
01191                 VMessageBox::Cancel|VMessageBox::Escape);
01192       
01193       if (ret == VMessageBox::Cancel)
01194         goto cancel;
01195       QString cookieDir = QFileDialog::getOpenFileName(this,
01196                             tr("Data Directory"),
01197                             settings.getDataDirectory(),
01198                             tr("Control Cookie (control_auth_cookie)"));
01199       if (cookieDir.isEmpty())
01200         goto cancel;
01201       cookieDir = QFileInfo(cookieDir).absolutePath();
01202       cookie = loadControlCookie(cookieDir);
01203     }
01204     vNotice("Authenticating using 'cookie' authentication.");
01205     return _torControl->authenticate(cookie);
01206   } else if (authMethod == TorSettings::PasswordAuth) {
01207     /* Get the control password and send it to Tor */
01208     vNotice("Authenticating using 'hashed password' authentication.");
01209     if (_useSavedPassword) {
01210       TorSettings settings;
01211       _controlPassword = settings.getControlPassword();
01212     }
01213     return _torControl->authenticate(_controlPassword);
01214   }
01215   /* No authentication. Send an empty password. */
01216   vNotice("Authenticating using 'null' authentication.");
01217   return _torControl->authenticate(QString(""));
01218 
01219 cancel:
01220   vWarn("Cancelling control authentication attempt.");
01221   if (_isVidaliaRunningTor)
01222     stop();
01223   else
01224     disconnect();
01225   return false;
01226 }
01227 
01228 /** Called when Vidalia has successfully authenticated to Tor. */
01229 void
01230 MainWindow::authenticated()
01231 {
01232   ServerSettings serverSettings(_torControl);
01233   QString errmsg;
01234 
01235   updateTorStatus(Authenticated);
01236   
01237   /* If Tor doesn't have bootstrapping events, then update the current
01238    * status string and bump the progress bar along a bit. */
01239   if (_torControl->getTorVersion() < 0x020101) {
01240     setStartupProgress(STARTUP_PROGRESS_CIRCUITBUILD,
01241                        tr("Connecting to the Tor network"));
01242   }
01243   
01244   /* Let people click on their beloved "New Identity" button */
01245   _newIdentityAct->setEnabled(true);
01246   ui.lblNewIdentity->setEnabled(true);
01247 
01248   /* Register for any pertinent asynchronous events. */
01249   if (!_torControl->setEvents(&errmsg)) {
01250     VMessageBox::warning(this, tr("Error Registering for Events"),
01251       p(tr("Vidalia was unable to register for some events. "
01252            "Many of Vidalia's features may be unavailable."))
01253          + p(errmsg),
01254       VMessageBox::Ok);
01255   } else {
01256     /* Stop reading from Tor's stdout immediately, since we successfully
01257      * registered for Tor events, including any desired log events. */
01258     _torControl->closeTorStdout();
01259   }
01260 
01261   /* Configure UPnP port forwarding if needed */
01262   serverSettings.configurePortForwarding();
01263 
01264   /* Check if Tor has a circuit established */
01265   if (_torControl->circuitEstablished())
01266     circuitEstablished();
01267   /* Check the status of Tor's version */
01268   if (_torControl->getTorVersion() >= 0x020001)
01269     checkTorVersion();
01270   if (_torControl->getTorVersion() >= 0x020102) {
01271     BootstrapStatus status = _torControl->bootstrapStatus();
01272     if (status.isValid())
01273       bootstrapStatusChanged(status);
01274   }
01275 }
01276 
01277 /** Called when Vidalia fails to authenticate to Tor. The failure reason is
01278  * specified in <b>errmsg</b>. */
01279 void
01280 MainWindow::authenticationFailed(QString errmsg)
01281 {
01282   bool retry = false;
01283   
01284   vWarn("Authentication failed: %1").arg(errmsg);
01285 
01286   /* Parsing log messages is evil, but we're left with little option */
01287   if (errmsg.contains("Password did not match")) {
01288     ControlPasswordInputDialog dlg;
01289     connect(&dlg, SIGNAL(helpRequested(QString)),
01290             this, SLOT(showHelpDialog(QString)));
01291 
01292     qint64 torPid = 0;
01293 
01294 #if defined(Q_OS_WIN32)
01295     QHash<qint64, QString> procs = process_list();
01296     foreach (qint64 pid, procs.keys()) {
01297       if (! procs.value(pid).compare("tor.exe", Qt::CaseInsensitive)) {
01298         torPid = pid;
01299         break;
01300       }
01301     }
01302     dlg.setResetEnabled(torPid > 0);
01303 #else
01304     dlg.setResetEnabled(false);
01305 #endif
01306 
01307     int ret = dlg.exec();
01308     if (ret == QDialogButtonBox::Ok) {
01309       if (dlg.isSavePasswordChecked()) {
01310         TorSettings settings;
01311         settings.setAuthenticationMethod(TorSettings::PasswordAuth);
01312         settings.setUseRandomPassword(false);
01313         settings.setControlPassword(dlg.password());
01314         _useSavedPassword = true;
01315       } else {
01316         _controlPassword = dlg.password();
01317         _useSavedPassword = false;
01318       }
01319       retry = true;
01320     } else if (ret == QDialogButtonBox::Reset) {
01321       if (! process_kill(torPid)) {
01322         VMessageBox::warning(this,
01323           tr("Password Reset Failed"),
01324           p(tr("Vidalia tried to reset Tor's control password, but was not "
01325                "able to restart the Tor software. Please check your Task "
01326                "Manager to ensure there are no other Tor processes running.")),
01327                VMessageBox::Ok|VMessageBox::Default);
01328       } else {
01329         retry = true;
01330       }
01331     }
01332   } else {
01333     /* Something else went wrong */
01334     int ret = VMessageBox::warning(this, 
01335                 tr("Authentication Error"),
01336                 p(tr("Vidalia was unable to authenticate to the Tor software. "
01337                      "(%1)").arg(errmsg)) + 
01338                 p(tr("Please check your control port authentication "
01339                      "settings.")),
01340                 VMessageBox::ShowSettings|VMessageBox::Default,
01341                 VMessageBox::Cancel|VMessageBox::Escape);
01342     
01343     if (ret == VMessageBox::ShowSettings)
01344       showConfigDialog(ConfigDialog::Advanced);
01345   }
01346   
01347   if (_torControl->isRunning())
01348     if (_isVidaliaRunningTor) 
01349       stop();
01350     else
01351       disconnect();
01352   if (retry)
01353     start();
01354 }
01355 
01356 /** Searches for and attempts to load the control authentication cookie. This
01357  * assumes the cookie is named 'control_auth_cookie'. If <b>cookiePath</b> is
01358  * empty, this method will search some default locations depending on the
01359  * current platform. <b>cookiePath</b> can point to either a cookie file or a
01360  * directory containing the cookie file. */
01361 QByteArray
01362 MainWindow::loadControlCookie(QString cookiePath)
01363 {
01364   QFile authCookie;
01365   QStringList pathList;
01366 
01367   if (!cookiePath.isEmpty()) {
01368     pathList << cookiePath;
01369   } else {
01370     /* Try some default locations */
01371     TorSettings settings;
01372     QString dataDir = settings.getDataDirectory();
01373     if (!dataDir.isEmpty())
01374       pathList << dataDir;
01375       
01376 #if defined(Q_WS_WIN)
01377     pathList << expand_filename("%APPDATA%\\Tor");
01378 #else
01379     pathList << expand_filename("~/.tor");
01380 #endif
01381   }
01382   
01383   /* Search for the cookie file */
01384   foreach (QString path, pathList) {
01385     QString cookieFile = QFileInfo(path).isFile() ?
01386                           path : path + "/control_auth_cookie";
01387     vDebug("Checking for authentication cookie in '%1'").arg(cookieFile);
01388     if (!QFileInfo(cookieFile).exists())
01389       continue;
01390     
01391     authCookie.setFileName(cookieFile);
01392     if (authCookie.open(QIODevice::ReadOnly)) {
01393       vInfo("Reading authentication cookie from '%1'").arg(cookieFile);
01394       return authCookie.readAll();
01395     } else {
01396       vWarn("Couldn't open cookie file '%1': %2")
01397         .arg(cookieFile).arg(authCookie.errorString());
01398     }
01399   }
01400   vWarn("Couldn't find a readable authentication cookie.");
01401   return QByteArray();
01402 }
01403 
01404 /** Called when Tor has successfully established a circuit. */
01405 void
01406 MainWindow::circuitEstablished()
01407 {
01408   updateTorStatus(CircuitEstablished);
01409   setStartupProgress(ui.progressBar->maximum(),
01410                      tr("Connected to the Tor network!"));
01411   startSubprocesses();
01412 }
01413 
01414 /** Checks the status of the current version of Tor to see if it's old,
01415  * unrecommended, or obsolete. */
01416 void
01417 MainWindow::checkTorVersion()
01418 {
01419   QString status;
01420   if (_torControl->getInfo("status/version/current", status)) {
01421     if (!status.compare("old", Qt::CaseInsensitive)
01422           || !status.compare("unrecommended", Qt::CaseInsensitive)
01423           || !status.compare("obsolete", Qt::CaseInsensitive)) {
01424       dangerousTorVersion();
01425     }
01426   }
01427 }
01428 
01429 /** Called when Tor thinks its version is old or unrecommended, and displays a
01430  * message notifying the user. */
01431 void
01432 MainWindow::dangerousTorVersion()
01433 {
01434   static bool alreadyWarned = false;
01435 
01436   if (!alreadyWarned) {
01437     QString website = "https://www.torproject.org/";
01438 #if QT_VERSION >= 0x040200
01439     website = QString("<a href=\"%1\">%1</a>").arg(website);
01440 #endif
01441 
01442     VMessageBox::information(this,
01443       tr("Tor Update Available"),
01444       p(tr("The currently installed version of Tor is out of date or no longer "
01445            "recommended. Please visit the Tor website to download the latest "
01446            "version.")) + p(tr("Tor website: %1").arg(website)),
01447       VMessageBox::Ok);
01448     alreadyWarned = true;
01449   }
01450 }
01451 
01452 /** Creates and displays Vidalia's About dialog. */
01453 void
01454 MainWindow::showAboutDialog()
01455 {
01456   static AboutDialog *aboutDialog = 0;
01457   if (!aboutDialog)
01458     aboutDialog = new AboutDialog(this);
01459   aboutDialog->showWindow();
01460 }
01461 
01462 /** Displays the help browser and displays the most recently viewed help
01463  * topic. */
01464 void
01465 MainWindow::showHelpDialog()
01466 {
01467   showHelpDialog(QString());
01468 }
01469 
01470 /**< Shows the help browser and displays the given help <b>topic</b>. */
01471 void
01472 MainWindow::showHelpDialog(const QString &topic)
01473 {
01474   static HelpBrowser *helpBrowser = 0;
01475   if (!helpBrowser)
01476     helpBrowser = new HelpBrowser(this);
01477   helpBrowser->showWindow(topic);
01478 }
01479 
01480 /** Creates and displays the Configuration dialog with the current page set to
01481  * <b>page</b>. */
01482 void
01483 MainWindow::showConfigDialog(ConfigDialog::Page page)
01484 {
01485   _configDialog->showWindow(page);
01486 }
01487 
01488 /** Displays the Configuration dialog, set to the Server page. */
01489 void
01490 MainWindow::showServerConfigDialog()
01491 {
01492   showConfigDialog(ConfigDialog::Server);
01493 }
01494 
01495 /** Called when the user selects the "New Identity" action from the menu. */
01496 void
01497 MainWindow::newIdentity()
01498 {
01499   QString errmsg;
01500 
01501   /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM
01502    * is successful, we will show the result as a balloon. Otherwise, we'll 
01503    * just use a message box. */
01504   if (_torControl->signal(TorSignal::NewNym, &errmsg)) {
01505     /* NEWNYM signal was successful */
01506     QString title = tr("New Identity");
01507     QString message = tr("All subsequent connections will "
01508                          "appear to be different than your "
01509                          "old connections.");
01510 
01511     /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */
01512     _newIdentityAct->setEnabled(false);
01513     ui.lblNewIdentity->setEnabled(false);
01514     QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, 
01515                        this, SLOT(enableNewIdentity()));
01516 
01517     if (TrayIcon::supportsBalloonMessages())
01518       _trayIcon.showBalloonMessage(title, message, TrayIcon::Information);
01519     else
01520       VMessageBox::information(this, title, message, VMessageBox::Ok);
01521   } else {
01522     /* NEWNYM signal failed */
01523     VMessageBox::warning(this, 
01524       tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok);
01525   }
01526 }
01527 
01528 /** Re-enables the 'New Identity' button after a delay from the previous time
01529  * 'New Identity' was used. */
01530 void
01531 MainWindow::enableNewIdentity()
01532 {
01533   if (_torControl->isConnected()) {
01534     _newIdentityAct->setEnabled(true);
01535     ui.lblNewIdentity->setEnabled(true);
01536   }
01537 }
01538 
01539 /** Converts a TorStatus enum value to a string for debug logging purposes. */
01540 QString
01541 MainWindow::toString(TorStatus status)
01542 {
01543   switch (status) {
01544     /* These strings only appear in debug logs, so they should not be
01545      * translated. */
01546     case Unset:     return "Unset";
01547     case Stopping:  return "Stopping";
01548     case Stopped:   return "Stopped";
01549     case Starting:  return "Starting";
01550     case Started:   return "Started";
01551     case Authenticating:  return "Authenticating";
01552     case Authenticated:   return "Authenticated";
01553     case CircuitEstablished: return "Circuit Established";
01554     default: break;
01555   }
01556   return "Unknown";
01557 }
01558 
01559 #if defined(USE_MINIUPNPC)
01560 /** Called when a UPnP error occurs. */
01561 void
01562 MainWindow::upnpError(UPNPControl::UPNPError error)
01563 {
01564   Q_UNUSED(error);
01565 
01566 #if 0
01567   /* XXX: Is there a better way to do this? Currently, this could get called
01568    * if there is an error when testing UPnP support, and again when attempting
01569    * to reset the UPnP state when the test dialog is closed. The user would
01570    * not be amused with all the warning dialogs. */
01571 
01572   VMessageBox::warning(this,
01573     tr("Port Forwarding Failed"),
01574     p(tr("Vidalia was unable to configure automatic port forwarding."))
01575       + p(UPNPControl::Instance()->errorString()),
01576     VMessageBox::Ok);
01577 #endif
01578 }
01579 #endif
01580 

Generated on Tue Jul 7 17:00:56 2009 for Vidalia by  doxygen 1.4.7