"Fossies" - the Fresh Open Source Software Archive

Member "Tardis-1.2.1/src/Tardis/Regenerator.py" (9 Jun 2021, 8065 Bytes) of package /linux/privat/Tardis-1.2.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "Regenerator.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.1.5_vs_1.2.1.

    1 # vim: set et sw=4 sts=4 fileencoding=utf-8:
    2 #
    3 # Tardis: A Backup System
    4 # Copyright 2013-2020, Eric Koldinger, All Rights Reserved.
    5 # kolding@washington.edu
    6 #
    7 # Redistribution and use in source and binary forms, with or without
    8 # modification, are permitted provided that the following conditions are met:
    9 #
   10 #     * Redistributions of source code must retain the above copyright
   11 #       notice, this list of conditions and the following disclaimer.
   12 #     * Redistributions in binary form must reproduce the above copyright
   13 #       notice, this list of conditions and the following disclaimer in the
   14 #       documentation and/or other materials provided with the distribution.
   15 #     * Neither the name of the copyright holder nor the
   16 #       names of its contributors may be used to endorse or promote products
   17 #       derived from this software without specific prior written permission.
   18 #
   19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   20 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   21 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   22 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
   23 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   24 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   25 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   26 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   27 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   28 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   29 # POSSIBILITY OF SUCH DAMAGE.
   30 
   31 import os
   32 import binascii
   33 import logging
   34 import tempfile
   35 import shutil
   36 import hashlib
   37 
   38 import Tardis.CompressedBuffer as CompressedBuffer
   39 
   40 import Tardis.librsync as librsync
   41 
   42 
   43 class RegenerateException(Exception):
   44     pass
   45 
   46 class Regenerator:
   47     errors = 0
   48 
   49     def __init__(self, cache, db, crypt=None, tempdir="/tmp"):
   50         self.logger = logging.getLogger("Regenerator")
   51         self.cacheDir = cache
   52         self.db = db
   53         self.tempdir = tempdir
   54         self.crypt = crypt
   55 
   56     def decryptFile(self, filename, size, authenticate=True):
   57         self.logger.debug("Decrypting %s", filename)
   58         infile = self.cacheDir.open(filename, 'rb')
   59 
   60         # Get the IV, if it's not specified.
   61         #infile.seek(0, os.SEEK_SET)
   62         iv = infile.read(self.crypt.ivLength)
   63 
   64         self.logger.debug("Got IV: %d %s", len(iv), binascii.hexlify(iv))
   65 
   66         # Create the cipher
   67         encryptor = self.crypt.getContentEncryptor(iv)
   68 
   69         outfile = tempfile.TemporaryFile()
   70 
   71         contentSize = size - self.crypt.ivLength - encryptor.getDigestSize()
   72         #self.logger.info("Computed Size: %d.  Specified size: %d.  Diff: %d", ctSize, size, (ctSize - size))
   73 
   74         rem = contentSize
   75         blocksize = 64 * 1024
   76         last = False
   77         while rem > 0:
   78             readsize = blocksize if rem > blocksize else rem
   79             if rem <= blocksize:
   80                 last = True
   81             ct = infile.read(readsize)
   82             pt = encryptor.decrypt(ct, last)
   83             if last:
   84                 # ie, we're the last block
   85                 digest = infile.read(encryptor.getDigestSize())
   86                 self.logger.debug("Got HMAC Digest: %d %s", len(digest), binascii.hexlify(digest))
   87                 readsize += len(digest)
   88                 if authenticate:
   89                     try:
   90                         encryptor.verify(digest)
   91                     except:
   92                         self.logger.debug("HMAC's:  File: %-128s Computed: %-128s", binascii.hexlify(digest), binascii.hexlify(encryptor.digest()))
   93                         raise RegenerateException("HMAC did not authenticate.")
   94             outfile.write(pt)
   95             rem -= readsize
   96 
   97         outfile.seek(0)
   98         return outfile
   99 
  100     def recoverChecksum(self, cksum, authenticate=True, chain=None, basisFile=None):
  101         self.logger.debug("Recovering checksum: %s", cksum)
  102         cksInfo = None
  103         if not chain:
  104             chain = self.db.getChecksumInfoChain(cksum)
  105 
  106         if chain:
  107             cksInfo = chain.pop(0)
  108             if cksInfo['checksum'] != cksum:
  109                 self.logger.error("Unexpected checksum: %s.  Expected: %s", cksInfo['checksum'], cksum)
  110                 return None
  111         else:
  112             cksInfo = self.db.getChecksumInfo(cksum)
  113 
  114         if cksInfo is None:
  115             self.logger.error("Checksum %s not found", cksum)
  116             return None
  117 
  118         #self.logger.debug(" %s: %s", cksum, str(cksInfo))
  119 
  120         try:
  121             if not cksInfo['isfile']:
  122                 raise RegenerateException("{} is not a file".format(cksum))
  123 
  124             if cksInfo['basis']:
  125                 if basisFile:
  126                     basis = basisFile
  127                     basis.seek(0)
  128                 else:
  129                     basis = self.recoverChecksum(cksInfo['basis'], authenticate, chain)
  130 
  131                 if cksInfo['encrypted']:
  132                     patchfile = self.decryptFile(cksum, cksInfo['disksize'], authenticate)
  133                 else:
  134                     patchfile = self.cacheDir.open(cksum, 'rb')
  135 
  136                 if cksInfo['compressed']:
  137                     self.logger.debug("Uncompressing %s", cksum)
  138                     temp = tempfile.TemporaryFile()
  139                     buf = CompressedBuffer.UncompressedBufferedReader(patchfile, compressor=cksInfo['compressed'])
  140                     shutil.copyfileobj(buf, temp)
  141                     temp.seek(0)
  142                     patchfile = temp
  143                 try:
  144                     output = librsync.patch(basis, patchfile)
  145                     #output.seek(0)
  146                     return output
  147                 except librsync.LibrsyncError as e:
  148                     self.logger.error("Recovering checksum: %s : %s", cksum, e)
  149                     raise RegenerateException("Checksum: {}: Error: {}".format(cksum, e))
  150             else:
  151                 if cksInfo['encrypted']:
  152                     output =  self.decryptFile(cksum, cksInfo['disksize'])
  153                 else:
  154                     output =  self.cacheDir.open(cksum, "rb")
  155 
  156                 if cksInfo['compressed'] is not None and cksInfo['compressed'].lower() != 'none':
  157                     self.logger.debug("Uncompressing %s", cksum)
  158                     temp = tempfile.TemporaryFile()
  159                     buf = CompressedBuffer.UncompressedBufferedReader(output, compressor=cksInfo['compressed'])
  160                     shutil.copyfileobj(buf, temp)
  161                     temp.seek(0)
  162                     output = temp
  163 
  164                 return output
  165 
  166         except RegenerateException:
  167             raise
  168         except Exception as e:
  169             self.logger.error("Unable to recover checksum %s: %s", cksum, e)
  170             #self.logger.exception(e)
  171             raise RegenerateException("Checksum: {}: Error: {}".format(cksum, e))
  172 
  173     def recoverFile(self, filename, bset=False, nameEncrypted=False, permchecker=None, authenticate=True):
  174         self.logger.info("Recovering file: %s", filename)
  175         name = filename
  176         if self.crypt and not nameEncrypted:
  177             name = self.crypt.encryptPath(filename)
  178         try:
  179             chain = self.db.getChecksumInfoChainByPath(name, bset, permchecker=permchecker)
  180             if chain:
  181                 cksum = chain[0]['checksum']
  182                 return self.recoverChecksum(cksum, authenticate, chain)
  183             else:
  184                 self.logger.error("Could not locate file: %s ", name)
  185                 return None
  186         except RegenerateException as e:
  187             self.logger.error("Could not regenerate file: %s: %s", filename, str(e))
  188             #self.logger.exception(e)
  189             return None
  190         except Exception as e:
  191             #logger.exception(e)
  192             self.logger.error("Error recovering file: %s: %s", filename, str(e))
  193             self.errors += 1
  194             return None
  195             #raise RegenerateException("Error recovering file: {}".format(filename))