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-or-later
9  */
10 
11 #include "config.h"
12 #define WS_LOG_DOMAIN LOG_DOMAIN_WSUTIL
13 
14 #include <time.h>
15 
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <errno.h>
20 
21 #include <glib.h>
22 #include <gmodule.h>
23 
24 #include <wsutil/filesystem.h>
25 #include <wsutil/privileges.h>
26 #include <wsutil/file_util.h>
27 #include <wsutil/report_message.h>
28 #include <wsutil/wslog.h>
29 
30 #include <wsutil/plugins.h>
31 #include <wsutil/ws_assert.h>
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_name;    /* user-facing name (what it does). Should these be capitalized? */
38 } plugin;
39 
40 #define TYPE_DIR_EPAN       "epan"
41 #define TYPE_DIR_WIRETAP    "wiretap"
42 #define TYPE_DIR_CODECS     "codecs"
43 
44 #define TYPE_NAME_DISSECTOR "dissector"
45 #define TYPE_NAME_FILE_TYPE "file type"
46 #define TYPE_NAME_CODEC     "codec"
47 
48 
49 static GSList *plugins_module_list = NULL;
50 
51 
52 static inline const char *
type_to_dir(plugin_type_e type)53 type_to_dir(plugin_type_e type)
54 {
55     switch (type) {
56     case WS_PLUGIN_EPAN:
57         return TYPE_DIR_EPAN;
58     case WS_PLUGIN_WIRETAP:
59         return TYPE_DIR_WIRETAP;
60     case WS_PLUGIN_CODEC:
61         return TYPE_DIR_CODECS;
62     default:
63         ws_error("Unknown plugin type: %u. Aborting.", (unsigned) type);
64         break;
65     }
66     ws_assert_not_reached();
67 }
68 
69 static inline const char *
type_to_name(plugin_type_e type)70 type_to_name(plugin_type_e type)
71 {
72     switch (type) {
73     case WS_PLUGIN_EPAN:
74         return TYPE_NAME_DISSECTOR;
75     case WS_PLUGIN_WIRETAP:
76         return TYPE_NAME_FILE_TYPE;
77     case WS_PLUGIN_CODEC:
78         return TYPE_NAME_CODEC;
79     default:
80         ws_error("Unknown plugin type: %u. Aborting.", (unsigned) type);
81         break;
82     }
83     ws_assert_not_reached();
84 }
85 
86 static void
free_plugin(gpointer data)87 free_plugin(gpointer data)
88 {
89     plugin *p = (plugin *)data;
90     g_module_close(p->handle);
91     g_free(p->name);
92     g_free(p);
93 }
94 
95 static gint
compare_plugins(gconstpointer a,gconstpointer b)96 compare_plugins(gconstpointer a, gconstpointer b)
97 {
98     return g_strcmp0((*(plugin *const *)a)->name, (*(plugin *const *)b)->name);
99 }
100 
101 static gboolean
pass_plugin_version_compatibility(GModule * handle,const char * name)102 pass_plugin_version_compatibility(GModule *handle, const char *name)
103 {
104     gpointer symb;
105     int major, minor;
106 
107     if(!g_module_symbol(handle, "plugin_want_major", &symb)) {
108         report_failure("The plugin '%s' has no \"plugin_want_major\" symbol", name);
109         return FALSE;
110     }
111     major = *(int *)symb;
112 
113     if(!g_module_symbol(handle, "plugin_want_minor", &symb)) {
114         report_failure("The plugin '%s' has no \"plugin_want_minor\" symbol", name);
115         return FALSE;
116     }
117     minor = *(int *)symb;
118 
119     if (major != VERSION_MAJOR || minor != VERSION_MINOR) {
120         report_failure("The plugin '%s' was compiled for Wireshark version %d.%d",
121                             name, major, minor);
122         return FALSE;
123     }
124 
125     return TRUE;
126 }
127 
128 static void
scan_plugins_dir(GHashTable * plugins_module,const char * dirpath,plugin_type_e type,gboolean append_type)129 scan_plugins_dir(GHashTable *plugins_module, const char *dirpath, plugin_type_e type, gboolean append_type)
130 {
131     GDir          *dir;
132     const char    *name;            /* current file name */
133     gchar         *plugin_folder;
134     gchar         *plugin_file;     /* current file full path */
135     GModule       *handle;          /* handle returned by g_module_open */
136     gpointer       symbol;
137     const char    *plug_version;
138     plugin        *new_plug;
139 
140     if (append_type)
141         plugin_folder = g_build_filename(dirpath, type_to_dir(type), (gchar *)NULL);
142     else
143         plugin_folder = g_strdup(dirpath);
144 
145     dir = g_dir_open(plugin_folder, 0, NULL);
146     if (dir == NULL) {
147         g_free(plugin_folder);
148         return;
149     }
150 
151     while ((name = g_dir_read_name(dir)) != NULL) {
152         /* Skip anything but files with G_MODULE_SUFFIX. */
153         if (!g_str_has_suffix(name, "." G_MODULE_SUFFIX))
154             continue;
155 
156         /*
157          * Check if the same name is already registered.
158          */
159         if (g_hash_table_lookup(plugins_module, name)) {
160             /* Yes, it is. */
161             report_warning("The plugin '%s' was found "
162                                 "in multiple directories", name);
163             continue;
164         }
165 
166         plugin_file = g_build_filename(plugin_folder, name, (gchar *)NULL);
167         handle = g_module_open(plugin_file, G_MODULE_BIND_LOCAL);
168         g_free(plugin_file);
169         if (handle == NULL) {
170             /* g_module_error() provides file path. */
171             report_failure("Couldn't load plugin '%s': %s", name,
172                             g_module_error());
173             continue;
174         }
175 
176         if (!g_module_symbol(handle, "plugin_version", &symbol))
177         {
178             report_failure("The plugin '%s' has no \"plugin_version\" symbol", name);
179             g_module_close(handle);
180             continue;
181         }
182         plug_version = (const char *)symbol;
183 
184         if (!pass_plugin_version_compatibility(handle, name)) {
185             g_module_close(handle);
186             continue;
187         }
188 
189         /* Search for the entry point for the plugin registration function */
190         if (!g_module_symbol(handle, "plugin_register", &symbol)) {
191             report_failure("The plugin '%s' has no \"plugin_register\" symbol", name);
192             g_module_close(handle);
193             continue;
194         }
195 
196 DIAG_OFF_PEDANTIC
197         /* Found it, call the plugin registration function. */
198         ((plugin_register_func)symbol)();
199 DIAG_ON_PEDANTIC
200 
201         new_plug = g_new(plugin, 1);
202         new_plug->handle = handle;
203         new_plug->name = g_strdup(name);
204         new_plug->version = plug_version;
205         new_plug->type_name = type_to_name(type);
206 
207         /* Add it to the list of plugins. */
208         g_hash_table_replace(plugins_module, new_plug->name, new_plug);
209     }
210     ws_dir_close(dir);
211     g_free(plugin_folder);
212 }
213 
214 /*
215  * Scan for plugins.
216  */
217 plugins_t *
plugins_init(plugin_type_e type)218 plugins_init(plugin_type_e type)
219 {
220     if (!g_module_supported())
221         return NULL; /* nothing to do */
222 
223     GHashTable *plugins_module = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, free_plugin);
224 
225     /*
226      * Scan the global plugin directory.
227      */
228     scan_plugins_dir(plugins_module, get_plugins_dir_with_version(), type, TRUE);
229 
230     /*
231      * If the program wasn't started with special privileges,
232      * scan the users plugin directory.  (Even if we relinquish
233      * them, plugins aren't safe unless we've *permanently*
234      * relinquished them, and we can't do that in Wireshark as,
235      * if we need privileges to start capturing, we'd need to
236      * reclaim them before each time we start capturing.)
237      */
238     if (!started_with_special_privs()) {
239         scan_plugins_dir(plugins_module, get_plugins_pers_dir_with_version(), type, TRUE);
240     }
241 
242     plugins_module_list = g_slist_prepend(plugins_module_list, plugins_module);
243 
244     return plugins_module;
245 }
246 
247 WS_DLL_PUBLIC void
plugins_get_descriptions(plugin_description_callback callback,void * callback_data)248 plugins_get_descriptions(plugin_description_callback callback, void *callback_data)
249 {
250     GPtrArray *plugins_array = g_ptr_array_new();
251     GHashTableIter iter;
252     gpointer value;
253 
254     for (GSList *l = plugins_module_list; l != NULL; l = l->next) {
255         g_hash_table_iter_init (&iter, (GHashTable *)l->data);
256         while (g_hash_table_iter_next (&iter, NULL, &value)) {
257             g_ptr_array_add(plugins_array, value);
258         }
259     }
260 
261     g_ptr_array_sort(plugins_array, compare_plugins);
262 
263     for (guint i = 0; i < plugins_array->len; i++) {
264         plugin *plug = (plugin *)plugins_array->pdata[i];
265         callback(plug->name, plug->version, plug->type_name, g_module_name(plug->handle), callback_data);
266     }
267 
268     g_ptr_array_free(plugins_array, TRUE);
269 }
270 
271 static void
print_plugin_description(const char * name,const char * version,const char * description,const char * filename,void * user_data _U_)272 print_plugin_description(const char *name, const char *version,
273                          const char *description, const char *filename,
274                          void *user_data _U_)
275 {
276     printf("%-16s\t%s\t%s\t%s\n", name, version, description, filename);
277 }
278 
279 void
plugins_dump_all(void)280 plugins_dump_all(void)
281 {
282     plugins_get_descriptions(print_plugin_description, NULL);
283 }
284 
285 int
plugins_get_count(void)286 plugins_get_count(void)
287 {
288     guint count = 0;
289 
290     for (GSList *l = plugins_module_list; l != NULL; l = l->next) {
291         count += g_hash_table_size((GHashTable *)l->data);
292     }
293     return count;
294 }
295 
296 void
plugins_cleanup(plugins_t * plugins)297 plugins_cleanup(plugins_t *plugins)
298 {
299     if (!plugins)
300         return;
301 
302     plugins_module_list = g_slist_remove(plugins_module_list, plugins);
303     g_hash_table_destroy((GHashTable *)plugins);
304 }
305 
306 /*
307  * Editor modelines
308  *
309  * Local Variables:
310  * c-basic-offset: 4
311  * tab-width: 8
312  * indent-tabs-mode: nil
313  * End:
314  *
315  * ex: set shiftwidth=4 tabstop=8 expandtab:
316  * :indentSize=4:tabSize=8:noTabs=true:
317  */
318