1 /* extcap_options_dialog.cpp
2  *
3  * Wireshark - Network traffic analyzer
4  * By Gerald Combs <gerald@wireshark.org>
5  * Copyright 1998 Gerald Combs
6  *
7  * SPDX-License-Identifier: GPL-2.0-or-later
8  */
9 
10 #include <config.h>
11 
12 #include <glib.h>
13 
14 #include <extcap_options_dialog.h>
15 #include <ui_extcap_options_dialog.h>
16 
17 #include <wireshark_application.h>
18 
19 #include <QMessageBox>
20 #include <QHash>
21 #include <QHBoxLayout>
22 #include <QVBoxLayout>
23 #include <QGridLayout>
24 #include <QUrl>
25 #include <QDesktopServices>
26 #include <QTabWidget>
27 
28 #include "ringbuffer.h"
29 #include "ui/capture_ui_utils.h"
30 #include "ui/capture_globals.h"
31 #include "ui/iface_lists.h"
32 #include "ui/last_open_dir.h"
33 
34 #include "ui/ws_ui_util.h"
35 #include "ui/util.h"
36 #include <wsutil/utf8_entities.h>
37 
38 #include <cstdio>
39 #include <epan/addr_resolv.h>
40 #include <wsutil/filesystem.h>
41 
42 #include <extcap.h>
43 #include <extcap_parser.h>
44 
45 #include <ui/qt/utils/qt_ui_utils.h>
46 
47 #include <epan/prefs.h>
48 #include <ui/preference_utils.h>
49 
50 #include <ui/qt/wireshark_application.h>
51 #include <ui/qt/utils/variant_pointer.h>
52 
53 #include <ui/qt/extcap_argument.h>
54 #include <ui/qt/extcap_argument_file.h>
55 #include <ui/qt/extcap_argument_multiselect.h>
56 
ExtcapOptionsDialog(QWidget * parent)57 ExtcapOptionsDialog::ExtcapOptionsDialog(QWidget *parent) :
58     QDialog(parent),
59     ui(new Ui::ExtcapOptionsDialog),
60     device_name(""),
61     device_idx(0)
62 {
63     ui->setupUi(this);
64 
65     setWindowTitle(wsApp->windowTitleString(tr("Interface Options")));
66 
67     ui->checkSaveOnStart->setCheckState(prefs.extcap_save_on_start ? Qt::Checked : Qt::Unchecked);
68 
69     ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Start"));
70 }
71 
createForDevice(QString & dev_name,QWidget * parent)72 ExtcapOptionsDialog * ExtcapOptionsDialog::createForDevice(QString &dev_name, QWidget *parent)
73 {
74     interface_t *device;
75     ExtcapOptionsDialog * resultDialog = NULL;
76     bool dev_found = false;
77     guint if_idx;
78 
79     if (dev_name.length() == 0)
80         return NULL;
81 
82     for (if_idx = 0; if_idx < global_capture_opts.all_ifaces->len; if_idx++)
83     {
84         device = &g_array_index(global_capture_opts.all_ifaces, interface_t, if_idx);
85         if (dev_name.compare(QString(device->name)) == 0 && device->if_info.type == IF_EXTCAP)
86         {
87             dev_found = true;
88             break;
89         }
90     }
91 
92     if (! dev_found)
93         return NULL;
94 
95     resultDialog = new ExtcapOptionsDialog(parent);
96     resultDialog->device_name = QString(dev_name);
97     resultDialog->device_idx = if_idx;
98 
99     resultDialog->setWindowTitle(wsApp->windowTitleString(tr("Interface Options") + ": " + device->display_name));
100 
101     resultDialog->updateWidgets();
102 
103     /* mark required fields */
104     resultDialog->anyValueChanged();
105 
106     return resultDialog;
107 }
108 
109 
~ExtcapOptionsDialog()110 ExtcapOptionsDialog::~ExtcapOptionsDialog()
111 {
112     delete ui;
113 }
114 
on_buttonBox_accepted()115 void ExtcapOptionsDialog::on_buttonBox_accepted()
116 {
117     if (saveOptionToCaptureInfo()) {
118         /* Starting a new capture with those values */
119         prefs.extcap_save_on_start = ui->checkSaveOnStart->checkState() == Qt::Checked;
120 
121         if (prefs.extcap_save_on_start)
122             storeValues();
123 
124         accept();
125     }
126 }
127 
anyValueChanged()128 void ExtcapOptionsDialog::anyValueChanged()
129 {
130     bool allowStart = true;
131 
132     ExtcapArgumentList::const_iterator iter;
133 
134     /* All arguments are being iterated, to ensure, that any error handling catches all arguments */
135     for (iter = extcapArguments.constBegin(); iter != extcapArguments.constEnd(); ++iter)
136     {
137         /* The dynamic casts are necessary, because we come here using the Signal/Slot system
138          * of Qt, and -in short- Q_OBJECT classes cannot be multiple inherited. Another possibility
139          * would be to use Q_INTERFACE, but this causes way more nightmares, and we really just
140          * need here an explicit cast for the check functionality */
141         if (dynamic_cast<ExtArgBool *>((*iter)) != NULL)
142         {
143             if (! ((ExtArgBool *)*iter)->isValid())
144                 allowStart = false;
145         }
146         else if (dynamic_cast<ExtArgRadio *>((*iter)) != NULL)
147         {
148             if (! ((ExtArgRadio *)*iter)->isValid())
149                 allowStart = false;
150         }
151         else if (dynamic_cast<ExtArgSelector *>((*iter)) != NULL)
152         {
153             if (! ((ExtArgSelector *)*iter)->isValid())
154                 allowStart = false;
155         }
156         else if (dynamic_cast<ExtArgMultiSelect *>((*iter)) != NULL)
157         {
158             if (! ((ExtArgMultiSelect *)*iter)->isValid())
159                 allowStart = false;
160         }
161         else if (dynamic_cast<ExtcapArgumentFileSelection *>((*iter)) != NULL)
162         {
163             if (! ((ExtcapArgumentFileSelection *)*iter)->isValid())
164                 allowStart = false;
165         }
166         else if (dynamic_cast<ExtArgNumber *>((*iter)) != NULL)
167         {
168             if (! ((ExtArgNumber *)*iter)->isValid())
169                 allowStart = false;
170         }
171         else if (dynamic_cast<ExtArgText *>((*iter)) != NULL)
172         {
173             if (! ((ExtArgText *)*iter)->isValid())
174                 allowStart = false;
175         }
176         else if (dynamic_cast<ExtArgTimestamp *>((*iter)) != NULL)
177         {
178             if (! ((ExtArgTimestamp *)*iter)->isValid())
179                 allowStart = false;
180         }
181         else
182             if (! (*iter)->isValid())
183                 allowStart = false;
184     }
185 
186     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowStart);
187 }
188 
loadArguments()189 void ExtcapOptionsDialog::loadArguments()
190 {
191     GList * arguments = Q_NULLPTR, * walker = Q_NULLPTR, * item = Q_NULLPTR;
192     ExtcapArgument * argument = Q_NULLPTR;
193 
194     if (device_name.length() == 0  )
195         return;
196 
197     extcapArguments.clear();
198 
199     arguments = g_list_first(extcap_get_if_configuration(device_name.toUtf8().constData()));
200 
201     ExtcapArgumentList required;
202     ExtcapArgumentList optional;
203 
204     walker = arguments;
205     while (walker != Q_NULLPTR)
206     {
207         item = g_list_first(gxx_list_data(GList *, walker));
208         while (item != Q_NULLPTR)
209         {
210             argument = ExtcapArgument::create(gxx_list_data(extcap_arg *, item), this);
211             if (argument != Q_NULLPTR)
212             {
213                 if (argument->isRequired())
214                     required << argument;
215                 else
216                     optional << argument;
217 
218             }
219             item = item->next;
220         }
221         walker = gxx_list_next(walker);
222     }
223 
224     if (required.length() > 0)
225         extcapArguments << required;
226 
227     if (optional.length() > 0)
228         extcapArguments << optional;
229 
230     /* argument items are now owned by ExtcapArgument. Only free the lists */
231     extcap_free_if_configuration(arguments, FALSE);
232 }
233 
updateWidgets()234 void ExtcapOptionsDialog::updateWidgets()
235 {
236     QWidget * lblWidget = NULL, *editWidget = NULL;
237     ExtcapArgument * argument = NULL;
238     bool allowStart = true;
239 
240     unsigned int counter = 0;
241 
242     if (device_name.length() == 0  )
243         return;
244 
245     /* find existing layout */
246     if (ui->verticalLayout->children().count() > 0)
247     {
248         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
249         QWidget * item = ui->verticalLayout->itemAt(0)->widget();
250         if (item)
251         {
252             ui->verticalLayout->removeItem(ui->verticalLayout->itemAt(0));
253             delete item;
254         }
255     }
256 
257     QHash<QString, QWidget *> layouts;
258 
259     /* Load all extcap arguments */
260     loadArguments();
261 
262     /* exit if no arguments have been found. This is a precaution, it should
263      * never happen, that this dialog get's called without any arguments */
264     if (extcapArguments.count() == 0)
265     {
266         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
267         return;
268     }
269     ui->checkSaveOnStart->setText(tr("Save parameter(s) on capture start", "", extcapArguments.count()));
270 
271     QStringList groupKeys;
272     QString defaultKeyName(tr("Default"));
273     /* QMap sorts keys, therefore the groups are sorted by appearance */
274     QMap<int, QString> groups;
275 
276     /* Look for all necessary tabs */
277     ExtcapArgumentList::iterator iter = extcapArguments.begin();
278     while (iter != extcapArguments.end())
279     {
280         argument = (ExtcapArgument *)(*iter);
281         QString groupKey = argument->group();
282         if (groupKey.length() > 0)
283         {
284             if (! groups.values().contains(groupKey))
285                 groups.insert(argument->argNr(), groupKey);
286         }
287         else if (! groups.keys().contains(0))
288         {
289             groups.insert(0, defaultKeyName);
290             groupKey = defaultKeyName;
291         }
292 
293         if (! layouts.keys().contains(groupKey))
294         {
295             QWidget * tabWidget = new QWidget(this);
296             QGridLayout * tabLayout = new QGridLayout(tabWidget);
297             tabWidget->setLayout(tabLayout);
298 
299             layouts.insert(groupKey, tabWidget);
300         }
301 
302         ++iter;
303     }
304     groupKeys << groups.values();
305 
306     /* Iterate over all arguments and do the following:
307      *  1. create the label for each element
308      *  2. create an editor for each element
309      *  3. add both to the layout for the tab widget
310      */
311     iter = extcapArguments.begin();
312     while (iter != extcapArguments.end())
313     {
314         argument = (ExtcapArgument *)(*iter);
315         QString groupKey = defaultKeyName;
316         if (argument->group().length() > 0)
317             groupKey = argument->group();
318 
319         /* Skip non-assigned group keys, this happens if the configuration of the extcap is faulty */
320         if (! layouts.keys().contains(groupKey))
321         {
322             ++iter;
323             continue;
324         }
325 
326         QGridLayout * layout = ((QGridLayout *)layouts[groupKey]->layout());
327         lblWidget = argument->createLabel((QWidget *)this);
328         if (lblWidget != NULL)
329         {
330             layout->addWidget(lblWidget, counter, 0, Qt::AlignVCenter);
331             editWidget = argument->createEditor((QWidget *) this);
332             if (editWidget != NULL)
333             {
334                 editWidget->setProperty(QString("extcap").toLocal8Bit(), VariantPointer<ExtcapArgument>::asQVariant(argument));
335                 layout->addWidget(editWidget, counter, 1, Qt::AlignVCenter);
336             }
337 
338             if (argument->isRequired() && ! argument->isValid())
339                 allowStart = false;
340 
341             connect(argument, SIGNAL(valueChanged()), this, SLOT(anyValueChanged()));
342 
343             counter++;
344         }
345         ++iter;
346     }
347 
348     if (counter > 0)
349     {
350         setStyleSheet ("QLabel[isRequired=\"true\"] { font-weight: bold; } ");
351 
352         ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(allowStart);
353 
354         QWidget * mainWidget = Q_NULLPTR;
355 
356         /* We should never display the dialog, if no settings are present */
357         Q_ASSERT(layouts.count() > 0);
358 
359         if (layouts.count() > 1)
360         {
361             QTabWidget * tabs = new QTabWidget(this);
362             foreach (QString key, groupKeys)
363             {
364                 layouts[key]->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding));
365                 tabs->addTab(layouts[key], key);
366             }
367 
368             tabs->setCurrentIndex(0);
369             mainWidget = tabs;
370         }
371         else if (layouts.count() == 1)
372             mainWidget = layouts[layouts.keys().at(0)];
373 
374         ui->verticalLayout->addWidget(mainWidget);
375         ui->verticalLayout->addSpacerItem(new QSpacerItem(20, 100, QSizePolicy::Minimum, QSizePolicy::Expanding));
376     }
377     else
378     {
379         QList<QString> keys = layouts.keys();
380         foreach (QString key, keys)
381             delete(layouts[key]);
382     }
383 }
384 
385 // Not sure why we have to do this manually.
on_buttonBox_rejected()386 void ExtcapOptionsDialog::on_buttonBox_rejected()
387 {
388     reject();
389 }
390 
on_buttonBox_helpRequested()391 void ExtcapOptionsDialog::on_buttonBox_helpRequested()
392 {
393     interface_t *device;
394     QString interface_help = NULL;
395 
396     device = &g_array_index(global_capture_opts.all_ifaces, interface_t, device_idx);
397     interface_help = QString(extcap_get_help_for_ifname(device->name));
398     /* The extcap interface didn't provide an help. Let's go with the default */
399     if (interface_help.isEmpty()) {
400         wsApp->helpTopicAction(HELP_EXTCAP_OPTIONS_DIALOG);
401         return;
402     }
403 
404     QUrl help_url(interface_help);
405 
406     /* Check the existence for a local file */
407     if (help_url.isLocalFile()) {
408         QFileInfo help_file(help_url.toLocalFile());
409         if (!help_file.exists()) {
410             QMessageBox::warning(this, tr("Extcap Help cannot be found"),
411                 QString(tr("The help for the extcap interface %1 cannot be found. Given file: %2"))
412                     .arg(device->name).arg(help_url.path()),
413                 QMessageBox::Ok);
414             return;
415         }
416     }
417 
418     /* We have an actual url or an existing local file. Let's open it. */
419     QDesktopServices::openUrl(help_url);
420 }
421 
saveOptionToCaptureInfo()422 bool ExtcapOptionsDialog::saveOptionToCaptureInfo()
423 {
424     GHashTable * ret_args;
425     interface_t *device;
426 
427     device = &g_array_index(global_capture_opts.all_ifaces, interface_t, device_idx);
428     ret_args = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
429 
430     ExtcapArgumentList::const_iterator iter;
431 
432     for (iter = extcapArguments.constBegin(); iter != extcapArguments.constEnd(); ++iter)
433     {
434         QString call = (*iter)->call();
435         QString value = (*iter)->value();
436 
437         if ((*iter)->argument()->arg_type != EXTCAP_ARG_BOOLFLAG && value.length() == 0)
438             continue;
439 
440         if (call.length() <= 0)
441             continue;
442 
443         if (value.compare((*iter)->defaultValue()) == 0)
444             continue;
445 
446         gchar * call_string = g_strdup(call.toStdString().c_str());
447         gchar * value_string = NULL;
448         if (value.length() > 0)
449             value_string = g_strdup(value.toStdString().c_str());
450 
451         g_hash_table_insert(ret_args, call_string, value_string);
452     }
453 
454     if (device->external_cap_args_settings != NULL)
455       g_hash_table_unref(device->external_cap_args_settings);
456     device->external_cap_args_settings = ret_args;
457     return true;
458 }
459 
on_buttonBox_clicked(QAbstractButton * button)460 void ExtcapOptionsDialog::on_buttonBox_clicked(QAbstractButton *button)
461 {
462     /* Only the save button has the ActionRole */
463     if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::ResetRole)
464         resetValues();
465 }
466 
resetValues()467 void ExtcapOptionsDialog::resetValues()
468 {
469     ExtcapArgumentList::const_iterator iter;
470     QString value;
471     bool doStore = false;
472 
473     int count = ui->verticalLayout->count();
474     if (count > 0)
475     {
476         QList<QLayout *> layouts;
477         if (qobject_cast<QTabWidget *>(ui->verticalLayout->itemAt(0)->widget()))
478         {
479             QTabWidget * tabs = qobject_cast<QTabWidget *>(ui->verticalLayout->itemAt(0)->widget());
480             for (int cnt = 0; cnt < tabs->count(); cnt++)
481             {
482                 layouts.append(tabs->widget(cnt)->layout());
483             }
484         }
485         else
486             layouts.append(ui->verticalLayout->itemAt(0)->layout());
487 
488         for (int cnt = 0; cnt < layouts.count(); cnt++)
489         {
490             QGridLayout * layout = qobject_cast<QGridLayout *>(layouts.at(cnt));
491             if (! layout)
492                 continue;
493 
494             for (int row = 0; row < layout->rowCount(); row++)
495             {
496                 QWidget * child = Q_NULLPTR;
497                 if (layout->itemAtPosition(row, 1))
498                     child = qobject_cast<QWidget *>(layout->itemAtPosition(row, 1)->widget());
499 
500                 if (child)
501                 {
502                     /* Don't need labels, the edit widget contains the extcapargument property value */
503                     ExtcapArgument * arg = 0;
504                     QVariant prop = child->property(QString("extcap").toLocal8Bit());
505 
506                     if (prop.isValid())
507                     {
508                         arg = VariantPointer<ExtcapArgument>::asPtr(prop);
509 
510                         /* value<> can fail */
511                         if (arg)
512                         {
513                             arg->resetValue();
514 
515                             /* replacing the edit widget after resetting will lead to default value */
516                             QWidget * newWidget = arg->createEditor((QWidget *) this);
517                             if (newWidget != NULL)
518                             {
519                                 newWidget->setProperty(QString("extcap").toLocal8Bit(), VariantPointer<ExtcapArgument>::asQVariant(arg));
520                                 QLayoutItem * oldItem = layout->replaceWidget(child, newWidget);
521                                 if (oldItem)
522                                 {
523                                     delete child;
524                                     delete oldItem;
525                                 }
526                             }
527 
528                             doStore = true;
529                         }
530                     }
531                 }
532             }
533 
534         }
535 
536         /* this stores all values to the preferences */
537         if (doStore)
538         {
539             storeValues();
540             anyValueChanged();
541         }
542     }
543 }
544 
getArgumentSettings(bool useCallsAsKey,bool includeEmptyValues)545 GHashTable *ExtcapOptionsDialog::getArgumentSettings(bool useCallsAsKey, bool includeEmptyValues)
546 {
547     GHashTable * entries = g_hash_table_new(g_str_hash, g_str_equal);
548     ExtcapArgumentList::const_iterator iter;
549 
550     QString value;
551 
552     /* All arguments are being iterated, to ensure, that any error handling catches all arguments */
553     for (iter = extcapArguments.constBegin(); iter != extcapArguments.constEnd(); ++iter)
554     {
555         ExtcapArgument * argument = (ExtcapArgument *)(*iter);
556         bool isBoolflag = false;
557 
558         /* The dynamic casts are necessary, because we come here using the Signal/Slot system
559          * of Qt, and -in short- Q_OBJECT classes cannot be multiple inherited. Another possibility
560          * would be to use Q_INTERFACE, but this causes way more nightmares, and we really just
561          * need here an explicit cast for the check functionality */
562         if (dynamic_cast<ExtArgBool *>((*iter)) != NULL)
563         {
564             value = ((ExtArgBool *)*iter)->prefValue();
565             isBoolflag = true;
566         }
567         else if (dynamic_cast<ExtArgRadio *>((*iter)) != NULL)
568         {
569             value = ((ExtArgRadio *)*iter)->prefValue();
570         }
571         else if (dynamic_cast<ExtArgSelector *>((*iter)) != NULL)
572         {
573             value = ((ExtArgSelector *)*iter)->prefValue();
574         }
575         else if (dynamic_cast<ExtArgMultiSelect *>((*iter)) != NULL)
576         {
577             value = ((ExtArgMultiSelect *)*iter)->prefValue();
578         }
579         else if (dynamic_cast<ExtcapArgumentFileSelection *>((*iter)) != NULL)
580         {
581             value = ((ExtcapArgumentFileSelection *)*iter)->prefValue();
582         }
583         else if (dynamic_cast<ExtArgNumber *>((*iter)) != NULL)
584         {
585             value = ((ExtArgNumber *)*iter)->prefValue();
586         }
587         else if (dynamic_cast<ExtArgText *>((*iter)) != NULL)
588         {
589             value = ((ExtArgText *)*iter)->prefValue();
590         }
591         else if (dynamic_cast<ExtArgTimestamp *>((*iter)) != NULL)
592         {
593             value = ((ExtArgTimestamp *)*iter)->prefValue();
594         }
595         else
596             value = (*iter)->prefValue();
597 
598         QString key = argument->prefKey(device_name);
599         if (useCallsAsKey)
600             key = argument->call();
601 
602         if ((key.length() > 0) && (includeEmptyValues || isBoolflag || value.length() > 0) )
603         {
604             gchar * val = g_strdup(value.toStdString().c_str());
605 
606             g_hash_table_insert(entries, g_strdup(key.toStdString().c_str()), val);
607         }
608     }
609 
610     return entries;
611 }
612 
storeValues()613 void ExtcapOptionsDialog::storeValues()
614 {
615     GHashTable * entries = getArgumentSettings();
616 
617     if (g_hash_table_size(entries) > 0)
618     {
619         if (prefs_store_ext_multiple("extcap", entries))
620             wsApp->emitAppSignal(WiresharkApplication::PreferencesChanged);
621 
622     }
623 }
624 
loadValuesFor(int argNum,QString argumentName,QString parent)625 ExtcapValueList ExtcapOptionsDialog::loadValuesFor(int argNum, QString argumentName, QString parent)
626 {
627     ExtcapValueList elements;
628     GList * walker = 0, * values = 0;
629     extcap_value * v;
630 
631     QList<QWidget *> children = findChildren<QWidget *>();
632     foreach (QWidget * child, children)
633         child->setEnabled(false);
634 
635     QString argcall = argumentName;
636     if (argcall.startsWith("--"))
637         argcall = argcall.right(argcall.size()-2);
638 
639     GHashTable * entries = getArgumentSettings(true, false);
640 
641     values = extcap_get_if_configuration_values(this->device_name.toStdString().c_str(), argcall.toStdString().c_str(), entries);
642 
643     for (walker = g_list_first((GList *)(values)); walker != NULL ; walker = walker->next)
644     {
645         v = (extcap_value *) walker->data;
646         if (v == NULL || v->display == NULL || v->call == NULL)
647             break;
648 
649         /* Only accept values for this argument */
650         if (v->arg_num != argNum)
651             break;
652 
653         QString valParent = QString().fromUtf8(v->parent);
654 
655         if (parent.compare(valParent) == 0)
656         {
657 
658             QString display = QString().fromUtf8(v->display);
659             QString call = QString().fromUtf8(v->call);
660 
661             ExtcapValue element = ExtcapValue(display, call,
662                             v->enabled == (gboolean)TRUE, v->is_default == (gboolean)TRUE);
663 
664 #if 0
665             /* TODO: Disabled due to wrong parent handling. It leads to an infinite loop for now. To implement this properly, other things
666                will be needed, like new arguments for setting the parent in the call to the extcap utility*/
667             if (!call.isEmpty())
668                 element.setChildren(this->loadValuesFor(argumentName, call));
669 #endif
670 
671             elements.append(element);
672         }
673     }
674 
675     foreach (QWidget * child, children)
676         child->setEnabled(true);
677 
678     return elements;
679 }
680