Qt: Add Install Plugin to Tools menu

Add an option to the tools menu to copy a binary plugin file
(a .dll or .so) to the personal plugin folder.

This avoids the user having to create the paths manually and
knowning a lot of relatively unimportant details about where and
how Wireshark loads binary plugins.

It will also try to validate the plugin and do some sanity checks to
ensure the ABI is compatible.
This commit is contained in:
João Valverde 2023-12-07 01:12:49 +00:00
parent 158e104569
commit c76a28fca4
6 changed files with 162 additions and 8 deletions

View File

@ -105,6 +105,9 @@ The following features are new (or have been significantly updated) since versio
selected rows or the entire table can be saved or copied to the clipboard
in several formats.
* New menu:Tools[Install Plugin] option provides a convenient method to install
a binary plugin to the personal folder.
//=== Removed Features and Support
// === Removed Dissectors

View File

@ -25,6 +25,9 @@ DIAG_ON(frame-larger-than=)
#include <wsutil/wslog.h>
#include <wsutil/ws_assert.h>
#include <wsutil/version_info.h>
#ifdef HAVE_PLUGINS
#include <wsutil/plugins.h>
#endif
#include <epan/prefs.h>
#include <epan/stats_tree_priv.h>
#include <epan/plugin_if.h>
@ -3292,3 +3295,89 @@ void WiresharkMainWindow::openTLSKeylogDialog()
tlskeylog_dialog_->raise();
tlskeylog_dialog_->activateWindow();
}
#ifdef HAVE_PLUGINS
void WiresharkMainWindow::installPersonalBinaryPlugin()
{
QMessageBox::StandardButton reply;
QString caption = mainApp->windowTitleString(tr("Install plugin"));
// Get the plugin file path to install
QString plugin_filter = tr("Binary plugin (*%1)").arg(WS_PLUGIN_MODULE_SUFFIX);
QString src_path = WiresharkFileDialog::getOpenFileName(this, caption, "", plugin_filter);
if (src_path.isEmpty()) {
return;
}
// Plugins from untrusted sources can be dangerous.
// Inform the user and ask for confirmation.
// We need to do this before checking the plugin compatibility.
reply = QMessageBox::question(this, caption,
tr("Plugins can execute arbitrary code as the current user. "
"Make sure you trust it before installing.\n\n"
"Continue installing the file \"%1\" to the personal plugin folder?")
.arg(src_path));
if (reply != QMessageBox::Yes) {
return;
}
// Check if this is a valid plugin file and get the plugin binary type.
// The function will report any errors.
plugin_type_e have_type = plugins_check_file(qUtf8Printable(src_path));
if (have_type == WS_PLUGIN_NONE)
return;
// Create the destination folder if necessary
QString type_path = gchar_free_to_qstring(plugins_pers_type_folder(have_type));
QDir type_dir(type_path);
if (!type_dir.exists(type_path)) {
if (!type_dir.mkpath(type_path)) {
QMessageBox::warning(this, caption,
tr("Failed to create the directory: %1").arg(type_path));
return;
}
}
// Check if the file exists in the destination folder, in case we need to overwrite it
// XXX Overwriting will probably fail on Windows because the plugin is loaded. We need
// a way to load and unload plugins without having to restart the program.
QFileInfo file_info(src_path);
QString file_name = file_info.fileName();
if (type_dir.exists(file_name)) {
#ifdef Q_OS_WIN
QMessageBox::warning(this, tr("Install Plugin"),
tr("The plugin already exists in the personal plugin folder."));
return;
#else
reply = QMessageBox::question(this, caption,
tr("The file already exists. Do you want to overwrite it?"));
if (reply == QMessageBox::Yes) {
if (!type_dir.remove(file_name)) {
QMessageBox::warning(this, caption,
tr("Error removing the old plugin file from the personal plugin folder."));
return;
}
}
else {
// Overwrite refused, we are done
return;
}
#endif // Q_OS_WIN
}
// File does not exist in the destination or the user chose to overwrite it
// Do the copy to install it.
QString dst_path = type_dir.filePath(file_name);
if (!QFile::copy(src_path, dst_path)) {
QMessageBox::warning(this, caption,
tr("Failed to copy the file to the destination: %1").arg(dst_path));
return;
}
// Success
QMessageBox::information(this, caption,
tr("Plugin '%1' installed successfully. "
"You must restart the program to be able to use it.").arg(file_name));
}
#endif

View File

@ -524,6 +524,9 @@ private slots:
QString findRtpStreams(QVector<rtpstream_id_t *> *stream_ids, bool reverse);
void openTLSKeylogDialog();
#ifdef HAVE_PLUGINS
void installPersonalBinaryPlugin();
#endif
friend class MainApplication;
};

View File

@ -3798,6 +3798,12 @@ void WiresharkMainWindow::connectToolsMenuActions()
});
connect(main_ui_->actionToolsTLSKeylog, &QAction::triggered, this, &WiresharkMainWindow::openTLSKeylogDialog);
#ifdef HAVE_PLUGINS
QAction *actionToolsInstallPlugin = new QAction(tr("Install Plugin"), this);
connect(actionToolsInstallPlugin, &QAction::triggered, this, &WiresharkMainWindow::installPersonalBinaryPlugin);
main_ui_->menuTools->addAction(actionToolsInstallPlugin);
#endif
}
// Help Menu

View File

@ -122,13 +122,6 @@ pass_plugin_compatibility(const char *name, plugin_type_e type,
return true;
}
// GLib and Qt allow ".dylib" and ".so" on macOS. Should we do the same?
#ifdef _WIN32
#define MODULE_SUFFIX ".dll"
#else
#define MODULE_SUFFIX ".so"
#endif
static void
scan_plugins_dir(GHashTable *plugins_module, const char *dirpath,
plugin_type_e type, plugin_scope_e scope)
@ -156,7 +149,7 @@ scan_plugins_dir(GHashTable *plugins_module, const char *dirpath,
while ((name = g_dir_read_name(dir)) != NULL) {
/* Skip anything but files with .dll or .so. */
if (!g_str_has_suffix(name, MODULE_SUFFIX))
if (!g_str_has_suffix(name, WS_PLUGIN_MODULE_SUFFIX))
continue;
/*
@ -330,6 +323,54 @@ plugins_supported(void)
return g_module_supported();
}
plugin_type_e
plugins_check_file(const char *from_filename)
{
char *name;
GModule *handle;
void *symbol;
plugin_type_e have_type;
int abi_version;
struct ws_module *module;
handle = g_module_open(from_filename, G_MODULE_BIND_LAZY);
if (handle == NULL) {
/* g_module_error() provides file path. */
report_failure("Couldn't load file: %s", g_module_error());
return WS_PLUGIN_NONE;
}
/* Search for the entry point for the plugin registration function */
if (!g_module_symbol(handle, "wireshark_load_module", &symbol)) {
report_failure("The file '%s' has no \"wireshark_load_module\" symbol", from_filename);
return WS_PLUGIN_NONE;
}
DIAG_OFF_PEDANTIC
/* Load module. */
have_type = ((ws_load_module_func)symbol)(&abi_version, NULL, &module);
DIAG_ON_PEDANTIC
name = g_path_get_basename(from_filename);
if (!pass_plugin_compatibility(name, have_type, abi_version)) {
g_module_close(handle);
g_free(name);
return WS_PLUGIN_NONE;
}
g_module_close(handle);
g_free(name);
return have_type;
}
char *
plugins_pers_type_folder(plugin_type_e type)
{
return g_build_filename(get_plugins_pers_dir_with_version(),
type_to_dir(type), (const char *)NULL);
}
int
plugins_abi_version(plugin_type_e type)
{

View File

@ -18,6 +18,7 @@ extern "C" {
#endif /* __cplusplus */
typedef enum {
WS_PLUGIN_NONE,
WS_PLUGIN_EPAN,
WS_PLUGIN_WIRETAP,
WS_PLUGIN_CODEC
@ -39,6 +40,13 @@ typedef enum {
#define WS_PLUGIN_DESC_TAP_LISTENER (1UL << 4)
#define WS_PLUGIN_DESC_DFILTER (1UL << 5)
// GLib and Qt allow ".dylib" and ".so" on macOS. Should we do the same?
#ifdef _WIN32
#define WS_PLUGIN_MODULE_SUFFIX ".dll"
#else
#define WS_PLUGIN_MODULE_SUFFIX ".so"
#endif
typedef void plugins_t;
typedef void (*module_register_func)(void);
@ -72,6 +80,10 @@ WS_DLL_PUBLIC void plugins_cleanup(plugins_t *plugins);
WS_DLL_PUBLIC bool plugins_supported(void);
WS_DLL_PUBLIC plugin_type_e plugins_check_file(const char *path);
WS_DLL_PUBLIC char *plugins_pers_type_folder(plugin_type_e type);
WS_DLL_PUBLIC
int plugins_abi_version(plugin_type_e type);