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