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