Vidalia 0.3.1
ServicePage.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#include "ServicePage.h"
12#include "Service.h"
13#include "ServiceList.h"
14#include "VMessageBox.h"
15#include "ConfigDialog.h"
16#include "IpValidator.h"
17#include "DomainValidator.h"
18#include "Vidalia.h"
19
20#include "stringutil.h"
21#include "file.h"
22
23#include <QHeaderView>
24#include <QClipboard>
25#include <QFile>
26#include <QTextStream>
27
28
29/** Constructor */
31: ConfigPage(parent, "Services")
32{
33 /* Invoke the Qt Designer generated object setup routine */
34 ui.setupUi(this);
35 /* A QMap, mapping from the row number to the Entity for
36 * all services */
37 _services = new QMap<int, Service>();
38 /* A QMap, mapping from the directory path to the Entity for
39 * all Tor services */
40 _torServices = new QMap<QString, Service>();
41
42 ui.serviceWidget->horizontalHeader()->resizeSection(0, 150);
43 ui.serviceWidget->horizontalHeader()->resizeSection(1, 89);
44 ui.serviceWidget->horizontalHeader()->resizeSection(2, 100);
45 ui.serviceWidget->horizontalHeader()->resizeSection(3, 120);
46 ui.serviceWidget->horizontalHeader()->resizeSection(4, 60);
47 ui.serviceWidget->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
48 ui.serviceWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
49 ui.serviceWidget->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch);
50 ui.serviceWidget->horizontalHeader()->setResizeMode(3, QHeaderView::Stretch);
51 ui.serviceWidget->verticalHeader()->hide();
52
53 connect(ui.addButton, SIGNAL(clicked()), this, SLOT(addService()));
54 connect(ui.removeButton, SIGNAL(clicked()), this, SLOT(removeService()));
55 connect(ui.copyButton, SIGNAL(clicked()), this, SLOT(copyToClipboard()));
56 connect(ui.browseButton, SIGNAL(clicked()), this, SLOT(browseDirectory()));
57 connect(ui.serviceWidget, SIGNAL(itemClicked(QTableWidgetItem*)),
58 this, SLOT(serviceSelectionChanged()));
59 connect(ui.serviceWidget, SIGNAL(itemChanged(QTableWidgetItem*)),
60 this, SLOT(valueChanged()));
61}
62
63/** Destructor */
65{
66 delete _services;
67 delete _torServices;
68}
69
70/** Called when the user changes the UI translation. */
71void
73{
74 ui.retranslateUi(this);
75}
76
77/** Saves changes made to settings on the Server settings page. */
78bool
79ServicePage::save(QString &errmsg)
80{
81 ServiceSettings serviceSettings(Vidalia::torControl());
82 QList<Service> serviceList;
83 QList<Service> publishedServices;
84 int index = 0;
85
86 while(index < ui.serviceWidget->rowCount()) {
87 QString address = ui.serviceWidget->item(index,0)->text();
88 QString virtualPort = ui.serviceWidget->item(index,1)->text();
89 QString physicalAddress = ui.serviceWidget->item(index,2)->text();
90 QString directoryPath = ui.serviceWidget->item(index,3)->text();
91 bool enabled = _services->value(index).enabled();
92 Service temp(address, virtualPort, physicalAddress, directoryPath,
93 enabled);
95 _services->value(ui.serviceWidget->currentRow()).additionalServiceOptions());
96 serviceList.push_back(temp);
97 if(enabled) {
98 publishedServices.push_back(temp);
99 }
100 index++;
101 }
102
103 bool save = checkBeforeSaving(serviceList);
104 if(save) {
105 ServiceList sList;
106 if(serviceList.size() > 0) {
107 sList.setServices(serviceList);
108 } else {
109 _services = new QMap<int, Service>();
110 sList.setServices(_services->values());
111 }
112 serviceSettings.setServices(sList);
113 if(publishedServices.size() > 0) {
114 startServicesInTor(publishedServices);
115 } else {
116 QString errmsg1 = tr("Error while trying to unpublish all services");
117 QString &errmsg = errmsg1;
118 serviceSettings.unpublishAllServices(&errmsg);
119 }
120 return true;
121 } else {
122 errmsg = tr("Please configure at least a service directory and a virtual "
123 "port for each service you want to save. Remove the other ones.");
124 return false;
125 }
126}
127
128/** this method checks if either all services have minimal
129 * configuration or not */
130bool
131ServicePage::checkBeforeSaving(QList<Service> serviceList)
132{
133 bool result = true;
134 foreach(Service s, serviceList) {
135 if(s.serviceDirectory().isEmpty() || s.virtualPort().isEmpty()) {
136 result = false;
137 break;
138 }
139 }
140 return result;
141}
142
143/** this method generates the configuration string for a list of services */
144void
145ServicePage::startServicesInTor(QList<Service> services)
146{
147 ServiceSettings serviceSettings(Vidalia::torControl());
148 QString serviceConfString;
149 QString errmsg = "Error while trying to publish services.";
150 QListIterator<Service> it(services);
151
152 while(it.hasNext()) {
153 Service temp = it.next();
154 serviceConfString.append("hiddenservicedir=" +
155 string_escape(temp.serviceDirectory()) + " ");
156 serviceConfString.append("hiddenserviceport=" +
158 (temp.physicalAddressPort().isEmpty() ? "" : " " +
159 temp.physicalAddressPort())));
160 serviceConfString.append(" " + temp.additionalServiceOptions());
161 }
162 serviceSettings.applyServices(serviceConfString, &errmsg);
163}
164
165/** Loads previously saved settings */
166void
168{
169 ServiceSettings serviceSettings(Vidalia::torControl());
170 QList<Service> torServiceList;
171
172 ui.removeButton->setEnabled(false);
173 ui.copyButton->setEnabled(false);
174 ui.browseButton->setEnabled(false);
175 // get all services
176 _services->clear();
177 _torServices->clear();
178
179 QString torConfigurationString = serviceSettings.getHiddenServiceDirectories();
180 torServiceList = extractSingleServices(torConfigurationString);
181 QList<Service> completeList = torServiceList;
182 // the services stored with vidalia
183 ServiceList serviceList = serviceSettings.getServices();
184 QList<Service> serviceSettingsList = serviceList.services();
185 QListIterator<Service> it(serviceSettingsList);
186 // check whether a service is already in the list because he is published
187 while(it.hasNext()) {
188 Service temp = it.next();
189 if(isServicePublished(temp, torServiceList) == false) {
190 completeList.push_back(temp);
191 }
192 }
193 // generate the _services data structure used during vidalia session
194 QListIterator<Service> it2(completeList);
195 int index = 0;
196 while (it2.hasNext()) {
197 Service tempService = it2.next();
198 _services->insert(index, tempService);
199 index++;
200 }
202}
203
204/** this method returns a list of services by parsing the configuration
205 * string given by the tor controller */
206QList<Service>
208{
209 QList<Service> list;
210 QStringList strList = conf.split("250 HiddenServiceDir");
211 strList.removeFirst();
212 QListIterator<QString> it(strList);
213 //for each service directory splitted string = service
214 while(it.hasNext()) {
215 QString temp = it.next();
216 list.push_back(generateService(temp));
217 }
218 return list;
219}
220
221/** this return a Service by parseing the configuration string
222 * of Tor and storeing its values into the object */
225{
226 QString additionalOptions = s;
227 // remove directory
228 int index = additionalOptions.indexOf("250",1);
229 additionalOptions.remove(0, index+4);
230 // remove the first appearance of the port
231 int startindex = additionalOptions.indexOf("hiddenserviceport", 0,
232 Qt::CaseInsensitive);
233 int endindex = additionalOptions.indexOf("250", startindex);
234 if(endindex != -1) {
235 additionalOptions.remove(startindex, (endindex-startindex)+4);
236 //remove all appearances of "250"
237 while(additionalOptions.contains("250")) {
238 int i = additionalOptions.indexOf("250", 0);
239 additionalOptions.remove(i, 4);
240 }
241 // prepare for correct quotation
242 if (!additionalOptions.endsWith('\n')) {
243 additionalOptions.append("\n");
244 }
245 //quote the values
246 int j = additionalOptions.indexOf("=", 0);
247 while(j != -1) {
248 additionalOptions.insert(j+1, "\"");
249 int end = additionalOptions.indexOf("\n", j);
250 additionalOptions.insert(end, "\"");
251 j = additionalOptions.indexOf("=", end);
252 }
253 //replace the line brakes with a space and create one single line
254 additionalOptions.replace(QString("\n"), QString(" "));
255 } else {
256 additionalOptions = "";
257 }
258
259 QString address, virtualPort, physAddressPort, serviceDir;
260 // service directory
261 QStringList strList = s.split("\n");
262 QString tempServiceDir = strList.first().trimmed();
263 serviceDir = tempServiceDir.remove(0, 1);
264 //virtual port
265 QStringList strList2 = s.split("HiddenServicePort");
266 strList2.removeFirst();
267 QStringList strList3 = strList2.first().split("\n");
268 QStringList strList4 = strList3.first().split(" ");
269 if(strList4.size() > 0) {
270 QString tempVirtualPort = strList4.first();
271 virtualPort = tempVirtualPort.remove(0, 1);
272 strList4.removeFirst();
273 //physical address:port
274 if(!strList4.isEmpty()) {
275 physAddressPort = strList4.first().trimmed();
276 }
277 } else {
278 QString tempVirtualPort = strList3.first();
279 virtualPort = tempVirtualPort.remove(0, 1);
280 }
281 //get .onion address
282 QString serviceHostnameDir = serviceDir;
283 serviceHostnameDir.append("/");
284 serviceHostnameDir.append("hostname");
285 QFile file(serviceHostnameDir);
286 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
287 address = "[Directory not found]";
288 } else {
289 QTextStream in(&file);
290 QString hostname;
291 while (!in.atEnd()) {
292 hostname.append(in.readLine());
293 }
294 address = hostname;
295 }
296 Service service(address, virtualPort, physAddressPort, serviceDir, true);
297 service.setAdditionalServiceOptions(additionalOptions);
298 _torServices->insert(serviceDir, service);
299 return service;
300}
301
302/** this method checks either a service is published or not */
303bool
304ServicePage::isServicePublished(Service service, QList<Service> torServices)
305{
306 QListIterator<Service> it(torServices);
307 while(it.hasNext()) {
308 Service temp = it.next();
309 if(temp.serviceDirectory().compare(service.serviceDirectory()) == 0) {
310 return true;
311 }
312 }
313 return false;
314}
315
316/** this method creates/displays the values for each service
317 * shown in the service listing */
318void
319ServicePage::initServiceTable(QMap<int, Service>* services)
320{
321 // clean the widget
322 int rows = ui.serviceWidget->rowCount();
323 for(int i = 0; i < rows; i++) {
324 ui.serviceWidget->removeRow(0);
325 }
326 //for each service
327 int index = 0;
328 while(index < services->size()) {
329 Service tempService = services->value(index);
330 ui.serviceWidget->insertRow(index);
331 QTableWidgetItem *cboxitem = new QTableWidgetItem();
332 cboxitem->setFlags(Qt::ItemIsSelectable);
333 QTableWidgetItem *addressitem = new QTableWidgetItem();
334 addressitem->setFlags(Qt::ItemIsSelectable);
335 if(tempService.serviceAddress().length() < 0) {
336 addressitem->setText(tempService.serviceAddress());
337 } else {
338 QString serviceHostnameDir = tempService.serviceDirectory();
339 serviceHostnameDir.append("/");
340 serviceHostnameDir.append("hostname");
341 QFile file(serviceHostnameDir);
342 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
343 addressitem->setText("[Directory not found]");
344 } else {
345 QTextStream in(&file);
346 QString hostname;
347 while (!in.atEnd()) {
348 hostname.append(in.readLine());
349 }
350 addressitem->setText(hostname);
351 tempService.setServiceAddress(hostname);
352 }
353 }
354 addressitem->setData(32, addressitem->text());
355 QTableWidgetItem *serviceDir =
356 new QTableWidgetItem(tempService.serviceDirectory(), 0);
357 serviceDir->setData(32, tempService.serviceDirectory());
358 QTableWidgetItem* virtualportitem =
359 new QTableWidgetItem(tempService.virtualPort(), 0);
360 virtualportitem->setData(32, tempService.virtualPort());
361 QTableWidgetItem* targetitem =
362 new QTableWidgetItem(tempService.physicalAddressPort(),0);
363 targetitem->setData(32, tempService.physicalAddressPort());
364 if(tempService.enabled()) {
365 cboxitem->setCheckState(Qt::Checked);
366 serviceDir->setFlags(Qt::ItemIsSelectable);
367 } else {
368 cboxitem->setCheckState(Qt::Unchecked);
369 }
370 cboxitem->setTextAlignment(Qt::AlignCenter);
371 ui.serviceWidget->setItem(index, 0, addressitem);
372 ui.serviceWidget->setItem(index, 1, virtualportitem);
373 ui.serviceWidget->setItem(index, 2, targetitem);
374 ui.serviceWidget->setItem(index, 3, serviceDir);
375 ui.serviceWidget->setItem(index, 4, cboxitem);
376 index++;
377 }
378}
379
380/** this method is called when the user clicks the "Add"-Button
381 * it generates a new empty table entrie(row) */
382void
384{
385 int rows = ui.serviceWidget->rowCount();
386 ui.serviceWidget->insertRow(rows);
387 QTableWidgetItem *address = new QTableWidgetItem("["+tr("Created by Tor")+"]");
388 address->setFlags(Qt::ItemIsSelectable);
389 QTableWidgetItem *dummy = new QTableWidgetItem();
390 QTableWidgetItem *dummy2 = new QTableWidgetItem();
391 QTableWidgetItem *dummy3 = new QTableWidgetItem();
392 QTableWidgetItem *cboxitem = new QTableWidgetItem();
393 cboxitem->setFlags(Qt::ItemIsSelectable);
394 cboxitem->setCheckState(Qt::Checked);
395 ui.serviceWidget->setItem(rows, 0, address);
396 ui.serviceWidget->setItem(rows, 1, dummy);
397 ui.serviceWidget->setItem(rows, 2, dummy2);
398 ui.serviceWidget->setItem(rows, 3, dummy3);
399 ui.serviceWidget->setItem(rows, 4, cboxitem);
400 Service s;
401 s.setEnabled(true);
402 _services->insert(rows, s);
403}
404
405/** this method is called when the user clicks the "Remove"-Button
406 * it removes a service/row of the service listing */
407void
409{
410 int rows = ui.serviceWidget->rowCount();
411 int selrow = ui.serviceWidget->currentRow();
412 if(selrow < 0 || selrow >= _services->size()) {
413 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."),
415 return;
416 } else {
417 ui.serviceWidget->removeRow(selrow);
418 //decrease all other service keys
419 for(int i = 0; i < (rows-selrow-1); i++) {
420 int index = i+selrow;
421 Service s = _services->take(index+1);
422 _services->insert(index, s);
423 }
424 }
426}
427
428/** this method is called when the user clicks on the "Copy"-Button, it
429 * copies the .onion-Address of the selected service into the clipboard */
430void
432{
433 int selrow = ui.serviceWidget->currentRow();
434 if(selrow < 0 || selrow >= _services->size()) {
435 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."),
437 return;
438 } else {
439 QString onionAddress = ui.serviceWidget->item(selrow,0)->text();
440 QClipboard *clipboard = QApplication::clipboard();
441 QString clipboardText;
442 QTableWidgetItem* selectedItem = ui.serviceWidget->item(selrow,0);
443 clipboardText.append(selectedItem->text());
444 clipboard->setText(clipboardText);
445 }
446}
447
448/** this method is called when the user clicks on the "Brows"-Button it opens
449 * a QFileDialog to choose a service directory */
450void
452{
453 int selrow = ui.serviceWidget->currentRow();
454 if(selrow < 0 || selrow >= _services->size()) {
455 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."),
457 return;
458 } else {
459 QString dirname =
460 QFileDialog::getExistingDirectory(this,
461 tr("Select Service Directory"), "",
462 QFileDialog::ShowDirsOnly
463 | QFileDialog::DontResolveSymlinks);
464
465 if (dirname.isEmpty()) {
466 return;
467 }
468 ui.serviceWidget->item(selrow,3)->setText(dirname);
469 Service s = _services->take(selrow);
470 s.setServiceDirectory(dirname);
471 _services->insert(selrow, s);
472 }
473}
474
475/** this method is called when the selects an other tablewidgetitem */
476void
478{
479 bool emptyTable = false;
480 if(ui.serviceWidget->rowCount() > 0) {
481 ui.removeButton->setEnabled(true);
482 ui.copyButton->setEnabled(true);
483 ui.browseButton->setEnabled(true);
484 } else {
485 ui.removeButton->setEnabled(false);
486 ui.copyButton->setEnabled(false);
487 ui.browseButton->setEnabled(false);
488 emptyTable = true;
489 }
490 int currentRow = ui.serviceWidget->currentRow();
491 if(emptyTable == false) {
492 QTableWidgetItem* item = ui.serviceWidget->item(currentRow, 0);
493 if(item != NULL) {
494 bool b = item->text().contains(".onion");
495 ui.copyButton->setEnabled(b);
496 }
497 }
498
499 QString selDir = _services->value(ui.serviceWidget->currentRow()).
500 serviceDirectory();
501 QList<QString> strList = _torServices->keys();
502 if(selDir.length() > 0) {
503 QListIterator<QString> it(strList);
504 while(it.hasNext()) {
505 QString temp = it.next();
506 if(selDir.compare(temp) == 0) {
507 ui.browseButton->setEnabled(false);
508 break;
509 }
510 }
511 }
512 // if the user has clicked on the checkbox cell
513 if(ui.serviceWidget->currentColumn() == 4) {
514 Service service = _services->take(currentRow);
515 QTableWidgetItem* item = ui.serviceWidget->item(currentRow,4);
516 if(service.enabled()) {
517 item->setCheckState(Qt::Unchecked);
518 service.setEnabled(false);
519 } else {
520 item->setCheckState(Qt::Checked);
521 service.setEnabled(true);
522 }
523 _services->insert(currentRow, service);
524 }
525}
526
527/** this method is called when the user finished editing a cell and it provides
528 * that only valid values are set */
529void
531{
532 int pos = 0;
533 QIntValidator* portValidator = new QIntValidator(1, 65535, this);
534 DomainValidator* domainValidator = new DomainValidator(this);
535 IpValidator* ipValidator = new IpValidator(this);
536 QTableWidgetItem* item = ui.serviceWidget->currentItem();
537 if (item == NULL || item->text() == NULL || item->text().length() == 0) {
538 // nothing to validate here
539 return;
540 }
541 QString text = item->text();
542 switch (item->column()) {
543 case 1: // virtual port
544 if(portValidator->validate(text, pos) == QValidator::Acceptable) {
545 // correct data; buffer value in user role 32
546 item->setData(32, text);
547 } else {
548 //incorrect data; restore value from user role 32
549 VMessageBox::warning(this, tr("Error"),
550 tr("Virtual Port may only contain valid port numbers [1..65535]."),
552 item->setText(item->data(32).toString());
553 }
554 break;
555 case 2: // target
556 if(text.contains(":")) {
557 // check for <address>:<port>
558 QStringList strList = text.split(":");
559 if (strList.size() != 2) {
560 goto invalid;
561 }
562 QString address = strList.at(0);
563 QString port = strList.at(1);
564 if((address.compare("localhost") != 0 &&
565 ipValidator->validate(address, pos) != QValidator::Acceptable &&
566 domainValidator->validate(address, pos) != QValidator::Acceptable) ||
567 portValidator->validate(port, pos) != QValidator::Acceptable) {
568 goto invalid;
569 }
570 } else { // either <address> or <port>
571 if (text.compare("localhost") != 0 &&
572 ipValidator->validate(text, pos) != QValidator::Acceptable &&
573 domainValidator->validate(text, pos) != QValidator::Acceptable &&
574 portValidator->validate(text, pos) != QValidator::Acceptable) {
575 goto invalid;
576 }
577 }
578 goto valid;
579 invalid:
580 VMessageBox::warning(this, tr("Error"),
581 tr("Target may only contain address:port, address, or port."),
583 item->setText(item->data(32).toString());
584 break;
585 valid:
586 item->setData(32, text);
587 break;
588 case 3: // service directory
589 // compare with directories of other enabled services
590 for (int index = 0; index < ui.serviceWidget->rowCount(); index++) {
591 // skip own row
592 if(index == item->row()) {
593 continue;
594 }
595 QTableWidgetItem* compareWith = ui.serviceWidget->item(index, 3);
596 if(compareWith != NULL) {
597 QString actualDir = compareWith->text();
598 if(actualDir.length() > 0 && text.compare(actualDir) == 0) {
599 // service directory already in use
600 VMessageBox::warning(this, tr("Error"),
601 tr("Directory already in use by another service."),
603 item->setText(item->data(32).toString());
604 return;
605 }
606 }
607 }
608 // correct data; buffer value in user role 32
609 item->setData(32, text);
610 break;
611 }
612}
613
stop errmsg connect(const QHostAddress &address, quint16 port)
QValidator::State validate(QString &input) const
Definition: IpValidator.cpp:45
QString virtualPort() const
Definition: Service.h:32
void setAdditionalServiceOptions(QString options)
Definition: Service.cpp:66
void setEnabled(bool enabled)
Definition: Service.cpp:36
QString physicalAddressPort() const
Definition: Service.h:34
void setServiceAddress(QString serviceAddress)
Definition: Service.cpp:42
void setServiceDirectory(QString serviceDirectory)
Definition: Service.cpp:60
QString serviceDirectory() const
Definition: Service.h:36
QString additionalServiceOptions() const
Definition: Service.h:40
bool enabled() const
Definition: Service.h:38
QString serviceAddress() const
Definition: Service.h:30
QList< Service > services() const
Definition: ServiceList.h:32
void setServices(QList< Service > services)
Definition: ServiceList.cpp:31
bool save(QString &errmsg)
Definition: ServicePage.cpp:79
virtual void retranslateUi()
Definition: ServicePage.cpp:72
void initServiceTable(QMap< int, Service > *_services)
bool isServicePublished(Service service, QList< Service > torServices)
QMap< int, Service > * _services
Definition: ServicePage.h:70
Service generateService(QString serviceString)
void copyToClipboard()
Ui::ServicePage ui
Definition: ServicePage.h:75
bool checkBeforeSaving(QList< Service > services)
void browseDirectory()
void valueChanged()
QMap< QString, Service > * _torServices
Definition: ServicePage.h:72
void startServicesInTor(QList< Service > services)
void removeService()
ServicePage(QWidget *parent=0)
Definition: ServicePage.cpp:30
QList< Service > extractSingleServices(QString conf)
void serviceSelectionChanged()
void addService()
void setServices(ServiceList services)
void applyServices(QString value, QString *errmsg)
ServiceList getServices()
void unpublishAllServices(QString *errmsg)
QString getHiddenServiceDirectories()
static int warning(QWidget *parent, QString caption, QString text, int button0, int button1=NoButton, int button2=NoButton)
static TorControl * torControl()
Definition: Vidalia.h:76
QString i(QString str)
Definition: html.cpp:32
QString b(QString str)
Definition: html.cpp:39
QString string_escape(const QString &str)
Definition: stringutil.cpp:131