1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3# BAREOS - Backup Archiving REcovery Open Sourced
4#
5# Copyright (C) 2014-2020 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# Author: Maik Aussendorf
23#
24# Bareos python plugins class that adds files from a local list to
25# the backup fileset
26
27
28import bareosfd
29from bareosfd import *
30import os
31import re
32import hashlib
33import time
34import BareosFdPluginBaseclass
35
36
37class BareosFdPluginLocalFilesetWithRestoreObjects(
38    BareosFdPluginBaseclass.BareosFdPluginBaseclass
39):
40    """
41    This Bareos-FD-Plugin-Class was created for automated test purposes only.
42    It is based on the BareosFdPluginLocalFileset class that parses a file
43    and backups all files listed there.
44    Filename is taken from plugin argument 'filename'.
45    In addition to the file backup and restore, this plugin also tests
46    restore objects of different sizes. As restore objects are compressed
47    automatically, when they are greater then 1000 bytes, both uncompressed
48    and compressed restore objects are tested.
49    """
50
51    def __init__(self, plugindef):
52        bareosfd.DebugMessage(
53            100,
54            "Constructor called in module %s with plugindef=%s\n"
55            % (__name__, plugindef),
56        )
57        # Last argument of super constructor is a list of mandatory arguments
58        super(BareosFdPluginLocalFilesetWithRestoreObjects, self).__init__(
59            plugindef, ["filename"]
60        )
61        self.files_to_backup = []
62        self.allow = None
63        self.deny = None
64        self.object_index_seq = int((time.time() - 1546297200) * 10)
65        self.sha256sums_by_filename = {}
66        # bareosfd.SetValue(bVarSinceTime,999)
67        # triple_nine = bareosfd.GetValue(bVarSinceTime)
68        # if triple_nine != 999:
69        #     bareosfd.JobMessage(
70        #         M_ERROR,
71        #         "Wrong value for since time (should be 999 but is %d)\n" % triple_nine
72        #     )
73
74    def filename_is_allowed(self, filename, allowregex, denyregex):
75        """
76        Check, if filename is allowed.
77        True, if matches allowreg and not denyregex.
78        If allowreg is None, filename always matches
79        If denyreg is None, it never matches
80        """
81        if allowregex is None or allowregex.search(filename):
82            allowed = True
83        else:
84            allowed = False
85        if denyregex is None or not denyregex.search(filename):
86            denied = False
87        else:
88            denied = True
89        if not allowed or denied:
90            bareosfd.DebugMessage(100, "File %s denied by configuration\n" % (filename))
91            bareosfd.JobMessage(
92                M_ERROR,
93                "File %s denied by configuration\n" % (filename),
94            )
95            return False
96        else:
97            return True
98
99    def start_backup_job(self):
100        """
101        At this point, plugin options were passed and checked already.
102        We try to read from filename and setup the list of file to backup
103        in self.files_to_backup
104        """
105
106        bareosfd.DebugMessage(
107            100,
108            "Using %s to search for local files\n" % (self.options["filename"]),
109        )
110        if os.path.exists(self.options["filename"]):
111            try:
112                config_file = open(self.options["filename"], "r")
113                # config_file = open(self.options["filename"], "rb")
114            except:
115                bareosfd.DebugMessage(
116                    100,
117                    "Could not open file %s\n" % (self.options["filename"]),
118                )
119                return bRC_Error
120        else:
121            bareosfd.DebugMessage(
122                100, "File %s does not exist\n" % (self.options["filename"])
123            )
124            return bRC_Error
125        # Check, if we have allow or deny regular expressions defined
126        if "allow" in self.options:
127            self.allow = re.compile(self.options["allow"])
128        if "deny" in self.options:
129            self.deny = re.compile(self.options["deny"])
130
131        for listItem in config_file.read().splitlines():
132            if os.path.isfile(listItem) and self.filename_is_allowed(
133                listItem, self.allow, self.deny
134            ):
135                self.files_to_backup.append(listItem)
136            if os.path.isdir(listItem):
137                for topdir, dirNames, fileNames in os.walk(listItem):
138                    for fileName in fileNames:
139                        if self.filename_is_allowed(
140                            os.path.join(topdir, fileName),
141                            self.allow,
142                            self.deny,
143                        ):
144                            self.files_to_backup.append(os.path.join(topdir, fileName))
145                            if os.path.isfile(os.path.join(topdir, fileName)):
146                                self.files_to_backup.append(
147                                    os.path.join(topdir, fileName) + ".sha256sum"
148                                )
149                                self.files_to_backup.append(
150                                    os.path.join(topdir, fileName) + ".abspath"
151                                )
152            else:
153                if os.path.isfile(listItem):
154                    self.files_to_backup.append(listItem + ".sha256sum")
155                    self.files_to_backup.append(listItem + ".abspath")
156
157        for longrestoreobject_length in range(998, 1004):
158            self.files_to_backup.append(
159                "%s.longrestoreobject" % longrestoreobject_length
160            )
161
162        if not self.files_to_backup:
163            bareosfd.JobMessage(
164                M_ERROR,
165                "No (allowed) files to backup found\n",
166            )
167            return bRC_Error
168        else:
169            return bRC_Cancel
170
171    def start_backup_file(self, savepkt):
172        """
173        Defines the file to backup and creates the savepkt. In this example
174        only files (no directories) are allowed
175        """
176        bareosfd.DebugMessage(100, "start_backup_file() called\n")
177        if not self.files_to_backup:
178            bareosfd.DebugMessage(100, "No files to backup\n")
179            return bRC_Skip
180
181        file_to_backup = self.files_to_backup.pop()
182
183        statp = bareosfd.StatPacket()
184        savepkt.statp = statp
185
186        if file_to_backup.endswith(".sha256sum"):
187            checksum = self.get_sha256sum(os.path.splitext(file_to_backup)[0])
188            savepkt.type = FT_RESTORE_FIRST
189            savepkt.fname = file_to_backup
190            savepkt.object_name = file_to_backup
191            savepkt.object = bytearray(checksum.encode("utf-8"))
192            savepkt.object_len = len(savepkt.object)
193            savepkt.object_index = self.object_index_seq
194            self.object_index_seq += 1
195
196        elif file_to_backup.endswith(".abspath"):
197            savepkt.type = FT_RESTORE_FIRST
198            savepkt.fname = file_to_backup
199            savepkt.object_name = file_to_backup
200            savepkt.object = bytearray(
201                os.path.splitext(file_to_backup)[0].encode("utf-8")
202            )
203
204            savepkt.object_len = len(savepkt.object)
205            savepkt.object_index = self.object_index_seq
206            self.object_index_seq += 1
207
208        elif file_to_backup.endswith(".longrestoreobject"):
209            teststring_length = int(os.path.splitext(file_to_backup)[0])
210            savepkt.type = FT_RESTORE_FIRST
211            savepkt.fname = file_to_backup
212            savepkt.object_name = file_to_backup
213            savepkt.object = bytearray(b"a" * teststring_length)
214            savepkt.object_len = len(savepkt.object)
215            savepkt.object_index = self.object_index_seq
216            self.object_index_seq += 1
217
218        else:
219            savepkt.fname = file_to_backup
220            savepkt.type = FT_REG
221
222        bareosfd.JobMessage(
223            M_INFO,
224            "Starting backup of {}\n".format(file_to_backup),
225        )
226        return bRC_OK
227
228    def end_backup_file(self):
229        """
230        Here we return 'bRC_More' as long as our list files_to_backup is not
231        empty and bRC_OK when we are done
232        """
233        bareosfd.DebugMessage(100, "end_backup_file() entry point in Python called\n")
234        if self.files_to_backup:
235            return bRC_More
236        else:
237            return bRC_OK
238
239    def set_file_attributes(self, restorepkt):
240        bareosfd.DebugMessage(
241            100,
242            "set_file_attributes() entry point in Python called with %s\n"
243            % (str(restorepkt)),
244        )
245
246        orig_fname = "/" + os.path.relpath(restorepkt.ofname, restorepkt.where)
247        bareosfd.DebugMessage(
248            100, "set_file_attributes() orig_fname: {} \n".format(orig_fname)
249        )
250        restoreobject_sha256sum = self.sha256sums_by_filename[orig_fname]
251
252        file_sha256sum = self.get_sha256sum(orig_fname).encode("utf-8")
253        bareosfd.DebugMessage(
254            100,
255            "set_file_attributes() orig_fname: %s restoreobject_sha256sum: %s file_sha256sum: %s\n"
256            % (orig_fname, repr(restoreobject_sha256sum), repr(file_sha256sum)),
257        )
258        if file_sha256sum != restoreobject_sha256sum:
259            bareosfd.JobMessage(
260                M_ERROR,
261                "bad restoreobject orig_fname: %s restoreobject_sha256sum: %s file_sha256sum: %s\n"
262                % (orig_fname, repr(restoreobject_sha256sum), repr(file_sha256sum)),
263            )
264
265        return bRC_OK
266
267    def end_restore_file(self):
268        bareosfd.DebugMessage(100, "end_restore_file() self.FNAME: %s\n" % self.FNAME)
269        return bRC_OK
270
271    def restore_object_data(self, ROP):
272        """
273        Note:
274        This is called in two cases:
275        - on diff/inc backup (should be called only once)
276        - on restore (for every job id being restored)
277        But at the point in time called, it is not possible
278        to distinguish which of them it is, because job type
279        is "I" until the bEventStartBackupJob event
280        """
281        bareosfd.DebugMessage(
282            100,
283            "BareosFdPluginLocalFilesetWithRestoreObjects:restore_object_data() called with ROP:%s\n"
284            % (ROP),
285        )
286        bareosfd.DebugMessage(
287            100,
288            "ROP.object_name(%s): %s\n" % (type(ROP.object_name), ROP.object_name),
289        )
290        bareosfd.DebugMessage(
291            100,
292            "ROP.plugin_name(%s): %s\n" % (type(ROP.plugin_name), ROP.plugin_name),
293        )
294        bareosfd.DebugMessage(
295            100,
296            "ROP.object_len(%s): %s\n" % (type(ROP.object_len), ROP.object_len),
297        )
298        bareosfd.DebugMessage(
299            100,
300            "ROP.object_full_len(%s): %s\n"
301            % (type(ROP.object_full_len), ROP.object_full_len),
302        )
303        bareosfd.DebugMessage(
304            100, "ROP.object(%s): %s\n" % (type(ROP.object), repr(ROP.object))
305        )
306        orig_filename = os.path.splitext(ROP.object_name)[0]
307        if ROP.object_name.endswith(".sha256sum"):
308            self.sha256sums_by_filename[orig_filename] = ROP.object
309            # self.sha256sums_by_filename[orig_filename] = str(ROP.object)
310        elif ROP.object_name.endswith(".abspath"):
311            if ROP.object.decode() != orig_filename:
312                bareosfd.JobMessage(
313                    M_ERROR,
314                    "bad restoreobject orig_fname: %s restoreobject_fname: %s\n"
315                    % (orig_filename, ROP.object.decode()),
316                )
317        elif ROP.object_name.endswith(".longrestoreobject"):
318            stored_length = int(os.path.splitext(ROP.object_name)[0])
319            if ROP.object.decode() != "a" * stored_length:
320                bareosfd.JobMessage(
321                    M_ERROR,
322                    "bad long restoreobject {} does not match stored object: {}, {}\n".format(
323                        ROP.object_name, ROP.object.decode(), "a" * stored_length
324                    ),
325                )
326        else:
327            bareosfd.DebugMessage(
328                100,
329                "not checking restoreobject: %s\n" % (type(ROP.object_name)),
330            )
331        return bRC_OK
332
333    def get_sha256sum(self, filename):
334        f = open(filename, "rb")
335        m = hashlib.sha256()
336        while True:
337            d = f.read(8096)
338            if not d:
339                break
340            m.update(d)
341        f.close()
342        return m.hexdigest()
343
344
345# vim: ts=4 tabstop=4 expandtab shiftwidth=4 softtabstop=4
346