2 Bacula(R) - The Network Backup Solution
4 Copyright (C) 2000-2017 Kern Sibbald
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
17 Bacula(R) is a registered trademark of Kern Sibbald.
20 * Bacula File Daemon verify-vol.c Verify files on a Volume
21 * versus attributes in Catalog
23 * Kern Sibbald, July MMII
25 * Data verification added by Eric Bollengier
30 #include "findlib/win32filter.h"
32 #if defined(HAVE_LIBZ)
33 const bool have_libz = true;
35 const bool have_libz = false;
39 const bool have_lzo = true;
41 const bool have_lzo = false;
44 /* Context used during Verify Data job. We use it in the
45 * verify loop to compute checksums and check attributes.
50 int32_t stream; /* stream less new bits */
51 int32_t prev_stream; /* previous stream */
52 int32_t full_stream; /* full stream including new bits */
53 int32_t type; /* file type FT_ */
54 int64_t size; /* current file size */
55 ATTR *attr; /* Pointer to attributes */
57 bool check_size; /* Check or not the size attribute */
58 bool check_chksum; /* Check the checksum */
60 crypto_digest_t digesttype;
61 Win32Filter win32filter;
62 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)]; /* current digest */
65 jcr(ajcr), stream(0), prev_stream(0), full_stream(0), type(0), size(-1),
66 attr(new_attr(jcr)), check_size(false), check_chksum(false),
67 digesttype(CRYPTO_DIGEST_NONE), win32filter()
75 /* Call this function when we change the file
76 * We check the st_size and we compute the digest
78 bool close_previous_stream();
80 /* Call when we have a sparse record */
81 void skip_sparse_header(char **data, uint32_t *length);
83 /* Scan the fileset to know if we want to check checksums or st_size */
86 /* Check the catalog to locate the file */
87 void check_accurate();
89 /* In cleanup, we reset the current file size to -1 */
94 /* Used for sparse files */
95 void set_size(int64_t val) {
96 size = MAX(size, val);
99 void update_size(int64_t val) {
106 void update_checksum(char *wbuf, int32_t wsize) {
107 if (wsize > 0 && check_chksum) {
108 if (!jcr->crypto.digest) {
109 jcr->crypto.digest = crypto_digest_new(jcr, digesttype);
111 crypto_digest_update(jcr->crypto.digest, (uint8_t *)wbuf, wsize);
116 /* Data received from Storage Daemon */
117 static char rec_header[] = "rechdr %ld %ld %ld %ld %ld";
119 /* Forward referenced functions */
121 /* We don't know in advance which digest mode is needed, we do not
122 * want to store files on disk either to check afterward. So, we read
123 * the fileset definition and we try to guess the digest that will be
124 * used. If the FileSet uses multiple digests, it will not work.
126 void v_ctx::scan_fileset()
128 findFILESET *fileset;
130 check_size = check_chksum = false;
131 digesttype = CRYPTO_DIGEST_NONE;
133 if (!jcr->ff || !jcr->ff->fileset) {
137 fileset = jcr->ff->fileset;
139 for (int i=0; i<fileset->include_list.size(); i++) {
140 findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
142 for (int j=0; j<incexe->opts_list.size(); j++) {
143 findFOPTS *fo = (findFOPTS *)incexe->opts_list.get(j);
144 check_size = (strchr(fo->VerifyOpts, 's') != NULL);
145 if ((strchr(fo->VerifyOpts, '1') != NULL) ||
146 (strchr(fo->VerifyOpts, '5') != NULL))
151 if (fo->flags & FO_MD5) {
152 digesttype = CRYPTO_DIGEST_MD5;
155 if (fo->flags & FO_SHA1) {
156 digesttype = CRYPTO_DIGEST_SHA1;
159 if (fo->flags & FO_SHA256) {
160 digesttype = CRYPTO_DIGEST_SHA256;
163 if (fo->flags & FO_SHA512) {
164 digesttype = CRYPTO_DIGEST_SHA512;
169 digesttype = CRYPTO_DIGEST_NONE;
171 Jmsg(jcr, M_WARNING, 0, _("Checksum verification required in Verify FileSet option, but no Signature found in the FileSet\n"));
172 check_chksum = false;
176 /* Compute the file size for sparse records and adjust the data */
177 void v_ctx::skip_sparse_header(char **data, uint32_t *length)
181 unser_begin(*data, OFFSET_FADDR_SIZE);
184 /* For sparse, we assume that the file is at least big as faddr */
187 *data += OFFSET_FADDR_SIZE;
188 *length -= OFFSET_FADDR_SIZE;
191 void v_ctx::check_accurate()
193 attr->fname = jcr->last_fname; /* struct stat is still valid, but not the fname */
194 if (accurate_check_file(jcr, attr, digest)) {
195 jcr->setJobStatus(JS_Differences);
200 * If extracting, close any previous stream
202 bool v_ctx::close_previous_stream()
205 uint8_t buf[CRYPTO_DIGEST_MAX_SIZE];
206 uint32_t len = CRYPTO_DIGEST_MAX_SIZE;
207 char ed1[50], ed2[50];
209 /* Reset the win32 filter that strips header stream out of the file */
212 /* Check the size if possible */
213 if (check_size && size >= 0) {
214 if (attr->type == FT_REG && size != (int64_t)attr->statp.st_size) {
215 Dmsg1(50, "Size comparison failed for %s\n", jcr->last_fname);
217 _(" st_size differs on \"%s\". Vol: %s File: %s\n"),
219 edit_int64(size, ed1),
220 edit_int64((int64_t)attr->statp.st_size, ed2));
221 jcr->setJobStatus(JS_Differences);
226 /* Compute the digest and store it */
228 if (jcr->crypto.digest) {
229 if (!crypto_digest_finalize(jcr->crypto.digest, buf, &len)) {
230 Dmsg1(50, "Unable to finalize digest for %s\n", jcr->last_fname);
234 bin_to_base64(digest, sizeof(digest), (char *)buf, len, true);
236 crypto_digest_free(jcr->crypto.digest);
237 jcr->crypto.digest = NULL;
243 * Verify attributes or data of the requested files on the Volume
246 void do_verify_volume(JCR *jcr)
250 uint32_t VolSessionId, VolSessionTime, file_index;
251 char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
254 char *wbuf; /* write buffer */
255 uint32_t wsize; /* write size */
256 uint32_t rsize; /* read size */
257 bool msg_encrypt = false, do_check_accurate=false;
259 ATTR *attr = vctx.attr;
261 sd = jcr->store_bsock;
263 Jmsg(jcr, M_FATAL, 0, _("Storage command not issued before Verify.\n"));
264 jcr->setJobStatus(JS_FatalError);
267 dir = jcr->dir_bsock;
268 jcr->setJobStatus(JS_Running);
271 CLIENT *client = (CLIENT *)GetNextRes(R_CLIENT, NULL);
275 buf_size = client->max_network_buffer_size;
277 buf_size = 0; /* use default */
279 if (!sd->set_buffer_size(buf_size, BNET_SETBUF_WRITE)) {
280 jcr->setJobStatus(JS_FatalError);
283 jcr->buf_size = sd->msglen;
285 /* use the same buffer size to decompress both gzip and lzo */
286 if (have_libz || have_lzo) {
287 uint32_t compress_buf_size = jcr->buf_size + 12 + ((jcr->buf_size+999) / 1000) + 100;
288 jcr->compress_buf = get_memory(compress_buf_size);
289 jcr->compress_buf_size = compress_buf_size;
293 fdmsg = New(GetMsg(jcr, sd, rec_header, GETMSG_MAX_MSG_SIZE));
295 fdmsg->start_read_sock();
296 bmessage *bmsg = fdmsg->new_msg(); /* get a message, to exchange with fdmsg */
299 * Get a record from the Storage daemon
301 while ((bget_ret = fdmsg->bget_msg(&bmsg)) >= 0 && !job_canceled(jcr)) {
302 /* Remember previous stream type */
303 vctx.prev_stream = vctx.stream;
306 * First we expect a Stream Record Header
308 if (sscanf(bmsg->rbuf, rec_header, &VolSessionId, &VolSessionTime, &file_index,
309 &vctx.full_stream, &size) != 5) {
310 Jmsg1(jcr, M_FATAL, 0, _("Record header scan error: %s\n"), bmsg->rbuf);
313 vctx.stream = vctx.full_stream & STREAMMASK_TYPE;
314 Dmsg4(30, "Got hdr: FilInx=%d FullStream=%d Stream=%d size=%d.\n",
315 file_index, vctx.full_stream, vctx.stream, size);
318 * Now we expect the Stream Data
320 if ((bget_ret = fdmsg->bget_msg(&bmsg)) < 0) {
321 if (bget_ret != BNET_EXT_TERMINATE) {
322 Jmsg1(jcr, M_FATAL, 0, _("Data record error. ERR=%s\n"), sd->bstrerror());
324 /* The error has been handled somewhere else, just quit */
328 if (size != ((uint32_t)bmsg->origlen)) {
329 Jmsg2(jcr, M_FATAL, 0, _("Actual data size %d not same as header %d\n"), bmsg->origlen, size);
332 Dmsg2(30, "Got stream data %s, len=%d\n", stream_to_ascii(vctx.stream), bmsg->rbuflen);
334 /* File Attributes stream */
335 switch (vctx.stream) {
336 case STREAM_UNIX_ATTRIBUTES:
337 case STREAM_UNIX_ATTRIBUTES_EX:
338 Dmsg0(400, "Stream=Unix Attributes.\n");
339 if (!vctx.close_previous_stream()) {
342 if (do_check_accurate) {
343 vctx.check_accurate();
345 /* Next loop, we want to check the file (or we do it with the md5) */
346 do_check_accurate = true;
349 * Unpack attributes and do sanity check them
351 if (!unpack_attributes_record(jcr, vctx.stream,
352 bmsg->rbuf, bmsg->rbuflen, attr)) {
356 attr->data_stream = decode_stat(attr->attr, &attr->statp,
357 sizeof(attr->statp), &attr->LinkFI);
361 jcr->num_files_examined++;
362 pm_strcpy(jcr->last_fname, attr->fname); /* last file examined */
365 if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
367 * Send file attributes to Director
371 * Filename (full path)
373 * Link name (if type==FT_LNK)
374 * For a directory, link is the same as fname, but with trailing
375 * slash. For a linked file, link is the link.
377 /* Send file attributes to Director */
378 Dmsg2(200, "send ATTR inx=%d fname=%s\n", jcr->JobFiles, attr->fname);
379 if (attr->type == FT_LNK || attr->type == FT_LNKSAVED) {
380 stat = dir->fsend("%d %d %s %s%c%s%c%s%c", jcr->JobFiles,
381 STREAM_UNIX_ATTRIBUTES, "pinsug5", attr->fname,
382 0, attr->attr, 0, attr->lname, 0);
383 /* for a deleted record, we set fileindex=0 */
384 } else if (attr->type == FT_DELETED) {
385 stat = dir->fsend("%d %d %s %s%c%s%c%c", 0,
386 STREAM_UNIX_ATTRIBUTES, "pinsug5", attr->fname,
387 0, attr->attr, 0, 0);
389 stat = dir->fsend("%d %d %s %s%c%s%c%c", jcr->JobFiles,
390 STREAM_UNIX_ATTRIBUTES, "pinsug5", attr->fname,
391 0, attr->attr, 0, 0);
393 Dmsg2(200, "bfiled>bdird: attribs len=%d: msg=%s\n", dir->msglen, dir->msg);
395 Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), dir->bstrerror());
402 * Restore stream object is counted, but not restored here
404 case STREAM_RESTORE_OBJECT:
407 jcr->num_files_examined++;
415 const char *digest_code = NULL;
417 switch(vctx.stream) {
418 case STREAM_MD5_DIGEST:
419 bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_MD5_SIZE, true);
423 case STREAM_SHA1_DIGEST:
424 bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA1_SIZE, true);
425 digest_code = "SHA1";
428 case STREAM_SHA256_DIGEST:
429 bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA256_SIZE, true);
430 digest_code = "SHA256";
433 case STREAM_SHA512_DIGEST:
434 bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA512_SIZE, true);
435 digest_code = "SHA512";
443 if (digest_code && jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
444 dir->fsend("%d %d %s *%s-%d*", jcr->JobFiles, vctx.stream,
445 digest, digest_code, jcr->JobFiles);
447 } else if (jcr->getJobLevel() == L_VERIFY_DATA) {
449 if (vctx.check_chksum && *digest) {
450 /* probably an empty file, we can create an empty crypto session */
451 if (!jcr->crypto.digest) {
452 jcr->crypto.digest = crypto_digest_new(jcr, vctx.digesttype);
454 vctx.close_previous_stream();
455 if (strncmp(digest, vctx.digest,
456 MIN(sizeof(digest), sizeof(vctx.digest))) != 0)
459 _(" %s differs on \"%s\". File=%s Vol=%s\n"),
460 stream_to_ascii(vctx.stream), jcr->last_fname,
461 vctx.digest, digest);
462 jcr->setJobStatus(JS_Differences);
463 Dmsg3(50, "Signature verification failed for %s %s != %s\n",
464 jcr->last_fname, digest, vctx.digest);
466 if (do_check_accurate) {
467 vctx.check_accurate();
468 do_check_accurate = false; /* Don't do it in the next loop */
472 /* Compute size and checksum for level=Data */
473 switch (vctx.stream) {
474 case STREAM_ENCRYPTED_FILE_DATA:
475 case STREAM_ENCRYPTED_WIN32_DATA:
476 case STREAM_ENCRYPTED_FILE_GZIP_DATA:
477 case STREAM_ENCRYPTED_WIN32_GZIP_DATA:
478 case STREAM_ENCRYPTED_FILE_COMPRESSED_DATA:
479 case STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA:
481 Jmsg(jcr, M_WARNING, 0,
482 _("Verification of encrypted file data is not supported.\n"));
487 case STREAM_PLUGIN_DATA:
488 case STREAM_FILE_DATA:
489 case STREAM_SPARSE_DATA:
490 case STREAM_WIN32_DATA:
491 case STREAM_GZIP_DATA:
492 case STREAM_SPARSE_GZIP_DATA:
493 case STREAM_WIN32_GZIP_DATA:
494 case STREAM_COMPRESSED_DATA:
495 case STREAM_SPARSE_COMPRESSED_DATA:
496 case STREAM_WIN32_COMPRESSED_DATA:
497 if (!(attr->type == FT_RAW || attr->type == FT_FIFO || attr->type == FT_REG || attr->type == FT_REGE)) {
502 rsize = bmsg->rbuflen;
503 jcr->ReadBytes += rsize;
506 if (vctx.stream == STREAM_SPARSE_DATA
507 || vctx.stream == STREAM_SPARSE_COMPRESSED_DATA
508 || vctx.stream == STREAM_SPARSE_GZIP_DATA) {
509 vctx.skip_sparse_header(&wbuf, &wsize);
512 if (vctx.stream == STREAM_GZIP_DATA
513 || vctx.stream == STREAM_SPARSE_GZIP_DATA
514 || vctx.stream == STREAM_WIN32_GZIP_DATA
515 || vctx.stream == STREAM_ENCRYPTED_FILE_GZIP_DATA
516 || vctx.stream == STREAM_COMPRESSED_DATA
517 || vctx.stream == STREAM_SPARSE_COMPRESSED_DATA
518 || vctx.stream == STREAM_WIN32_COMPRESSED_DATA
519 || vctx.stream == STREAM_ENCRYPTED_FILE_COMPRESSED_DATA
520 || vctx.stream == STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA
521 || vctx.stream == STREAM_ENCRYPTED_WIN32_GZIP_DATA) {
523 if (!decompress_data(jcr, vctx.stream, &wbuf, &wsize)) {
524 dequeue_messages(jcr);
529 vctx.update_checksum(wbuf, wsize);
531 if (vctx.stream == STREAM_WIN32_GZIP_DATA
532 || vctx.stream == STREAM_WIN32_DATA
533 || vctx.stream == STREAM_WIN32_COMPRESSED_DATA
534 || vctx.stream == STREAM_ENCRYPTED_WIN32_COMPRESSED_DATA
535 || vctx.stream == STREAM_ENCRYPTED_WIN32_GZIP_DATA) {
537 int64_t wbuf_len = wsize;
539 if (vctx.win32filter.have_data(&wbuf, &wbuf_len, &wsize64)) {
543 jcr->JobBytes += wsize;
544 vctx.update_size(wsize);
547 /* TODO: Handle data to compute checksums */
548 /* Ignore everything else */
553 } /* end while bnet_get */
554 if (bget_ret == BNET_EXT_TERMINATE) {
557 if (!vctx.close_previous_stream()) {
560 /* Check the last file */
561 if (do_check_accurate) {
562 vctx.check_accurate();
564 if (!accurate_finish(jcr)) {
567 jcr->setJobStatus(JS_Terminated);
571 jcr->setJobStatus(JS_ErrorTerminated);
574 fdmsg->wait_read_sock(jcr->is_job_canceled());
577 if (jcr->compress_buf) {
578 free_pool_memory(jcr->compress_buf);
579 jcr->compress_buf = NULL;
581 /* TODO: We probably want to mark the job as failed if we have errors */
582 Dmsg2(50, "End Verify-Vol. Files=%d Bytes=%" lld "\n", jcr->JobFiles,