Vidalia  0.2.21
MainWindow.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file MainWindow.cpp
13 ** \brief Main (hidden) window. Creates tray menu and child windows
14 **
15 ** Implements the main window. The main window is a hidden window that serves
16 ** as the parent of the tray icon and popup menu, as well as other application
17 ** dialogs.
18 */
19 
20 #include "MainWindow.h"
21 #include "Vidalia.h"
22 #include "VMessageBox.h"
24 #include "TorSettings.h"
25 #include "ServerSettings.h"
26 #ifdef USE_AUTOUPDATE
27 #include "UpdatesAvailableDialog.h"
28 #endif
29 
30 #include "ProtocolInfo.h"
31 
32 #include "net.h"
33 #include "file.h"
34 #include "html.h"
35 #include "stringutil.h"
36 #include "procutil.h"
37 
38 #include <QMenuBar>
39 #include <QTimer>
40 #include <QTextStream>
41 
42 #ifdef Q_WS_MAC
43 #include <Carbon/Carbon.h>
44 #endif
45 
46 #define IMG_BWGRAPH ":/images/16x16/utilities-system-monitor.png"
47 #define IMG_CONTROL_PANEL ":/images/16x16/system-run.png"
48 #define IMG_MESSAGELOG ":/images/16x16/format-justify-fill.png"
49 #define IMG_CONFIG ":/images/16x16/preferences-system.png"
50 #define IMG_IDENTITY ":/images/16x16/view-media-artist.png"
51 #define IMG_HELP ":/images/16x16/system-help.png"
52 #define IMG_ABOUT ":/images/16x16/help-about.png"
53 #define IMG_EXIT ":/images/16x16/application-exit.png"
54 #define IMG_NETWORK ":/images/16x16/applications-internet.png"
55 
56 #define IMG_START_TOR_16 ":/images/16x16/media-playback-start.png"
57 #define IMG_STOP_TOR_16 ":/images/16x16/media-playback-stop.png"
58 #define IMG_START_TOR_48 ":/images/48x48/media-playback-start.png"
59 #define IMG_STOP_TOR_48 ":/images/48x48/media-playback-stop.png"
60 #define IMG_TOR_STOPPED_48 ":/images/48x48/tor-off.png"
61 #define IMG_TOR_RUNNING_48 ":/images/48x48/tor-on.png"
62 #define IMG_TOR_STARTING_48 ":/images/48x48/tor-starting.png"
63 #define IMG_TOR_STOPPING_48 ":/images/48x48/tor-stopping.png"
64 
65 /* Decide which of our four sets of tray icons to use. */
66 #if defined(Q_WS_WIN)
67 /* QSystemTrayIcon on Windows wants 16x16 .png files */
68 #define IMG_TOR_STOPPED ":/images/16x16/tor-off.png"
69 #define IMG_TOR_RUNNING ":/images/16x16/tor-on.png"
70 #define IMG_TOR_STARTING ":/images/16x16/tor-starting.png"
71 #define IMG_TOR_STOPPING ":/images/16x16/tor-stopping.png"
72 #elif defined(Q_WS_MAC)
73 /* On Mac, the dock icons look best at 128x128, otherwise they get blurry
74  * if resized from a smaller image */
75 #define IMG_TOR_STOPPED ":/images/128x128/tor-off.png"
76 #define IMG_TOR_RUNNING ":/images/128x128/tor-on.png"
77 #define IMG_TOR_STARTING ":/images/128x128/tor-starting.png"
78 #define IMG_TOR_STOPPING ":/images/128x128/tor-stopping.png"
79 void qt_mac_set_dock_menu(QMenu *menu);
80 #else
81 /* On X11, we just use always the 22x22 .png files */
82 #define IMG_TOR_STOPPED ":/images/22x22/tor-off.png"
83 #define IMG_TOR_RUNNING ":/images/22x22/tor-on.png"
84 #define IMG_TOR_STARTING ":/images/22x22/tor-starting.png"
85 #define IMG_TOR_STOPPING ":/images/22x22/tor-stopping.png"
86 #endif
87 
88 /** Only allow 'New Identity' to be clicked once every 10 seconds. */
89 #define MIN_NEWIDENTITY_INTERVAL (10*1000)
90 
91 /* Startup progress milestones */
92 #define STARTUP_PROGRESS_STARTING 0
93 #define STARTUP_PROGRESS_CONNECTING 10
94 #define STARTUP_PROGRESS_AUTHENTICATING 20
95 #define STARTUP_PROGRESS_BOOTSTRAPPING 30
96 #define STARTUP_PROGRESS_CIRCUITBUILD 75
97 #define STARTUP_PROGRESS_MAXIMUM (STARTUP_PROGRESS_BOOTSTRAPPING+100)
98 
99 /** Default constructor. It installs an icon in the system tray area and
100  * creates the popup menu associated with that icon. */
102 : VidaliaWindow("MainWindow")
103 {
104  VidaliaSettings settings;
105 
106  ui.setupUi(this);
107 
108  _warnTimer = new QTimer();
109 
110  /* Pressing 'Esc' or 'Ctrl+W' will close the window */
111  Vidalia::createShortcut("Ctrl+W", this, ui.btnHide, SLOT(click()));
112  Vidalia::createShortcut("Esc", this, ui.btnHide, SLOT(click()));
113 
114  /* Create all the dialogs of which we only want one instance */
115  _messageLog = new MessageLog();
117  _netViewer = new NetViewer();
118  _configDialog = new ConfigDialog();
119  _menuBar = 0;
120  connect(_messageLog, SIGNAL(helpRequested(QString)),
121  this, SLOT(showHelpDialog(QString)));
122  connect(_netViewer, SIGNAL(helpRequested(QString)),
123  this, SLOT(showHelpDialog(QString)));
124  connect(_configDialog, SIGNAL(helpRequested(QString)),
125  this, SLOT(showHelpDialog(QString)));
126  connect(_configDialog, SIGNAL(restartTor()),
127  this, SLOT(restart()));
128 
129  /* Create the actions that will go in the tray menu */
130  createActions();
131  /* Creates a tray icon with a context menu and adds it to the system's
132  * notification area. */
133  createTrayIcon();
134  /* Start with Tor initially stopped */
135  _status = Unset;
136  _isVidaliaRunningTor = false;
138 
139  /* Create a new TorControl object, used to communicate with Tor */
141  connect(_torControl, SIGNAL(started()), this, SLOT(started()));
142  connect(_torControl, SIGNAL(startFailed(QString)),
143  this, SLOT(startFailed(QString)));
144  connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)),
145  this, SLOT(stopped(int, QProcess::ExitStatus)));
146  connect(_torControl, SIGNAL(connected()), this, SLOT(connected()));
147  connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected()));
148  connect(_torControl, SIGNAL(connectFailed(QString)),
149  this, SLOT(connectFailed(QString)));
150  connect(_torControl, SIGNAL(authenticated()), this, SLOT(authenticated()));
151  connect(_torControl, SIGNAL(authenticationFailed(QString)),
152  this, SLOT(authenticationFailed(QString)));
153 
156  QString, QStringList)),
158  QString, QStringList)));
159 
163  connect(_torControl, SIGNAL(circuitEstablished()),
164  this, SLOT(circuitEstablished()));
165  connect(_torControl, SIGNAL(dangerousPort(quint16, bool)),
166  this, SLOT(warnDangerousPort(quint16, bool)));
167  connect(_torControl, SIGNAL(logMessage(tc::Severity, QString)),
168  this, SLOT(log(tc::Severity, QString)));
169 
170  /* Create a new HelperProcess object, used to start the web browser */
171  _browserProcess = new HelperProcess(this);
172  connect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
173  this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
174  connect(_browserProcess, SIGNAL(startFailed(QString)),
175  this, SLOT(onBrowserFailed(QString)));
176 
177  /* Create a new HelperProcess object, used to start the IM client */
178  _imProcess = new HelperProcess(this);
179  connect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)),
180  this, SLOT(onSubprocessFinished(int, QProcess::ExitStatus)));
181  connect(_imProcess, SIGNAL(startFailed(QString)),
182  this, SLOT(onIMFailed(QString)));
183 
184  /* Create a new HelperProcess object, used to start the proxy server */
185  _proxyProcess = new HelperProcess(this);
186  connect(_proxyProcess, SIGNAL(startFailed(QString)),
187  this, SLOT(onProxyFailed(QString)));
188 
189  /* Catch signals when the application is running or shutting down */
190  connect(vApp, SIGNAL(running()), this, SLOT(running()));
191  connect(vApp, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
192 
193  connect(_warnTimer, SIGNAL(timeout()), this, SLOT(warnButton()));
194 
195 #if defined(USE_AUTOUPDATE)
196  /* Create a timer used to remind us to check for software updates */
197  connect(&_updateTimer, SIGNAL(timeout()), this, SLOT(checkForUpdates()));
198 
199  /* Also check for updates in the foreground when the user clicks the
200  * "Check Now" button in the config dialog. */
201  connect(_configDialog, SIGNAL(checkForUpdates()),
202  this, SLOT(checkForUpdatesWithUi()));
203 
204  /* The rest of these slots are called as the update process executes. */
205  connect(&_updateProcess, SIGNAL(downloadProgress(QString,int,int)),
206  &_updateProgressDialog, SLOT(setDownloadProgress(QString,int,int)));
207  connect(&_updateProcess, SIGNAL(updatesAvailable(UpdateProcess::BundleInfo,PackageList)),
208  this, SLOT(updatesAvailable(UpdateProcess::BundleInfo,PackageList)));
209  connect(&_updateProcess, SIGNAL(updatesInstalled(int)),
210  this, SLOT(updatesInstalled(int)));
211  connect(&_updateProcess, SIGNAL(installUpdatesFailed(QString)),
212  this, SLOT(installUpdatesFailed(QString)));
213  connect(&_updateProgressDialog, SIGNAL(cancelUpdate()),
214  &_updateProcess, SLOT(cancel()));
215 #endif
216 
217 #if defined(USE_MINIUPNPC)
218  /* Catch UPnP-related signals */
220  this, SLOT(upnpError(UPNPControl::UPNPError)));
221 #endif
222 
223  ui.chkShowOnStartup->setChecked(settings.showMainWindowAtStart());
224  if (ui.chkShowOnStartup->isChecked())
225  show();
226  /* Optimistically hope that the tray icon gets added. */
227  _trayIcon.show();
228 
229 #if defined(Q_WS_MAC)
230  /* Display OSX dock icon if icon preference is not set to "Tray Only" */
231  if (settings.getIconPref() != VidaliaSettings::Tray) {
232  ProcessSerialNumber psn = { 0, kCurrentProcess };
233  TransformProcessType(&psn, kProcessTransformToForegroundApplication);
234  }
235  /* Vidalia launched in background (LSUIElement=true). Bring to foreground. */
237 #endif
238 
239  _flashToggle = false;
240 }
241 
242 /** Destructor. */
244 {
245  _trayIcon.hide();
246  delete _messageLog;
247  delete _bandwidthGraph;
248  delete _netViewer;
249  delete _configDialog;
250 }
251 
252 void
254 {
255  if (visible) {
256  /* In Gnome, will hide buttons if Vidalia is run on startup. */
257  if (!QSystemTrayIcon::isSystemTrayAvailable()) {
258  /* Don't let people hide the main window, since that's all they have. */
259  ui.chkShowOnStartup->hide();
260  ui.btnHide->hide();
261  /* Causes window to not appear in Enlightenment. */
262  //setMinimumHeight(height()-ui.btnHide->height());
263  //setMaximumHeight(height()-ui.btnHide->height());
264  }
265  }
266  VidaliaWindow::setVisible(visible);
267 }
268 
269 void
271 {
272  ui.retranslateUi(this);
273 
275  if (_status == Stopped) {
276  _actionStartStopTor->setText(tr("Start Tor"));
277  ui.lblStartStopTor->setText(tr("Start Tor"));
278  } else if (_status == Starting) {
279  _actionStartStopTor->setText(tr("Starting Tor"));
280  ui.lblStartStopTor->setText(tr("Starting Tor"));
281  } else {
282  _actionStartStopTor->setText(tr("Stop Tor"));
283  ui.lblStartStopTor->setText(tr("Stop Tor"));
284  }
285 
286  _actionShowBandwidth->setText(tr("Bandwidth Graph"));
287  _actionShowMessageLog->setText(tr("Message Log"));
288  _actionShowNetworkMap->setText(tr("Network Map"));
289  _actionShowControlPanel->setText(tr("Control Panel"));
290  _actionShowHelp->setText(tr("Help"));
291  _actionNewIdentity->setText(tr("New Identity"));
292 
293 #if !defined(Q_WS_MAC)
294  _actionShowAbout->setText(tr("About"));
295  _actionShowConfig->setText(tr("Settings"));
296  _actionExit->setText(tr("Exit"));
297 #else
298  createMenuBar();
299 #endif
300 }
301 
302 /** Called when the application has started and the main event loop is
303  * running. */
304 void
306 {
307  VidaliaSettings settings;
308 
309  if (vApp->readPasswordFromStdin()) {
310  QTextStream in(stdin);
311  in >> _controlPassword;
312  _useSavedPassword = false;
313  } else {
314  /* Initialize _useSavedPassword to true. If Tor is already running when
315  * Vidalia starts, then there is no point in generating a random password.
316  * If Tor is not already running, then this will be set according to the
317  * current configuration in the start() method.
318  */
319  _useSavedPassword = true;
320  }
321 
322  if (settings.runTorAtStart()) {
323  /* If we're supposed to start Tor when Vidalia starts, then do it now */
324  start();
325  }
326 
327  /* Start the proxy server, if configured */
328  if (settings.runProxyAtStart())
329  startProxy();
330 
331 #if defined(USE_AUTOUPDATE)
332  if (settings.isAutoUpdateEnabled()) {
333  QDateTime lastCheckedAt = settings.lastCheckedForUpdates();
334  if (UpdateProcess::shouldCheckForUpdates(lastCheckedAt)) {
335  if (settings.runTorAtStart() && ! _torControl->isCircuitEstablished()) {
336  /* We started Tor but it hasn't bootstrapped yet, so give it a bit
337  * before we decide to check for updates. If Tor manages to build a
338  * circuit before this timer times out, we will stop the timer and
339  * launch a check for updates immediately. (see circuitEstablished()).
340  */
341  _updateTimer.start(5*60*1000);
342  } else {
343  /* Initiate a background check for updates now */
344  checkForUpdates();
345  }
346  } else {
347  /* Schedule the next time to check for updates */
348  QDateTime nextCheckAt = UpdateProcess::nextCheckForUpdates(lastCheckedAt);
349  QDateTime now = QDateTime::currentDateTime().toUTC();
350 
351  vInfo("Last checked for software updates at %1. Will check again at %2.")
352  .arg(lastCheckedAt.toLocalTime().toString("dd-MM-yyyy hh:mm:ss"))
353  .arg(nextCheckAt.toLocalTime().toString("dd-MM-yyyy hh:mm:ss"));
354  _updateTimer.start((nextCheckAt.toTime_t() - now.toTime_t()) * 1000);
355  }
356  }
357 #endif
358 }
359 
360 /** Terminate the Tor process if it is being run under Vidalia, disconnect all
361  * TorControl signals, and exit Vidalia. */
362 void
364 {
365  vNotice("Cleaning up before exiting.");
366 
368  /* Kill our Tor process now */
369  _torControl->stop();
370  }
371 
372  /* Disable port forwarding */
373  ServerSettings settings(_torControl);
374  settings.cleanupPortForwarding();
375 
376  if (_proxyProcess->state() != QProcess::NotRunning) {
377  /* Close the proxy server (Polipo ignores the WM_CLOSE event sent by
378  * terminate() so we have to kill() it) */
379  _proxyProcess->kill();
380  }
381 
382  /* Kill the browser and IM client if using the new launcher */
383  VidaliaSettings vidalia_settings;
384 
385  if (! vidalia_settings.getBrowserDirectory().isEmpty()) {
386  /* Disconnect the finished signals so that we won't try to exit Vidalia again */
387  QObject::disconnect(_browserProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
388  QObject::disconnect(_imProcess, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
389 
390  /* Use QProcess terminate function */
391  if (_browserProcess->state() == QProcess::Running)
392  _browserProcess->terminate();
393 
394 #if defined(Q_OS_WIN)
395  /* Kill any processes which might have been forked off */
397 #endif
398 
399  if (_imProcess->state() == QProcess::Running)
400  _imProcess->terminate();
401  }
402 
403  /* Disconnect all of the TorControl object's signals */
404  QObject::disconnect(_torControl, 0, 0, 0);
405 }
406 
407 /** Called when the application is closing, by selecting "Exit" from the tray
408  * menu. If we're running a Tor server, then ask if we want to kill Tor now,
409  * or do a delayed shutdown. */
410 void
412 {
414  /* If we're running a server currently, ask if we want to do a delayed
415  * shutdown. If we do, then close Vidalia only when Tor stops. Otherwise,
416  * kill Tor and bail now. */
417  ServerSettings settings(_torControl);
418  if (_torControl->isConnected() && settings.isServerEnabled()) {
419  connect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit()));
420  if (!stop())
421  QObject::disconnect(_torControl, SIGNAL(stopped()), vApp, SLOT(quit()));
422  return;
423  }
424  }
425  vApp->quit();
426 }
427 
428 /** Create and bind actions to events. Setup for initial
429  * tray menu configuration. */
430 void
432 {
433  _actionStartStopTor = new QAction(tr("Start Tor"), this);
434  connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(start()));
435 
436  _actionExit = new QAction(tr("Exit"), this);
437  connect(_actionExit, SIGNAL(triggered()), this, SLOT(close()));
438 
439  _actionShowBandwidth = new QAction(tr("Bandwidth Graph"), this);
440  connect(_actionShowBandwidth, SIGNAL(triggered()),
441  _bandwidthGraph, SLOT(showWindow()));
442  connect(ui.lblBandwidthGraph, SIGNAL(clicked()),
443  _bandwidthGraph, SLOT(showWindow()));
444 
445  _actionShowMessageLog = new QAction(tr("Message Log"), this);
446  connect(_actionShowMessageLog, SIGNAL(triggered()),
447  _messageLog, SLOT(showWindow()));
448  connect(ui.lblMessageLog, SIGNAL(clicked()),
449  _messageLog, SLOT(showWindow()));
450  connect(ui.lblMessageLog, SIGNAL(clicked()),
451  _warnTimer, SLOT(stop()));
452  connect(ui.lblMessageLog, SIGNAL(clicked()),
453  ui.lblMessageLog, SLOT(disableFlashing()));
454 
455  _actionShowNetworkMap = new QAction(tr("Network Map"), this);
456  connect(_actionShowNetworkMap, SIGNAL(triggered()),
457  _netViewer, SLOT(showWindow()));
458  connect(ui.lblViewNetwork, SIGNAL(clicked()),
459  _netViewer, SLOT(showWindow()));
460 
461  _actionShowControlPanel = new QAction(tr("Control Panel"), this);
462  connect(_actionShowControlPanel, SIGNAL(triggered()), this, SLOT(show()));
463 
464  _actionShowConfig = new QAction(tr("Settings"), this);
465  connect(_actionShowConfig, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
466 
467  _actionShowAbout = new QAction(tr("About"), this);
468  connect(_actionShowAbout, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
469 
470  _actionShowHelp = new QAction(tr("Help"), this);
471  connect(_actionShowHelp, SIGNAL(triggered()), this, SLOT(showHelpDialog()));
472  connect(ui.lblHelpBrowser, SIGNAL(clicked()), this, SLOT(showHelpDialog()));
473 
474  _actionNewIdentity = new QAction(tr("New Identity"), this);
475  _actionNewIdentity->setEnabled(false);
476  connect(_actionNewIdentity, SIGNAL(triggered()), this, SLOT(newIdentity()));
477 
478 #if !defined(Q_WS_MAC)
479  /* Don't give the menu items icons on OS X, since they end up in the
480  * application menu bar. Menu bar items on OS X typically do not have
481  * icons. */
482  _actionStartStopTor->setIcon(QIcon(IMG_START_TOR_16));
483  _actionExit->setIcon(QIcon(IMG_EXIT));
484  _actionShowBandwidth->setIcon(QIcon(IMG_BWGRAPH));
485  _actionShowMessageLog->setIcon(QIcon(IMG_MESSAGELOG));
486  _actionShowNetworkMap->setIcon(QIcon(IMG_NETWORK));
488  _actionShowConfig->setIcon(QIcon(IMG_CONFIG));
489  _actionShowAbout->setIcon(QIcon(IMG_ABOUT));
490  _actionShowHelp->setIcon(QIcon(IMG_HELP));
491  _actionNewIdentity->setIcon(QIcon(IMG_IDENTITY));
492 #endif
493 }
494 
495 /** Creates a tray icon with a context menu and adds it to the system
496  * notification area. On Mac, we also set up an application menubar. */
497 void
499 {
500  QMenu *menu = createTrayMenu();
501 
502  /* Add the menu it to the tray icon */
503  _trayIcon.setContextMenu(menu);
504 
505  connect(&_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
506  this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
507 
508 #if defined(Q_WS_MAC)
509  createMenuBar();
510  qt_mac_set_dock_menu(menu);
511 #endif
512 }
513 
514 /** Creates a QMenu object that contains QActions which compose the system
515  * tray menu. */
516 QMenu*
518 {
519  QMenu *menu = new QMenu(this);
520  menu->addAction(_actionStartStopTor);
521  menu->addSeparator();
522  menu->addAction(_actionShowBandwidth);
523  menu->addAction(_actionShowMessageLog);
524  menu->addAction(_actionShowNetworkMap);
525  menu->addAction(_actionNewIdentity);
526  menu->addSeparator();
527  menu->addAction(_actionShowControlPanel);
528 
529 #if !defined(Q_WS_MAC)
530  /* These aren't added to the dock menu on Mac, since they are in the
531  * standard Mac locations in the menu bar. */
532  menu->addAction(_actionShowConfig);
533  menu->addAction(_actionShowHelp);
534  menu->addAction(_actionShowAbout);
535  menu->addSeparator();
536  menu->addAction(_actionExit);
537 #endif
538  return menu;
539 }
540 
541 /** Creates a new menubar with no parent, so Qt will use this as the "default
542  * menubar" on Mac. This adds on to the existing actions from the createMens()
543  * method. */
544 void
546 {
547 #if defined(Q_WS_MAC)
548  /* Mac users sure like their shortcuts. Actions NOT mentioned below
549  * don't explicitly need shortcuts, since they are merged to the default
550  * menubar and get the default shortcuts anyway. */
551  _actionStartStopTor->setShortcut(tr("Ctrl+T"));
552  _actionShowBandwidth->setShortcut(tr("Ctrl+B"));
553  _actionShowMessageLog->setShortcut(tr("Ctrl+L"));
554  _actionShowNetworkMap->setShortcut(tr("Ctrl+N"));
555  _actionShowHelp->setShortcut(tr("Ctrl+?"));
556  _actionNewIdentity->setShortcut(tr("Ctrl+I"));
557  _actionShowControlPanel->setShortcut(tr("Ctrl+P"));
558 
559  /* Force Qt to put merge the Exit, Configure, and About menubar options into
560  * the default menu, even if Vidalia is currently not speaking English. */
561  _actionShowConfig->setText("config");
562  _actionShowConfig->setMenuRole(QAction::PreferencesRole);
563  _actionShowAbout->setText("about");
564  _actionShowAbout->setMenuRole(QAction::AboutRole);
565  _actionExit->setText("quit");
566  _actionExit->setMenuRole(QAction::QuitRole);
567 
568  /* The File, Help, and Configure menus will get merged into the application
569  * menu by Qt. */
570  if (_menuBar)
571  delete _menuBar;
572  _menuBar = new QMenuBar(0);
573  QMenu *fileMenu = _menuBar->addMenu("File");
574  fileMenu->addAction(_actionExit);
575  fileMenu->addAction(_actionShowConfig);
576 
577  QMenu *torMenu = _menuBar->addMenu(tr("Tor"));
578  torMenu->addAction(_actionStartStopTor);
579  torMenu->addSeparator();
580  torMenu->addAction(_actionNewIdentity);
581 
582  QMenu *viewMenu = _menuBar->addMenu(tr("View"));
583  viewMenu->addAction(_actionShowControlPanel);
584  viewMenu->addSeparator();
585  viewMenu->addAction(_actionShowBandwidth);
586  viewMenu->addAction(_actionShowMessageLog);
587  viewMenu->addAction(_actionShowNetworkMap);
588 
589  QMenu *helpMenu = _menuBar->addMenu(tr("Help"));
590  _actionShowHelp->setText(tr("Vidalia Help"));
591  helpMenu->addAction(_actionShowHelp);
592  helpMenu->addAction(_actionShowAbout);
593 #endif
594 }
595 
596 /** Sets the current tray or dock icon image to <b>iconFile</b>. */
597 void
598 MainWindow::setTrayIcon(const QString &iconFile)
599 {
600 #if defined(Q_WS_MAC)
601  VidaliaSettings settings;
602  QApplication::setWindowIcon(QPixmap(iconFile));
603  /* only display tray icon if icon preference is not set to "Dock Only" */
604  if (settings.getIconPref() != VidaliaSettings::Dock)
605  _trayIcon.setIcon(QIcon(iconFile));
606 #else
607  /* always display tray icon for other platforms */
608  _trayIcon.setIcon(QIcon(iconFile));
609 #endif
610 }
611 
612 /** Respond to a double-click on the tray icon by opening the Control Panel
613  * window. */
614 void
615 MainWindow::trayIconActivated(QSystemTrayIcon::ActivationReason reason)
616 {
617  if (reason == QSystemTrayIcon::DoubleClick)
618  setVisible(true);
619 }
620 
621 /** Start a web browser when given the directory containing the executable and profile */
622 void
624 {
625  VidaliaSettings settings;
626 
627  /** Directory for the browser */
628  QString browserDirectory = settings.getBrowserDirectory();
629  if(QDir(browserDirectory).isRelative())
630  browserDirectory = QDir(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()
631  + "/" + browserDirectory)).canonicalPath();
632 
633  /** Relative path to the browser executable from the browserDirectory */
634  QString browserExecutable = QDir::toNativeSeparators(browserDirectory + "/" + settings.getBrowserExecutable());
635 
636  /** Relative path to profile from the browserDirectory */
637  QString profileDirectory = QDir::toNativeSeparators(settings.getProfileDirectory());
638  /** Default profile to copy from */
639  QString defaultProfileDirectory = QDir::toNativeSeparators(settings.getDefaultProfileDirectory());
640 
641  /** Relative path to the plugins directory from the browserDirectory */
642  QString pluginsDirectory = QDir::toNativeSeparators(settings.getPluginsDirectory());
643  /** Relative path to the default plugins directory from the browserDirectory */
644  QString defaultPluginsDirectory = QDir::toNativeSeparators(settings.getDefaultPluginsDirectory());
645 
646  QString profileDir = QDir(browserDirectory + "/" + profileDirectory).canonicalPath();
647 
648  _browserProcess->setEnvironment(updateBrowserEnv());
649 
650  QDir browserDirObj = QDir(browserDirectory);
651 
652  /* Copy the profile directory if it's not already there */
653  if (!browserDirObj.exists(profileDirectory)) {
654  browserDirObj.mkdir(profileDirectory);
655  copy_dir(QDir(browserDirectory + "/" + defaultProfileDirectory).canonicalPath(),
656  QDir(browserDirectory + "/" + profileDirectory).canonicalPath());
657  }
658 
659  /* Copy the pluginss directory if it's not already there */
660  if (!browserDirObj.exists(pluginsDirectory)) {
661  browserDirObj.mkdir(pluginsDirectory);
662  copy_dir(QDir(browserDirectory + "/" + defaultPluginsDirectory).canonicalPath(),
663  QDir(browserDirectory + "/" + pluginsDirectory).canonicalPath());
664  }
665 
666  /* Build the command line arguments */
667  QStringList commandLine;
668  // Is this better or worse than MOZ_NO_REMOTE?
669  commandLine << "-no-remote";
670  commandLine << "-profile";
671  commandLine << profileDir;
672 
673  /* Launch the browser */
674  if(!_browserProcess->state() != QProcess::NotRunning)
675  _browserProcess->start(browserExecutable, commandLine);
677 }
678 
679 /** Starts the web browser and IM client, if appropriately configured */
680 void
682 {
683  VidaliaSettings settings;
684  QString subprocess;
685 
686  /* Launch the web browser */
687  if (!(subprocess = settings.getBrowserDirectory()).isEmpty()) {
688  /* The user has set BrowserDirectory; use this */
690  } else if (!(subprocess = settings.getBrowserExecutable()).isEmpty()) {
691  /* BrowserDirectory is not set, but BrowserExecutable is; use this */
692  _browserProcess->setEnvironment(updateBrowserEnv());
693  if(!_browserProcess->state() != QProcess::NotRunning)
694  _browserProcess->start(subprocess, QStringList());
696  }
697 
698  /* Launch the IM client */
699  subprocess = settings.getIMExecutable();
700 
701  if (!subprocess.isEmpty())
702  _imProcess->start(subprocess, QStringList());
703 }
704 
705 /** Called when browser or IM client have exited */
706 void
707 MainWindow::onSubprocessFinished(int exitCode, QProcess::ExitStatus exitStatus)
708 {
709  Q_UNUSED(exitCode)
710  Q_UNUSED(exitStatus)
711 
712  /* Get path to browser and IM client */
713  VidaliaSettings settings;
714  QString browserExecutable = settings.getBrowserExecutable();
715  QString browserDirectory = settings.getBrowserDirectory();
716  QString imExecutable = settings.getIMExecutable();
717 
718  /* A subprocess is finished if it successfully exited or was never asked to start */
719  bool browserDone = (browserExecutable.isEmpty()
720  && browserDirectory.isEmpty())
721  || _browserProcess->isDone();
722  bool imDone = imExecutable.isEmpty() || _imProcess->isDone();
723 
724  /* Exit if both subprocesses are finished */
725  if (browserDone && imDone) {
726  if (browserDirectory.isEmpty()) {
727  /* We are using the standard launcher, exit immediately */
728  vApp->quit();
729  } else {
730  /* We are using the alternate launcher, wait until the browser has really died */
731  QTimer *browserWatcher = new QTimer(this);
732  connect(browserWatcher, SIGNAL(timeout()), this, SLOT(onCheckForBrowser()));
733  browserWatcher->start(2000);
734  }
735  }
736 }
737 
738 /** Called periodically to check if the browser is running. If it is not,
739  * exit Vidalia cleanly */
740 void
742 {
743 /* This only works on Windows for now */
744 #if defined(Q_OS_WIN)
745 
746  VidaliaSettings settings;
747  QString browserDirectoryFilename = settings.getBrowserExecutable();
748 
749  /* Get list of running processes */
750  QHash<qint64, QString> procList = win32_process_list();
751 
752  /* On old versions of Windows win32_process_list() will return
753  an empty list. In this case, just keep Vidalia open */
754  if (procList.isEmpty()) {
755  return;
756  }
757 
758  /* Loop over all processes or until we find <browserDirectoryFilename> */
759  QHashIterator<qint64, QString> i(procList);
760  while (i.hasNext()) {
761  i.next();
762  if (i.value().toLower() == browserDirectoryFilename) {
763  /* The browser is still running, so Vidalia should keep running too */
764  return;
765  }
766  }
767 
768  /* The browser isn't running, exit Vidalia */
769  vApp->quit();
770 #endif
771 }
772 
773 /** Called when the web browser failed to start, for example, because the path
774  * specified to the web browser executable didn't lead to an executable. */
775 void
777 {
778  Q_UNUSED(errmsg);
779 
780  /* Display an error message and see if the user wants some help */
781  VMessageBox::warning(this, tr("Error starting web browser"),
782  tr("Vidalia was unable to start the configured web browser"),
783  VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
784 }
785 
786 /** Called when the IM client failed to start, for example, because the path
787  * specified to the IM client executable didn't lead to an executable. */
788 void
789 MainWindow::onIMFailed(QString errmsg)
790 {
791  Q_UNUSED(errmsg);
792 
793  /* Display an error message and see if the user wants some help */
794  VMessageBox::warning(this, tr("Error starting IM client"),
795  tr("Vidalia was unable to start the configured IM client"),
796  VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
797 }
798 
799 /** Starts the proxy server, if appropriately configured */
800 void
802 {
803  VidaliaSettings settings;
804  QString executable = settings.getProxyExecutable();
805  _proxyProcess->start(executable, settings.getProxyExecutableArguments());
806 }
807 
808 /** Called when the proxy server fails to start, for example, because
809  * the path specified didn't lead to an executable. */
810 void
812 {
813  Q_UNUSED(errmsg);
814 
815  /* Display an error message and see if the user wants some help */
816  VMessageBox::warning(this, tr("Error starting proxy server"),
817  tr("Vidalia was unable to start the configured proxy server"),
818  VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape);
819 }
820 
821 /** Called when Tor's bootstrapping status changes. <b>bse</b> represents
822  * Tor's current estimate of its bootstrapping progress. */
823 void
825 {
826  int percentComplete = STARTUP_PROGRESS_BOOTSTRAPPING + bs.percentComplete();
827  bool warn = (bs.severity() == tc::WarnSeverity &&
829 
830  QString description;
831  switch (bs.status()) {
833  description = tr("Connecting to a relay directory");
834  break;
837  description = tr("Establishing an encrypted directory connection");
838  break;
840  description = tr("Retrieving network status");
841  break;
843  description = tr("Loading network status");
844  break;
846  description = tr("Loading authority certificates");
847  break;
849  description = tr("Requesting relay information");
850  break;
852  description = tr("Loading relay information");
853  break;
855  description = tr("Connecting to the Tor network");
856  break;
859  description = tr("Establishing a Tor circuit");
860  break;
862  description = tr("Connected to the Tor network!");
863  warn = false; /* probably false anyway */
864  break;
865  default:
866  description = bs.description();
867  }
868  if (warn) {
869  QString reason;
870  /* Is it really a good idea to translate these? */
871  switch (bs.reason()) {
873  reason = tr("miscellaneous");
874  break;
876  reason = tr("identity mismatch");
877  break;
878  case tc::ConnectionDone:
879  reason = tr("done");
880  break;
882  reason = tr("connection refused");
883  break;
885  reason = tr("connection timeout");
886  break;
888  reason = tr("read/write error");
889  break;
890  case tc::NoRouteToHost:
891  reason = tr("no route to host");
892  break;
894  reason = tr("insufficient resources");
895  break;
896  default:
897  reason = tr("unknown");
898  }
899  description += tr(" failed (%1)").arg(reason);
900  }
901  setStartupProgress(percentComplete, description);
902 }
903 
904 /** Updates the UI to reflect Tor's current <b>status</b>. Returns the
905  * previously set TorStatus value.*/
908 {
909  QString statusText, actionText;
910  QString trayIconFile, statusIconFile;
911  TorStatus prevStatus = _status;
912 
913  vNotice("Tor status changed from '%1' to '%2'.")
914  .arg(toString(prevStatus)).arg(toString(status));
915  _status = status;
916 
917  if (status == Stopped) {
918  statusText = tr("Tor is not running");
919  actionText = tr("Start Tor");
920  trayIconFile = IMG_TOR_STOPPED;
921  statusIconFile = IMG_TOR_STOPPED_48;
922  _actionStartStopTor->setEnabled(true);
923  _actionStartStopTor->setText(actionText);
924  _actionStartStopTor->setIcon(QIcon(IMG_START_TOR_16));
925  ui.lblStartStopTor->setEnabled(true);
926  ui.lblStartStopTor->setText(actionText);
927  ui.lblStartStopTor->setPixmap(QPixmap(IMG_START_TOR_48));
928  ui.lblStartStopTor->setStatusTip(actionText);
929 
930  /* XXX: This might need to be smarter if we ever start connecting other
931  * slots to these triggered() and clicked() signals. */
932  QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
933  QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
934  connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(start()));
935  connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(start()));
937  } else if (status == Stopping) {
939  statusText = tr("Your relay is shutting down.\n"
940  "Click 'Stop' again to stop your relay now.");
941  } else {
942  statusText = tr("Tor is shutting down");
943  }
944  trayIconFile = IMG_TOR_STOPPING;
945  statusIconFile = IMG_TOR_STOPPING_48;
946 
947  ui.lblStartStopTor->setStatusTip(tr("Stop Tor Now"));
948  } else if (status == Started) {
949  actionText = tr("Stop Tor");
950  _actionStartStopTor->setEnabled(true);
951  _actionStartStopTor->setText(actionText);
952  _actionStartStopTor->setIcon(QIcon(IMG_STOP_TOR_16));
953  ui.lblStartStopTor->setEnabled(true);
954  ui.lblStartStopTor->setText(actionText);
955  ui.lblStartStopTor->setPixmap(QPixmap(IMG_STOP_TOR_48));
956  ui.lblStartStopTor->setStatusTip(actionText);
957 
958  /* XXX: This might need to be smarter if we ever start connecting other
959  * slots to these triggered() and clicked() signals. */
960  QObject::disconnect(_actionStartStopTor, SIGNAL(triggered()), this, 0);
961  QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
962  connect(_actionStartStopTor, SIGNAL(triggered()), this, SLOT(stop()));
963  connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(stop()));
964  } else if (status == Starting) {
965  statusText = tr("Starting the Tor software");
966  trayIconFile = IMG_TOR_STARTING;
967  statusIconFile = IMG_TOR_STARTING_48;
968  _actionStartStopTor->setEnabled(false);
969  ui.lblStartStopTor->setText(tr("Starting Tor"));
970  ui.lblStartStopTor->setEnabled(false);
971  ui.lblStartStopTor->setStatusTip(statusText);
974  } else if (status == CircuitEstablished) {
975  statusText = tr("Connected to the Tor network!");
976  trayIconFile = IMG_TOR_RUNNING;
977  statusIconFile = IMG_TOR_RUNNING_48;
979  }
980 
981  /* Update the tray icon */
982  if (!trayIconFile.isEmpty()) {
983  setTrayIcon(trayIconFile);
984  }
985  /* Update the status banner on the control panel */
986  if (!statusIconFile.isEmpty())
987  ui.lblTorStatusImg->setPixmap(QPixmap(statusIconFile));
988  if (!statusText.isEmpty()) {
989  _trayIcon.setToolTip(statusText);
990  ui.lblTorStatus->setText(statusText);
991  }
992  return prevStatus;
993 }
994 
995 /** Called when the "show on startup" checkbox is toggled. */
996 void
998 {
999  VidaliaSettings settings;
1000  settings.setShowMainWindowAtStart(checked);
1001 }
1002 
1003 /** Sets the visibility of the startup status description and progress bar to
1004  * <b>visible</b>. */
1005 void
1007 {
1008  /* XXX: We force a repaint() to make sure the progress bar and onion status
1009  * icon don't overlap briefly. This is pretty hacktastic. */
1010  if (visible) {
1011  ui.lblTorStatus->setVisible(false);
1012  ui.lblTorStatusImg->setVisible(false);
1013  repaint(ui.grpStatus->rect());
1014  ui.lblStartupProgress->setVisible(true);
1015  ui.progressBar->setVisible(true);
1016  } else {
1017  ui.lblStartupProgress->setVisible(false);
1018  ui.progressBar->setVisible(false);
1019  repaint(ui.grpStatus->rect());
1020  ui.lblTorStatus->setVisible(true);
1021  ui.lblTorStatusImg->setVisible(true);
1022  }
1023 }
1024 
1025 /** Sets the progress bar completion value to <b>progressValue</b> and sets
1026  * the status text to <b>description</b>. */
1027 void
1029  const QString &description)
1030 {
1031  ui.progressBar->setValue(progressValue);
1032  ui.lblStartupProgress->setText(description);
1033  _trayIcon.setToolTip(description);
1034 }
1035 
1036 /** Attempts to start Tor. If Tor fails to start, then startFailed() will be
1037  * called with an error message containing the reason. */
1038 void
1040 {
1041  TorSettings settings;
1042  QStringList args;
1043 
1045 
1046  // Disable autoconfiguration if there are missing config data
1047  if(settings.autoControlPort()) {
1048  if(settings.getDataDirectory().isEmpty()) {
1049  vWarn("Disabling ControlPort autoconfiguration. DataDirectory is empty!");
1050  settings.setAutoControlPort(false);
1051  }
1052  }
1053 
1054  /* Check if Tor is already running separately */
1055  if(settings.getControlMethod() == ControlMethod::Port) {
1056  if(!settings.autoControlPort() && net_test_connect(settings.getControlAddress(),
1057  settings.getControlPort())) {
1058  started();
1059  return;
1060  }
1061  } else {
1062  if (socket_test_connect(settings.getSocketPath())) {
1063  started();
1064  return;
1065  }
1066  }
1067 
1068  QString torrc = settings.getTorrc();
1069  QFileInfo torrcInfo(torrc);
1070 
1071  if(QDir(torrcInfo.filePath()).isRelative()) {
1072  torrc = QCoreApplication::applicationDirPath() + "/" + torrc;
1073 
1074  QFileInfo newTorrcInfo(torrc);
1075  if(!newTorrcInfo.exists() and torrcInfo.exists()) {
1076  torrc = QDir(QCoreApplication::applicationDirPath()).relativeFilePath(torrcInfo.absoluteFilePath());
1077  vWarn("Automigrating configuration for Torrc:\nOld path: %1\nNew path: %2")
1078  .arg(newTorrcInfo.filePath())
1079  .arg(torrc);
1080  settings.setTorrc(torrc);
1081  torrc = QCoreApplication::applicationDirPath() + "/" + torrc;
1082  }
1083  }
1084 
1085  if(settings.bootstrap()) {
1086  QString boottorrc = settings.bootstrapFrom();
1087  vNotice(tr("Bootstrapping torrc from %1 to %2")
1088  .arg(boottorrc).arg(torrc));
1089  if(QFileInfo(boottorrc).exists()) {
1090  if(QFile::copy(boottorrc, torrc)) {
1091  settings.setBootstrap(false);
1092  }
1093  }
1094  }
1095 
1096  /* Make sure the torrc we want to use really exists. */
1097  if (!torrc.isEmpty()) {
1098  if (!QFileInfo(torrc).exists())
1099  touch_file(torrc, true);
1100  args << "-f" << torrc;
1101  }
1102 
1103  /* Specify Tor's data directory, if different from the default */
1104  QString dataDirectory = settings.getDataDirectory();
1105  QFileInfo dataDirectoryInfo(dataDirectory);
1106 
1107  if(not dataDirectory.isEmpty() and QDir(dataDirectory).isRelative()) {
1108  dataDirectory = QCoreApplication::applicationDirPath() + "/" + dataDirectory;
1109 
1110  QFileInfo newDataDirectoryInfo(dataDirectory);
1111  if(!newDataDirectoryInfo.exists() and dataDirectoryInfo.exists()) {
1112  dataDirectory = QDir(QCoreApplication::applicationDirPath()).relativeFilePath(dataDirectoryInfo.absoluteFilePath());
1113  vWarn("Automigrating configuration for DataDirectory:\nOld path: %1\nNew path: %2")
1114  .arg(newDataDirectoryInfo.absoluteFilePath())
1115  .arg(dataDirectory);
1116  settings.setDataDirectory(dataDirectory);
1117  dataDirectory = QCoreApplication::applicationDirPath() + "/" + dataDirectory;
1118  }
1119  }
1120 
1121  QString expDataDirectory = QDir(expand_filename(dataDirectory)).canonicalPath();
1122  if (!dataDirectory.isEmpty())
1123  args << "DataDirectory" << expDataDirectory;
1124 
1125  if(settings.getControlMethod() == ControlMethod::Port) {
1126  if(settings.autoControlPort()) {
1127  QString dataDirectory = settings.getDataDirectory();
1128  if(QDir(dataDirectory).isRelative())
1129  dataDirectory = QCoreApplication::applicationDirPath() + "/" + dataDirectory;
1130 
1131  QString relativePortConf = QDir(QDir::currentPath())
1132  .relativeFilePath(QString("%1/port.conf").arg(dataDirectory));
1133 
1134 #if defined(Q_WS_WIN)
1135  QString torPath = settings.getExecutable();
1136  if(QDir(torPath).isRelative())
1137  torPath = QCoreApplication::applicationDirPath() + "/" + torPath;
1138  relativePortConf = QDir(torPath).relativeFilePath(QString("%1/port.conf").arg(dataDirectory));
1139 #endif
1140  QString portconf = QString("%1/port.conf").arg(expDataDirectory);
1141  if(!QFile::remove(portconf))
1142  vWarn(QString("Unable to remove %1, may be it didn't existed.").arg(portconf));
1143 
1144  args << "ControlPort" << "auto";
1145  args << "SocksPort" << "auto";
1146  args << "ControlPortWriteToFile" << relativePortConf;
1147  } else {
1148  /* Add the intended control port value */
1149  quint16 controlPort = settings.getControlPort();
1150  if (controlPort)
1151  args << "ControlPort" << QString::number(controlPort);
1152  }
1153  } else {
1154  QString path = settings.getSocketPath();
1155  args << "ControlSocket" << path;
1156  }
1157 
1158  args << "__OwningControllerProcess" << QString::number(QCoreApplication::applicationPid());
1159 
1160  /* Add the control port authentication arguments */
1161  switch (settings.getAuthenticationMethod()) {
1163  if (! vApp->readPasswordFromStdin()) {
1164  if (settings.useRandomPassword()) {
1166  _useSavedPassword = false;
1167  } else {
1168  _controlPassword = settings.getControlPassword();
1169  _useSavedPassword = true;
1170  }
1171  }
1172  args << "HashedControlPassword"
1174  break;
1176  args << "CookieAuthentication" << "1";
1177  break;
1178  default:
1179  args << "CookieAuthentication" << "0";
1180  }
1181 
1182  /* This doesn't get set to false until Tor is actually up and running, so we
1183  * don't yell at users twice if their Tor doesn't even start, due to the fact
1184  * that QProcess::stopped() is emitted even if the process didn't even
1185  * start. */
1186  _isIntentionalExit = true;
1187  /* Kick off the Tor process */
1188  QString torExecutable = settings.getExecutable();
1189  QFileInfo torExecutableInfo(torExecutable);
1190 
1191  if(QDir(torExecutableInfo.filePath()).isRelative()) {
1192  torExecutable = QCoreApplication::applicationDirPath() + "/" + torExecutable;
1193 
1194  QFileInfo newTorExecutableInfo(torExecutable);
1195  if(!newTorExecutableInfo.exists() and torExecutableInfo.exists()) {
1196  torExecutable = QDir(QCoreApplication::applicationDirPath()).relativeFilePath(torExecutableInfo.absoluteFilePath());
1197  vWarn("Automigrating configuration for TorExecutable:\nOld path: %1\nNew path: %2")
1198  .arg(newTorExecutableInfo.filePath())
1199  .arg(torExecutable);
1200  settings.setExecutable(torExecutable);
1201  torExecutable = QCoreApplication::applicationDirPath() + "/" + torExecutable;
1202  }
1203  }
1204 
1205  _torControl->start(torExecutable, args);
1206 }
1207 
1208 /** Called when the user changes a setting that needs Tor restarting */
1209 void
1211 {
1212  if(_torControl->stop()) {
1213  start();
1214  }
1215 }
1216 
1217 /** Called when the Tor process fails to start, for example, because the path
1218  * specified to the Tor executable didn't lead to an executable. */
1219 void
1221 {
1222  /* We don't display the error message for now, because the error message
1223  * that Qt gives us in this instance is almost always "Unknown Error". That
1224  * will make users sad. */
1225  Q_UNUSED(errmsg);
1226 
1228 
1229  /* Display an error message and see if the user wants some help */
1230  int response = VMessageBox::warning(this, tr("Error Starting Tor"),
1231  tr("Vidalia was unable to start Tor. Check your settings "
1232  "to ensure the correct name and location of your Tor "
1233  "executable is specified."),
1234  VMessageBox::ShowSettings|VMessageBox::Default,
1235  VMessageBox::Cancel|VMessageBox::Escape,
1237 
1238  if (response == VMessageBox::ShowSettings) {
1239  /* Show the settings dialog so the user can make sure they're pointing to
1240  * the correct Tor. */
1241  showConfigDialog();
1242  } else if (response == VMessageBox::Help) {
1243  /* Show troubleshooting information about starting Tor */
1244  showHelpDialog("troubleshooting.start");
1245  }
1246 }
1247 
1248 /** Slot: Called when the Tor process is started. It will connect the control
1249  * socket and set the icons and tooltips accordingly. */
1250 void
1252 {
1253  TorSettings settings;
1254 
1256 
1257  /* Now that Tor is running, we want to know if it dies when we didn't want
1258  * it to. */
1259  _isIntentionalExit = false;
1260  /* We haven't started a delayed shutdown yet. */
1261  _delayedShutdownStarted = false;
1262  /* Remember whether we started Tor or not */
1264  /* Try to connect to Tor's control port */
1265  if(settings.autoControlPort()) {
1266  QString dataDirectory = settings.getDataDirectory();
1267  if(QDir(dataDirectory).isRelative())
1268  dataDirectory = QCoreApplication::applicationDirPath() + "/" + dataDirectory;
1269 
1270  QFile file(QString("%1/port.conf").arg(QDir(expand_filename(dataDirectory)).canonicalPath()));
1271  int tries = 0, maxtries = 5;
1272  while((!file.open(QIODevice::ReadOnly | QIODevice::Text)) and
1273  (tries++ < maxtries)) {
1274  vWarn(QString("This is try number: %1.").arg(tries));
1275 #if defined(Q_WS_WIN)
1276  Sleep(1000);
1277 #else
1278  sleep(1);
1279 #endif
1280  }
1281 
1282  if(tries >= maxtries) {
1283  vWarn("Couldn't read port.conf file");
1284  if(_torControl->isRunning()) {
1285  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")
1286  .arg(file.fileName())
1287  .arg(file.errorString()));
1288  } else {
1289  vWarn("Tor isn't running!");
1290  connectFailed(tr("It seems Tor has stopped running since Vidalia started it.\n\nSee the Advanced Message Log for more information."));
1291  }
1292 
1293  return;
1294  }
1295 
1296  QTextStream in(&file);
1297  if(!in.atEnd()) {
1298  QString line = in.readLine();
1299  QStringList parts = line.split("=");
1300  if(parts.size() != 2) return;
1301  if(parts[0].trimmed() != "PORT") return;
1302 
1303  QStringList addrPort = parts[1].split(":");
1304  if(addrPort.size() != 2) return;
1305 
1306  QHostAddress addr(addrPort.at(0));
1307  _autoControlPort = addrPort.at(1).toInt();
1308  _torControl->connect(addr, _autoControlPort);
1309  }
1310 
1311  file.close();
1312  } else {
1313  /* Try to connect to Tor's control port */
1314  if(settings.getControlMethod() == ControlMethod::Port) {
1316  settings.getControlPort());
1317  _autoControlPort = settings.getControlPort();
1318  } else
1319  _torControl->connect(settings.getSocketPath());
1320  }
1321  setStartupProgress(STARTUP_PROGRESS_CONNECTING, tr("Connecting to Tor"));
1322 }
1323 
1324 /** Called when the connection to the control socket fails. The reason will be
1325  * given in the errmsg parameter. */
1326 void
1328 {
1329  /* Ok, ok. It really isn't going to connect. I give up. */
1330  int response = VMessageBox::warning(this,
1331  tr("Connection Error"), p(errmsg),
1332  VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape,
1334 
1335 
1336  if (response == VMessageBox::Retry) {
1337  /* Let's give it another try. */
1338  TorSettings settings;
1340  settings.getControlPort());
1341  } else {
1342  /* Show the help browser (if requested) */
1343  if (response == VMessageBox::Help)
1344  showHelpDialog("troubleshooting.connect");
1345  /* Since Vidalia can't connect, we can't really do much, so stop Tor. */
1346  _torControl->stop();
1347  }
1348 }
1349 
1350 /** Disconnects the control socket and stops the Tor process. */
1351 bool
1353 {
1354  ServerSettings server(_torControl);
1355  QString errmsg;
1356  TorStatus prevStatus;
1357  bool rc;
1358 
1359  /* If we're running a server, give users the option of terminating
1360  * gracefully so clients have time to find new servers. */
1361  if (server.isServerEnabled() && !_delayedShutdownStarted) {
1362  /* Ask the user if they want to shutdown nicely. */
1363  int response = VMessageBox::question(this, tr("Relaying is Enabled"),
1364  tr("You are currently running a relay. "
1365  "Terminating your relay will interrupt any "
1366  "open connections from clients.\n\n"
1367  "Would you like to shutdown gracefully and "
1368  "give clients time to find a new relay?"),
1369  VMessageBox::Yes|VMessageBox::Default,
1371  VMessageBox::Cancel|VMessageBox::Escape);
1372  if (response == VMessageBox::Yes)
1373  _delayedShutdownStarted = true;
1374  else if (response == VMessageBox::Cancel)
1375  return false;
1376  }
1377 
1378  prevStatus = updateTorStatus(Stopping);
1380  /* Start a delayed shutdown */
1381  rc = _torControl->signal(TorSignal::Shutdown, &errmsg);
1382  } else {
1383  /* We want Tor to stop now, regardless of whether we're a server. */
1384  _isIntentionalExit = true;
1385  rc = _torControl->stop(&errmsg);
1386  }
1387 
1388  if (!rc) {
1389  /* We couldn't tell Tor to stop, for some reason. */
1390  int response = VMessageBox::warning(this, tr("Error Shutting Down"),
1391  p(tr("Vidalia was unable to stop the Tor software."))
1392  + p(errmsg),
1393  VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape,
1395 
1396  if (response == VMessageBox::Help) {
1397  /* Show some troubleshooting help */
1398  showHelpDialog("troubleshooting.stop");
1399  }
1400  /* Tor is still running since stopping failed */
1401  _isIntentionalExit = false;
1402  _delayedShutdownStarted = false;
1403  updateTorStatus(prevStatus);
1404  }
1405  return rc;
1406 }
1407 
1408 /** Slot: Called when the Tor process has exited. It will adjust the tray
1409  * icons and tooltips accordingly. */
1410 void
1411 MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus)
1412 {
1414 
1415  /* If we didn't intentionally close Tor, then check to see if it crashed or
1416  * if it closed itself and returned an error code. */
1417  if (!_isIntentionalExit) {
1418  /* A quick overview of Tor's code tells me that if it catches a SIGTERM or
1419  * SIGINT, Tor will exit(0). We might need to change this warning message
1420  * if this turns out to not be the case. */
1421  if (exitStatus == QProcess::CrashExit || exitCode != 0) {
1422  int ret = VMessageBox::warning(this, tr("Unexpected Error"),
1423  tr("Vidalia detected that the Tor software exited "
1424  "unexpectedly.\n\n"
1425  "Please check the message log for recent "
1426  "warning or error messages."),
1427  VMessageBox::Ok|VMessageBox::Escape,
1428  VMessageBox::ShowLog|VMessageBox::Default,
1430  if (ret == VMessageBox::ShowLog)
1432  else if (ret == VMessageBox::Help)
1433  showHelpDialog("troubleshooting.torexited");
1434  }
1435  }
1436 }
1437 
1438 /** Called when the control socket has successfully connected to Tor. */
1439 void
1441 {
1442  authenticate();
1444  QString err;
1445  if(!_torControl->takeOwnership(&err))
1446  vWarn(err);
1447  }
1448 }
1449 
1450 /** Called when Vidalia wants to disconnect from a Tor it did not start. */
1451 void
1453 {
1455 }
1456 
1457 /** Called when the control socket has been disconnected. */
1458 void
1460 {
1461  if (!_isVidaliaRunningTor) {
1462  /* If we didn't start our own Tor process, interpret losing the control
1463  * connection as "Tor is stopped". */
1465  }
1466 
1467  /*XXX We should warn here if we get disconnected when we didn't intend to */
1468  _actionNewIdentity->setEnabled(false);
1469  ui.lblNewIdentity->setEnabled(false);
1470  _isVidaliaRunningTor = false;
1471 }
1472 
1473 /** Attempts to authenticate to Tor's control port, depending on the
1474  * authentication method specified in TorSettings::getAuthenticationMethod().
1475  */
1476 bool
1478 {
1480  TorSettings settings;
1481  ProtocolInfo pi;
1482 
1485  tr("Authenticating to Tor"));
1486 
1487  authMethod = settings.getAuthenticationMethod();
1488  pi = _torControl->protocolInfo();
1489  if (!pi.isEmpty()) {
1490  QStringList authMethods = pi.authMethods();
1491  if (authMethods.contains("COOKIE"))
1492  authMethod = TorSettings::CookieAuth;
1493  else if (authMethods.contains("HASHEDPASSWORD"))
1494  authMethod = TorSettings::PasswordAuth;
1495  else if (authMethods.contains("NULL"))
1496  authMethod = TorSettings::NullAuth;
1497  }
1498 
1499  if (authMethod == TorSettings::CookieAuth) {
1500  /* Try to load an auth cookie and send it to Tor */
1501  QByteArray cookie = loadControlCookie(pi.cookieAuthFile());
1502  while (cookie.isEmpty()) {
1503  /* Prompt the user to find their control_auth_cookie */
1504  int ret = VMessageBox::question(this,
1505  tr("Cookie Authentication Required"),
1506  p(tr("The Tor software requires Vidalia to send the "
1507  "contents of an authentication cookie, but Vidalia "
1508  "was unable to find one."))
1509  + p(tr("Would you like to browse for the file "
1510  "'control_auth_cookie' yourself?")),
1511  VMessageBox::Browse|VMessageBox::Default,
1512  VMessageBox::Cancel|VMessageBox::Escape);
1513 
1514  if (ret == VMessageBox::Cancel)
1515  goto cancel;
1516  QString cookieDir = QFileDialog::getOpenFileName(this,
1517  tr("Data Directory"),
1518  settings.getDataDirectory(),
1519  tr("Control Cookie (control_auth_cookie)"));
1520  if (cookieDir.isEmpty())
1521  goto cancel;
1522  cookieDir = QFileInfo(cookieDir).absolutePath();
1523  cookie = loadControlCookie(cookieDir);
1524  }
1525  if(cookie.size() != 32) {
1526  vWarn(QString("Cookie length has to be exactly 32 bytes long. Found %1 bytes")
1527  .arg(cookie.size()));
1528  goto cancel;
1529  }
1530  vNotice("Authenticating using 'cookie' authentication.");
1531  return _torControl->authenticate(cookie);
1532  } else if (authMethod == TorSettings::PasswordAuth) {
1533  /* Get the control password and send it to Tor */
1534  vNotice("Authenticating using 'hashed password' authentication.");
1535  if (_useSavedPassword) {
1536  TorSettings settings;
1537  _controlPassword = settings.getControlPassword();
1538  }
1540  }
1541  /* No authentication. Send an empty password. */
1542  vNotice("Authenticating using 'null' authentication.");
1543  return _torControl->authenticate(QString(""));
1544 
1545 cancel:
1546  vWarn("Cancelling control authentication attempt.");
1548  stop();
1549  else
1550  disconnect();
1551  return false;
1552 }
1553 
1554 /** Called when Vidalia has successfully authenticated to Tor. */
1555 void
1557 {
1558  ServerSettings serverSettings(_torControl);
1559  QString errmsg;
1560 
1562 
1563  /* If Tor doesn't have bootstrapping events, then update the current
1564  * status string and bump the progress bar along a bit. */
1565  if (_torControl->getTorVersion() < 0x020101) {
1567  tr("Connecting to the Tor network"));
1568  }
1569 
1570  /* Let people click on their beloved "New Identity" button */
1571  _actionNewIdentity->setEnabled(true);
1572  ui.lblNewIdentity->setEnabled(true);
1573 
1574  /* Register for any pertinent asynchronous events. */
1575  if (!_torControl->setEvents(&errmsg)) {
1576  VMessageBox::warning(this, tr("Error Registering for Events"),
1577  p(tr("Vidalia was unable to register for some events. "
1578  "Many of Vidalia's features may be unavailable."))
1579  + p(errmsg),
1580  VMessageBox::Ok);
1581  } else {
1582  /* Stop reading from Tor's stdout immediately, since we successfully
1583  * registered for Tor events, including any desired log events. */
1585  }
1586 
1587  /* Configure UPnP port forwarding if needed */
1588  serverSettings.configurePortForwarding();
1589 
1590  /* Check if Tor has a circuit established */
1593  /* Check the status of Tor's version */
1594  if (_torControl->getTorVersion() >= 0x020001)
1595  checkTorVersion();
1596  if (_torControl->getTorVersion() >= 0x020102) {
1598  if (status.isValid())
1599  bootstrapStatusChanged(status);
1600  }
1601 }
1602 
1603 /** Called when Vidalia fails to authenticate to Tor. The failure reason is
1604  * specified in <b>errmsg</b>. */
1605 void
1607 {
1608  bool retry = false;
1609 
1610  vWarn("Authentication failed: %1").arg(errmsg);
1611 
1612  /* Parsing log messages is evil, but we're left with little option */
1613  if (errmsg.contains("Password did not match")) {
1615  connect(&dlg, SIGNAL(helpRequested(QString)),
1616  this, SLOT(showHelpDialog(QString)));
1617 
1618  qint64 torPid = 0;
1619 
1620 #if defined(Q_OS_WIN32)
1621  QHash<qint64, QString> procs = process_list();
1622  foreach (qint64 pid, procs.keys()) {
1623  if (! procs.value(pid).compare("tor.exe", Qt::CaseInsensitive)) {
1624  torPid = pid;
1625  break;
1626  }
1627  }
1628  dlg.setResetEnabled(torPid > 0);
1629 #else
1630  dlg.setResetEnabled(false);
1631 #endif
1632 
1633  int ret = dlg.exec();
1634  if (ret == QDialogButtonBox::Ok) {
1635  if (dlg.isSavePasswordChecked()) {
1636  TorSettings settings;
1638  settings.setUseRandomPassword(false);
1639  settings.setControlPassword(dlg.password());
1640  _useSavedPassword = true;
1641  } else {
1642  _controlPassword = dlg.password();
1643  _useSavedPassword = false;
1644  }
1645  retry = true;
1646  } else if (ret == QDialogButtonBox::Reset) {
1647  if (! process_kill(torPid)) {
1648  VMessageBox::warning(this,
1649  tr("Password Reset Failed"),
1650  p(tr("Vidalia tried to reset Tor's control password, but was not "
1651  "able to restart the Tor software. Please check your Task "
1652  "Manager to ensure there are no other Tor processes running.")),
1653  VMessageBox::Ok|VMessageBox::Default);
1654  } else {
1655  retry = true;
1656  }
1657  }
1658  } else {
1659  /* Something else went wrong */
1660  int ret = VMessageBox::warning(this,
1661  tr("Authentication Error"),
1662  p(tr("Vidalia was unable to authenticate to the Tor software. "
1663  "(%1)").arg(errmsg)) +
1664  p(tr("Please check your control port authentication "
1665  "settings.")),
1666  VMessageBox::ShowSettings|VMessageBox::Default,
1667  VMessageBox::Cancel|VMessageBox::Escape);
1668 
1669  if (ret == VMessageBox::ShowSettings)
1671  }
1672 
1673  if (_torControl->isRunning())
1675  stop();
1676  else
1677  disconnect();
1678  if (retry)
1679  start();
1680 }
1681 
1682 /** Searches for and attempts to load the control authentication cookie. This
1683  * assumes the cookie is named 'control_auth_cookie'. If <b>cookiePath</b> is
1684  * empty, this method will search some default locations depending on the
1685  * current platform. <b>cookiePath</b> can point to either a cookie file or a
1686  * directory containing the cookie file. */
1687 QByteArray
1689 {
1690  QFile authCookie;
1691  QStringList pathList;
1692 
1693  if (!cookiePath.isEmpty()) {
1694  pathList << cookiePath;
1695  } else {
1696  /* Try some default locations */
1697  TorSettings settings;
1698  QString dataDir = settings.getDataDirectory();
1699  if (!dataDir.isEmpty())
1700  pathList << dataDir;
1701 
1702 #if defined(Q_WS_WIN)
1703  pathList << expand_filename("%APPDATA%\\Tor");
1704 #else
1705  pathList << expand_filename("~/.tor");
1706 #endif
1707  }
1708 
1709  /* Search for the cookie file */
1710  foreach (QString path, pathList) {
1711  QString cookieFile = QFileInfo(path).isFile() ?
1712  path : path + "/control_auth_cookie";
1713  vDebug("Checking for authentication cookie in '%1'").arg(cookieFile);
1714  if (!QFileInfo(cookieFile).exists())
1715  continue;
1716 
1717  authCookie.setFileName(cookieFile);
1718  if (authCookie.open(QIODevice::ReadOnly)) {
1719  vInfo("Reading authentication cookie from '%1'").arg(cookieFile);
1720  return authCookie.readAll();
1721  } else {
1722  vWarn("Couldn't open cookie file '%1': %2")
1723  .arg(cookieFile).arg(authCookie.errorString());
1724  }
1725  }
1726  vWarn("Couldn't find a readable authentication cookie.");
1727  return QByteArray();
1728 }
1729 
1730 /** Called when Tor has successfully established a circuit. */
1731 void
1733 {
1735  setStartupProgress(ui.progressBar->maximum(),
1736  tr("Connected to the Tor network!"));
1738 
1739 #if defined(USE_AUTOUPDATE)
1740  VidaliaSettings settings;
1741  if (settings.isAutoUpdateEnabled()) {
1742  QDateTime lastCheckedAt = settings.lastCheckedForUpdates();
1743  if (UpdateProcess::shouldCheckForUpdates(lastCheckedAt)) {
1744  /* Initiate a background check for updates now */
1745  _updateTimer.stop();
1746  checkForUpdates();
1747  }
1748  }
1749 #endif
1750 }
1751 
1752 /** Checks the status of the current version of Tor to see if it's old,
1753  * unrecommended, or obsolete. */
1754 void
1756 {
1757  VidaliaSettings settings;
1758  if(settings.skipVersionCheck())
1759  return;
1760  QString status;
1761  if (_torControl->getInfo("status/version/current", status)) {
1762  if (!status.compare("old", Qt::CaseInsensitive)
1763  || !status.compare("unrecommended", Qt::CaseInsensitive)
1764  || !status.compare("obsolete", Qt::CaseInsensitive)) {
1766  }
1767  }
1768 }
1769 
1770 /** Called when Tor thinks its version is old or unrecommended, and displays
1771  * a message notifying the user. */
1772 void
1774  const QString &current,
1775  const QStringList &recommended)
1776 {
1777  Q_UNUSED(current);
1778  Q_UNUSED(recommended);
1779 
1780  if (reason == tc::ObsoleteTorVersion
1781  || reason == tc::UnrecommendedTorVersion)
1783 }
1784 
1785 /** Called when Tor thinks its version is old or unrecommended, and displays a
1786  * message notifying the user. */
1787 void
1789 {
1790  static bool alreadyWarned = false;
1791 
1792  if (!alreadyWarned) {
1793 #if !defined(USE_AUTOUPDATE)
1794  QString website = "https://www.torproject.org/";
1795 # if QT_VERSION >= 0x040200
1796  website = QString("<a href=\"%1\">%1</a>").arg(website);
1797 # endif
1798 
1799  VMessageBox::information(this, tr("Tor Update Available"),
1800  p(tr("The currently installed version of Tor is out of date or no longer "
1801  "recommended. Please visit the Tor website to download the latest "
1802  "version.")) + p(tr("Tor website: %1").arg(website)),
1803  VMessageBox::Ok);
1804 #else
1805  int ret = VMessageBox::information(this,
1806  tr("Tor Update Available"),
1807  p(tr("The currently installed version of Tor is out of date "
1808  "or no longer recommended."))
1809  + p(tr("Would you like to check if a newer package is "
1810  "available for installation?")),
1811  VMessageBox::Yes|VMessageBox::Default,
1812  VMessageBox::No|VMessageBox::Escape);
1813 
1814  if (ret == VMessageBox::Yes)
1815  checkForUpdatesWithUi();
1816 #endif
1817  alreadyWarned = true;
1818  }
1819 }
1820 
1821 /** Called when Tor thinks the user has tried to connect to a port that
1822  * typically is used for unencrypted applications. Warns the user and allows
1823  * them to ignore future warnings on <b>port</b>. It is possible that Tor
1824  * will produce multiple asynchronous status events warning of dangerous ports
1825  * while the message box is displayed (for example, while the user is away
1826  * from the keyboard), so subsequent messages will be discarded until the
1827  * first message box is dismissed. */
1828 void
1829 MainWindow::warnDangerousPort(quint16 port, bool rejected)
1830 {
1831  static QMessageBox *dlg = 0;
1832 
1833  /* Don't display another message box until the first one is dismissed */
1834  if (dlg)
1835  return;
1836 
1837  QString application;
1838  switch (port) {
1839  case 23:
1840  application = tr("(probably Telnet)");
1841  break;
1842 
1843  case 109:
1844  case 110:
1845  case 143:
1846  application = tr("(probably an email client)");
1847  break;
1848 
1849  default:
1850  application = "";
1851  }
1852 
1853  QString text = tr("One of your applications %1 appears to be making a "
1854  "potentially unencrypted and unsafe connection to port %2.")
1855  .arg(application).arg(port);
1856 
1857  QString extraText = p(tr("Anything sent over this connection could be "
1858  "monitored. Please check your application's "
1859  "configuration and use only encrypted protocols, "
1860  "such as SSL, if possible."));
1861  if (rejected) {
1862  extraText.append(p(tr("Tor has automatically closed your connection in "
1863  "order to protect your anonymity.")));
1864  }
1865 
1866  dlg = new QMessageBox(QMessageBox::Warning,
1867  tr("Potentially Unsafe Connection"), text,
1868  QMessageBox::Ok | QMessageBox::Ignore);
1869  dlg->setInformativeText(extraText);
1870  dlg->setDefaultButton(QMessageBox::Ok);
1871  dlg->setEscapeButton(QMessageBox::Ok);
1872 
1873  int ret = dlg->exec();
1874  if (ret == QMessageBox::Ignore) {
1876  TorSettings settings;
1877  QStringList portList;
1878  QList<quint16> ports;
1879  int idx;
1880 
1881  ports = settings.getWarnPlaintextPorts();
1882  idx = ports.indexOf(port);
1883  if (idx >= 0) {
1884  ports.removeAt(idx);
1885  settings.setWarnPlaintextPorts(ports);
1886 
1887  foreach (quint16 port, ports) {
1888  portList << QString::number(port);
1889  }
1890  tc->setConf("WarnPlaintextPorts", portList.join(","));
1891  portList.clear();
1892  }
1893 
1894  ports = settings.getRejectPlaintextPorts();
1895  idx = ports.indexOf(port);
1896  if (idx >= 0) {
1897  ports.removeAt(idx);
1898  settings.setRejectPlaintextPorts(ports);
1899 
1900  foreach (quint16 port, ports) {
1901  portList << QString::number(port);
1902  }
1903  tc->setConf("RejectPlaintextPorts", portList.join(","));
1904  }
1905  }
1906  delete dlg;
1907  dlg = 0;
1908 }
1909 
1910 /** Creates and displays Vidalia's About dialog. */
1911 void
1913 {
1914  AboutDialog dlg(this);
1915  dlg.exec();
1916 }
1917 
1918 /** Displays the help browser and displays the most recently viewed help
1919  * topic. */
1920 void
1922 {
1923  showHelpDialog(QString());
1924 }
1925 
1926 /**< Shows the help browser and displays the given help <b>topic</b>. */
1927 void
1928 MainWindow::showHelpDialog(const QString &topic)
1929 {
1930  static HelpBrowser *helpBrowser = 0;
1931  if (!helpBrowser)
1932  helpBrowser = new HelpBrowser(this);
1933  helpBrowser->showWindow(topic);
1934 }
1935 
1936 /** Creates and displays the Configuration dialog with the current page set to
1937  * <b>page</b>. */
1938 void
1940 {
1941  _configDialog->showWindow(page);
1942 }
1943 
1944 /** Displays the Configuration dialog, set to the Server page. */
1945 void
1947 {
1949 }
1950 
1951 /** Called when the user selects the "New Identity" action from the menu. */
1952 void
1954 {
1955  QString errmsg;
1956 
1957  /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM
1958  * is successful, we will show the result as a balloon. Otherwise, we'll
1959  * just use a message box. */
1960  if (_torControl->signal(TorSignal::NewNym, &errmsg)) {
1961  /* NEWNYM signal was successful */
1962  QString title = tr("New Identity");
1963  QString message = tr("All subsequent connections will "
1964  "appear to be different than your "
1965  "old connections.");
1966 
1967  /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */
1968  _actionNewIdentity->setEnabled(false);
1969  ui.lblNewIdentity->setEnabled(false);
1970  QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL,
1971  this, SLOT(enableNewIdentity()));
1972 
1973  if (QSystemTrayIcon::supportsMessages())
1974  _trayIcon.showMessage(title, message, QSystemTrayIcon::Information);
1975  else
1976  VMessageBox::information(this, title, message, VMessageBox::Ok);
1977  } else {
1978  /* NEWNYM signal failed */
1979  VMessageBox::warning(this,
1980  tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok);
1981  }
1982 }
1983 
1984 /** Re-enables the 'New Identity' button after a delay from the previous time
1985  * 'New Identity' was used. */
1986 void
1988 {
1989  if (_torControl->isConnected()) {
1990  _actionNewIdentity->setEnabled(true);
1991  ui.lblNewIdentity->setEnabled(true);
1992  }
1993 }
1994 
1995 /** Converts a TorStatus enum value to a string for debug logging purposes. */
1996 QString
1998 {
1999  switch (status) {
2000  /* These strings only appear in debug logs, so they should not be
2001  * translated. */
2002  case Unset: return "Unset";
2003  case Stopping: return "Stopping";
2004  case Stopped: return "Stopped";
2005  case Starting: return "Starting";
2006  case Started: return "Started";
2007  case Authenticating: return "Authenticating";
2008  case Authenticated: return "Authenticated";
2009  case CircuitEstablished: return "Circuit Established";
2010  default: break;
2011  }
2012  return "Unknown";
2013 }
2014 
2015 #if defined(USE_MINIUPNPC)
2016 /** Called when a UPnP error occurs. */
2017 void
2018 MainWindow::upnpError(UPNPControl::UPNPError error)
2019 {
2020  Q_UNUSED(error);
2021 
2022 #if 0
2023  /* XXX: Is there a better way to do this? Currently, this could get called
2024  * if there is an error when testing UPnP support, and again when attempting
2025  * to reset the UPnP state when the test dialog is closed. The user would
2026  * not be amused with all the warning dialogs. */
2027 
2028  VMessageBox::warning(this,
2029  tr("Port Forwarding Failed"),
2030  p(tr("Vidalia was unable to configure automatic port forwarding."))
2031  + p(UPNPControl::Instance()->errorString()),
2032  VMessageBox::Ok);
2033 #endif
2034 }
2035 #endif
2036 
2037 #if defined(USE_AUTOUPDATE)
2038 /** Called when the user clicks the 'Check Now' button in the General
2039  * settings page. */
2040 void
2041 MainWindow::checkForUpdatesWithUi()
2042 {
2043  checkForUpdates(true);
2044 }
2045 
2046 /** Called when the update interval timer expires, notifying Vidalia that
2047  * we should check for updates again. */
2048 void
2049 MainWindow::checkForUpdates(bool showProgress)
2050 {
2051  VidaliaSettings settings;
2052 
2053  if (_updateProcess.isRunning()) {
2054  if (showProgress) {
2055  /* A check for updates is already in progress, so just bring the update
2056  * progress dialog into focus.
2057  */
2058  _updateProgressDialog.show();
2059  }
2060  } else {
2061  /* If Tor is running and bootstrapped, then use Tor to check for updates */
2063  _updateProcess.setSocksPort(_torControl->getSocksPort());
2064  else
2065  _updateProcess.setSocksPort(0);
2066 
2067  /* Initialize the UpdateProgressDialog and display it, if necessary. */
2068  _updateProgressDialog.setStatus(UpdateProgressDialog::CheckingForUpdates);
2069  if (showProgress)
2070  _updateProgressDialog.show();
2071 
2072  /* Initiate a check for available software updates. This check will
2073  * be done in the background, notifying the user only if there are
2074  * updates to be installed.
2075  */
2076  _updateProcess.checkForUpdates(UpdateProcess::TorBundleInfo);
2077 
2078  /* Remember when we last checked for software updates */
2079  settings.setLastCheckedForUpdates(QDateTime::currentDateTime().toUTC());
2080 
2081  /* Restart the "Check for Updates" timer */
2082  _updateTimer.start(UpdateProcess::checkForUpdatesInterval() * 1000);
2083  }
2084 }
2085 
2086 /** Called when the check for software updates fails. */
2087 void
2088 MainWindow::checkForUpdatesFailed(const QString &errmsg)
2089 {
2090  if (_updateProgressDialog.isVisible()) {
2091  _updateProgressDialog.hide();
2092  VMessageBox::warning(this, tr("Update Failed"), errmsg,
2093  VMessageBox::Ok);
2094  }
2095 }
2096 
2097 /** Called when there is an update available for installation. */
2098 void
2099 MainWindow::updatesAvailable(UpdateProcess::BundleInfo bi,
2100  const PackageList &packageList)
2101 {
2102  vInfo("%1 software update(s) available").arg(packageList.size());
2103  if (packageList.size() > 0) {
2104  UpdatesAvailableDialog dlg(packageList, &_updateProgressDialog);
2105 
2106  switch (dlg.exec()) {
2108  installUpdates(bi);
2109  break;
2110 
2111  default:
2112  _updateProgressDialog.hide();
2113  break;
2114  }
2115  } else {
2116  if (_updateProgressDialog.isVisible()) {
2117  _updateProgressDialog.hide();
2118  VMessageBox::information(this, tr("Your software is up to date"),
2119  tr("There are no new Tor software packages "
2120  "available for your computer at this time."),
2121  VMessageBox::Ok);
2122  }
2123  }
2124 }
2125 
2126 /** Stops Tor (if necessary), installs any available for <b>bi</b>, and
2127  * restarts Tor (if necessary). */
2128 void
2129 MainWindow::installUpdates(UpdateProcess::BundleInfo bi)
2130 {
2131  _updateProgressDialog.setStatus(UpdateProgressDialog::InstallingUpdates);
2132  _updateProgressDialog.show();
2133 
2134  if (_isVidaliaRunningTor) {
2135  _restartTorAfterUpgrade = true;
2136  _isIntentionalExit = true;
2137  _torControl->stop();
2138  } else {
2139  _restartTorAfterUpgrade = false;
2140  }
2141  _updateProcess.installUpdates(bi);
2142 }
2143 
2144 /** Called when all <b>numUpdates</b> software updates have been installed
2145  * successfully. */
2146 void
2147 MainWindow::updatesInstalled(int numUpdates)
2148 {
2149  _updateProgressDialog.setStatus(UpdateProgressDialog::UpdatesInstalled);
2150  _updateProgressDialog.show();
2151 
2152  if (_restartTorAfterUpgrade)
2153  start();
2154 }
2155 
2156 /** Called when an update fails to install. <b>errmsg</b> contains details
2157  * about the failure. */
2158 void
2159 MainWindow::installUpdatesFailed(const QString &errmsg)
2160 {
2161  _updateProgressDialog.hide();
2162 
2163  VMessageBox::warning(this, tr("Installation Failed"),
2164  p(tr("Vidalia was unable to install your software updates."))
2165  + p(tr("The following error occurred:"))
2166  + p(errmsg),
2167  VMessageBox::Ok);
2168 
2169  if (_restartTorAfterUpgrade)
2170  start();
2171 }
2172 
2173 #endif
2174 
2175 QStringList
2177  TorSettings settings;
2178  QStringList env = QProcess::systemEnvironment();
2179  env << "TZ=UTC";
2180  env << "MOZ_NO_REMOTE=1";
2181 
2182  if(settings.autoControlPort()) {
2183  QString errmsg, socks;
2184  if(!(_torControl->getInfo("net/listeners/socks", socks, &errmsg))) {
2185  vInfo(errmsg);
2186  return env;
2187  }
2188 
2189  QStringList addrPort = socks.split(":");
2190  if(addrPort.size() != 2) return env;
2191 
2192  QHostAddress addr(addrPort.at(0));
2193  quint16 port = addrPort.at(1).toInt();
2194 
2195  env << QString("TOR_SOCKS_HOST=%1").arg(addr.toString());
2196  env << QString("TOR_SOCKS_PORT=%1").arg(port);
2197 
2198  vInfo(QString("Using automatic ControlPort and SocksPort configuration:\n"
2199  " ControlPort=%1\n SocksPort=%2\n Host=%3\n Configuration file:%4")
2200  .arg(_autoControlPort)
2201  .arg(port)
2202  .arg(addrPort.at(0))
2203  .arg(QString("%1/port.conf").arg(expand_filename(settings.getDataDirectory()))));
2204  }
2205 
2207  env << QString("TOR_CONTROL_PASSWD=%1").arg(QString(_controlPassword.toAscii().toHex()));
2208  env << QString("TOR_CONTROL_PORT=%1").arg(_autoControlPort);
2209  }
2210 
2211  return env;
2212 }
2213 
2214 void
2215 MainWindow::log(tc::Severity type, const QString &message)
2216 {
2217  if(type == tc::WarnSeverity)
2218  _warnTimer->start(1000);
2219 }
2220 
2221 void
2223 {
2224  if(_flashToggle) {
2225  ui.lblMessageLog->enableFlashing();
2226  } else {
2227  ui.lblMessageLog->disableFlashing();
2228  }
2229 
2231 }