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