Vidalia  0.3.1
NetworkPage.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file NetworkPage.cpp
13 ** \brief Network and firewall configuration options
14 */
15 
16 #include "NetworkPage.h"
17 #include "NetworkSettings.h"
18 #include "VMessageBox.h"
19 #include "Vidalia.h"
21 #include "DomainValidator.h"
22 
23 #include "stringutil.h"
24 
25 #include <QMenu>
26 #include <QIntValidator>
27 #include <QClipboard>
28 #include <QHostAddress>
29 #include <QRegExp>
30 #include <QMessageBox>
31 
32 #define IMG_COPY ":/images/22x22/edit-copy.png"
33 
34 
35 /** Constructor */
36 NetworkPage::NetworkPage(QWidget *parent)
37 : ConfigPage(parent, "Network")
38 {
39  /* Invoke the Qt Designer generated object setup routine */
40  ui.setupUi(this);
41 
42  connect(ui.btnAddBridge, SIGNAL(clicked()), this, SLOT(addBridge()));
43  connect(ui.btnRemoveBridge, SIGNAL(clicked()), this, SLOT(removeBridge()));
44  connect(ui.btnCopyBridge, SIGNAL(clicked()),
45  this, SLOT(copySelectedBridgesToClipboard()));
46  connect(ui.listBridges, SIGNAL(customContextMenuRequested(QPoint)),
47  this, SLOT(bridgeContextMenuRequested(QPoint)));
48  connect(ui.listBridges, SIGNAL(itemSelectionChanged()),
49  this, SLOT(bridgeSelectionChanged()));
50  connect(ui.lineBridge, SIGNAL(returnPressed()), this, SLOT(addBridge()));
51  connect(ui.lblHelpFindBridges, SIGNAL(linkActivated(QString)),
52  this, SLOT(onLinkActivated(QString)));
53  connect(ui.btnFindBridges, SIGNAL(clicked()), this, SLOT(findBridges()));
54  connect(ui.cmboProxyType, SIGNAL(currentIndexChanged(int)),
55  this, SLOT(proxyTypeChanged(int)));
56 
57  ui.lineProxyAddress->setValidator(new DomainValidator(this));
58  ui.lineProxyPort->setValidator(new QIntValidator(1, 65535, this));
59 
60  vApp->createShortcut(QKeySequence(QKeySequence::Copy),
61  ui.listBridges, this,
63 
65  ui.btnFindBridges->setVisible(false);
66  ui.lblHelpFindBridges->setText(
67  tr("<a href=\"bridges.finding\">How can I find bridges?</a>"));
69  } else {
71  connect(_bridgeDownloader, SIGNAL(bridgeRequestFinished(QStringList)),
72  this, SLOT(bridgeRequestFinished(QStringList)));
73  }
74 
75 #if defined(Q_WS_MAC)
76  /* On OS X, the network page looks better without frame titles. Everywhere
77  * else needs titles or else there's a break in the frame border. */
78  ui.grpProxySettings->setTitle("");
79  ui.grpFirewallSettings->setTitle("");
80  ui.grpBridgeSettings->setTitle("");
81 #endif
82 }
83 
84 /** Called when the user changes the UI translation. */
85 void
87 {
88  ui.retranslateUi(this);
89 }
90 
91 /** Applies the network configuration settings to Tor. Returns true if the *
92  * settings were applied successfully. Otherwise, <b>errmsg</b> is set and *
93  * false is returned. */
94 bool
95 NetworkPage::apply(QString &errmsg)
96 {
97  return NetworkSettings(Vidalia::torControl()).apply(&errmsg);
98 }
99 
100 /** Returns true if the user has changed their server settings since the *
101  * last time they were applied to Tor. */
102 bool
104 {
106 }
107 
108 /** Reverts the server configuration settings to their values at the last *
109  * time they were successfully applied to Tor. */
110 void
112 {
114  settings.revert();
115 }
116 
117 /** Called when a link in a label is clicked. <b>url</b> is the target of
118  * the clicked link. */
119 void
120 NetworkPage::onLinkActivated(const QString &url)
121 {
122  emit helpRequested(url);
123 }
124 
125 /** Adds a bridge to the bridge list box. */
126 void
128 {
129  QString input = ui.lineBridge->text().trimmed();
130 
131  if (input.isEmpty())
132  return;
133  if (!ui.listBridges->findItems(input, Qt::MatchFixedString).isEmpty())
134  return; /* duplicate bridge */
135 
136  ui.listBridges->addItem(input);
137  ui.lineBridge->clear();
138 }
139 
140 /** Removes one or more selected bridges from the bridge list box. */
141 void
143 {
144  qDeleteAll(ui.listBridges->selectedItems());
145 }
146 
147 /** Copies all selected bridges to the clipboard. */
148 void
150 {
151  QString contents;
152 
153  foreach (QListWidgetItem *item, ui.listBridges->selectedItems()) {
154 #if defined(Q_WS_WIN)
155  contents += item->text() + "\r\n";
156 #else
157  contents += item->text() + "\n";
158 #endif
159  }
160  if (!contents.isEmpty())
161  vApp->clipboard()->setText(contents.trimmed());
162 }
163 
164 /** Called when the user right-clicks on a bridge and displays a context
165  * menu. */
166 void
168 {
169  QMenu menu(this);
170 
171  QListWidgetItem *item = ui.listBridges->itemAt(pos);
172  if (!item)
173  return;
174 
175  QAction *copyAction =
176  new QAction(QIcon(IMG_COPY), tr("Copy (Ctrl+C)"), &menu);
177  connect(copyAction, SIGNAL(triggered()),
178  this, SLOT(copySelectedBridgesToClipboard()));
179 
180  menu.addAction(copyAction);
181  menu.exec(ui.listBridges->mapToGlobal(pos));
182 }
183 
184 /** Called when the user changes which bridges they have selected. */
185 void
187 {
188  bool enabled = !ui.listBridges->selectedItems().isEmpty();
189  ui.btnCopyBridge->setEnabled(enabled);
190  ui.btnRemoveBridge->setEnabled(enabled);
191 }
192 
193 /** Saves changes made to settings on the Firewall settings page. */
194 bool
195 NetworkPage::save(QString &errmsg)
196 {
198  QString addr;
199  QString user, pass;
201  QStringList bridgeList;
202  QList<quint16> reachablePorts;
203  bool ok;
204 
205  if (ui.chkUseProxy->isChecked()) {
206  if (ui.lineProxyAddress->text().isEmpty()
207  || ui.lineProxyPort->text().isEmpty()) {
208  errmsg = tr("You must specify both an IP address or hostname and a "
209  "port number to configure Tor to use a proxy to access "
210  "the Internet.");
211  return false;
212  }
213  if (ui.cmboProxyType->currentIndex() < 0) {
214  errmsg = tr("You must select the proxy type.");
215  return false;
216  }
217  }
218  if (ui.chkFascistFirewall->isChecked()
219  && ui.lineReachablePorts->text().isEmpty()) {
220  errmsg = tr("You must specify one or more ports to which your "
221  "firewall allows you to connect.");
222  return false;
223  }
224 
225  if (ui.chkUseProxy->isChecked()) {
226  if (!ui.lineProxyAddress->text().isEmpty()) {
227  addr = ui.lineProxyAddress->text();
228  if (!ui.lineProxyPort->text().isEmpty())
229  addr += ":" + ui.lineProxyPort->text();
230  }
231 
232  user = ui.lineProxyUsername->text();
233  pass = ui.lineProxyPassword->text();
234 
235  QVariant data;
236  int type;
237 
238  data = ui.cmboProxyType->itemData(ui.cmboProxyType->currentIndex());
239  Q_ASSERT(data.isValid());
240  type = data.toInt();
241  Q_ASSERT(type >= NetworkSettings::ProxyTypeMin &&
243  proxy = static_cast<NetworkSettings::ProxyType>(type);
244  }
245 
246  settings.setProxyType(proxy);
247  settings.setProxyAddress(addr);
248  settings.setProxyUsername(user);
249  settings.setProxyPassword(pass);
250 
251  /* Save the reachable port settings */
252  settings.setFascistFirewall(ui.chkFascistFirewall->isChecked());
253  foreach (QString portString,
254  ui.lineReachablePorts->text().split(",", QString::SkipEmptyParts)) {
255  quint32 port = portString.toUInt(&ok);
256  if (!ok || port < 1 || port > 65535) {
257  errmsg = tr("'%1' is not a valid port number.").arg(portString);
258  return false;
259  }
260  reachablePorts << (quint16)port;
261  }
262  settings.setReachablePorts(reachablePorts);
263 
264  if (ui.chkUseBridges->isChecked()) {
265  if (ui.listBridges->count() < 1) {
266  errmsg = tr("You must specify one or more bridges.");
267  return false;
268  }
269  }
270 
271  /* Save the bridge settings */
272  settings.setUseBridges(ui.chkUseBridges->isChecked());
273  for (int i = 0; i < ui.listBridges->count(); i++)
274  bridgeList << ui.listBridges->item(i)->text();
275  settings.setBridgeList(bridgeList);
276 
277  return true;
278 }
279 
280 /** Loads previously saved settings */
281 void
283 {
285  QStringList reachablePortStrings;
286  NetworkSettings::ProxyType proxyType;
287 
288  /* Load proxy settings */
289  proxyType = settings.getProxyType();
290  ui.chkUseProxy->setChecked(proxyType != NetworkSettings::NoProxy);
291  QStringList proxy = settings.getProxyAddress().split(":");
292  if (proxy.size() >= 1)
293  ui.lineProxyAddress->setText(proxy.at(0));
294  if (proxy.size() >= 2)
295  ui.lineProxyPort->setText(proxy.at(1));
296  ui.lineProxyUsername->setText(settings.getProxyUsername());
297  ui.lineProxyPassword->setText(settings.getProxyPassword());
298 
299  /* SOCKS options are only available on Tor >= 0.2.2.1-alpha, so don't show
300  * them if Tor is running and its version is less than that. */
301  ui.cmboProxyType->clear();
302  if (!vApp->torControl()->isRunning()
303  || vApp->torControl()->getTorVersion() >= 0x020201) {
304  ui.cmboProxyType->addItem(tr("SOCKS 4"), NetworkSettings::Socks4Proxy);
305  ui.cmboProxyType->addItem(tr("SOCKS 5"), NetworkSettings::Socks5Proxy);
306  } else if (proxyType == NetworkSettings::Socks4Proxy
307  || proxyType == NetworkSettings::Socks5Proxy) {
308  /* Disable proxy if the settings include a SOCKS proxy and our version of
309  * Tor is not compatible. */
310  proxyType = NetworkSettings::NoProxy;
311  ui.chkUseProxy->setChecked(false);
312  }
313  ui.cmboProxyType->addItem(tr("HTTP / HTTPS"),
315 
316  ui.cmboProxyType->setCurrentIndex(ui.cmboProxyType->findData(proxyType));
317 
318  /* Load firewall settings */
319  ui.chkFascistFirewall->setChecked(settings.getFascistFirewall());
320  QList<quint16> reachablePorts = settings.getReachablePorts();
321  foreach (quint16 port, reachablePorts) {
322  reachablePortStrings << QString::number(port);
323  }
324  ui.lineReachablePorts->setText(reachablePortStrings.join(","));
325 
326  /* Load bridge settings */
327  ui.chkUseBridges->setChecked(settings.getUseBridges());
328  ui.listBridges->clear();
329  ui.listBridges->addItems(settings.getBridgeList());
330 }
331 
332 /** Called when the user clicks the "Find Bridges Now" button.
333  * Attempts to establish an HTTPS connection to bridges.torproject.org
334  * and download one or more bridge addresses. */
335 void
337 {
339 
340  connect(_bridgeDownloader, SIGNAL(statusChanged(QString)),
341  dlg, SLOT(setStatus(QString)));
342  connect(_bridgeDownloader, SIGNAL(downloadProgress(qint64, qint64)),
343  dlg, SLOT(setDownloadProgress(qint64, qint64)));
344  connect(_bridgeDownloader, SIGNAL(bridgeRequestFailed(QString)),
345  dlg, SLOT(bridgeRequestFailed(QString)));
346  connect(_bridgeDownloader, SIGNAL(bridgeRequestFinished(QStringList)),
347  dlg, SLOT(bridgeRequestFinished(QStringList)));
348  connect(dlg, SIGNAL(retry()), this, SLOT(startBridgeRequest()));
349 
351  switch (dlg->exec()) {
352  case QDialogButtonBox::Cancel:
354  break;
355 
356  case QDialogButtonBox::Help:
357  emit helpRequested("bridges.finding");
358  break;
359  }
360 
361  delete dlg;
362 }
363 
364 /** Starts a new request for additional bridge addresses. */
365 void
367 {
368  if (ui.chkUseProxy->isChecked() &&
369  ui.cmboProxyType->currentIndex() == NetworkSettings::HttpHttpsProxy) {
370  _bridgeDownloader->setProxy(ui.lineProxyAddress->text(),
371  ui.lineProxyPort->text().toUInt(),
372  ui.lineProxyUsername->text(),
373  ui.lineProxyPassword->text());
374  }
375 
377 }
378 
379 /** Called when a previous bridge request initiated by the findBridges()
380  * method has completed. <b>bridges</b> contains a list of all bridges
381  * received. */
382 void
383 NetworkPage::bridgeRequestFinished(const QStringList &bridges)
384 {
385  bool foundNewBridges = false;
386  QString normalized;
387 
388  foreach (QString bridge, bridges) {
389  QString address = normalized.split(" ").at(0);
390  if (ui.listBridges->findItems(address, Qt::MatchContains).isEmpty()) {
391  ui.listBridges->addItem(normalized);
392  foundNewBridges = true;
393  }
394  }
395 
396  if (! foundNewBridges) {
397  QMessageBox dlg(this);
398  dlg.setIcon(QMessageBox::Information);
399  dlg.setText(tr("No new bridges are currently available. You can either "
400  "wait a while and try again, or try another method of "
401  "finding new bridges."));
402  dlg.setInformativeText(tr("Click Help to see other methods of finding "
403  "new bridges."));
404  dlg.setStandardButtons(QMessageBox::Ok | QMessageBox::Help);
405 
406  if (dlg.exec() == QMessageBox::Help)
407  emit helpRequested("bridges.finding");
408  }
409 }
410 
411 /** Disable proxy username and password fields when the user wants to use
412  * a SOCKS 4 proxy. */
413 void
415 {
416  QVariant data = ui.cmboProxyType->itemData(selection);
417 
418  if (data.isValid()
419  && data.toInt() == NetworkSettings::Socks4Proxy) {
420  ui.lineProxyUsername->setEnabled(false);
421  ui.lineProxyPassword->setEnabled(false);
422  } else {
423  ui.lineProxyUsername->setEnabled(true);
424  ui.lineProxyPassword->setEnabled(true);
425  }
426 }
427