1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2016-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 and included
10    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  * Kern Sibbald, October MM
24  */
25 /**
26  * @file
27  * Verify files.
28  */
29 
30 #include "include/bareos.h"
31 #include "filed/filed.h"
32 #include "filed/jcr_private.h"
33 #include "findlib/find.h"
34 #include "findlib/attribs.h"
35 #include "lib/attribs.h"
36 #include "lib/berrno.h"
37 #include "lib/bnet.h"
38 #include "lib/bsock.h"
39 #include "lib/util.h"
40 
41 namespace filedaemon {
42 
43 #ifdef HAVE_DARWIN_OS
44 const bool have_darwin_os = true;
45 #else
46 const bool have_darwin_os = false;
47 #endif
48 
49 static int VerifyFile(JobControlRecord* jcr, FindFilesPacket* ff_pkt, bool);
50 static int ReadDigest(BareosWinFilePacket* bfd,
51                       DIGEST* digest,
52                       JobControlRecord* jcr);
53 static bool calculate_file_chksum(JobControlRecord* jcr,
54                                   FindFilesPacket* ff_pkt,
55                                   DIGEST** digest,
56                                   int* digest_stream,
57                                   char** digest_buf,
58                                   const char** digest_name);
59 
60 /**
61  * Find all the requested files and send attributes
62  * to the Director.
63  *
64  */
DoVerify(JobControlRecord * jcr)65 void DoVerify(JobControlRecord* jcr)
66 {
67   jcr->setJobStatus(JS_Running);
68   jcr->buf_size = DEFAULT_NETWORK_BUFFER_SIZE;
69   if ((jcr->impl->big_buf = (char*)malloc(jcr->buf_size)) == NULL) {
70     Jmsg1(jcr, M_ABORT, 0, _("Cannot malloc %d network read buffer\n"),
71           DEFAULT_NETWORK_BUFFER_SIZE);
72   }
73   SetFindOptions((FindFilesPacket*)jcr->impl->ff, jcr->impl->incremental,
74                  jcr->impl->since_time);
75   Dmsg0(10, "Start find files\n");
76   /* Subroutine VerifyFile() is called for each file */
77   FindFiles(jcr, (FindFilesPacket*)jcr->impl->ff, VerifyFile, NULL);
78   Dmsg0(10, "End find files\n");
79 
80   if (jcr->impl->big_buf) {
81     free(jcr->impl->big_buf);
82     jcr->impl->big_buf = NULL;
83   }
84   jcr->setJobStatus(JS_Terminated);
85 }
86 
87 /**
88  * Called here by find() for each file.
89  *
90  *  Find the file, compute the MD5 or SHA1 and send it back to the Director
91  */
VerifyFile(JobControlRecord * jcr,FindFilesPacket * ff_pkt,bool top_level)92 static int VerifyFile(JobControlRecord* jcr,
93                       FindFilesPacket* ff_pkt,
94                       bool top_level)
95 {
96   PoolMem attribs(PM_NAME), attribsEx(PM_NAME);
97   int status;
98   BareosSocket* dir;
99 
100   if (JobCanceled(jcr)) { return 0; }
101 
102   dir = jcr->dir_bsock;
103   jcr->impl->num_files_examined++; /* bump total file count */
104 
105   switch (ff_pkt->type) {
106     case FT_LNKSAVED: /* Hard linked, file already saved */
107       Dmsg2(30, "FT_LNKSAVED saving: %s => %s\n", ff_pkt->fname, ff_pkt->link);
108       break;
109     case FT_REGE:
110       Dmsg1(30, "FT_REGE saving: %s\n", ff_pkt->fname);
111       break;
112     case FT_REG:
113       Dmsg1(30, "FT_REG saving: %s\n", ff_pkt->fname);
114       break;
115     case FT_LNK:
116       Dmsg2(30, "FT_LNK saving: %s -> %s\n", ff_pkt->fname, ff_pkt->link);
117       break;
118     case FT_DIRBEGIN:
119       jcr->impl->num_files_examined--; /* correct file count */
120       return 1;                        /* ignored */
121     case FT_REPARSE:
122     case FT_JUNCTION:
123     case FT_DIREND:
124       Dmsg1(30, "FT_DIR saving: %s\n", ff_pkt->fname);
125       break;
126     case FT_SPEC:
127       Dmsg1(30, "FT_SPEC saving: %s\n", ff_pkt->fname);
128       break;
129     case FT_RAW:
130       Dmsg1(30, "FT_RAW saving: %s\n", ff_pkt->fname);
131       break;
132     case FT_FIFO:
133       Dmsg1(30, "FT_FIFO saving: %s\n", ff_pkt->fname);
134       break;
135     case FT_NOACCESS: {
136       BErrNo be;
137       be.SetErrno(ff_pkt->ff_errno);
138       Jmsg(jcr, M_NOTSAVED, 1, _("     Could not access %s: ERR=%s\n"),
139            ff_pkt->fname, be.bstrerror());
140       jcr->JobErrors++;
141       return 1;
142     }
143     case FT_NOFOLLOW: {
144       BErrNo be;
145       be.SetErrno(ff_pkt->ff_errno);
146       Jmsg(jcr, M_NOTSAVED, 1, _("     Could not follow link %s: ERR=%s\n"),
147            ff_pkt->fname, be.bstrerror());
148       jcr->JobErrors++;
149       return 1;
150     }
151     case FT_NOSTAT: {
152       BErrNo be;
153       be.SetErrno(ff_pkt->ff_errno);
154       Jmsg(jcr, M_NOTSAVED, 1, _("     Could not stat %s: ERR=%s\n"),
155            ff_pkt->fname, be.bstrerror());
156       jcr->JobErrors++;
157       return 1;
158     }
159     case FT_DIRNOCHG:
160     case FT_NOCHG:
161       Jmsg(jcr, M_SKIPPED, 1, _("     Unchanged file skipped: %s\n"),
162            ff_pkt->fname);
163       return 1;
164     case FT_ISARCH:
165       Jmsg(jcr, M_SKIPPED, 1, _("     Archive file skipped: %s\n"),
166            ff_pkt->fname);
167       return 1;
168     case FT_NORECURSE:
169       Jmsg(jcr, M_SKIPPED, 1,
170            _("     Recursion turned off. Directory skipped: %s\n"),
171            ff_pkt->fname);
172       ff_pkt->type = FT_DIREND; /* directory entry was backed up */
173       break;
174     case FT_NOFSCHG:
175       Jmsg(jcr, M_SKIPPED, 1,
176            _("     File system change prohibited. Directory skipped: %s\n"),
177            ff_pkt->fname);
178       return 1;
179     case FT_PLUGIN_CONFIG:
180     case FT_RESTORE_FIRST:
181       return 1; /* silently skip */
182     case FT_NOOPEN: {
183       BErrNo be;
184       be.SetErrno(ff_pkt->ff_errno);
185       Jmsg(jcr, M_NOTSAVED, 1, _("     Could not open directory %s: ERR=%s\n"),
186            ff_pkt->fname, be.bstrerror());
187       jcr->JobErrors++;
188       return 1;
189     }
190     default:
191       Jmsg(jcr, M_NOTSAVED, 0, _("     Unknown file type %d: %s\n"),
192            ff_pkt->type, ff_pkt->fname);
193       jcr->JobErrors++;
194       return 1;
195   }
196 
197   /* Encode attributes and possibly extend them */
198   EncodeStat(attribs.c_str(), &ff_pkt->statp, sizeof(ff_pkt->statp),
199              ff_pkt->LinkFI, 0);
200   encode_attribsEx(jcr, attribsEx.c_str(), ff_pkt);
201 
202   jcr->lock();
203   jcr->JobFiles++; /* increment number of files sent */
204   PmStrcpy(jcr->impl->last_fname, ff_pkt->fname);
205   jcr->unlock();
206 
207   /*
208    * Send file attributes to Director
209    *   File_index
210    *   Stream
211    *   Verify Options
212    *   Filename (full path)
213    *   Encoded attributes
214    *   Link name (if type==FT_LNK)
215    * For a directory, link is the same as fname, but with trailing
216    * slash. For a linked file, link is the link.
217    */
218   /*
219    * Send file attributes to Director (note different format than for Storage)
220    */
221   Dmsg2(400, "send Attributes inx=%d fname=%s\n", jcr->JobFiles, ff_pkt->fname);
222   if (ff_pkt->type == FT_LNK || ff_pkt->type == FT_LNKSAVED) {
223     status = dir->fsend("%d %d %s %s%c%s%c%s%c", jcr->JobFiles,
224                         STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts,
225                         ff_pkt->fname, 0, attribs.c_str(), 0, ff_pkt->link, 0);
226   } else if (ff_pkt->type == FT_DIREND || ff_pkt->type == FT_REPARSE
227              || ff_pkt->type == FT_JUNCTION) {
228     /*
229      * Here link is the canonical filename (i.e. with trailing slash)
230      */
231     status = dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles,
232                         STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts,
233                         ff_pkt->link, 0, attribs.c_str(), 0, 0);
234   } else {
235     status = dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles,
236                         STREAM_UNIX_ATTRIBUTES, ff_pkt->VerifyOpts,
237                         ff_pkt->fname, 0, attribs.c_str(), 0, 0);
238   }
239   Dmsg2(20, "filed>dir: attribs len=%d: msg=%s\n", dir->message_length,
240         dir->msg);
241   if (!status) {
242     Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"),
243          BnetStrerror(dir));
244     return 0;
245   }
246 
247   if (ff_pkt->type != FT_LNKSAVED && S_ISREG(ff_pkt->statp.st_mode)
248       && (BitIsSet(FO_MD5, ff_pkt->flags) || BitIsSet(FO_SHA1, ff_pkt->flags)
249           || BitIsSet(FO_SHA256, ff_pkt->flags)
250           || BitIsSet(FO_SHA512, ff_pkt->flags))) {
251     int digest_stream = STREAM_NONE;
252     DIGEST* digest = NULL;
253     char* digest_buf = NULL;
254     const char* digest_name = NULL;
255 
256     if (calculate_file_chksum(jcr, ff_pkt, &digest, &digest_stream, &digest_buf,
257                               &digest_name)) {
258       /*
259        * Did digest initialization fail?
260        */
261       if (digest_stream != STREAM_NONE && digest == NULL) {
262         Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
263              stream_to_ascii(digest_stream));
264       } else if (digest && digest_buf) {
265         Dmsg3(400, "send inx=%d %s=%s\n", jcr->JobFiles, digest_name,
266               digest_buf);
267         dir->fsend("%d %d %s *%s-%d*", jcr->JobFiles, digest_stream, digest_buf,
268                    digest_name, jcr->JobFiles);
269         Dmsg3(20, "filed>dir: %s len=%d: msg=%s\n", digest_name,
270               dir->message_length, dir->msg);
271       }
272     }
273 
274     /*
275      * Cleanup.
276      */
277     if (digest_buf) { free(digest_buf); }
278 
279     if (digest) { CryptoDigestFree(digest); }
280   }
281 
282   return 1;
283 }
284 
285 /**
286  * Compute message digest for the file specified by ff_pkt.
287  * In case of errors we need the job control record and file name.
288  */
DigestFile(JobControlRecord * jcr,FindFilesPacket * ff_pkt,DIGEST * digest)289 int DigestFile(JobControlRecord* jcr, FindFilesPacket* ff_pkt, DIGEST* digest)
290 {
291   BareosWinFilePacket bfd;
292 
293   binit(&bfd);
294 
295   int noatime = BitIsSet(FO_NOATIME, ff_pkt->flags) ? O_NOATIME : 0;
296 
297   if ((bopen(&bfd, ff_pkt->fname, O_RDONLY | O_BINARY | noatime, 0,
298              ff_pkt->statp.st_rdev))
299       < 0) {
300     ff_pkt->ff_errno = errno;
301     BErrNo be;
302     be.SetErrno(bfd.BErrNo);
303     Dmsg2(100, "Cannot open %s: ERR=%s\n", ff_pkt->fname, be.bstrerror());
304     Jmsg(jcr, M_ERROR, 1, _("     Cannot open %s: ERR=%s.\n"), ff_pkt->fname,
305          be.bstrerror());
306     return 1;
307   }
308   ReadDigest(&bfd, digest, jcr);
309   bclose(&bfd);
310 
311   if (have_darwin_os) {
312     /*
313      * Open resource fork if necessary
314      */
315     if (BitIsSet(FO_HFSPLUS, ff_pkt->flags) && ff_pkt->hfsinfo.rsrclength > 0) {
316       if (BopenRsrc(&bfd, ff_pkt->fname, O_RDONLY | O_BINARY, 0) < 0) {
317         ff_pkt->ff_errno = errno;
318         BErrNo be;
319         Jmsg(jcr, M_ERROR, -1,
320              _("     Cannot open resource fork for %s: ERR=%s.\n"),
321              ff_pkt->fname, be.bstrerror());
322         if (IsBopen(&ff_pkt->bfd)) { bclose(&ff_pkt->bfd); }
323         return 1;
324       }
325       ReadDigest(&bfd, digest, jcr);
326       bclose(&bfd);
327     }
328 
329     if (digest && BitIsSet(FO_HFSPLUS, ff_pkt->flags)) {
330       CryptoDigestUpdate(digest, (uint8_t*)ff_pkt->hfsinfo.fndrinfo, 32);
331     }
332   }
333   return 0;
334 }
335 
336 /**
337  * Read message digest of bfd, updating digest
338  * In case of errors we need the job control record and file name.
339  */
ReadDigest(BareosWinFilePacket * bfd,DIGEST * digest,JobControlRecord * jcr)340 static int ReadDigest(BareosWinFilePacket* bfd,
341                       DIGEST* digest,
342                       JobControlRecord* jcr)
343 {
344   char buf[DEFAULT_NETWORK_BUFFER_SIZE];
345   int64_t n;
346   int64_t bufsiz = (int64_t)sizeof(buf);
347   FindFilesPacket* ff_pkt = (FindFilesPacket*)jcr->impl->ff;
348   uint64_t fileAddr = 0; /* file address */
349 
350 
351   Dmsg0(50, "=== ReadDigest\n");
352   while ((n = bread(bfd, buf, bufsiz)) > 0) {
353     /* Check for sparse blocks */
354     if (BitIsSet(FO_SPARSE, ff_pkt->flags)) {
355       bool allZeros = false;
356       if ((n == bufsiz && fileAddr + n < (uint64_t)ff_pkt->statp.st_size)
357           || ((ff_pkt->type == FT_RAW || ff_pkt->type == FT_FIFO)
358               && (uint64_t)ff_pkt->statp.st_size == 0)) {
359         allZeros = IsBufZero(buf, bufsiz);
360       }
361       fileAddr += n; /* update file address */
362       /* Skip any block of all zeros */
363       if (allZeros) { continue; /* skip block of zeros */ }
364     }
365 
366     CryptoDigestUpdate(digest, (uint8_t*)buf, n);
367 
368     /* Can be used by BaseJobs or with accurate, update only for Verify
369      * jobs
370      */
371     if (jcr->is_JobType(JT_VERIFY)) { jcr->JobBytes += n; }
372     jcr->ReadBytes += n;
373   }
374   if (n < 0) {
375     BErrNo be;
376     be.SetErrno(bfd->BErrNo);
377     Dmsg2(100, "Error reading file %s: ERR=%s\n", jcr->impl->last_fname,
378           be.bstrerror());
379     Jmsg(jcr, M_ERROR, 1, _("Error reading file %s: ERR=%s\n"),
380          jcr->impl->last_fname, be.bstrerror());
381     jcr->JobErrors++;
382     return -1;
383   }
384   return 0;
385 }
386 
387 /**
388  * Calculate the chksum of a whole file and updates:
389  * - digest
390  * - digest_stream
391  * - digest_buffer
392  * - digest_name
393  *
394  * Returns: true   if digest calculation succeeded.
395  *          false  if digest calculation failed.
396  */
calculate_file_chksum(JobControlRecord * jcr,FindFilesPacket * ff_pkt,DIGEST ** digest,int * digest_stream,char ** digest_buf,const char ** digest_name)397 static bool calculate_file_chksum(JobControlRecord* jcr,
398                                   FindFilesPacket* ff_pkt,
399                                   DIGEST** digest,
400                                   int* digest_stream,
401                                   char** digest_buf,
402                                   const char** digest_name)
403 {
404   /*
405    * Create our digest context.
406    * If this fails, the digest will be set to NULL and not used.
407    */
408   if (BitIsSet(FO_MD5, ff_pkt->flags)) {
409     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
410     *digest_stream = STREAM_MD5_DIGEST;
411   } else if (BitIsSet(FO_SHA1, ff_pkt->flags)) {
412     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
413     *digest_stream = STREAM_SHA1_DIGEST;
414   } else if (BitIsSet(FO_SHA256, ff_pkt->flags)) {
415     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
416     *digest_stream = STREAM_SHA256_DIGEST;
417   } else if (BitIsSet(FO_SHA512, ff_pkt->flags)) {
418     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
419     *digest_stream = STREAM_SHA512_DIGEST;
420   }
421 
422   /*
423    * compute MD5 or SHA1 hash
424    */
425   if (*digest) {
426     uint32_t size;
427     char md[CRYPTO_DIGEST_MAX_SIZE];
428 
429     size = sizeof(md);
430     if (DigestFile(jcr, ff_pkt, *digest) != 0) {
431       jcr->JobErrors++;
432       return false;
433     }
434 
435     if (CryptoDigestFinalize(*digest, (uint8_t*)md, &size)) {
436       *digest_buf = (char*)malloc(BASE64_SIZE(size));
437       *digest_name = crypto_digest_name(*digest);
438 
439       BinToBase64(*digest_buf, BASE64_SIZE(size), md, size, true);
440     }
441   }
442 
443   return true;
444 }
445 
446 /**
447  * Compare a files chksum against a stored chksum.
448  *
449  * Returns: true   if chksum matches.
450  *          false  if chksum is different.
451  */
CalculateAndCompareFileChksum(JobControlRecord * jcr,FindFilesPacket * ff_pkt,const char * fname,const char * chksum)452 bool CalculateAndCompareFileChksum(JobControlRecord* jcr,
453                                    FindFilesPacket* ff_pkt,
454                                    const char* fname,
455                                    const char* chksum)
456 {
457   DIGEST* digest = NULL;
458   int digest_stream = STREAM_NONE;
459   char* digest_buf = NULL;
460   const char* digest_name;
461   bool retval = false;
462 
463   if (calculate_file_chksum(jcr, ff_pkt, &digest, &digest_stream, &digest_buf,
464                             &digest_name)) {
465     if (digest_stream != STREAM_NONE && digest == NULL) {
466       Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
467            stream_to_ascii(digest_stream));
468     } else if (digest && digest_buf) {
469       if (!bstrcmp(digest_buf, chksum)) {
470         Dmsg4(100, "%s      %s chksum  diff. Cat: %s File: %s\n", fname,
471               digest_name, chksum, digest_buf);
472       } else {
473         retval = true;
474       }
475     }
476 
477     if (digest_buf) { free(digest_buf); }
478 
479     if (digest) { CryptoDigestFree(digest); }
480   }
481 
482   return retval;
483 }
484 } /* namespace filedaemon */
485