1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2011-2014 Planets Communications B.V.
5    Copyright (C) 2013-2019 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  * Marco van Wieringen, August 2012
24  */
25 /**
26  * @file
27  * Python Storage daemon Plugin program
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 "stored/stored.h"
37 
38 using namespace storagedaemon;
39 
40 #if (PY_VERSION_HEX < 0x02060000)
41 #error "Need at least Python version 2.6 or newer"
42 #endif
43 
44 static const int debuglevel = 150;
45 
46 #define PLUGIN_LICENSE "Bareos AGPLv3"
47 #define PLUGIN_AUTHOR "Marco van Wieringen"
48 #define PLUGIN_DATE "October 2013"
49 #define PLUGIN_VERSION "3"
50 #define PLUGIN_DESCRIPTION "Python Storage Daemon Plugin"
51 #define PLUGIN_USAGE                                                           \
52   "python:instance=<instance_id>:module_path=<path-to-python-modules>:module_" \
53   "name=<python-module-to-load>"
54 
55 #define Dmsg(context, level, ...) \
56   bfuncs->DebugMessage(context, __FILE__, __LINE__, level, __VA_ARGS__)
57 #define Jmsg(context, type, ...) \
58   bfuncs->JobMessage(context, __FILE__, __LINE__, type, 0, __VA_ARGS__)
59 
60 /* Forward referenced functions */
61 static bRC newPlugin(bpContext* ctx);
62 static bRC freePlugin(bpContext* ctx);
63 static bRC getPluginValue(bpContext* ctx, psdVariable var, void* value);
64 static bRC setPluginValue(bpContext* ctx, psdVariable var, void* value);
65 static bRC handlePluginEvent(bpContext* ctx, bsdEvent* event, void* value);
66 static bRC parse_plugin_definition(bpContext* ctx,
67                                    void* value,
68                                    PoolMem& plugin_options);
69 
70 static void PyErrorHandler(bpContext* ctx, int msgtype);
71 static bRC PyLoadModule(bpContext* ctx, void* value);
72 static bRC PyParsePluginDefinition(bpContext* ctx, void* value);
73 static bRC PyGetPluginValue(bpContext* ctx, psdVariable var, void* value);
74 static bRC PySetPluginValue(bpContext* ctx, psdVariable var, void* value);
75 static bRC PyHandlePluginEvent(bpContext* ctx, bsdEvent* event, void* value);
76 
77 /* Pointers to Bareos functions */
78 static bsdFuncs* bfuncs = NULL;
79 static bsdInfo* binfo = NULL;
80 
81 static genpInfo pluginInfo = {sizeof(pluginInfo), SD_PLUGIN_INTERFACE_VERSION,
82                               SD_PLUGIN_MAGIC,    PLUGIN_LICENSE,
83                               PLUGIN_AUTHOR,      PLUGIN_DATE,
84                               PLUGIN_VERSION,     PLUGIN_DESCRIPTION,
85                               PLUGIN_USAGE};
86 
87 static psdFuncs pluginFuncs = {sizeof(pluginFuncs), SD_PLUGIN_INTERFACE_VERSION,
88 
89                                /* Entry points into plugin */
90                                newPlugin,  /* new plugin instance */
91                                freePlugin, /* free plugin instance */
92                                getPluginValue, setPluginValue,
93                                handlePluginEvent};
94 
95 /**
96  * Plugin private context
97  */
98 struct plugin_ctx {
99   int64_t instance;     /* Instance number of plugin */
100   bool python_loaded;   /* Plugin has python module loaded ? */
101   bool python_path_set; /* Python plugin search path is set ? */
102   char* module_path;    /* Plugin Module Path */
103   char* module_name;    /* Plugin Module Name */
104   PyThreadState*
105       interpreter;     /* Python interpreter for this instance of the plugin */
106   PyObject* pInstance; /* Python Module instance */
107   PyObject* pModule;   /* Python Module entry point */
108   PyObject* pDict;     /* Python Dictionary */
109   PyObject* bpContext; /* Python representation of plugin context */
110 };
111 
112 #include "python-sd.h"
113 #include "lib/edit.h"
114 
115 /**
116  * We don't actually use this but we need it to tear down the
117  * final python interpreter on unload of the plugin. Each instance of
118  * the plugin get its own interpreter.
119  */
120 static PyThreadState* mainThreadState;
121 
122 #ifdef __cplusplus
123 extern "C" {
124 #endif
125 
126 /**
127  * loadPlugin() and unloadPlugin() are entry points that are
128  *  exported, so Bareos can directly call these two entry points
129  *  they are common to all Bareos plugins.
130  *
131  * External entry point called by Bareos to "load" the plugin
132  */
loadPlugin(bsdInfo * lbinfo,bsdFuncs * lbfuncs,genpInfo ** pinfo,psdFuncs ** pfuncs)133 bRC loadPlugin(bsdInfo* lbinfo,
134                bsdFuncs* lbfuncs,
135                genpInfo** pinfo,
136                psdFuncs** pfuncs)
137 {
138   bfuncs = lbfuncs; /* Set Bareos funct pointers */
139   binfo = lbinfo;
140 
141   *pinfo = &pluginInfo;   /* Return pointer to our info */
142   *pfuncs = &pluginFuncs; /* Return pointer to our functions */
143 
144   /*
145    * Setup Python
146    */
147   Py_InitializeEx(0);
148   PyEval_InitThreads();
149   mainThreadState = PyEval_SaveThread();
150 
151   return bRC_OK;
152 }
153 
154 /**
155  * External entry point to unload the plugin
156  */
unloadPlugin()157 bRC unloadPlugin()
158 {
159   /*
160    * Terminate Python
161    */
162   PyEval_RestoreThread(mainThreadState);
163   Py_Finalize();
164 
165   return bRC_OK;
166 }
167 
168 #ifdef __cplusplus
169 }
170 #endif
171 
172 /**
173  * The following entry points are accessed through the function
174  * pointers we supplied to Bareos. Each plugin type (dir, fd, sd)
175  * has its own set of entry points that the plugin must define.
176  */
177 /**
178  * Create a new instance of the plugin i.e. allocate our private storage
179  */
newPlugin(bpContext * ctx)180 static bRC newPlugin(bpContext* ctx)
181 {
182   struct plugin_ctx* p_ctx;
183 
184   p_ctx = (struct plugin_ctx*)malloc(sizeof(struct plugin_ctx));
185   if (!p_ctx) { return bRC_Error; }
186   memset(p_ctx, 0, sizeof(struct plugin_ctx));
187   ctx->pContext = (void*)p_ctx; /* set our context pointer */
188 
189   /*
190    * For each plugin instance we instantiate a new Python interpreter.
191    */
192   PyEval_AcquireLock();
193   p_ctx->interpreter = Py_NewInterpreter();
194   PyEval_ReleaseThread(p_ctx->interpreter);
195 
196   /*
197    * Always register some events the python plugin itself can register
198    * any other events it is interested in.
199    */
200   bfuncs->registerBareosEvents(ctx, 1, bsdEventNewPluginOptions);
201 
202   return bRC_OK;
203 }
204 
205 /**
206  * Free a plugin instance, i.e. release our private storage
207  */
freePlugin(bpContext * ctx)208 static bRC freePlugin(bpContext* ctx)
209 {
210   struct plugin_ctx* p_ctx = (struct plugin_ctx*)ctx->pContext;
211 
212   if (!p_ctx) { return bRC_Error; }
213 
214   /*
215    * Stop any sub interpreter started per plugin instance.
216    */
217   PyEval_AcquireThread(p_ctx->interpreter);
218 
219   /*
220    * Do python cleanup calls.
221    */
222   if (p_ctx->bpContext) { Py_DECREF(p_ctx->bpContext); }
223 
224   if (p_ctx->pModule) { Py_DECREF(p_ctx->pModule); }
225 
226   Py_EndInterpreter(p_ctx->interpreter);
227   PyEval_ReleaseLock();
228 
229   free(p_ctx);
230   ctx->pContext = NULL;
231 
232   return bRC_OK;
233 }
234 
235 /**
236  * Return some plugin value (none defined)
237  */
getPluginValue(bpContext * ctx,psdVariable var,void * value)238 static bRC getPluginValue(bpContext* ctx, psdVariable var, void* value)
239 {
240   struct plugin_ctx* p_ctx = (struct plugin_ctx*)ctx->pContext;
241   bRC retval = bRC_Error;
242 
243   PyEval_AcquireThread(p_ctx->interpreter);
244   retval = PyGetPluginValue(ctx, var, value);
245   PyEval_ReleaseThread(p_ctx->interpreter);
246 
247   return retval;
248 }
249 
250 /**
251  * Set a plugin value (none defined)
252  */
setPluginValue(bpContext * ctx,psdVariable var,void * value)253 static bRC setPluginValue(bpContext* ctx, psdVariable var, void* value)
254 {
255   struct plugin_ctx* p_ctx = (struct plugin_ctx*)ctx->pContext;
256   bRC retval = bRC_Error;
257 
258   PyEval_AcquireThread(p_ctx->interpreter);
259   retval = PySetPluginValue(ctx, var, value);
260   PyEval_ReleaseThread(p_ctx->interpreter);
261 
262   return retval;
263 }
264 
265 /**
266  * Handle an event that was generated in Bareos
267  */
handlePluginEvent(bpContext * ctx,bsdEvent * event,void * value)268 static bRC handlePluginEvent(bpContext* ctx, bsdEvent* event, void* value)
269 {
270   bRC retval = bRC_Error;
271   bool event_dispatched = false;
272   PoolMem plugin_options(PM_FNAME);
273   plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
274 
275   if (!p_ctx) { goto bail_out; }
276 
277   /*
278    * First handle some events internally before calling python if it
279    * want to do some special handling on the event triggered.
280    */
281   switch (event->eventType) {
282     case bsdEventNewPluginOptions:
283       event_dispatched = true;
284       retval = parse_plugin_definition(ctx, value, plugin_options);
285       break;
286     default:
287       break;
288   }
289 
290   /*
291    * See if we have been triggered in the previous switch if not we have to
292    * always dispatch the event. If we already processed the event internally
293    * we only do a dispatch to the python entry point when that internal
294    * processing was successfull (e.g. retval == bRC_OK).
295    */
296   if (!event_dispatched || retval == bRC_OK) {
297     PyEval_AcquireThread(p_ctx->interpreter);
298 
299     /*
300      * Now dispatch the event to Python.
301      * First the calls that need special handling.
302      */
303     switch (event->eventType) {
304       case bsdEventNewPluginOptions:
305         /*
306          * See if we already loaded the Python modules.
307          */
308         if (!p_ctx->python_loaded) {
309           retval = PyLoadModule(ctx, plugin_options.c_str());
310         }
311 
312         /*
313          * Only try to call when the loading succeeded.
314          */
315         if (retval == bRC_OK) {
316           retval = PyParsePluginDefinition(ctx, plugin_options.c_str());
317         }
318         break;
319       default:
320         /*
321          * Handle the generic events e.g. the ones which are just passed on.
322          * We only try to call Python when we loaded the right module until
323          * that time we pretend the call succeeded.
324          */
325         if (p_ctx->python_loaded) {
326           retval = PyHandlePluginEvent(ctx, event, value);
327         } else {
328           retval = bRC_OK;
329         }
330         break;
331     }
332 
333     PyEval_ReleaseThread(p_ctx->interpreter);
334   }
335 
336 bail_out:
337   return retval;
338 }
339 
340 /**
341  * Strip any backslashes in the string.
342  */
StripBackSlashes(char * value)343 static inline void StripBackSlashes(char* value)
344 {
345   char* bp;
346 
347   bp = value;
348   while (*bp) {
349     switch (*bp) {
350       case '\\':
351         bstrinlinecpy(bp, bp + 1);
352         break;
353       default:
354         break;
355     }
356 
357     bp++;
358   }
359 }
360 
361 /**
362  * Parse a integer value.
363  */
parse_integer(const char * argument_value)364 static inline int64_t parse_integer(const char* argument_value)
365 {
366   return str_to_int64(argument_value);
367 }
368 
369 /**
370  * Parse a boolean value e.g. check if its yes or true anything else translates
371  * to false.
372  */
ParseBoolean(const char * argument_value)373 static inline bool ParseBoolean(const char* argument_value)
374 {
375   if (Bstrcasecmp(argument_value, "yes") ||
376       Bstrcasecmp(argument_value, "true")) {
377     return true;
378   } else {
379     return false;
380   }
381 }
382 
383 /**
384  * Always set destination to value and clean any previous one.
385  */
SetString(char ** destination,char * value)386 static inline void SetString(char** destination, char* value)
387 {
388   if (*destination) { free(*destination); }
389 
390   *destination = strdup(value);
391   StripBackSlashes(*destination);
392 }
393 
394 /**
395  * Parse the plugin definition passed in.
396  *
397  * The definition is in this form:
398  *
399  * python:module_path=<path>:module_name=<python_module_name>:...
400  */
parse_plugin_definition(bpContext * ctx,void * value,PoolMem & plugin_options)401 static bRC parse_plugin_definition(bpContext* ctx,
402                                    void* value,
403                                    PoolMem& plugin_options)
404 {
405   bool found;
406   int i, cnt;
407   PoolMem plugin_definition(PM_FNAME);
408   char *bp, *argument, *argument_value;
409   plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
410 
411   if (!value) { return bRC_Error; }
412 
413   /*
414    * Parse the plugin definition.
415    * Make a private copy of the whole string.
416    */
417   PmStrcpy(plugin_definition, (char*)value);
418 
419   bp = strchr(plugin_definition.c_str(), ':');
420   if (!bp) {
421     Jmsg(ctx, M_FATAL, "python-sd: Illegal plugin definition %s\n",
422          plugin_definition.c_str());
423     Dmsg(ctx, debuglevel, "python-sd: Illegal plugin definition %s\n",
424          plugin_definition.c_str());
425     goto bail_out;
426   }
427 
428   /*
429    * Skip the first ':'
430    */
431   bp++;
432 
433   cnt = 0;
434   while (bp) {
435     if (strlen(bp) == 0) { break; }
436 
437     /*
438      * Each argument is in the form:
439      *    <argument> = <argument_value>
440      *
441      * So we setup the right pointers here, argument to the beginning
442      * of the argument, argument_value to the beginning of the argument_value.
443      */
444     argument = bp;
445     argument_value = strchr(bp, '=');
446     if (!argument_value) {
447       Jmsg(ctx, M_FATAL, "python-sd: Illegal argument %s without value\n",
448            argument);
449       Dmsg(ctx, debuglevel, "python-sd: Illegal argument %s without value\n",
450            argument);
451       goto bail_out;
452     }
453     *argument_value++ = '\0';
454 
455     /*
456      * See if there are more arguments and setup for the next run.
457      */
458     bp = argument_value;
459     do {
460       bp = strchr(bp, ':');
461       if (bp) {
462         if (*(bp - 1) != '\\') {
463           *bp++ = '\0';
464           break;
465         } else {
466           bp++;
467         }
468       }
469     } while (bp);
470 
471     found = false;
472     for (i = 0; plugin_arguments[i].name; i++) {
473       if (Bstrcasecmp(argument, plugin_arguments[i].name)) {
474         int64_t* int_destination = NULL;
475         char** str_destination = NULL;
476         bool* bool_destination = NULL;
477 
478         switch (plugin_arguments[i].type) {
479           case argument_instance:
480             int_destination = &p_ctx->instance;
481             break;
482           case argument_module_path:
483             str_destination = &p_ctx->module_path;
484             break;
485           case argument_module_name:
486             str_destination = &p_ctx->module_name;
487             break;
488           default:
489             break;
490         }
491 
492         if (int_destination) {
493           *int_destination = parse_integer(argument_value);
494         }
495 
496         if (str_destination) { SetString(str_destination, argument_value); }
497 
498         if (bool_destination) {
499           *bool_destination = ParseBoolean(argument_value);
500         }
501 
502         /*
503          * When we have a match break the loop.
504          */
505         found = true;
506         break;
507       }
508     }
509 
510     /*
511      * If we didn't consume this parameter we add it to the plugin_options list.
512      */
513     if (!found) {
514       PoolMem option(PM_FNAME);
515 
516       if (cnt) {
517         Mmsg(option, ":%s=%s", argument, argument_value);
518         PmStrcat(plugin_options, option.c_str());
519       } else {
520         Mmsg(option, "%s=%s", argument, argument_value);
521         PmStrcat(plugin_options, option.c_str());
522       }
523       cnt++;
524     }
525   }
526 
527   if (cnt > 0) { PmStrcat(plugin_options, ":"); }
528 
529   return bRC_OK;
530 
531 bail_out:
532   return bRC_Error;
533 }
534 
535 /**
536  * Work around API changes in Python versions.
537  * These function abstract the storage and retrieval of the bpContext
538  * which is passed to the Python methods and which the method can pass
539  * back and which allow the callback function to understand what bpContext
540  * its talking about.
541  */
542 #if ((PY_VERSION_HEX < 0x02070000) || \
543      ((PY_VERSION_HEX >= 0x03000000) && (PY_VERSION_HEX < 0x03010000)))
544 /**
545  * Python version before 2.7 and 3.0.
546  */
PyCreatebpContext(bpContext * ctx)547 static PyObject* PyCreatebpContext(bpContext* ctx)
548 {
549   /*
550    * Setup a new CObject which holds the bpContext structure used here
551    * internally.
552    */
553   return PyCObject_FromVoidPtr((void*)ctx, NULL);
554 }
555 
PyGetbpContext(PyObject * pyCtx)556 static bpContext* PyGetbpContext(PyObject* pyCtx)
557 {
558   return (bpContext*)PyCObject_AsVoidPtr(pyCtx);
559 }
560 #else
561 /**
562  * Python version after 2.6 and 3.1.
563  */
PyCreatebpContext(bpContext * ctx)564 static PyObject* PyCreatebpContext(bpContext* ctx)
565 {
566   /*
567    * Setup a new Capsule which holds the bpContext structure used here
568    * internally.
569    */
570   return PyCapsule_New((void*)ctx, "bareos.bpContext", NULL);
571 }
572 
PyGetbpContext(PyObject * pyCtx)573 static bpContext* PyGetbpContext(PyObject* pyCtx)
574 {
575   return (bpContext*)PyCapsule_GetPointer(pyCtx, "bareos.bpContext");
576 }
577 #endif
578 
579 /**
580  * Convert a return value into a bRC enum value.
581  */
conv_python_retval(PyObject * pRetVal)582 static inline bRC conv_python_retval(PyObject* pRetVal)
583 {
584   return (bRC)PyInt_AsLong(pRetVal);
585 }
586 
587 /**
588  * Convert a return value from bRC enum value into Python Object.
589  */
conv_retval_python(bRC retval)590 static inline PyObject* conv_retval_python(bRC retval)
591 {
592   return (PyObject*)PyInt_FromLong((int)retval);
593 }
594 
595 /**
596  * Handle a Python error.
597  *
598  * Python equivalent:
599  *
600  * import traceback, sys
601  * return "".join(traceback.format_exception(sys.exc_type,
602  *    sys.exc_value, sys.exc_traceback))
603  */
PyErrorHandler(bpContext * ctx,int msgtype)604 static void PyErrorHandler(bpContext* ctx, int msgtype)
605 {
606   PyObject *type, *value, *traceback;
607   PyObject* tracebackModule;
608   char* error_string;
609 
610   PyErr_Fetch(&type, &value, &traceback);
611 
612   tracebackModule = PyImport_ImportModule("traceback");
613   if (tracebackModule != NULL) {
614     PyObject *tbList, *emptyString, *strRetval;
615 
616     tbList =
617         PyObject_CallMethod(tracebackModule, (char*)"format_exception",
618                             (char*)"OOO", type, value == NULL ? Py_None : value,
619                             traceback == NULL ? Py_None : traceback);
620 
621     emptyString = PyString_FromString("");
622     strRetval =
623         PyObject_CallMethod(emptyString, (char*)"join", (char*)"O", tbList);
624 
625     error_string = strdup(PyString_AsString(strRetval));
626 
627     Py_DECREF(tbList);
628     Py_DECREF(emptyString);
629     Py_DECREF(strRetval);
630     Py_DECREF(tracebackModule);
631   } else {
632     error_string = strdup("Unable to import traceback module.");
633   }
634 
635   Py_DECREF(type);
636   Py_XDECREF(value);
637   Py_XDECREF(traceback);
638 
639   Dmsg(ctx, debuglevel, "python-sd: %s\n", error_string);
640   if (msgtype) { Jmsg(ctx, msgtype, "python-sd: %s\n", error_string); }
641 
642   free(error_string);
643 }
644 
645 /**
646  * Initial load of the Python module.
647  *
648  * Based on the parsed plugin options we set some prerequisits like the
649  * module path and the module to load. We also load the dictionary used
650  * for looking up the Python methods.
651  */
PyLoadModule(bpContext * ctx,void * value)652 static bRC PyLoadModule(bpContext* ctx, void* value)
653 {
654   bRC retval = bRC_Error;
655   struct plugin_ctx* p_ctx = (struct plugin_ctx*)ctx->pContext;
656   PyObject *sysPath, *mPath, *pName, *pFunc;
657 
658   /*
659    * See if we already setup the python search path.
660    */
661   if (!p_ctx->python_path_set) {
662     /*
663      * Extend the Python search path with the given module_path.
664      */
665     if (p_ctx->module_path) {
666       sysPath = PySys_GetObject((char*)"path");
667       mPath = PyString_FromString(p_ctx->module_path);
668       PyList_Append(sysPath, mPath);
669       Py_DECREF(mPath);
670       p_ctx->python_path_set = true;
671     }
672   }
673 
674   /*
675    * See if we already setup the module structure.
676    */
677   if (!p_ctx->pInstance) {
678     /*
679      * Make our callback methods available for Python.
680      */
681     p_ctx->pInstance = Py_InitModule("bareossd", BareosSDMethods);
682   }
683 
684   /*
685    * Try to load the Python module by name.
686    */
687   if (p_ctx->module_name) {
688     Dmsg(ctx, debuglevel, "python-sd: Trying to load module with name %s\n",
689          p_ctx->module_name);
690     pName = PyString_FromString(p_ctx->module_name);
691     p_ctx->pModule = PyImport_Import(pName);
692     Py_DECREF(pName);
693 
694     if (!p_ctx->pModule) {
695       Dmsg(ctx, debuglevel, "python-sd: Failed to load module with name %s\n",
696            p_ctx->module_name);
697       goto bail_out;
698     }
699 
700     Dmsg(ctx, debuglevel,
701          "python-sd: Successfully loaded module with name %s\n",
702          p_ctx->module_name);
703 
704     /*
705      * Get the Python dictionary for lookups in the Python namespace.
706      */
707     p_ctx->pDict = PyModule_GetDict(p_ctx->pModule); /* Borrowed reference */
708 
709     /*
710      * Encode the bpContext so a Python method can pass it in on calling back.
711      */
712     p_ctx->bpContext = PyCreatebpContext(ctx);
713 
714     /*
715      * Lookup the load_bareos_plugin() function in the python module.
716      */
717     pFunc = PyDict_GetItemString(p_ctx->pDict,
718                                  "load_bareos_plugin"); /* Borrowed reference */
719     if (pFunc && PyCallable_Check(pFunc)) {
720       PyObject *pPluginDefinition, *pRetVal;
721 
722       pPluginDefinition = PyString_FromString((char*)value);
723       if (!pPluginDefinition) { goto bail_out; }
724 
725       pRetVal = PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext,
726                                              pPluginDefinition, NULL);
727       Py_DECREF(pPluginDefinition);
728 
729       if (!pRetVal) {
730         goto bail_out;
731       } else {
732         retval = conv_python_retval(pRetVal);
733         Py_DECREF(pRetVal);
734       }
735     } else {
736       Dmsg(ctx, debuglevel,
737            "python-sd: Failed to find function named load_bareos_plugins()\n");
738       goto bail_out;
739     }
740 
741     /*
742      * Keep track we successfully loaded.
743      */
744     p_ctx->python_loaded = true;
745   }
746 
747   return retval;
748 
749 bail_out:
750   if (PyErr_Occurred()) { PyErrorHandler(ctx, M_FATAL); }
751 
752   return retval;
753 }
754 
755 /**
756  * Any plugin options which are passed in are dispatched here to a Python method
757  * and it can parse the plugin options. This function is also called after
758  * PyLoadModule() has loaded the Python module and made sure things are
759  * operational.
760  */
PyParsePluginDefinition(bpContext * ctx,void * value)761 static bRC PyParsePluginDefinition(bpContext* ctx, void* value)
762 {
763   bRC retval = bRC_Error;
764   struct plugin_ctx* p_ctx = (struct plugin_ctx*)ctx->pContext;
765   PyObject* pFunc;
766 
767   /*
768    * Lookup the parse_plugin_definition() function in the python module.
769    */
770   pFunc = PyDict_GetItemString(
771       p_ctx->pDict, "parse_plugin_definition"); /* Borrowed reference */
772   if (pFunc && PyCallable_Check(pFunc)) {
773     PyObject *pPluginDefinition, *pRetVal;
774 
775     pPluginDefinition = PyString_FromString((char*)value);
776     if (!pPluginDefinition) { goto bail_out; }
777 
778     pRetVal = PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext,
779                                            pPluginDefinition, NULL);
780     Py_DECREF(pPluginDefinition);
781 
782     if (!pRetVal) {
783       goto bail_out;
784     } else {
785       retval = conv_python_retval(pRetVal);
786       Py_DECREF(pRetVal);
787     }
788 
789     return retval;
790   } else {
791     Dmsg(
792         ctx, debuglevel,
793         "python-sd: Failed to find function named parse_plugin_definition()\n");
794     return bRC_Error;
795   }
796 
797 bail_out:
798   if (PyErr_Occurred()) { PyErrorHandler(ctx, M_FATAL); }
799 
800   return retval;
801 }
802 
PyGetPluginValue(bpContext * ctx,psdVariable var,void * value)803 static bRC PyGetPluginValue(bpContext* ctx, psdVariable var, void* value)
804 {
805   return bRC_OK;
806 }
807 
PySetPluginValue(bpContext * ctx,psdVariable var,void * value)808 static bRC PySetPluginValue(bpContext* ctx, psdVariable var, void* value)
809 {
810   return bRC_OK;
811 }
812 
PyHandlePluginEvent(bpContext * ctx,bsdEvent * event,void * value)813 static bRC PyHandlePluginEvent(bpContext* ctx, bsdEvent* event, void* value)
814 {
815   bRC retval = bRC_Error;
816   plugin_ctx* p_ctx = (plugin_ctx*)ctx->pContext;
817   PyObject* pFunc;
818 
819   /*
820    * Lookup the handle_plugin_event() function in the python module.
821    */
822   pFunc = PyDict_GetItemString(p_ctx->pDict,
823                                "handle_plugin_event"); /* Borrowed reference */
824   if (pFunc && PyCallable_Check(pFunc)) {
825     PyObject *pEventType, *pRetVal;
826 
827     pEventType = PyInt_FromLong(event->eventType);
828 
829     pRetVal =
830         PyObject_CallFunctionObjArgs(pFunc, p_ctx->bpContext, pEventType, NULL);
831     Py_DECREF(pEventType);
832 
833     if (!pRetVal) {
834       goto bail_out;
835     } else {
836       retval = conv_python_retval(pRetVal);
837       Py_DECREF(pRetVal);
838     }
839   } else {
840     Dmsg(ctx, debuglevel,
841          "python-sd: Failed to find function named handle_plugin_event()\n");
842   }
843 
844   return retval;
845 
846 bail_out:
847   if (PyErr_Occurred()) { PyErrorHandler(ctx, M_FATAL); }
848 
849   return retval;
850 }
851 
852 /**
853  * Callback function which is exposed as a part of the additional methods which
854  * allow a Python plugin to get certain internal values of the current Job.
855  */
PyBareosGetValue(PyObject * self,PyObject * args)856 static PyObject* PyBareosGetValue(PyObject* self, PyObject* args)
857 {
858   int var;
859   bpContext* ctx = NULL;
860   PyObject* pyCtx;
861   PyObject* pRetVal = NULL;
862 
863   if (!PyArg_ParseTuple(args, "Oi:BareosGetValue", &pyCtx, &var)) {
864     return NULL;
865   }
866 
867   switch (var) {
868     case bsdVarJobId:
869     case bsdVarLevel:
870     case bsdVarType:
871     case bsdVarJobStatus: {
872       int value;
873 
874       ctx = PyGetbpContext(pyCtx);
875       if (bfuncs->getBareosValue(ctx, (bsdrVariable)var, &value) == bRC_OK) {
876         pRetVal = PyInt_FromLong(value);
877       }
878       break;
879     }
880     case bsdVarJobErrors:
881     case bsdVarJobFiles:
882     case bsdVarJobBytes: {
883       uint64_t value = 0;
884 
885       ctx = PyGetbpContext(pyCtx);
886       if (bfuncs->getBareosValue(ctx, (bsdrVariable)var, &value) == bRC_OK) {
887         pRetVal = PyLong_FromUnsignedLong(value);
888       }
889       break;
890     }
891     case bsdVarJobName:
892     case bsdVarJob:
893     case bsdVarClient:
894     case bsdVarPool:
895     case bsdVarPoolType:
896     case bsdVarStorage:
897     case bsdVarMediaType:
898     case bsdVarVolumeName: {
899       char* value = NULL;
900 
901       ctx = PyGetbpContext(pyCtx);
902       if (bfuncs->getBareosValue(ctx, (bsdrVariable)var, &value) == bRC_OK) {
903         if (value) { pRetVal = PyString_FromString(value); }
904       }
905       break;
906     }
907     case bsdVarCompatible: {
908       bool value;
909 
910       if (bfuncs->getBareosValue(NULL, (bsdrVariable)var, &value) == bRC_OK) {
911         long bool_value;
912 
913         bool_value = (value) ? 1 : 0;
914         pRetVal = PyBool_FromLong(bool_value);
915       }
916       break;
917     }
918     case bsdVarPluginDir: {
919       char* value = NULL;
920 
921       if (bfuncs->getBareosValue(NULL, (bsdrVariable)var, &value) == bRC_OK) {
922         if (value) { pRetVal = PyString_FromString(value); }
923       }
924       break;
925     }
926     default:
927       ctx = PyGetbpContext(pyCtx);
928       Dmsg(ctx, debuglevel,
929            "python-sd: PyBareosGetValue unknown variable requested %d\n", var);
930       break;
931   }
932 
933   if (!pRetVal) {
934     Py_INCREF(Py_None);
935     pRetVal = Py_None;
936   }
937 
938   return pRetVal;
939 }
940 
941 /**
942  * Callback function which is exposed as a part of the additional methods which
943  * allow a Python plugin to get certain internal values of the current Job.
944  */
PyBareosSetValue(PyObject * self,PyObject * args)945 static PyObject* PyBareosSetValue(PyObject* self, PyObject* args)
946 {
947   int var;
948   bpContext* ctx = NULL;
949   bRC retval = bRC_Error;
950   PyObject *pyCtx, *pyValue;
951 
952   if (!PyArg_ParseTuple(args, "OiO:BareosSetValue", &pyCtx, &var, &pyValue)) {
953     goto bail_out;
954   }
955 
956   switch (var) {
957     case bsdwVarVolumeName: {
958       char* value;
959 
960       ctx = PyGetbpContext(pyCtx);
961       value = PyString_AsString(pyValue);
962       if (value) { bfuncs->setBareosValue(ctx, (bsdwVariable)var, value); }
963 
964       break;
965     }
966     case bsdwVarPriority:
967     case bsdwVarJobLevel: {
968       int value;
969 
970       ctx = PyGetbpContext(pyCtx);
971       value = PyInt_AsLong(pyValue);
972       if (value >= 0) {
973         retval = bfuncs->setBareosValue(ctx, (bsdwVariable)var, &value);
974       }
975       break;
976     }
977     default:
978       ctx = PyGetbpContext(pyCtx);
979       Dmsg(ctx, debuglevel,
980            "python-sd: PyBareosSetValue unknown variable requested %d\n", var);
981       break;
982   }
983 
984 bail_out:
985   return conv_retval_python(retval);
986 }
987 
988 /**
989  * Callback function which is exposed as a part of the additional methods which
990  * allow a Python plugin to issue debug messages using the Bareos debug message
991  * facility.
992  */
PyBareosDebugMessage(PyObject * self,PyObject * args)993 static PyObject* PyBareosDebugMessage(PyObject* self, PyObject* args)
994 {
995   int level;
996   char* dbgmsg = NULL;
997   bpContext* ctx;
998   PyObject* pyCtx;
999 
1000   if (!PyArg_ParseTuple(args, "Oi|z:BareosDebugMessage", &pyCtx, &level,
1001                         &dbgmsg)) {
1002     return NULL;
1003   }
1004 
1005   if (dbgmsg) {
1006     ctx = PyGetbpContext(pyCtx);
1007     Dmsg(ctx, level, "python-sd: %s", dbgmsg);
1008   }
1009 
1010   Py_INCREF(Py_None);
1011   return Py_None;
1012 }
1013 
1014 /**
1015  * Callback function which is exposed as a part of the additional methods which
1016  * allow a Python plugin to issue Job messages using the Bareos Job message
1017  * facility.
1018  */
PyBareosJobMessage(PyObject * self,PyObject * args)1019 static PyObject* PyBareosJobMessage(PyObject* self, PyObject* args)
1020 {
1021   int type;
1022   char* jobmsg = NULL;
1023   bpContext* ctx;
1024   PyObject* pyCtx;
1025 
1026   if (!PyArg_ParseTuple(args, "Oi|z:BareosJobMessage", &pyCtx, &type,
1027                         &jobmsg)) {
1028     return NULL;
1029   }
1030 
1031   if (jobmsg) {
1032     ctx = PyGetbpContext(pyCtx);
1033     Jmsg(ctx, type, "python-sd: %s", jobmsg);
1034   }
1035 
1036   Py_INCREF(Py_None);
1037   return Py_None;
1038 }
1039 
1040 /**
1041  * Callback function which is exposed as a part of the additional methods which
1042  * allow a Python plugin to issue a Register Event to register additional events
1043  * it wants to receive.
1044  */
PyBareosRegisterEvents(PyObject * self,PyObject * args)1045 static PyObject* PyBareosRegisterEvents(PyObject* self, PyObject* args)
1046 {
1047   int len, event;
1048   bpContext* ctx;
1049   bRC retval = bRC_Error;
1050   PyObject *pyCtx, *pyEvents, *pySeq, *pyEvent;
1051 
1052   if (!PyArg_ParseTuple(args, "OO:BareosRegisterEvents", &pyCtx, &pyEvents)) {
1053     goto bail_out;
1054   }
1055 
1056   pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
1057   if (!pySeq) { goto bail_out; }
1058 
1059   len = PySequence_Fast_GET_SIZE(pySeq);
1060 
1061   ctx = PyGetbpContext(pyCtx);
1062   for (int i = 0; i < len; i++) {
1063     pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
1064     event = PyInt_AsLong(pyEvent);
1065 
1066     if (event >= bsdEventJobStart && event <= bsdEventWriteRecordTranslation) {
1067       Dmsg(ctx, debuglevel,
1068            "python-sd: PyBareosRegisterEvents registering event %d\n", event);
1069       retval = bfuncs->registerBareosEvents(ctx, 1, event);
1070 
1071       if (retval != bRC_OK) { break; }
1072     }
1073   }
1074 
1075   Py_DECREF(pySeq);
1076 
1077 bail_out:
1078   return conv_retval_python(retval);
1079 }
1080 
1081 /**
1082  * Callback function which is exposed as a part of the additional methods which
1083  * allow a Python plugin to issue an Unregister Event to unregister events it
1084  * doesn't want to receive anymore.
1085  */
PyBareosUnRegisterEvents(PyObject * self,PyObject * args)1086 static PyObject* PyBareosUnRegisterEvents(PyObject* self, PyObject* args)
1087 {
1088   int len, event;
1089   bpContext* ctx;
1090   bRC retval = bRC_Error;
1091   PyObject *pyCtx, *pyEvents, *pySeq, *pyEvent;
1092 
1093   if (!PyArg_ParseTuple(args, "OO:BareosUnRegisterEvents", &pyCtx, &pyEvents)) {
1094     goto bail_out;
1095   }
1096 
1097   pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
1098   if (!pySeq) { goto bail_out; }
1099 
1100   len = PySequence_Fast_GET_SIZE(pySeq);
1101 
1102   ctx = PyGetbpContext(pyCtx);
1103   for (int i = 0; i < len; i++) {
1104     pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
1105     event = PyInt_AsLong(pyEvent);
1106 
1107     if (event >= bsdEventJobStart && event <= bsdEventWriteRecordTranslation) {
1108       Dmsg(ctx, debuglevel, "PyBareosUnRegisterEvents: registering event %d\n",
1109            event);
1110       retval = bfuncs->unregisterBareosEvents(ctx, 1, event);
1111 
1112       if (retval != bRC_OK) { break; }
1113     }
1114   }
1115 
1116   Py_DECREF(pySeq);
1117 
1118 bail_out:
1119   return conv_retval_python(retval);
1120 }
1121 
1122 /**
1123  * Callback function which is exposed as a part of the additional methods which
1124  * allow a Python plugin to issue a GetInstanceCount to retrieve the number of
1125  * instances of the current plugin being loaded into the daemon.
1126  */
PyBareosGetInstanceCount(PyObject * self,PyObject * args)1127 static PyObject* PyBareosGetInstanceCount(PyObject* self, PyObject* args)
1128 {
1129   int value;
1130   bpContext* ctx = NULL;
1131   PyObject* pyCtx;
1132   PyObject* pRetVal = NULL;
1133 
1134   if (!PyArg_ParseTuple(args, "O:BareosGetInstanceCount", &pyCtx)) {
1135     return NULL;
1136   }
1137 
1138   ctx = PyGetbpContext(pyCtx);
1139   if (bfuncs->getInstanceCount(ctx, &value) == bRC_OK) {
1140     pRetVal = PyInt_FromLong(value);
1141   }
1142 
1143   if (!pRetVal) {
1144     Py_INCREF(Py_None);
1145     pRetVal = Py_None;
1146   }
1147 
1148   return pRetVal;
1149 }
1150