Vidalia  0.2.17
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.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file MainWindow.cpp
00013 ** \brief Main (hidden) window. Creates tray menu and child windows
00014 **
00015 ** Implements the main window. The main window is a hidden window that serves
00016 ** as the parent of the tray icon and popup menu, as well as other application
00017 ** dialogs.
00018 */
00019 
00020 #include "MainWindow.h"
00021 #include "Vidalia.h"
00022 #include "VMessageBox.h"
00023 #include "ControlPasswordInputDialog.h"
00024 #include "TorSettings.h"
00025 #include "ServerSettings.h"
00026 #ifdef USE_AUTOUPDATE
00027 #include "UpdatesAvailableDialog.h"
00028 #endif
00029 
00030 #include "ProtocolInfo.h"
00031 
00032 #include "net.h"
00033 #include "file.h"
00034 #include "html.h"
00035 #include "stringutil.h"
00036 #include "procutil.h"
00037 
00038 #include <QMenuBar>
00039 #include <QTimer>
00040 #include <QTextStream>
00041 
00042 #ifdef Q_WS_MAC
00043 #include <Carbon/Carbon.h>
00044 #endif
00045 
00046 #define IMG_BWGRAPH        ":/images/16x16/utilities-system-monitor.png"
00047 #define IMG_CONTROL_PANEL  ":/images/16x16/system-run.png"
00048 #define IMG_MESSAGELOG     ":/images/16x16/format-justify-fill.png"
00049 #define IMG_CONFIG         ":/images/16x16/preferences-system.png"
00050 #define IMG_IDENTITY       ":/images/16x16/view-media-artist.png"
00051 #define IMG_HELP           ":/images/16x16/system-help.png"
00052 #define IMG_ABOUT          ":/images/16x16/help-about.png"
00053 #define IMG_EXIT           ":/images/16x16/application-exit.png"
00054 #define IMG_NETWORK        ":/images/16x16/applications-internet.png"
00055 
00056 #define IMG_START_TOR_16     ":/images/16x16/media-playback-start.png"
00057 #define IMG_STOP_TOR_16      ":/images/16x16/media-playback-stop.png"
00058 #define IMG_START_TOR_48     ":/images/48x48/media-playback-start.png"
00059 #define IMG_STOP_TOR_48      ":/images/48x48/media-playback-stop.png"
00060 #define IMG_TOR_STOPPED_48   ":/images/48x48/tor-off.png"
00061 #define IMG_TOR_RUNNING_48   ":/images/48x48/tor-on.png"
00062 #define IMG_TOR_STARTING_48  ":/images/48x48/tor-starting.png"
00063 #define IMG_TOR_STOPPING_48  ":/images/48x48/tor-stopping.png"
00064 
00065 /* Decide which of our four sets of tray icons to use. */
00066 #if defined(Q_WS_WIN)
00067 /* QSystemTrayIcon on Windows wants 16x16 .png files */
00068 #define IMG_TOR_STOPPED  ":/images/16x16/tor-off.png"
00069 #define IMG_TOR_RUNNING  ":/images/16x16/tor-on.png"
00070 #define IMG_TOR_STARTING ":/images/16x16/tor-starting.png"
00071 #define IMG_TOR_STOPPING ":/images/16x16/tor-stopping.png"
00072 #elif defined(Q_WS_MAC)
00073 /* On Mac, the dock icons look best at 128x128, otherwise they get blurry
00074  * if resized from a smaller image */
00075 #define IMG_TOR_STOPPED    ":/images/128x128/tor-off.png"
00076 #define IMG_TOR_RUNNING    ":/images/128x128/tor-on.png"
00077 #define IMG_TOR_STARTING   ":/images/128x128/tor-starting.png"
00078 #define IMG_TOR_STOPPING   ":/images/128x128/tor-stopping.png"
00079 void qt_mac_set_dock_menu(QMenu *menu);
00080 #else
00081 /* On X11, we just use always the 22x22 .png files */
00082 #define IMG_TOR_STOPPED    ":/images/22x22/tor-off.png"
00083 #define IMG_TOR_RUNNING    ":/images/22x22/tor-on.png"
00084 #define IMG_TOR_STARTING   ":/images/22x22/tor-starting.png"
00085 #define IMG_TOR_STOPPING   ":/images/22x22/tor-stopping.png"
00086 #endif
00087 
00088 /** Only allow 'New Identity' to be clicked once every 10 seconds. */
00089 #define MIN_NEWIDENTITY_INTERVAL   (10*1000)
00090 
00091 /* Startup progress milestones */
00092 #define STARTUP_PROGRESS_STARTING          0
00093 #define STARTUP_PROGRESS_CONNECTING       10
00094 #define STARTUP_PROGRESS_AUTHENTICATING   20
00095 #define STARTUP_PROGRESS_BOOTSTRAPPING    30
00096 #define STARTUP_PROGRESS_CIRCUITBUILD     75
00097 #define STARTUP_PROGRESS_MAXIMUM          (STARTUP_PROGRESS_BOOTSTRAPPING+100)
00098 
00099 /** Default constructor. It installs an icon in the system tray area and
00100  * creates the popup menu associated with that icon. */
00101 MainWindow::MainWindow()
00102 : VidaliaWindow("MainWindow")
00103 {
00104   VidaliaSettings settings;
00105 
00106   ui.setupUi(this);
00107 
00108   /* Pressing 'Esc' or 'Ctrl+W' will close the window */
00109   Vidalia::createShortcut("Ctrl+W", this, ui.btnHide, SLOT(click()));
00110   Vidalia::createShortcut("Esc", this, ui.btnHide, SLOT(click()));
00111 
00112   /* Create all the dialogs of which we only want one instance */
00113   _messageLog     = new MessageLog();
00114   _bandwidthGraph = new BandwidthGraph();
00115   _netViewer      = new NetViewer();
00116   _configDialog   = new ConfigDialog();
00117   _menuBar        = 0;
00118   connect(_messageLog, SIGNAL(helpRequested(QString)),
00119           this, SLOT(showHelpDialog(QString)));
00120   connect(_netViewer, SIGNAL(helpRequested(QString)),
00121           this, SLOT(showHelpDialog(QString)));
00122   connect(_configDialog, SIGNAL(helpRequested(QString)),
00123           this, SLOT(showHelpDialog(QString)));
00124   connect(_configDialog, SIGNAL(restartTor()),
00125           this, SLOT(restart()));
00126 
00127   /* Create the actions that will go in the tray menu */
00128   createActions();
00129   /* Creates a tray icon with a context menu and adds it to the system's
00130    * notification area. */
00131   createTrayIcon();
00132   /* Start with Tor initially stopped */
00133   _status = Unset;
00134   _isVidaliaRunningTor = false;
00135   updateTorStatus(Stopped);
00136 
00137   /* Create a new TorControl object, used to communicate with Tor */
00138   _torControl = Vidalia::torControl(); 
00139   connect(_torControl, SIGNAL(started()), this, SLOT(started()));
00140   connect(_torControl, SIGNAL(startFailed(QString)),
00141           this, SLOT(startFailed(QString)));
00142   connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)),
00143           this, SLOT(stopped(int, QProcess::ExitStatus)));
00144   connect(_torControl, SIGNAL(connected()), this, SLOT(connected()));
00145   connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected()));
00146   connect(_torControl, SIGNAL(connectFailed(QString)), 
00147           this, SLOT(connectFailed(QString)));
00148   connect(_torControl, SIGNAL(authenticated()), this, SLOT(authenticated()));
00149   connect(_torControl, SIGNAL(authenticationFailed(QString)),
00150           this, SLOT(authenticationFailed(QString)));
00151 
00152   _torControl->setEvent(TorEvents::GeneralStatus);
00153   connect(_torControl, SIGNAL(dangerousTorVersion(tc::TorVersionStatus,
00154                                                   QString, QStringList)),
00155           this, SLOT(dangerousTorVersion(tc::TorVersionStatus,
00156                                          QString, QStringList)));
00157 
00158   _torControl->setEvent(TorEvents::ClientStatus);
00159   connect(_torControl, SIGNAL(bootstrapStatusChanged(BootstrapStatus)),
00160           this, SLOT(bootstrapStatusChanged(BootstrapStatus)));
00161   connect(_torControl, SIGNAL(circuitEstablished()),
00162           this, SLOT(circuitEstablished()));
00163   connect(_torControl, SIGNAL(dangerousPort(quint16, bool)),
00164           this, SLOT(warnDangerousPort(quint16, bool)));
00165 
00166   /* Create a new HelperProcess object, used to start the web browser */
00167   _browserProcess = new HelperProcess(this);
00168   connect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
00169            this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
00170   connect(_browserProcess, SIGNAL(startFailed(QString)),
00171            this, SLOT(onBrowserFailed(QString)));
00172 
00173   /* Create a new HelperProcess object, used to start the IM client */
00174   _imProcess = new HelperProcess(this);
00175   connect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
00176            this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
00177   connect(_imProcess, SIGNAL(startFailed(QString)),
00178            this, SLOT(onIMFailed(QString)));
00179 
00180   /* Create a new HelperProcess object, used to start the proxy server */
00181   _proxyProcess = new HelperProcess(this);
00182   connect(_proxyProcess, SIGNAL(startFailed(QString)),
00183            this, SLOT(onProxyFailed(QString)));
00184 
00185   /* Catch signals when the application is running or shutting down */
00186   connect(vApp, SIGNAL(running()), this, SLOT(running()));
00187   connect(vApp, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
00188 
00189 #if defined(USE_AUTOUPDATE)
00190   /* Create a timer used to remind us to check for software updates */
00191   connect(&_updateTimer, SIGNAL(timeout()), this, SLOT(checkForUpdates()));
00192 
00193   /* Also check for updates in the foreground when the user clicks the
00194    * "Check Now" button in the config dialog. */
00195   connect(_configDialog, SIGNAL(checkForUpdates()),
00196           this, SLOT(checkForUpdatesWithUi()));
00197 
00198   /* The rest of these slots are called as the update process executes. */
00199   connect(&_updateProcess, SIGNAL(downloadProgress(QString,int,int)),
00200           &_updateProgressDialog, SLOT(setDownloadProgress(QString,int,int)));
00201   connect(&_updateProcess, SIGNAL(updatesAvailable(UpdateProcess::BundleInfo,PackageList)),
00202           this, SLOT(updatesAvailable(UpdateProcess::BundleInfo,PackageList)));
00203   connect(&_updateProcess, SIGNAL(updatesInstalled(int)),
00204           this, SLOT(updatesInstalled(int)));
00205   connect(&_updateProcess, SIGNAL(installUpdatesFailed(QString)),
00206           this, SLOT(installUpdatesFailed(QString)));
00207   connect(&_updateProgressDialog, SIGNAL(cancelUpdate()),
00208           &_updateProcess, SLOT(cancel()));
00209 #endif
00210 
00211 #if defined(USE_MINIUPNPC)
00212   /* Catch UPnP-related signals */
00213   connect(UPNPControl::instance(), SIGNAL(error(UPNPControl::UPNPError)),
00214          this, SLOT(upnpError(UPNPControl::UPNPError)));
00215 #endif
00216 
00217   ui.chkShowOnStartup->setChecked(settings.showMainWindowAtStart());
00218   if (ui.chkShowOnStartup->isChecked())
00219     show(); 
00220   /* Optimistically hope that the tray icon gets added. */
00221   _trayIcon.show();
00222 
00223 #if defined(Q_WS_MAC)
00224   /* Display OSX dock icon if icon preference is not set to "Tray Only" */
00225   if (settings.getIconPref() != VidaliaSettings::Tray) {
00226       ProcessSerialNumber psn = { 0, kCurrentProcess };
00227       TransformProcessType(&psn, kProcessTransformToForegroundApplication);
00228   }
00229   /* Vidalia launched in background (LSUIElement=true). Bring to foreground. */
00230   VidaliaWindow::setVisible(true);
00231 #endif
00232 }
00233 
00234 /** Destructor. */
00235 MainWindow::~MainWindow()
00236 {
00237   _trayIcon.hide();
00238   delete _messageLog;
00239   delete _bandwidthGraph;
00240   delete _netViewer;
00241   delete _configDialog;
00242 }
00243 
00244 void
00245 MainWindow::setVisible(bool visible)
00246 {
00247   if (visible) {
00248     /* In Gnome, will hide buttons if Vidalia is run on startup. */
00249     if (!QSystemTrayIcon::isSystemTrayAvailable()) {
00250       /* Don't let people hide the main window, since that's all they have. */
00251       ui.chkShowOnStartup->hide();
00252       ui.btnHide->hide();
00253       /* Causes window to not appear in Enlightenment. */
00254       //setMinimumHeight(height()-ui.btnHide->height());
00255       //setMaximumHeight(height()-ui.btnHide->height());
00256     }
00257   }
00258   VidaliaWindow::setVisible(visible);
00259 }
00260 
00261 void
00262 MainWindow::retranslateUi()
00263 {
00264   ui.retranslateUi(this);
00265 
00266   updateTorStatus(_status);
00267   if (_status == Stopped) {
00268     _actionStartStopTor->setText(tr("Start Tor"));
00269     ui.lblStartStopTor->setText(tr("Start Tor"));
00270   } else if (_status == Starting) {
00271     _actionStartStopTor->setText(tr("Starting Tor"));
00272     ui.lblStartStopTor->setText(tr("Starting Tor"));
00273   } else {
00274     _actionStartStopTor->setText(tr("Stop Tor"));
00275     ui.lblStartStopTor->setText(tr("Stop Tor"));
00276   }
00277 
00278   _actionShowBandwidth->setText(tr("Bandwidth Graph"));
00279   _actionShowMessageLog->setText(tr("Message Log"));
00280   _actionShowNetworkMap->setText(tr("Network Map"));
00281   _actionShowControlPanel->setText(tr("Control Panel"));
00282   _actionShowHelp->setText(tr("Help"));
00283   _actionNewIdentity->setText(tr("New Identity"));
00284 
00285 #if !defined(Q_WS_MAC)
00286   _actionShowAbout->setText(tr("About"));
00287   _actionShowConfig->setText(tr("Settings"));
00288   _actionExit->setText(tr("Exit"));
00289 #else
00290   createMenuBar();
00291 #endif
00292 }
00293 
00294 /** Called when the application has started and the main event loop is
00295  * running. */
00296 void
00297 MainWindow::running()
00298 {
00299   VidaliaSettings settings;
00300 
00301   if (vApp->readPasswordFromStdin()) {
00302     QTextStream in(stdin);
00303     in >> _controlPassword;
00304     _useSavedPassword = false;
00305   } else {
00306     /* Initialize _useSavedPassword to true. If Tor is already running when
00307      * Vidalia starts, then there is no point in generating a random password.
00308      * If Tor is not already running, then this will be set according to the
00309      * current configuration in the start() method.
00310      */
00311     _useSavedPassword = true;
00312   }
00313 
00314   if (settings.runTorAtStart()) {
00315     /* If we're supposed to start Tor when Vidalia starts, then do it now */
00316     start();
00317   }
00318 
00319   /* Start the proxy server, if configured */
00320   if (settings.runProxyAtStart())
00321     startProxy();
00322 
00323 #if defined(USE_AUTOUPDATE)
00324   if (settings.isAutoUpdateEnabled()) {
00325     QDateTime lastCheckedAt = settings.lastCheckedForUpdates();
00326     if (UpdateProcess::shouldCheckForUpdates(lastCheckedAt)) {
00327       if (settings.runTorAtStart() && ! _torControl->isCircuitEstablished()) {
00328         /* We started Tor but it hasn't bootstrapped yet, so give it a bit
00329          * before we decide to check for updates. If Tor manages to build a
00330          * circuit before this timer times out, we will stop the timer and
00331          * launch a check for updates immediately. (see circuitEstablished()).
00332          */
00333         _updateTimer.start(5*60*1000);
00334       } else {
00335         /* Initiate a background check for updates now */
00336         checkForUpdates();
00337       }
00338     } else {
00339       /* Schedule the next time to check for updates */
00340       QDateTime nextCheckAt = UpdateProcess::nextCheckForUpdates(lastCheckedAt);
00341       QDateTime now = QDateTime::currentDateTime().toUTC();
00342 
00343       vInfo("Last checked for software updates at %1. Will check again at %2.")
00344         .arg(lastCheckedAt.toLocalTime().toString("dd-MM-yyyy hh:mm:ss"))
00345         .arg(nextCheckAt.toLocalTime().toString("dd-MM-yyyy hh:mm:ss"));
00346       _updateTimer.start((nextCheckAt.toTime_t() - now.toTime_t()) * 1000);
00347     }
00348   }
00349 #endif
00350 }
00351 
00352 /** Terminate the Tor process if it is being run under Vidalia, disconnect all
00353  * TorControl signals, and exit Vidalia. */
00354 void
00355 MainWindow::aboutToQuit()
00356 {
00357   vNotice("Cleaning up before exiting.");
00358 
00359   if (_torControl->isVidaliaRunningTor()) {
00360     /* Kill our Tor process now */ 
00361     _torControl->stop();
00362   }
00363 
00364   /* Disable port forwarding */
00365   ServerSettings settings(_torControl);
00366   settings.cleanupPortForwarding();
00367 
00368   if (_proxyProcess->state() != QProcess::NotRunning) {
00369     /* Close the proxy server (Polipo ignores the WM_CLOSE event sent by
00370      * terminate() so we have to kill() it) */
00371     _proxyProcess->kill();
00372   }
00373 
00374   /* Kill the browser and IM client if using the new launcher */
00375   VidaliaSettings vidalia_settings;
00376 
00377   if (! vidalia_settings.getBrowserDirectory().isEmpty()) {
00378     /* Disconnect the finished signals so that we won't try to exit Vidalia again */
00379     QObject::disconnect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
00380     QObject::disconnect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
00381 
00382     /* Use QProcess terminate function */
00383     if (_browserProcess->state() == QProcess::Running)
00384       _browserProcess->terminate();
00385 
00386 #if defined(Q_OS_WIN)
00387     /* Kill any processes which might have been forked off */
00388     win32_end_process_by_filename(vidalia_settings.getBrowserExecutable());
00389 #endif
00390 
00391     if (_imProcess->state() == QProcess::Running)
00392       _imProcess->terminate();    
00393   }
00394 
00395   /* Disconnect all of the TorControl object's signals */
00396   QObject::disconnect(_torControl, 0, 0, 0);
00397 }
00398 
00399 /** Called when the application is closing, by selecting "Exit" from the tray
00400  * menu. If we're running a Tor server, then ask if we want to kill Tor now,
00401  * or do a delayed shutdown. */
00402 void
00403 MainWindow::close()
00404 {
00405   if (_torControl->isVidaliaRunningTor()) {
00406     /* If we're running a server currently, ask if we want to do a delayed
00407      * shutdown. If we do, then close Vidalia only when Tor stops. Otherwise,
00408      * kill Tor and bail now. */
00409     ServerSettings settings(_torControl);
00410     if (_torControl->isConnected() && settings.isServerEnabled()) {
00411       connect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit()));
00412       if (!stop())
00413         QObject::disconnect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit()));
00414       return;
00415     }
00416   }
00417   vApp->quit();
00418 }
00419 
00420 /** Create and bind actions to events. Setup for initial
00421  * tray menu configuration. */
00422 void 
00423 MainWindow::createActions()
00424 {
00425   _actionStartStopTor = new QAction(tr("Start Tor"), this);
00426   connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(start()));
00427 
00428   _actionExit = new QAction(tr("Exit"), this);
00429   connect(_actionExit, SIGNAL(triggered()), this, SLOT(close()));
00430 
00431   _actionShowBandwidth = new QAction(tr("Bandwidth Graph"), this);
00432   connect(_actionShowBandwidth, SIGNAL(triggered()), 
00433           _bandwidthGraph, SLOT(showWindow()));
00434   connect(ui.lblBandwidthGraph, SIGNAL(clicked()),
00435           _bandwidthGraph, SLOT(showWindow()));
00436 
00437   _actionShowMessageLog = new QAction(tr("Message Log"), this);
00438   connect(_actionShowMessageLog, SIGNAL(triggered()),
00439           _messageLog, SLOT(showWindow()));
00440   connect(ui.lblMessageLog, SIGNAL(clicked()),
00441           _messageLog, SLOT(showWindow()));
00442 
00443   _actionShowNetworkMap = new QAction(tr("Network Map"), this);
00444   connect(_actionShowNetworkMap, SIGNAL(triggered()), 
00445           _netViewer, SLOT(showWindow()));
00446   connect(ui.lblViewNetwork, SIGNAL(clicked()),
00447           _netViewer, SLOT(showWindow()));
00448 
00449   _actionShowControlPanel = new QAction(tr("Control Panel"), this);
00450   connect(_actionShowControlPanel, SIGNAL(triggered()), this, SLOT(show()));
00451 
00452   _actionShowConfig = new QAction(tr("Settings"), this);
00453   connect(_actionShowConfig, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
00454   
00455   _actionShowAbout = new QAction(tr("About"), this);
00456   connect(_actionShowAbout, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
00457 
00458   _actionShowHelp = new QAction(tr("Help"), this);
00459   connect(_actionShowHelp, SIGNAL(triggered()), this, SLOT(showHelpDialog()));
00460   connect(ui.lblHelpBrowser, SIGNAL(clicked()), this, SLOT(showHelpDialog()));
00461 
00462   _actionNewIdentity = new QAction(tr("New Identity"), this);
00463   _actionNewIdentity->setEnabled(false);
00464   connect(_actionNewIdentity, SIGNAL(triggered()), this, SLOT(newIdentity()));
00465 
00466 #if !defined(Q_WS_MAC)
00467   /* Don't give the menu items icons on OS X, since they end up in the
00468    * application menu bar. Menu bar items on OS X typically do not have
00469    * icons. */
00470   _actionStartStopTor->setIcon(QIcon(IMG_START_TOR_16));
00471   _actionExit->setIcon(QIcon(IMG_EXIT));
00472   _actionShowBandwidth->setIcon(QIcon(IMG_BWGRAPH));
00473   _actionShowMessageLog->setIcon(QIcon(IMG_MESSAGELOG));
00474   _actionShowNetworkMap->setIcon(QIcon(IMG_NETWORK));
00475   _actionShowControlPanel->setIcon(QIcon(IMG_CONTROL_PANEL));
00476   _actionShowConfig->setIcon(QIcon(IMG_CONFIG));
00477   _actionShowAbout->setIcon(QIcon(IMG_ABOUT));
00478   _actionShowHelp->setIcon(QIcon(IMG_HELP));
00479   _actionNewIdentity->setIcon(QIcon(IMG_IDENTITY));
00480 #endif
00481 }
00482 
00483 /** Creates a tray icon with a context menu and adds it to the system
00484  * notification area. On Mac, we also set up an application menubar. */
00485 void
00486 MainWindow::createTrayIcon()
00487 {
00488   QMenu *menu = createTrayMenu();
00489 
00490   /* Add the menu it to the tray icon */
00491   _trayIcon.setContextMenu(menu);
00492 
00493   connect(&_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
00494           this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
00495 
00496 #if defined(Q_WS_MAC)
00497   createMenuBar();
00498   qt_mac_set_dock_menu(menu);
00499 #endif
00500 }
00501 
00502 /** Creates a QMenu object that contains QActions which compose the system 
00503  * tray menu. */
00504 QMenu* 
00505 MainWindow::createTrayMenu()
00506 {
00507   QMenu *menu = new QMenu(this);
00508   menu->addAction(_actionStartStopTor);
00509   menu->addSeparator();
00510   menu->addAction(_actionShowBandwidth);
00511   menu->addAction(_actionShowMessageLog);
00512   menu->addAction(_actionShowNetworkMap);
00513   menu->addAction(_actionNewIdentity);
00514   menu->addSeparator();
00515   menu->addAction(_actionShowControlPanel);
00516   
00517 #if !defined(Q_WS_MAC)
00518   /* These aren't added to the dock menu on Mac, since they are in the
00519    * standard Mac locations in the menu bar. */
00520   menu->addAction(_actionShowConfig);
00521   menu->addAction(_actionShowHelp);
00522   menu->addAction(_actionShowAbout);
00523   menu->addSeparator();
00524   menu->addAction(_actionExit);
00525 #endif
00526   return menu;
00527 }
00528 
00529 /** Creates a new menubar with no parent, so Qt will use this as the "default
00530  * menubar" on Mac. This adds on to the existing actions from the createMens()
00531  * method. */
00532 void
00533 MainWindow::createMenuBar()
00534 {
00535 #if defined(Q_WS_MAC)
00536   /* Mac users sure like their shortcuts. Actions NOT mentioned below
00537    * don't explicitly need shortcuts, since they are merged to the default
00538    * menubar and get the default shortcuts anyway. */
00539   _actionStartStopTor->setShortcut(tr("Ctrl+T"));
00540   _actionShowBandwidth->setShortcut(tr("Ctrl+B"));
00541   _actionShowMessageLog->setShortcut(tr("Ctrl+L"));
00542   _actionShowNetworkMap->setShortcut(tr("Ctrl+N"));
00543   _actionShowHelp->setShortcut(tr("Ctrl+?"));
00544   _actionNewIdentity->setShortcut(tr("Ctrl+I"));
00545   _actionShowControlPanel->setShortcut(tr("Ctrl+P"));
00546 
00547   /* Force Qt to put merge the Exit, Configure, and About menubar options into
00548    * the default menu, even if Vidalia is currently not speaking English. */
00549   _actionShowConfig->setText("config");
00550   _actionShowConfig->setMenuRole(QAction::PreferencesRole);
00551   _actionShowAbout->setText("about");
00552   _actionShowAbout->setMenuRole(QAction::AboutRole);
00553   _actionExit->setText("quit");
00554   _actionExit->setMenuRole(QAction::QuitRole);
00555 
00556   /* The File, Help, and Configure menus will get merged into the application
00557    * menu by Qt. */
00558   if (_menuBar)
00559     delete _menuBar;
00560   _menuBar = new QMenuBar(0);
00561   QMenu *fileMenu = _menuBar->addMenu("File");
00562   fileMenu->addAction(_actionExit);
00563   fileMenu->addAction(_actionShowConfig);
00564 
00565   QMenu *torMenu = _menuBar->addMenu(tr("Tor"));
00566   torMenu->addAction(_actionStartStopTor);
00567   torMenu->addSeparator();
00568   torMenu->addAction(_actionNewIdentity);
00569 
00570   QMenu *viewMenu = _menuBar->addMenu(tr("View"));
00571   viewMenu->addAction(_actionShowControlPanel);
00572   viewMenu->addSeparator();
00573   viewMenu->addAction(_actionShowBandwidth);
00574   viewMenu->addAction(_actionShowMessageLog);
00575   viewMenu->addAction(_actionShowNetworkMap);
00576   
00577   QMenu *helpMenu = _menuBar->addMenu(tr("Help"));
00578   _actionShowHelp->setText(tr("Vidalia Help"));
00579   helpMenu->addAction(_actionShowHelp);
00580   helpMenu->addAction(_actionShowAbout);
00581 #endif
00582 }
00583 
00584 /** Sets the current tray or dock icon image to <b>iconFile</b>. */
00585 void
00586 MainWindow::setTrayIcon(const QString &iconFile)
00587 {
00588 #if defined(Q_WS_MAC)
00589   VidaliaSettings settings;
00590   QApplication::setWindowIcon(QPixmap(iconFile));
00591   /* only display tray icon if icon preference is not set to "Dock Only" */
00592   if (settings.getIconPref() != VidaliaSettings::Dock)
00593     _trayIcon.setIcon(QIcon(iconFile));
00594 #else
00595   /* always display tray icon for other platforms */
00596   _trayIcon.setIcon(QIcon(iconFile));
00597 #endif
00598 }
00599 
00600 /** Respond to a double-click on the tray icon by opening the Control Panel
00601  * window. */
00602 void
00603 MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
00604 {
00605   if (reason == QSystemTrayIcon::DoubleClick)
00606     setVisible(true);
00607 }
00608 
00609 /** Start a web browser when given the directory containing the executable and profile */
00610 void
00611 MainWindow::launchBrowserFromDirectory()
00612 {
00613   VidaliaSettings settings;
00614 
00615   QString browserDirectory = settings.getBrowserDirectory();
00616   QString browserDirectoryFilename = settings.getBrowserExecutable();
00617 
00618   _browserProcess->setEnvironment(updateBrowserEnv());
00619 
00620   /* The browser is in <browserDirectory>/App/Firefox/<browserDirectoryFilename> */
00621   QString browserExecutable =
00622     QDir::toNativeSeparators(browserDirectory + "/App/Firefox/" + browserDirectoryFilename);
00623   /* The profile is in <browserDirectory>/Data/profile */
00624   QString profileDir =
00625     QDir::toNativeSeparators(browserDirectory + "/Data/profile");
00626 
00627   /* Copy the profile directory if it's not already there */
00628   QDir browserDirObj = QDir(browserDirectory);
00629 
00630   /* Copy the profile directory if it's not already there */
00631   if (!browserDirObj.exists("Data/profile")) {
00632     browserDirObj.mkdir("Data/profile");
00633     copy_dir(browserDirectory + "/App/DefaultData/profile", browserDirectory + "/Data/profile");
00634   }
00635 
00636   /* Copy the plugins directory if it's not already there */
00637   if (!browserDirObj.exists("Data/plugins")) {
00638     browserDirObj.mkdir("Data/plugins");
00639     copy_dir(browserDirectory + "/App/DefaultData/plugins", browserDirectory + "/Data/plugins");
00640   }
00641 
00642   /* Build the command line arguments */
00643   QStringList commandLine;
00644   // Is this better or worse than MOZ_NO_REMOTE?
00645   commandLine << "-no-remote";
00646   commandLine << "-profile";
00647   commandLine << profileDir;
00648 
00649   /* Launch the browser */
00650   if(!_browserProcess->state() != QProcess::NotRunning)
00651     _browserProcess->start(browserExecutable, commandLine);
00652   _browserProcess->toForeground();
00653 }
00654 
00655 /** Starts the web browser and IM client, if appropriately configured */
00656 void
00657 MainWindow::startSubprocesses()
00658 {
00659   VidaliaSettings settings;
00660   QString subprocess;
00661 
00662   /* Launch the web browser */
00663   if (!(subprocess = settings.getBrowserDirectory()).isEmpty()) {
00664     /* The user has set BrowserDirectory; use this */
00665     launchBrowserFromDirectory();
00666   } else if (!(subprocess = settings.getBrowserExecutable()).isEmpty()) {
00667     /* BrowserDirectory is not set, but BrowserExecutable is; use this */
00668     _browserProcess->setEnvironment(updateBrowserEnv());
00669     if(!_browserProcess->state() != QProcess::NotRunning)
00670       _browserProcess->start(subprocess, QStringList());
00671     _browserProcess->toForeground();
00672   }
00673 
00674   /* Launch the IM client */
00675   subprocess = settings.getIMExecutable();
00676 
00677   if (!subprocess.isEmpty())
00678     _imProcess->start(subprocess, QStringList());
00679 }
00680 
00681 /** Called when browser or IM client have exited */
00682 void
00683 MainWindow::onSubprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
00684 {
00685   Q_UNUSED(exitCode)
00686   Q_UNUSED(exitStatus)
00687 
00688   /* Get path to browser and IM client */
00689   VidaliaSettings settings;
00690   QString browserExecutable = settings.getBrowserExecutable();
00691   QString browserDirectory = settings.getBrowserDirectory();
00692   QString imExecutable = settings.getIMExecutable();
00693 
00694   /* A subprocess is finished if it successfully exited or was never asked to start */
00695   bool browserDone = (browserExecutable.isEmpty()
00696                         && browserDirectory.isEmpty())
00697                         || _browserProcess->isDone();
00698   bool imDone = imExecutable.isEmpty() || _imProcess->isDone();
00699 
00700   /* Exit if both subprocesses are finished */
00701   if (browserDone && imDone) {
00702     if (browserDirectory.isEmpty()) {
00703       /* We are using the standard launcher, exit immediately */
00704       vApp->quit();
00705     } else {
00706       /* We are using the alternate launcher, wait until the browser has really died */
00707       QTimer *browserWatcher = new QTimer(this);
00708       connect(browserWatcher, SIGNAL(timeout()), this, SLOT(onCheckForBrowser()));
00709       browserWatcher->start(2000);
00710     }
00711   }
00712 }
00713 
00714 /** Called periodically to check if the browser is running. If it is not,
00715  * exit Vidalia cleanly */
00716 void
00717 MainWindow::onCheckForBrowser()
00718 {
00719 /* This only works on Windows for now */
00720 #if defined(Q_OS_WIN)
00721 
00722   VidaliaSettings settings;
00723   QString browserDirectoryFilename = settings.getBrowserExecutable();
00724 
00725   /* Get list of running processes */
00726   QHash<qint64, QString> procList = win32_process_list();
00727 
00728   /* On old versions of Windows win32_process_list() will return
00729      an empty list. In this case, just keep Vidalia open */
00730   if (procList.isEmpty()) {
00731     return;
00732   }
00733 
00734   /* Loop over all processes or until we find <browserDirectoryFilename> */
00735   QHashIterator<qint64, QString> i(procList);
00736   while (i.hasNext()) {
00737     i.next();
00738     if (i.value().toLower() == browserDirectoryFilename) {
00739       /* The browser is still running, so Vidalia should keep running too */
00740       return;
00741     }
00742   }
00743 
00744   /* The browser isn't running, exit Vidalia */
00745   vApp->quit();
00746 #endif  
00747 }
00748 
00749 /** Called when the web browser failed to start, for example, because the path
00750  * specified to the web browser executable didn't lead to an executable. */
00751 void
00752 MainWindow::onBrowserFailed(QString errmsg)
00753 {
00754   Q_UNUSED(errmsg);
00755  
00756   /* Display an error message and see if the user wants some help */
00757   VMessageBox::warning(this, tr("Error starting web browser"),
00758               tr("Vidalia was unable to start the configured web browser"),
00759               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00760 }
00761 
00762 /** Called when the IM client failed to start, for example, because the path
00763  * specified to the IM client executable didn't lead to an executable. */
00764 void
00765 MainWindow::onIMFailed(QString errmsg)
00766 {
00767   Q_UNUSED(errmsg);
00768  
00769   /* Display an error message and see if the user wants some help */
00770   VMessageBox::warning(this, tr("Error starting IM client"),
00771               tr("Vidalia was unable to start the configured IM client"),
00772               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00773 }
00774 
00775 /** Starts the proxy server, if appropriately configured */
00776 void
00777 MainWindow::startProxy()
00778 {
00779   VidaliaSettings settings;
00780   QString executable = settings.getProxyExecutable();
00781   _proxyProcess->start(executable, settings.getProxyExecutableArguments());
00782 }
00783 
00784 /** Called when the proxy server fails to start, for example, because
00785  * the path specified didn't lead to an executable. */
00786 void
00787 MainWindow::onProxyFailed(QString errmsg)
00788 {
00789   Q_UNUSED(errmsg);
00790  
00791   /* Display an error message and see if the user wants some help */
00792   VMessageBox::warning(this, tr("Error starting proxy server"),
00793               tr("Vidalia was unable to start the configured proxy server"),
00794               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00795 }
00796 
00797 /** Called when Tor's bootstrapping status changes. <b>bse</b> represents
00798  * Tor's current estimate of its bootstrapping progress. */
00799 void
00800 MainWindow::bootstrapStatusChanged(const BootstrapStatus &bs)
00801 {
00802   int percentComplete = STARTUP_PROGRESS_BOOTSTRAPPING + bs.percentComplete();
00803   bool warn = (bs.severity() == tc::WarnSeverity && 
00804                bs.recommendedAction() != BootstrapStatus::RecommendIgnore);
00805 
00806   QString description;
00807   switch (bs.status()) {
00808     case BootstrapStatus::ConnectingToDirMirror:
00809       description = tr("Connecting to a relay directory");
00810       break;
00811     case BootstrapStatus::HandshakingWithDirMirror:
00812     case BootstrapStatus::CreatingOneHopCircuit:
00813       description = tr("Establishing an encrypted directory connection");
00814       break;
00815     case BootstrapStatus::RequestingNetworkStatus:
00816       description = tr("Retrieving network status");
00817       break;
00818     case BootstrapStatus::LoadingNetworkStatus:
00819       description = tr("Loading network status");
00820       break;
00821     case BootstrapStatus::LoadingAuthorityCertificates:
00822       description = tr("Loading authority certificates");
00823       break;
00824     case BootstrapStatus::RequestingDescriptors:
00825       description = tr("Requesting relay information");
00826       break;
00827     case BootstrapStatus::LoadingDescriptors:
00828       description = tr("Loading relay information");
00829       break;
00830     case BootstrapStatus::ConnectingToEntryGuard:
00831       description = tr("Connecting to the Tor network");
00832       break;
00833     case BootstrapStatus::HandshakingWithEntryGuard:
00834     case BootstrapStatus::EstablishingCircuit:
00835       description = tr("Establishing a Tor circuit");
00836       break;
00837     case BootstrapStatus::BootstrappingDone:
00838       description = tr("Connected to the Tor network!");
00839       warn = false; /* probably false anyway */
00840       break;
00841     default:
00842       description = tr("Unrecognized startup status");
00843   }
00844   if (warn) {
00845     QString reason;
00846     /* Is it really a good idea to translate these? */
00847     switch (bs.reason()) {
00848       case tc::MiscellaneousReason:
00849         reason = tr("miscellaneous");
00850         break;
00851       case tc::IdentityMismatch:
00852         reason = tr("identity mismatch");
00853         break;
00854       case tc::ConnectionDone:
00855         reason = tr("done");
00856         break;
00857       case tc::ConnectionRefused:
00858         reason = tr("connection refused");
00859         break;
00860       case tc::ConnectionTimeout:
00861         reason = tr("connection timeout");
00862         break;
00863       case tc::ConnectionIoError:
00864         reason = tr("read/write error");
00865         break;
00866       case tc::NoRouteToHost:
00867         reason = tr("no route to host");
00868         break;
00869       case tc::ResourceLimitReached:
00870         reason = tr("insufficient resources");
00871         break;
00872       default:
00873         reason = tr("unknown");
00874     }
00875     description += tr(" failed (%1)").arg(reason);
00876   }
00877   setStartupProgress(percentComplete, description);
00878 }
00879 
00880 /** Updates the UI to reflect Tor's current <b>status</b>. Returns the
00881  * previously set TorStatus value.*/
00882 MainWindow::TorStatus
00883 MainWindow::updateTorStatus(TorStatus status)
00884 {
00885   QString statusText, actionText;
00886   QString trayIconFile, statusIconFile;
00887   TorStatus prevStatus = _status;
00888  
00889   vNotice("Tor status changed from '%1' to '%2'.")
00890     .arg(toString(prevStatus)).arg(toString(status));
00891   _status = status;
00892 
00893   if (status == Stopped) {
00894       statusText = tr("Tor is not running");
00895       actionText = tr("Start Tor");
00896       trayIconFile = IMG_TOR_STOPPED;
00897       statusIconFile = IMG_TOR_STOPPED_48;
00898       _actionStartStopTor->setEnabled(true);
00899       _actionStartStopTor->setText(actionText);
00900       _actionStartStopTor->setIcon(QIcon(IMG_START_TOR_16));
00901       ui.lblStartStopTor->setEnabled(true);
00902       ui.lblStartStopTor->setText(actionText);
00903       ui.lblStartStopTor->setPixmap(QPixmap(IMG_START_TOR_48));
00904       ui.lblStartStopTor->setStatusTip(actionText);
00905 
00906       /* XXX: This might need to be smarter if we ever start connecting other
00907        * slots to these triggered() and clicked() signals. */
00908       QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
00909       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00910       connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(start()));
00911       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(start()));
00912       setStartupProgressVisible(false);
00913   } else if (status == Stopping) {
00914       if (_delayedShutdownStarted) {
00915         statusText = tr("Your relay is shutting down.\n" 
00916                         "Click 'Stop' again to stop your relay now.");
00917       } else {
00918         statusText = tr("Tor is shutting down");
00919       }
00920       trayIconFile = IMG_TOR_STOPPING;
00921       statusIconFile = IMG_TOR_STOPPING_48;
00922       
00923       ui.lblStartStopTor->setStatusTip(tr("Stop Tor Now"));
00924   } else if (status == Started) {
00925       actionText = tr("Stop Tor");
00926       _actionStartStopTor->setEnabled(true);
00927       _actionStartStopTor->setText(actionText);
00928       _actionStartStopTor->setIcon(QIcon(IMG_STOP_TOR_16));
00929       ui.lblStartStopTor->setEnabled(true);
00930       ui.lblStartStopTor->setText(actionText);
00931       ui.lblStartStopTor->setPixmap(QPixmap(IMG_STOP_TOR_48));
00932       ui.lblStartStopTor->setStatusTip(actionText);
00933             
00934       /* XXX: This might need to be smarter if we ever start connecting other
00935        * slots to these triggered() and clicked() signals. */
00936       QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
00937       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00938       connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(stop()));
00939       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(stop()));
00940   } else if (status == Starting)  {
00941       statusText = tr("Starting the Tor software");
00942       trayIconFile = IMG_TOR_STARTING;
00943       statusIconFile = IMG_TOR_STARTING_48;
00944       _actionStartStopTor->setEnabled(false);
00945       ui.lblStartStopTor->setText(tr("Starting Tor"));
00946       ui.lblStartStopTor->setEnabled(false);
00947       ui.lblStartStopTor->setStatusTip(statusText);
00948       setStartupProgressVisible(true);
00949       setStartupProgress(STARTUP_PROGRESS_STARTING, statusText);
00950   } else if (status == CircuitEstablished) {
00951       statusText = tr("Connected to the Tor network!");
00952       trayIconFile = IMG_TOR_RUNNING;
00953       statusIconFile = IMG_TOR_RUNNING_48;
00954       setStartupProgressVisible(false);
00955   }
00956 
00957   /* Update the tray icon */
00958   if (!trayIconFile.isEmpty()) {
00959     setTrayIcon(trayIconFile);
00960   }
00961   /* Update the status banner on the control panel */
00962   if (!statusIconFile.isEmpty())
00963     ui.lblTorStatusImg->setPixmap(QPixmap(statusIconFile));
00964   if (!statusText.isEmpty()) {
00965     _trayIcon.setToolTip(statusText);
00966     ui.lblTorStatus->setText(statusText);
00967   }
00968   return prevStatus;
00969 }
00970 
00971 /** Called when the "show on startup" checkbox is toggled. */
00972 void
00973 MainWindow::toggleShowOnStartup(bool checked)
00974 {
00975   VidaliaSettings settings;
00976   settings.setShowMainWindowAtStart(checked);
00977 }
00978 
00979 /** Sets the visibility of the startup status description and progress bar to
00980  * <b>visible</b>. */
00981 void
00982 MainWindow::setStartupProgressVisible(bool visible)
00983 {
00984   /* XXX: We force a repaint() to make sure the progress bar and onion status
00985    * icon don't overlap briefly. This is pretty hacktastic. */
00986   if (visible) {
00987     ui.lblTorStatus->setVisible(false);
00988     ui.lblTorStatusImg->setVisible(false);
00989     repaint(ui.grpStatus->rect());
00990     ui.lblStartupProgress->setVisible(true);
00991     ui.progressBar->setVisible(true);
00992   } else {
00993     ui.lblStartupProgress->setVisible(false);
00994     ui.progressBar->setVisible(false);
00995     repaint(ui.grpStatus->rect());
00996     ui.lblTorStatus->setVisible(true);
00997     ui.lblTorStatusImg->setVisible(true);
00998   }
00999 }
01000 
01001 /** Sets the progress bar completion value to <b>progressValue</b> and sets
01002  * the status text to <b>description</b>. */
01003 void
01004 MainWindow::setStartupProgress(int progressValue,
01005                                const QString &description)
01006 {
01007   ui.progressBar->setValue(progressValue);
01008   ui.lblStartupProgress->setText(description);
01009   _trayIcon.setToolTip(description);
01010 }
01011 
01012 /** Attempts to start Tor. If Tor fails to start, then startFailed() will be
01013  * called with an error message containing the reason. */
01014 void 
01015 MainWindow::start()
01016 {
01017   TorSettings settings;
01018   QStringList args;
01019 
01020   updateTorStatus(Starting);
01021 
01022   // Disable autoconfiguration if there are missing config data
01023   if(settings.autoControlPort()) {
01024     if(settings.getDataDirectory().isEmpty()) {
01025       vWarn("Disabling ControlPort autoconfiguration. DataDirectory is empty!");
01026       settings.setAutoControlPort(false);
01027     }
01028   }
01029 
01030   /* Check if Tor is already running separately */
01031   if(settings.getControlMethod() == ControlMethod::Port) {
01032     if(!settings.autoControlPort() && net_test_connect(settings.getControlAddress(),
01033                                                        settings.getControlPort())) {
01034       started();
01035       return;
01036     }
01037   } else {
01038     if (socket_test_connect(settings.getSocketPath())) {
01039       started();
01040       return;
01041     }
01042   }
01043 
01044   QString torrc = settings.getTorrc();
01045 
01046   if(settings.bootstrap()) {
01047     QString boottorrc = settings.bootstrapFrom();
01048     vNotice(tr("Bootstrapping torrc from %1 to %2")
01049         .arg(boottorrc).arg(torrc));
01050     if(QFileInfo(boottorrc).exists()) {
01051       if(QFile::copy(boottorrc, torrc)) {
01052         settings.setBootstrap(false);
01053       }
01054     }
01055   }
01056 
01057   /* Make sure the torrc we want to use really exists. */
01058   if (!torrc.isEmpty()) {
01059     if (!QFileInfo(torrc).exists())
01060       touch_file(torrc, true);
01061     args << "-f" << torrc;
01062   }
01063 
01064   /* Specify Tor's data directory, if different from the default */
01065   QString dataDirectory = settings.getDataDirectory();
01066   QString expDataDirectory = expand_filename(dataDirectory);
01067   if (!dataDirectory.isEmpty())
01068     args << "DataDirectory" << expDataDirectory;
01069   
01070   if(settings.getControlMethod() == ControlMethod::Port) {
01071     if(settings.autoControlPort()) {
01072       QString portconf = QString("%1/port.conf").arg(expDataDirectory);
01073       if(!QFile::remove(portconf))
01074         vWarn(QString("Unable to remove %s, may be it didn't existed.").arg(portconf));
01075 
01076       args << "ControlPort" << "auto";
01077       args << "SocksPort" << "auto";
01078       args << "ControlPortWriteToFile" << portconf;
01079     } else {
01080       /* Add the intended control port value */
01081       quint16 controlPort = settings.getControlPort();
01082       if (controlPort)
01083         args << "ControlPort" << QString::number(controlPort);
01084     }
01085   } else {
01086     QString path = settings.getSocketPath();
01087     args << "ControlSocket" << path;
01088   }
01089 
01090   args << "__OwningControllerProcess" << QString::number(QCoreApplication::applicationPid());
01091   
01092   /* Add the control port authentication arguments */
01093   switch (settings.getAuthenticationMethod()) {
01094     case TorSettings::PasswordAuth:
01095       if (! vApp->readPasswordFromStdin()) {
01096         if (settings.useRandomPassword()) {
01097           _controlPassword = TorSettings::randomPassword();
01098           _useSavedPassword = false;
01099         } else {
01100           _controlPassword = settings.getControlPassword();
01101           _useSavedPassword = true;
01102         }
01103       }
01104       args << "HashedControlPassword"
01105            << TorSettings::hashPassword(_controlPassword);
01106       break;
01107     case TorSettings::CookieAuth:
01108       args << "CookieAuthentication"  << "1";
01109       break;
01110     default:
01111       args << "CookieAuthentication"  << "0";
01112   }
01113 
01114   /* This doesn't get set to false until Tor is actually up and running, so we
01115    * don't yell at users twice if their Tor doesn't even start, due to the fact
01116    * that QProcess::stopped() is emitted even if the process didn't even
01117    * start. */
01118   _isIntentionalExit = true;
01119   /* Kick off the Tor process */
01120   _torControl->start(settings.getExecutable(), args);
01121 }
01122 
01123 /** Called when the user changes a setting that needs Tor restarting */
01124 void
01125 MainWindow::restart()
01126 {
01127   if(_torControl->stop()) {
01128     start();
01129   }
01130 }
01131 
01132 /** Called when the Tor process fails to start, for example, because the path
01133  * specified to the Tor executable didn't lead to an executable. */
01134 void
01135 MainWindow::startFailed(QString errmsg)
01136 {
01137   /* We don't display the error message for now, because the error message
01138    * that Qt gives us in this instance is almost always "Unknown Error". That
01139    * will make users sad. */
01140   Q_UNUSED(errmsg);
01141  
01142   updateTorStatus(Stopped);
01143 
01144   /* Display an error message and see if the user wants some help */
01145   int response = VMessageBox::warning(this, tr("Error Starting Tor"),
01146                    tr("Vidalia was unable to start Tor. Check your settings "
01147                         "to ensure the correct name and location of your Tor "
01148                         "executable is specified."),
01149                    VMessageBox::ShowSettings|VMessageBox::Default,
01150                    VMessageBox::Cancel|VMessageBox::Escape,
01151                    VMessageBox::Help);
01152 
01153   if (response == VMessageBox::ShowSettings) {
01154     /* Show the settings dialog so the user can make sure they're pointing to
01155      * the correct Tor. */
01156      showConfigDialog();
01157   } else if (response == VMessageBox::Help) {
01158     /* Show troubleshooting information about starting Tor */
01159     showHelpDialog("troubleshooting.start");
01160   }
01161 }
01162 
01163 /** Slot: Called when the Tor process is started. It will connect the control
01164  * socket and set the icons and tooltips accordingly. */
01165 void 
01166 MainWindow::started()
01167 {
01168   TorSettings settings;
01169 
01170   updateTorStatus(Started);
01171 
01172   /* Now that Tor is running, we want to know if it dies when we didn't want
01173    * it to. */
01174   _isIntentionalExit = false;
01175   /* We haven't started a delayed shutdown yet. */
01176   _delayedShutdownStarted = false;
01177   /* Remember whether we started Tor or not */
01178   _isVidaliaRunningTor = _torControl->isVidaliaRunningTor();
01179   /* Try to connect to Tor's control port */
01180   if(settings.autoControlPort()) {
01181     QString dataDirectory = settings.getDataDirectory();
01182     QFile file(QString("%1/port.conf").arg(expand_filename(dataDirectory)));
01183     int tries = 0, maxtries = 5;
01184     while((!file.open(QIODevice::ReadOnly | QIODevice::Text)) and
01185           (tries++ < maxtries)) {
01186       vWarn(QString("This is try number: %1.").arg(tries));
01187 #if defined(Q_WS_WIN)
01188       Sleep(1000);
01189 #else
01190       sleep(1);
01191 #endif
01192     }
01193 
01194     if(tries >= maxtries) {
01195       vWarn("Couldn't read port.conf file");
01196       if(_torControl->isRunning()) {
01197         connectFailed(tr("Vidalia can't find out how to talk to Tor because it can't access this file: %1\n\nHere's the last error message:\n%2")
01198                       .arg(file.fileName())
01199                       .arg(file.errorString()));
01200       } else {
01201         vWarn("Tor isn't running!");
01202         connectFailed(tr("It seems Tor has stopped running since Vidalia started it.\n\nSee the Advanced Message Log for more information."));
01203       }
01204 
01205       return;
01206     }
01207 
01208     QTextStream in(&file);
01209     if(!in.atEnd()) {
01210       QString line = in.readLine();
01211       QStringList parts = line.split("=");
01212       if(parts.size() != 2) return;
01213       if(parts[0].trimmed() != "PORT") return;
01214 
01215       QStringList addrPort = parts[1].split(":");
01216       if(addrPort.size() != 2) return;
01217 
01218       QHostAddress addr(addrPort.at(0));
01219       _autoControlPort = addrPort.at(1).toInt();
01220       _torControl->connect(addr, _autoControlPort);
01221     }
01222 
01223     file.close();
01224   } else {
01225     /* Try to connect to Tor's control port */
01226     if(settings.getControlMethod() == ControlMethod::Port) {
01227       _torControl->connect(settings.getControlAddress(),
01228                           settings.getControlPort());
01229       _autoControlPort = settings.getControlPort();
01230     } else
01231       _torControl->connect(settings.getSocketPath());
01232   }
01233   setStartupProgress(STARTUP_PROGRESS_CONNECTING, tr("Connecting to Tor"));
01234 }
01235 
01236 /** Called when the connection to the control socket fails. The reason will be
01237  * given in the errmsg parameter. */
01238 void
01239 MainWindow::connectFailed(QString errmsg)
01240 {
01241   /* Ok, ok. It really isn't going to connect. I give up. */
01242   int response = VMessageBox::warning(this, 
01243                    tr("Connection Error"), p(errmsg),
01244                    VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01245                    VMessageBox::Retry, VMessageBox::Help);
01246 
01247 
01248   if (response == VMessageBox::Retry) {
01249     /* Let's give it another try. */
01250     TorSettings settings;
01251     _torControl->connect(settings.getControlAddress(),
01252                          settings.getControlPort());
01253   } else {
01254     /* Show the help browser (if requested) */
01255     if (response == VMessageBox::Help)
01256       showHelpDialog("troubleshooting.connect");
01257     /* Since Vidalia can't connect, we can't really do much, so stop Tor. */
01258     _torControl->stop();
01259   }
01260 }
01261 
01262 /** Disconnects the control socket and stops the Tor process. */
01263 bool
01264 MainWindow::stop()
01265 {
01266   ServerSettings server(_torControl);
01267   QString errmsg;
01268   TorStatus prevStatus;
01269   bool rc;
01270 
01271   /* If we're running a server, give users the option of terminating
01272    * gracefully so clients have time to find new servers. */
01273   if (server.isServerEnabled() && !_delayedShutdownStarted) {
01274     /* Ask the user if they want to shutdown nicely. */
01275     int response = VMessageBox::question(this, tr("Relaying is Enabled"),
01276                      tr("You are currently running a relay. "
01277                         "Terminating your relay will interrupt any "
01278                         "open connections from clients.\n\n"
01279                         "Would you like to shutdown gracefully and "
01280                         "give clients time to find a new relay?"),
01281                         VMessageBox::Yes|VMessageBox::Default, 
01282                         VMessageBox::No, 
01283                         VMessageBox::Cancel|VMessageBox::Escape);
01284     if (response == VMessageBox::Yes)
01285       _delayedShutdownStarted = true;
01286     else if (response == VMessageBox::Cancel)
01287       return false;
01288   }
01289   
01290   prevStatus = updateTorStatus(Stopping);  
01291   if (_delayedShutdownStarted) {
01292     /* Start a delayed shutdown */
01293     rc = _torControl->signal(TorSignal::Shutdown, &errmsg);
01294   } else {
01295     /* We want Tor to stop now, regardless of whether we're a server. */
01296     _isIntentionalExit = true;
01297     rc = _torControl->stop(&errmsg);
01298   }
01299   
01300   if (!rc) {
01301     /* We couldn't tell Tor to stop, for some reason. */
01302     int response = VMessageBox::warning(this, tr("Error Shutting Down"),
01303                      p(tr("Vidalia was unable to stop the Tor software.")) 
01304                        + p(errmsg),
01305                      VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01306                      VMessageBox::Help);
01307       
01308     if (response == VMessageBox::Help) {
01309       /* Show some troubleshooting help */
01310       showHelpDialog("troubleshooting.stop");
01311     }
01312     /* Tor is still running since stopping failed */
01313     _isIntentionalExit = false;
01314     _delayedShutdownStarted = false;
01315     updateTorStatus(prevStatus);
01316   }
01317   return rc;
01318 }
01319 
01320 /** Slot: Called when the Tor process has exited. It will adjust the tray
01321  * icons and tooltips accordingly. */
01322 void 
01323 MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus)
01324 {
01325   updateTorStatus(Stopped);
01326 
01327   /* If we didn't intentionally close Tor, then check to see if it crashed or
01328    * if it closed itself and returned an error code. */
01329   if (!_isIntentionalExit) {
01330     /* A quick overview of Tor's code tells me that if it catches a SIGTERM or
01331      * SIGINT, Tor will exit(0). We might need to change this warning message
01332      * if this turns out to not be the case. */
01333     if (exitStatus == QProcess::CrashExit || exitCode != 0) {
01334       int ret = VMessageBox::warning(this, tr("Unexpected Error"),
01335                   tr("Vidalia detected that the Tor software exited "
01336                      "unexpectedly.\n\n"
01337                      "Please check the message log for recent "
01338                      "warning or error messages."),
01339                   VMessageBox::Ok|VMessageBox::Escape, 
01340                   VMessageBox::ShowLog|VMessageBox::Default,
01341                   VMessageBox::Help);
01342       if (ret == VMessageBox::ShowLog)
01343         _messageLog->showWindow();  
01344       else if (ret == VMessageBox::Help)
01345         showHelpDialog("troubleshooting.torexited");
01346     }
01347   }
01348 }
01349 
01350 /** Called when the control socket has successfully connected to Tor. */
01351 void
01352 MainWindow::connected()
01353 {
01354   authenticate();
01355   if(_torControl->isVidaliaRunningTor()) {
01356     QString err;
01357     if(!_torControl->takeOwnership(&err))
01358       vWarn(err);
01359   }
01360 }
01361 
01362 /** Called when Vidalia wants to disconnect from a Tor it did not start. */
01363 void
01364 MainWindow::disconnect()
01365 {
01366   _torControl->disconnect();
01367 }
01368 
01369 /** Called when the control socket has been disconnected. */
01370 void
01371 MainWindow::disconnected()
01372 {
01373   if (!_isVidaliaRunningTor) {
01374     /* If we didn't start our own Tor process, interpret losing the control
01375      * connection as "Tor is stopped". */
01376     updateTorStatus(Stopped);
01377   }
01378   
01379   /*XXX We should warn here if we get disconnected when we didn't intend to */
01380   _actionNewIdentity->setEnabled(false);
01381   ui.lblNewIdentity->setEnabled(false);
01382   _isVidaliaRunningTor = false;
01383 }
01384 
01385 /** Attempts to authenticate to Tor's control port, depending on the
01386  * authentication method specified in TorSettings::getAuthenticationMethod().
01387  */
01388 bool
01389 MainWindow::authenticate()
01390 {
01391   TorSettings::AuthenticationMethod authMethod;
01392   TorSettings settings;
01393   ProtocolInfo pi;
01394   
01395   updateTorStatus(Authenticating);
01396   setStartupProgress(STARTUP_PROGRESS_AUTHENTICATING,
01397                      tr("Authenticating to Tor"));
01398 
01399   authMethod = settings.getAuthenticationMethod(); 
01400   pi = _torControl->protocolInfo();
01401   if (!pi.isEmpty()) {
01402     QStringList authMethods = pi.authMethods();
01403     if (authMethods.contains("COOKIE"))
01404       authMethod = TorSettings::CookieAuth;
01405     else if (authMethods.contains("HASHEDPASSWORD"))
01406       authMethod = TorSettings::PasswordAuth;
01407     else if (authMethods.contains("NULL"))
01408       authMethod = TorSettings::NullAuth;
01409   }
01410   
01411   if (authMethod == TorSettings::CookieAuth) {
01412     /* Try to load an auth cookie and send it to Tor */
01413     QByteArray cookie = loadControlCookie(pi.cookieAuthFile());
01414     while (cookie.isEmpty()) {
01415       /* Prompt the user to find their control_auth_cookie */
01416       int ret = VMessageBox::question(this,
01417                   tr("Cookie Authentication Required"),
01418                   p(tr("The Tor software requires Vidalia to send the "
01419                        "contents of an authentication cookie, but Vidalia "
01420                        "was unable to find one."))
01421                   + p(tr("Would you like to browse for the file "
01422                          "'control_auth_cookie' yourself?")),
01423                 VMessageBox::Browse|VMessageBox::Default,
01424                 VMessageBox::Cancel|VMessageBox::Escape);
01425       
01426       if (ret == VMessageBox::Cancel)
01427         goto cancel;
01428       QString cookieDir = QFileDialog::getOpenFileName(this,
01429                             tr("Data Directory"),
01430                             settings.getDataDirectory(),
01431                             tr("Control Cookie (control_auth_cookie)"));
01432       if (cookieDir.isEmpty())
01433         goto cancel;
01434       cookieDir = QFileInfo(cookieDir).absolutePath();
01435       cookie = loadControlCookie(cookieDir);
01436     }
01437     if(cookie.size() != 32) {
01438       vWarn(QString("Cookie length has to be exactly 32 bytes long. Found %s bytes")
01439             .arg(cookie.size()));
01440       goto cancel;
01441     }
01442     vNotice("Authenticating using 'cookie' authentication.");
01443     return _torControl->authenticate(cookie);
01444   } else if (authMethod == TorSettings::PasswordAuth) {
01445     /* Get the control password and send it to Tor */
01446     vNotice("Authenticating using 'hashed password' authentication.");
01447     if (_useSavedPassword) {
01448       TorSettings settings;
01449       _controlPassword = settings.getControlPassword();
01450     }
01451     return _torControl->authenticate(_controlPassword);
01452   }
01453   /* No authentication. Send an empty password. */
01454   vNotice("Authenticating using 'null' authentication.");
01455   return _torControl->authenticate(QString(""));
01456 
01457 cancel:
01458   vWarn("Cancelling control authentication attempt.");
01459   if (_isVidaliaRunningTor)
01460     stop();
01461   else
01462     disconnect();
01463   return false;
01464 }
01465 
01466 /** Called when Vidalia has successfully authenticated to Tor. */
01467 void
01468 MainWindow::authenticated()
01469 {
01470   ServerSettings serverSettings(_torControl);
01471   QString errmsg;
01472 
01473   updateTorStatus(Authenticated);
01474   
01475   /* If Tor doesn't have bootstrapping events, then update the current
01476    * status string and bump the progress bar along a bit. */
01477   if (_torControl->getTorVersion() < 0x020101) {
01478     setStartupProgress(STARTUP_PROGRESS_CIRCUITBUILD,
01479                        tr("Connecting to the Tor network"));
01480   }
01481   
01482   /* Let people click on their beloved "New Identity" button */
01483   _actionNewIdentity->setEnabled(true);
01484   ui.lblNewIdentity->setEnabled(true);
01485 
01486   /* Register for any pertinent asynchronous events. */
01487   if (!_torControl->setEvents(&errmsg)) {
01488     VMessageBox::warning(this, tr("Error Registering for Events"),
01489       p(tr("Vidalia was unable to register for some events. "
01490            "Many of Vidalia's features may be unavailable."))
01491          + p(errmsg),
01492       VMessageBox::Ok);
01493   } else {
01494     /* Stop reading from Tor's stdout immediately, since we successfully
01495      * registered for Tor events, including any desired log events. */
01496     _torControl->closeTorStdout();
01497   }
01498 
01499   /* Configure UPnP port forwarding if needed */
01500   serverSettings.configurePortForwarding();
01501 
01502   /* Check if Tor has a circuit established */
01503   if (_torControl->isCircuitEstablished())
01504     circuitEstablished();
01505   /* Check the status of Tor's version */
01506   if (_torControl->getTorVersion() >= 0x020001)
01507     checkTorVersion();
01508   if (_torControl->getTorVersion() >= 0x020102) {
01509     BootstrapStatus status = _torControl->bootstrapStatus();
01510     if (status.isValid())
01511       bootstrapStatusChanged(status);
01512   }
01513 }
01514 
01515 /** Called when Vidalia fails to authenticate to Tor. The failure reason is
01516  * specified in <b>errmsg</b>. */
01517 void
01518 MainWindow::authenticationFailed(QString errmsg)
01519 {
01520   bool retry = false;
01521   
01522   vWarn("Authentication failed: %1").arg(errmsg);
01523 
01524   /* Parsing log messages is evil, but we're left with little option */
01525   if (errmsg.contains("Password did not match")) {
01526     ControlPasswordInputDialog dlg;
01527     connect(&dlg, SIGNAL(helpRequested(QString)),
01528             this, SLOT(showHelpDialog(QString)));
01529 
01530     qint64 torPid = 0;
01531 
01532 #if defined(Q_OS_WIN32)
01533     QHash<qint64, QString> procs = process_list();
01534     foreach (qint64 pid, procs.keys()) {
01535       if (! procs.value(pid).compare("tor.exe", Qt::CaseInsensitive)) {
01536         torPid = pid;
01537         break;
01538       }
01539     }
01540     dlg.setResetEnabled(torPid > 0);
01541 #else
01542     dlg.setResetEnabled(false);
01543 #endif
01544 
01545     int ret = dlg.exec();
01546     if (ret == QDialogButtonBox::Ok) {
01547       if (dlg.isSavePasswordChecked()) {
01548         TorSettings settings;
01549         settings.setAuthenticationMethod(TorSettings::PasswordAuth);
01550         settings.setUseRandomPassword(false);
01551         settings.setControlPassword(dlg.password());
01552         _useSavedPassword = true;
01553       } else {
01554         _controlPassword = dlg.password();
01555         _useSavedPassword = false;
01556       }
01557       retry = true;
01558     } else if (ret == QDialogButtonBox::Reset) {
01559       if (! process_kill(torPid)) {
01560         VMessageBox::warning(this,
01561           tr("Password Reset Failed"),
01562           p(tr("Vidalia tried to reset Tor's control password, but was not "
01563                "able to restart the Tor software. Please check your Task "
01564                "Manager to ensure there are no other Tor processes running.")),
01565                VMessageBox::Ok|VMessageBox::Default);
01566       } else {
01567         retry = true;
01568       }
01569     }
01570   } else {
01571     /* Something else went wrong */
01572     int ret = VMessageBox::warning(this, 
01573                 tr("Authentication Error"),
01574                 p(tr("Vidalia was unable to authenticate to the Tor software. "
01575                      "(%1)").arg(errmsg)) + 
01576                 p(tr("Please check your control port authentication "
01577                      "settings.")),
01578                 VMessageBox::ShowSettings|VMessageBox::Default,
01579                 VMessageBox::Cancel|VMessageBox::Escape);
01580     
01581     if (ret == VMessageBox::ShowSettings)
01582       showConfigDialog(ConfigDialog::Advanced);
01583   }
01584   
01585   if (_torControl->isRunning())
01586     if (_isVidaliaRunningTor) 
01587       stop();
01588     else
01589       disconnect();
01590   if (retry)
01591     start();
01592 }
01593 
01594 /** Searches for and attempts to load the control authentication cookie. This
01595  * assumes the cookie is named 'control_auth_cookie'. If <b>cookiePath</b> is
01596  * empty, this method will search some default locations depending on the
01597  * current platform. <b>cookiePath</b> can point to either a cookie file or a
01598  * directory containing the cookie file. */
01599 QByteArray
01600 MainWindow::loadControlCookie(QString cookiePath)
01601 {
01602   QFile authCookie;
01603   QStringList pathList;
01604 
01605   if (!cookiePath.isEmpty()) {
01606     pathList << cookiePath;
01607   } else {
01608     /* Try some default locations */
01609     TorSettings settings;
01610     QString dataDir = settings.getDataDirectory();
01611     if (!dataDir.isEmpty())
01612       pathList << dataDir;
01613       
01614 #if defined(Q_WS_WIN)
01615     pathList << expand_filename("%APPDATA%\\Tor");
01616 #else
01617     pathList << expand_filename("~/.tor");
01618 #endif
01619   }
01620   
01621   /* Search for the cookie file */
01622   foreach (QString path, pathList) {
01623     QString cookieFile = QFileInfo(path).isFile() ?
01624                           path : path + "/control_auth_cookie";
01625     vDebug("Checking for authentication cookie in '%1'").arg(cookieFile);
01626     if (!QFileInfo(cookieFile).exists())
01627       continue;
01628     
01629     authCookie.setFileName(cookieFile);
01630     if (authCookie.open(QIODevice::ReadOnly)) {
01631       vInfo("Reading authentication cookie from '%1'").arg(cookieFile);
01632       return authCookie.readAll();
01633     } else {
01634       vWarn("Couldn't open cookie file '%1': %2")
01635         .arg(cookieFile).arg(authCookie.errorString());
01636     }
01637   }
01638   vWarn("Couldn't find a readable authentication cookie.");
01639   return QByteArray();
01640 }
01641 
01642 /** Called when Tor has successfully established a circuit. */
01643 void
01644 MainWindow::circuitEstablished()
01645 {
01646   updateTorStatus(CircuitEstablished);
01647   setStartupProgress(ui.progressBar->maximum(),
01648                      tr("Connected to the Tor network!"));
01649   startSubprocesses();
01650 
01651 #if defined(USE_AUTOUPDATE)
01652   VidaliaSettings settings;
01653   if (settings.isAutoUpdateEnabled()) {
01654     QDateTime lastCheckedAt = settings.lastCheckedForUpdates();
01655     if (UpdateProcess::shouldCheckForUpdates(lastCheckedAt)) {
01656       /* Initiate a background check for updates now */
01657       _updateTimer.stop();
01658       checkForUpdates();
01659     }
01660   }
01661 #endif
01662 }
01663 
01664 /** Checks the status of the current version of Tor to see if it's old,
01665  * unrecommended, or obsolete. */
01666 void
01667 MainWindow::checkTorVersion()
01668 {
01669   VidaliaSettings settings;
01670   if(settings.skipVersionCheck())
01671     return;
01672   QString status;
01673   if (_torControl->getInfo("status/version/current", status)) {
01674     if (!status.compare("old", Qt::CaseInsensitive)
01675           || !status.compare("unrecommended", Qt::CaseInsensitive)
01676           || !status.compare("obsolete", Qt::CaseInsensitive)) {
01677       displayTorVersionWarning();
01678     }
01679   }
01680 }
01681 
01682 /** Called when Tor thinks its version is old or unrecommended, and displays
01683  * a message notifying the user. */
01684 void
01685 MainWindow::dangerousTorVersion(tc::TorVersionStatus reason,
01686                                 const QString &current,
01687                                 const QStringList &recommended)
01688 {
01689   Q_UNUSED(current);
01690   Q_UNUSED(recommended);
01691 
01692   if (reason == tc::ObsoleteTorVersion
01693         || reason == tc::UnrecommendedTorVersion)
01694     displayTorVersionWarning();
01695 }
01696 
01697 /** Called when Tor thinks its version is old or unrecommended, and displays a
01698  * message notifying the user. */
01699 void
01700 MainWindow::displayTorVersionWarning()
01701 {
01702   static bool alreadyWarned = false;
01703 
01704   if (!alreadyWarned) {
01705 #if !defined(USE_AUTOUPDATE)
01706     QString website = "https://www.torproject.org/";
01707 # if QT_VERSION >= 0x040200
01708     website = QString("<a href=\"%1\">%1</a>").arg(website);
01709 # endif
01710 
01711     VMessageBox::information(this, tr("Tor Update Available"),
01712       p(tr("The currently installed version of Tor is out of date or no longer "
01713            "recommended. Please visit the Tor website to download the latest "
01714            "version.")) + p(tr("Tor website: %1").arg(website)),
01715       VMessageBox::Ok);
01716 #else
01717     int ret = VMessageBox::information(this,
01718                 tr("Tor Update Available"),
01719                 p(tr("The currently installed version of Tor is out of date "
01720                      "or no longer recommended."))
01721                   + p(tr("Would you like to check if a newer package is "
01722                          "available for installation?")),
01723                 VMessageBox::Yes|VMessageBox::Default,
01724                 VMessageBox::No|VMessageBox::Escape);
01725 
01726     if (ret == VMessageBox::Yes)
01727       checkForUpdatesWithUi();
01728 #endif
01729     alreadyWarned = true;
01730   }
01731 }
01732 
01733 /** Called when Tor thinks the user has tried to connect to a port that
01734  * typically is used for unencrypted applications. Warns the user and allows
01735  * them to ignore future warnings on <b>port</b>. It is possible that Tor
01736  * will produce multiple asynchronous status events warning of dangerous ports
01737  * while the message box is displayed (for example, while the user is away
01738  * from the keyboard), so subsequent messages will be discarded until the
01739  * first message box is dismissed. */
01740 void
01741 MainWindow::warnDangerousPort(quint16 port, bool rejected)
01742 {
01743   static QMessageBox *dlg = 0;
01744 
01745   /* Don't display another message box until the first one is dismissed */
01746   if (dlg)
01747     return;
01748 
01749   QString application;
01750   switch (port) {
01751     case  23:
01752      application = tr("(probably Telnet)");
01753      break;
01754 
01755     case 109:
01756     case 110:
01757     case 143:
01758       application = tr("(probably an email client)"); 
01759       break;
01760 
01761     default:
01762       application = "";
01763   }
01764 
01765   QString text = tr("One of your applications %1 appears to be making a "
01766                     "potentially unencrypted and unsafe connection to port %2.")
01767                     .arg(application).arg(port);
01768 
01769   QString extraText = p(tr("Anything sent over this connection could be "
01770                            "monitored. Please check your application's "
01771                            "configuration and use only encrypted protocols, "
01772                            "such as SSL, if possible."));
01773   if (rejected) {
01774     extraText.append(p(tr("Tor has automatically closed your connection in "
01775                           "order to protect your anonymity.")));
01776   }
01777 
01778   dlg = new QMessageBox(QMessageBox::Warning,
01779                         tr("Potentially Unsafe Connection"), text,
01780                         QMessageBox::Ok | QMessageBox::Ignore);
01781   dlg->setInformativeText(extraText);
01782   dlg->setDefaultButton(QMessageBox::Ok);
01783   dlg->setEscapeButton(QMessageBox::Ok);
01784 
01785   int ret = dlg->exec();
01786   if (ret == QMessageBox::Ignore) {
01787     TorControl *tc = Vidalia::torControl();
01788     TorSettings settings;
01789     QStringList portList;
01790     QList<quint16> ports;
01791     int idx;
01792 
01793     ports = settings.getWarnPlaintextPorts();
01794     idx   = ports.indexOf(port);
01795     if (idx >= 0) {
01796       ports.removeAt(idx);
01797       settings.setWarnPlaintextPorts(ports);
01798 
01799       foreach (quint16 port, ports) {
01800         portList << QString::number(port);
01801       }
01802       tc->setConf("WarnPlaintextPorts", portList.join(","));
01803       portList.clear();
01804     }
01805 
01806     ports = settings.getRejectPlaintextPorts();
01807     idx   = ports.indexOf(port);
01808     if (idx >= 0) {
01809       ports.removeAt(idx);
01810       settings.setRejectPlaintextPorts(ports);
01811 
01812       foreach (quint16 port, ports) {
01813         portList << QString::number(port);
01814       }
01815       tc->setConf("RejectPlaintextPorts", portList.join(","));
01816     }
01817   }
01818   delete dlg;
01819   dlg = 0;
01820 }
01821 
01822 /** Creates and displays Vidalia's About dialog. */
01823 void
01824 MainWindow::showAboutDialog()
01825 {
01826   AboutDialog dlg(this);
01827   dlg.exec();
01828 }
01829 
01830 /** Displays the help browser and displays the most recently viewed help
01831  * topic. */
01832 void
01833 MainWindow::showHelpDialog()
01834 {
01835   showHelpDialog(QString());
01836 }
01837 
01838 /**< Shows the help browser and displays the given help <b>topic</b>. */
01839 void
01840 MainWindow::showHelpDialog(const QString &topic)
01841 {
01842   static HelpBrowser *helpBrowser = 0;
01843   if (!helpBrowser)
01844     helpBrowser = new HelpBrowser(this);
01845   helpBrowser->showWindow(topic);
01846 }
01847 
01848 /** Creates and displays the Configuration dialog with the current page set to
01849  * <b>page</b>. */
01850 void
01851 MainWindow::showConfigDialog(ConfigDialog::Page page)
01852 {
01853   _configDialog->showWindow(page);
01854 }
01855 
01856 /** Displays the Configuration dialog, set to the Server page. */
01857 void
01858 MainWindow::showServerConfigDialog()
01859 {
01860   showConfigDialog(ConfigDialog::Server);
01861 }
01862 
01863 /** Called when the user selects the "New Identity" action from the menu. */
01864 void
01865 MainWindow::newIdentity()
01866 {
01867   QString errmsg;
01868 
01869   /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM
01870    * is successful, we will show the result as a balloon. Otherwise, we'll 
01871    * just use a message box. */
01872   if (_torControl->signal(TorSignal::NewNym, &errmsg)) {
01873     /* NEWNYM signal was successful */
01874     QString title = tr("New Identity");
01875     QString message = tr("All subsequent connections will "
01876                          "appear to be different than your "
01877                          "old connections.");
01878 
01879     /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */
01880     _actionNewIdentity->setEnabled(false);
01881     ui.lblNewIdentity->setEnabled(false);
01882     QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, 
01883                        this, SLOT(enableNewIdentity()));
01884 
01885     if (QSystemTrayIcon::supportsMessages())
01886       _trayIcon.showMessage(title, message, QSystemTrayIcon::Information);
01887     else
01888       VMessageBox::information(this, title, message, VMessageBox::Ok);
01889   } else {
01890     /* NEWNYM signal failed */
01891     VMessageBox::warning(this, 
01892       tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok);
01893   }
01894 }
01895 
01896 /** Re-enables the 'New Identity' button after a delay from the previous time
01897  * 'New Identity' was used. */
01898 void
01899 MainWindow::enableNewIdentity()
01900 {
01901   if (_torControl->isConnected()) {
01902     _actionNewIdentity->setEnabled(true);
01903     ui.lblNewIdentity->setEnabled(true);
01904   }
01905 }
01906 
01907 /** Converts a TorStatus enum value to a string for debug logging purposes. */
01908 QString
01909 MainWindow::toString(TorStatus status)
01910 {
01911   switch (status) {
01912     /* These strings only appear in debug logs, so they should not be
01913      * translated. */
01914     case Unset:     return "Unset";
01915     case Stopping:  return "Stopping";
01916     case Stopped:   return "Stopped";
01917     case Starting:  return "Starting";
01918     case Started:   return "Started";
01919     case Authenticating:  return "Authenticating";
01920     case Authenticated:   return "Authenticated";
01921     case CircuitEstablished: return "Circuit Established";
01922     default: break;
01923   }
01924   return "Unknown";
01925 }
01926 
01927 #if defined(USE_MINIUPNPC)
01928 /** Called when a UPnP error occurs. */
01929 void
01930 MainWindow::upnpError(UPNPControl::UPNPError error)
01931 {
01932   Q_UNUSED(error);
01933 
01934 #if 0
01935   /* XXX: Is there a better way to do this? Currently, this could get called
01936    * if there is an error when testing UPnP support, and again when attempting
01937    * to reset the UPnP state when the test dialog is closed. The user would
01938    * not be amused with all the warning dialogs. */
01939 
01940   VMessageBox::warning(this,
01941     tr("Port Forwarding Failed"),
01942     p(tr("Vidalia was unable to configure automatic port forwarding."))
01943       + p(UPNPControl::Instance()->errorString()),
01944     VMessageBox::Ok);
01945 #endif
01946 }
01947 #endif
01948 
01949 #if defined(USE_AUTOUPDATE)
01950 /** Called when the user clicks the 'Check Now' button in the General
01951  * settings page. */
01952 void
01953 MainWindow::checkForUpdatesWithUi()
01954 {
01955   checkForUpdates(true);
01956 }
01957 
01958 /** Called when the update interval timer expires, notifying Vidalia that
01959  * we should check for updates again. */
01960 void
01961 MainWindow::checkForUpdates(bool showProgress)
01962 {
01963   VidaliaSettings settings;
01964 
01965   if (_updateProcess.isRunning()) {
01966     if (showProgress) {
01967       /* A check for updates is already in progress, so just bring the update
01968        * progress dialog into focus.
01969        */
01970       _updateProgressDialog.show();
01971     }
01972   } else {
01973     /* If Tor is running and bootstrapped, then use Tor to check for updates */
01974     if (_torControl->isRunning() && _torControl->circuitEstablished())
01975       _updateProcess.setSocksPort(_torControl->getSocksPort());
01976     else
01977       _updateProcess.setSocksPort(0);
01978 
01979     /* Initialize the UpdateProgressDialog and display it, if necessary. */
01980     _updateProgressDialog.setStatus(UpdateProgressDialog::CheckingForUpdates);
01981     if (showProgress)
01982       _updateProgressDialog.show();
01983 
01984     /* Initiate a check for available software updates. This check will
01985      * be done in the background, notifying the user only if there are
01986      * updates to be installed.
01987      */
01988     _updateProcess.checkForUpdates(UpdateProcess::TorBundleInfo);
01989 
01990     /* Remember when we last checked for software updates */
01991     settings.setLastCheckedForUpdates(QDateTime::currentDateTime().toUTC());
01992 
01993     /* Restart the "Check for Updates" timer */
01994     _updateTimer.start(UpdateProcess::checkForUpdatesInterval() * 1000);
01995   }
01996 }
01997 
01998 /** Called when the check for software updates fails. */
01999 void
02000 MainWindow::checkForUpdatesFailed(const QString &errmsg)
02001 {
02002   if (_updateProgressDialog.isVisible()) {
02003     _updateProgressDialog.hide();
02004     VMessageBox::warning(this, tr("Update Failed"), errmsg,
02005                          VMessageBox::Ok);
02006   }
02007 }
02008 
02009 /** Called when there is an update available for installation. */
02010 void
02011 MainWindow::updatesAvailable(UpdateProcess::BundleInfo bi,
02012                              const PackageList &packageList)
02013 {
02014   vInfo("%1 software update(s) available").arg(packageList.size());
02015   if (packageList.size() > 0) {
02016     UpdatesAvailableDialog dlg(packageList, &_updateProgressDialog);
02017 
02018     switch (dlg.exec()) {
02019       case UpdatesAvailableDialog::InstallUpdatesNow:
02020         installUpdates(bi);
02021         break;
02022 
02023       default:
02024         _updateProgressDialog.hide();
02025         break;
02026     }
02027   } else {
02028     if (_updateProgressDialog.isVisible()) {
02029       _updateProgressDialog.hide();
02030       VMessageBox::information(this, tr("Your software is up to date"),
02031                                tr("There are no new Tor software packages "
02032                                   "available for your computer at this time."),
02033                                VMessageBox::Ok);
02034     }
02035   }
02036 }
02037 
02038 /** Stops Tor (if necessary), installs any available for <b>bi</b>, and
02039  * restarts Tor (if necessary). */
02040 void
02041 MainWindow::installUpdates(UpdateProcess::BundleInfo bi)
02042 {
02043   _updateProgressDialog.setStatus(UpdateProgressDialog::InstallingUpdates);
02044   _updateProgressDialog.show();
02045 
02046   if (_isVidaliaRunningTor) {
02047     _restartTorAfterUpgrade = true;
02048     _isIntentionalExit = true;
02049     _torControl->stop();
02050   } else {
02051     _restartTorAfterUpgrade = false;
02052   }
02053   _updateProcess.installUpdates(bi);
02054 }
02055 
02056 /** Called when all <b>numUpdates</b> software updates have been installed
02057  * successfully. */
02058 void
02059 MainWindow::updatesInstalled(int numUpdates)
02060 {
02061   _updateProgressDialog.setStatus(UpdateProgressDialog::UpdatesInstalled);
02062   _updateProgressDialog.show();
02063 
02064   if (_restartTorAfterUpgrade)
02065     start();
02066 }
02067 
02068 /** Called when an update fails to install. <b>errmsg</b> contains details
02069  * about the failure. */
02070 void
02071 MainWindow::installUpdatesFailed(const QString &errmsg)
02072 {
02073   _updateProgressDialog.hide();
02074 
02075   VMessageBox::warning(this, tr("Installation Failed"),
02076                        p(tr("Vidalia was unable to install your software updates."))
02077                          + p(tr("The following error occurred:")) 
02078                          + p(errmsg),
02079                        VMessageBox::Ok);
02080 
02081   if (_restartTorAfterUpgrade)
02082     start();
02083 }
02084 
02085 #endif
02086 
02087 QStringList
02088 MainWindow::updateBrowserEnv() {
02089   TorSettings settings;
02090   QStringList env = QProcess::systemEnvironment();
02091   env << "TZ=UTC";
02092   env << "MOZ_NO_REMOTE=1";
02093 
02094   if(settings.autoControlPort()) {
02095     QString errmsg, socks;
02096     if(!(_torControl->getInfo("net/listeners/socks", socks, &errmsg))) {
02097       vInfo(errmsg);
02098       return env;
02099     }
02100 
02101     QStringList addrPort = socks.split(":");
02102     if(addrPort.size() != 2) return env;
02103 
02104     QHostAddress addr(addrPort.at(0));
02105     quint16 port = addrPort.at(1).toInt();
02106 
02107     env << QString("TOR_SOCKS_HOST=%1").arg(addr.toString());
02108     env << QString("TOR_SOCKS_PORT=%1").arg(port);
02109 
02110     vInfo(QString("Using automatic ControlPort and SocksPort configuration:\n"
02111                   "   ControlPort=%1\n   SocksPort=%2\n   Host=%3\n   Configuration file:%4")
02112           .arg(_autoControlPort)
02113           .arg(port)
02114           .arg(addrPort.at(0))
02115           .arg(QString("%1/port.conf").arg(expand_filename(settings.getDataDirectory()))));
02116   }
02117 
02118   if(settings.getAuthenticationMethod() == TorSettings::PasswordAuth) {
02119     env << QString("TOR_CONTROL_PASSWD=%1").arg(QString(_controlPassword.toAscii().toHex()));
02120     env << QString("TOR_CONTROL_PORT=%1").arg(_autoControlPort);
02121   }
02122 
02123   return env;
02124 }