Vidalia  0.2.15
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 /** Verifies that <b>bridge</b> is a valid bridge identifier and places a 
00126  * normalized identifier in <b>out</b>. The normalized identifier will have
00127  * all spaces removed from the fingerprint portion (if any) and all
00128  * hexadecimal characters converted to uppercase. Returns true if
00129  * <b>bridge</b> is a valid bridge identifier, false otherwise. */
00130 bool
00131 NetworkPage::validateBridge(const QString &bridge, QString *out)
00132 {
00133   QString temp = bridge;
00134   if (temp.startsWith("bridge ", Qt::CaseInsensitive))
00135     temp = temp.remove(0, 7); /* remove "bridge " */
00136 
00137   QStringList parts = temp.split(" ", QString::SkipEmptyParts);
00138   if (parts.isEmpty())
00139     return false;
00140 
00141   QString s = parts.at(0);
00142   QRegExp re("(\\d{1,3}\\.){3}\\d{1,3}(:\\d{1,5})?");
00143   if (re.exactMatch(s)) {
00144     if (s.endsWith(":"))
00145       return false;
00146 
00147     int index = s.indexOf(":");
00148     QString host = s.mid(0, index);
00149     if (QHostAddress(host).isNull()
00150           || QHostAddress(host).protocol() != QAbstractSocket::IPv4Protocol) {
00151       return false;
00152     }
00153     if (index > 0) {
00154       QString port = s.mid(index + 1);
00155       if (port.toUInt() < 1 || port.toUInt() > 65535)
00156         return false;
00157     }
00158 
00159     temp = s;
00160     if (parts.size() > 1) {
00161       QString fp = static_cast<QStringList>(parts.mid(1)).join("");
00162       if (fp.length() != 40 || !string_is_hex(fp))
00163         return false;
00164       temp += " " + fp.toUpper();
00165     }
00166   } else {
00167     return false;
00168   }
00169   *out = temp;
00170   return true;
00171 }
00172 
00173 /** Adds a bridge to the bridge list box. */
00174 void
00175 NetworkPage::addBridge()
00176 {
00177   QString bridge;
00178   QString input = ui.lineBridge->text().trimmed();
00179 
00180   if (input.isEmpty())
00181     return;
00182   if (!validateBridge(input, &bridge)) {
00183     VMessageBox::warning(this,
00184                   tr("Invalid Bridge"),
00185                   tr("The specified bridge identifier is not valid."),
00186                   VMessageBox::Ok|VMessageBox::Default);
00187     return;
00188   }
00189   if (!ui.listBridges->findItems(bridge, Qt::MatchFixedString).isEmpty())
00190     return; /* duplicate bridge */
00191 
00192   ui.listBridges->addItem(bridge);
00193   ui.lineBridge->clear();
00194 }
00195 
00196 /** Removes one or more selected bridges from the bridge list box. */
00197 void
00198 NetworkPage::removeBridge()
00199 {
00200   qDeleteAll(ui.listBridges->selectedItems());
00201 }
00202 
00203 /** Copies all selected bridges to the clipboard. */
00204 void
00205 NetworkPage::copySelectedBridgesToClipboard()
00206 {
00207   QString contents;
00208 
00209   foreach (QListWidgetItem *item, ui.listBridges->selectedItems()) {
00210 #if defined(Q_WS_WIN)
00211     contents += item->text() + "\r\n";
00212 #else
00213     contents += item->text() + "\n";
00214 #endif
00215   }
00216   if (!contents.isEmpty())
00217     vApp->clipboard()->setText(contents.trimmed());
00218 }
00219 
00220 /** Called when the user right-clicks on a bridge and displays a context
00221  * menu. */
00222 void
00223 NetworkPage::bridgeContextMenuRequested(const QPoint &pos)
00224 {
00225   QMenu menu(this);
00226   
00227   QListWidgetItem *item = ui.listBridges->itemAt(pos);
00228   if (!item)
00229     return;
00230   
00231   QAction *copyAction =
00232     new QAction(QIcon(IMG_COPY), tr("Copy (Ctrl+C)"), &menu);
00233   connect(copyAction, SIGNAL(triggered()),
00234           this, SLOT(copySelectedBridgesToClipboard()));
00235 
00236   menu.addAction(copyAction);
00237   menu.exec(ui.listBridges->mapToGlobal(pos));
00238 }
00239 
00240 /** Called when the user changes which bridges they have selected. */
00241 void
00242 NetworkPage::bridgeSelectionChanged()
00243 {
00244   bool enabled = !ui.listBridges->selectedItems().isEmpty();
00245   ui.btnCopyBridge->setEnabled(enabled);
00246   ui.btnRemoveBridge->setEnabled(enabled);
00247 }
00248 
00249 /** Saves changes made to settings on the Firewall settings page. */
00250 bool
00251 NetworkPage::save(QString &errmsg)
00252 {
00253   NetworkSettings settings(Vidalia::torControl());
00254   QString addr;
00255   QString user, pass;
00256   NetworkSettings::ProxyType proxy = NetworkSettings::NoProxy;
00257   QStringList bridgeList;
00258   QList<quint16> reachablePorts;
00259   bool ok;
00260   
00261   if (ui.chkUseProxy->isChecked()) {
00262     if (ui.lineProxyAddress->text().isEmpty()
00263           || ui.lineProxyPort->text().isEmpty()) {
00264       errmsg = tr("You must specify both an IP address or hostname and a "
00265                   "port number to configure Tor to use a proxy to access "
00266                   "the Internet.");
00267       return false;
00268     }
00269     if (ui.cmboProxyType->currentIndex() < 0) {
00270       errmsg = tr("You must select the proxy type.");
00271       return false;
00272     }
00273   }
00274   if (ui.chkFascistFirewall->isChecked()
00275         && ui.lineReachablePorts->text().isEmpty()) {
00276     errmsg = tr("You must specify one or more ports to which your "
00277                 "firewall allows you to connect.");
00278     return false;
00279   }
00280 
00281   if (ui.chkUseProxy->isChecked()) {
00282     if (!ui.lineProxyAddress->text().isEmpty()) {
00283       addr = ui.lineProxyAddress->text();
00284       if (!ui.lineProxyPort->text().isEmpty())
00285         addr += ":" + ui.lineProxyPort->text();
00286     }
00287 
00288     user = ui.lineProxyUsername->text();
00289     pass = ui.lineProxyPassword->text();
00290  
00291     QVariant data;
00292     int type;
00293 
00294     data = ui.cmboProxyType->itemData(ui.cmboProxyType->currentIndex());
00295     Q_ASSERT(data.isValid());
00296     type = data.toInt();
00297     Q_ASSERT(type >= NetworkSettings::ProxyTypeMin &&
00298              type <= NetworkSettings::ProxyTypeMax);
00299     proxy = static_cast<NetworkSettings::ProxyType>(type);
00300   }
00301 
00302   settings.setProxyType(proxy);
00303   settings.setProxyAddress(addr);
00304   settings.setProxyUsername(user);
00305   settings.setProxyPassword(pass);
00306  
00307   /* Save the reachable port settings */
00308   settings.setFascistFirewall(ui.chkFascistFirewall->isChecked());
00309   foreach (QString portString,
00310            ui.lineReachablePorts->text().split(",", QString::SkipEmptyParts)) {
00311     quint32 port = portString.toUInt(&ok);
00312     if (!ok || port < 1 || port > 65535) {
00313       errmsg = tr("'%1' is not a valid port number.").arg(portString);
00314       return false;
00315     }
00316     reachablePorts << (quint16)port;
00317   }
00318   settings.setReachablePorts(reachablePorts);
00319 
00320   /* Save the bridge settings */
00321   settings.setUseBridges(ui.chkUseBridges->isChecked());
00322   for (int i = 0; i < ui.listBridges->count(); i++)
00323     bridgeList << ui.listBridges->item(i)->text();
00324   settings.setBridgeList(bridgeList);
00325 
00326   return true;
00327 }
00328 
00329 /** Loads previously saved settings */
00330 void
00331 NetworkPage::load()
00332 {
00333   NetworkSettings settings(Vidalia::torControl());
00334   QStringList reachablePortStrings;
00335   NetworkSettings::ProxyType proxyType;
00336 
00337   /* Load proxy settings */
00338   proxyType = settings.getProxyType();
00339   ui.chkUseProxy->setChecked(proxyType != NetworkSettings::NoProxy);
00340   QStringList proxy = settings.getProxyAddress().split(":");
00341   if (proxy.size() >= 1)
00342     ui.lineProxyAddress->setText(proxy.at(0));
00343   if (proxy.size() >= 2)
00344     ui.lineProxyPort->setText(proxy.at(1));
00345   ui.lineProxyUsername->setText(settings.getProxyUsername());
00346   ui.lineProxyPassword->setText(settings.getProxyPassword());
00347 
00348   /* SOCKS options are only available on Tor >= 0.2.2.1-alpha, so don't show
00349    * them if Tor is running and its version is less than that. */
00350   ui.cmboProxyType->clear();
00351   if (!vApp->torControl()->isRunning()
00352         || vApp->torControl()->getTorVersion() >= 0x020201) {
00353     ui.cmboProxyType->addItem(tr("SOCKS 4"), NetworkSettings::Socks4Proxy);
00354     ui.cmboProxyType->addItem(tr("SOCKS 5"), NetworkSettings::Socks5Proxy);
00355   } else if (proxyType == NetworkSettings::Socks4Proxy
00356               || proxyType == NetworkSettings::Socks5Proxy) {
00357     /* Disable proxy if the settings include a SOCKS proxy and our version of
00358      * Tor is not compatible. */
00359     proxyType = NetworkSettings::NoProxy;
00360     ui.chkUseProxy->setChecked(false);
00361   }
00362   ui.cmboProxyType->addItem(tr("HTTP"), NetworkSettings::HttpProxy);
00363   ui.cmboProxyType->addItem(tr("HTTP / HTTPS"),
00364                             NetworkSettings::HttpHttpsProxy);
00365 
00366   ui.cmboProxyType->setCurrentIndex(ui.cmboProxyType->findData(proxyType));
00367 
00368   /* Load firewall settings */
00369   ui.chkFascistFirewall->setChecked(settings.getFascistFirewall());
00370   QList<quint16> reachablePorts = settings.getReachablePorts();
00371   foreach (quint16 port, reachablePorts) {
00372     reachablePortStrings << QString::number(port);
00373   }
00374   ui.lineReachablePorts->setText(reachablePortStrings.join(","));
00375 
00376   /* Load bridge settings */
00377   ui.chkUseBridges->setChecked(settings.getUseBridges()); 
00378   ui.listBridges->clear();
00379   ui.listBridges->addItems(settings.getBridgeList());
00380 }
00381 
00382 /** Called when the user clicks the "Find Bridges Now" button.
00383  * Attempts to establish an HTTPS connection to bridges.torproject.org
00384  * and download one or more bridge addresses. */
00385 void
00386 NetworkPage::findBridges()
00387 {
00388   BridgeDownloaderProgressDialog *dlg = new BridgeDownloaderProgressDialog(this);
00389 
00390   connect(_bridgeDownloader, SIGNAL(statusChanged(QString)),
00391           dlg, SLOT(setStatus(QString)));
00392   connect(_bridgeDownloader, SIGNAL(downloadProgress(qint64, qint64)),
00393           dlg, SLOT(setDownloadProgress(qint64, qint64)));
00394   connect(_bridgeDownloader, SIGNAL(bridgeRequestFailed(QString)),
00395           dlg, SLOT(bridgeRequestFailed(QString)));
00396   connect(_bridgeDownloader, SIGNAL(bridgeRequestFinished(QStringList)),
00397           dlg, SLOT(bridgeRequestFinished(QStringList)));
00398   connect(dlg, SIGNAL(retry()), this, SLOT(startBridgeRequest()));
00399 
00400   startBridgeRequest();
00401   switch (dlg->exec()) {
00402     case QDialogButtonBox::Cancel:
00403       _bridgeDownloader->cancelBridgeRequest();
00404       break;
00405 
00406     case QDialogButtonBox::Help:
00407       emit helpRequested("bridges.finding");
00408       break;
00409   }
00410 
00411   delete dlg;
00412 }
00413 
00414 /** Starts a new request for additional bridge addresses. */
00415 void
00416 NetworkPage::startBridgeRequest()
00417 { 
00418   if (ui.chkUseProxy->isChecked() &&
00419      ui.cmboProxyType->currentIndex() == NetworkSettings::HttpHttpsProxy) {
00420     _bridgeDownloader->setProxy(ui.lineProxyAddress->text(),
00421                                 ui.lineProxyPort->text().toUInt(),
00422                                 ui.lineProxyUsername->text(),
00423                                 ui.lineProxyPassword->text());
00424   }
00425 
00426   _bridgeDownloader->downloadBridges(BridgeDownloader::DownloadMethodHttps);
00427 }
00428 
00429 /** Called when a previous bridge request initiated by the findBridges()
00430  * method has completed. <b>bridges</b> contains a list of all bridges
00431  * received. */
00432 void
00433 NetworkPage::bridgeRequestFinished(const QStringList &bridges)
00434 {
00435   bool foundNewBridges = false;
00436   QString normalized;
00437 
00438   foreach (QString bridge, bridges) {
00439     if (! validateBridge(bridge, &normalized))
00440       continue;
00441 
00442     QString address = normalized.split(" ").at(0);
00443     if (ui.listBridges->findItems(address, Qt::MatchContains).isEmpty()) {
00444       ui.listBridges->addItem(normalized);
00445       foundNewBridges = true;
00446     }
00447   }
00448 
00449   if (! foundNewBridges) {
00450     QMessageBox dlg(this);
00451     dlg.setIcon(QMessageBox::Information);
00452     dlg.setText(tr("No new bridges are currently available. You can either "
00453                    "wait a while and try again, or try another method of "
00454                    "finding new bridges."));
00455     dlg.setInformativeText(tr("Click Help to see other methods of finding "
00456                               "new bridges."));
00457     dlg.setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
00458  
00459     if (dlg.exec() == QMessageBox::Help)
00460       emit helpRequested("bridges.finding");      
00461   }
00462 }
00463 
00464 /** Disable proxy username and password fields when the user wants to use
00465  * a SOCKS 4 proxy. */
00466 void
00467 NetworkPage::proxyTypeChanged(int selection)
00468 {
00469   QVariant data = ui.cmboProxyType->itemData(selection);
00470 
00471   if (data.isValid()
00472       && data.toInt() == NetworkSettings::Socks4Proxy) {
00473     ui.lineProxyUsername->setEnabled(false);
00474     ui.lineProxyPassword->setEnabled(false);
00475   } else {
00476     ui.lineProxyUsername->setEnabled(true);
00477     ui.lineProxyPassword->setEnabled(true);
00478   }
00479 }
00480