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