main.c revision 1.125
1/* $OpenBSD: main.c,v 1.125 2021/03/26 16:03:29 claudio Exp $ */ 2/* 3 * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/queue.h> 19#include <sys/socket.h> 20#include <sys/resource.h> 21#include <sys/stat.h> 22#include <sys/tree.h> 23#include <sys/types.h> 24#include <sys/wait.h> 25 26#include <assert.h> 27#include <err.h> 28#include <errno.h> 29#include <dirent.h> 30#include <fcntl.h> 31#include <fnmatch.h> 32#include <fts.h> 33#include <poll.h> 34#include <pwd.h> 35#include <stdio.h> 36#include <stdlib.h> 37#include <signal.h> 38#include <string.h> 39#include <limits.h> 40#include <syslog.h> 41#include <unistd.h> 42#include <imsg.h> 43 44#include "extern.h" 45 46/* 47 * Maximum number of TAL files we'll load. 48 */ 49#define TALSZ_MAX 8 50 51/* 52 * An rsync repository. 53 */ 54#define REPO_MAX_URI 2 55struct repo { 56 SLIST_ENTRY(repo) entry; 57 char *repouri; /* CA repository base URI */ 58 char *local; /* local path name */ 59 char *temp; /* temporary file / dir */ 60 char *uris[REPO_MAX_URI]; /* URIs to fetch from */ 61 struct entityq queue; /* files waiting for this repo */ 62 size_t id; /* identifier (array index) */ 63 int uriidx; /* which URI is fetched */ 64 int loaded; /* whether loaded or not */ 65}; 66 67size_t entity_queue; 68int timeout = 60*60; 69volatile sig_atomic_t killme; 70void suicide(int sig); 71 72/* 73 * Table of all known repositories. 74 */ 75SLIST_HEAD(, repo) repos = SLIST_HEAD_INITIALIZER(repos); 76size_t repoid; 77 78/* 79 * Database of all file path accessed during a run. 80 */ 81struct filepath { 82 RB_ENTRY(filepath) entry; 83 char *file; 84}; 85 86static inline int 87filepathcmp(struct filepath *a, struct filepath *b) 88{ 89 return strcmp(a->file, b->file); 90} 91 92RB_HEAD(filepath_tree, filepath); 93RB_PROTOTYPE(filepath_tree, filepath, entry, filepathcmp); 94 95static struct filepath_tree fpt = RB_INITIALIZER(&fpt); 96static struct msgbuf procq, rsyncq, httpq; 97static int cachefd, outdirfd; 98 99const char *bird_tablename = "ROAS"; 100 101int verbose; 102int noop; 103 104struct stats stats; 105 106static void repo_fetch(struct repo *); 107static char *ta_filename(const struct repo *, int); 108 109/* 110 * Log a message to stderr if and only if "verbose" is non-zero. 111 * This uses the err(3) functionality. 112 */ 113void 114logx(const char *fmt, ...) 115{ 116 va_list ap; 117 118 if (verbose && fmt != NULL) { 119 va_start(ap, fmt); 120 vwarnx(fmt, ap); 121 va_end(ap); 122 } 123} 124 125/* 126 * Functions to lookup which files have been accessed during computation. 127 */ 128static int 129filepath_add(char *file) 130{ 131 struct filepath *fp; 132 133 if ((fp = malloc(sizeof(*fp))) == NULL) 134 err(1, NULL); 135 if ((fp->file = strdup(file)) == NULL) 136 err(1, NULL); 137 138 if (RB_INSERT(filepath_tree, &fpt, fp) != NULL) { 139 /* already in the tree */ 140 free(fp->file); 141 free(fp); 142 return 0; 143 } 144 145 return 1; 146} 147 148static int 149filepath_exists(char *file) 150{ 151 struct filepath needle; 152 153 needle.file = file; 154 return RB_FIND(filepath_tree, &fpt, &needle) != NULL; 155} 156 157RB_GENERATE(filepath_tree, filepath, entry, filepathcmp); 158 159void 160entity_free(struct entity *ent) 161{ 162 163 if (ent == NULL) 164 return; 165 166 free(ent->pkey); 167 free(ent->file); 168 free(ent->descr); 169 free(ent); 170} 171 172/* 173 * Read a queue entity from the descriptor. 174 * Matched by entity_buffer_req(). 175 * The pointer must be passed entity_free(). 176 */ 177void 178entity_read_req(int fd, struct entity *ent) 179{ 180 181 io_simple_read(fd, &ent->type, sizeof(enum rtype)); 182 io_str_read(fd, &ent->file); 183 io_simple_read(fd, &ent->has_pkey, sizeof(int)); 184 if (ent->has_pkey) 185 io_buf_read_alloc(fd, (void **)&ent->pkey, &ent->pkeysz); 186 io_str_read(fd, &ent->descr); 187} 188 189/* 190 * Write the queue entity. 191 * Matched by entity_read_req(). 192 */ 193static void 194entity_write_req(const struct entity *ent) 195{ 196 struct ibuf *b; 197 198 if ((b = ibuf_dynamic(sizeof(*ent), UINT_MAX)) == NULL) 199 err(1, NULL); 200 io_simple_buffer(b, &ent->type, sizeof(ent->type)); 201 io_str_buffer(b, ent->file); 202 io_simple_buffer(b, &ent->has_pkey, sizeof(int)); 203 if (ent->has_pkey) 204 io_buf_buffer(b, ent->pkey, ent->pkeysz); 205 io_str_buffer(b, ent->descr); 206 ibuf_close(&procq, b); 207} 208 209/* 210 * Scan through all queued requests and see which ones are in the given 211 * repo, then flush those into the parser process. 212 */ 213static void 214entityq_flush(struct repo *repo) 215{ 216 struct entity *p, *np; 217 218 TAILQ_FOREACH_SAFE(p, &repo->queue, entries, np) { 219 entity_write_req(p); 220 TAILQ_REMOVE(&repo->queue, p, entries); 221 entity_free(p); 222 } 223} 224 225/* 226 * Add the heap-allocated file to the queue for processing. 227 */ 228static void 229entityq_add(char *file, enum rtype type, struct repo *rp, 230 const unsigned char *pkey, size_t pkeysz, char *descr) 231{ 232 struct entity *p; 233 234 if (filepath_add(file) == 0) { 235 warnx("%s: File already visited", file); 236 return; 237 } 238 239 if ((p = calloc(1, sizeof(struct entity))) == NULL) 240 err(1, NULL); 241 242 p->type = type; 243 p->file = file; 244 p->has_pkey = pkey != NULL; 245 if (p->has_pkey) { 246 p->pkeysz = pkeysz; 247 if ((p->pkey = malloc(pkeysz)) == NULL) 248 err(1, NULL); 249 memcpy(p->pkey, pkey, pkeysz); 250 } 251 if (descr != NULL) 252 if ((p->descr = strdup(descr)) == NULL) 253 err(1, NULL); 254 255 entity_queue++; 256 257 /* 258 * Write to the queue if there's no repo or the repo has already 259 * been loaded else enqueue it for later. 260 */ 261 262 if (rp == NULL || rp->loaded) { 263 entity_write_req(p); 264 entity_free(p); 265 } else 266 TAILQ_INSERT_TAIL(&rp->queue, p, entries); 267} 268 269/* 270 * Allocate and insert a new repository. 271 */ 272static struct repo * 273repo_alloc(void) 274{ 275 struct repo *rp; 276 277 if ((rp = calloc(1, sizeof(*rp))) == NULL) 278 err(1, NULL); 279 280 rp->id = ++repoid; 281 TAILQ_INIT(&rp->queue); 282 SLIST_INSERT_HEAD(&repos, rp, entry); 283 284 return rp; 285} 286 287static struct repo * 288repo_find(size_t id) 289{ 290 struct repo *rp; 291 292 SLIST_FOREACH(rp, &repos, entry) 293 if (id == rp->id) 294 break; 295 return rp; 296} 297 298static void 299http_ta_fetch(struct repo *rp) 300{ 301 struct ibuf *b; 302 int filefd; 303 304 rp->temp = ta_filename(rp, 1); 305 306 filefd = mkostemp(rp->temp, O_CLOEXEC); 307 if (filefd == -1) 308 err(1, "mkostemp: %s", rp->temp); 309 if (fchmod(filefd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) 310 warn("fchmod: %s", rp->temp); 311 312 if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL) 313 err(1, NULL); 314 io_simple_buffer(b, &rp->id, sizeof(rp->id)); 315 io_str_buffer(b, rp->uris[rp->uriidx]); 316 /* TODO last modified time */ 317 io_str_buffer(b, NULL); 318 /* pass file as fd */ 319 b->fd = filefd; 320 ibuf_close(&httpq, b); 321} 322 323static int 324http_done(struct repo *rp, enum http_result res) 325{ 326 if (rp->repouri == NULL) { 327 /* Move downloaded TA file into place, or unlink on failure. */ 328 if (res == HTTP_OK) { 329 char *file; 330 331 file = ta_filename(rp, 0); 332 if (renameat(cachefd, rp->temp, cachefd, file) == -1) 333 warn("rename to %s", file); 334 } else { 335 if (unlinkat(cachefd, rp->temp, 0) == -1) 336 warn("unlink %s", rp->temp); 337 } 338 free(rp->temp); 339 rp->temp = NULL; 340 } 341 342 if (res == HTTP_OK) 343 logx("%s: loaded from network", rp->local); 344 else if (rp->uriidx < REPO_MAX_URI - 1 && 345 rp->uris[rp->uriidx + 1] != NULL) { 346 logx("%s: load from network failed, retry", rp->local); 347 348 rp->uriidx++; 349 repo_fetch(rp); 350 return 0; 351 } else 352 logx("%s: load from network failed, " 353 "fallback to cache", rp->local); 354 355 return 1; 356} 357 358static void 359repo_fetch(struct repo *rp) 360{ 361 struct ibuf *b; 362 363 if (noop) { 364 rp->loaded = 1; 365 logx("%s: using cache", rp->local); 366 stats.repos++; 367 /* there is nothing in the queue so no need to flush */ 368 return; 369 } 370 371 /* 372 * Create destination location. 373 * Build up the tree to this point. 374 */ 375 376 if (mkpath(rp->local) == -1) 377 err(1, "%s", rp->local); 378 379 logx("%s: pulling from %s", rp->local, rp->uris[rp->uriidx]); 380 381 if (strncasecmp(rp->uris[rp->uriidx], "rsync://", 8) == 0) { 382 if ((b = ibuf_dynamic(256, UINT_MAX)) == NULL) 383 err(1, NULL); 384 io_simple_buffer(b, &rp->id, sizeof(rp->id)); 385 io_str_buffer(b, rp->local); 386 io_str_buffer(b, rp->uris[rp->uriidx]); 387 ibuf_close(&rsyncq, b); 388 } else { 389 /* 390 * Two cases for https. TA files load directly while 391 * for RRDP XML files are downloaded and parsed to build 392 * the repo. TA repos have a NULL repouri. 393 */ 394 if (rp->repouri == NULL) { 395 http_ta_fetch(rp); 396 } 397 } 398} 399 400/* 401 * Look up a trust anchor, queueing it for download if not found. 402 */ 403static struct repo * 404ta_lookup(const struct tal *tal) 405{ 406 struct repo *rp; 407 char *local; 408 size_t i, j; 409 410 if (asprintf(&local, "ta/%s", tal->descr) == -1) 411 err(1, NULL); 412 413 /* Look up in repository table. (Lookup should actually fail here) */ 414 SLIST_FOREACH(rp, &repos, entry) { 415 if (strcmp(rp->local, local) != 0) 416 continue; 417 free(local); 418 return rp; 419 } 420 421 rp = repo_alloc(); 422 rp->local = local; 423 for (i = 0, j = 0; i < tal->urisz && j < 2; i++) { 424 if ((rp->uris[j++] = strdup(tal->uri[i])) == NULL) 425 err(1, NULL); 426 } 427 if (j == 0) 428 errx(1, "TAL %s has no URI", tal->descr); 429 430 repo_fetch(rp); 431 return rp; 432} 433 434/* 435 * Look up a repository, queueing it for discovery if not found. 436 */ 437static struct repo * 438repo_lookup(const char *uri) 439{ 440 char *local, *repo; 441 struct repo *rp; 442 443 if ((repo = rsync_base_uri(uri)) == NULL) 444 return NULL; 445 446 /* Look up in repository table. */ 447 SLIST_FOREACH(rp, &repos, entry) { 448 if (rp->repouri == NULL || 449 strcmp(rp->repouri, repo) != 0) 450 continue; 451 free(repo); 452 return rp; 453 } 454 455 rp = repo_alloc(); 456 rp->repouri = repo; 457 local = strchr(repo, ':') + strlen("://"); 458 if (asprintf(&rp->local, "rsync/%s", local) == -1) 459 err(1, NULL); 460 if ((rp->uris[0] = strdup(repo)) == NULL) 461 err(1, NULL); 462 463 repo_fetch(rp); 464 return rp; 465} 466 467static char * 468ta_filename(const struct repo *repo, int temp) 469{ 470 const char *file; 471 char *nfile; 472 473 /* does not matter which URI, all end with same filename */ 474 file = strrchr(repo->uris[0], '/'); 475 assert(file); 476 477 if (asprintf(&nfile, "%s%s%s", repo->local, file, 478 temp ? ".XXXXXXXX": "") == -1) 479 err(1, NULL); 480 481 return nfile; 482} 483 484/* 485 * Build local file name base on the URI and the repo info. 486 */ 487static char * 488repo_filename(const struct repo *repo, const char *uri) 489{ 490 char *nfile; 491 492 if (strstr(uri, repo->repouri) != uri) 493 errx(1, "%s: URI outside of repository", uri); 494 uri += strlen(repo->repouri) + 1; /* skip base and '/' */ 495 496 if (asprintf(&nfile, "%s/%s", repo->local, uri) == -1) 497 err(1, NULL); 498 return nfile; 499} 500 501/* 502 * Add a file (CER, ROA, CRL) from an MFT file, RFC 6486. 503 * These are always relative to the directory in which "mft" sits. 504 */ 505static void 506queue_add_from_mft(const char *mft, const struct mftfile *file, enum rtype type) 507{ 508 char *cp, *nfile; 509 510 /* Construct local path from filename. */ 511 cp = strrchr(mft, '/'); 512 assert(cp != NULL); 513 assert(cp - mft < INT_MAX); 514 if (asprintf(&nfile, "%.*s/%s", (int)(cp - mft), mft, file->file) == -1) 515 err(1, NULL); 516 517 /* 518 * Since we're from the same directory as the MFT file, we know 519 * that the repository has already been loaded. 520 */ 521 522 entityq_add(nfile, type, NULL, NULL, 0, NULL); 523} 524 525/* 526 * Loops over queue_add_from_mft() for all files. 527 * The order here is important: we want to parse the revocation 528 * list *before* we parse anything else. 529 * FIXME: set the type of file in the mftfile so that we don't need to 530 * keep doing the check (this should be done in the parser, where we 531 * check the suffix anyway). 532 */ 533static void 534queue_add_from_mft_set(const struct mft *mft) 535{ 536 size_t i, sz; 537 const struct mftfile *f; 538 539 for (i = 0; i < mft->filesz; i++) { 540 f = &mft->files[i]; 541 sz = strlen(f->file); 542 assert(sz > 4); 543 if (strcasecmp(f->file + sz - 4, ".crl") != 0) 544 continue; 545 queue_add_from_mft(mft->file, f, RTYPE_CRL); 546 } 547 548 for (i = 0; i < mft->filesz; i++) { 549 f = &mft->files[i]; 550 sz = strlen(f->file); 551 assert(sz > 4); 552 if (strcasecmp(f->file + sz - 4, ".crl") == 0) 553 continue; 554 else if (strcasecmp(f->file + sz - 4, ".cer") == 0) 555 queue_add_from_mft(mft->file, f, RTYPE_CER); 556 else if (strcasecmp(f->file + sz - 4, ".roa") == 0) 557 queue_add_from_mft(mft->file, f, RTYPE_ROA); 558 else if (strcasecmp(f->file + sz - 4, ".gbr") == 0) 559 queue_add_from_mft(mft->file, f, RTYPE_GBR); 560 else 561 logx("%s: unsupported file type: %s", mft->file, 562 f->file); 563 } 564} 565 566/* 567 * Add a local TAL file (RFC 7730) to the queue of files to fetch. 568 */ 569static void 570queue_add_tal(const char *file) 571{ 572 char *nfile, *buf; 573 574 if ((nfile = strdup(file)) == NULL) 575 err(1, NULL); 576 buf = tal_read_file(file); 577 578 /* Record tal for later reporting */ 579 if (stats.talnames == NULL) { 580 if ((stats.talnames = strdup(file)) == NULL) 581 err(1, NULL); 582 } else { 583 char *tmp; 584 if (asprintf(&tmp, "%s %s", stats.talnames, file) == -1) 585 err(1, NULL); 586 free(stats.talnames); 587 stats.talnames = tmp; 588 } 589 590 /* Not in a repository, so directly add to queue. */ 591 entityq_add(nfile, RTYPE_TAL, NULL, NULL, 0, buf); 592 /* entityq_add makes a copy of buf */ 593 free(buf); 594} 595 596/* 597 * Add URIs (CER) from a TAL file, RFC 8630. 598 */ 599static void 600queue_add_from_tal(const struct tal *tal) 601{ 602 char *nfile; 603 struct repo *repo; 604 605 assert(tal->urisz); 606 607 /* Look up the repository. */ 608 repo = ta_lookup(tal); 609 610 nfile = ta_filename(repo, 0); 611 entityq_add(nfile, RTYPE_CER, repo, tal->pkey, 612 tal->pkeysz, tal->descr); 613} 614 615/* 616 * Add a manifest (MFT) found in an X509 certificate, RFC 6487. 617 */ 618static void 619queue_add_from_cert(const struct cert *cert) 620{ 621 struct repo *repo; 622 char *nfile; 623 624 repo = repo_lookup(cert->mft); 625 if (repo == NULL) /* bad repository URI */ 626 return; 627 628 nfile = repo_filename(repo, cert->mft); 629 630 entityq_add(nfile, RTYPE_MFT, repo, NULL, 0, NULL); 631} 632 633/* 634 * Process parsed content. 635 * For non-ROAs, we grok for more data. 636 * For ROAs, we want to extract the valid info. 637 * In all cases, we gather statistics. 638 */ 639static void 640entity_process(int proc, struct stats *st, struct vrp_tree *tree) 641{ 642 enum rtype type; 643 struct tal *tal; 644 struct cert *cert; 645 struct mft *mft; 646 struct roa *roa; 647 int c; 648 649 /* 650 * For most of these, we first read whether there's any content 651 * at all---this means that the syntactic parse failed (X509 652 * certificate, for example). 653 * We follow that up with whether the resources didn't parse. 654 */ 655 io_simple_read(proc, &type, sizeof(type)); 656 657 switch (type) { 658 case RTYPE_TAL: 659 st->tals++; 660 tal = tal_read(proc); 661 queue_add_from_tal(tal); 662 tal_free(tal); 663 break; 664 case RTYPE_CER: 665 st->certs++; 666 io_simple_read(proc, &c, sizeof(int)); 667 if (c == 0) { 668 st->certs_fail++; 669 break; 670 } 671 cert = cert_read(proc); 672 if (cert->valid) { 673 /* 674 * Process the revocation list from the 675 * certificate *first*, since it might mark that 676 * we're revoked and then we don't want to 677 * process the MFT. 678 */ 679 queue_add_from_cert(cert); 680 } else 681 st->certs_invalid++; 682 cert_free(cert); 683 break; 684 case RTYPE_MFT: 685 st->mfts++; 686 io_simple_read(proc, &c, sizeof(int)); 687 if (c == 0) { 688 st->mfts_fail++; 689 break; 690 } 691 mft = mft_read(proc); 692 if (mft->stale) 693 st->mfts_stale++; 694 queue_add_from_mft_set(mft); 695 mft_free(mft); 696 break; 697 case RTYPE_CRL: 698 st->crls++; 699 break; 700 case RTYPE_ROA: 701 st->roas++; 702 io_simple_read(proc, &c, sizeof(int)); 703 if (c == 0) { 704 st->roas_fail++; 705 break; 706 } 707 roa = roa_read(proc); 708 if (roa->valid) 709 roa_insert_vrps(tree, roa, &st->vrps, &st->uniqs); 710 else 711 st->roas_invalid++; 712 roa_free(roa); 713 break; 714 case RTYPE_GBR: 715 st->gbrs++; 716 break; 717 default: 718 abort(); 719 } 720 721 entity_queue--; 722} 723 724/* 725 * Assign filenames ending in ".tal" in "/etc/rpki" into "tals", 726 * returning the number of files found and filled-in. 727 * This may be zero. 728 * Don't exceded "max" filenames. 729 */ 730static size_t 731tal_load_default(const char *tals[], size_t max) 732{ 733 static const char *confdir = "/etc/rpki"; 734 size_t s = 0; 735 char *path; 736 DIR *dirp; 737 struct dirent *dp; 738 739 dirp = opendir(confdir); 740 if (dirp == NULL) 741 err(1, "open %s", confdir); 742 while ((dp = readdir(dirp)) != NULL) { 743 if (fnmatch("*.tal", dp->d_name, FNM_PERIOD) == FNM_NOMATCH) 744 continue; 745 if (s >= max) 746 err(1, "too many tal files found in %s", 747 confdir); 748 if (asprintf(&path, "%s/%s", confdir, dp->d_name) == -1) 749 err(1, NULL); 750 tals[s++] = path; 751 } 752 closedir (dirp); 753 return (s); 754} 755 756static char ** 757add_to_del(char **del, size_t *dsz, char *file) 758{ 759 size_t i = *dsz; 760 761 del = reallocarray(del, i + 1, sizeof(*del)); 762 if (del == NULL) 763 err(1, NULL); 764 if ((del[i] = strdup(file)) == NULL) 765 err(1, NULL); 766 *dsz = i + 1; 767 return del; 768} 769 770static size_t 771repo_cleanup(void) 772{ 773 size_t i, delsz = 0; 774 char *argv[2], **del = NULL; 775 struct repo *rp; 776 FTS *fts; 777 FTSENT *e; 778 779 SLIST_FOREACH(rp, &repos, entry) { 780 argv[0] = rp->local; 781 argv[1] = NULL; 782 if ((fts = fts_open(argv, FTS_PHYSICAL | FTS_NOSTAT, 783 NULL)) == NULL) 784 err(1, "fts_open"); 785 errno = 0; 786 while ((e = fts_read(fts)) != NULL) { 787 switch (e->fts_info) { 788 case FTS_NSOK: 789 if (!filepath_exists(e->fts_path)) 790 del = add_to_del(del, &delsz, 791 e->fts_path); 792 break; 793 case FTS_D: 794 case FTS_DP: 795 /* TODO empty directory pruning */ 796 break; 797 case FTS_SL: 798 case FTS_SLNONE: 799 warnx("symlink %s", e->fts_path); 800 del = add_to_del(del, &delsz, e->fts_path); 801 break; 802 case FTS_NS: 803 case FTS_ERR: 804 warnx("fts_read %s: %s", e->fts_path, 805 strerror(e->fts_errno)); 806 break; 807 default: 808 warnx("unhandled[%x] %s", e->fts_info, 809 e->fts_path); 810 break; 811 } 812 813 errno = 0; 814 } 815 if (errno) 816 err(1, "fts_read"); 817 if (fts_close(fts) == -1) 818 err(1, "fts_close"); 819 } 820 821 for (i = 0; i < delsz; i++) { 822 if (unlink(del[i]) == -1) 823 warn("unlink %s", del[i]); 824 if (verbose > 1) 825 logx("deleted %s", del[i]); 826 free(del[i]); 827 } 828 free(del); 829 830 return delsz; 831} 832 833void 834suicide(int sig __attribute__((unused))) 835{ 836 killme = 1; 837 838} 839 840#define NPFD 3 841 842int 843main(int argc, char *argv[]) 844{ 845 int rc = 1, c, st, proc, rsync, http, ok, 846 fl = SOCK_STREAM | SOCK_CLOEXEC; 847 size_t i, id, outsz = 0, talsz = 0; 848 pid_t procpid, rsyncpid, httppid; 849 int fd[2]; 850 struct pollfd pfd[NPFD]; 851 struct msgbuf *queues[NPFD]; 852 struct roa **out = NULL; 853 struct repo *rp; 854 char *rsync_prog = "openrsync"; 855 char *bind_addr = NULL; 856 const char *cachedir = NULL, *outputdir = NULL; 857 const char *tals[TALSZ_MAX], *errs; 858 struct vrp_tree v = RB_INITIALIZER(&v); 859 struct rusage ru; 860 struct timeval start_time, now_time; 861 862 gettimeofday(&start_time, NULL); 863 864 /* If started as root, priv-drop to _rpki-client */ 865 if (getuid() == 0) { 866 struct passwd *pw; 867 868 pw = getpwnam("_rpki-client"); 869 if (!pw) 870 errx(1, "no _rpki-client user to revoke to"); 871 if (setgroups(1, &pw->pw_gid) == -1 || 872 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || 873 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) 874 err(1, "unable to revoke privs"); 875 876 } 877 cachedir = RPKI_PATH_BASE_DIR; 878 outputdir = RPKI_PATH_OUT_DIR; 879 880 if (pledge("stdio rpath wpath cpath inet fattr dns sendfd recvfd " 881 "proc exec unveil", NULL) == -1) 882 err(1, "pledge"); 883 884 while ((c = getopt(argc, argv, "b:Bcd:e:jnos:t:T:vV")) != -1) 885 switch (c) { 886 case 'b': 887 bind_addr = optarg; 888 break; 889 case 'B': 890 outformats |= FORMAT_BIRD; 891 break; 892 case 'c': 893 outformats |= FORMAT_CSV; 894 break; 895 case 'd': 896 cachedir = optarg; 897 break; 898 case 'e': 899 rsync_prog = optarg; 900 break; 901 case 'j': 902 outformats |= FORMAT_JSON; 903 break; 904 case 'n': 905 noop = 1; 906 break; 907 case 'o': 908 outformats |= FORMAT_OPENBGPD; 909 break; 910 case 's': 911 timeout = strtonum(optarg, 0, 24*60*60, &errs); 912 if (errs) 913 errx(1, "-s: %s", errs); 914 break; 915 case 't': 916 if (talsz >= TALSZ_MAX) 917 err(1, 918 "too many tal files specified"); 919 tals[talsz++] = optarg; 920 break; 921 case 'T': 922 bird_tablename = optarg; 923 break; 924 case 'v': 925 verbose++; 926 break; 927 case 'V': 928 errx(0, "version: %s", RPKI_VERSION); 929 default: 930 goto usage; 931 } 932 933 argv += optind; 934 argc -= optind; 935 if (argc == 1) 936 outputdir = argv[0]; 937 else if (argc > 1) 938 goto usage; 939 940 if (timeout) { 941 signal(SIGALRM, suicide); 942 /* Commit suicide eventually - cron will normally start a new one */ 943 alarm(timeout); 944 } 945 946 if (cachedir == NULL) { 947 warnx("cache directory required"); 948 goto usage; 949 } 950 if (outputdir == NULL) { 951 warnx("output directory required"); 952 goto usage; 953 } 954 955 if ((cachefd = open(cachedir, O_RDONLY, 0)) == -1) 956 err(1, "cache directory %s", cachedir); 957 if ((outdirfd = open(outputdir, O_RDONLY, 0)) == -1) 958 err(1, "output directory %s", outputdir); 959 960 if (outformats == 0) 961 outformats = FORMAT_OPENBGPD; 962 963 if (talsz == 0) 964 talsz = tal_load_default(tals, TALSZ_MAX); 965 if (talsz == 0) 966 err(1, "no TAL files found in %s", "/etc/rpki"); 967 968 /* 969 * Create the file reader as a jailed child process. 970 * It will be responsible for reading all of the files (ROAs, 971 * manifests, certificates, etc.) and returning contents. 972 */ 973 974 if (socketpair(AF_UNIX, fl, 0, fd) == -1) 975 err(1, "socketpair"); 976 if ((procpid = fork()) == -1) 977 err(1, "fork"); 978 979 if (procpid == 0) { 980 close(fd[1]); 981 982 /* change working directory to the cache directory */ 983 if (fchdir(cachefd) == -1) 984 err(1, "fchdir"); 985 986 /* Only allow access to the cache directory. */ 987 if (unveil(".", "r") == -1) 988 err(1, "%s: unveil", cachedir); 989 if (pledge("stdio rpath", NULL) == -1) 990 err(1, "pledge"); 991 proc_parser(fd[0]); 992 errx(1, "parser process returned"); 993 } 994 995 close(fd[0]); 996 proc = fd[1]; 997 998 /* 999 * Create a process that will do the rsync'ing. 1000 * This process is responsible for making sure that all the 1001 * repositories referenced by a certificate manifest (or the 1002 * TAL) exists and has been downloaded. 1003 */ 1004 1005 if (!noop) { 1006 if (socketpair(AF_UNIX, fl, 0, fd) == -1) 1007 err(1, "socketpair"); 1008 if ((rsyncpid = fork()) == -1) 1009 err(1, "fork"); 1010 1011 if (rsyncpid == 0) { 1012 close(proc); 1013 close(fd[1]); 1014 1015 /* change working directory to the cache directory */ 1016 if (fchdir(cachefd) == -1) 1017 err(1, "fchdir"); 1018 1019 if (pledge("stdio rpath proc exec unveil", NULL) == -1) 1020 err(1, "pledge"); 1021 1022 proc_rsync(rsync_prog, bind_addr, fd[0]); 1023 errx(1, "rsync process returned"); 1024 } 1025 1026 close(fd[0]); 1027 rsync = fd[1]; 1028 } else { 1029 rsync = -1; 1030 rsyncpid = -1; 1031 } 1032 1033 /* 1034 * Create a process that will fetch data via https. 1035 * With every request the http process receives a file descriptor 1036 * where the data should be written to. 1037 */ 1038 1039 if (!noop) { 1040 if (socketpair(AF_UNIX, fl, 0, fd) == -1) 1041 err(1, "socketpair"); 1042 if ((httppid = fork()) == -1) 1043 err(1, "fork"); 1044 1045 if (httppid == 0) { 1046 close(proc); 1047 close(rsync); 1048 close(fd[1]); 1049 1050 /* change working directory to the cache directory */ 1051 if (fchdir(cachefd) == -1) 1052 err(1, "fchdir"); 1053 1054 if (pledge("stdio rpath inet dns recvfd", NULL) == -1) 1055 err(1, "pledge"); 1056 1057 proc_http(bind_addr, fd[0]); 1058 errx(1, "http process returned"); 1059 } 1060 1061 close(fd[0]); 1062 http = fd[1]; 1063 } else { 1064 http = -1; 1065 httppid = -1; 1066 } 1067 1068 if (pledge("stdio rpath wpath cpath fattr sendfd", NULL) == -1) 1069 err(1, "pledge"); 1070 1071 msgbuf_init(&procq); 1072 msgbuf_init(&rsyncq); 1073 msgbuf_init(&httpq); 1074 procq.fd = proc; 1075 rsyncq.fd = rsync; 1076 httpq.fd = http; 1077 1078 /* 1079 * The main process drives the top-down scan to leaf ROAs using 1080 * data downloaded by the rsync process and parsed by the 1081 * parsing process. 1082 */ 1083 1084 pfd[0].fd = rsync; 1085 queues[0] = &rsyncq; 1086 pfd[1].fd = proc; 1087 queues[1] = &procq; 1088 pfd[2].fd = http; 1089 queues[2] = &httpq; 1090 1091 /* 1092 * Prime the process with our TAL file. 1093 * This will contain (hopefully) links to our manifest and we 1094 * can get the ball rolling. 1095 */ 1096 1097 for (i = 0; i < talsz; i++) 1098 queue_add_tal(tals[i]); 1099 1100 /* change working directory to the cache directory */ 1101 if (fchdir(cachefd) == -1) 1102 err(1, "fchdir"); 1103 1104 while (entity_queue > 0 && !killme) { 1105 for (i = 0; i < NPFD; i++) { 1106 pfd[i].events = POLLIN; 1107 if (queues[i]->queued) 1108 pfd[i].events |= POLLOUT; 1109 } 1110 1111 if ((c = poll(pfd, NPFD, INFTIM)) == -1) { 1112 if (errno == EINTR) 1113 continue; 1114 err(1, "poll"); 1115 } 1116 1117 for (i = 0; i < NPFD; i++) { 1118 if (pfd[i].revents & (POLLERR|POLLNVAL)) 1119 errx(1, "poll[%zu]: bad fd", i); 1120 if (pfd[i].revents & POLLHUP) 1121 errx(1, "poll[%zu]: hangup", i); 1122 if (pfd[i].revents & POLLOUT) { 1123 switch (msgbuf_write(queues[i])) { 1124 case 0: 1125 errx(1, "write[%zu]: " 1126 "connection closed", i); 1127 case -1: 1128 err(1, "write[%zu]", i); 1129 } 1130 } 1131 } 1132 1133 1134 /* 1135 * Check the rsync and http process. 1136 * This means that one of our modules has completed 1137 * downloading and we can flush the module requests into 1138 * the parser process. 1139 */ 1140 1141 if ((pfd[0].revents & POLLIN)) { 1142 io_simple_read(rsync, &id, sizeof(id)); 1143 io_simple_read(rsync, &ok, sizeof(ok)); 1144 rp = repo_find(id); 1145 if (rp == NULL) 1146 errx(1, "unknown repository id: %zu", id); 1147 1148 assert(!rp->loaded); 1149 if (ok) 1150 logx("%s: loaded from network", rp->local); 1151 else 1152 logx("%s: load from network failed, " 1153 "fallback to cache", rp->local); 1154 rp->loaded = 1; 1155 stats.repos++; 1156 entityq_flush(rp); 1157 } 1158 1159 if ((pfd[2].revents & POLLIN)) { 1160 enum http_result res; 1161 char *last_mod; 1162 1163 io_simple_read(http, &id, sizeof(id)); 1164 io_simple_read(http, &res, sizeof(res)); 1165 io_str_read(http, &last_mod); 1166 rp = repo_find(id); 1167 if (rp == NULL) 1168 errx(1, "unknown repository id: %zu", id); 1169 1170 assert(!rp->loaded); 1171 if (http_done(rp, res)) { 1172 rp->loaded = 1; 1173 stats.repos++; 1174 entityq_flush(rp); 1175 } 1176 free(last_mod); 1177 } 1178 1179 /* 1180 * The parser has finished something for us. 1181 * Dequeue these one by one. 1182 */ 1183 1184 if ((pfd[1].revents & POLLIN)) { 1185 entity_process(proc, &stats, &v); 1186 } 1187 } 1188 1189 if (killme) { 1190 syslog(LOG_CRIT|LOG_DAEMON, 1191 "excessive runtime (%d seconds), giving up", timeout); 1192 errx(1, "excessive runtime (%d seconds), giving up", timeout); 1193 } 1194 1195 assert(entity_queue == 0); 1196 logx("all files parsed: generating output"); 1197 rc = 0; 1198 1199 /* 1200 * For clean-up, close the input for the parser and rsync 1201 * process. 1202 * This will cause them to exit, then we reap them. 1203 */ 1204 1205 close(proc); 1206 close(rsync); 1207 close(http); 1208 1209 if (waitpid(procpid, &st, 0) == -1) 1210 err(1, "waitpid"); 1211 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 1212 warnx("parser process exited abnormally"); 1213 rc = 1; 1214 } 1215 if (!noop) { 1216 if (waitpid(rsyncpid, &st, 0) == -1) 1217 err(1, "waitpid"); 1218 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 1219 warnx("rsync process exited abnormally"); 1220 rc = 1; 1221 } 1222 1223 if (waitpid(httppid, &st, 0) == -1) 1224 err(1, "waitpid"); 1225 if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) { 1226 warnx("http process exited abnormally"); 1227 rc = 1; 1228 } 1229 } 1230 1231 stats.del_files = repo_cleanup(); 1232 1233 gettimeofday(&now_time, NULL); 1234 timersub(&now_time, &start_time, &stats.elapsed_time); 1235 if (getrusage(RUSAGE_SELF, &ru) == 0) { 1236 stats.user_time = ru.ru_utime; 1237 stats.system_time = ru.ru_stime; 1238 } 1239 if (getrusage(RUSAGE_CHILDREN, &ru) == 0) { 1240 timeradd(&stats.user_time, &ru.ru_utime, &stats.user_time); 1241 timeradd(&stats.system_time, &ru.ru_stime, &stats.system_time); 1242 } 1243 1244 /* change working directory to the cache directory */ 1245 if (fchdir(outdirfd) == -1) 1246 err(1, "fchdir output dir"); 1247 1248 if (outputfiles(&v, &stats)) 1249 rc = 1; 1250 1251 1252 logx("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)", 1253 stats.roas, stats.roas_fail, stats.roas_invalid); 1254 logx("Certificates: %zu (%zu failed parse, %zu invalid)", 1255 stats.certs, stats.certs_fail, stats.certs_invalid); 1256 logx("Trust Anchor Locators: %zu", stats.tals); 1257 logx("Manifests: %zu (%zu failed parse, %zu stale)", 1258 stats.mfts, stats.mfts_fail, stats.mfts_stale); 1259 logx("Certificate revocation lists: %zu", stats.crls); 1260 logx("Ghostbuster records: %zu", stats.gbrs); 1261 logx("Repositories: %zu", stats.repos); 1262 logx("Files removed: %zu", stats.del_files); 1263 logx("VRP Entries: %zu (%zu unique)", stats.vrps, stats.uniqs); 1264 1265 /* Memory cleanup. */ 1266 while ((rp = SLIST_FIRST(&repos)) != NULL) { 1267 SLIST_REMOVE_HEAD(&repos, entry); 1268 free(rp->repouri); 1269 free(rp->local); 1270 free(rp->temp); 1271 free(rp->uris[0]); 1272 free(rp->uris[1]); 1273 free(rp); 1274 } 1275 1276 for (i = 0; i < outsz; i++) 1277 roa_free(out[i]); 1278 free(out); 1279 1280 return rc; 1281 1282usage: 1283 fprintf(stderr, 1284 "usage: rpki-client [-BcjnoVv] [-b sourceaddr] [-d cachedir]" 1285 " [-e rsync_prog]\n" 1286 " [-s timeout] [-T table] [-t tal]" 1287 " [outputdir]\n"); 1288 return 1; 1289} 1290