Vidalia 0.2.10
|
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