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