Vidalia  0.2.17
NetworkPage.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 NetworkPage.cpp
00013 ** \brief Network and firewall configuration options
00014 */
00015 
00016 #include "NetworkPage.h"
00017 #include "NetworkSettings.h"
00018 #include "VMessageBox.h"
00019 #include "Vidalia.h"
00020 #include "BridgeDownloaderProgressDialog.h"
00021 #include "DomainValidator.h"
00022 
00023 #include "stringutil.h"
00024 
00025 #include <QMenu>
00026 #include <QIntValidator>
00027 #include <QClipboard>
00028 #include <QHostAddress>
00029 #include <QRegExp>
00030 #include <QMessageBox>
00031 
00032 #define IMG_COPY  ":/images/22x22/edit-copy.png"
00033 
00034 
00035 /** Constructor */
00036 NetworkPage::NetworkPage(QWidget *parent)
00037 : ConfigPage(parent, "Network")
00038 {
00039   /* Invoke the Qt Designer generated object setup routine */
00040   ui.setupUi(this);
00041 
00042   connect(ui.btnAddBridge, SIGNAL(clicked()), this, SLOT(addBridge()));
00043   connect(ui.btnRemoveBridge, SIGNAL(clicked()), this, SLOT(removeBridge()));
00044   connect(ui.btnCopyBridge, SIGNAL(clicked()), 
00045           this, SLOT(copySelectedBridgesToClipboard()));
00046   connect(ui.listBridges, SIGNAL(customContextMenuRequested(QPoint)),
00047           this, SLOT(bridgeContextMenuRequested(QPoint)));
00048   connect(ui.listBridges, SIGNAL(itemSelectionChanged()),
00049           this, SLOT(bridgeSelectionChanged()));
00050   connect(ui.lineBridge, SIGNAL(returnPressed()), this, SLOT(addBridge()));
00051   connect(ui.lblHelpFindBridges, SIGNAL(linkActivated(QString)),
00052           this, SLOT(onLinkActivated(QString)));
00053   connect(ui.btnFindBridges, SIGNAL(clicked()), this, SLOT(findBridges()));
00054   connect(ui.cmboProxyType, SIGNAL(currentIndexChanged(int)),
00055           this, SLOT(proxyTypeChanged(int)));
00056 
00057   ui.lineProxyAddress->setValidator(new DomainValidator(this));
00058   ui.lineProxyPort->setValidator(new QIntValidator(1, 65535, this));
00059 
00060   vApp->createShortcut(QKeySequence(QKeySequence::Copy),
00061                        ui.listBridges, this,
00062                        SLOT(copySelectedBridgesToClipboard()));
00063 
00064   if (! BridgeDownloader::isMethodSupported(BridgeDownloader::DownloadMethodHttps)) {
00065     ui.btnFindBridges->setVisible(false);
00066     ui.lblHelpFindBridges->setText(
00067       tr("<a href=\"bridges.finding\">How can I find bridges?</a>"));
00068     _bridgeDownloader = 0;
00069   } else {
00070     _bridgeDownloader = new BridgeDownloader(this);
00071     connect(_bridgeDownloader, SIGNAL(bridgeRequestFinished(QStringList)),
00072             this, SLOT(bridgeRequestFinished(QStringList)));
00073   }
00074 
00075 #if defined(Q_WS_MAC)
00076   /* On OS X, the network page looks better without frame titles. Everywhere
00077    * else needs titles or else there's a break in the frame border. */
00078   ui.grpProxySettings->setTitle("");
00079   ui.grpFirewallSettings->setTitle("");
00080   ui.grpBridgeSettings->setTitle("");
00081 #endif
00082 }
00083 
00084 /** Called when the user changes the UI translation. */
00085 void
00086 NetworkPage::retranslateUi()
00087 {
00088   ui.retranslateUi(this);
00089 }
00090 
00091 /** Applies the network configuration settings to Tor. Returns true if the   *
00092  * settings were applied successfully. Otherwise, <b>errmsg</b> is set and   *
00093  * false is returned. */
00094 bool
00095 NetworkPage::apply(QString &errmsg)
00096 {
00097   return NetworkSettings(Vidalia::torControl()).apply(&errmsg);
00098 }
00099 
00100 /** Returns true if the user has changed their server settings since the   *
00101  * last time they were applied to Tor. */
00102 bool
00103 NetworkPage::changedSinceLastApply()
00104 {
00105   return NetworkSettings(Vidalia::torControl()).changedSinceLastApply();
00106 }
00107 
00108 /** Reverts the server configuration settings to their values at the last   *
00109  * time they were successfully applied to Tor. */
00110 void
00111 NetworkPage::revert()
00112 {
00113   NetworkSettings settings(Vidalia::torControl());
00114   settings.revert();
00115 }
00116 
00117 /** Called when a link in a label is clicked. <b>url</b> is the target of
00118  * the clicked link. */
00119 void
00120 NetworkPage::onLinkActivated(const QString &url)
00121 {
00122   emit helpRequested(url);
00123 }
00124 
00125 /** Adds a bridge to the bridge list box. */
00126 void
00127 NetworkPage::addBridge()
00128 {
00129   QString input = ui.lineBridge->text().trimmed();
00130 
00131   if (input.isEmpty())
00132     return;
00133   if (!ui.listBridges->findItems(input, Qt::MatchFixedString).isEmpty())
00134     return; /* duplicate bridge */
00135 
00136   ui.listBridges->addItem(input);
00137   ui.lineBridge->clear();
00138 }
00139 
00140 /** Removes one or more selected bridges from the bridge list box. */
00141 void
00142 NetworkPage::removeBridge()
00143 {
00144   qDeleteAll(ui.listBridges->selectedItems());
00145 }
00146 
00147 /** Copies all selected bridges to the clipboard. */
00148 void
00149 NetworkPage::copySelectedBridgesToClipboard()
00150 {
00151   QString contents;
00152 
00153   foreach (QListWidgetItem *item, ui.listBridges->selectedItems()) {
00154 #if defined(Q_WS_WIN)
00155     contents += item->text() + "\r\n";
00156 #else
00157     contents += item->text() + "\n";
00158 #endif
00159   }
00160   if (!contents.isEmpty())
00161     vApp->clipboard()->setText(contents.trimmed());
00162 }
00163 
00164 /** Called when the user right-clicks on a bridge and displays a context
00165  * menu. */
00166 void
00167 NetworkPage::bridgeContextMenuRequested(const QPoint &pos)
00168 {
00169   QMenu menu(this);
00170   
00171   QListWidgetItem *item = ui.listBridges->itemAt(pos);
00172   if (!item)
00173     return;
00174   
00175   QAction *copyAction =
00176     new QAction(QIcon(IMG_COPY), tr("Copy (Ctrl+C)"), &menu);
00177   connect(copyAction, SIGNAL(triggered()),
00178           this, SLOT(copySelectedBridgesToClipboard()));
00179 
00180   menu.addAction(copyAction);
00181   menu.exec(ui.listBridges->mapToGlobal(pos));
00182 }
00183 
00184 /** Called when the user changes which bridges they have selected. */
00185 void
00186 NetworkPage::bridgeSelectionChanged()
00187 {
00188   bool enabled = !ui.listBridges->selectedItems().isEmpty();
00189   ui.btnCopyBridge->setEnabled(enabled);
00190   ui.btnRemoveBridge->setEnabled(enabled);
00191 }
00192 
00193 /** Saves changes made to settings on the Firewall settings page. */
00194 bool
00195 NetworkPage::save(QString &errmsg)
00196 {
00197   NetworkSettings settings(Vidalia::torControl());
00198   QString addr;
00199   QString user, pass;
00200   NetworkSettings::ProxyType proxy = NetworkSettings::NoProxy;
00201   QStringList bridgeList;
00202   QList<quint16> reachablePorts;
00203   bool ok;
00204   
00205   if (ui.chkUseProxy->isChecked()) {
00206     if (ui.lineProxyAddress->text().isEmpty()
00207           || ui.lineProxyPort->text().isEmpty()) {
00208       errmsg = tr("You must specify both an IP address or hostname and a "
00209                   "port number to configure Tor to use a proxy to access "
00210                   "the Internet.");
00211       return false;
00212     }
00213     if (ui.cmboProxyType->currentIndex() < 0) {
00214       errmsg = tr("You must select the proxy type.");
00215       return false;
00216     }
00217   }
00218   if (ui.chkFascistFirewall->isChecked()
00219         && ui.lineReachablePorts->text().isEmpty()) {
00220     errmsg = tr("You must specify one or more ports to which your "
00221                 "firewall allows you to connect.");
00222     return false;
00223   }
00224 
00225   if (ui.chkUseProxy->isChecked()) {
00226     if (!ui.lineProxyAddress->text().isEmpty()) {
00227       addr = ui.lineProxyAddress->text();
00228       if (!ui.lineProxyPort->text().isEmpty())
00229         addr += ":" + ui.lineProxyPort->text();
00230     }
00231 
00232     user = ui.lineProxyUsername->text();
00233     pass = ui.lineProxyPassword->text();
00234  
00235     QVariant data;
00236     int type;
00237 
00238     data = ui.cmboProxyType->itemData(ui.cmboProxyType->currentIndex());
00239     Q_ASSERT(data.isValid());
00240     type = data.toInt();
00241     Q_ASSERT(type >= NetworkSettings::ProxyTypeMin &&
00242              type <= NetworkSettings::ProxyTypeMax);
00243     proxy = static_cast<NetworkSettings::ProxyType>(type);
00244   }
00245 
00246   settings.setProxyType(proxy);
00247   settings.setProxyAddress(addr);
00248   settings.setProxyUsername(user);
00249   settings.setProxyPassword(pass);
00250  
00251   /* Save the reachable port settings */
00252   settings.setFascistFirewall(ui.chkFascistFirewall->isChecked());
00253   foreach (QString portString,
00254            ui.lineReachablePorts->text().split(",", QString::SkipEmptyParts)) {
00255     quint32 port = portString.toUInt(&ok);
00256     if (!ok || port < 1 || port > 65535) {
00257       errmsg = tr("'%1' is not a valid port number.").arg(portString);
00258       return false;
00259     }
00260     reachablePorts << (quint16)port;
00261   }
00262   settings.setReachablePorts(reachablePorts);
00263 
00264   if (ui.chkUseBridges->isChecked()) {
00265     if (ui.listBridges->count() < 1) {
00266       errmsg = tr("You must specify one or more bridges.");
00267       return false;
00268     }
00269   }
00270 
00271   /* Save the bridge settings */
00272   settings.setUseBridges(ui.chkUseBridges->isChecked());
00273   for (int i = 0; i < ui.listBridges->count(); i++)
00274     bridgeList << ui.listBridges->item(i)->text();
00275   settings.setBridgeList(bridgeList);
00276 
00277   return true;
00278 }
00279 
00280 /** Loads previously saved settings */
00281 void
00282 NetworkPage::load()
00283 {
00284   NetworkSettings settings(Vidalia::torControl());
00285   QStringList reachablePortStrings;
00286   NetworkSettings::ProxyType proxyType;
00287 
00288   /* Load proxy settings */
00289   proxyType = settings.getProxyType();
00290   ui.chkUseProxy->setChecked(proxyType != NetworkSettings::NoProxy);
00291   QStringList proxy = settings.getProxyAddress().split(":");
00292   if (proxy.size() >= 1)
00293     ui.lineProxyAddress->setText(proxy.at(0));
00294   if (proxy.size() >= 2)
00295     ui.lineProxyPort->setText(proxy.at(1));
00296   ui.lineProxyUsername->setText(settings.getProxyUsername());
00297   ui.lineProxyPassword->setText(settings.getProxyPassword());
00298 
00299   /* SOCKS options are only available on Tor >= 0.2.2.1-alpha, so don't show
00300    * them if Tor is running and its version is less than that. */
00301   ui.cmboProxyType->clear();
00302   if (!vApp->torControl()->isRunning()
00303         || vApp->torControl()->getTorVersion() >= 0x020201) {
00304     ui.cmboProxyType->addItem(tr("SOCKS 4"), NetworkSettings::Socks4Proxy);
00305     ui.cmboProxyType->addItem(tr("SOCKS 5"), NetworkSettings::Socks5Proxy);
00306   } else if (proxyType == NetworkSettings::Socks4Proxy
00307               || proxyType == NetworkSettings::Socks5Proxy) {
00308     /* Disable proxy if the settings include a SOCKS proxy and our version of
00309      * Tor is not compatible. */
00310     proxyType = NetworkSettings::NoProxy;
00311     ui.chkUseProxy->setChecked(false);
00312   }
00313   ui.cmboProxyType->addItem(tr("HTTP / HTTPS"),
00314                             NetworkSettings::HttpHttpsProxy);
00315 
00316   ui.cmboProxyType->setCurrentIndex(ui.cmboProxyType->findData(proxyType));
00317 
00318   /* Load firewall settings */
00319   ui.chkFascistFirewall->setChecked(settings.getFascistFirewall());
00320   QList<quint16> reachablePorts = settings.getReachablePorts();
00321   foreach (quint16 port, reachablePorts) {
00322     reachablePortStrings << QString::number(port);
00323   }
00324   ui.lineReachablePorts->setText(reachablePortStrings.join(","));
00325 
00326   /* Load bridge settings */
00327   ui.chkUseBridges->setChecked(settings.getUseBridges()); 
00328   ui.listBridges->clear();
00329   ui.listBridges->addItems(settings.getBridgeList());
00330 }
00331 
00332 /** Called when the user clicks the "Find Bridges Now" button.
00333  * Attempts to establish an HTTPS connection to bridges.torproject.org
00334  * and download one or more bridge addresses. */
00335 void
00336 NetworkPage::findBridges()
00337 {
00338   BridgeDownloaderProgressDialog *dlg = new BridgeDownloaderProgressDialog(this);
00339 
00340   connect(_bridgeDownloader, SIGNAL(statusChanged(QString)),
00341           dlg, SLOT(setStatus(QString)));
00342   connect(_bridgeDownloader, SIGNAL(downloadProgress(qint64, qint64)),
00343           dlg, SLOT(setDownloadProgress(qint64, qint64)));
00344   connect(_bridgeDownloader, SIGNAL(bridgeRequestFailed(QString)),
00345           dlg, SLOT(bridgeRequestFailed(QString)));
00346   connect(_bridgeDownloader, SIGNAL(bridgeRequestFinished(QStringList)),
00347           dlg, SLOT(bridgeRequestFinished(QStringList)));
00348   connect(dlg, SIGNAL(retry()), this, SLOT(startBridgeRequest()));
00349 
00350   startBridgeRequest();
00351   switch (dlg->exec()) {
00352     case QDialogButtonBox::Cancel:
00353       _bridgeDownloader->cancelBridgeRequest();
00354       break;
00355 
00356     case QDialogButtonBox::Help:
00357       emit helpRequested("bridges.finding");
00358       break;
00359   }
00360 
00361   delete dlg;
00362 }
00363 
00364 /** Starts a new request for additional bridge addresses. */
00365 void
00366 NetworkPage::startBridgeRequest()
00367 { 
00368   if (ui.chkUseProxy->isChecked() &&
00369      ui.cmboProxyType->currentIndex() == NetworkSettings::HttpHttpsProxy) {
00370     _bridgeDownloader->setProxy(ui.lineProxyAddress->text(),
00371                                 ui.lineProxyPort->text().toUInt(),
00372                                 ui.lineProxyUsername->text(),
00373                                 ui.lineProxyPassword->text());
00374   }
00375 
00376   _bridgeDownloader->downloadBridges(BridgeDownloader::DownloadMethodHttps);
00377 }
00378 
00379 /** Called when a previous bridge request initiated by the findBridges()
00380  * method has completed. <b>bridges</b> contains a list of all bridges
00381  * received. */
00382 void
00383 NetworkPage::bridgeRequestFinished(const QStringList &bridges)
00384 {
00385   bool foundNewBridges = false;
00386   QString normalized;
00387 
00388   foreach (QString bridge, bridges) {
00389     QString address = normalized.split(" ").at(0);
00390     if (ui.listBridges->findItems(address, Qt::MatchContains).isEmpty()) {
00391       ui.listBridges->addItem(normalized);
00392       foundNewBridges = true;
00393     }
00394   }
00395 
00396   if (! foundNewBridges) {
00397     QMessageBox dlg(this);
00398     dlg.setIcon(QMessageBox::Information);
00399     dlg.setText(tr("No new bridges are currently available. You can either "
00400                    "wait a while and try again, or try another method of "
00401                    "finding new bridges."));
00402     dlg.setInformativeText(tr("Click Help to see other methods of finding "
00403                               "new bridges."));
00404     dlg.setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
00405  
00406     if (dlg.exec() == QMessageBox::Help)
00407       emit helpRequested("bridges.finding");      
00408   }
00409 }
00410 
00411 /** Disable proxy username and password fields when the user wants to use
00412  * a SOCKS 4 proxy. */
00413 void
00414 NetworkPage::proxyTypeChanged(int selection)
00415 {
00416   QVariant data = ui.cmboProxyType->itemData(selection);
00417 
00418   if (data.isValid()
00419       && data.toInt() == NetworkSettings::Socks4Proxy) {
00420     ui.lineProxyUsername->setEnabled(false);
00421     ui.lineProxyPassword->setEnabled(false);
00422   } else {
00423     ui.lineProxyUsername->setEnabled(true);
00424     ui.lineProxyPassword->setEnabled(true);
00425   }
00426 }
00427