Use an enum for plugin types.
[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     const gchar    *type_dir;     /* filesystem name (where it resides). */
38     const gchar    *type_name;    /* user-facing name (what it does). Should these be capitalized? */
39 } plugin;
40
41 #define TYPE_DIR_EPAN       "epan"
42 #define TYPE_DIR_WIRETAP    "wiretap"
43 #define TYPE_DIR_CODECS     "codecs"
44
45 #define TYPE_NAME_DISSECTOR "dissector"
46 #define TYPE_NAME_FILE_TYPE "file type"
47 #define TYPE_NAME_CODEC     "codec"
48
49 /* array of plugins */
50 static GPtrArray *plugins_array = NULL;
51 /* map of names to plugin */
52 static GHashTable *plugins_table = NULL;
53
54 static void
55 free_plugin(gpointer data)
56 {
57     plugin *p = (plugin *)data;
58     g_module_close(p->handle);
59     g_free(p->name);
60     g_free(p);
61 }
62
63 static gint
64 compare_plugins(gconstpointer a, gconstpointer b)
65 {
66     return g_strcmp0((*(const plugin **)a)->name, (*(const plugin **)b)->name);
67 }
68
69 static void
70 plugins_scan_dir(GPtrArray **plugins_ptr, const char *dirpath, plugin_type_e type, gboolean build_dir)
71 {
72     GDir          *dir;
73     const char    *name;            /* current file name */
74     gchar         *path;            /* current file full path */
75     GModule       *handle;          /* handle returned by g_module_open */
76     gpointer       symbol;
77     const char    *plug_version, *plug_release;
78     plugin        *new_plug;
79     gchar         *dot;
80
81     dir = g_dir_open(dirpath, 0, NULL);
82     if (dir == NULL)
83         return;
84
85     while ((name = g_dir_read_name(dir)) != NULL) {
86         /* Skip anything but files with G_MODULE_SUFFIX. */
87         dot = strrchr(name, '.');
88         if (dot == NULL || strcmp(dot+1, G_MODULE_SUFFIX) != 0)
89             continue;
90
91 #if WIN32
92         if (strncmp(name, "nordic_ble.dll", 14) == 0) {
93             /*
94              * Skip the Nordic BLE Sniffer dll on WIN32 because
95              * the dissector has been added as internal.
96              */
97             continue;
98         }
99 #endif
100
101         /*
102          * Check if the same name is already registered.
103          */
104         if (g_hash_table_lookup(plugins_table, name)) {
105             /* Yes, it is. */
106             if (!build_dir) {
107                 report_warning("The plugin '%s' was found "
108                                 "in multiple directories", name);
109             }
110             continue;
111         }
112
113         path = g_build_filename(dirpath, name, (gchar *)NULL);
114         handle = g_module_open(path, G_MODULE_BIND_LOCAL);
115         g_free(path);
116         if (handle == NULL) {
117             /* g_module_error() provides file path. */
118             report_failure("Couldn't load plugin '%s': %s", name,
119                             g_module_error());
120             continue;
121         }
122
123         if (!g_module_symbol(handle, "plugin_version", &symbol))
124         {
125             report_failure("The plugin '%s' has no \"plugin_version\" symbol", name);
126             g_module_close(handle);
127             continue;
128         }
129         plug_version = (const char *)symbol;
130
131         if (!g_module_symbol(handle, "plugin_release", &symbol))
132         {
133             report_failure("The plugin '%s' has no \"plugin_release\" symbol", name);
134             g_module_close(handle);
135             continue;
136         }
137         plug_release = (const char *)symbol;
138         if (strcmp(plug_release, VERSION_RELEASE) != 0) {
139             report_failure("The plugin '%s' was compiled for Wireshark version %s", name, plug_release);
140             g_module_close(handle);
141             continue;
142         }
143
144         /* Search for the entry point for the plugin type */
145         if (!g_module_symbol(handle, "plugin_register", &symbol)) {
146             report_failure("The plugin '%s' has no \"plugin_register\" symbol", name);
147             g_module_close(handle);
148             continue;
149         }
150
151 DIAG_OFF(pedantic)
152         /* Found it, call the plugin registration function. */
153         ((plugin_register_func)symbol)();
154 DIAG_ON(pedantic)
155
156         new_plug = (plugin *)g_malloc(sizeof(plugin));
157         new_plug->handle = handle;
158         new_plug->name = g_strdup(name);
159         new_plug->version = plug_version;
160         new_plug->type_dir = "[build]";
161         switch (type) {
162         case WS_PLUGIN_EPAN:
163             if (!build_dir) {
164                 new_plug->type_dir = TYPE_DIR_EPAN;
165             }
166             // XXX This isn't true for stats_tree and TRANSUM.
167             new_plug->type_name = TYPE_NAME_DISSECTOR;
168             break;
169         case WS_PLUGIN_WIRETAP:
170             if (!build_dir) {
171                 new_plug->type_dir = TYPE_DIR_WIRETAP;
172             }
173             new_plug->type_name = TYPE_NAME_FILE_TYPE;
174             break;
175         case WS_PLUGIN_CODEC:
176             if (!build_dir) {
177                 new_plug->type_dir = TYPE_DIR_CODECS;
178             }
179             new_plug->type_name = TYPE_NAME_CODEC;
180             break;
181         default:
182             g_error("Unknown plugin type: %u. Aborting.", (unsigned) type);
183             break;
184         }
185
186         /* Add it to the list of plugins. */
187         if (*plugins_ptr == NULL)
188             *plugins_ptr = g_ptr_array_new_with_free_func(free_plugin);
189         g_ptr_array_add(*plugins_ptr, new_plug);
190         g_ptr_array_add(plugins_array, new_plug);
191         g_hash_table_insert(plugins_table, new_plug->name, new_plug);
192     }
193     ws_dir_close(dir);
194 }
195
196 /*
197  * Scan the buildir for plugins.
198  */
199 static void
200 scan_plugins_build_dir(GPtrArray **plugins_ptr, plugin_type_e type)
201 {
202     const char *plugin_dir;
203     const char *name;
204     char *plugin_dir_path;
205     WS_DIR *dir;                /* scanned directory */
206     WS_DIRENT *file;            /* current file */
207
208     plugin_dir = get_plugins_dir();
209     if ((dir = ws_dir_open(plugin_dir, 0, NULL)) == NULL)
210         return;
211
212     plugins_scan_dir(plugins_ptr, plugin_dir, type, TRUE);
213     while ((file = ws_dir_read_name(dir)) != NULL)
214     {
215         name = ws_dir_get_name(file);
216         if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
217             continue;        /* skip "." and ".." */
218         /*
219          * Get the full path of a ".libs" subdirectory of that
220          * directory.
221          */
222         plugin_dir_path = g_build_filename(plugin_dir, name, ".libs", (gchar *)NULL);
223         if (test_for_directory(plugin_dir_path) != EISDIR) {
224             /*
225              * Either it doesn't refer to a directory or it
226              * refers to something that doesn't exist.
227              *
228              * Assume that means that the plugins are in
229              * the subdirectory of the plugin directory, not
230              * a ".libs" subdirectory of that subdirectory.
231              */
232             g_free(plugin_dir_path);
233             plugin_dir_path = g_build_filename(plugin_dir, name, (gchar *)NULL);
234         }
235         plugins_scan_dir(plugins_ptr, plugin_dir_path, type, TRUE);
236         g_free(plugin_dir_path);
237     }
238     ws_dir_close(dir);
239 }
240
241 /*
242  * Scan for plugins.
243  */
244 plugins_t *
245 plugins_init(plugin_type_e type)
246 {
247     if (!g_module_supported())
248         return NULL; /* nothing to do */
249
250     const char *type_dir;
251
252     switch (type) {
253     case WS_PLUGIN_EPAN:
254             type_dir = TYPE_DIR_EPAN;
255         break;
256     case WS_PLUGIN_WIRETAP:
257         type_dir = TYPE_DIR_WIRETAP;
258         break;
259     case WS_PLUGIN_CODEC:
260         type_dir = TYPE_DIR_CODECS;
261         break;
262     default:
263         g_error("Unknown plugin type: %u. Aborting.", (unsigned) type);
264         break;
265     }
266
267     gchar *dirpath;
268     GPtrArray *plugins = NULL;
269
270     if (plugins_table == NULL)
271         plugins_table = g_hash_table_new(g_str_hash, g_str_equal);
272     if (plugins_array == NULL)
273         plugins_array = g_ptr_array_new();
274
275     /*
276      * Scan the global plugin directory.
277      * If we're running from a build directory, scan the "plugins"
278      * subdirectory, as that's where plugins are located in an
279      * out-of-tree build. If we find subdirectories scan those since
280      * they will contain plugins in the case of an in-tree build.
281      */
282     if (running_in_build_directory())
283     {
284         scan_plugins_build_dir(&plugins, type);
285     }
286     else
287     {
288         dirpath = g_build_filename(get_plugins_dir_with_version(), type_dir, (gchar *)NULL);
289         plugins_scan_dir(&plugins, dirpath, type, FALSE);
290         g_free(dirpath);
291     }
292
293     /*
294      * If the program wasn't started with special privileges,
295      * scan the users plugin directory.  (Even if we relinquish
296      * them, plugins aren't safe unless we've *permanently*
297      * relinquished them, and we can't do that in Wireshark as,
298      * if we need privileges to start capturing, we'd need to
299      * reclaim them before each time we start capturing.)
300      */
301     if (!started_with_special_privs())
302     {
303         dirpath = g_build_filename(get_plugins_pers_dir_with_version(), type_dir, (gchar *)NULL);
304         plugins_scan_dir(&plugins, dirpath, type, FALSE);
305         g_free(dirpath);
306     }
307
308     g_ptr_array_sort(plugins_array, compare_plugins);
309
310     return plugins;
311 }
312
313 WS_DLL_PUBLIC void
314 plugins_get_descriptions(plugin_description_callback callback, void *callback_data)
315 {
316     if (!plugins_array)
317         return;
318
319     for (guint i = 0; i < plugins_array->len; i++) {
320         plugin *plug = (plugin *)plugins_array->pdata[i];
321         callback(plug->name, plug->version, plug->type_name, g_module_name(plug->handle), callback_data);
322     }
323 }
324
325 static void
326 print_plugin_description(const char *name, const char *version,
327                          const char *description, const char *filename,
328                          void *user_data _U_)
329 {
330     ws_debug_printf("%s\t%s\t%s\t%s\n", name, version, description, filename);
331 }
332
333 void
334 plugins_dump_all(void)
335 {
336     plugins_get_descriptions(print_plugin_description, NULL);
337 }
338
339 int
340 plugins_get_count(void)
341 {
342     if (plugins_table)
343         return g_hash_table_size(plugins_table);
344     return 0;
345 }
346
347 void
348 plugins_cleanup(plugins_t *plugins)
349 {
350     if (plugins)
351         g_ptr_array_free((GPtrArray *)plugins, TRUE);
352
353     /*
354      * This module uses global bookkeeping data structures and per-library
355      * objects sharing data. To avoid having to walk the plugins GPtrArray
356      * and delete each plugin from the global data structures we purge them
357      * once the first plugin cleanup function is called. This means that after
358      * calling ONE OF POSSIBLY MANY plugin cleanup function NO OTHER plugin
359      * APIs can be used except plugins_cleanup. If it ever becomes an issue
360      * it will be easy to change, for a small performance penalty.
361      */
362     if (plugins_table) {
363         g_hash_table_destroy(plugins_table);
364         plugins_table = NULL;
365     }
366     if (plugins_array) {
367         g_ptr_array_free(plugins_array, FALSE);
368         plugins_array = NULL;
369     }
370 }
371
372 #endif /* HAVE_PLUGINS */
373
374 /*
375  * Editor modelines
376  *
377  * Local Variables:
378  * c-basic-offset: 4
379  * tab-width: 8
380  * indent-tabs-mode: nil
381  * End:
382  *
383  * ex: set shiftwidth=4 tabstop=8 expandtab:
384  * :indentSize=4:tabSize=8:noTabs=true:
385  */