1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2020-2020 Bareos GmbH & Co. KG
5 
6    This program is Free Software; you can redistribute it and/or
7    modify it under the terms of version three of the GNU Affero General Public
8    License as published by the Free Software Foundation, which is
9    listed in the file LICENSE.
10 
11    This program is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14    Affero General Public License for more details.
15 
16    You should have received a copy of the GNU Affero General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19    02110-1301, USA.
20 */
21 /**
22  * @file
23  * Python module for the Bareos director plugin
24  */
25 #define PY_SSIZE_T_CLEAN
26 #define BUILD_PLUGIN
27 
28 #if defined(HAVE_WIN32)
29 #  include "include/bareos.h"
30 #  include <Python.h>
31 #else
32 #  include <Python.h>
33 #  include "include/bareos.h"
34 #endif
35 
36 #include "include/version_hex.h"
37 
38 #if PY_VERSION_HEX < VERSION_HEX(3, 0, 0)
39 #  define LOGPREFIX "python-dir-mod: "
40 #else
41 #  define LOGPREFIX "python3-dir-mod: "
42 #endif
43 
44 #include "dird/dird.h"
45 #include "dird/dir_plugins.h"
46 
47 
48 #include "plugins/include/common.h"  // for Dmsg Macro
49 
50 #include "plugins/dird/python/plugin_private_context.h"
51 
52 #include "plugins/include/python3compat.h"
53 
54 #define BAREOSDIR_MODULE
55 #include "bareosdir.h"
56 #include "lib/edit.h"
57 
58 namespace directordaemon {
59 static const int debuglevel = 150;
60 
61 static bRC set_bareos_core_functions(CoreFunctions* new_bareos_core_functions);
62 static bRC set_plugin_context(PluginContext* new_plugin_context);
63 
64 static bRC PyParsePluginDefinition(PluginContext* plugin_ctx, void* value);
65 static bRC PyGetPluginValue(PluginContext* plugin_ctx,
66                             pVariable var,
67                             void* value);
68 static bRC PySetPluginValue(PluginContext* plugin_ctx,
69                             pVariable var,
70                             void* value);
71 static bRC PyHandlePluginEvent(PluginContext* plugin_ctx,
72                                bDirEvent* event,
73                                void* value);
74 
75 /* Pointers to Bareos functions */
76 static CoreFunctions* bareos_core_functions = NULL;
77 
78 
79 #define NOPLUGINSETGETVALUE 1
80 /* functions common to all plugins */
81 #include "plugins/include/python_plugins_common.inc"
82 
83 
84 /* set the bareos_core_functions pointer to the given value */
set_bareos_core_functions(CoreFunctions * new_bareos_core_functions)85 static bRC set_bareos_core_functions(CoreFunctions* new_bareos_core_functions)
86 {
87   bareos_core_functions = new_bareos_core_functions;
88   return bRC_OK;
89 }
90 
91 /* set the plugin context pointer to the given value */
set_plugin_context(PluginContext * new_plugin_context)92 static bRC set_plugin_context(PluginContext* new_plugin_context)
93 {
94   plugin_context = new_plugin_context;
95   return bRC_OK;
96 }
97 
98 
99 /**
100  * Any plugin options which are passed in are dispatched here to a Python
101  * method and it can parse the plugin options. This function is also called
102  * after PyLoadModule() has loaded the Python module and made sure things are
103  * operational.
104  */
PyParsePluginDefinition(PluginContext * plugin_ctx,void * value)105 static bRC PyParsePluginDefinition(PluginContext* plugin_ctx, void* value)
106 {
107   bRC retval = bRC_Error;
108   struct plugin_private_context* plugin_priv_ctx
109       = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
110   PyObject* pFunc;
111 
112   /*
113    * Lookup the parse_plugin_definition() function in the python module.
114    */
115   pFunc = PyDict_GetItemString(
116       plugin_priv_ctx->pyModuleFunctionsDict,
117       "parse_plugin_definition"); /* Borrowed reference */
118   if (pFunc && PyCallable_Check(pFunc)) {
119     PyObject *pPluginDefinition, *pRetVal;
120 
121     pPluginDefinition = PyUnicode_FromString((char*)value);
122     if (!pPluginDefinition) { goto bail_out; }
123 
124     pRetVal = PyObject_CallFunctionObjArgs(pFunc, pPluginDefinition, NULL);
125     Py_DECREF(pPluginDefinition);
126 
127     if (!pRetVal) {
128       goto bail_out;
129     } else {
130       retval = ConvertPythonRetvalTobRCRetval(pRetVal);
131       Py_DECREF(pRetVal);
132     }
133 
134     return retval;
135   } else {
136     Dmsg(plugin_ctx, debuglevel,
137          LOGPREFIX
138          "Failed to find function named "
139          "parse_plugin_definition()\n");
140     return bRC_Error;
141   }
142 
143 bail_out:
144   if (PyErr_Occurred()) { PyErrorHandler(plugin_ctx, M_FATAL); }
145 
146   return retval;
147 }
148 
PyGetPluginValue(PluginContext * plugin_ctx,pVariable var,void * value)149 static bRC PyGetPluginValue(PluginContext* plugin_ctx,
150                             pVariable var,
151                             void* value)
152 {
153   return bRC_OK;
154 }
155 
PySetPluginValue(PluginContext * plugin_ctx,pVariable var,void * value)156 static bRC PySetPluginValue(PluginContext* plugin_ctx,
157                             pVariable var,
158                             void* value)
159 {
160   return bRC_OK;
161 }
162 
PyHandlePluginEvent(PluginContext * plugin_ctx,bDirEvent * event,void * value)163 static bRC PyHandlePluginEvent(PluginContext* plugin_ctx,
164                                bDirEvent* event,
165                                void* value)
166 {
167   bRC retval = bRC_Error;
168   struct plugin_private_context* plugin_priv_ctx
169       = (struct plugin_private_context*)plugin_ctx->plugin_private_context;
170   PyObject* pFunc;
171 
172   /*
173    * Lookup the handle_plugin_event() function in the python module.
174    */
175   pFunc = PyDict_GetItemString(plugin_priv_ctx->pyModuleFunctionsDict,
176                                "handle_plugin_event"); /* Borrowed reference */
177   if (pFunc && PyCallable_Check(pFunc)) {
178     PyObject *pEventType, *pRetVal;
179 
180     pEventType = PyLong_FromLong(event->eventType);
181 
182     pRetVal = PyObject_CallFunctionObjArgs(pFunc, pEventType, NULL);
183     Py_DECREF(pEventType);
184 
185     if (!pRetVal) {
186       goto bail_out;
187     } else {
188       retval = ConvertPythonRetvalTobRCRetval(pRetVal);
189       Py_DECREF(pRetVal);
190     }
191   } else {
192     Dmsg(plugin_ctx, debuglevel,
193          LOGPREFIX "Failed to find function named handle_plugin_event()\n");
194   }
195 
196   return retval;
197 
198 bail_out:
199   if (PyErr_Occurred()) { PyErrorHandler(plugin_ctx, M_FATAL); }
200 
201   return retval;
202 }
203 
204 /**
205  * Callback function which is exposed as a part of the additional methods
206  * which allow a Python plugin to get certain internal values of the current
207  * Job.
208  */
PyBareosGetValue(PyObject * self,PyObject * args)209 static PyObject* PyBareosGetValue(PyObject* self, PyObject* args)
210 {
211   int var;
212   PluginContext* plugin_ctx = plugin_context;
213   PyObject* pRetVal = NULL;
214 
215   if (!PyArg_ParseTuple(args, "i:BareosGetValue", &var)) { return NULL; }
216   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
217 
218   switch (var) {
219     case bDirVarJobId:
220     case bDirVarLevel:
221     case bDirVarType:
222     case bDirVarNumVols:
223     case bDirVarJobStatus:
224     case bDirVarPriority:
225     case bDirVarFDJobStatus:
226     case bDirVarSDJobStatus: {
227       int value = 0;
228 
229       if (bareos_core_functions->getBareosValue(plugin_ctx, (brDirVariable)var,
230                                                 &value)
231           == bRC_OK) {
232         pRetVal = PyLong_FromLong(value);
233       }
234       break;
235     }
236     case bDirVarJobErrors:
237     case bDirVarSDErrors:
238     case bDirVarJobFiles:
239     case bDirVarSDJobFiles:
240     case bDirVarLastRate:
241     case bDirVarJobBytes:
242     case bDirVarReadBytes: {
243       uint64_t value = 0;
244 
245       if (bareos_core_functions->getBareosValue(plugin_ctx, (brDirVariable)var,
246                                                 &value)
247           == bRC_OK) {
248         pRetVal = PyLong_FromUnsignedLong(value);
249       }
250       break;
251     }
252     case bDirVarJobName:
253     case bDirVarJob:
254     case bDirVarClient:
255     case bDirVarPool:
256     case bDirVarStorage:
257     case bDirVarWriteStorage:
258     case bDirVarReadStorage:
259     case bDirVarCatalog:
260     case bDirVarMediaType:
261     case bDirVarVolumeName: {
262       char* value = NULL;
263 
264       if (bareos_core_functions->getBareosValue(plugin_ctx, (brDirVariable)var,
265                                                 &value)
266           == bRC_OK) {
267         if (value) { pRetVal = PyUnicode_FromString(value); }
268       }
269       break;
270     }
271     case bDirVarPluginDir: {
272       char* value = NULL;
273 
274       if (bareos_core_functions->getBareosValue(NULL, (brDirVariable)var,
275                                                 &value)
276           == bRC_OK) {
277         if (value) { pRetVal = PyUnicode_FromString(value); }
278       }
279       break;
280     }
281     default:
282       Dmsg(plugin_ctx, debuglevel,
283            LOGPREFIX "PyBareosGetValue unknown variable requested %d\n", var);
284       break;
285   }
286 
287   if (!pRetVal) { Py_RETURN_NONE; }
288 
289   return pRetVal;
290 }
291 
292 /**
293  * Callback function which is exposed as a part of the additional methods
294  * which allow a Python plugin to set certain internal values of the current
295  * Job.
296  */
PyBareosSetValue(PyObject * self,PyObject * args)297 static PyObject* PyBareosSetValue(PyObject* self, PyObject* args)
298 {
299   int var;
300   PluginContext* plugin_ctx = plugin_context;
301   bRC retval = bRC_Error;
302   PyObject* pyValue;
303 
304   if (!PyArg_ParseTuple(args, "iO:BareosSetValue", &var, &pyValue)) {
305     goto bail_out;
306   }
307   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
308 
309   switch (var) {
310     case bwDirVarVolumeName: {
311       const char* value;
312 
313       value = PyUnicode_AsUTF8(pyValue);
314       if (value) {
315         retval = bareos_core_functions->setBareosValue(
316             plugin_ctx, (bwDirVariable)var,
317             static_cast<void*>(const_cast<char*>(value)));
318       }
319 
320       break;
321     }
322     case bwDirVarPriority:
323     case bwDirVarJobLevel: {
324       int value;
325 
326       value = PyLong_AsLong(pyValue);
327       if (value >= 0) {
328         retval = bareos_core_functions->setBareosValue(
329             plugin_ctx, (bwDirVariable)var, &value);
330       }
331       break;
332     }
333     default:
334       Dmsg(plugin_ctx, debuglevel,
335            LOGPREFIX "PyBareosSetValue unknown variable requested %d\n", var);
336       break;
337   }
338 
339 bail_out:
340   return ConvertbRCRetvalToPythonRetval(retval);
341 }
342 
343 /**
344  * Callback function which is exposed as a part of the additional methods
345  * which allow a Python plugin to issue debug messages using the Bareos debug
346  * message facility.
347  */
PyBareosDebugMessage(PyObject * self,PyObject * args)348 static PyObject* PyBareosDebugMessage(PyObject* self, PyObject* args)
349 {
350   int level;
351   char* dbgmsg = NULL;
352   PluginContext* plugin_ctx = plugin_context;
353 
354   if (!PyArg_ParseTuple(args, "i|z:BareosDebugMessage", &level, &dbgmsg)) {
355     return NULL;
356   }
357   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
358 
359   if (dbgmsg) { Dmsg(plugin_ctx, level, LOGPREFIX "%s", dbgmsg); }
360 
361   Py_RETURN_NONE;
362 }
363 
364 /**
365  * Callback function which is exposed as a part of the additional methods
366  * which allow a Python plugin to issue Job messages using the Bareos Job
367  * message facility.
368  */
PyBareosJobMessage(PyObject * self,PyObject * args)369 static PyObject* PyBareosJobMessage(PyObject* self, PyObject* args)
370 {
371   int type;
372   char* jobmsg = NULL;
373   PluginContext* plugin_ctx = plugin_context;
374 
375   if (!PyArg_ParseTuple(args, "i|z:BareosJobMessage", &type, &jobmsg)) {
376     return NULL;
377   }
378   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
379 
380   if (jobmsg) { Jmsg(plugin_ctx, type, LOGPREFIX "%s", jobmsg); }
381 
382   Py_RETURN_NONE;
383 }
384 
385 /**
386  * Callback function which is exposed as a part of the additional methods
387  * which allow a Python plugin to issue a Register Event to register
388  * additional events it wants to receive.
389  */
PyBareosRegisterEvents(PyObject * self,PyObject * args)390 static PyObject* PyBareosRegisterEvents(PyObject* self, PyObject* args)
391 {
392   int len, event;
393   PluginContext* plugin_ctx = plugin_context;
394   bRC retval = bRC_Error;
395   PyObject *pyEvents, *pySeq, *pyEvent;
396 
397   if (!PyArg_ParseTuple(args, "O:BareosRegisterEvents", &pyEvents)) {
398     goto bail_out;
399   }
400   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
401 
402   pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
403   if (!pySeq) { goto bail_out; }
404 
405   len = PySequence_Fast_GET_SIZE(pySeq);
406 
407   for (int i = 0; i < len; i++) {
408     pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
409     event = PyLong_AsLong(pyEvent);
410 
411     if (event >= bDirEventJobStart && event <= bDirEventGetScratch) {
412       Dmsg(plugin_ctx, debuglevel,
413            LOGPREFIX "PyBareosRegisterEvents registering event %d\n", event);
414       retval
415           = bareos_core_functions->registerBareosEvents(plugin_ctx, 1, event);
416 
417       if (retval != bRC_OK) { break; }
418     }
419   }
420 
421   Py_DECREF(pySeq);
422 
423 bail_out:
424   return ConvertbRCRetvalToPythonRetval(retval);
425 }
426 
427 /**
428  * Callback function which is exposed as a part of the additional methods
429  * which allow a Python plugin to issue an Unregister Event to unregister
430  * events it doesn't want to receive anymore.
431  */
PyBareosUnRegisterEvents(PyObject * self,PyObject * args)432 static PyObject* PyBareosUnRegisterEvents(PyObject* self, PyObject* args)
433 {
434   int len, event;
435   PluginContext* plugin_ctx = plugin_context;
436   bRC retval = bRC_Error;
437   PyObject *pyEvents, *pySeq, *pyEvent;
438 
439   if (!PyArg_ParseTuple(args, "O:BareosUnRegisterEvents", &pyEvents)) {
440     goto bail_out;
441   }
442   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
443 
444   pySeq = PySequence_Fast(pyEvents, "Expected a sequence of events");
445   if (!pySeq) { goto bail_out; }
446 
447   len = PySequence_Fast_GET_SIZE(pySeq);
448 
449   for (int i = 0; i < len; i++) {
450     pyEvent = PySequence_Fast_GET_ITEM(pySeq, i);
451     event = PyLong_AsLong(pyEvent);
452 
453     if (event >= bDirEventJobStart && event <= bDirEventGetScratch) {
454       Dmsg(plugin_ctx, debuglevel,
455            "PyBareosUnRegisterEvents: registering event %d\n", event);
456       retval
457           = bareos_core_functions->unregisterBareosEvents(plugin_ctx, 1, event);
458 
459       if (retval != bRC_OK) { break; }
460     }
461   }
462 
463   Py_DECREF(pySeq);
464 
465 bail_out:
466   return ConvertbRCRetvalToPythonRetval(retval);
467 }
468 
469 /**
470  * Callback function which is exposed as a part of the additional methods
471  * which allow a Python plugin to issue a GetInstanceCount to retrieve the
472  * number of instances of the current plugin being loaded into the daemon.
473  */
PyBareosGetInstanceCount(PyObject * self,PyObject * args)474 static PyObject* PyBareosGetInstanceCount(PyObject* self, PyObject* args)
475 {
476   int value;
477   PluginContext* plugin_ctx = plugin_context;
478   PyObject* pRetVal = NULL;
479 
480   if (!PyArg_ParseTuple(args, ":BareosGetInstanceCount")) { return NULL; }
481   RETURN_RUNTIME_ERROR_IF_BFUNC_OR_BAREOS_PLUGIN_CTX_UNSET()
482 
483   if (bareos_core_functions->getInstanceCount(plugin_ctx, &value) == bRC_OK) {
484     pRetVal = PyLong_FromLong(value);
485   }
486 
487   if (!pRetVal) { Py_RETURN_NONE; }
488 
489   return pRetVal;
490 }
491 
492 } /* namespace directordaemon */
493