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