1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2011-2014 Planets Communications B.V.
5    Copyright (C) 2013-2021 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation, which is
10    listed in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /**
23  * @file
24  * Python plugin for the Bareos Storage Daemon
25  */
26 #define PY_SSIZE_T_CLEAN
27 #define BUILD_PLUGIN
28 
29 #if defined(HAVE_WIN32)
30 #  include "include/bareos.h"
31 #  include <Python.h>
32 #else
33 #  include <Python.h>
34 #  include "include/bareos.h"
35 #endif
36 #include "include/version_hex.h"
37 
38 #define PLUGIN_DAEMON "sd"
39 
40 #if PY_VERSION_HEX < VERSION_HEX(3, 0, 0)
41 #  define PLUGIN_NAME "python"
42 #  define PLUGIN_DIR PY2MODDIR
43 #else
44 #  define PLUGIN_NAME "python3"
45 #  define PLUGIN_DIR PY3MODDIR
46 #endif
47 
48 #define LOGPREFIX PLUGIN_NAME "-" PLUGIN_DAEMON ": "
49 
50 #include "stored/stored.h"
51 #include "plugins/include/python3compat.h"
52 
53 #include "python-sd.h"
54 #include "module/bareossd.h"
55 #include "lib/edit.h"
56 
57 namespace storagedaemon {
58 
59 static const int debuglevel = 150;
60 
61 #define PLUGIN_LICENSE "Bareos AGPLv3"
62 #define PLUGIN_AUTHOR "Bareos GmbH & Co.KG"
63 #define PLUGIN_DATE "May 2020"
64 #define PLUGIN_VERSION "4"
65 #define PLUGIN_DESCRIPTION "Python Storage Daemon Plugin"
66 #define PLUGIN_USAGE                                                     \
67   PLUGIN_NAME                                                            \
68   ":instance=<instance_id>:module_path=<path-to-python-modules>:module_" \
69   "name=<python-module-to-load>"
70 
71 
72 /* Forward referenced functions */
73 static bRC newPlugin(PluginContext* plugin_ctx);
74 static bRC freePlugin(PluginContext* plugin_ctx);
75 static bRC getPluginValue(PluginContext* plugin_ctx,
76                           pVariable var,
77                           void* value);
78 static bRC setPluginValue(PluginContext* plugin_ctx,
79                           pVariable var,
80                           void* value);
81 static bRC handlePluginEvent(PluginContext* plugin_ctx,
82                              bSdEvent* event,
83                              void* value);
84 static bRC parse_plugin_definition(PluginContext* plugin_ctx,
85                                    void* value,
86                                    PoolMem& plugin_options);
87 
88 static void PyErrorHandler(PluginContext* plugin_ctx, int msgtype);
89 static bRC PyLoadModule(PluginContext* plugin_ctx, void* value);
90 
91 /* Pointers to Bareos functions */
92 static CoreFunctions* bareos_core_functions = NULL;
93 static PluginApiDefinition* bareos_plugin_interface_version = NULL;
94 
95 static PluginInformation pluginInfo
96     = {sizeof(pluginInfo), SD_PLUGIN_INTERFACE_VERSION,
97        SD_PLUGIN_MAGIC,    PLUGIN_LICENSE,
98        PLUGIN_AUTHOR,      PLUGIN_DATE,
99        PLUGIN_VERSION,     PLUGIN_DESCRIPTION,
100        PLUGIN_USAGE};
101 
102 static PluginFunctions pluginFuncs
103     = {sizeof(pluginFuncs), SD_PLUGIN_INTERFACE_VERSION,
104 
105        /* Entry points into plugin */
106        newPlugin,  /* new plugin instance */
107        freePlugin, /* free plugin instance */
108        getPluginValue, setPluginValue, handlePluginEvent};
109 
110 #include "plugin_private_context.h"
111 
112 
113 /**
114  * We don't actually use this but we need it to tear down the
115  * final python interpreter on unload of the plugin. Each instance of
116  * the plugin get its own interpreter.
117  */
118 static PyThreadState* mainThreadState{nullptr};
119 
120 /* functions common to all plugins */
121 #include "plugins/include/python_plugins_common.inc"
122 
123 /* Common functions used in all python plugins.  */
getPluginValue(PluginContext * bareos_plugin_ctx,pVariable var,void * value)124 static bRC getPluginValue(PluginContext* bareos_plugin_ctx,
125                           pVariable var,
126                           void* value)
127 {
128   struct plugin_private_context* plugin_priv_ctx
129       = (struct plugin_private_context*)
130             bareos_plugin_ctx->plugin_private_context;
131   bRC retval = bRC_Error;
132 
133   if (!plugin_priv_ctx) { goto bail_out; }
134 
135   PyEval_AcquireThread(plugin_priv_ctx->interpreter);
136   retval = Bareossd_PyGetPluginValue(bareos_plugin_ctx, var, value);
137   PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
138 
139 bail_out:
140   return retval;
141 }
142 
setPluginValue(PluginContext * bareos_plugin_ctx,pVariable var,void * value)143 static bRC setPluginValue(PluginContext* bareos_plugin_ctx,
144                           pVariable var,
145                           void* value)
146 {
147   struct plugin_private_context* plugin_priv_ctx
148       = (struct plugin_private_context*)
149             bareos_plugin_ctx->plugin_private_context;
150   bRC retval = bRC_Error;
151 
152   if (!plugin_priv_ctx) { return bRC_Error; }
153 
154   PyEval_AcquireThread(plugin_priv_ctx->interpreter);
155   retval = Bareossd_PySetPluginValue(bareos_plugin_ctx, var, value);
156   PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
157 
158   return retval;
159 }
160 
161 
162 #ifdef __cplusplus
163 extern "C" {
164 #endif
165 
PyErrorHandler()166 static void PyErrorHandler()
167 {
168   PyObject *type, *value, *traceback;
169   PyObject* tracebackModule;
170   char* error_string;
171 
172   PyErr_Fetch(&type, &value, &traceback);
173   PyErr_NormalizeException(&type, &value, &traceback);
174 
175   tracebackModule = PyImport_ImportModule("traceback");
176   if (tracebackModule != NULL) {
177     PyObject *tbList, *emptyString, *strRetval;
178 
179     tbList = PyObject_CallMethod(tracebackModule, (char*)"format_exception",
180                                  (char*)"OOO", type,
181                                  value == NULL ? Py_None : value,
182                                  traceback == NULL ? Py_None : traceback);
183 
184     emptyString = PyUnicode_FromString("");
185     strRetval
186         = PyObject_CallMethod(emptyString, (char*)"join", (char*)"O", tbList);
187 
188     error_string = strdup(PyUnicode_AsUTF8(strRetval));
189 
190     Py_DECREF(tbList);
191     Py_DECREF(emptyString);
192     Py_DECREF(strRetval);
193     Py_DECREF(tracebackModule);
194   } else {
195     error_string = strdup("Unable to import traceback module.");
196   }
197   Py_DECREF(type);
198   Py_XDECREF(value);
199   Py_XDECREF(traceback);
200 
201   free(error_string);
202   exit(1);
203 }
204 
205 
206 /**
207  * loadPlugin() and unloadPlugin() are entry points that are
208  *  exported, so Bareos can directly call these two entry points
209  *  they are common to all Bareos plugins.
210  *
211  * External entry point called by Bareos to "load" the plugin
212  */
loadPlugin(PluginApiDefinition * lbareos_plugin_interface_version,CoreFunctions * lbareos_core_functions,PluginInformation ** plugin_information,PluginFunctions ** plugin_functions)213 bRC loadPlugin(PluginApiDefinition* lbareos_plugin_interface_version,
214                CoreFunctions* lbareos_core_functions,
215                PluginInformation** plugin_information,
216                PluginFunctions** plugin_functions)
217 {
218   if (Py_IsInitialized()) { return bRC_Error; }
219 
220   Py_InitializeEx(0);
221   // add bareos plugin path to python module search path
222   PyObject* sysPath = PySys_GetObject((char*)"path");
223   PyObject* pluginPath = PyUnicode_FromString(PLUGIN_DIR);
224   PyList_Append(sysPath, pluginPath);
225   Py_DECREF(pluginPath);
226 
227   /* import the bareossd module */
228   PyObject* bareossdModule = PyImport_ImportModule("bareossd");
229   if (!bareossdModule) {
230     printf("loading of bareossd extension module failed\n");
231     if (PyErr_Occurred()) { PyErrorHandler(); }
232   }
233 
234   /* import the CAPI from the bareossd python module
235    * afterwards, Bareossd_* macros are initialized to
236    * point to the corresponding functions in the bareossd python
237    * module */
238   import_bareossd();
239 
240   /* set bareos_core_functions inside of barossd module */
241   Bareossd_set_bareos_core_functions(lbareos_core_functions);
242 
243   bareos_core_functions
244       = lbareos_core_functions; /* Set Bareos funct pointers */
245   bareos_plugin_interface_version = lbareos_plugin_interface_version;
246 
247   *plugin_information = &pluginInfo; /* Return pointer to our info */
248   *plugin_functions = &pluginFuncs;  /* Return pointer to our functions */
249 
250 #if PY_VERSION_HEX < VERSION_HEX(3, 7, 0)
251   PyEval_InitThreads();
252 #endif
253 
254   mainThreadState = PyEval_SaveThread();
255   return bRC_OK;
256 }
257 
258 /**
259  * Plugin called here when it is unloaded, normally when Bareos is going to
260  * exit.
261  */
unloadPlugin()262 bRC unloadPlugin()
263 {
264   /* Terminate Python if it was initialized correctly */
265   if (mainThreadState) {
266     PyEval_RestoreThread(mainThreadState);
267     Py_Finalize();
268     mainThreadState = nullptr;
269   }
270   return bRC_OK;
271 }
272 
273 #ifdef __cplusplus
274 }
275 #endif
276 
277 /* Create a new instance of the plugin i.e. allocate our private storage */
newPlugin(PluginContext * plugin_ctx)278 static bRC newPlugin(PluginContext* plugin_ctx)
279 {
280   struct plugin_private_context* plugin_priv_ctx
281       = (struct plugin_private_context*)malloc(
282           sizeof(struct plugin_private_context));
283   if (!plugin_priv_ctx) { return bRC_Error; }
284   memset(plugin_priv_ctx, 0, sizeof(struct plugin_private_context));
285   plugin_ctx->plugin_private_context
286       = (void*)plugin_priv_ctx; /* set our context pointer */
287 
288   /* set bareos_plugin_context inside of barossd module */
289   Bareossd_set_plugin_context(plugin_ctx);
290   /* For each plugin instance we instantiate a new Python interpreter. */
291   PyEval_AcquireThread(mainThreadState);
292   plugin_priv_ctx->interpreter = Py_NewInterpreter();
293   PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
294 
295   /*
296    * Always register some events the python plugin itself can register
297    * any other events it is interested in.
298    */
299   bareos_core_functions->registerBareosEvents(plugin_ctx, 1,
300                                               bSdEventNewPluginOptions);
301 
302   return bRC_OK;
303 }
304 
305 /* Free a plugin instance, i.e. release our private storage */
freePlugin(PluginContext * plugin_ctx)306 static bRC freePlugin(PluginContext* plugin_ctx)
307 {
308   struct plugin_private_context* plugin_priv_ctx
309       = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
310 
311   if (!plugin_priv_ctx) { return bRC_Error; }
312 
313   /*
314    * Stop any sub interpreter started per plugin instance.
315    */
316   PyEval_AcquireThread(plugin_priv_ctx->interpreter);
317 
318 
319   if (plugin_priv_ctx->pModule) { Py_DECREF(plugin_priv_ctx->pModule); }
320 
321   Py_EndInterpreter(plugin_priv_ctx->interpreter);
322 #if PY_VERSION_HEX < VERSION_HEX(3, 2, 0)
323   PyEval_ReleaseLock();
324 #else
325   PyThreadState_Swap(mainThreadState);
326   PyEval_ReleaseThread(mainThreadState);
327 #endif
328 
329   free(plugin_priv_ctx);
330   plugin_ctx->plugin_private_context = NULL;
331 
332   return bRC_OK;
333 }
334 
335 
handlePluginEvent(PluginContext * plugin_ctx,bSdEvent * event,void * value)336 static bRC handlePluginEvent(PluginContext* plugin_ctx,
337                              bSdEvent* event,
338                              void* value)
339 {
340   bRC retval = bRC_Error;
341   bool event_dispatched = false;
342   PoolMem plugin_options(PM_FNAME);
343   plugin_private_context* plugin_priv_ctx
344       = (plugin_private_context*)plugin_ctx->plugin_private_context;
345 
346   if (!plugin_priv_ctx) { goto bail_out; }
347 
348   /*
349    * First handle some events internally before calling python if it
350    * want to do some special handling on the event triggered.
351    */
352   switch (event->eventType) {
353     case bSdEventNewPluginOptions:
354       event_dispatched = true;
355       retval = parse_plugin_definition(plugin_ctx, value, plugin_options);
356       break;
357     default:
358       break;
359   }
360 
361   /*
362    * See if we have been triggered in the previous switch if not we have to
363    * always dispatch the event. If we already processed the event internally
364    * we only do a dispatch to the python entry point when that internal
365    * processing was successful (e.g. retval == bRC_OK).
366    */
367   if (!event_dispatched || retval == bRC_OK) {
368     PyEval_AcquireThread(plugin_priv_ctx->interpreter);
369 
370     /*
371      * Now dispatch the event to Python.
372      * First the calls that need special handling.
373      */
374     switch (event->eventType) {
375       case bSdEventNewPluginOptions:
376         /*
377          * See if we already loaded the Python modules.
378          */
379         if (!plugin_priv_ctx->python_loaded) {
380           retval = PyLoadModule(plugin_ctx, plugin_options.c_str());
381         }
382 
383         /* Only try to call when the loading succeeded. */
384         if (retval == bRC_OK) {
385           retval = Bareossd_PyParsePluginDefinition(plugin_ctx,
386                                                     plugin_options.c_str());
387         }
388         break;
389       default:
390         /*
391          * Handle the generic events e.g. the ones which are just passed on.
392          * We only try to call Python when we loaded the right module until
393          * that time we pretend the call succeeded.
394          */
395         if (plugin_priv_ctx->python_loaded) {
396           retval = Bareossd_PyHandlePluginEvent(plugin_ctx, event, value);
397         } else {
398           retval = bRC_OK;
399         }
400         break;
401     }
402 
403     PyEval_ReleaseThread(plugin_priv_ctx->interpreter);
404   }
405 
406 bail_out:
407   return retval;
408 }
409 
410 /**
411  * Parse the plugin definition passed in.
412  *
413  * The definition is in this form:
414  *
415  * python:module_path=<path>:module_name=<python_module_name>:...
416  */
parse_plugin_definition(PluginContext * plugin_ctx,void * value,PoolMem & plugin_options)417 static bRC parse_plugin_definition(PluginContext* plugin_ctx,
418                                    void* value,
419                                    PoolMem& plugin_options)
420 {
421   bool found;
422   int i, cnt;
423   PoolMem plugin_definition(PM_FNAME);
424   char *bp, *argument, *argument_value;
425   plugin_private_context* plugin_priv_ctx
426       = (plugin_private_context*)plugin_ctx->plugin_private_context;
427 
428   if (!value) { return bRC_Error; }
429 
430   /*
431    * Parse the plugin definition.
432    * Make a private copy of the whole string.
433    */
434   PmStrcpy(plugin_definition, (char*)value);
435 
436   bp = strchr(plugin_definition.c_str(), ':');
437   if (!bp) {
438     Jmsg(plugin_ctx, M_FATAL, LOGPREFIX "Illegal plugin definition %s\n",
439          plugin_definition.c_str());
440     Dmsg(plugin_ctx, debuglevel, LOGPREFIX "Illegal plugin definition %s\n",
441          plugin_definition.c_str());
442     goto bail_out;
443   }
444 
445   /*
446    * Skip the first ':'
447    */
448   bp++;
449 
450   cnt = 0;
451   while (bp) {
452     if (strlen(bp) == 0) { break; }
453 
454     /*
455      * Each argument is in the form:
456      *    <argument> = <argument_value>
457      *
458      * So we setup the right pointers here, argument to the beginning
459      * of the argument, argument_value to the beginning of the argument_value.
460      */
461     argument = bp;
462     argument_value = strchr(bp, '=');
463     if (!argument_value) {
464       Jmsg(plugin_ctx, M_FATAL, LOGPREFIX "Illegal argument %s without value\n",
465            argument);
466       Dmsg(plugin_ctx, debuglevel,
467            LOGPREFIX "Illegal argument %s without value\n", argument);
468       goto bail_out;
469     }
470     *argument_value++ = '\0';
471 
472     /*
473      * See if there are more arguments and setup for the next run.
474      */
475     bp = argument_value;
476     do {
477       bp = strchr(bp, ':');
478       if (bp) {
479         if (*(bp - 1) != '\\') {
480           *bp++ = '\0';
481           break;
482         } else {
483           bp++;
484         }
485       }
486     } while (bp);
487 
488     found = false;
489     for (i = 0; plugin_arguments[i].name; i++) {
490       if (Bstrcasecmp(argument, plugin_arguments[i].name)) {
491         int64_t* int_destination = NULL;
492         char** str_destination = NULL;
493         bool* bool_destination = NULL;
494 
495         switch (plugin_arguments[i].type) {
496           case argument_instance:
497             int_destination = &plugin_priv_ctx->instance;
498             break;
499           case argument_module_path:
500             str_destination = &plugin_priv_ctx->module_path;
501             break;
502           case argument_module_name:
503             str_destination = &plugin_priv_ctx->module_name;
504             break;
505           default:
506             break;
507         }
508 
509         if (int_destination) {
510           *int_destination = parse_integer(argument_value);
511         }
512 
513         if (str_destination) { SetString(str_destination, argument_value); }
514 
515         if (bool_destination) {
516           *bool_destination = ParseBoolean(argument_value);
517         }
518 
519         /*
520          * When we have a match break the loop.
521          */
522         found = true;
523         break;
524       }
525     }
526 
527     /*
528      * If we didn't consume this parameter we add it to the plugin_options list.
529      */
530     if (!found) {
531       PoolMem option(PM_FNAME);
532 
533       if (cnt) {
534         Mmsg(option, ":%s=%s", argument, argument_value);
535         PmStrcat(plugin_options, option.c_str());
536       } else {
537         Mmsg(option, "%s=%s", argument, argument_value);
538         PmStrcat(plugin_options, option.c_str());
539       }
540       cnt++;
541     }
542   }
543 
544   if (cnt > 0) { PmStrcat(plugin_options, ":"); }
545 
546   return bRC_OK;
547 
548 bail_out:
549   return bRC_Error;
550 }
551 
552 /**
553  * Initial load of the Python module.
554  *
555  * Based on the parsed plugin options we set some prerequisites like the
556  * module path and the module to load. We also load the dictionary used
557  * for looking up the Python methods.
558  */
PyLoadModule(PluginContext * plugin_ctx,void * value)559 static bRC PyLoadModule(PluginContext* plugin_ctx, void* value)
560 {
561   bRC retval = bRC_Error;
562   struct plugin_private_context* plugin_priv_ctx
563       = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
564   PyObject *sysPath, *mPath, *pName, *pFunc;
565   /* See if we already setup the python search path.  */
566   if (!plugin_priv_ctx->python_path_set) {
567     /* Extend the Python search path with the given module_path */
568     if (plugin_priv_ctx->module_path) {
569       sysPath = PySys_GetObject((char*)"path");
570       mPath = PyUnicode_FromString(plugin_priv_ctx->module_path);
571       PyList_Append(sysPath, mPath);
572       Py_DECREF(mPath);
573       plugin_priv_ctx->python_path_set = true;
574     }
575   }
576 
577   /* Try to load the Python module by name. */
578   if (plugin_priv_ctx->module_name) {
579     Dmsg(plugin_ctx, debuglevel,
580          LOGPREFIX "Trying to load module with name %s\n",
581          plugin_priv_ctx->module_name);
582     pName = PyUnicode_FromString(plugin_priv_ctx->module_name);
583     plugin_priv_ctx->pModule = PyImport_Import(pName);
584     Py_DECREF(pName);
585 
586     if (!plugin_priv_ctx->pModule) {
587       Dmsg(plugin_ctx, debuglevel,
588            LOGPREFIX "Failed to load module with name %s\n",
589            plugin_priv_ctx->module_name);
590       goto bail_out;
591     }
592 
593     Dmsg(plugin_ctx, debuglevel,
594          LOGPREFIX "Successfully loaded module with name %s\n",
595          plugin_priv_ctx->module_name);
596 
597     /* Get the Python dictionary for lookups in the Python namespace.  */
598     plugin_priv_ctx->pyModuleFunctionsDict
599         = PyModule_GetDict(plugin_priv_ctx->pModule); /* Borrowed reference */
600 
601 
602     /* Lookup the load_bareos_plugin() function in the python module.  */
603     pFunc = PyDict_GetItemString(plugin_priv_ctx->pyModuleFunctionsDict,
604                                  "load_bareos_plugin"); /* Borrowed reference */
605     if (pFunc && PyCallable_Check(pFunc)) {
606       PyObject *pPluginDefinition, *pRetVal;
607 
608       pPluginDefinition = PyUnicode_FromString((char*)value);
609       if (!pPluginDefinition) { goto bail_out; }
610 
611       pRetVal = PyObject_CallFunctionObjArgs(pFunc, pPluginDefinition, NULL);
612       Py_DECREF(pPluginDefinition);
613 
614       if (!pRetVal) {
615         goto bail_out;
616       } else {
617         retval = ConvertPythonRetvalTobRCRetval(pRetVal);
618         Py_DECREF(pRetVal);
619       }
620     } else {
621       Dmsg(plugin_ctx, debuglevel,
622            LOGPREFIX "Failed to find function named load_bareos_plugin()\n");
623       goto bail_out;
624     }
625 
626     /*
627      * Keep track we successfully loaded.
628      */
629     plugin_priv_ctx->python_loaded = true;
630   }
631 
632   return retval;
633 
634 bail_out:
635   if (PyErr_Occurred()) { PyErrorHandler(plugin_ctx, M_FATAL); }
636 
637   return retval;
638 }
639 
640 } /* namespace storagedaemon*/
641