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
prune_as0_tals(struct vrp_tree * vrps)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
outputfiles(struct vrp_tree * v,struct brk_tree * b,struct vap_tree * a,struct vsp_tree * p,struct stats * st)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 *
output_createtmp(char * name)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
output_finish(FILE * out)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
output_cleantmp(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
sig_handler(int sig)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
set_signal_handler(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
outputheader(FILE * out,struct stats * st)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