Convert to using use SPDX identifier on wsutil directory
[metze/wireshark/wip.git] / wsutil / plugins.c
1 /* plugins.c
2  * plugin routines
3  *
4  * Wireshark - Network traffic analyzer
5  * By Gerald Combs <gerald@wireshark.org>
6  * Copyright 1998 Gerald Combs
7  *
8  * SPDX-License-Identifier: GPL-2.0+
9  */
10
11 #include "config.h"
12
13 #ifdef HAVE_PLUGINS
14
15 #include <time.h>
16
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <errno.h>
21
22 #include <glib.h>
23 #include <gmodule.h>
24
25 #include <wsutil/filesystem.h>
26 #include <wsutil/privileges.h>
27 #include <wsutil/file_util.h>
28 #include <wsutil/report_message.h>
29
30 #include <wsutil/plugins.h>
31 #include <wsutil/ws_printf.h> /* ws_debug_printf */
32
33 typedef struct _plugin {
34     GModule        *handle;       /* handle returned by g_module_open */
35     gchar          *name;         /* plugin name */
36     const gchar    *version;      /* plugin version */
37     GString        *types;        /* description with types this plugin supports */
38 } plugin;
39
40 static GHashTable *plugins_table = NULL;
41
42 static void
43 free_plugin(gpointer _p)
44 {
45     plugin *p = (plugin *)_p;
46     g_module_close(p->handle);
47     g_free(p->name);
48     g_string_free(p->types, TRUE);
49     g_free(p);
50 }
51
52 /*
53  * Add a new plugin type.
54  * Takes a callback routine as an argument; it is called for each plugin
55  * we find, and handed a handle for the plugin, the name of the plugin,
56  * and the version string for the plugin.  The plugin returns TRUE if
57  * it's a plugin for that type and FALSE if not.
58  */
59 typedef struct {
60     const char *name;
61     plugin_check_type_callback check_type;
62 } plugin_type;
63
64 static GSList *plugin_types = NULL;
65
66 static void
67 free_plugin_type(gpointer p, gpointer user_data _U_)
68 {
69     g_free(p);
70 }
71
72 void
73 add_plugin_type(const char *name, plugin_check_type_callback check_type)
74 {
75     plugin_type *new_type;
76
77     new_type = (plugin_type *)g_malloc(sizeof (plugin_type));
78     new_type->name = name;
79     new_type->check_type = check_type;
80     plugin_types = g_slist_prepend(plugin_types, new_type);
81 }
82
83 static void
84 call_plugin_callback(gpointer data, gpointer user_data)
85 {
86     plugin_type *type = (plugin_type *)data;
87     plugin *new_plug = (plugin *)user_data;
88
89     if (type->check_type(new_plug->handle)) {
90         /* The plugin supports this type */
91         if (new_plug->types->len > 0)
92             g_string_append(new_plug->types, ", ");
93         g_string_append(new_plug->types, type->name);
94     }
95 }
96
97 static void
98 plugins_scan_dir(const char *dirname, plugin_load_failure_mode mode)
99 {
100     WS_DIR        *dir;             /* scanned directory */
101     WS_DIRENT     *file;            /* current file */
102     const char    *name;
103     gchar         *filename;        /* current file name */
104     GModule       *handle;          /* handle returned by g_module_open */
105     gpointer       symbol;
106     const char    *plug_version, *plug_release;
107     plugin        *new_plug;
108     gchar         *dot;
109
110     if (!g_file_test(dirname, G_FILE_TEST_EXISTS) || !g_file_test(dirname, G_FILE_TEST_IS_DIR)) {
111         return;
112     }
113
114     if ((dir = ws_dir_open(dirname, 0, NULL)) != NULL)
115     {
116         while ((file = ws_dir_read_name(dir)) != NULL)
117         {
118             name = ws_dir_get_name(file);
119
120             /*
121              * GLib 2.x defines G_MODULE_SUFFIX as the extension used on
122              * this platform for loadable modules.
123              */
124             /* skip anything but files with G_MODULE_SUFFIX */
125             dot = strrchr(name, '.');
126             if (dot == NULL || strcmp(dot+1, G_MODULE_SUFFIX) != 0)
127                 continue;
128
129 #if WIN32
130             if (strncmp(name, "nordic_ble.dll", 14) == 0)
131                 /*
132                  * Skip the Nordic BLE Sniffer dll on WIN32 because
133                  * the dissector has been added as internal.
134                  */
135                 continue;
136 #endif
137
138             /*
139              * Check if the same name is already registered.
140              */
141             if (g_hash_table_lookup(plugins_table, name)) {
142                 /* Yes, it is. */
143                 if (mode == REPORT_LOAD_FAILURE) {
144                     report_warning("The plugin '%s' was found "
145                             "in multiple directories", name);
146                 }
147                 continue;
148             }
149
150             filename = g_build_filename(dirname, name, (gchar *)NULL);
151             handle = g_module_open(filename, G_MODULE_BIND_LOCAL);
152             g_free(filename);
153             if (handle == NULL)
154             {
155                 /*
156                  * Only report load failures if we were asked to.
157                  *
158                  * XXX - we really should put different types of plugins
159                  * (libwiretap, libwireshark) in different subdirectories,
160                  * give libwiretap and libwireshark init routines that
161                  * load the plugins, and have them scan the appropriate
162                  * subdirectories so tha we don't even *try* to, for
163                  * example, load libwireshark plugins in programs that
164                  * only use libwiretap.
165                  */
166                 if (mode == REPORT_LOAD_FAILURE) {
167                     /* g_module_error() provides filename. */
168                     report_failure("Couldn't load plugin '%s': %s", name,
169                                    g_module_error());
170                 }
171                 continue;
172             }
173
174             if (!g_module_symbol(handle, "plugin_version", &symbol))
175             {
176                 report_failure("The plugin '%s' has no \"plugin_version\" symbol", name);
177                 g_module_close(handle);
178                 continue;
179             }
180             plug_version = (const char *)symbol;
181
182             if (!g_module_symbol(handle, "plugin_release", &symbol))
183             {
184                 report_failure("The plugin '%s' has no \"plugin_release\" symbol", name);
185                 g_module_close(handle);
186                 continue;
187             }
188             plug_release = (const char *)symbol;
189             if (strcmp(plug_release, VERSION_RELEASE) != 0) {
190                 report_failure("The plugin '%s' was compiled for Wireshark version %s", name, plug_release);
191                 g_module_close(handle);
192                 continue;
193             }
194
195             new_plug = (plugin *)g_malloc(sizeof(plugin));
196             new_plug->handle = handle;
197             new_plug->name = g_strdup(name);
198             new_plug->version = plug_version;
199             new_plug->types = g_string_new(NULL);
200
201             /*
202              * Hand the plugin to each of the plugin type callbacks.
203              */
204             g_slist_foreach(plugin_types, call_plugin_callback, new_plug);
205
206             /*
207              * Does this dissector do anything useful?
208              */
209             if (new_plug->types->len == 0)
210             {
211                 /*
212                  * No.
213                  *
214                  * Only report this failure if we were asked to; it might
215                  * just mean that it's a plugin type that this program
216                  * doesn't support, such as a libwireshark plugin in
217                  * a program that doesn't use libwireshark.
218                  *
219                  * XXX - we really should put different types of plugins
220                  * (libwiretap, libwireshark) in different subdirectories,
221                  * give libwiretap and libwireshark init routines that
222                  * load the plugins, and have them scan the appropriate
223                  * subdirectories so tha we don't even *try* to, for
224                  * example, load libwireshark plugins in programs that
225                  * only use libwiretap.
226                  */
227                 if (mode == REPORT_LOAD_FAILURE) {
228                     report_failure("The plugin '%s' has no registration routines",
229                                    name);
230                 }
231                 free_plugin(new_plug);
232                 continue;
233             }
234
235             /*
236              * OK, add it to the list of plugins.
237              */
238             g_hash_table_insert(plugins_table, new_plug->name, new_plug);
239         }
240         ws_dir_close(dir);
241     }
242 }
243
244 /*
245  * Scan the buildir for plugins.
246  */
247 static void
248 scan_plugins_build_dir(plugin_load_failure_mode mode)
249 {
250     const char *plugin_dir;
251     const char *name;
252     char *plugin_dir_path;
253     WS_DIR *dir;                /* scanned directory */
254     WS_DIRENT *file;            /* current file */
255
256     plugin_dir = get_plugins_dir();
257     if ((dir = ws_dir_open(plugin_dir, 0, NULL)) == NULL)
258         return;
259
260     plugins_scan_dir(plugin_dir, mode);
261     while ((file = ws_dir_read_name(dir)) != NULL)
262     {
263         name = ws_dir_get_name(file);
264         if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
265             continue;        /* skip "." and ".." */
266         /*
267          * Get the full path of a ".libs" subdirectory of that
268          * directory.
269          */
270         plugin_dir_path = g_build_filename(plugin_dir, name, ".libs", (gchar *)NULL);
271         if (test_for_directory(plugin_dir_path) != EISDIR) {
272             /*
273              * Either it doesn't refer to a directory or it
274              * refers to something that doesn't exist.
275              *
276              * Assume that means that the plugins are in
277              * the subdirectory of the plugin directory, not
278              * a ".libs" subdirectory of that subdirectory.
279              */
280             g_free(plugin_dir_path);
281             plugin_dir_path = g_build_filename(plugin_dir, name, (gchar *)NULL);
282         }
283         plugins_scan_dir(plugin_dir_path, mode);
284         g_free(plugin_dir_path);
285     }
286     ws_dir_close(dir);
287 }
288
289 /*
290  * Scan for plugins.
291  */
292 void
293 scan_plugins(plugin_load_failure_mode mode)
294 {
295     if (!g_module_supported())
296         return; /* nothing to do */
297
298     if (plugins_table != NULL)
299         return; /* only scan for plugins once */
300
301     plugins_table = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free_plugin);
302     /*
303      * Scan the global plugin directory.
304      * If we're running from a build directory, scan the "plugins"
305      * subdirectory, as that's where plugins are located in an
306      * out-of-tree build. If we find subdirectories scan those since
307      * they will contain plugins in the case of an in-tree build.
308      */
309     if (running_in_build_directory())
310     {
311         scan_plugins_build_dir(mode);
312     }
313     else
314     {
315         plugins_scan_dir(get_plugins_dir_with_version(), mode);
316     }
317
318     /*
319      * If the program wasn't started with special privileges,
320      * scan the users plugin directory.  (Even if we relinquish
321      * them, plugins aren't safe unless we've *permanently*
322      * relinquished them, and we can't do that in Wireshark as,
323      * if we need privileges to start capturing, we'd need to
324      * reclaim them before each time we start capturing.)
325      */
326     if (!started_with_special_privs())
327     {
328         plugins_scan_dir(get_plugins_pers_dir_with_version(), mode);
329     }
330 }
331
332 static void
333 add_plugin_to_descriptions(gpointer key _U_, gpointer value, gpointer user_data)
334 {
335     g_ptr_array_add((GPtrArray *)user_data, value);
336 }
337
338 static int
339 compare_plugins(gconstpointer _a, gconstpointer _b)
340 {
341     const plugin *a = *(const plugin **)_a;
342     const plugin *b = *(const plugin **)_b;
343
344     return strcmp(a->name, b->name);
345 }
346
347 struct description_callback {
348     plugin_description_callback callback;
349     gpointer callback_data;
350 };
351
352 static void
353 call_description_callback(gpointer data, gpointer user_data)
354 {
355     plugin *plug = (plugin *)data;
356     struct description_callback *desc = (struct description_callback *)user_data;
357
358     desc->callback(plug->name, plug->version, plug->types->str, g_module_name(plug->handle), desc->callback_data);
359 }
360
361 WS_DLL_PUBLIC void
362 plugins_get_descriptions(plugin_description_callback callback, void *user_data)
363 {
364     GPtrArray *descriptions;
365     struct description_callback cb;
366
367     if (!plugins_table)
368         return;
369
370     descriptions = g_ptr_array_sized_new(g_hash_table_size(plugins_table));
371     g_hash_table_foreach(plugins_table, add_plugin_to_descriptions, descriptions);
372     g_ptr_array_sort(descriptions, compare_plugins);
373     cb.callback = callback;
374     cb.callback_data = user_data;
375     g_ptr_array_foreach(descriptions, call_description_callback, &cb);
376     g_ptr_array_free(descriptions, FALSE);
377 }
378
379 static void
380 print_plugin_description(const char *name, const char *version,
381                          const char *description, const char *filename,
382                          void *user_data _U_)
383 {
384     ws_debug_printf("%s\t%s\t%s\t%s\n", name, version, description, filename);
385 }
386
387 void
388 plugins_dump_all(void)
389 {
390     plugins_get_descriptions(print_plugin_description, NULL);
391 }
392
393 int
394 plugins_get_count(void)
395 {
396     if (plugins_table)
397         return g_hash_table_size(plugins_table);
398     return 0;
399 }
400
401 void
402 plugins_cleanup(void)
403 {
404     if (plugins_table)
405         g_hash_table_destroy(plugins_table);
406     g_slist_foreach(plugin_types, free_plugin_type, NULL);
407     g_slist_free(plugin_types);
408 }
409
410 #endif /* HAVE_PLUGINS */
411
412 /*
413  * Editor modelines
414  *
415  * Local Variables:
416  * c-basic-offset: 4
417  * tab-width: 8
418  * indent-tabs-mode: nil
419  * End:
420  *
421  * ex: set shiftwidth=4 tabstop=8 expandtab:
422  * :indentSize=4:tabSize=8:noTabs=true:
423  */