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

Generated on Wed Dec 23 21:06:55 2009 for Vidalia by  doxygen 1.6.1