xref: /openbsd-src/usr.sbin/rpki-client/output.c (revision 29bf64ca9b984c45cb3416a0bc49cef8859122f1)
1 /*	$OpenBSD: output.c,v 1.38 2025/01/03 10:14:32 job Exp $ */
2 /*
3  * Copyright (c) 2019 Theo de Raadt <deraadt@openbsd.org>
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 /*-
19  * Copyright (C) 2009 Gabor Kovesdan <gabor@FreeBSD.org>
20  * Copyright (C) 2012 Oleg Moskalenko <mom040267@gmail.com>
21  * All rights reserved.
22  *
23  * Redistribution and use in source and binary forms, with or without
24  * modification, are permitted provided that the following conditions
25  * are met:
26  * 1. Redistributions of source code must retain the above copyright
27  *    notice, this list of conditions and the following disclaimer.
28  * 2. Redistributions in binary form must reproduce the above copyright
29  *    notice, this list of conditions and the following disclaimer in the
30  *    documentation and/or other materials provided with the distribution.
31  *
32  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
33  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
36  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
37  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
38  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
40  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
41  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42  * SUCH DAMAGE.
43  */
44 
45 #include <sys/stat.h>
46 
47 #include <err.h>
48 #include <fcntl.h>
49 #include <unistd.h>
50 #include <netdb.h>
51 #include <signal.h>
52 #include <string.h>
53 #include <limits.h>
54 #include <time.h>
55 
56 #include "extern.h"
57 
58 int		 outformats;
59 
60 static char	 output_tmpname[PATH_MAX];
61 static char	 output_name[PATH_MAX];
62 
63 static const struct outputs {
64 	int	 format;
65 	char	*name;
66 	int	(*fn)(FILE *, struct vrp_tree *, struct brk_tree *,
67 		    struct vap_tree *, struct vsp_tree *, struct stats *);
68 } outputs[] = {
69 	{ FORMAT_OPENBGPD, "openbgpd", output_bgpd },
70 	{ FORMAT_BIRD, "bird", output_bird },
71 	{ FORMAT_CSV, "csv", output_csv },
72 	{ FORMAT_JSON, "json", output_json },
73 	{ FORMAT_OMETRIC, "metrics", output_ometric },
74 	{ 0, NULL, NULL }
75 };
76 
77 static FILE	*output_createtmp(char *);
78 static void	 output_cleantmp(void);
79 static int	 output_finish(FILE *);
80 static void	 sig_handler(int);
81 static void	 set_signal_handler(void);
82 
83 /*
84  * Detect & reject so-called "AS0 TALs".
85  * AS0 TALs are TALs where for each and every subordinate ROA the asID field
86  * set to 0. Such TALs introduce operational risk, as they change the fail-safe
87  * from 'fail-open' to 'fail-closed'. Some context:
88  *     https://lists.afrinic.net/pipermail/rpd/2021/013312.html
89  *     https://lists.afrinic.net/pipermail/rpd/2021/013314.html
90  */
91 static void
92 prune_as0_tals(struct vrp_tree *vrps)
93 {
94 	struct vrp *v, *tv;
95 	int talid;
96 	int has_vrps[TALSZ_MAX] = { 0 };
97 	int is_as0_tal[TALSZ_MAX] = { 0 };
98 
99 	for (talid = 0; talid < talsz; talid++)
100 		is_as0_tal[talid] = 1;
101 
102 	RB_FOREACH(v, vrp_tree, vrps) {
103 		has_vrps[v->talid] = 1;
104 		if (v->asid != 0)
105 			is_as0_tal[v->talid] = 0;
106 	}
107 
108 	for (talid = 0; talid < talsz; talid++) {
109 		if (is_as0_tal[talid] && has_vrps[talid]) {
110 			warnx("%s: Detected AS0 TAL, pruning associated VRPs",
111 			    taldescs[talid]);
112 		}
113 	}
114 
115 	RB_FOREACH_SAFE(v, vrp_tree, vrps, tv) {
116 		if (is_as0_tal[v->talid]) {
117 			RB_REMOVE(vrp_tree, vrps, v);
118 			free(v);
119 		}
120 	}
121 
122 	/* XXX: update talstats? */
123 }
124 
125 int
126 outputfiles(struct vrp_tree *v, struct brk_tree *b, struct vap_tree *a,
127     struct vsp_tree *p, struct stats *st)
128 {
129 	int i, rc = 0;
130 
131 	atexit(output_cleantmp);
132 	set_signal_handler();
133 
134 	if (excludeas0)
135 		prune_as0_tals(v);
136 
137 	for (i = 0; outputs[i].name; i++) {
138 		FILE *fout;
139 
140 		if (!(outformats & outputs[i].format))
141 			continue;
142 
143 		fout = output_createtmp(outputs[i].name);
144 		if (fout == NULL) {
145 			warn("cannot create %s", outputs[i].name);
146 			rc = 1;
147 			continue;
148 		}
149 		if ((*outputs[i].fn)(fout, v, b, a, p, st) != 0) {
150 			warn("output for %s format failed", outputs[i].name);
151 			fclose(fout);
152 			output_cleantmp();
153 			rc = 1;
154 			continue;
155 		}
156 		if (output_finish(fout) != 0) {
157 			warn("finish for %s format failed", outputs[i].name);
158 			output_cleantmp();
159 			rc = 1;
160 			continue;
161 		}
162 	}
163 
164 	return rc;
165 }
166 
167 static FILE *
168 output_createtmp(char *name)
169 {
170 	FILE *f;
171 	int fd, r;
172 
173 	if (strlcpy(output_name, name, sizeof output_name) >=
174 	    sizeof output_name)
175 		err(1, "path too long");
176 	r = snprintf(output_tmpname, sizeof output_tmpname,
177 	    "%s.XXXXXXXXXXX", output_name);
178 	if (r < 0 || r > (int)sizeof(output_tmpname))
179 		err(1, "path too long");
180 	fd = mkostemp(output_tmpname, O_CLOEXEC);
181 	if (fd == -1)
182 		err(1, "mkostemp: %s", output_tmpname);
183 	(void) fchmod(fd, 0644);
184 	f = fdopen(fd, "w");
185 	if (f == NULL)
186 		err(1, "fdopen");
187 	return f;
188 }
189 
190 static int
191 output_finish(FILE *out)
192 {
193 	if (fclose(out) != 0)
194 		return -1;
195 	if (rename(output_tmpname, output_name) == -1)
196 		return -1;
197 	output_tmpname[0] = '\0';
198 	return 0;
199 }
200 
201 static void
202 output_cleantmp(void)
203 {
204 	if (*output_tmpname)
205 		unlink(output_tmpname);
206 	output_tmpname[0] = '\0';
207 }
208 
209 /*
210  * Signal handler that clears the temporary files.
211  */
212 static void
213 sig_handler(int sig)
214 {
215 	output_cleantmp();
216 	_exit(2);
217 }
218 
219 /*
220  * Set signal handler on panic signals.
221  */
222 static void
223 set_signal_handler(void)
224 {
225 	struct sigaction sa;
226 	int i, signals[] = {SIGTERM, SIGHUP, SIGINT, SIGUSR1, SIGUSR2,
227 	    SIGPIPE, SIGXCPU, SIGXFSZ, 0};
228 
229 	memset(&sa, 0, sizeof(sa));
230 	sigfillset(&sa.sa_mask);
231 	sa.sa_flags = SA_RESTART;
232 	sa.sa_handler = sig_handler;
233 
234 	for (i = 0; signals[i] != 0; i++) {
235 		if (sigaction(signals[i], &sa, NULL) == -1) {
236 			warn("sigaction(%s)", strsignal(signals[i]));
237 			continue;
238 		}
239 	}
240 }
241 
242 int
243 outputheader(FILE *out, struct stats *st)
244 {
245 	char		hn[NI_MAXHOST], tbuf[80];
246 	struct tm	*tp;
247 	time_t		t;
248 	int		i;
249 
250 	time(&t);
251 	tp = gmtime(&t);
252 	strftime(tbuf, sizeof tbuf, "%a %b %e %H:%M:%S UTC %Y", tp);
253 
254 	gethostname(hn, sizeof hn);
255 
256 	if (fprintf(out,
257 	    "# Generated on host %s at %s\n"
258 	    "# Processing time %lld seconds (%llds user, %llds system)\n"
259 	    "# Route Origin Authorizations: %u (%u failed parse, %u invalid)\n"
260 	    "# BGPsec Router Certificates: %u\n"
261 	    "# Certificates: %u (%u invalid)\n",
262 	    hn, tbuf, (long long)st->elapsed_time.tv_sec,
263 	    (long long)st->user_time.tv_sec, (long long)st->system_time.tv_sec,
264 	    st->repo_tal_stats.roas, st->repo_tal_stats.roas_fail,
265 	    st->repo_tal_stats.roas_invalid, st->repo_tal_stats.brks,
266 	    st->repo_tal_stats.certs, st->repo_tal_stats.certs_fail) < 0)
267 		return -1;
268 
269 	if (fprintf(out,
270 	    "# Trust Anchor Locators: %u (%u invalid) [", st->tals,
271 	    talsz - st->tals) < 0)
272 		return -1;
273 	for (i = 0; i < talsz; i++)
274 		if (fprintf(out, " %s", tals[i]) < 0)
275 			return -1;
276 
277 	if (fprintf(out,
278 	    " ]\n"
279 	    "# Manifests: %u (%u failed parse)\n"
280 	    "# Certificate revocation lists: %u\n"
281 	    "# Ghostbuster records: %u\n"
282 	    "# Repositories: %u\n"
283 	    "# VRP Entries: %u (%u unique)\n",
284 	    st->repo_tal_stats.mfts, st->repo_tal_stats.mfts_fail,
285 	    st->repo_tal_stats.crls,
286 	    st->repo_tal_stats.gbrs,
287 	    st->repos,
288 	    st->repo_tal_stats.vrps, st->repo_tal_stats.vrps_uniqs) < 0)
289 		return -1;
290 	return 0;
291 }
292