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 #include "ServicePage.h" 00012 #include "Service.h" 00013 #include "ServiceList.h" 00014 #include "VMessageBox.h" 00015 #include "ConfigDialog.h" 00016 #include "IpValidator.h" 00017 #include "DomainValidator.h" 00018 #include "Vidalia.h" 00019 00020 #include "stringutil.h" 00021 #include "file.h" 00022 00023 #include <QHeaderView> 00024 #include <QClipboard> 00025 #include <QFile> 00026 #include <QTextStream> 00027 00028 00029 /** Constructor */ 00030 ServicePage::ServicePage(QWidget *parent) 00031 : ConfigPage(parent, "Services") 00032 { 00033 /* Invoke the Qt Designer generated object setup routine */ 00034 ui.setupUi(this); 00035 /* A QMap, mapping from the row number to the Entity for 00036 * all services */ 00037 _services = new QMap<int, Service>(); 00038 /* A QMap, mapping from the directory path to the Entity for 00039 * all Tor services */ 00040 _torServices = new QMap<QString, Service>(); 00041 00042 ui.serviceWidget->horizontalHeader()->resizeSection(0, 150); 00043 ui.serviceWidget->horizontalHeader()->resizeSection(1, 89); 00044 ui.serviceWidget->horizontalHeader()->resizeSection(2, 100); 00045 ui.serviceWidget->horizontalHeader()->resizeSection(3, 120); 00046 ui.serviceWidget->horizontalHeader()->resizeSection(4, 60); 00047 ui.serviceWidget->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch); 00048 ui.serviceWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); 00049 ui.serviceWidget->horizontalHeader()->setResizeMode(2, QHeaderView::Stretch); 00050 ui.serviceWidget->horizontalHeader()->setResizeMode(3, QHeaderView::Stretch); 00051 ui.serviceWidget->verticalHeader()->hide(); 00052 00053 connect(ui.addButton, SIGNAL(clicked()), this, SLOT(addService())); 00054 connect(ui.removeButton, SIGNAL(clicked()), this, SLOT(removeService())); 00055 connect(ui.copyButton, SIGNAL(clicked()), this, SLOT(copyToClipboard())); 00056 connect(ui.browseButton, SIGNAL(clicked()), this, SLOT(browseDirectory())); 00057 connect(ui.serviceWidget, SIGNAL(itemClicked(QTableWidgetItem*)), 00058 this, SLOT(serviceSelectionChanged())); 00059 connect(ui.serviceWidget, SIGNAL(itemChanged(QTableWidgetItem*)), 00060 this, SLOT(valueChanged())); 00061 } 00062 00063 /** Destructor */ 00064 ServicePage::~ServicePage() 00065 { 00066 delete _services; 00067 delete _torServices; 00068 } 00069 00070 /** Called when the user changes the UI translation. */ 00071 void 00072 ServicePage::retranslateUi() 00073 { 00074 ui.retranslateUi(this); 00075 } 00076 00077 /** Saves changes made to settings on the Server settings page. */ 00078 bool 00079 ServicePage::save(QString &errmsg) 00080 { 00081 ServiceSettings serviceSettings(Vidalia::torControl()); 00082 QList<Service> serviceList; 00083 QList<Service> publishedServices; 00084 int index = 0; 00085 00086 while(index < ui.serviceWidget->rowCount()) { 00087 QString address = ui.serviceWidget->item(index,0)->text(); 00088 QString virtualPort = ui.serviceWidget->item(index,1)->text(); 00089 QString physicalAddress = ui.serviceWidget->item(index,2)->text(); 00090 QString directoryPath = ui.serviceWidget->item(index,3)->text(); 00091 bool enabled = _services->value(index).enabled(); 00092 Service temp(address, virtualPort, physicalAddress, directoryPath, 00093 enabled); 00094 temp.setAdditionalServiceOptions( 00095 _services->value(ui.serviceWidget->currentRow()).additionalServiceOptions()); 00096 serviceList.push_back(temp); 00097 if(enabled) { 00098 publishedServices.push_back(temp); 00099 } 00100 index++; 00101 } 00102 00103 bool save = checkBeforeSaving(serviceList); 00104 if(save) { 00105 ServiceList sList; 00106 if(serviceList.size() > 0) { 00107 sList.setServices(serviceList); 00108 } else { 00109 _services = new QMap<int, Service>(); 00110 sList.setServices(_services->values()); 00111 } 00112 serviceSettings.setServices(sList); 00113 if(publishedServices.size() > 0) { 00114 startServicesInTor(publishedServices); 00115 } else { 00116 QString errmsg1 = tr("Error while trying to unpublish all services"); 00117 QString &errmsg = errmsg1; 00118 serviceSettings.unpublishAllServices(&errmsg); 00119 } 00120 return true; 00121 } else { 00122 errmsg = tr("Please configure at least a service directory and a virtual " 00123 "port for each service you want to save. Remove the other ones."); 00124 return false; 00125 } 00126 } 00127 00128 /** this method checks if either all services have minimal 00129 * configuration or not */ 00130 bool 00131 ServicePage::checkBeforeSaving(QList<Service> serviceList) 00132 { 00133 bool result = true; 00134 foreach(Service s, serviceList) { 00135 if(s.serviceDirectory().isEmpty() || s.virtualPort().isEmpty()) { 00136 result = false; 00137 break; 00138 } 00139 } 00140 return result; 00141 } 00142 00143 /** this method generates the configuration string for a list of services */ 00144 void 00145 ServicePage::startServicesInTor(QList<Service> services) 00146 { 00147 ServiceSettings serviceSettings(Vidalia::torControl()); 00148 QString serviceConfString; 00149 QString errmsg = "Error while trying to publish services."; 00150 QListIterator<Service> it(services); 00151 00152 while(it.hasNext()) { 00153 Service temp = it.next(); 00154 serviceConfString.append("hiddenservicedir=" + 00155 string_escape(temp.serviceDirectory()) + " "); 00156 serviceConfString.append("hiddenserviceport=" + 00157 string_escape(temp.virtualPort() + 00158 (temp.physicalAddressPort().isEmpty() ? "" : " " + 00159 temp.physicalAddressPort()))); 00160 serviceConfString.append(" " + temp.additionalServiceOptions()); 00161 } 00162 serviceSettings.applyServices(serviceConfString, &errmsg); 00163 } 00164 00165 /** Loads previously saved settings */ 00166 void 00167 ServicePage::load() 00168 { 00169 ServiceSettings serviceSettings(Vidalia::torControl()); 00170 QList<Service> torServiceList; 00171 00172 ui.removeButton->setEnabled(false); 00173 ui.copyButton->setEnabled(false); 00174 ui.browseButton->setEnabled(false); 00175 // get all services 00176 _services->clear(); 00177 _torServices->clear(); 00178 00179 QString torConfigurationString = serviceSettings.getHiddenServiceDirectories(); 00180 torServiceList = extractSingleServices(torConfigurationString); 00181 QList<Service> completeList = torServiceList; 00182 // the services stored with vidalia 00183 ServiceList serviceList = serviceSettings.getServices(); 00184 QList<Service> serviceSettingsList = serviceList.services(); 00185 QListIterator<Service> it(serviceSettingsList); 00186 // check whether a service is already in the list because he is published 00187 while(it.hasNext()) { 00188 Service temp = it.next(); 00189 if(isServicePublished(temp, torServiceList) == false) { 00190 completeList.push_back(temp); 00191 } 00192 } 00193 // generate the _services data structure used during vidalia session 00194 QListIterator<Service> it2(completeList); 00195 int index = 0; 00196 while (it2.hasNext()) { 00197 Service tempService = it2.next(); 00198 _services->insert(index, tempService); 00199 index++; 00200 } 00201 initServiceTable(_services); 00202 } 00203 00204 /** this method returns a list of services by parsing the configuration 00205 * string given by the tor controller */ 00206 QList<Service> 00207 ServicePage::extractSingleServices(QString conf) 00208 { 00209 QList<Service> list; 00210 QStringList strList = conf.split("250 HiddenServiceDir"); 00211 strList.removeFirst(); 00212 QListIterator<QString> it(strList); 00213 //for each service directory splitted string = service 00214 while(it.hasNext()) { 00215 QString temp = it.next(); 00216 list.push_back(generateService(temp)); 00217 } 00218 return list; 00219 } 00220 00221 /** this return a Service by parseing the configuration string 00222 * of Tor and storeing its values into the object */ 00223 Service 00224 ServicePage::generateService(QString s) 00225 { 00226 QString additionalOptions = s; 00227 // remove directory 00228 int index = additionalOptions.indexOf("250",1); 00229 additionalOptions.remove(0, index+4); 00230 // remove the first appearance of the port 00231 int startindex = additionalOptions.indexOf("hiddenserviceport", 0, 00232 Qt::CaseInsensitive); 00233 int endindex = additionalOptions.indexOf("250", startindex); 00234 if(endindex != -1) { 00235 additionalOptions.remove(startindex, (endindex-startindex)+4); 00236 //remove all appearances of "250" 00237 while(additionalOptions.contains("250")) { 00238 int i = additionalOptions.indexOf("250", 0); 00239 additionalOptions.remove(i, 4); 00240 } 00241 // prepare for correct quotation 00242 if (!additionalOptions.endsWith('\n')) { 00243 additionalOptions.append("\n"); 00244 } 00245 //quote the values 00246 int j = additionalOptions.indexOf("=", 0); 00247 while(j != -1) { 00248 additionalOptions.insert(j+1, "\""); 00249 int end = additionalOptions.indexOf("\n", j); 00250 additionalOptions.insert(end, "\""); 00251 j = additionalOptions.indexOf("=", end); 00252 } 00253 //replace the line brakes with a space and create one single line 00254 additionalOptions.replace(QString("\n"), QString(" ")); 00255 } else { 00256 additionalOptions = ""; 00257 } 00258 00259 QString address, virtualPort, physAddressPort, serviceDir; 00260 // service directory 00261 QStringList strList = s.split("\n"); 00262 QString tempServiceDir = strList.first().trimmed(); 00263 serviceDir = tempServiceDir.remove(0, 1); 00264 //virtual port 00265 QStringList strList2 = s.split("HiddenServicePort"); 00266 strList2.removeFirst(); 00267 QStringList strList3 = strList2.first().split("\n"); 00268 QStringList strList4 = strList3.first().split(" "); 00269 if(strList4.size() > 0) { 00270 QString tempVirtualPort = strList4.first(); 00271 virtualPort = tempVirtualPort.remove(0, 1); 00272 strList4.removeFirst(); 00273 //physical address:port 00274 if(!strList4.isEmpty()) { 00275 physAddressPort = strList4.first().trimmed(); 00276 } 00277 } else { 00278 QString tempVirtualPort = strList3.first(); 00279 virtualPort = tempVirtualPort.remove(0, 1); 00280 } 00281 //get .onion address 00282 QString serviceHostnameDir = serviceDir; 00283 serviceHostnameDir.append("/"); 00284 serviceHostnameDir.append("hostname"); 00285 QFile file(serviceHostnameDir); 00286 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 00287 address = "[Directory not found]"; 00288 } else { 00289 QTextStream in(&file); 00290 QString hostname; 00291 while (!in.atEnd()) { 00292 hostname.append(in.readLine()); 00293 } 00294 address = hostname; 00295 } 00296 Service service(address, virtualPort, physAddressPort, serviceDir, true); 00297 service.setAdditionalServiceOptions(additionalOptions); 00298 _torServices->insert(serviceDir, service); 00299 return service; 00300 } 00301 00302 /** this method checks either a service is published or not */ 00303 bool 00304 ServicePage::isServicePublished(Service service, QList<Service> torServices) 00305 { 00306 QListIterator<Service> it(torServices); 00307 while(it.hasNext()) { 00308 Service temp = it.next(); 00309 if(temp.serviceDirectory().compare(service.serviceDirectory()) == 0) { 00310 return true; 00311 } 00312 } 00313 return false; 00314 } 00315 00316 /** this method creates/displays the values for each service 00317 * shown in the service listing */ 00318 void 00319 ServicePage::initServiceTable(QMap<int, Service>* services) 00320 { 00321 // clean the widget 00322 int rows = ui.serviceWidget->rowCount(); 00323 for(int i = 0; i < rows; i++) { 00324 ui.serviceWidget->removeRow(0); 00325 } 00326 //for each service 00327 int index = 0; 00328 while(index < services->size()) { 00329 Service tempService = services->value(index); 00330 ui.serviceWidget->insertRow(index); 00331 QTableWidgetItem *cboxitem = new QTableWidgetItem(); 00332 cboxitem->setFlags(Qt::ItemIsSelectable); 00333 QTableWidgetItem *addressitem = new QTableWidgetItem(); 00334 addressitem->setFlags(Qt::ItemIsSelectable); 00335 if(tempService.serviceAddress().length() < 0) { 00336 addressitem->setText(tempService.serviceAddress()); 00337 } else { 00338 QString serviceHostnameDir = tempService.serviceDirectory(); 00339 serviceHostnameDir.append("/"); 00340 serviceHostnameDir.append("hostname"); 00341 QFile file(serviceHostnameDir); 00342 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 00343 addressitem->setText("[Directory not found]"); 00344 } else { 00345 QTextStream in(&file); 00346 QString hostname; 00347 while (!in.atEnd()) { 00348 hostname.append(in.readLine()); 00349 } 00350 addressitem->setText(hostname); 00351 tempService.setServiceAddress(hostname); 00352 } 00353 } 00354 addressitem->setData(32, addressitem->text()); 00355 QTableWidgetItem *serviceDir = 00356 new QTableWidgetItem(tempService.serviceDirectory(), 0); 00357 serviceDir->setData(32, tempService.serviceDirectory()); 00358 QTableWidgetItem* virtualportitem = 00359 new QTableWidgetItem(tempService.virtualPort(), 0); 00360 virtualportitem->setData(32, tempService.virtualPort()); 00361 QTableWidgetItem* targetitem = 00362 new QTableWidgetItem(tempService.physicalAddressPort(),0); 00363 targetitem->setData(32, tempService.physicalAddressPort()); 00364 if(tempService.enabled()) { 00365 cboxitem->setCheckState(Qt::Checked); 00366 serviceDir->setFlags(Qt::ItemIsSelectable); 00367 } else { 00368 cboxitem->setCheckState(Qt::Unchecked); 00369 } 00370 cboxitem->setTextAlignment(Qt::AlignCenter); 00371 ui.serviceWidget->setItem(index, 0, addressitem); 00372 ui.serviceWidget->setItem(index, 1, virtualportitem); 00373 ui.serviceWidget->setItem(index, 2, targetitem); 00374 ui.serviceWidget->setItem(index, 3, serviceDir); 00375 ui.serviceWidget->setItem(index, 4, cboxitem); 00376 index++; 00377 } 00378 } 00379 00380 /** this method is called when the user clicks the "Add"-Button 00381 * it generates a new empty table entrie(row) */ 00382 void 00383 ServicePage::addService() 00384 { 00385 int rows = ui.serviceWidget->rowCount(); 00386 ui.serviceWidget->insertRow(rows); 00387 QTableWidgetItem *address = new QTableWidgetItem("["+tr("Created by Tor")+"]"); 00388 address->setFlags(Qt::ItemIsSelectable); 00389 QTableWidgetItem *dummy = new QTableWidgetItem(); 00390 QTableWidgetItem *dummy2 = new QTableWidgetItem(); 00391 QTableWidgetItem *dummy3 = new QTableWidgetItem(); 00392 QTableWidgetItem *cboxitem = new QTableWidgetItem(); 00393 cboxitem->setFlags(Qt::ItemIsSelectable); 00394 cboxitem->setCheckState(Qt::Checked); 00395 ui.serviceWidget->setItem(rows, 0, address); 00396 ui.serviceWidget->setItem(rows, 1, dummy); 00397 ui.serviceWidget->setItem(rows, 2, dummy2); 00398 ui.serviceWidget->setItem(rows, 3, dummy3); 00399 ui.serviceWidget->setItem(rows, 4, cboxitem); 00400 Service s; 00401 s.setEnabled(true); 00402 _services->insert(rows, s); 00403 } 00404 00405 /** this method is called when the user clicks the "Remove"-Button 00406 * it removes a service/row of the service listing */ 00407 void 00408 ServicePage::removeService() 00409 { 00410 int rows = ui.serviceWidget->rowCount(); 00411 int selrow = ui.serviceWidget->currentRow(); 00412 if(selrow < 0 || selrow >= _services->size()) { 00413 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."), 00414 VMessageBox::Ok); 00415 return; 00416 } else { 00417 ui.serviceWidget->removeRow(selrow); 00418 //decrease all other service keys 00419 for(int i = 0; i < (rows-selrow-1); i++) { 00420 int index = i+selrow; 00421 Service s = _services->take(index+1); 00422 _services->insert(index, s); 00423 } 00424 } 00425 serviceSelectionChanged(); 00426 } 00427 00428 /** this method is called when the user clicks on the "Copy"-Button, it 00429 * copies the .onion-Address of the selected service into the clipboard */ 00430 void 00431 ServicePage::copyToClipboard() 00432 { 00433 int selrow = ui.serviceWidget->currentRow(); 00434 if(selrow < 0 || selrow >= _services->size()) { 00435 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."), 00436 VMessageBox::Ok); 00437 return; 00438 } else { 00439 QString onionAddress = ui.serviceWidget->item(selrow,0)->text(); 00440 QClipboard *clipboard = QApplication::clipboard(); 00441 QString clipboardText; 00442 QTableWidgetItem* selectedItem = ui.serviceWidget->item(selrow,0); 00443 clipboardText.append(selectedItem->text()); 00444 clipboard->setText(clipboardText); 00445 } 00446 } 00447 00448 /** this method is called when the user clicks on the "Brows"-Button it opens 00449 * a QFileDialog to choose a service directory */ 00450 void 00451 ServicePage::browseDirectory() 00452 { 00453 int selrow = ui.serviceWidget->currentRow(); 00454 if(selrow < 0 || selrow >= _services->size()) { 00455 VMessageBox::warning(this, tr("Error"), tr("Please select a Service."), 00456 VMessageBox::Ok); 00457 return; 00458 } else { 00459 QString dirname = 00460 QFileDialog::getExistingDirectory(this, 00461 tr("Select Service Directory"), "", 00462 QFileDialog::ShowDirsOnly 00463 | QFileDialog::DontResolveSymlinks); 00464 00465 if (dirname.isEmpty()) { 00466 return; 00467 } 00468 ui.serviceWidget->item(selrow,3)->setText(dirname); 00469 Service s = _services->take(selrow); 00470 s.setServiceDirectory(dirname); 00471 _services->insert(selrow, s); 00472 } 00473 } 00474 00475 /** this method is called when the selects an other tablewidgetitem */ 00476 void 00477 ServicePage::serviceSelectionChanged() 00478 { 00479 bool emptyTable = false; 00480 if(ui.serviceWidget->rowCount() > 0) { 00481 ui.removeButton->setEnabled(true); 00482 ui.copyButton->setEnabled(true); 00483 ui.browseButton->setEnabled(true); 00484 } else { 00485 ui.removeButton->setEnabled(false); 00486 ui.copyButton->setEnabled(false); 00487 ui.browseButton->setEnabled(false); 00488 emptyTable = true; 00489 } 00490 int currentRow = ui.serviceWidget->currentRow(); 00491 if(emptyTable == false) { 00492 QTableWidgetItem* item = ui.serviceWidget->item(currentRow, 0); 00493 if(item != NULL) { 00494 bool b = item->text().contains(".onion"); 00495 ui.copyButton->setEnabled(b); 00496 } 00497 } 00498 00499 QString selDir = _services->value(ui.serviceWidget->currentRow()). 00500 serviceDirectory(); 00501 QList<QString> strList = _torServices->keys(); 00502 if(selDir.length() > 0) { 00503 QListIterator<QString> it(strList); 00504 while(it.hasNext()) { 00505 QString temp = it.next(); 00506 if(selDir.compare(temp) == 0) { 00507 ui.browseButton->setEnabled(false); 00508 break; 00509 } 00510 } 00511 } 00512 // if the user has clicked on the checkbox cell 00513 if(ui.serviceWidget->currentColumn() == 4) { 00514 Service service = _services->take(currentRow); 00515 QTableWidgetItem* item = ui.serviceWidget->item(currentRow,4); 00516 if(service.enabled()) { 00517 item->setCheckState(Qt::Unchecked); 00518 service.setEnabled(false); 00519 } else { 00520 item->setCheckState(Qt::Checked); 00521 service.setEnabled(true); 00522 } 00523 _services->insert(currentRow, service); 00524 } 00525 } 00526 00527 /** this method is called when the user finished editing a cell and it provides 00528 * that only valid values are set */ 00529 void 00530 ServicePage::valueChanged() 00531 { 00532 int pos = 0; 00533 QIntValidator* portValidator = new QIntValidator(1, 65535, this); 00534 DomainValidator* domainValidator = new DomainValidator(this); 00535 IpValidator* ipValidator = new IpValidator(this); 00536 QTableWidgetItem* item = ui.serviceWidget->currentItem(); 00537 if (item == NULL || item->text() == NULL || item->text().length() == 0) { 00538 // nothing to validate here 00539 return; 00540 } 00541 QString text = item->text(); 00542 switch (item->column()) { 00543 case 1: // virtual port 00544 if(portValidator->validate(text, pos) == QValidator::Acceptable) { 00545 // correct data; buffer value in user role 32 00546 item->setData(32, text); 00547 } else { 00548 //incorrect data; restore value from user role 32 00549 VMessageBox::warning(this, tr("Error"), 00550 tr("Virtual Port may only contain valid port numbers [1..65535]."), 00551 VMessageBox::Ok); 00552 item->setText(item->data(32).toString()); 00553 } 00554 break; 00555 case 2: // target 00556 if(text.contains(":")) { 00557 // check for <address>:<port> 00558 QStringList strList = text.split(":"); 00559 if (strList.size() != 2) { 00560 goto invalid; 00561 } 00562 QString address = strList.at(0); 00563 QString port = strList.at(1); 00564 if((address.compare("localhost") != 0 && 00565 ipValidator->validate(address, pos) != QValidator::Acceptable && 00566 domainValidator->validate(address, pos) != QValidator::Acceptable) || 00567 portValidator->validate(port, pos) != QValidator::Acceptable) { 00568 goto invalid; 00569 } 00570 } else { // either <address> or <port> 00571 if (text.compare("localhost") != 0 && 00572 ipValidator->validate(text, pos) != QValidator::Acceptable && 00573 domainValidator->validate(text, pos) != QValidator::Acceptable && 00574 portValidator->validate(text, pos) != QValidator::Acceptable) { 00575 goto invalid; 00576 } 00577 } 00578 goto valid; 00579 invalid: 00580 VMessageBox::warning(this, tr("Error"), 00581 tr("Target may only contain address:port, address, or port."), 00582 VMessageBox::Ok); 00583 item->setText(item->data(32).toString()); 00584 break; 00585 valid: 00586 item->setData(32, text); 00587 break; 00588 case 3: // service directory 00589 // compare with directories of other enabled services 00590 for (int index = 0; index < ui.serviceWidget->rowCount(); index++) { 00591 // skip own row 00592 if(index == item->row()) { 00593 continue; 00594 } 00595 QTableWidgetItem* compareWith = ui.serviceWidget->item(index, 3); 00596 if(compareWith != NULL) { 00597 QString actualDir = compareWith->text(); 00598 if(actualDir.length() > 0 && text.compare(actualDir) == 0) { 00599 // service directory already in use 00600 VMessageBox::warning(this, tr("Error"), 00601 tr("Directory already in use by another service."), 00602 VMessageBox::Ok); 00603 item->setText(item->data(32).toString()); 00604 return; 00605 } 00606 } 00607 } 00608 // correct data; buffer value in user role 32 00609 item->setData(32, text); 00610 break; 00611 } 00612 } 00613