]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/filed/verify_vol.c
Big backport from Enterprise
[bacula/bacula] / bacula / src / filed / verify_vol.c
1 /*
2    Bacula(R) - The Network Backup Solution
3
4    Copyright (C) 2000-2017 Kern Sibbald
5
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.
8
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.
13
14    This notice must be preserved when any source code is
15    conveyed and/or propagated.
16
17    Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20  *  Bacula File Daemon  verify-vol.c Verify files on a Volume
21  *    versus attributes in Catalog
22  *
23  *    Kern Sibbald, July MMII
24  *
25  *  Data verification added by Eric Bollengier
26  */
27
28 #include "bacula.h"
29 #include "filed.h"
30 #include "findlib/win32filter.h"
31
32 #if   defined(HAVE_LIBZ)
33 const bool have_libz = true;
34 #else
35 const bool have_libz = false;
36 #endif
37
38 #ifdef HAVE_LZO
39 const bool have_lzo = true;
40 #else
41 const bool have_lzo = false;
42 #endif
43
44 /* Context used during Verify Data job. We use it in the
45  * verify loop to compute checksums and check attributes.
46  */
47 class v_ctx {
48 public:
49    JCR *jcr;
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 */
56
57    bool check_size;             /* Check or not the size attribute */
58    bool check_chksum;           /* Check the checksum */
59
60    crypto_digest_t digesttype;
61    Win32Filter win32filter;
62    char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)]; /* current digest */
63
64    v_ctx(JCR *ajcr) :
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()
68    {
69       *digest = 0;
70       scan_fileset();
71    };
72    ~v_ctx() {
73       free_attr(attr);
74    };
75    /* Call this function when we change the file
76     * We check the st_size and we compute the digest
77     */
78    bool close_previous_stream();
79
80    /* Call when we have a sparse record */
81    void skip_sparse_header(char **data, uint32_t *length);
82
83    /* Scan the fileset to know if we want to check checksums or st_size */
84    void scan_fileset();
85
86    /* Check the catalog to locate the file */
87    void check_accurate();
88
89    /* In cleanup, we reset the current file size to -1 */
90    void reset_size() {
91       size = -1;
92    };
93
94    /* Used for sparse files */
95    void set_size(int64_t val) {
96       size = MAX(size, val);
97    };
98
99    void update_size(int64_t val) {
100       if (size == -1) {
101          size = 0;
102       }
103       size += val;
104    };
105
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);
110          }
111          crypto_digest_update(jcr->crypto.digest, (uint8_t *)wbuf, wsize);
112       }
113    };
114 };
115
116 /* Data received from Storage Daemon */
117 static char rec_header[] = "rechdr %ld %ld %ld %ld %ld";
118
119 /* Forward referenced functions */
120
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.
125  */
126 void v_ctx::scan_fileset()
127 {
128    findFILESET *fileset;
129
130    check_size = check_chksum = false;
131    digesttype = CRYPTO_DIGEST_NONE;
132
133    if (!jcr->ff || !jcr->ff->fileset) {
134       return;
135    }
136
137    fileset = jcr->ff->fileset;
138
139    for (int i=0; i<fileset->include_list.size(); i++) {
140       findINCEXE *incexe = (findINCEXE *)fileset->include_list.get(i);
141
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))
147          {
148             check_chksum = true;
149          } 
150
151          if (fo->flags & FO_MD5) {
152             digesttype = CRYPTO_DIGEST_MD5;
153             return;
154          }
155          if (fo->flags & FO_SHA1) {
156             digesttype = CRYPTO_DIGEST_SHA1;
157             return;
158          }
159          if (fo->flags & FO_SHA256) {
160             digesttype = CRYPTO_DIGEST_SHA256;
161             return;
162          }
163          if (fo->flags & FO_SHA512) {
164             digesttype = CRYPTO_DIGEST_SHA512;
165             return;
166          }
167       }
168    }
169    digesttype = CRYPTO_DIGEST_NONE;
170    if (check_chksum) {
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;
173    }
174 }
175
176 /* Compute the file size for sparse records and adjust the data */
177 void v_ctx::skip_sparse_header(char **data, uint32_t *length)
178 {
179    unser_declare;
180    uint64_t faddr;
181    unser_begin(*data, OFFSET_FADDR_SIZE);
182    unser_uint64(faddr);
183
184    /* For sparse, we assume that the file is at least big as faddr */
185    set_size(faddr);
186    
187    *data += OFFSET_FADDR_SIZE;
188    *length -= OFFSET_FADDR_SIZE;
189 }
190
191 void v_ctx::check_accurate()
192 {
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);
196    }
197 }
198
199 /*
200  * If extracting, close any previous stream
201  */
202 bool v_ctx::close_previous_stream()
203 {
204    bool rtn = true;
205    uint8_t buf[CRYPTO_DIGEST_MAX_SIZE];
206    uint32_t len = CRYPTO_DIGEST_MAX_SIZE;
207    char ed1[50], ed2[50];
208
209    /* Reset the win32 filter that strips header stream out of the file */
210    win32filter.init();
211
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);
216          Jmsg(jcr, M_INFO, 0,
217               _("   st_size  differs on \"%s\". Vol: %s File: %s\n"),
218               jcr->last_fname,
219               edit_int64(size, ed1),
220               edit_int64((int64_t)attr->statp.st_size, ed2));
221          jcr->setJobStatus(JS_Differences);
222       }
223       reset_size();
224    }
225
226    /* Compute the digest and store it */
227    *digest = 0;
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);
231          rtn = false;
232
233       } else {
234          bin_to_base64(digest, sizeof(digest), (char *)buf, len, true);
235       }
236       crypto_digest_free(jcr->crypto.digest);
237       jcr->crypto.digest = NULL;
238    }
239    return rtn;
240 }
241
242 /*
243  * Verify attributes or data of the requested files on the Volume
244  *
245  */
246 void do_verify_volume(JCR *jcr)
247 {
248    BSOCK *sd, *dir;
249    uint32_t size;
250    uint32_t VolSessionId, VolSessionTime, file_index;
251    char digest[BASE64_SIZE(CRYPTO_DIGEST_MAX_SIZE)];
252    int stat;
253    int bget_ret = 0;
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;
258    v_ctx vctx(jcr);
259    ATTR *attr = vctx.attr;
260
261    sd = jcr->store_bsock;
262    if (!sd) {
263       Jmsg(jcr, M_FATAL, 0, _("Storage command not issued before Verify.\n"));
264       jcr->setJobStatus(JS_FatalError);
265       return;
266    }
267    dir = jcr->dir_bsock;
268    jcr->setJobStatus(JS_Running);
269
270    LockRes();
271    CLIENT *client = (CLIENT *)GetNextRes(R_CLIENT, NULL);
272    UnlockRes();
273    uint32_t buf_size;
274    if (client) {
275       buf_size = client->max_network_buffer_size;
276    } else {
277       buf_size = 0;                   /* use default */
278    }
279    if (!sd->set_buffer_size(buf_size, BNET_SETBUF_WRITE)) {
280       jcr->setJobStatus(JS_FatalError);
281       return;
282    }
283    jcr->buf_size = sd->msglen;
284
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;
290    }
291
292    GetMsg *fdmsg;
293    fdmsg = New(GetMsg(jcr, sd, rec_header, GETMSG_MAX_MSG_SIZE));
294
295    fdmsg->start_read_sock();
296    bmessage *bmsg = fdmsg->new_msg(); /* get a message, to exchange with fdmsg */
297
298    /*
299     * Get a record from the Storage daemon
300     */
301    while ((bget_ret = fdmsg->bget_msg(&bmsg)) >= 0 && !job_canceled(jcr)) {
302       /* Remember previous stream type */
303       vctx.prev_stream = vctx.stream;
304
305       /*
306        * First we expect a Stream Record Header
307        */
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);
311          goto bail_out;
312       }
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);
316
317       /*
318        * Now we expect the Stream Data
319        */
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());
323          } else {
324             /* The error has been handled somewhere else, just quit */
325          }
326          goto bail_out;
327       }
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);
330          goto bail_out;
331       }
332       Dmsg2(30, "Got stream data %s, len=%d\n", stream_to_ascii(vctx.stream), bmsg->rbuflen);
333
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()) {
340             goto bail_out;
341          }
342          if (do_check_accurate) {
343             vctx.check_accurate();
344          }
345          /* Next loop, we want to check the file (or we do it with the md5) */
346          do_check_accurate = true;
347
348          /*
349           * Unpack attributes and do sanity check them
350           */
351          if (!unpack_attributes_record(jcr, vctx.stream,
352                                        bmsg->rbuf, bmsg->rbuflen, attr)) {
353             goto bail_out;
354          }
355
356          attr->data_stream = decode_stat(attr->attr, &attr->statp,
357                                          sizeof(attr->statp), &attr->LinkFI);
358
359          jcr->lock();
360          jcr->JobFiles++;
361          jcr->num_files_examined++;
362          pm_strcpy(jcr->last_fname, attr->fname); /* last file examined */
363          jcr->unlock();
364
365          if (jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG) {
366             /*
367              * Send file attributes to Director
368              *   File_index
369              *   Stream
370              *   Verify Options
371              *   Filename (full path)
372              *   Encoded attributes
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.
376              */
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);
388             } else {
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);
392             }
393             Dmsg2(200, "bfiled>bdird: attribs len=%d: msg=%s\n", dir->msglen, dir->msg);
394             if (!stat) {
395                Jmsg(jcr, M_FATAL, 0, _("Network error in send to Director: ERR=%s\n"), dir->bstrerror());
396                goto bail_out;
397             }
398          }
399          break;
400
401          /*
402           * Restore stream object is counted, but not restored here
403           */
404       case STREAM_RESTORE_OBJECT:
405          jcr->lock();
406          jcr->JobFiles++;
407          jcr->num_files_examined++;
408          jcr->unlock();
409          break;
410
411       default:
412          break;
413       }
414
415       const char *digest_code = NULL;
416
417       switch(vctx.stream) {
418       case STREAM_MD5_DIGEST:
419          bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_MD5_SIZE, true);
420          digest_code = "MD5";
421          break;
422
423       case STREAM_SHA1_DIGEST:
424          bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA1_SIZE, true);
425          digest_code = "SHA1";
426          break;
427
428       case STREAM_SHA256_DIGEST:
429          bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA256_SIZE, true);
430          digest_code = "SHA256";
431          break;
432
433       case STREAM_SHA512_DIGEST:
434          bin_to_base64(digest, sizeof(digest), (char *)bmsg->rbuf, CRYPTO_DIGEST_SHA512_SIZE, true);
435          digest_code = "SHA512";
436          break;
437
438       default:
439          *digest = 0;
440          break;
441       }
442
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);
446
447       } else if (jcr->getJobLevel() == L_VERIFY_DATA) {
448          /* Compare digest */
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);
453             }
454             vctx.close_previous_stream();
455             if (strncmp(digest, vctx.digest,
456                         MIN(sizeof(digest), sizeof(vctx.digest))) != 0)
457             {
458                Jmsg(jcr, M_INFO, 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);
465             }
466             if (do_check_accurate) {
467                vctx.check_accurate();
468                do_check_accurate = false; /* Don't do it in the next loop */
469             }
470          }
471
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:
480             if (!msg_encrypt) {
481                Jmsg(jcr, M_WARNING, 0,
482                   _("Verification of encrypted file data is not supported.\n"));
483                msg_encrypt = true;
484             }
485             break;
486
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)) {
498                break;
499             }
500
501             wbuf = bmsg->rbuf;
502             rsize = bmsg->rbuflen;
503             jcr->ReadBytes += rsize;
504             wsize = rsize;
505
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);
510             }
511
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) {
522
523                if (!decompress_data(jcr, vctx.stream, &wbuf, &wsize)) {
524                   dequeue_messages(jcr);
525                   goto bail_out;
526                }
527             }
528
529             vctx.update_checksum(wbuf, wsize);
530
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) {
536
537                int64_t wbuf_len = wsize;
538                int64_t wsize64 = 0;
539                if (vctx.win32filter.have_data(&wbuf, &wbuf_len, &wsize64)) {
540                   wsize = wsize64;
541                }
542             }
543             jcr->JobBytes += wsize;
544             vctx.update_size(wsize);
545             break;
546
547             /* TODO: Handle data to compute checksums */
548             /* Ignore everything else */
549          default:
550             break;
551          }
552       } /* end switch */
553    } /* end while bnet_get */
554    if (bget_ret == BNET_EXT_TERMINATE) {
555       goto bail_out;
556    }
557    if (!vctx.close_previous_stream()) {
558       goto bail_out;
559    }
560    /* Check the last file */
561    if (do_check_accurate) {
562       vctx.check_accurate();
563    }
564    if (!accurate_finish(jcr)) {
565       goto bail_out;
566    }
567    jcr->setJobStatus(JS_Terminated);
568    goto ok_out;
569
570 bail_out:
571    jcr->setJobStatus(JS_ErrorTerminated);
572
573 ok_out:
574    fdmsg->wait_read_sock(jcr->is_job_canceled());
575    delete bmsg;
576    free_GetMsg(fdmsg);
577    if (jcr->compress_buf) {
578       free_pool_memory(jcr->compress_buf);
579       jcr->compress_buf = NULL;
580    }
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,
583       jcr->JobBytes);
584 }