Vidalia  0.2.15
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   _browserProcess->start(browserExecutable, commandLine);
00651   _browserProcess->toForeground();
00652 }
00653 
00654 /** Starts the web browser and IM client, if appropriately configured */
00655 void
00656 MainWindow::startSubprocesses()
00657 {
00658   VidaliaSettings settings;
00659   QString subprocess;
00660 
00661   /* Launch the web browser */
00662   if (!(subprocess = settings.getBrowserDirectory()).isEmpty()) {
00663     /* The user has set BrowserDirectory; use this */
00664     launchBrowserFromDirectory();
00665   } else if (!(subprocess = settings.getBrowserExecutable()).isEmpty()) {
00666     /* BrowserDirectory is not set, but BrowserExecutable is; use this */
00667     _browserProcess->setEnvironment(updateBrowserEnv());
00668     _browserProcess->start(subprocess, QStringList());
00669     _browserProcess->toForeground();
00670   }
00671 
00672   /* Launch the IM client */
00673   subprocess = settings.getIMExecutable();
00674 
00675   if (!subprocess.isEmpty())
00676     _imProcess->start(subprocess, QStringList());
00677 }
00678 
00679 /** Called when browser or IM client have exited */
00680 void
00681 MainWindow::onSubprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
00682 {
00683   Q_UNUSED(exitCode)
00684   Q_UNUSED(exitStatus)
00685 
00686   /* Get path to browser and IM client */
00687   VidaliaSettings settings;
00688   QString browserExecutable = settings.getBrowserExecutable();
00689   QString browserDirectory = settings.getBrowserDirectory();
00690   QString imExecutable = settings.getIMExecutable();
00691 
00692   /* A subprocess is finished if it successfully exited or was never asked to start */
00693   bool browserDone = (browserExecutable.isEmpty()
00694                         && browserDirectory.isEmpty())
00695                         || _browserProcess->isDone();
00696   bool imDone = imExecutable.isEmpty() || _imProcess->isDone();
00697 
00698   /* Exit if both subprocesses are finished */
00699   if (browserDone && imDone) {
00700     if (browserDirectory.isEmpty()) {
00701       /* We are using the standard launcher, exit immediately */
00702       vApp->quit();
00703     } else {
00704       /* We are using the alternate launcher, wait until the browser has really died */
00705       QTimer *browserWatcher = new QTimer(this);
00706       connect(browserWatcher, SIGNAL(timeout()), this, SLOT(onCheckForBrowser()));
00707       browserWatcher->start(2000);
00708     }
00709   }
00710 }
00711 
00712 /** Called periodically to check if the browser is running. If it is not,
00713  * exit Vidalia cleanly */
00714 void
00715 MainWindow::onCheckForBrowser()
00716 {
00717 /* This only works on Windows for now */
00718 #if defined(Q_OS_WIN)
00719 
00720   VidaliaSettings settings;
00721   QString browserDirectoryFilename = settings.getBrowserExecutable();
00722 
00723   /* Get list of running processes */
00724   QHash<qint64, QString> procList = win32_process_list();
00725 
00726   /* On old versions of Windows win32_process_list() will return
00727      an empty list. In this case, just keep Vidalia open */
00728   if (procList.isEmpty()) {
00729     return;
00730   }
00731 
00732   /* Loop over all processes or until we find <browserDirectoryFilename> */
00733   QHashIterator<qint64, QString> i(procList);
00734   while (i.hasNext()) {
00735     i.next();
00736     if (i.value().toLower() == browserDirectoryFilename) {
00737       /* The browser is still running, so Vidalia should keep running too */
00738       return;
00739     }
00740   }
00741 
00742   /* The browser isn't running, exit Vidalia */
00743   vApp->quit();
00744 #endif  
00745 }
00746 
00747 /** Called when the web browser failed to start, for example, because the path
00748  * specified to the web browser executable didn't lead to an executable. */
00749 void
00750 MainWindow::onBrowserFailed(QString errmsg)
00751 {
00752   Q_UNUSED(errmsg);
00753  
00754   /* Display an error message and see if the user wants some help */
00755   VMessageBox::warning(this, tr("Error starting web browser"),
00756               tr("Vidalia was unable to start the configured web browser"),
00757               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00758 }
00759 
00760 /** Called when the IM client failed to start, for example, because the path
00761  * specified to the IM client executable didn't lead to an executable. */
00762 void
00763 MainWindow::onIMFailed(QString errmsg)
00764 {
00765   Q_UNUSED(errmsg);
00766  
00767   /* Display an error message and see if the user wants some help */
00768   VMessageBox::warning(this, tr("Error starting IM client"),
00769               tr("Vidalia was unable to start the configured IM client"),
00770               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00771 }
00772 
00773 /** Starts the proxy server, if appropriately configured */
00774 void
00775 MainWindow::startProxy()
00776 {
00777   VidaliaSettings settings;
00778   QString executable = settings.getProxyExecutable();
00779   _proxyProcess->start(executable, settings.getProxyExecutableArguments());
00780 }
00781 
00782 /** Called when the proxy server fails to start, for example, because
00783  * the path specified didn't lead to an executable. */
00784 void
00785 MainWindow::onProxyFailed(QString errmsg)
00786 {
00787   Q_UNUSED(errmsg);
00788  
00789   /* Display an error message and see if the user wants some help */
00790   VMessageBox::warning(this, tr("Error starting proxy server"),
00791               tr("Vidalia was unable to start the configured proxy server"),
00792               VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
00793 }
00794 
00795 /** Called when Tor's bootstrapping status changes. <b>bse</b> represents
00796  * Tor's current estimate of its bootstrapping progress. */
00797 void
00798 MainWindow::bootstrapStatusChanged(const BootstrapStatus &bs)
00799 {
00800   int percentComplete = STARTUP_PROGRESS_BOOTSTRAPPING + bs.percentComplete();
00801   bool warn = (bs.severity() == tc::WarnSeverity && 
00802                bs.recommendedAction() != BootstrapStatus::RecommendIgnore);
00803 
00804   QString description;
00805   switch (bs.status()) {
00806     case BootstrapStatus::ConnectingToDirMirror:
00807       description = tr("Connecting to a relay directory");
00808       break;
00809     case BootstrapStatus::HandshakingWithDirMirror:
00810     case BootstrapStatus::CreatingOneHopCircuit:
00811       description = tr("Establishing an encrypted directory connection");
00812       break;
00813     case BootstrapStatus::RequestingNetworkStatus:
00814       description = tr("Retrieving network status");
00815       break;
00816     case BootstrapStatus::LoadingNetworkStatus:
00817       description = tr("Loading network status");
00818       break;
00819     case BootstrapStatus::LoadingAuthorityCertificates:
00820       description = tr("Loading authority certificates");
00821       break;
00822     case BootstrapStatus::RequestingDescriptors:
00823       description = tr("Requesting relay information");
00824       break;
00825     case BootstrapStatus::LoadingDescriptors:
00826       description = tr("Loading relay information");
00827       break;
00828     case BootstrapStatus::ConnectingToEntryGuard:
00829       description = tr("Connecting to the Tor network");
00830       break;
00831     case BootstrapStatus::HandshakingWithEntryGuard:
00832     case BootstrapStatus::EstablishingCircuit:
00833       description = tr("Establishing a Tor circuit");
00834       break;
00835     case BootstrapStatus::BootstrappingDone:
00836       description = tr("Connected to the Tor network!");
00837       warn = false; /* probably false anyway */
00838       break;
00839     default:
00840       description = tr("Unrecognized startup status");
00841   }
00842   if (warn) {
00843     QString reason;
00844     /* Is it really a good idea to translate these? */
00845     switch (bs.reason()) {
00846       case tc::MiscellaneousReason:
00847         reason = tr("miscellaneous");
00848         break;
00849       case tc::IdentityMismatch:
00850         reason = tr("identity mismatch");
00851         break;
00852       case tc::ConnectionDone:
00853         reason = tr("done");
00854         break;
00855       case tc::ConnectionRefused:
00856         reason = tr("connection refused");
00857         break;
00858       case tc::ConnectionTimeout:
00859         reason = tr("connection timeout");
00860         break;
00861       case tc::ConnectionIoError:
00862         reason = tr("read/write error");
00863         break;
00864       case tc::NoRouteToHost:
00865         reason = tr("no route to host");
00866         break;
00867       case tc::ResourceLimitReached:
00868         reason = tr("insufficient resources");
00869         break;
00870       default:
00871         reason = tr("unknown");
00872     }
00873     description += tr(" failed (%1)").arg(reason);
00874   }
00875   setStartupProgress(percentComplete, description);
00876 }
00877 
00878 /** Updates the UI to reflect Tor's current <b>status</b>. Returns the
00879  * previously set TorStatus value.*/
00880 MainWindow::TorStatus
00881 MainWindow::updateTorStatus(TorStatus status)
00882 {
00883   QString statusText, actionText;
00884   QString trayIconFile, statusIconFile;
00885   TorStatus prevStatus = _status;
00886  
00887   vNotice("Tor status changed from '%1' to '%2'.")
00888     .arg(toString(prevStatus)).arg(toString(status));
00889   _status = status;
00890 
00891   if (status == Stopped) {
00892       statusText = tr("Tor is not running");
00893       actionText = tr("Start Tor");
00894       trayIconFile = IMG_TOR_STOPPED;
00895       statusIconFile = IMG_TOR_STOPPED_48;
00896       _actionStartStopTor->setEnabled(true);
00897       _actionStartStopTor->setText(actionText);
00898       _actionStartStopTor->setIcon(QIcon(IMG_START_TOR_16));
00899       ui.lblStartStopTor->setEnabled(true);
00900       ui.lblStartStopTor->setText(actionText);
00901       ui.lblStartStopTor->setPixmap(QPixmap(IMG_START_TOR_48));
00902       ui.lblStartStopTor->setStatusTip(actionText);
00903 
00904       /* XXX: This might need to be smarter if we ever start connecting other
00905        * slots to these triggered() and clicked() signals. */
00906       QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
00907       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00908       connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(start()));
00909       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(start()));
00910       setStartupProgressVisible(false);
00911   } else if (status == Stopping) {
00912       if (_delayedShutdownStarted) {
00913         statusText = tr("Your relay is shutting down.\n" 
00914                         "Click 'Stop' again to stop your relay now.");
00915       } else {
00916         statusText = tr("Tor is shutting down");
00917       }
00918       trayIconFile = IMG_TOR_STOPPING;
00919       statusIconFile = IMG_TOR_STOPPING_48;
00920       
00921       ui.lblStartStopTor->setStatusTip(tr("Stop Tor Now"));
00922   } else if (status == Started) {
00923       actionText = tr("Stop Tor");
00924       _actionStartStopTor->setEnabled(true);
00925       _actionStartStopTor->setText(actionText);
00926       _actionStartStopTor->setIcon(QIcon(IMG_STOP_TOR_16));
00927       ui.lblStartStopTor->setEnabled(true);
00928       ui.lblStartStopTor->setText(actionText);
00929       ui.lblStartStopTor->setPixmap(QPixmap(IMG_STOP_TOR_48));
00930       ui.lblStartStopTor->setStatusTip(actionText);
00931             
00932       /* XXX: This might need to be smarter if we ever start connecting other
00933        * slots to these triggered() and clicked() signals. */
00934       QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
00935       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00936       connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(stop()));
00937       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(stop()));
00938   } else if (status == Starting)  {
00939       statusText = tr("Starting the Tor software");
00940       trayIconFile = IMG_TOR_STARTING;
00941       statusIconFile = IMG_TOR_STARTING_48;
00942       _actionStartStopTor->setEnabled(false);
00943       ui.lblStartStopTor->setText(tr("Starting Tor"));
00944       ui.lblStartStopTor->setEnabled(false);
00945       ui.lblStartStopTor->setStatusTip(statusText);
00946       setStartupProgressVisible(true);
00947       setStartupProgress(STARTUP_PROGRESS_STARTING, statusText);
00948   } else if (status == CircuitEstablished) {
00949       statusText = tr("Connected to the Tor network!");
00950       trayIconFile = IMG_TOR_RUNNING;
00951       statusIconFile = IMG_TOR_RUNNING_48;
00952       setStartupProgressVisible(false);
00953   }
00954 
00955   /* Update the tray icon */
00956   if (!trayIconFile.isEmpty()) {
00957     setTrayIcon(trayIconFile);
00958   }
00959   /* Update the status banner on the control panel */
00960   if (!statusIconFile.isEmpty())
00961     ui.lblTorStatusImg->setPixmap(QPixmap(statusIconFile));
00962   if (!statusText.isEmpty()) {
00963     _trayIcon.setToolTip(statusText);
00964     ui.lblTorStatus->setText(statusText);
00965   }
00966   return prevStatus;
00967 }
00968 
00969 /** Called when the "show on startup" checkbox is toggled. */
00970 void
00971 MainWindow::toggleShowOnStartup(bool checked)
00972 {
00973   VidaliaSettings settings;
00974   settings.setShowMainWindowAtStart(checked);
00975 }
00976 
00977 /** Sets the visibility of the startup status description and progress bar to
00978  * <b>visible</b>. */
00979 void
00980 MainWindow::setStartupProgressVisible(bool visible)
00981 {
00982   /* XXX: We force a repaint() to make sure the progress bar and onion status
00983    * icon don't overlap briefly. This is pretty hacktastic. */
00984   if (visible) {
00985     ui.lblTorStatus->setVisible(false);
00986     ui.lblTorStatusImg->setVisible(false);
00987     repaint(ui.grpStatus->rect());
00988     ui.lblStartupProgress->setVisible(true);
00989     ui.progressBar->setVisible(true);
00990   } else {
00991     ui.lblStartupProgress->setVisible(false);
00992     ui.progressBar->setVisible(false);
00993     repaint(ui.grpStatus->rect());
00994     ui.lblTorStatus->setVisible(true);
00995     ui.lblTorStatusImg->setVisible(true);
00996   }
00997 }
00998 
00999 /** Sets the progress bar completion value to <b>progressValue</b> and sets
01000  * the status text to <b>description</b>. */
01001 void
01002 MainWindow::setStartupProgress(int progressValue,
01003                                const QString &description)
01004 {
01005   ui.progressBar->setValue(progressValue);
01006   ui.lblStartupProgress->setText(description);
01007   _trayIcon.setToolTip(description);
01008 }
01009 
01010 /** Attempts to start Tor. If Tor fails to start, then startFailed() will be
01011  * called with an error message containing the reason. */
01012 void 
01013 MainWindow::start()
01014 {
01015   TorSettings settings;
01016   QStringList args;
01017 
01018   updateTorStatus(Starting);
01019 
01020   // Disable autoconfiguration if there are missing config data
01021   if(settings.autoControlPort()) {
01022     if(settings.getDataDirectory().isEmpty()) {
01023       vWarn("Disabling ControlPort autoconfiguration. DataDirectory is empty!");
01024       settings.setAutoControlPort(false);
01025     }
01026   }
01027 
01028   /* Check if Tor is already running separately */
01029   if(settings.getControlMethod() == ControlMethod::Port) {
01030     if(!settings.autoControlPort() && net_test_connect(settings.getControlAddress(),
01031                                                        settings.getControlPort())) {
01032       started();
01033       return;
01034     }
01035   } else {
01036     if (socket_test_connect(settings.getSocketPath())) {
01037       started();
01038       return;
01039     }
01040   }
01041 
01042   QString torrc = settings.getTorrc();
01043 
01044   if(settings.bootstrap()) {
01045     QString boottorrc = settings.bootstrapFrom();
01046     vNotice(tr("Bootstrapping torrc from %1 to %2")
01047         .arg(boottorrc).arg(torrc));
01048     if(QFileInfo(boottorrc).exists()) {
01049       if(QFile::copy(boottorrc, torrc)) {
01050         settings.setBootstrap(false);
01051       }
01052     }
01053   }
01054 
01055   /* Make sure the torrc we want to use really exists. */
01056   if (!torrc.isEmpty()) {
01057     if (!QFileInfo(torrc).exists())
01058       touch_file(torrc, true);
01059     args << "-f" << torrc;
01060   }
01061 
01062   /* Specify Tor's data directory, if different from the default */
01063   QString dataDirectory = settings.getDataDirectory();
01064   QString expDataDirectory = expand_filename(dataDirectory);
01065   if (!dataDirectory.isEmpty())
01066     args << "DataDirectory" << expDataDirectory;
01067   
01068   if(settings.getControlMethod() == ControlMethod::Port) {
01069     if(settings.autoControlPort()) {
01070       args << "ControlPort" << "auto";
01071       args << "SocksPort" << "auto";
01072       args << "ControlPortWriteToFile" << QString("%1/port.conf").arg(expDataDirectory);
01073     } else {
01074       /* Add the intended control port value */
01075       quint16 controlPort = settings.getControlPort();
01076       if (controlPort)
01077         args << "ControlPort" << QString::number(controlPort);
01078     }
01079   } else {
01080     QString path = settings.getSocketPath();
01081     args << "ControlSocket" << path;
01082   }
01083   
01084   /* Add the control port authentication arguments */
01085   switch (settings.getAuthenticationMethod()) {
01086     case TorSettings::PasswordAuth:
01087       if (! vApp->readPasswordFromStdin()) {
01088         if (settings.useRandomPassword()) {
01089           _controlPassword = TorSettings::randomPassword();
01090           _useSavedPassword = false;
01091         } else {
01092           _controlPassword = settings.getControlPassword();
01093           _useSavedPassword = true;
01094         }
01095       }
01096       args << "HashedControlPassword"
01097            << TorSettings::hashPassword(_controlPassword);
01098       break;
01099     case TorSettings::CookieAuth:
01100       args << "CookieAuthentication"  << "1";
01101       break;
01102     default:
01103       args << "CookieAuthentication"  << "0";
01104   }
01105 
01106   /* This doesn't get set to false until Tor is actually up and running, so we
01107    * don't yell at users twice if their Tor doesn't even start, due to the fact
01108    * that QProcess::stopped() is emitted even if the process didn't even
01109    * start. */
01110   _isIntentionalExit = true;
01111   /* Kick off the Tor process */
01112   _torControl->start(settings.getExecutable(), args);
01113 }
01114 
01115 /** Called when the user changes a setting that needs Tor restarting */
01116 void
01117 MainWindow::restart()
01118 {
01119   if(_torControl->stop()) {
01120     start();
01121   }
01122 }
01123 
01124 /** Called when the Tor process fails to start, for example, because the path
01125  * specified to the Tor executable didn't lead to an executable. */
01126 void
01127 MainWindow::startFailed(QString errmsg)
01128 {
01129   /* We don't display the error message for now, because the error message
01130    * that Qt gives us in this instance is almost always "Unknown Error". That
01131    * will make users sad. */
01132   Q_UNUSED(errmsg);
01133  
01134   updateTorStatus(Stopped);
01135 
01136   /* Display an error message and see if the user wants some help */
01137   int response = VMessageBox::warning(this, tr("Error Starting Tor"),
01138                    tr("Vidalia was unable to start Tor. Check your settings "
01139                         "to ensure the correct name and location of your Tor "
01140                         "executable is specified."),
01141                    VMessageBox::ShowSettings|VMessageBox::Default,
01142                    VMessageBox::Cancel|VMessageBox::Escape,
01143                    VMessageBox::Help);
01144 
01145   if (response == VMessageBox::ShowSettings) {
01146     /* Show the settings dialog so the user can make sure they're pointing to
01147      * the correct Tor. */
01148      showConfigDialog();
01149   } else if (response == VMessageBox::Help) {
01150     /* Show troubleshooting information about starting Tor */
01151     showHelpDialog("troubleshooting.start");
01152   }
01153 }
01154 
01155 /** Slot: Called when the Tor process is started. It will connect the control
01156  * socket and set the icons and tooltips accordingly. */
01157 void 
01158 MainWindow::started()
01159 {
01160   TorSettings settings;
01161 
01162   updateTorStatus(Started);
01163 
01164   /* Now that Tor is running, we want to know if it dies when we didn't want
01165    * it to. */
01166   _isIntentionalExit = false;
01167   /* We haven't started a delayed shutdown yet. */
01168   _delayedShutdownStarted = false;
01169   /* Remember whether we started Tor or not */
01170   _isVidaliaRunningTor = _torControl->isVidaliaRunningTor();
01171   /* Try to connect to Tor's control port */
01172   if(settings.autoControlPort()) {
01173     QString dataDirectory = settings.getDataDirectory();
01174     QFile file(QString("%1/port.conf").arg(expand_filename(dataDirectory)));
01175     int tries = 0, maxtries = 5;
01176     while((!file.open(QIODevice::ReadOnly | QIODevice::Text)) and
01177           (tries++ < maxtries)) {
01178       vWarn(QString("This is try number: %1.").arg(tries));
01179 #if defined(Q_WS_WIN)
01180       Sleep(1000);
01181 #else
01182       sleep(1);
01183 #endif
01184     }
01185 
01186     if(tries >= maxtries) {
01187       vWarn("Couldn't read port.conf file");
01188       if(_torControl->isRunning()) {
01189         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")
01190                       .arg(file.fileName())
01191                       .arg(file.errorString()));
01192       } else {
01193         vWarn("Tor isn't running!");
01194         connectFailed(tr("It seems Tor has stopped running since Vidalia started it.\n\nSee the Advanced Message Log for more information."));
01195       }
01196 
01197       return;
01198     }
01199 
01200     QTextStream in(&file);
01201     if(!in.atEnd()) {
01202       QString line = in.readLine();
01203       QStringList parts = line.split("=");
01204       if(parts.size() != 2) return;
01205       if(parts[0].trimmed() != "PORT") return;
01206 
01207       QStringList addrPort = parts[1].split(":");
01208       if(addrPort.size() != 2) return;
01209 
01210       QHostAddress addr(addrPort.at(0));
01211       _autoControlPort = addrPort.at(1).toInt();
01212       _torControl->connect(addr, _autoControlPort);
01213     }
01214 
01215     file.close();
01216   } else {
01217     /* Try to connect to Tor's control port */
01218     if(settings.getControlMethod() == ControlMethod::Port) {
01219       _torControl->connect(settings.getControlAddress(),
01220                           settings.getControlPort());
01221       _autoControlPort = settings.getControlPort();
01222     } else
01223       _torControl->connect(settings.getSocketPath());
01224   }
01225   setStartupProgress(STARTUP_PROGRESS_CONNECTING, tr("Connecting to Tor"));
01226 }
01227 
01228 /** Called when the connection to the control socket fails. The reason will be
01229  * given in the errmsg parameter. */
01230 void
01231 MainWindow::connectFailed(QString errmsg)
01232 {
01233   /* Ok, ok. It really isn't going to connect. I give up. */
01234   int response = VMessageBox::warning(this, 
01235                    tr("Connection Error"), p(errmsg),
01236                    VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01237                    VMessageBox::Retry, VMessageBox::Help);
01238 
01239 
01240   if (response == VMessageBox::Retry) {
01241     /* Let's give it another try. */
01242     TorSettings settings;
01243     _torControl->connect(settings.getControlAddress(),
01244                          settings.getControlPort());
01245   } else {
01246     /* Show the help browser (if requested) */
01247     if (response == VMessageBox::Help)
01248       showHelpDialog("troubleshooting.connect");
01249     /* Since Vidalia can't connect, we can't really do much, so stop Tor. */
01250     _torControl->stop();
01251   }
01252 }
01253 
01254 /** Disconnects the control socket and stops the Tor process. */
01255 bool
01256 MainWindow::stop()
01257 {
01258   ServerSettings server(_torControl);
01259   QString errmsg;
01260   TorStatus prevStatus;
01261   bool rc;
01262 
01263   /* If we're running a server, give users the option of terminating
01264    * gracefully so clients have time to find new servers. */
01265   if (server.isServerEnabled() && !_delayedShutdownStarted) {
01266     /* Ask the user if they want to shutdown nicely. */
01267     int response = VMessageBox::question(this, tr("Relaying is Enabled"),
01268                      tr("You are currently running a relay. "
01269                         "Terminating your relay will interrupt any "
01270                         "open connections from clients.\n\n"
01271                         "Would you like to shutdown gracefully and "
01272                         "give clients time to find a new relay?"),
01273                         VMessageBox::Yes|VMessageBox::Default, 
01274                         VMessageBox::No, 
01275                         VMessageBox::Cancel|VMessageBox::Escape);
01276     if (response == VMessageBox::Yes)
01277       _delayedShutdownStarted = true;
01278     else if (response == VMessageBox::Cancel)
01279       return false;
01280   }
01281   
01282   prevStatus = updateTorStatus(Stopping);  
01283   if (_delayedShutdownStarted) {
01284     /* Start a delayed shutdown */
01285     rc = _torControl->signal(TorSignal::Shutdown, &errmsg);
01286   } else {
01287     /* We want Tor to stop now, regardless of whether we're a server. */
01288     _isIntentionalExit = true;
01289     rc = _torControl->stop(&errmsg);
01290   }
01291   
01292   if (!rc) {
01293     /* We couldn't tell Tor to stop, for some reason. */
01294     int response = VMessageBox::warning(this, tr("Error Shutting Down"),
01295                      p(tr("Vidalia was unable to stop the Tor software.")) 
01296                        + p(errmsg),
01297                      VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
01298                      VMessageBox::Help);
01299       
01300     if (response == VMessageBox::Help) {
01301       /* Show some troubleshooting help */
01302       showHelpDialog("troubleshooting.stop");
01303     }
01304     /* Tor is still running since stopping failed */
01305     _isIntentionalExit = false;
01306     _delayedShutdownStarted = false;
01307     updateTorStatus(prevStatus);
01308   }
01309   return rc;
01310 }
01311 
01312 /** Slot: Called when the Tor process has exited. It will adjust the tray
01313  * icons and tooltips accordingly. */
01314 void 
01315 MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus)
01316 {
01317   updateTorStatus(Stopped);
01318 
01319   /* If we didn't intentionally close Tor, then check to see if it crashed or
01320    * if it closed itself and returned an error code. */
01321   if (!_isIntentionalExit) {
01322     /* A quick overview of Tor's code tells me that if it catches a SIGTERM or
01323      * SIGINT, Tor will exit(0). We might need to change this warning message
01324      * if this turns out to not be the case. */
01325     if (exitStatus == QProcess::CrashExit || exitCode != 0) {
01326       int ret = VMessageBox::warning(this, tr("Unexpected Error"),
01327                   tr("Vidalia detected that the Tor software exited "
01328                      "unexpectedly.\n\n"
01329                      "Please check the message log for recent "
01330                      "warning or error messages."),
01331                   VMessageBox::Ok|VMessageBox::Escape, 
01332                   VMessageBox::ShowLog|VMessageBox::Default,
01333                   VMessageBox::Help);
01334       if (ret == VMessageBox::ShowLog)
01335         _messageLog->showWindow();  
01336       else if (ret == VMessageBox::Help)
01337         showHelpDialog("troubleshooting.torexited");
01338     }
01339   }
01340 }
01341 
01342 /** Called when the control socket has successfully connected to Tor. */
01343 void
01344 MainWindow::connected()
01345 {
01346   authenticate();
01347 }
01348 
01349 /** Called when Vidalia wants to disconnect from a Tor it did not start. */
01350 void
01351 MainWindow::disconnect()
01352 {
01353   _torControl->disconnect();
01354 }
01355 
01356 /** Called when the control socket has been disconnected. */
01357 void
01358 MainWindow::disconnected()
01359 {
01360   if (!_isVidaliaRunningTor) {
01361     /* If we didn't start our own Tor process, interpret losing the control
01362      * connection as "Tor is stopped". */
01363     updateTorStatus(Stopped);
01364   }
01365   
01366   /*XXX We should warn here if we get disconnected when we didn't intend to */
01367   _actionNewIdentity->setEnabled(false);
01368   ui.lblNewIdentity->setEnabled(false);
01369   _isVidaliaRunningTor = false;
01370 }
01371 
01372 /** Attempts to authenticate to Tor's control port, depending on the
01373  * authentication method specified in TorSettings::getAuthenticationMethod().
01374  */
01375 bool
01376 MainWindow::authenticate()
01377 {
01378   TorSettings::AuthenticationMethod authMethod;
01379   TorSettings settings;
01380   ProtocolInfo pi;
01381   
01382   updateTorStatus(Authenticating);
01383   setStartupProgress(STARTUP_PROGRESS_AUTHENTICATING,
01384                      tr("Authenticating to Tor"));
01385 
01386   authMethod = settings.getAuthenticationMethod(); 
01387   pi = _torControl->protocolInfo();
01388   if (!pi.isEmpty()) {
01389     QStringList authMethods = pi.authMethods();
01390     if (authMethods.contains("COOKIE"))
01391       authMethod = TorSettings::CookieAuth;
01392     else if (authMethods.contains("HASHEDPASSWORD"))
01393       authMethod = TorSettings::PasswordAuth;
01394     else if (authMethods.contains("NULL"))
01395       authMethod = TorSettings::NullAuth;
01396   }
01397   
01398   if (authMethod == TorSettings::CookieAuth) {
01399     /* Try to load an auth cookie and send it to Tor */
01400     QByteArray cookie = loadControlCookie(pi.cookieAuthFile());
01401     while (cookie.isEmpty()) {
01402       /* Prompt the user to find their control_auth_cookie */
01403       int ret = VMessageBox::question(this,
01404                   tr("Cookie Authentication Required"),
01405                   p(tr("The Tor software requires Vidalia to send the "
01406                        "contents of an authentication cookie, but Vidalia "
01407                        "was unable to find one."))
01408                   + p(tr("Would you like to browse for the file "
01409                          "'control_auth_cookie' yourself?")),
01410                 VMessageBox::Browse|VMessageBox::Default,
01411                 VMessageBox::Cancel|VMessageBox::Escape);
01412       
01413       if (ret == VMessageBox::Cancel)
01414         goto cancel;
01415       QString cookieDir = QFileDialog::getOpenFileName(this,
01416                             tr("Data Directory"),
01417                             settings.getDataDirectory(),
01418                             tr("Control Cookie (control_auth_cookie)"));
01419       if (cookieDir.isEmpty())
01420         goto cancel;
01421       cookieDir = QFileInfo(cookieDir).absolutePath();
01422       cookie = loadControlCookie(cookieDir);
01423     }
01424     vNotice("Authenticating using 'cookie' authentication.");
01425     return _torControl->authenticate(cookie);
01426   } else if (authMethod == TorSettings::PasswordAuth) {
01427     /* Get the control password and send it to Tor */
01428     vNotice("Authenticating using 'hashed password' authentication.");
01429     if (_useSavedPassword) {
01430       TorSettings settings;
01431       _controlPassword = settings.getControlPassword();
01432     }
01433     return _torControl->authenticate(_controlPassword);
01434   }
01435   /* No authentication. Send an empty password. */
01436   vNotice("Authenticating using 'null' authentication.");
01437   return _torControl->authenticate(QString(""));
01438 
01439 cancel:
01440   vWarn("Cancelling control authentication attempt.");
01441   if (_isVidaliaRunningTor)
01442     stop();
01443   else
01444     disconnect();
01445   return false;
01446 }
01447 
01448 /** Called when Vidalia has successfully authenticated to Tor. */
01449 void
01450 MainWindow::authenticated()
01451 {
01452   ServerSettings serverSettings(_torControl);
01453   QString errmsg;
01454 
01455   updateTorStatus(Authenticated);
01456   
01457   /* If Tor doesn't have bootstrapping events, then update the current
01458    * status string and bump the progress bar along a bit. */
01459   if (_torControl->getTorVersion() < 0x020101) {
01460     setStartupProgress(STARTUP_PROGRESS_CIRCUITBUILD,
01461                        tr("Connecting to the Tor network"));
01462   }
01463   
01464   /* Let people click on their beloved "New Identity" button */
01465   _actionNewIdentity->setEnabled(true);
01466   ui.lblNewIdentity->setEnabled(true);
01467 
01468   /* Register for any pertinent asynchronous events. */
01469   if (!_torControl->setEvents(&errmsg)) {
01470     VMessageBox::warning(this, tr("Error Registering for Events"),
01471       p(tr("Vidalia was unable to register for some events. "
01472            "Many of Vidalia's features may be unavailable."))
01473          + p(errmsg),
01474       VMessageBox::Ok);
01475   } else {
01476     /* Stop reading from Tor's stdout immediately, since we successfully
01477      * registered for Tor events, including any desired log events. */
01478     _torControl->closeTorStdout();
01479   }
01480 
01481   /* Configure UPnP port forwarding if needed */
01482   serverSettings.configurePortForwarding();
01483 
01484   /* Check if Tor has a circuit established */
01485   if (_torControl->isCircuitEstablished())
01486     circuitEstablished();
01487   /* Check the status of Tor's version */
01488   if (_torControl->getTorVersion() >= 0x020001)
01489     checkTorVersion();
01490   if (_torControl->getTorVersion() >= 0x020102) {
01491     BootstrapStatus status = _torControl->bootstrapStatus();
01492     if (status.isValid())
01493       bootstrapStatusChanged(status);
01494   }
01495 }
01496 
01497 /** Called when Vidalia fails to authenticate to Tor. The failure reason is
01498  * specified in <b>errmsg</b>. */
01499 void
01500 MainWindow::authenticationFailed(QString errmsg)
01501 {
01502   bool retry = false;
01503   
01504   vWarn("Authentication failed: %1").arg(errmsg);
01505 
01506   /* Parsing log messages is evil, but we're left with little option */
01507   if (errmsg.contains("Password did not match")) {
01508     ControlPasswordInputDialog dlg;
01509     connect(&dlg, SIGNAL(helpRequested(QString)),
01510             this, SLOT(showHelpDialog(QString)));
01511 
01512     qint64 torPid = 0;
01513 
01514 #if defined(Q_OS_WIN32)
01515     QHash<qint64, QString> procs = process_list();
01516     foreach (qint64 pid, procs.keys()) {
01517       if (! procs.value(pid).compare("tor.exe", Qt::CaseInsensitive)) {
01518         torPid = pid;
01519         break;
01520       }
01521     }
01522     dlg.setResetEnabled(torPid > 0);
01523 #else
01524     dlg.setResetEnabled(false);
01525 #endif
01526 
01527     int ret = dlg.exec();
01528     if (ret == QDialogButtonBox::Ok) {
01529       if (dlg.isSavePasswordChecked()) {
01530         TorSettings settings;
01531         settings.setAuthenticationMethod(TorSettings::PasswordAuth);
01532         settings.setUseRandomPassword(false);
01533         settings.setControlPassword(dlg.password());
01534         _useSavedPassword = true;
01535       } else {
01536         _controlPassword = dlg.password();
01537         _useSavedPassword = false;
01538       }
01539       retry = true;
01540     } else if (ret == QDialogButtonBox::Reset) {
01541       if (! process_kill(torPid)) {
01542         VMessageBox::warning(this,
01543           tr("Password Reset Failed"),
01544           p(tr("Vidalia tried to reset Tor's control password, but was not "
01545                "able to restart the Tor software. Please check your Task "
01546                "Manager to ensure there are no other Tor processes running.")),
01547                VMessageBox::Ok|VMessageBox::Default);
01548       } else {
01549         retry = true;
01550       }
01551     }
01552   } else {
01553     /* Something else went wrong */
01554     int ret = VMessageBox::warning(this, 
01555                 tr("Authentication Error"),
01556                 p(tr("Vidalia was unable to authenticate to the Tor software. "
01557                      "(%1)").arg(errmsg)) + 
01558                 p(tr("Please check your control port authentication "
01559                      "settings.")),
01560                 VMessageBox::ShowSettings|VMessageBox::Default,
01561                 VMessageBox::Cancel|VMessageBox::Escape);
01562     
01563     if (ret == VMessageBox::ShowSettings)
01564       showConfigDialog(ConfigDialog::Advanced);
01565   }
01566   
01567   if (_torControl->isRunning())
01568     if (_isVidaliaRunningTor) 
01569       stop();
01570     else
01571       disconnect();
01572   if (retry)
01573     start();
01574 }
01575 
01576 /** Searches for and attempts to load the control authentication cookie. This
01577  * assumes the cookie is named 'control_auth_cookie'. If <b>cookiePath</b> is
01578  * empty, this method will search some default locations depending on the
01579  * current platform. <b>cookiePath</b> can point to either a cookie file or a
01580  * directory containing the cookie file. */
01581 QByteArray
01582 MainWindow::loadControlCookie(QString cookiePath)
01583 {
01584   QFile authCookie;
01585   QStringList pathList;
01586 
01587   if (!cookiePath.isEmpty()) {
01588     pathList << cookiePath;
01589   } else {
01590     /* Try some default locations */
01591     TorSettings settings;
01592     QString dataDir = settings.getDataDirectory();
01593     if (!dataDir.isEmpty())
01594       pathList << dataDir;
01595       
01596 #if defined(Q_WS_WIN)
01597     pathList << expand_filename("%APPDATA%\\Tor");
01598 #else
01599     pathList << expand_filename("~/.tor");
01600 #endif
01601   }
01602   
01603   /* Search for the cookie file */
01604   foreach (QString path, pathList) {
01605     QString cookieFile = QFileInfo(path).isFile() ?
01606                           path : path + "/control_auth_cookie";
01607     vDebug("Checking for authentication cookie in '%1'").arg(cookieFile);
01608     if (!QFileInfo(cookieFile).exists())
01609       continue;
01610     
01611     authCookie.setFileName(cookieFile);
01612     if (authCookie.open(QIODevice::ReadOnly)) {
01613       vInfo("Reading authentication cookie from '%1'").arg(cookieFile);
01614       return authCookie.readAll();
01615     } else {
01616       vWarn("Couldn't open cookie file '%1': %2")
01617         .arg(cookieFile).arg(authCookie.errorString());
01618     }
01619   }
01620   vWarn("Couldn't find a readable authentication cookie.");
01621   return QByteArray();
01622 }
01623 
01624 /** Called when Tor has successfully established a circuit. */
01625 void
01626 MainWindow::circuitEstablished()
01627 {
01628   updateTorStatus(CircuitEstablished);
01629   setStartupProgress(ui.progressBar->maximum(),
01630                      tr("Connected to the Tor network!"));
01631   startSubprocesses();
01632 
01633 #if defined(USE_AUTOUPDATE)
01634   VidaliaSettings settings;
01635   if (settings.isAutoUpdateEnabled()) {
01636     QDateTime lastCheckedAt = settings.lastCheckedForUpdates();
01637     if (UpdateProcess::shouldCheckForUpdates(lastCheckedAt)) {
01638       /* Initiate a background check for updates now */
01639       _updateTimer.stop();
01640       checkForUpdates();
01641     }
01642   }
01643 #endif
01644 }
01645 
01646 /** Checks the status of the current version of Tor to see if it's old,
01647  * unrecommended, or obsolete. */
01648 void
01649 MainWindow::checkTorVersion()
01650 {
01651   QString status;
01652   if (_torControl->getInfo("status/version/current", status)) {
01653     if (!status.compare("old", Qt::CaseInsensitive)
01654           || !status.compare("unrecommended", Qt::CaseInsensitive)
01655           || !status.compare("obsolete", Qt::CaseInsensitive)) {
01656       displayTorVersionWarning();
01657     }
01658   }
01659 }
01660 
01661 /** Called when Tor thinks its version is old or unrecommended, and displays
01662  * a message notifying the user. */
01663 void
01664 MainWindow::dangerousTorVersion(tc::TorVersionStatus reason,
01665                                 const QString &current,
01666                                 const QStringList &recommended)
01667 {
01668   Q_UNUSED(current);
01669   Q_UNUSED(recommended);
01670 
01671   if (reason == tc::ObsoleteTorVersion
01672         || reason == tc::UnrecommendedTorVersion)
01673     displayTorVersionWarning();
01674 }
01675 
01676 /** Called when Tor thinks its version is old or unrecommended, and displays a
01677  * message notifying the user. */
01678 void
01679 MainWindow::displayTorVersionWarning()
01680 {
01681   static bool alreadyWarned = false;
01682 
01683   if (!alreadyWarned) {
01684 #if !defined(USE_AUTOUPDATE)
01685     QString website = "https://www.torproject.org/";
01686 # if QT_VERSION >= 0x040200
01687     website = QString("<a href=\"%1\">%1</a>").arg(website);
01688 # endif
01689 
01690     VMessageBox::information(this, tr("Tor Update Available"),
01691       p(tr("The currently installed version of Tor is out of date or no longer "
01692            "recommended. Please visit the Tor website to download the latest "
01693            "version.")) + p(tr("Tor website: %1").arg(website)),
01694       VMessageBox::Ok);
01695 #else
01696     int ret = VMessageBox::information(this,
01697                 tr("Tor Update Available"),
01698                 p(tr("The currently installed version of Tor is out of date "
01699                      "or no longer recommended."))
01700                   + p(tr("Would you like to check if a newer package is "
01701                          "available for installation?")),
01702                 VMessageBox::Yes|VMessageBox::Default,
01703                 VMessageBox::No|VMessageBox::Escape);
01704 
01705     if (ret == VMessageBox::Yes)
01706       checkForUpdatesWithUi();
01707 #endif
01708     alreadyWarned = true;
01709   }
01710 }
01711 
01712 /** Called when Tor thinks the user has tried to connect to a port that
01713  * typically is used for unencrypted applications. Warns the user and allows
01714  * them to ignore future warnings on <b>port</b>. It is possible that Tor
01715  * will produce multiple asynchronous status events warning of dangerous ports
01716  * while the message box is displayed (for example, while the user is away
01717  * from the keyboard), so subsequent messages will be discarded until the
01718  * first message box is dismissed. */
01719 void
01720 MainWindow::warnDangerousPort(quint16 port, bool rejected)
01721 {
01722   static QMessageBox *dlg = 0;
01723 
01724   /* Don't display another message box until the first one is dismissed */
01725   if (dlg)
01726     return;
01727 
01728   QString application;
01729   switch (port) {
01730     case  23:
01731      application = tr("(probably Telnet)");
01732      break;
01733 
01734     case 109:
01735     case 110:
01736     case 143:
01737       application = tr("(probably an email client)"); 
01738       break;
01739 
01740     default:
01741       application = "";
01742   }
01743 
01744   QString text = tr("One of your applications %1 appears to be making a "
01745                     "potentially unencrypted and unsafe connection to port %2.")
01746                     .arg(application).arg(port);
01747 
01748   QString extraText = p(tr("Anything sent over this connection could be "
01749                            "monitored. Please check your application's "
01750                            "configuration and use only encrypted protocols, "
01751                            "such as SSL, if possible."));
01752   if (rejected) {
01753     extraText.append(p(tr("Tor has automatically closed your connection in "
01754                           "order to protect your anonymity.")));
01755   }
01756 
01757   dlg = new QMessageBox(QMessageBox::Warning,
01758                         tr("Potentially Unsafe Connection"), text,
01759                         QMessageBox::Ok | QMessageBox::Ignore);
01760   dlg->setInformativeText(extraText);
01761   dlg->setDefaultButton(QMessageBox::Ok);
01762   dlg->setEscapeButton(QMessageBox::Ok);
01763 
01764   int ret = dlg->exec();
01765   if (ret == QMessageBox::Ignore) {
01766     TorControl *tc = Vidalia::torControl();
01767     TorSettings settings;
01768     QStringList portList;
01769     QList<quint16> ports;
01770     int idx;
01771 
01772     ports = settings.getWarnPlaintextPorts();
01773     idx   = ports.indexOf(port);
01774     if (idx >= 0) {
01775       ports.removeAt(idx);
01776       settings.setWarnPlaintextPorts(ports);
01777 
01778       foreach (quint16 port, ports) {
01779         portList << QString::number(port);
01780       }
01781       tc->setConf("WarnPlaintextPorts", portList.join(","));
01782       portList.clear();
01783     }
01784 
01785     ports = settings.getRejectPlaintextPorts();
01786     idx   = ports.indexOf(port);
01787     if (idx >= 0) {
01788       ports.removeAt(idx);
01789       settings.setRejectPlaintextPorts(ports);
01790 
01791       foreach (quint16 port, ports) {
01792         portList << QString::number(port);
01793       }
01794       tc->setConf("RejectPlaintextPorts", portList.join(","));
01795     }
01796   }
01797   delete dlg;
01798   dlg = 0;
01799 }
01800 
01801 /** Creates and displays Vidalia's About dialog. */
01802 void
01803 MainWindow::showAboutDialog()
01804 {
01805   AboutDialog dlg(this);
01806   dlg.exec();
01807 }
01808 
01809 /** Displays the help browser and displays the most recently viewed help
01810  * topic. */
01811 void
01812 MainWindow::showHelpDialog()
01813 {
01814   showHelpDialog(QString());
01815 }
01816 
01817 /**< Shows the help browser and displays the given help <b>topic</b>. */
01818 void
01819 MainWindow::showHelpDialog(const QString &topic)
01820 {
01821   static HelpBrowser *helpBrowser = 0;
01822   if (!helpBrowser)
01823     helpBrowser = new HelpBrowser(this);
01824   helpBrowser->showWindow(topic);
01825 }
01826 
01827 /** Creates and displays the Configuration dialog with the current page set to
01828  * <b>page</b>. */
01829 void
01830 MainWindow::showConfigDialog(ConfigDialog::Page page)
01831 {
01832   _configDialog->showWindow(page);
01833 }
01834 
01835 /** Displays the Configuration dialog, set to the Server page. */
01836 void
01837 MainWindow::showServerConfigDialog()
01838 {
01839   showConfigDialog(ConfigDialog::Server);
01840 }
01841 
01842 /** Called when the user selects the "New Identity" action from the menu. */
01843 void
01844 MainWindow::newIdentity()
01845 {
01846   QString errmsg;
01847 
01848   /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM
01849    * is successful, we will show the result as a balloon. Otherwise, we'll 
01850    * just use a message box. */
01851   if (_torControl->signal(TorSignal::NewNym, &errmsg)) {
01852     /* NEWNYM signal was successful */
01853     QString title = tr("New Identity");
01854     QString message = tr("All subsequent connections will "
01855                          "appear to be different than your "
01856                          "old connections.");
01857 
01858     /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */
01859     _actionNewIdentity->setEnabled(false);
01860     ui.lblNewIdentity->setEnabled(false);
01861     QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, 
01862                        this, SLOT(enableNewIdentity()));
01863 
01864     if (QSystemTrayIcon::supportsMessages())
01865       _trayIcon.showMessage(title, message, QSystemTrayIcon::Information);
01866     else
01867       VMessageBox::information(this, title, message, VMessageBox::Ok);
01868   } else {
01869     /* NEWNYM signal failed */
01870     VMessageBox::warning(this, 
01871       tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok);
01872   }
01873 }
01874 
01875 /** Re-enables the 'New Identity' button after a delay from the previous time
01876  * 'New Identity' was used. */
01877 void
01878 MainWindow::enableNewIdentity()
01879 {
01880   if (_torControl->isConnected()) {
01881     _actionNewIdentity->setEnabled(true);
01882     ui.lblNewIdentity->setEnabled(true);
01883   }
01884 }
01885 
01886 /** Converts a TorStatus enum value to a string for debug logging purposes. */
01887 QString
01888 MainWindow::toString(TorStatus status)
01889 {
01890   switch (status) {
01891     /* These strings only appear in debug logs, so they should not be
01892      * translated. */
01893     case Unset:     return "Unset";
01894     case Stopping:  return "Stopping";
01895     case Stopped:   return "Stopped";
01896     case Starting:  return "Starting";
01897     case Started:   return "Started";
01898     case Authenticating:  return "Authenticating";
01899     case Authenticated:   return "Authenticated";
01900     case CircuitEstablished: return "Circuit Established";
01901     default: break;
01902   }
01903   return "Unknown";
01904 }
01905 
01906 #if defined(USE_MINIUPNPC)
01907 /** Called when a UPnP error occurs. */
01908 void
01909 MainWindow::upnpError(UPNPControl::UPNPError error)
01910 {
01911   Q_UNUSED(error);
01912 
01913 #if 0
01914   /* XXX: Is there a better way to do this? Currently, this could get called
01915    * if there is an error when testing UPnP support, and again when attempting
01916    * to reset the UPnP state when the test dialog is closed. The user would
01917    * not be amused with all the warning dialogs. */
01918 
01919   VMessageBox::warning(this,
01920     tr("Port Forwarding Failed"),
01921     p(tr("Vidalia was unable to configure automatic port forwarding."))
01922       + p(UPNPControl::Instance()->errorString()),
01923     VMessageBox::Ok);
01924 #endif
01925 }
01926 #endif
01927 
01928 #if defined(USE_AUTOUPDATE)
01929 /** Called when the user clicks the 'Check Now' button in the General
01930  * settings page. */
01931 void
01932 MainWindow::checkForUpdatesWithUi()
01933 {
01934   checkForUpdates(true);
01935 }
01936 
01937 /** Called when the update interval timer expires, notifying Vidalia that
01938  * we should check for updates again. */
01939 void
01940 MainWindow::checkForUpdates(bool showProgress)
01941 {
01942   VidaliaSettings settings;
01943 
01944   if (_updateProcess.isRunning()) {
01945     if (showProgress) {
01946       /* A check for updates is already in progress, so just bring the update
01947        * progress dialog into focus.
01948        */
01949       _updateProgressDialog.show();
01950     }
01951   } else {
01952     /* If Tor is running and bootstrapped, then use Tor to check for updates */
01953     if (_torControl->isRunning() && _torControl->circuitEstablished())
01954       _updateProcess.setSocksPort(_torControl->getSocksPort());
01955     else
01956       _updateProcess.setSocksPort(0);
01957 
01958     /* Initialize the UpdateProgressDialog and display it, if necessary. */
01959     _updateProgressDialog.setStatus(UpdateProgressDialog::CheckingForUpdates);
01960     if (showProgress)
01961       _updateProgressDialog.show();
01962 
01963     /* Initiate a check for available software updates. This check will
01964      * be done in the background, notifying the user only if there are
01965      * updates to be installed.
01966      */
01967     _updateProcess.checkForUpdates(UpdateProcess::TorBundleInfo);
01968 
01969     /* Remember when we last checked for software updates */
01970     settings.setLastCheckedForUpdates(QDateTime::currentDateTime().toUTC());
01971 
01972     /* Restart the "Check for Updates" timer */
01973     _updateTimer.start(UpdateProcess::checkForUpdatesInterval() * 1000);
01974   }
01975 }
01976 
01977 /** Called when the check for software updates fails. */
01978 void
01979 MainWindow::checkForUpdatesFailed(const QString &errmsg)
01980 {
01981   if (_updateProgressDialog.isVisible()) {
01982     _updateProgressDialog.hide();
01983     VMessageBox::warning(this, tr("Update Failed"), errmsg,
01984                          VMessageBox::Ok);
01985   }
01986 }
01987 
01988 /** Called when there is an update available for installation. */
01989 void
01990 MainWindow::updatesAvailable(UpdateProcess::BundleInfo bi,
01991                              const PackageList &packageList)
01992 {
01993   vInfo("%1 software update(s) available").arg(packageList.size());
01994   if (packageList.size() > 0) {
01995     UpdatesAvailableDialog dlg(packageList, &_updateProgressDialog);
01996 
01997     switch (dlg.exec()) {
01998       case UpdatesAvailableDialog::InstallUpdatesNow:
01999         installUpdates(bi);
02000         break;
02001 
02002       default:
02003         _updateProgressDialog.hide();
02004         break;
02005     }
02006   } else {
02007     if (_updateProgressDialog.isVisible()) {
02008       _updateProgressDialog.hide();
02009       VMessageBox::information(this, tr("Your software is up to date"),
02010                                tr("There are no new Tor software packages "
02011                                   "available for your computer at this time."),
02012                                VMessageBox::Ok);
02013     }
02014   }
02015 }
02016 
02017 /** Stops Tor (if necessary), installs any available for <b>bi</b>, and
02018  * restarts Tor (if necessary). */
02019 void
02020 MainWindow::installUpdates(UpdateProcess::BundleInfo bi)
02021 {
02022   _updateProgressDialog.setStatus(UpdateProgressDialog::InstallingUpdates);
02023   _updateProgressDialog.show();
02024 
02025   if (_isVidaliaRunningTor) {
02026     _restartTorAfterUpgrade = true;
02027     _isIntentionalExit = true;
02028     _torControl->stop();
02029   } else {
02030     _restartTorAfterUpgrade = false;
02031   }
02032   _updateProcess.installUpdates(bi);
02033 }
02034 
02035 /** Called when all <b>numUpdates</b> software updates have been installed
02036  * successfully. */
02037 void
02038 MainWindow::updatesInstalled(int numUpdates)
02039 {
02040   _updateProgressDialog.setStatus(UpdateProgressDialog::UpdatesInstalled);
02041   _updateProgressDialog.show();
02042 
02043   if (_restartTorAfterUpgrade)
02044     start();
02045 }
02046 
02047 /** Called when an update fails to install. <b>errmsg</b> contains details
02048  * about the failure. */
02049 void
02050 MainWindow::installUpdatesFailed(const QString &errmsg)
02051 {
02052   _updateProgressDialog.hide();
02053 
02054   VMessageBox::warning(this, tr("Installation Failed"),
02055                        p(tr("Vidalia was unable to install your software updates."))
02056                          + p(tr("The following error occurred:")) 
02057                          + p(errmsg),
02058                        VMessageBox::Ok);
02059 
02060   if (_restartTorAfterUpgrade)
02061     start();
02062 }
02063 
02064 #endif
02065 
02066 QStringList
02067 MainWindow::updateBrowserEnv() {
02068   TorSettings settings;
02069   QStringList env = QProcess::systemEnvironment();
02070   env << "TZ=UTC";
02071   env << "MOZ_NO_REMOTE=1";
02072 
02073   if(settings.autoControlPort()) {
02074     QString errmsg, socks;
02075     if(!(_torControl->getInfo("net/listeners/socks", socks, &errmsg))) {
02076       vInfo(errmsg);
02077       return env;
02078     }
02079 
02080     QStringList addrPort = socks.split(":");
02081     if(addrPort.size() != 2) return env;
02082 
02083     QHostAddress addr(addrPort.at(0));
02084     quint16 port = addrPort.at(1).toInt();
02085 
02086     env << QString("TOR_SOCKS_HOST=%1").arg(addr.toString());
02087     env << QString("TOR_SOCKS_PORT=%1").arg(port);
02088 
02089     vInfo(QString("Using automatic ControlPort and SocksPort configuration:\n"
02090                   "   ControlPort=%1\n   SocksPort=%2\n   Host=%3\n   Configuration file:%4")
02091           .arg(_autoControlPort)
02092           .arg(port)
02093           .arg(addrPort.at(0))
02094           .arg(QString("%1/port.conf").arg(expand_filename(settings.getDataDirectory()))));
02095   }
02096 
02097   if(settings.getAuthenticationMethod() == TorSettings::PasswordAuth) {
02098     env << QString("TOR_CONTROL_PASSWD=%1").arg(QString(_controlPassword.toAscii().toHex()));
02099     env << QString("TOR_CONTROL_PORT=%1").arg(_autoControlPort);
02100   }
02101 
02102   return env;
02103 }