1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2007-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2012 Planets Communications B.V.
6 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation, which is
11 listed in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Plugin load/unloader for all BAREOS daemons
25 *
26 * Kern Sibbald, October 2007
27 */
28
29 #include "include/bareos.h"
30 #include "lib/alist.h"
31 #include "lib/berrno.h"
32
33 #include <dlfcn.h>
34 #include <dirent.h>
35 #define NAMELEN(dirent) (strlen((dirent)->d_name))
36 #ifndef HAVE_READDIR_R
37 int Readdir_r(DIR* dirp, struct dirent* entry, struct dirent** result);
38 #endif
39
40 #ifndef RTLD_NOW
41 # define RTLD_NOW 2
42 #endif
43
44 #if !defined(LT_LAZY_OR_NOW)
45 # if defined(RTLD_LAZY)
46 # define LT_LAZY_OR_NOW RTLD_LAZY
47 # else
48 # if defined(DL_LAZY)
49 # define LT_LAZY_OR_NOW DL_LAZY
50 # endif
51 # endif /* !RTLD_LAZY */
52 #endif
53 #if !defined(LT_LAZY_OR_NOW)
54 # if defined(RTLD_NOW)
55 # define LT_LAZY_OR_NOW RTLD_NOW
56 # else
57 # if defined(DL_NOW)
58 # define LT_LAZY_OR_NOW DL_NOW
59 # endif
60 # endif /* !RTLD_NOW */
61 #endif
62
63 #if !defined(LT_LAZY_OR_NOW)
64 # define LT_LAZY_OR_NOW 0
65 #endif /* !LT_LAZY_OR_NOW */
66
67 #if !defined(LT_GLOBAL)
68 # if defined(RTLD_GLOBAL)
69 # define LT_GLOBAL RTLD_GLOBAL
70 # else
71 # if defined(DL_GLOBAL)
72 # define LT_GLOBAL DL_GLOBAL
73 # endif
74 # endif /* !RTLD_GLOBAL */
75 #endif
76
77 #include "plugins.h"
78
79 static const int debuglevel = 50;
80
81 /*
82 * Create a new plugin "class" entry and enter it in the list of plugins.
83 * Note, this is not the same as an instance of the plugin.
84 */
new_plugin()85 static Plugin* new_plugin()
86 {
87 Plugin* plugin;
88
89 plugin = (Plugin*)malloc(sizeof(Plugin));
90 memset(plugin, 0, sizeof(Plugin));
91 return plugin;
92 }
93
ClosePlugin(Plugin * plugin)94 static void ClosePlugin(Plugin* plugin)
95 {
96 if (plugin->file) {
97 Dmsg1(50, "Got plugin=%s but not accepted.\n", plugin->file);
98 }
99 if (plugin->unloadPlugin) { plugin->unloadPlugin(); }
100 if (plugin->plugin_handle) { dlclose(plugin->plugin_handle); }
101 if (plugin->file) { free(plugin->file); }
102 free(plugin);
103 }
104
105 /*
106 * Load a specific plugin and check if the plugin had the correct
107 * entry points, the license is compatible and initialize the plugin.
108 */
load_a_plugin(void * bareos_plugin_interface_version,void * bareos_core_functions,const char * plugin_pathname,const char * plugin_name,const char * type,alist * plugin_list,bool IsPluginCompatible (Plugin * plugin))109 static bool load_a_plugin(void* bareos_plugin_interface_version,
110 void* bareos_core_functions,
111 const char* plugin_pathname,
112 const char* plugin_name,
113 const char* type,
114 alist* plugin_list,
115 bool IsPluginCompatible(Plugin* plugin))
116 {
117 t_loadPlugin loadPlugin;
118 Plugin* plugin = NULL;
119
120 plugin = new_plugin();
121 plugin->file = strdup(plugin_name);
122 plugin->file_len = strstr(plugin->file, type) - plugin->file;
123
124 plugin->plugin_handle = dlopen(plugin_pathname, LT_LAZY_OR_NOW | LT_GLOBAL);
125
126 if (!plugin->plugin_handle) {
127 const char* error = dlerror();
128
129 Jmsg(NULL, M_ERROR, 0, _("dlopen plugin %s failed: ERR=%s\n"),
130 plugin_pathname, NPRT(error));
131 Dmsg2(debuglevel, "dlopen plugin %s failed: ERR=%s\n", plugin_pathname,
132 NPRT(error));
133
134 ClosePlugin(plugin);
135
136 return false;
137 }
138
139 /*
140 * Get two global entry points
141 */
142 loadPlugin = (t_loadPlugin)dlsym(plugin->plugin_handle, "loadPlugin");
143 if (!loadPlugin) {
144 Jmsg(NULL, M_ERROR, 0,
145 _("Lookup of loadPlugin in plugin %s failed: ERR=%s\n"),
146 plugin_pathname, NPRT(dlerror()));
147 Dmsg2(debuglevel, "Lookup of loadPlugin in plugin %s failed: ERR=%s\n",
148 plugin_pathname, NPRT(dlerror()));
149
150 ClosePlugin(plugin);
151
152 return false;
153 }
154
155 plugin->unloadPlugin
156 = (t_unloadPlugin)dlsym(plugin->plugin_handle, "unloadPlugin");
157 if (!plugin->unloadPlugin) {
158 Jmsg(NULL, M_ERROR, 0,
159 _("Lookup of unloadPlugin in plugin %s failed: ERR=%s\n"),
160 plugin_pathname, NPRT(dlerror()));
161 Dmsg2(debuglevel, "Lookup of unloadPlugin in plugin %s failed: ERR=%s\n",
162 plugin_pathname, NPRT(dlerror()));
163
164 ClosePlugin(plugin);
165
166 return false;
167 }
168
169 /*
170 * Initialize the plugin
171 */
172 if (loadPlugin(bareos_plugin_interface_version, bareos_core_functions,
173 &plugin->plugin_information, &plugin->plugin_functions)
174 != bRC_OK) {
175 ClosePlugin(plugin);
176
177 return false;
178 }
179
180 if (!IsPluginCompatible) {
181 Dmsg0(50, "Plugin compatibility pointer not set.\n");
182 } else if (!IsPluginCompatible(plugin)) {
183 ClosePlugin(plugin);
184
185 return false;
186 }
187
188 plugin_list->append(plugin);
189
190 return true;
191 }
192
193 /*
194 * Load all the plugins in the specified directory.
195 * Or when plugin_names is give it has a list of plugins
196 * to load from the specified directory.
197 */
LoadPlugins(void * bareos_plugin_interface_version,void * bareos_core_functions,alist * plugin_list,const char * plugin_dir,alist * plugin_names,const char * type,bool IsPluginCompatible (Plugin * plugin))198 bool LoadPlugins(void* bareos_plugin_interface_version,
199 void* bareos_core_functions,
200 alist* plugin_list,
201 const char* plugin_dir,
202 alist* plugin_names,
203 const char* type,
204 bool IsPluginCompatible(Plugin* plugin))
205 {
206 struct stat statp;
207 bool found = false;
208 PoolMem fname(PM_FNAME);
209 bool need_slash = false;
210 int len;
211
212 Dmsg0(debuglevel, "LoadPlugins\n");
213
214 len = strlen(plugin_dir);
215 if (len > 0) { need_slash = !IsPathSeparator(plugin_dir[len - 1]); }
216
217 /*
218 * See if we are loading certain plugins only or all plugins of a certain
219 * type.
220 */
221 if (plugin_names && plugin_names->size()) {
222 char* name = nullptr;
223 PoolMem plugin_name(PM_FNAME);
224
225 foreach_alist (name, plugin_names) {
226 /*
227 * Generate the plugin name e.g. <name>-<daemon>.so
228 */
229 Mmsg(plugin_name, "%s%s", name, type);
230
231 /*
232 * Generate the full pathname to the plugin to load.
233 */
234 Mmsg(fname, "%s%s%s", plugin_dir, (need_slash) ? "/" : "",
235 plugin_name.c_str());
236
237 /*
238 * Make sure the plugin exists and is a regular file.
239 */
240 if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
241 continue;
242 }
243
244 /*
245 * Try to load the plugin and resolve the wanted symbols.
246 */
247 if (load_a_plugin(bareos_plugin_interface_version, bareos_core_functions,
248 fname.c_str(), plugin_name.c_str(), type, plugin_list,
249 IsPluginCompatible)) {
250 found = true;
251 }
252 }
253 } else {
254 int name_max, type_len;
255 DIR* dp = NULL;
256 struct dirent* result;
257 #ifdef USE_READDIR_R
258 struct dirent* entry = NULL;
259 #endif
260 name_max = pathconf(".", _PC_NAME_MAX);
261 if (name_max < 1024) { name_max = 1024; }
262
263 if (!(dp = opendir(plugin_dir))) {
264 BErrNo be;
265 Jmsg(NULL, M_ERROR_TERM, 0,
266 _("Failed to open Plugin directory %s: ERR=%s\n"), plugin_dir,
267 be.bstrerror());
268 Dmsg2(debuglevel, "Failed to open Plugin directory %s: ERR=%s\n",
269 plugin_dir, be.bstrerror());
270 goto bail_out;
271 }
272
273 #ifdef USE_READDIR_R
274 entry = (struct dirent*)malloc(sizeof(struct dirent) + name_max + 1000);
275 while (1) {
276 if ((Readdir_r(dp, entry, &result) != 0) || (result == NULL)) {
277 #else
278 while (1) {
279 result = readdir(dp);
280 if (result == NULL) {
281 #endif
282 if (!found) {
283 Jmsg(NULL, M_WARNING, 0, _("Failed to find any plugins in %s\n"),
284 plugin_dir);
285 Dmsg1(debuglevel, "Failed to find any plugins in %s\n", plugin_dir);
286 }
287 break;
288 }
289
290 if (bstrcmp(result->d_name, ".") || bstrcmp(result->d_name, "..")) {
291 continue;
292 }
293
294 len = strlen(result->d_name);
295 type_len = strlen(type);
296 if (len < type_len + 1
297 || !bstrcmp(&result->d_name[len - type_len], type)) {
298 Dmsg3(debuglevel, "Rejected plugin: want=%s name=%s len=%d\n", type,
299 result->d_name, len);
300 continue;
301 }
302 Dmsg2(debuglevel, "Found plugin: name=%s len=%d\n", result->d_name, len);
303
304 PmStrcpy(fname, plugin_dir);
305 if (need_slash) { PmStrcat(fname, "/"); }
306 PmStrcat(fname, result->d_name);
307
308 /*
309 * Make sure the plugin exists and is a regular file.
310 */
311 if (lstat(fname.c_str(), &statp) != 0 || !S_ISREG(statp.st_mode)) {
312 continue;
313 }
314
315 /*
316 * Try to load the plugin and resolve the wanted symbols.
317 */
318 if (load_a_plugin(bareos_plugin_interface_version, bareos_core_functions,
319 fname.c_str(), result->d_name, type, plugin_list,
320 IsPluginCompatible)) {
321 found = true;
322 }
323 }
324
325 #ifdef USE_READDIR_R
326 if (entry) { free(entry); }
327 #endif
328 if (dp) { closedir(dp); }
329 }
330
331 bail_out:
332 return found;
333 }
334
335 /*
336 * Unload all the loaded plugins
337 */
338 void UnloadPlugins(alist* plugin_list)
339 {
340 int i{};
341 Plugin* plugin{};
342
343 if (!plugin_list) { return; }
344 foreach_alist_index (i, plugin, plugin_list) {
345 /*
346 * Shut it down and unload it
347 */
348 plugin->unloadPlugin();
349 dlclose(plugin->plugin_handle);
350 if (plugin->file) { free(plugin->file); }
351 free(plugin);
352 }
353 }
354
355 void UnloadPlugin(alist* plugin_list, Plugin* plugin, int index)
356 {
357 /*
358 * Shut it down and unload it
359 */
360 plugin->unloadPlugin();
361 dlclose(plugin->plugin_handle);
362 if (plugin->file) { free(plugin->file); }
363 plugin_list->remove(index);
364 free(plugin);
365 }
366
367 int ListPlugins(alist* plugin_list, PoolMem& msg)
368 {
369 int i{};
370 int len{};
371 Plugin* plugin{};
372
373 if (plugin_list && plugin_list->size() > 0) {
374 PmStrcpy(msg, "Plugin Info:\n");
375 foreach_alist_index (i, plugin, plugin_list) {
376 PmStrcat(msg, " Plugin : ");
377 len = PmStrcat(msg, plugin->file);
378 if (plugin->plugin_information) {
379 PluginInformation* info
380 = (PluginInformation*)plugin->plugin_information;
381 PmStrcat(msg, "\n");
382 PmStrcat(msg, " Description: ");
383 PmStrcat(msg, NPRT(info->plugin_description));
384 PmStrcat(msg, "\n");
385
386 PmStrcat(msg, " Version : ");
387 PmStrcat(msg, NPRT(info->plugin_version));
388 PmStrcat(msg, ", Date: ");
389 PmStrcat(msg, NPRT(info->plugin_date));
390 PmStrcat(msg, "\n");
391
392 PmStrcat(msg, " Author : ");
393 PmStrcat(msg, NPRT(info->plugin_author));
394 PmStrcat(msg, "\n");
395
396 PmStrcat(msg, " License : ");
397 PmStrcat(msg, NPRT(info->plugin_license));
398 PmStrcat(msg, "\n");
399
400 if (info->plugin_usage) {
401 PmStrcat(msg, " Usage : ");
402 PmStrcat(msg, info->plugin_usage);
403 PmStrcat(msg, "\n");
404 }
405
406 PmStrcat(msg, "\n");
407 }
408 }
409 len = PmStrcat(msg, "\n");
410 }
411
412 return len;
413 }
414
415 /*
416 * Dump plugin information
417 * Each daemon can register a hook that will be called
418 * after a fatal signal.
419 */
420 #define DBG_MAX_HOOK 10
421 static dbg_plugin_hook_t* dbg_plugin_hooks[DBG_MAX_HOOK];
422 static dbg_print_plugin_hook_t* dbg_print_plugin_hook = NULL;
423 static int dbg_plugin_hook_count = 0;
424
425 void DbgPluginAddHook(dbg_plugin_hook_t* fct)
426 {
427 ASSERT(dbg_plugin_hook_count < DBG_MAX_HOOK);
428 dbg_plugin_hooks[dbg_plugin_hook_count++] = fct;
429 }
430
431 void DbgPrintPluginAddHook(dbg_print_plugin_hook_t* fct)
432 {
433 dbg_print_plugin_hook = fct;
434 }
435
436 void DumpPlugins(alist* plugin_list, FILE* fp)
437 {
438 int i{};
439 Plugin* plugin{};
440 fprintf(fp, "Attempt to dump plugins. Hook count=%d\n",
441 dbg_plugin_hook_count);
442
443 if (!plugin_list) { return; }
444 foreach_alist_index (i, plugin, plugin_list) {
445 for (int i = 0; i < dbg_plugin_hook_count; i++) {
446 // dbg_plugin_hook_t *fct = dbg_plugin_hooks[i];
447 fprintf(fp, "Plugin %p name=\"%s\"\n", plugin, plugin->file);
448 // fct(plugin, fp);
449 }
450 }
451 }
452
453 /*
454 * Bounce from library to daemon and back to get the proper plugin_list.
455 * As the function is called from the signal context we don't have the
456 * plugin_list as argument and we don't want to expose it as global variable.
457 * If the daemon didn't register a dump plugin function this is a NOP.
458 */
459 void DbgPrintPlugin(FILE* fp)
460 {
461 if (dbg_print_plugin_hook) { dbg_print_plugin_hook(fp); }
462 }
463