main.c revision 1.123
1/*	$OpenBSD: main.c,v 1.123 2021/03/25 12:18:45 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 strcasecmp(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
840int
841main(int argc, char *argv[])
842{
843	int		 rc = 1, c, st, proc, rsync, http, ok,
844			 fl = SOCK_STREAM | SOCK_CLOEXEC;
845	size_t		 i, id, outsz = 0, talsz = 0;
846	pid_t		 procpid, rsyncpid, httppid;
847	int		 fd[2];
848	struct pollfd	 pfd[3];
849	struct roa	**out = NULL;
850	struct repo	*rp;
851	char		*rsync_prog = "openrsync";
852	char		*bind_addr = NULL;
853	const char	*cachedir = NULL, *outputdir = NULL;
854	const char	*tals[TALSZ_MAX], *errs;
855	struct vrp_tree	 v = RB_INITIALIZER(&v);
856	struct rusage	ru;
857	struct timeval	start_time, now_time;
858
859	gettimeofday(&start_time, NULL);
860
861	/* If started as root, priv-drop to _rpki-client */
862	if (getuid() == 0) {
863		struct passwd *pw;
864
865		pw = getpwnam("_rpki-client");
866		if (!pw)
867			errx(1, "no _rpki-client user to revoke to");
868		if (setgroups(1, &pw->pw_gid) == -1 ||
869		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
870		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
871			err(1, "unable to revoke privs");
872
873	}
874	cachedir = RPKI_PATH_BASE_DIR;
875	outputdir = RPKI_PATH_OUT_DIR;
876
877	if (pledge("stdio rpath wpath cpath inet fattr dns sendfd recvfd "
878	    "proc exec unveil", NULL) == -1)
879		err(1, "pledge");
880
881	while ((c = getopt(argc, argv, "b:Bcd:e:jnos:t:T:vV")) != -1)
882		switch (c) {
883		case 'b':
884			bind_addr = optarg;
885			break;
886		case 'B':
887			outformats |= FORMAT_BIRD;
888			break;
889		case 'c':
890			outformats |= FORMAT_CSV;
891			break;
892		case 'd':
893			cachedir = optarg;
894			break;
895		case 'e':
896			rsync_prog = optarg;
897			break;
898		case 'j':
899			outformats |= FORMAT_JSON;
900			break;
901		case 'n':
902			noop = 1;
903			break;
904		case 'o':
905			outformats |= FORMAT_OPENBGPD;
906			break;
907		case 's':
908			timeout = strtonum(optarg, 0, 24*60*60, &errs);
909			if (errs)
910				errx(1, "-s: %s", errs);
911			break;
912		case 't':
913			if (talsz >= TALSZ_MAX)
914				err(1,
915				    "too many tal files specified");
916			tals[talsz++] = optarg;
917			break;
918		case 'T':
919			bird_tablename = optarg;
920			break;
921		case 'v':
922			verbose++;
923			break;
924		case 'V':
925			errx(0, "version: %s", RPKI_VERSION);
926		default:
927			goto usage;
928		}
929
930	argv += optind;
931	argc -= optind;
932	if (argc == 1)
933		outputdir = argv[0];
934	else if (argc > 1)
935		goto usage;
936
937	if (timeout) {
938		signal(SIGALRM, suicide);
939		/* Commit suicide eventually - cron will normally start a new one */
940		alarm(timeout);
941	}
942
943	if (cachedir == NULL) {
944		warnx("cache directory required");
945		goto usage;
946	}
947	if (outputdir == NULL) {
948		warnx("output directory required");
949		goto usage;
950	}
951
952	if ((cachefd = open(cachedir, O_RDONLY, 0)) == -1)
953		err(1, "cache directory %s", cachedir);
954	if ((outdirfd = open(outputdir, O_RDONLY, 0)) == -1)
955		err(1, "output directory %s", outputdir);
956
957	if (outformats == 0)
958		outformats = FORMAT_OPENBGPD;
959
960	if (talsz == 0)
961		talsz = tal_load_default(tals, TALSZ_MAX);
962	if (talsz == 0)
963		err(1, "no TAL files found in %s", "/etc/rpki");
964
965	/*
966	 * Create the file reader as a jailed child process.
967	 * It will be responsible for reading all of the files (ROAs,
968	 * manifests, certificates, etc.) and returning contents.
969	 */
970
971	if (socketpair(AF_UNIX, fl, 0, fd) == -1)
972		err(1, "socketpair");
973	if ((procpid = fork()) == -1)
974		err(1, "fork");
975
976	if (procpid == 0) {
977		close(fd[1]);
978
979		/* change working directory to the cache directory */
980		if (fchdir(cachefd) == -1)
981			err(1, "fchdir");
982
983		/* Only allow access to the cache directory. */
984		if (unveil(".", "r") == -1)
985			err(1, "%s: unveil", cachedir);
986		if (pledge("stdio rpath", NULL) == -1)
987			err(1, "pledge");
988		proc_parser(fd[0]);
989		errx(1, "parser process returned");
990	}
991
992	close(fd[0]);
993	proc = fd[1];
994
995	/*
996	 * Create a process that will do the rsync'ing.
997	 * This process is responsible for making sure that all the
998	 * repositories referenced by a certificate manifest (or the
999	 * TAL) exists and has been downloaded.
1000	 */
1001
1002	if (!noop) {
1003		if (socketpair(AF_UNIX, fl, 0, fd) == -1)
1004			err(1, "socketpair");
1005		if ((rsyncpid = fork()) == -1)
1006			err(1, "fork");
1007
1008		if (rsyncpid == 0) {
1009			close(proc);
1010			close(fd[1]);
1011
1012			/* change working directory to the cache directory */
1013			if (fchdir(cachefd) == -1)
1014				err(1, "fchdir");
1015
1016			if (pledge("stdio rpath proc exec unveil", NULL) == -1)
1017				err(1, "pledge");
1018
1019			proc_rsync(rsync_prog, bind_addr, fd[0]);
1020			errx(1, "rsync process returned");
1021		}
1022
1023		close(fd[0]);
1024		rsync = fd[1];
1025	} else {
1026		rsync = -1;
1027		rsyncpid = -1;
1028	}
1029
1030	/*
1031	 * Create a process that will fetch data via https.
1032	 * With every request the http process receives a file descriptor
1033	 * where the data should be written to.
1034	 */
1035
1036	if (!noop) {
1037		if (socketpair(AF_UNIX, fl, 0, fd) == -1)
1038			err(1, "socketpair");
1039		if ((httppid = fork()) == -1)
1040			err(1, "fork");
1041
1042		if (httppid == 0) {
1043			close(proc);
1044			close(rsync);
1045			close(fd[1]);
1046
1047			/* change working directory to the cache directory */
1048			if (fchdir(cachefd) == -1)
1049				err(1, "fchdir");
1050
1051			if (pledge("stdio rpath inet dns recvfd", NULL) == -1)
1052				err(1, "pledge");
1053
1054			proc_http(bind_addr, fd[0]);
1055			errx(1, "http process returned");
1056		}
1057
1058		close(fd[0]);
1059		http = fd[1];
1060	} else {
1061		http = -1;
1062		httppid = -1;
1063	}
1064
1065	if (pledge("stdio rpath wpath cpath fattr sendfd", NULL) == -1)
1066		err(1, "pledge");
1067
1068	msgbuf_init(&procq);
1069	msgbuf_init(&rsyncq);
1070	msgbuf_init(&httpq);
1071	procq.fd = proc;
1072	rsyncq.fd = rsync;
1073	httpq.fd = http;
1074
1075	/*
1076	 * The main process drives the top-down scan to leaf ROAs using
1077	 * data downloaded by the rsync process and parsed by the
1078	 * parsing process.
1079	 */
1080
1081	pfd[0].fd = rsync;
1082	pfd[1].fd = proc;
1083	pfd[2].fd = http;
1084
1085	/*
1086	 * Prime the process with our TAL file.
1087	 * This will contain (hopefully) links to our manifest and we
1088	 * can get the ball rolling.
1089	 */
1090
1091	for (i = 0; i < talsz; i++)
1092		queue_add_tal(tals[i]);
1093
1094	/* change working directory to the cache directory */
1095	if (fchdir(cachefd) == -1)
1096		err(1, "fchdir");
1097
1098	while (entity_queue > 0 && !killme) {
1099		pfd[0].events = POLLIN;
1100		if (rsyncq.queued)
1101			pfd[0].events |= POLLOUT;
1102		pfd[1].events = POLLIN;
1103		if (procq.queued)
1104			pfd[1].events |= POLLOUT;
1105		pfd[2].events = POLLIN;
1106		if (httpq.queued)
1107			pfd[2].events |= POLLOUT;
1108
1109		if ((c = poll(pfd, 3, INFTIM)) == -1) {
1110			if (errno == EINTR)
1111				continue;
1112			err(1, "poll");
1113		}
1114
1115		if ((pfd[0].revents & (POLLERR|POLLNVAL)) ||
1116		    (pfd[1].revents & (POLLERR|POLLNVAL)) ||
1117		    (pfd[2].revents & (POLLERR|POLLNVAL)))
1118			errx(1, "poll: bad fd");
1119		if ((pfd[0].revents & POLLHUP) ||
1120		    (pfd[1].revents & POLLHUP) ||
1121		    (pfd[2].revents & POLLHUP))
1122			errx(1, "poll: hangup");
1123
1124		if (pfd[0].revents & POLLOUT) {
1125			switch (msgbuf_write(&rsyncq)) {
1126			case 0:
1127				errx(1, "write: connection closed");
1128			case -1:
1129				err(1, "write");
1130			}
1131		}
1132		if (pfd[1].revents & POLLOUT) {
1133			switch (msgbuf_write(&procq)) {
1134			case 0:
1135				errx(1, "write: connection closed");
1136			case -1:
1137				err(1, "write");
1138			}
1139		}
1140		if (pfd[2].revents & POLLOUT) {
1141			switch (msgbuf_write(&httpq)) {
1142			case 0:
1143				errx(1, "write: connection closed");
1144			case -1:
1145				err(1, "write");
1146			}
1147		}
1148
1149
1150		/*
1151		 * Check the rsync and http process.
1152		 * This means that one of our modules has completed
1153		 * downloading and we can flush the module requests into
1154		 * the parser process.
1155		 */
1156
1157		if ((pfd[0].revents & POLLIN)) {
1158			io_simple_read(rsync, &id, sizeof(id));
1159			io_simple_read(rsync, &ok, sizeof(ok));
1160			rp = repo_find(id);
1161			if (rp == NULL)
1162				errx(1, "unknown repository id: %zu", id);
1163
1164			assert(!rp->loaded);
1165			if (ok)
1166				logx("%s: loaded from network", rp->local);
1167			else
1168				logx("%s: load from network failed, "
1169				    "fallback to cache", rp->local);
1170			rp->loaded = 1;
1171			stats.repos++;
1172			entityq_flush(rp);
1173		}
1174
1175		if ((pfd[2].revents & POLLIN)) {
1176			enum http_result res;
1177			char *last_mod;
1178
1179			io_simple_read(http, &id, sizeof(id));
1180			io_simple_read(http, &res, sizeof(res));
1181			io_str_read(http, &last_mod);
1182			rp = repo_find(id);
1183			if (rp == NULL)
1184				errx(1, "unknown repository id: %zu", id);
1185
1186			assert(!rp->loaded);
1187			if (http_done(rp, res)) {
1188				rp->loaded = 1;
1189				stats.repos++;
1190				entityq_flush(rp);
1191			}
1192			free(last_mod);
1193		}
1194
1195		/*
1196		 * The parser has finished something for us.
1197		 * Dequeue these one by one.
1198		 */
1199
1200		if ((pfd[1].revents & POLLIN)) {
1201			entity_process(proc, &stats, &v);
1202		}
1203	}
1204
1205	if (killme) {
1206		syslog(LOG_CRIT|LOG_DAEMON,
1207		    "excessive runtime (%d seconds), giving up", timeout);
1208		errx(1, "excessive runtime (%d seconds), giving up", timeout);
1209	}
1210
1211	assert(entity_queue == 0);
1212	logx("all files parsed: generating output");
1213	rc = 0;
1214
1215	/*
1216	 * For clean-up, close the input for the parser and rsync
1217	 * process.
1218	 * This will cause them to exit, then we reap them.
1219	 */
1220
1221	close(proc);
1222	close(rsync);
1223	close(http);
1224
1225	if (waitpid(procpid, &st, 0) == -1)
1226		err(1, "waitpid");
1227	if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
1228		warnx("parser process exited abnormally");
1229		rc = 1;
1230	}
1231	if (!noop) {
1232		if (waitpid(rsyncpid, &st, 0) == -1)
1233			err(1, "waitpid");
1234		if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
1235			warnx("rsync process exited abnormally");
1236			rc = 1;
1237		}
1238
1239		if (waitpid(httppid, &st, 0) == -1)
1240			err(1, "waitpid");
1241		if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) {
1242			warnx("http process exited abnormally");
1243			rc = 1;
1244		}
1245	}
1246
1247	stats.del_files = repo_cleanup();
1248
1249	gettimeofday(&now_time, NULL);
1250	timersub(&now_time, &start_time, &stats.elapsed_time);
1251	if (getrusage(RUSAGE_SELF, &ru) == 0) {
1252		stats.user_time = ru.ru_utime;
1253		stats.system_time = ru.ru_stime;
1254	}
1255	if (getrusage(RUSAGE_CHILDREN, &ru) == 0) {
1256		timeradd(&stats.user_time, &ru.ru_utime, &stats.user_time);
1257		timeradd(&stats.system_time, &ru.ru_stime, &stats.system_time);
1258	}
1259
1260	/* change working directory to the cache directory */
1261	if (fchdir(outdirfd) == -1)
1262		err(1, "fchdir output dir");
1263
1264	if (outputfiles(&v, &stats))
1265		rc = 1;
1266
1267
1268	logx("Route Origin Authorizations: %zu (%zu failed parse, %zu invalid)",
1269	    stats.roas, stats.roas_fail, stats.roas_invalid);
1270	logx("Certificates: %zu (%zu failed parse, %zu invalid)",
1271	    stats.certs, stats.certs_fail, stats.certs_invalid);
1272	logx("Trust Anchor Locators: %zu", stats.tals);
1273	logx("Manifests: %zu (%zu failed parse, %zu stale)",
1274	    stats.mfts, stats.mfts_fail, stats.mfts_stale);
1275	logx("Certificate revocation lists: %zu", stats.crls);
1276	logx("Ghostbuster records: %zu", stats.gbrs);
1277	logx("Repositories: %zu", stats.repos);
1278	logx("Files removed: %zu", stats.del_files);
1279	logx("VRP Entries: %zu (%zu unique)", stats.vrps, stats.uniqs);
1280
1281	/* Memory cleanup. */
1282	while ((rp = SLIST_FIRST(&repos)) != NULL) {
1283		SLIST_REMOVE_HEAD(&repos, entry);
1284		free(rp->repouri);
1285		free(rp->local);
1286		free(rp->temp);
1287		free(rp->uris[0]);
1288		free(rp->uris[1]);
1289		free(rp);
1290	}
1291
1292	for (i = 0; i < outsz; i++)
1293		roa_free(out[i]);
1294	free(out);
1295
1296	return rc;
1297
1298usage:
1299	fprintf(stderr,
1300	    "usage: rpki-client [-BcjnoVv] [-b sourceaddr] [-d cachedir]"
1301	    " [-e rsync_prog]\n"
1302	    "                   [-s timeout] [-T table] [-t tal]"
1303	    " [outputdir]\n");
1304	return 1;
1305}
1306