snap.c (10921B)
1 /* Snapshot archive implementation */ 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 5 #include <assert.h> 6 #include <errno.h> 7 #include <fcntl.h> 8 #include <limits.h> 9 #include <stdint.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <strings.h> 14 #include <unistd.h> 15 16 #include <sodium.h> 17 18 #include "config.h" 19 #include "misc.h" 20 #include "queue.h" 21 #include "snap.h" 22 #include "state.h" 23 24 /* snapshot encryption algorithms */ 25 #define SNONETYPE 0x400 26 #define SCHACHATYPE 0x401 27 28 /* snapshot header constants */ 29 #define SHDRMAGIC "SNAPSNAPPYSNOOP" 30 #define NSHDRMAGIC 16 31 32 #define VMIN 0 33 #define VMAJ 1 34 #define VMINMASK 0xff 35 #define VMAJSHIFT 8 36 #define VMAJMASK 0xff 37 38 #define CRYPTOHDRSIZE crypto_secretstream_xchacha20poly1305_HEADERBYTES 39 #define SHDRSIZE (NSHDRMAGIC + CRYPTOHDRSIZE + 8 + 8) 40 41 extern struct param param; 42 43 /* misc helpers */ 44 extern int pack(unsigned char *, char *, ...); 45 extern int unpack(unsigned char *, char *, ...); 46 47 /* Snapshot header structure */ 48 struct shdr { 49 char magic[NSHDRMAGIC]; /* magic number for file(1) */ 50 unsigned char header[CRYPTOHDRSIZE]; /* xchacha20-poly1305 crypto header */ 51 uint64_t flags; /* version number */ 52 uint64_t nbd; /* number of block hashes */ 53 }; 54 55 struct mdnode { 56 unsigned char md[MDSIZE]; /* hash of block */ 57 TAILQ_ENTRY(mdnode) e; /* mdhead link node */ 58 }; 59 60 struct sctx { 61 TAILQ_HEAD(mdhead, mdnode) mdhead; /* list of hashes contained in snapshot */ 62 struct mdnode *mdnext; /* next hash to be returned via sget() */ 63 int type; /* encryption algorithm */ 64 int fd; /* underlying snapshot file descriptor */ 65 int rdonly; /* when set, ssync() is a no-op */ 66 struct shdr shdr; /* snapshot header */ 67 }; 68 69 /* Unpack snapshot header */ 70 static int 71 unpackshdr(unsigned char *buf, struct shdr *shdr) 72 { 73 char fmt[BUFSIZ]; 74 int n; 75 76 snprintf(fmt, sizeof(fmt), "'%d'%dqq", NSHDRMAGIC, CRYPTOHDRSIZE); 77 n = unpack(buf, fmt, 78 shdr->magic, 79 shdr->header, 80 &shdr->flags, 81 &shdr->nbd); 82 83 assert(n == SHDRSIZE); 84 return n; 85 } 86 87 /* Pack snapshot header */ 88 static int 89 packshdr(unsigned char *buf, struct shdr *shdr) 90 { 91 char fmt[BUFSIZ]; 92 int n; 93 94 snprintf(fmt, sizeof(fmt), "'%d'%dqq", NSHDRMAGIC, CRYPTOHDRSIZE); 95 n = pack(buf, fmt, 96 shdr->magic, 97 shdr->header, 98 shdr->flags, 99 shdr->nbd); 100 101 assert(n == SHDRSIZE); 102 return n; 103 } 104 105 static int 106 loadmdnone(struct sctx *sctx, int first) 107 { 108 struct mdnode *mdnode; 109 110 mdnode = calloc(1, sizeof(*mdnode)); 111 if (mdnode == NULL) { 112 seterr("calloc: out of memory"); 113 return -1; 114 } 115 116 if (xread(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) { 117 seterr("failed to read block hash: %s", strerror(errno)); 118 return -1; 119 } 120 121 TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e); 122 return 0; 123 } 124 125 static int 126 loadmdchacha(struct sctx *sctx, int first) 127 { 128 unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES]; 129 unsigned char hdr[SHDRSIZE]; 130 static crypto_secretstream_xchacha20poly1305_state state; 131 struct mdnode *mdnode; 132 struct shdr *shdr; 133 134 shdr = &sctx->shdr; 135 packshdr(hdr, shdr); 136 if (first && crypto_secretstream_xchacha20poly1305_init_pull(&state, 137 shdr->header, 138 param.key) < 0) { 139 seterr("invalid crypto header"); 140 return -1; 141 } 142 143 if (xread(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) { 144 seterr("failed to read block hash: %s", strerror(errno)); 145 return -1; 146 } 147 148 mdnode = calloc(1, sizeof(*mdnode)); 149 if (mdnode == NULL) { 150 seterr("calloc: out of memory"); 151 return -1; 152 } 153 154 if (crypto_secretstream_xchacha20poly1305_pull(&state, mdnode->md, NULL, 155 NULL, buf, sizeof(buf), 156 hdr, sizeof(hdr)) < 0) { 157 free(mdnode); 158 seterr("authentication failed"); 159 return -1; 160 } 161 162 TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e); 163 return 0; 164 } 165 166 static int 167 initmdhead(struct sctx *sctx) 168 { 169 struct shdr *shdr; 170 int (*loadmd)(struct sctx *, int); 171 uint64_t i; 172 173 if (sctx->type == SNONETYPE) 174 loadmd = loadmdnone; 175 else 176 loadmd = loadmdchacha; 177 178 shdr = &sctx->shdr; 179 for (i = 0; i < shdr->nbd; i++) { 180 if ((*loadmd)(sctx, i == 0) == 0) 181 continue; 182 183 while (!TAILQ_EMPTY(&sctx->mdhead)) { 184 struct mdnode *mdnode; 185 186 mdnode = TAILQ_FIRST(&sctx->mdhead); 187 TAILQ_REMOVE(&sctx->mdhead, mdnode, e); 188 free(mdnode); 189 } 190 return -1; 191 } 192 return 0; 193 } 194 195 int 196 screat(char *path, int mode, struct sctx **sctx) 197 { 198 unsigned char buf[SHDRSIZE]; 199 struct shdr *shdr; 200 int type; 201 int fd; 202 203 if (path == NULL || sctx == NULL) { 204 seterr("invalid params"); 205 return -1; 206 } 207 208 /* Determine algorithm type */ 209 if (strcasecmp(param.ealgo, "none") == 0) { 210 type = SNONETYPE; 211 } else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) { 212 type = SCHACHATYPE; 213 } else { 214 seterr("invalid encryption type: %s", param.ealgo); 215 return -1; 216 } 217 218 /* Ensure a key has been provided if caller requested encryption */ 219 if (type != SNONETYPE && !param.keyloaded) { 220 seterr("expected encryption key"); 221 return -1; 222 } 223 224 if (sodium_init() < 0) { 225 seterr("sodium_init: failed"); 226 return -1; 227 } 228 229 fd = open(path, O_RDWR | O_CREAT | O_EXCL, mode); 230 if (fd < 0) { 231 seterr("open: %s", strerror(errno)); 232 return -1; 233 } 234 235 *sctx = calloc(1, sizeof(**sctx)); 236 if (*sctx == NULL) { 237 close(fd); 238 seterr("calloc: out of memory"); 239 return -1; 240 } 241 242 TAILQ_INIT(&(*sctx)->mdhead); 243 (*sctx)->mdnext = NULL; 244 (*sctx)->type = type; 245 (*sctx)->fd = fd; 246 247 shdr = &(*sctx)->shdr; 248 memcpy(shdr->magic, SHDRMAGIC, NSHDRMAGIC); 249 shdr->flags = (VMAJ << VMAJSHIFT) | VMIN; 250 shdr->nbd = 0; 251 252 packshdr(buf, shdr); 253 if (xwrite(fd, buf, SHDRSIZE) != SHDRSIZE) { 254 free(*sctx); 255 close(fd); 256 seterr("failed to write snapshot header: %s", strerror(errno)); 257 return -1; 258 } 259 return 0; 260 } 261 262 int 263 sopen(char *path, int flags, int mode, struct sctx **sctx) 264 { 265 unsigned char buf[SHDRSIZE]; 266 struct shdr *shdr; 267 int type; 268 int fd; 269 270 if (path == NULL || sctx == NULL) { 271 seterr("invalid params"); 272 return -1; 273 } 274 275 /* Existing snapshots are immutable */ 276 if (flags != S_READ) { 277 seterr("invalid params"); 278 return -1; 279 } 280 281 /* Determine algorithm type */ 282 if (strcasecmp(param.ealgo, "none") == 0) { 283 type = SNONETYPE; 284 } else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) { 285 type = SCHACHATYPE; 286 } else { 287 seterr("invalid encryption type: %s", param.ealgo); 288 return -1; 289 } 290 291 /* Ensure a key has been provided if caller requested encryption */ 292 if (type != SNONETYPE && !param.keyloaded) { 293 seterr("expected encryption key"); 294 return -1; 295 } 296 297 if (sodium_init() < 0) { 298 seterr("sodium_init: failed"); 299 return -1; 300 } 301 302 fd = open(path, O_RDONLY, mode); 303 if (fd < 0) { 304 seterr("open: %s", strerror(errno)); 305 return -1; 306 } 307 308 *sctx = calloc(1, sizeof(**sctx)); 309 if (*sctx == NULL) { 310 close(fd); 311 seterr("calloc: out of memory"); 312 return -1; 313 } 314 315 TAILQ_INIT(&(*sctx)->mdhead); 316 (*sctx)->mdnext = NULL; 317 (*sctx)->type = type; 318 (*sctx)->fd = fd; 319 (*sctx)->rdonly = 1; 320 321 shdr = &(*sctx)->shdr; 322 323 if (xread(fd, buf, SHDRSIZE) != SHDRSIZE) { 324 free(*sctx); 325 close(fd); 326 seterr("failed to read snapshot header: %s", strerror(errno)); 327 return -1; 328 } 329 unpackshdr(buf, shdr); 330 331 if (memcmp(shdr->magic, SHDRMAGIC, NSHDRMAGIC) != 0) { 332 free(*sctx); 333 close(fd); 334 seterr("unknown snapshot header magic"); 335 return -1; 336 } 337 338 /* If the major version is different, the format is incompatible */ 339 if (((shdr->flags >> VMAJSHIFT) & VMAJMASK) != VMAJ) { 340 free(*sctx); 341 close(fd); 342 seterr("snapshot header version mismatch"); 343 return -1; 344 } 345 346 if (initmdhead(*sctx) < 0) { 347 free(*sctx); 348 close(fd); 349 return -1; 350 } 351 return 0; 352 } 353 354 int 355 sput(struct sctx *sctx, unsigned char *md) 356 { 357 struct shdr *shdr; 358 struct mdnode *mdnode; 359 360 if (sctx == NULL || md == NULL) { 361 seterr("invalid params"); 362 return -1; 363 } 364 365 mdnode = calloc(1, sizeof(*mdnode)); 366 if (mdnode == NULL) { 367 seterr("calloc: out of memory"); 368 return -1; 369 } 370 shdr = &sctx->shdr; 371 shdr->nbd++; 372 memcpy(mdnode->md, md, MDSIZE); 373 TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e); 374 return 0; 375 } 376 377 int 378 sget(struct sctx *sctx, unsigned char *md) 379 { 380 struct mdnode *mdnode; 381 382 if (sctx == NULL || md == NULL) { 383 seterr("invalid params"); 384 return -1; 385 } 386 387 mdnode = sctx->mdnext; 388 if (mdnode == NULL) 389 mdnode = TAILQ_FIRST(&sctx->mdhead); 390 else 391 mdnode = TAILQ_NEXT(mdnode, e); 392 sctx->mdnext = mdnode; 393 if (mdnode != NULL) { 394 memcpy(md, mdnode->md, MDSIZE); 395 return MDSIZE; 396 } 397 return 0; 398 } 399 400 static int 401 syncnone(struct sctx *sctx) 402 { 403 unsigned char hdr[SHDRSIZE]; 404 struct mdnode *mdnode; 405 struct shdr *shdr; 406 407 shdr = &sctx->shdr; 408 packshdr(hdr, shdr); 409 if (xwrite(sctx->fd, hdr, SHDRSIZE) != SHDRSIZE) { 410 seterr("failed to write snapshot header: %s", strerror(errno)); 411 return -1; 412 } 413 414 TAILQ_FOREACH(mdnode, &sctx->mdhead, e) { 415 if (xwrite(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) { 416 seterr("failed to write block hash: %s", 417 strerror(errno)); 418 return -1; 419 } 420 } 421 return 0; 422 } 423 424 static int 425 syncchacha(struct sctx *sctx) 426 { 427 unsigned char hdr[SHDRSIZE]; 428 crypto_secretstream_xchacha20poly1305_state state; 429 struct mdnode *mdnode; 430 struct shdr *shdr; 431 432 shdr = &sctx->shdr; 433 crypto_secretstream_xchacha20poly1305_init_push(&state, 434 shdr->header, 435 param.key); 436 437 packshdr(hdr, shdr); 438 if (xwrite(sctx->fd, hdr, SHDRSIZE) != SHDRSIZE) { 439 seterr("failed to write snapshot header: %s", strerror(errno)); 440 return -1; 441 } 442 443 TAILQ_FOREACH(mdnode, &sctx->mdhead, e) { 444 unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES]; 445 unsigned char tag; 446 447 if (TAILQ_LAST(&sctx->mdhead, mdhead) == mdnode) 448 tag = crypto_secretstream_xchacha20poly1305_TAG_FINAL; 449 else 450 tag = 0; 451 452 crypto_secretstream_xchacha20poly1305_push(&state, 453 buf, NULL, 454 mdnode->md, MDSIZE, 455 hdr, SHDRSIZE, tag); 456 if (xwrite(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) { 457 seterr("failed to write block hash: %s", 458 strerror(errno)); 459 return -1; 460 } 461 } 462 return 0; 463 } 464 465 int 466 ssync(struct sctx *sctx) 467 { 468 if (sctx == NULL) { 469 seterr("invalid params"); 470 return -1; 471 } 472 473 if (sctx->rdonly) 474 return 0; 475 476 if (lseek(sctx->fd, 0, SEEK_SET) < 0) { 477 seterr("lseek: %s", strerror(errno)); 478 return -1; 479 } 480 481 if (sctx->type == SNONETYPE) 482 syncnone(sctx); 483 else 484 syncchacha(sctx); 485 486 fsync(sctx->fd); 487 return 0; 488 } 489 490 int 491 sclose(struct sctx *sctx) 492 { 493 int r; 494 495 if (sctx == NULL) 496 return -1; 497 498 if (ssync(sctx) < 0) 499 return -1; 500 501 /* Free block hash list */ 502 while (!TAILQ_EMPTY(&sctx->mdhead)) { 503 struct mdnode *mdnode; 504 505 mdnode = TAILQ_FIRST(&sctx->mdhead); 506 TAILQ_REMOVE(&sctx->mdhead, mdnode, e); 507 free(mdnode); 508 } 509 510 r = close(sctx->fd); 511 free(sctx); 512 if (r < 0) 513 seterr("close: %s", strerror(errno)); 514 return r; 515 }