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