Vidalia 0.2.10

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