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