ServicePage.cpp

Go to the documentation of this file.
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 #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 
Generated on Mon Aug 30 22:58:55 2010 for Vidalia by  doxygen 1.6.3