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