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