1 /* $OpenBSD: repo.c,v 1.7 2021/05/04 08:16:36 claudio Exp $ */ 2 /* 3 * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org> 4 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/queue.h> 20 #include <sys/tree.h> 21 #include <sys/types.h> 22 #include <sys/stat.h> 23 24 #include <assert.h> 25 #include <err.h> 26 #include <errno.h> 27 #include <fcntl.h> 28 #include <fts.h> 29 #include <limits.h> 30 #include <stdio.h> 31 #include <stdlib.h> 32 #include <string.h> 33 #include <unistd.h> 34 35 #include <imsg.h> 36 37 #include "extern.h" 38 39 extern struct stats stats; 40 extern int noop; 41 42 enum repo_state { 43 REPO_LOADING = 0, 44 REPO_DONE = 1, 45 REPO_FAILED = -1, 46 }; 47 48 /* 49 * A ta, rsync or rrdp repository. 50 * Depending on what is needed the generic repository is backed by 51 * a ta, rsync or rrdp repository. Multiple repositories can use the 52 * same backend. 53 */ 54 struct rrdprepo { 55 SLIST_ENTRY(rrdprepo) entry; 56 char *notifyuri; 57 char *basedir; 58 char *temp; 59 struct filepath_tree added; 60 struct filepath_tree deleted; 61 size_t id; 62 enum repo_state state; 63 }; 64 SLIST_HEAD(, rrdprepo) rrdprepos = SLIST_HEAD_INITIALIZER(rrdprepos); 65 66 struct rsyncrepo { 67 SLIST_ENTRY(rsyncrepo) entry; 68 char *repouri; 69 char *basedir; 70 size_t id; 71 enum repo_state state; 72 }; 73 SLIST_HEAD(, rsyncrepo) rsyncrepos = SLIST_HEAD_INITIALIZER(rsyncrepos); 74 75 struct tarepo { 76 SLIST_ENTRY(tarepo) entry; 77 char *descr; 78 char *basedir; 79 char *temp; 80 char **uri; 81 size_t urisz; 82 size_t uriidx; 83 size_t id; 84 enum repo_state state; 85 }; 86 SLIST_HEAD(, tarepo) tarepos = SLIST_HEAD_INITIALIZER(tarepos); 87 88 struct repo { 89 SLIST_ENTRY(repo) entry; 90 char *repouri; /* CA repository base URI */ 91 const struct rrdprepo *rrdp; 92 const struct rsyncrepo *rsync; 93 const struct tarepo *ta; 94 struct entityq queue; /* files waiting for repo */ 95 size_t id; /* identifier */ 96 }; 97 SLIST_HEAD(, repo) repos = SLIST_HEAD_INITIALIZER(repos); 98 99 /* counter for unique repo id */ 100 size_t repoid; 101 102 /* 103 * Database of all file path accessed during a run. 104 */ 105 struct filepath { 106 RB_ENTRY(filepath) entry; 107 char *file; 108 }; 109 110 static inline int 111 filepathcmp(struct filepath *a, struct filepath *b) 112 { 113 return strcmp(a->file, b->file); 114 } 115 116 RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp); 117 118 /* 119 * Functions to lookup which files have been accessed during computation. 120 */ 121 int 122 filepath_add(struct filepath_tree *tree, char *file) 123 { 124 struct filepath *fp; 125 126 if ((fp = malloc(sizeof(*fp))) == NULL) 127 err(1, NULL); 128 if ((fp->file = strdup(file)) == NULL) 129 err(1, NULL); 130 131 if (RB_INSERT(filepath_tree, tree, fp) != NULL) { 132 /* already in the tree */ 133 free(fp->file); 134 free(fp); 135 return 0; 136 } 137 138 return 1; 139 } 140 141 /* 142 * Lookup a file path in the tree and return the object if found or NULL. 143 */ 144 static struct filepath * 145 filepath_find(struct filepath_tree *tree, char *file) 146 { 147 struct filepath needle; 148 149 needle.file = file; 150 return RB_FIND(filepath_tree, tree, &needle); 151 } 152 153 /* 154 * Returns true if file exists in the tree. 155 */ 156 static int 157 filepath_exists(struct filepath_tree *tree, char *file) 158 { 159 return filepath_find(tree, file) != NULL; 160 } 161 162 /* 163 * Return true if a filepath entry exists that starts with path. 164 */ 165 static int 166 filepath_dir_exists(struct filepath_tree *tree, char *path) 167 { 168 struct filepath needle; 169 struct filepath *res; 170 171 needle.file = path; 172 res = RB_NFIND(filepath_tree, tree, &needle); 173 while (res != NULL && strstr(res->file, path) == res->file) { 174 /* make sure that filepath actually is in that path */ 175 if (res->file[strlen(path)] == '/') 176 return 1; 177 res = RB_NEXT(filepath_tree, tree, res); 178 } 179 return 0; 180 } 181 182 /* 183 * Remove entry from tree and free it. 184 */ 185 static void 186 filepath_put(struct filepath_tree *tree, struct filepath *fp) 187 { 188 RB_REMOVE(filepath_tree, tree, fp); 189 free((void *)fp->file); 190 free(fp); 191 } 192 193 /* 194 * Free all elements of a filepath tree. 195 */ 196 static void 197 filepath_free(struct filepath_tree *tree) 198 { 199 struct filepath *fp, *nfp; 200 201 RB_FOREACH_SAFE(fp, filepath_tree, tree, nfp) 202 filepath_put(tree, fp); 203 } 204 205 RB_GENERATE(filepath_tree, filepath, entry, filepathcmp); 206 207 /* 208 * Function to hash a string into a unique directory name. 209 * prefixed with dir. 210 */ 211 static char * 212 hash_dir(const char *uri, const char *dir) 213 { 214 const char hex[] = "0123456789abcdef"; 215 unsigned char m[SHA256_DIGEST_LENGTH]; 216 char hash[SHA256_DIGEST_LENGTH * 2 + 1]; 217 char *out; 218 size_t i; 219 220 SHA256(uri, strlen(uri), m); 221 for (i = 0; i < SHA256_DIGEST_LENGTH; i++) { 222 hash[i * 2] = hex[m[i] >> 4]; 223 hash[i * 2 + 1] = hex[m[i] & 0xf]; 224 } 225 hash[SHA256_DIGEST_LENGTH * 2] = '\0'; 226 227 asprintf(&out, "%s/%s", dir, hash); 228 return out; 229 } 230 231 /* 232 * Function to build the directory name based on URI and a directory 233 * as prefix. Skip the proto:// in URI but keep everything else. 234 */ 235 static char * 236 rsync_dir(const char *uri, const char *dir) 237 { 238 char *local, *out; 239 240 local = strchr(uri, ':') + strlen("://"); 241 242 asprintf(&out, "%s/%s", dir, local); 243 return out; 244 } 245 246 /* 247 * Function to create all missing directories to a path. 248 * This functions alters the path temporarily. 249 */ 250 static void 251 repo_mkpath(char *file) 252 { 253 char *slash; 254 255 /* build directory hierarchy */ 256 slash = strrchr(file, '/'); 257 assert(slash != NULL); 258 *slash = '\0'; 259 if (mkpath(file) == -1) 260 err(1, "%s", file); 261 *slash = '/'; 262 } 263 264 /* 265 * Build TA file name based on the repo info. 266 * If temp is set add Xs for mkostemp. 267 */ 268 static char * 269 ta_filename(const struct tarepo *tr, int temp) 270 { 271 const char *file; 272 char *nfile; 273 274 /* does not matter which URI, all end with same filename */ 275 file = strrchr(tr->uri[0], '/'); 276 assert(file); 277 278 if (asprintf(&nfile, "%s%s%s", tr->basedir, file, 279 temp ? ".XXXXXXXX": "") == -1) 280 err(1, NULL); 281 282 return nfile; 283 } 284 285 /* 286 * Build local file name base on the URI and the rrdprepo info. 287 */ 288 static char * 289 rrdp_filename(const struct rrdprepo *rr, const char *uri, int temp) 290 { 291 char *nfile; 292 char *dir = rr->basedir; 293 294 if (temp) 295 dir = rr->temp; 296 297 if (!valid_uri(uri, strlen(uri), "rsync://")) { 298 warnx("%s: bad URI %s", rr->basedir, uri); 299 return NULL; 300 } 301 302 uri += strlen("rsync://"); /* skip proto */ 303 if (asprintf(&nfile, "%s/%s", dir, uri) == -1) 304 err(1, NULL); 305 return nfile; 306 } 307 308 /* 309 * Build RRDP state file name based on the repo info. 310 * If temp is set add Xs for mkostemp. 311 */ 312 static char * 313 rrdp_state_filename(const struct rrdprepo *rr, int temp) 314 { 315 char *nfile; 316 317 if (asprintf(&nfile, "%s/.state%s", rr->basedir, 318 temp ? ".XXXXXXXX": "") == -1) 319 err(1, NULL); 320 321 return nfile; 322 } 323 324 325 326 static void 327 ta_fetch(struct tarepo *tr) 328 { 329 logx("ta/%s: pulling from %s", tr->descr, tr->uri[tr->uriidx]); 330 331 if (strncasecmp(tr->uri[tr->uriidx], "rsync://", 8) == 0) { 332 /* 333 * Create destination location. 334 * Build up the tree to this point. 335 */ 336 rsync_fetch(tr->id, tr->uri[tr->uriidx], tr->basedir); 337 } else { 338 int fd; 339 340 tr->temp = ta_filename(tr, 1); 341 fd = mkostemp(tr->temp, O_CLOEXEC); 342 if (fd == -1) { 343 err(1, "mkostemp: %s", tr->temp); 344 /* XXX switch to soft fail and restart with next file */ 345 } 346 if (fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) 347 warn("fchmod: %s", tr->temp); 348 349 http_fetch(tr->id, tr->uri[tr->uriidx], NULL, fd); 350 } 351 } 352 353 static struct tarepo * 354 ta_get(struct tal *tal) 355 { 356 struct tarepo *tr; 357 358 /* no need to look for possible other repo */ 359 360 if (tal->urisz == 0) 361 errx(1, "TAL %s has no URI", tal->descr); 362 363 if ((tr = calloc(1, sizeof(*tr))) == NULL) 364 err(1, NULL); 365 tr->id = ++repoid; 366 SLIST_INSERT_HEAD(&tarepos, tr, entry); 367 368 if ((tr->descr = strdup(tal->descr)) == NULL) 369 err(1, NULL); 370 if (asprintf(&tr->basedir, "ta/%s", tal->descr) == -1) 371 err(1, NULL); 372 373 /* steal URI infromation from TAL */ 374 tr->urisz = tal->urisz; 375 tr->uri = tal->uri; 376 tal->urisz = 0; 377 tal->uri = NULL; 378 379 /* create base directory */ 380 if (mkpath(tr->basedir) == -1) 381 err(1, "%s", tr->basedir); 382 383 if (noop) { 384 tr->state = REPO_DONE; 385 logx("ta/%s: using cache", tr->descr); 386 /* there is nothing in the queue so no need to flush */ 387 } else 388 ta_fetch(tr); 389 390 return tr; 391 } 392 393 static struct tarepo * 394 ta_find(size_t id) 395 { 396 struct tarepo *tr; 397 398 SLIST_FOREACH(tr, &tarepos, entry) 399 if (id == tr->id) 400 break; 401 return tr; 402 } 403 404 static void 405 ta_free(void) 406 { 407 struct tarepo *tr; 408 409 while ((tr = SLIST_FIRST(&tarepos)) != NULL) { 410 SLIST_REMOVE_HEAD(&tarepos, entry); 411 free(tr->descr); 412 free(tr->basedir); 413 free(tr->temp); 414 free(tr->uri); 415 free(tr); 416 } 417 } 418 419 static struct rsyncrepo * 420 rsync_get(const char *uri) 421 { 422 struct rsyncrepo *rr; 423 char *repo; 424 425 if ((repo = rsync_base_uri(uri)) == NULL) 426 errx(1, "bad caRepository URI: %s", uri); 427 428 SLIST_FOREACH(rr, &rsyncrepos, entry) 429 if (strcmp(rr->repouri, repo) == 0) { 430 free(repo); 431 return rr; 432 } 433 434 if ((rr = calloc(1, sizeof(*rr))) == NULL) 435 err(1, NULL); 436 437 rr->id = ++repoid; 438 SLIST_INSERT_HEAD(&rsyncrepos, rr, entry); 439 440 rr->repouri = repo; 441 rr->basedir = rsync_dir(repo, "rsync"); 442 443 /* create base directory */ 444 if (mkpath(rr->basedir) == -1) 445 err(1, "%s", rr->basedir); 446 447 if (noop) { 448 rr->state = REPO_DONE; 449 logx("%s: using cache", rr->basedir); 450 /* there is nothing in the queue so no need to flush */ 451 } else { 452 logx("%s: pulling from %s", rr->basedir, rr->repouri); 453 rsync_fetch(rr->id, rr->repouri, rr->basedir); 454 } 455 456 return rr; 457 } 458 459 static struct rsyncrepo * 460 rsync_find(size_t id) 461 { 462 struct rsyncrepo *rr; 463 464 SLIST_FOREACH(rr, &rsyncrepos, entry) 465 if (id == rr->id) 466 break; 467 return rr; 468 } 469 470 static void 471 rsync_free(void) 472 { 473 struct rsyncrepo *rr; 474 475 while ((rr = SLIST_FIRST(&rsyncrepos)) != NULL) { 476 SLIST_REMOVE_HEAD(&rsyncrepos, entry); 477 free(rr->repouri); 478 free(rr->basedir); 479 free(rr); 480 } 481 } 482 483 static void rrdprepo_fetch(struct rrdprepo *); 484 485 static struct rrdprepo * 486 rrdp_get(const char *uri) 487 { 488 struct rrdprepo *rr; 489 490 SLIST_FOREACH(rr, &rrdprepos, entry) 491 if (strcmp(rr->notifyuri, uri) == 0) { 492 if (rr->state == REPO_FAILED) 493 return NULL; 494 return rr; 495 } 496 497 if ((rr = calloc(1, sizeof(*rr))) == NULL) 498 err(1, NULL); 499 500 rr->id = ++repoid; 501 SLIST_INSERT_HEAD(&rrdprepos, rr, entry); 502 503 if ((rr->notifyuri = strdup(uri)) == NULL) 504 err(1, NULL); 505 rr->basedir = hash_dir(uri, "rrdp"); 506 507 RB_INIT(&rr->added); 508 RB_INIT(&rr->deleted); 509 510 /* create base directory */ 511 if (mkpath(rr->basedir) == -1) 512 err(1, "%s", rr->basedir); 513 514 if (noop) { 515 rr->state = REPO_DONE; 516 logx("%s: using cache", rr->notifyuri); 517 /* there is nothing in the queue so no need to flush */ 518 } else { 519 logx("%s: pulling from %s", rr->notifyuri, "network"); 520 rrdprepo_fetch(rr); 521 } 522 523 return rr; 524 } 525 526 static struct rrdprepo * 527 rrdp_find(size_t id) 528 { 529 struct rrdprepo *rr; 530 531 SLIST_FOREACH(rr, &rrdprepos, entry) 532 if (id == rr->id) 533 break; 534 return rr; 535 } 536 537 static void 538 rrdp_free(void) 539 { 540 struct rrdprepo *rr; 541 542 while ((rr = SLIST_FIRST(&rrdprepos)) != NULL) { 543 SLIST_REMOVE_HEAD(&rrdprepos, entry); 544 545 free(rr->notifyuri); 546 free(rr->basedir); 547 free(rr->temp); 548 549 filepath_free(&rr->added); 550 filepath_free(&rr->deleted); 551 552 free(rr); 553 } 554 } 555 556 static struct rrdprepo * 557 rrdp_basedir(const char *dir) 558 { 559 struct rrdprepo *rr; 560 561 SLIST_FOREACH(rr, &rrdprepos, entry) 562 if (strcmp(dir, rr->basedir) == 0) { 563 if (rr->state == REPO_FAILED) 564 return NULL; 565 return rr; 566 } 567 568 return NULL; 569 } 570 571 /* 572 * Allocate and insert a new repository. 573 */ 574 static struct repo * 575 repo_alloc(void) 576 { 577 struct repo *rp; 578 579 if ((rp = calloc(1, sizeof(*rp))) == NULL) 580 err(1, NULL); 581 582 rp->id = ++repoid; 583 TAILQ_INIT(&rp->queue); 584 SLIST_INSERT_HEAD(&repos, rp, entry); 585 586 stats.repos++; 587 return rp; 588 } 589 590 /* 591 * Return the state of a repository. 592 */ 593 static enum repo_state 594 repo_state(struct repo *rp) 595 { 596 if (rp->ta) 597 return rp->ta->state; 598 if (rp->rrdp) 599 return rp->rrdp->state; 600 if (rp->rsync) 601 return rp->rsync->state; 602 errx(1, "%s: bad repo", rp->repouri); 603 } 604 605 #if 0 606 /* 607 * locate a repository by ID. 608 */ 609 static struct repo * 610 repo_find(size_t id) 611 { 612 struct repo *rp; 613 614 SLIST_FOREACH(rp, &repos, entry) 615 if (id == rp->id) 616 break; 617 return rp; 618 } 619 #endif 620 621 622 /* 623 * Parse the RRDP state file if it exists and set the session struct 624 * based on that information. 625 */ 626 static void 627 rrdp_parse_state(const struct rrdprepo *rr, struct rrdp_session *state) 628 { 629 FILE *f; 630 int fd, ln = 0; 631 const char *errstr; 632 char *line = NULL, *file; 633 size_t len = 0; 634 ssize_t n; 635 636 file = rrdp_state_filename(rr, 0); 637 if ((fd = open(file, O_RDONLY)) == -1) { 638 if (errno != ENOENT) 639 warn("%s: open state file", rr->basedir); 640 free(file); 641 return; 642 } 643 free(file); 644 f = fdopen(fd, "r"); 645 if (f == NULL) 646 err(1, "fdopen"); 647 648 while ((n = getline(&line, &len, f)) != -1) { 649 if (line[n - 1] == '\n') 650 line[n - 1] = '\0'; 651 switch (ln) { 652 case 0: 653 if ((state->session_id = strdup(line)) == NULL) 654 err(1, NULL); 655 break; 656 case 1: 657 state->serial = strtonum(line, 1, LLONG_MAX, &errstr); 658 if (errstr) 659 goto fail; 660 break; 661 case 2: 662 if ((state->last_mod = strdup(line)) == NULL) 663 err(1, NULL); 664 break; 665 default: 666 goto fail; 667 } 668 ln++; 669 } 670 671 free(line); 672 if (ferror(f)) 673 goto fail; 674 fclose(f); 675 return; 676 677 fail: 678 warnx("%s: troubles reading state file", rr->basedir); 679 fclose(f); 680 free(state->session_id); 681 free(state->last_mod); 682 memset(state, 0, sizeof(*state)); 683 } 684 685 /* 686 * Carefully write the RRDP session state file back. 687 */ 688 void 689 rrdp_save_state(size_t id, struct rrdp_session *state) 690 { 691 struct rrdprepo *rr; 692 char *temp, *file; 693 FILE *f; 694 int fd; 695 696 rr = rrdp_find(id); 697 if (rr == NULL) 698 errx(1, "non-existant rrdp repo %zu", id); 699 700 file = rrdp_state_filename(rr, 0); 701 temp = rrdp_state_filename(rr, 1); 702 703 if ((fd = mkostemp(temp, O_CLOEXEC)) == -1) 704 err(1, "%s: mkostemp: %s", rr->basedir, temp); 705 (void) fchmod(fd, 0644); 706 f = fdopen(fd, "w"); 707 if (f == NULL) 708 err(1, "fdopen"); 709 710 /* write session state file out */ 711 if (fprintf(f, "%s\n%lld\n", state->session_id, 712 state->serial) < 0) { 713 fclose(f); 714 goto fail; 715 } 716 if (state->last_mod != NULL) { 717 if (fprintf(f, "%s\n", state->last_mod) < 0) { 718 fclose(f); 719 goto fail; 720 } 721 } 722 if (fclose(f) != 0) 723 goto fail; 724 725 if (rename(temp, file) == -1) 726 warn("%s: rename state file", rr->basedir); 727 728 free(temp); 729 free(file); 730 return; 731 732 fail: 733 warnx("%s: failed to save state", rr->basedir); 734 unlink(temp); 735 free(temp); 736 free(file); 737 } 738 739 int 740 rrdp_handle_file(size_t id, enum publish_type pt, char *uri, 741 char *hash, size_t hlen, char *data, size_t dlen) 742 { 743 struct rrdprepo *rr; 744 struct filepath *fp; 745 ssize_t s; 746 char *fn; 747 int fd; 748 749 rr = rrdp_find(id); 750 if (rr == NULL) 751 errx(1, "non-existant rrdp repo %zu", id); 752 753 /* belt and suspenders */ 754 if (!valid_uri(uri, strlen(uri), "rsync://")) { 755 warnx("%s: bad file URI", rr->basedir); 756 return 0; 757 } 758 759 if (pt == PUB_UPD || pt == PUB_DEL) { 760 if (filepath_exists(&rr->deleted, uri)) { 761 warnx("%s: already deleted", uri); 762 return 0; 763 } 764 fp = filepath_find(&rr->added, uri); 765 if (fp == NULL) { 766 if ((fn = rrdp_filename(rr, uri, 0)) == NULL) 767 return 0; 768 } else { 769 filepath_put(&rr->added, fp); 770 if ((fn = rrdp_filename(rr, uri, 1)) == NULL) 771 return 0; 772 } 773 if (!valid_filehash(fn, hash, hlen)) { 774 warnx("%s: bad message digest", fn); 775 free(fn); 776 return 0; 777 } 778 free(fn); 779 } 780 781 if (pt == PUB_DEL) { 782 filepath_add(&rr->deleted, uri); 783 } else { 784 fp = filepath_find(&rr->deleted, uri); 785 if (fp != NULL) 786 filepath_put(&rr->deleted, fp); 787 788 /* add new file to temp dir */ 789 if ((fn = rrdp_filename(rr, uri, 1)) == NULL) 790 return 0; 791 792 repo_mkpath(fn); 793 fd = open(fn, O_WRONLY|O_CREAT|O_TRUNC, 0644); 794 if (fd == -1) { 795 warn("open %s", fn); 796 free(fn); 797 return 0; 798 } 799 800 if ((s = write(fd, data, dlen)) == -1) { 801 warn("write %s", fn); 802 free(fn); 803 close(fd); 804 return 0; 805 } 806 close(fd); 807 if ((size_t)s != dlen) { 808 warnx("short write %s", fn); 809 free(fn); 810 return 0; 811 } 812 free(fn); 813 filepath_add(&rr->added, uri); 814 } 815 816 return 1; 817 } 818 819 /* 820 * Initiate a RRDP sync, create the required temporary directory and 821 * parse a possible state file before sending the request to the RRDP process. 822 */ 823 static void 824 rrdprepo_fetch(struct rrdprepo *rr) 825 { 826 struct rrdp_session state = { 0 }; 827 828 if (asprintf(&rr->temp, "%s.XXXXXXXX", rr->basedir) == -1) 829 err(1, NULL); 830 if (mkdtemp(rr->temp) == NULL) 831 err(1, "mkdtemp %s", rr->temp); 832 833 rrdp_parse_state(rr, &state); 834 rrdp_fetch(rr->id, rr->notifyuri, rr->notifyuri, &state); 835 836 free(state.session_id); 837 free(state.last_mod); 838 } 839 840 static void 841 rrdp_merge_repo(struct rrdprepo *rr) 842 { 843 struct filepath *fp, *nfp; 844 char *fn, *rfn; 845 846 RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) { 847 fn = rrdp_filename(rr, fp->file, 1); 848 rfn = rrdp_filename(rr, fp->file, 0); 849 850 if (fn == NULL || rfn == NULL) 851 errx(1, "bad filepath"); /* should not happen */ 852 853 repo_mkpath(rfn); 854 if (rename(fn, rfn) == -1) 855 warn("%s: rename", rfn); 856 857 free(rfn); 858 free(fn); 859 filepath_put(&rr->added, fp); 860 } 861 } 862 863 static void 864 rrdp_clean_temp(struct rrdprepo *rr) 865 { 866 struct filepath *fp, *nfp; 867 char *fn; 868 869 filepath_free(&rr->deleted); 870 871 RB_FOREACH_SAFE(fp, filepath_tree, &rr->added, nfp) { 872 if ((fn = rrdp_filename(rr, fp->file, 1)) != NULL) { 873 if (unlink(fn) == -1) 874 warn("%s: unlink", fn); 875 free(fn); 876 } 877 filepath_put(&rr->added, fp); 878 } 879 } 880 881 /* 882 * RSYNC sync finished, either with or without success. 883 */ 884 void 885 rsync_finish(size_t id, int ok) 886 { 887 struct rsyncrepo *rr; 888 struct tarepo *tr; 889 struct repo *rp; 890 891 tr = ta_find(id); 892 if (tr != NULL) { 893 if (ok) { 894 logx("ta/%s: loaded from network", tr->descr); 895 stats.rsync_repos++; 896 tr->state = REPO_DONE; 897 } else if (++tr->uriidx < tr->urisz) { 898 logx("ta/%s: load from network failed, retry", 899 tr->descr); 900 ta_fetch(tr); 901 return; 902 } else { 903 logx("ta/%s: load from network failed, " 904 "fallback to cache", tr->descr); 905 stats.rsync_fails++; 906 tr->state = REPO_FAILED; 907 } 908 SLIST_FOREACH(rp, &repos, entry) 909 if (rp->ta == tr) 910 entityq_flush(&rp->queue, rp); 911 912 return; 913 } 914 915 rr = rsync_find(id); 916 if (rr == NULL) 917 errx(1, "unknown rsync repo %zu", id); 918 919 if (ok) { 920 logx("%s: loaded from network", rr->basedir); 921 stats.rsync_repos++; 922 rr->state = REPO_DONE; 923 } else { 924 logx("%s: load from network failed, fallback to cache", 925 rr->basedir); 926 stats.rsync_fails++; 927 rr->state = REPO_FAILED; 928 } 929 930 SLIST_FOREACH(rp, &repos, entry) 931 if (rp->rsync == rr) 932 entityq_flush(&rp->queue, rp); 933 } 934 935 /* 936 * RRDP sync finshed, either with or without success. 937 */ 938 void 939 rrdp_finish(size_t id, int ok) 940 { 941 struct rrdprepo *rr; 942 struct repo *rp; 943 944 rr = rrdp_find(id); 945 if (rr == NULL) 946 errx(1, "unknown RRDP repo %zu", id); 947 948 if (ok) { 949 rrdp_merge_repo(rr); 950 logx("%s: loaded from network", rr->notifyuri); 951 rr->state = REPO_DONE; 952 stats.rrdp_repos++; 953 SLIST_FOREACH(rp, &repos, entry) 954 if (rp->rrdp == rr) 955 entityq_flush(&rp->queue, rp); 956 } else { 957 rrdp_clean_temp(rr); 958 stats.rrdp_fails++; 959 rr->state = REPO_FAILED; 960 logx("%s: load from network failed, fallback to rsync", 961 rr->notifyuri); 962 SLIST_FOREACH(rp, &repos, entry) 963 if (rp->rrdp == rr) { 964 rp->rrdp = NULL; 965 rp->rsync = rsync_get(rp->repouri); 966 /* need to check if it was already loaded */ 967 if (repo_state(rp) != REPO_LOADING) 968 entityq_flush(&rp->queue, rp); 969 } 970 } 971 } 972 973 /* 974 * Handle responses from the http process. For TA file, either rename 975 * or delete the temporary file. For RRDP requests relay the request 976 * over to the rrdp process. 977 */ 978 void 979 http_finish(size_t id, enum http_result res, const char *last_mod) 980 { 981 struct tarepo *tr; 982 struct repo *rp; 983 984 tr = ta_find(id); 985 if (tr == NULL) { 986 /* not a TA fetch therefor RRDP */ 987 rrdp_http_done(id, res, last_mod); 988 return; 989 } 990 991 /* Move downloaded TA file into place, or unlink on failure. */ 992 if (res == HTTP_OK) { 993 char *file; 994 995 file = ta_filename(tr, 0); 996 if (rename(tr->temp, file) == -1) 997 warn("rename to %s", file); 998 free(file); 999 1000 logx("ta/%s: loaded from network", tr->descr); 1001 tr->state = REPO_DONE; 1002 stats.http_repos++; 1003 } else { 1004 if (unlink(tr->temp) == -1) 1005 warn("unlink %s", tr->temp); 1006 1007 if (++tr->uriidx < tr->urisz) { 1008 logx("ta/%s: load from network failed, retry", 1009 tr->descr); 1010 ta_fetch(tr); 1011 return; 1012 } 1013 1014 tr->state = REPO_FAILED; 1015 logx("ta/%s: load from network failed, " 1016 "fallback to cache", tr->descr); 1017 } 1018 1019 SLIST_FOREACH(rp, &repos, entry) 1020 if (rp->ta == tr) 1021 entityq_flush(&rp->queue, rp); 1022 } 1023 1024 1025 1026 /* 1027 * Look up a trust anchor, queueing it for download if not found. 1028 */ 1029 struct repo * 1030 ta_lookup(struct tal *tal) 1031 { 1032 struct repo *rp; 1033 1034 /* Look up in repository table. (Lookup should actually fail here) */ 1035 SLIST_FOREACH(rp, &repos, entry) { 1036 if (strcmp(rp->repouri, tal->descr) == 0) 1037 return rp; 1038 } 1039 1040 rp = repo_alloc(); 1041 if ((rp->repouri = strdup(tal->descr)) == NULL) 1042 err(1, NULL); 1043 rp->ta = ta_get(tal); 1044 1045 return rp; 1046 } 1047 1048 /* 1049 * Look up a repository, queueing it for discovery if not found. 1050 */ 1051 struct repo * 1052 repo_lookup(const char *uri, const char *notify) 1053 { 1054 struct repo *rp; 1055 1056 /* Look up in repository table. */ 1057 SLIST_FOREACH(rp, &repos, entry) { 1058 if (strcmp(rp->repouri, uri) != 0) 1059 continue; 1060 return rp; 1061 } 1062 1063 rp = repo_alloc(); 1064 if ((rp->repouri = strdup(uri)) == NULL) 1065 err(1, NULL); 1066 1067 /* try RRDP first if available */ 1068 if (notify != NULL) 1069 rp->rrdp = rrdp_get(notify); 1070 if (rp->rrdp == NULL) 1071 rp->rsync = rsync_get(uri); 1072 1073 return rp; 1074 } 1075 1076 /* 1077 * Build local file name base on the URI and the repo info. 1078 */ 1079 char * 1080 repo_filename(const struct repo *rp, const char *uri) 1081 { 1082 char *nfile; 1083 char *dir, *repouri; 1084 1085 if (uri == NULL && rp->ta) 1086 return ta_filename(rp->ta, 0); 1087 1088 assert(uri != NULL); 1089 if (rp->rrdp) 1090 return rrdp_filename(rp->rrdp, uri, 0); 1091 1092 /* must be rsync */ 1093 dir = rp->rsync->basedir; 1094 repouri = rp->rsync->repouri; 1095 1096 if (strstr(uri, repouri) != uri) { 1097 warnx("%s: URI %s outside of repository", repouri, uri); 1098 return NULL; 1099 } 1100 1101 uri += strlen(repouri) + 1; /* skip base and '/' */ 1102 1103 if (asprintf(&nfile, "%s/%s", dir, uri) == -1) 1104 err(1, NULL); 1105 return nfile; 1106 } 1107 1108 int 1109 repo_queued(struct repo *rp, struct entity *p) 1110 { 1111 if (repo_state(rp) == REPO_LOADING) { 1112 TAILQ_INSERT_TAIL(&rp->queue, p, entries); 1113 return 1; 1114 } 1115 return 0; 1116 } 1117 1118 static char ** 1119 add_to_del(char **del, size_t *dsz, char *file) 1120 { 1121 size_t i = *dsz; 1122 1123 del = reallocarray(del, i + 1, sizeof(*del)); 1124 if (del == NULL) 1125 err(1, NULL); 1126 if ((del[i] = strdup(file)) == NULL) 1127 err(1, NULL); 1128 *dsz = i + 1; 1129 return del; 1130 } 1131 1132 static char ** 1133 repo_rrdp_cleanup(struct filepath_tree *tree, struct rrdprepo *rr, 1134 char **del, size_t *delsz) 1135 { 1136 struct filepath *fp, *nfp; 1137 char *fn; 1138 1139 RB_FOREACH_SAFE(fp, filepath_tree, &rr->deleted, nfp) { 1140 fn = rrdp_filename(rr, fp->file, 0); 1141 /* temp dir will be cleaned up by repo_cleanup() */ 1142 1143 if (fn == NULL) 1144 errx(1, "bad filepath"); /* should not happen */ 1145 1146 if (!filepath_exists(tree, fn)) 1147 del = add_to_del(del, delsz, fn); 1148 else 1149 warnx("%s: referenced file supposed to be deleted", fn); 1150 1151 free(fn); 1152 filepath_put(&rr->deleted, fp); 1153 } 1154 1155 return del; 1156 } 1157 1158 void 1159 repo_cleanup(struct filepath_tree *tree) 1160 { 1161 size_t i, cnt, delsz = 0, dirsz = 0; 1162 char **del = NULL, **dir = NULL; 1163 char *argv[4] = { "ta", "rsync", "rrdp", NULL }; 1164 struct rrdprepo *rr; 1165 FTS *fts; 1166 FTSENT *e; 1167 1168 if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, NULL)) == NULL) 1169 err(1, "fts_open"); 1170 errno = 0; 1171 while ((e = fts_read(fts)) != NULL) { 1172 switch (e->fts_info) { 1173 case FTS_NSOK: 1174 if (!filepath_exists(tree, e->fts_path)) 1175 del = add_to_del(del, &delsz, 1176 e->fts_path); 1177 break; 1178 case FTS_D: 1179 /* special cleanup for rrdp directories */ 1180 if ((rr = rrdp_basedir(e->fts_path)) != NULL) { 1181 del = repo_rrdp_cleanup(tree, rr, del, &delsz); 1182 if (fts_set(fts, e, FTS_SKIP) == -1) 1183 err(1, "fts_set"); 1184 } 1185 break; 1186 case FTS_DP: 1187 if (!filepath_dir_exists(tree, e->fts_path)) 1188 dir = add_to_del(dir, &dirsz, 1189 e->fts_path); 1190 break; 1191 case FTS_SL: 1192 case FTS_SLNONE: 1193 warnx("symlink %s", e->fts_path); 1194 del = add_to_del(del, &delsz, e->fts_path); 1195 break; 1196 case FTS_NS: 1197 case FTS_ERR: 1198 if (e->fts_errno == ENOENT && 1199 (strcmp(e->fts_path, "rsync") == 0 || 1200 strcmp(e->fts_path, "rrdp") == 0)) 1201 continue; 1202 warnx("fts_read %s: %s", e->fts_path, 1203 strerror(e->fts_errno)); 1204 break; 1205 default: 1206 warnx("unhandled[%x] %s", e->fts_info, 1207 e->fts_path); 1208 break; 1209 } 1210 1211 errno = 0; 1212 } 1213 if (errno) 1214 err(1, "fts_read"); 1215 if (fts_close(fts) == -1) 1216 err(1, "fts_close"); 1217 1218 cnt = 0; 1219 for (i = 0; i < delsz; i++) { 1220 if (unlink(del[i]) == -1) { 1221 if (errno != ENOENT) 1222 warn("unlink %s", del[i]); 1223 } else { 1224 if (verbose > 1) 1225 logx("deleted %s", del[i]); 1226 cnt++; 1227 } 1228 free(del[i]); 1229 } 1230 free(del); 1231 stats.del_files = cnt; 1232 1233 cnt = 0; 1234 for (i = 0; i < dirsz; i++) { 1235 if (rmdir(dir[i]) == -1) 1236 warn("rmdir %s", dir[i]); 1237 else 1238 cnt++; 1239 free(dir[i]); 1240 } 1241 free(dir); 1242 stats.del_dirs = cnt; 1243 } 1244 1245 void 1246 repo_free(void) 1247 { 1248 struct repo *rp; 1249 1250 while ((rp = SLIST_FIRST(&repos)) != NULL) { 1251 SLIST_REMOVE_HEAD(&repos, entry); 1252 free(rp->repouri); 1253 free(rp); 1254 } 1255 1256 ta_free(); 1257 rrdp_free(); 1258 rsync_free(); 1259 } 1260