1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2016-2016 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->mtime);
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 =
232         dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles, STREAM_UNIX_ATTRIBUTES,
233                    ff_pkt->VerifyOpts, ff_pkt->link, 0, attribs.c_str(), 0, 0);
234   } else {
235     status =
236         dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles, STREAM_UNIX_ATTRIBUTES,
237                    ff_pkt->VerifyOpts, 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)) < 0) {
299     ff_pkt->ff_errno = errno;
300     BErrNo be;
301     be.SetErrno(bfd.BErrNo);
302     Dmsg2(100, "Cannot open %s: ERR=%s\n", ff_pkt->fname, be.bstrerror());
303     Jmsg(jcr, M_ERROR, 1, _("     Cannot open %s: ERR=%s.\n"), ff_pkt->fname,
304          be.bstrerror());
305     return 1;
306   }
307   ReadDigest(&bfd, digest, jcr);
308   bclose(&bfd);
309 
310   if (have_darwin_os) {
311     /*
312      * Open resource fork if necessary
313      */
314     if (BitIsSet(FO_HFSPLUS, ff_pkt->flags) && ff_pkt->hfsinfo.rsrclength > 0) {
315       if (BopenRsrc(&bfd, ff_pkt->fname, O_RDONLY | O_BINARY, 0) < 0) {
316         ff_pkt->ff_errno = errno;
317         BErrNo be;
318         Jmsg(jcr, M_ERROR, -1,
319              _("     Cannot open resource fork for %s: ERR=%s.\n"),
320              ff_pkt->fname, be.bstrerror());
321         if (IsBopen(&ff_pkt->bfd)) { bclose(&ff_pkt->bfd); }
322         return 1;
323       }
324       ReadDigest(&bfd, digest, jcr);
325       bclose(&bfd);
326     }
327 
328     if (digest && BitIsSet(FO_HFSPLUS, ff_pkt->flags)) {
329       CryptoDigestUpdate(digest, (uint8_t*)ff_pkt->hfsinfo.fndrinfo, 32);
330     }
331   }
332   return 0;
333 }
334 
335 /**
336  * Read message digest of bfd, updating digest
337  * In case of errors we need the job control record and file name.
338  */
ReadDigest(BareosWinFilePacket * bfd,DIGEST * digest,JobControlRecord * jcr)339 static int ReadDigest(BareosWinFilePacket* bfd,
340                       DIGEST* digest,
341                       JobControlRecord* jcr)
342 {
343   char buf[DEFAULT_NETWORK_BUFFER_SIZE];
344   int64_t n;
345   int64_t bufsiz = (int64_t)sizeof(buf);
346   FindFilesPacket* ff_pkt = (FindFilesPacket*)jcr->impl->ff;
347   uint64_t fileAddr = 0; /* file address */
348 
349 
350   Dmsg0(50, "=== ReadDigest\n");
351   while ((n = bread(bfd, buf, bufsiz)) > 0) {
352     /* Check for sparse blocks */
353     if (BitIsSet(FO_SPARSE, ff_pkt->flags)) {
354       bool allZeros = false;
355       if ((n == bufsiz && fileAddr + n < (uint64_t)ff_pkt->statp.st_size) ||
356           ((ff_pkt->type == FT_RAW || ff_pkt->type == FT_FIFO) &&
357            (uint64_t)ff_pkt->statp.st_size == 0)) {
358         allZeros = IsBufZero(buf, bufsiz);
359       }
360       fileAddr += n; /* update file address */
361       /* Skip any block of all zeros */
362       if (allZeros) { continue; /* skip block of zeros */ }
363     }
364 
365     CryptoDigestUpdate(digest, (uint8_t*)buf, n);
366 
367     /* Can be used by BaseJobs or with accurate, update only for Verify
368      * jobs
369      */
370     if (jcr->is_JobType(JT_VERIFY)) { jcr->JobBytes += n; }
371     jcr->ReadBytes += n;
372   }
373   if (n < 0) {
374     BErrNo be;
375     be.SetErrno(bfd->BErrNo);
376     Dmsg2(100, "Error reading file %s: ERR=%s\n", jcr->impl->last_fname,
377           be.bstrerror());
378     Jmsg(jcr, M_ERROR, 1, _("Error reading file %s: ERR=%s\n"),
379          jcr->impl->last_fname, be.bstrerror());
380     jcr->JobErrors++;
381     return -1;
382   }
383   return 0;
384 }
385 
386 /**
387  * Calculate the chksum of a whole file and updates:
388  * - digest
389  * - digest_stream
390  * - digest_buffer
391  * - digest_name
392  *
393  * Returns: true   if digest calculation succeeded.
394  *          false  if digest calculation failed.
395  */
calculate_file_chksum(JobControlRecord * jcr,FindFilesPacket * ff_pkt,DIGEST ** digest,int * digest_stream,char ** digest_buf,const char ** digest_name)396 static bool calculate_file_chksum(JobControlRecord* jcr,
397                                   FindFilesPacket* ff_pkt,
398                                   DIGEST** digest,
399                                   int* digest_stream,
400                                   char** digest_buf,
401                                   const char** digest_name)
402 {
403   /*
404    * Create our digest context.
405    * If this fails, the digest will be set to NULL and not used.
406    */
407   if (BitIsSet(FO_MD5, ff_pkt->flags)) {
408     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_MD5);
409     *digest_stream = STREAM_MD5_DIGEST;
410   } else if (BitIsSet(FO_SHA1, ff_pkt->flags)) {
411     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA1);
412     *digest_stream = STREAM_SHA1_DIGEST;
413   } else if (BitIsSet(FO_SHA256, ff_pkt->flags)) {
414     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA256);
415     *digest_stream = STREAM_SHA256_DIGEST;
416   } else if (BitIsSet(FO_SHA512, ff_pkt->flags)) {
417     *digest = crypto_digest_new(jcr, CRYPTO_DIGEST_SHA512);
418     *digest_stream = STREAM_SHA512_DIGEST;
419   }
420 
421   /*
422    * compute MD5 or SHA1 hash
423    */
424   if (*digest) {
425     uint32_t size;
426     char md[CRYPTO_DIGEST_MAX_SIZE];
427 
428     size = sizeof(md);
429     if (DigestFile(jcr, ff_pkt, *digest) != 0) {
430       jcr->JobErrors++;
431       return false;
432     }
433 
434     if (CryptoDigestFinalize(*digest, (uint8_t*)md, &size)) {
435       *digest_buf = (char*)malloc(BASE64_SIZE(size));
436       *digest_name = crypto_digest_name(*digest);
437 
438       BinToBase64(*digest_buf, BASE64_SIZE(size), md, size, true);
439     }
440   }
441 
442   return true;
443 }
444 
445 /**
446  * Compare a files chksum against a stored chksum.
447  *
448  * Returns: true   if chksum matches.
449  *          false  if chksum is different.
450  */
CalculateAndCompareFileChksum(JobControlRecord * jcr,FindFilesPacket * ff_pkt,const char * fname,const char * chksum)451 bool CalculateAndCompareFileChksum(JobControlRecord* jcr,
452                                    FindFilesPacket* ff_pkt,
453                                    const char* fname,
454                                    const char* chksum)
455 {
456   DIGEST* digest = NULL;
457   int digest_stream = STREAM_NONE;
458   char* digest_buf = NULL;
459   const char* digest_name;
460   bool retval = false;
461 
462   if (calculate_file_chksum(jcr, ff_pkt, &digest, &digest_stream, &digest_buf,
463                             &digest_name)) {
464     if (digest_stream != STREAM_NONE && digest == NULL) {
465       Jmsg(jcr, M_WARNING, 0, _("%s digest initialization failed\n"),
466            stream_to_ascii(digest_stream));
467     } else if (digest && digest_buf) {
468       if (!bstrcmp(digest_buf, chksum)) {
469         Dmsg4(100, "%s      %s chksum  diff. Cat: %s File: %s\n", fname,
470               digest_name, chksum, digest_buf);
471       } else {
472         retval = true;
473       }
474     }
475 
476     if (digest_buf) { free(digest_buf); }
477 
478     if (digest) { CryptoDigestFree(digest); }
479   }
480 
481   return retval;
482 }
483 } /* namespace filedaemon */
484