xref: /openbsd/usr.sbin/rpki-client/print.c (revision cc6f004e)
1*cc6f004eSjob /*	$OpenBSD: print.c,v 1.26 2023/01/10 13:26:34 job Exp $ */
2714f4e3fSclaudio /*
3714f4e3fSclaudio  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
4714f4e3fSclaudio  * Copyright (c) 2019 Kristaps Dzonsons <kristaps@bsd.lv>
5714f4e3fSclaudio  *
6714f4e3fSclaudio  * Permission to use, copy, modify, and distribute this software for any
7714f4e3fSclaudio  * purpose with or without fee is hereby granted, provided that the above
8714f4e3fSclaudio  * copyright notice and this permission notice appear in all copies.
9714f4e3fSclaudio  *
10714f4e3fSclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11714f4e3fSclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12714f4e3fSclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13714f4e3fSclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14714f4e3fSclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15714f4e3fSclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16714f4e3fSclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17714f4e3fSclaudio  */
18714f4e3fSclaudio 
19714f4e3fSclaudio #include <sys/types.h>
20714f4e3fSclaudio #include <sys/socket.h>
21714f4e3fSclaudio #include <arpa/inet.h>
22714f4e3fSclaudio 
23714f4e3fSclaudio #include <err.h>
24714f4e3fSclaudio #include <stdio.h>
25714f4e3fSclaudio #include <string.h>
26714f4e3fSclaudio #include <time.h>
27714f4e3fSclaudio 
28e911df76Sjob #include <openssl/evp.h>
29e911df76Sjob 
30714f4e3fSclaudio #include "extern.h"
31714f4e3fSclaudio 
32714f4e3fSclaudio static const char *
33254fd372Sclaudio pretty_key_id(const char *hex)
34714f4e3fSclaudio {
35714f4e3fSclaudio 	static char buf[128];	/* bigger than SHA_DIGEST_LENGTH * 3 */
36714f4e3fSclaudio 	size_t i;
37714f4e3fSclaudio 
38714f4e3fSclaudio 	for (i = 0; i < sizeof(buf) && *hex != '\0'; i++) {
39254fd372Sclaudio 		if (i % 3 == 2)
40714f4e3fSclaudio 			buf[i] = ':';
41714f4e3fSclaudio 		else
42714f4e3fSclaudio 			buf[i] = *hex++;
43714f4e3fSclaudio 	}
44714f4e3fSclaudio 	if (i == sizeof(buf))
45714f4e3fSclaudio 		memcpy(buf + sizeof(buf) - 4, "...", 4);
46254fd372Sclaudio 	else
47254fd372Sclaudio 		buf[i] = '\0';
48714f4e3fSclaudio 	return buf;
49714f4e3fSclaudio }
50714f4e3fSclaudio 
51220c707cSclaudio char *
52220c707cSclaudio time2str(time_t t)
53220c707cSclaudio {
54220c707cSclaudio 	static char buf[64];
55220c707cSclaudio 	struct tm tm;
56220c707cSclaudio 
57220c707cSclaudio 	if (gmtime_r(&t, &tm) == NULL)
58220c707cSclaudio 		return "could not convert time";
59220c707cSclaudio 
60*cc6f004eSjob 	strftime(buf, sizeof(buf), "%a %d %b %Y %T %z", &tm);
61*cc6f004eSjob 
62220c707cSclaudio 	return buf;
63220c707cSclaudio }
64220c707cSclaudio 
65714f4e3fSclaudio void
66714f4e3fSclaudio tal_print(const struct tal *p)
67714f4e3fSclaudio {
68e911df76Sjob 	char			*ski;
69e911df76Sjob 	EVP_PKEY		*pk;
70e911df76Sjob 	RSA			*r;
71e911df76Sjob 	const unsigned char	*der;
72e911df76Sjob 	unsigned char		*rder = NULL;
73e911df76Sjob 	unsigned char		 md[SHA_DIGEST_LENGTH];
74e911df76Sjob 	int			 rder_len;
75714f4e3fSclaudio 	size_t			 i;
76714f4e3fSclaudio 
77e911df76Sjob 	der = p->pkey;
78e911df76Sjob 	pk = d2i_PUBKEY(NULL, &der, p->pkeysz);
79e911df76Sjob 	if (pk == NULL)
80e911df76Sjob 		errx(1, "d2i_PUBKEY failed in %s", __func__);
81e911df76Sjob 
82e911df76Sjob 	r = EVP_PKEY_get0_RSA(pk);
83e911df76Sjob 	if (r == NULL)
84e911df76Sjob 		errx(1, "EVP_PKEY_get0_RSA failed in %s", __func__);
85e911df76Sjob 	if ((rder_len = i2d_RSAPublicKey(r, &rder)) <= 0)
86e911df76Sjob 		errx(1, "i2d_RSAPublicKey failed in %s", __func__);
87e911df76Sjob 
88e911df76Sjob 	if (!EVP_Digest(rder, rder_len, md, NULL, EVP_sha1(), NULL))
89e911df76Sjob 		errx(1, "EVP_Digest failed in %s", __func__);
90e911df76Sjob 
91e911df76Sjob 	ski = hex_encode(md, SHA_DIGEST_LENGTH);
92e911df76Sjob 
93530399e8Sjob 	if (outformats & FORMAT_JSON) {
94530399e8Sjob 		printf("\t\"type\": \"tal\",\n");
953b3f075aSjob 		printf("\t\"name\": \"%s\",\n", p->descr);
963b3f075aSjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(ski));
97530399e8Sjob 		printf("\t\"trust_anchor_locations\": [");
98530399e8Sjob 		for (i = 0; i < p->urisz; i++) {
99530399e8Sjob 			printf("\"%s\"", p->uri[i]);
100530399e8Sjob 			if (i + 1 < p->urisz)
101530399e8Sjob 				printf(", ");
102530399e8Sjob 		}
1033b3f075aSjob 		printf("],\n");
104530399e8Sjob 	} else {
105530399e8Sjob 		printf("Trust anchor name:        %s\n", p->descr);
106530399e8Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(ski));
107e911df76Sjob 		printf("Trust anchor locations:\n");
108714f4e3fSclaudio 		for (i = 0; i < p->urisz; i++)
109e911df76Sjob 			printf("%5zu: %s\n", i + 1, p->uri[i]);
110530399e8Sjob 	}
111e911df76Sjob 
112e911df76Sjob 	EVP_PKEY_free(pk);
113e911df76Sjob 	free(rder);
114e911df76Sjob 	free(ski);
115714f4e3fSclaudio }
116714f4e3fSclaudio 
117714f4e3fSclaudio void
118530399e8Sjob x509_print(const X509 *x)
119530399e8Sjob {
120530399e8Sjob 	const ASN1_INTEGER	*xserial;
121e0b87278Sjob 	const X509_NAME		*xissuer;
122e0b87278Sjob 	char			*issuer = NULL;
123530399e8Sjob 	char			*serial = NULL;
124530399e8Sjob 
125e0b87278Sjob 	if ((xissuer = X509_get_issuer_name(x)) == NULL) {
126e0b87278Sjob 		warnx("X509_get_issuer_name failed");
127530399e8Sjob 		goto out;
128530399e8Sjob 	}
129530399e8Sjob 
130e0b87278Sjob 	if ((issuer = X509_NAME_oneline(xissuer, NULL, 0)) == NULL) {
131e0b87278Sjob 		warnx("X509_NAME_oneline failed");
132530399e8Sjob 		goto out;
133530399e8Sjob 	}
134530399e8Sjob 
135e0b87278Sjob 	if ((xserial = X509_get0_serialNumber(x)) == NULL) {
136e0b87278Sjob 		warnx("X509_get0_serialNumber failed");
137e0b87278Sjob 		goto out;
138e0b87278Sjob 	}
139e0b87278Sjob 
140e0b87278Sjob 	if ((serial = x509_convert_seqnum(__func__, xserial)) == NULL)
141e0b87278Sjob 		goto out;
142e0b87278Sjob 
143530399e8Sjob 	if (outformats & FORMAT_JSON) {
144e0b87278Sjob 		printf("\t\"cert_issuer\": \"%s\",\n", issuer);
145530399e8Sjob 		printf("\t\"cert_serial\": \"%s\",\n", serial);
146530399e8Sjob 	} else {
147e0b87278Sjob 		printf("Certificate issuer:       %s\n", issuer);
148530399e8Sjob 		printf("Certificate serial:       %s\n", serial);
149530399e8Sjob 	}
150530399e8Sjob 
151530399e8Sjob  out:
152e0b87278Sjob 	free(issuer);
153530399e8Sjob 	free(serial);
154530399e8Sjob }
155530399e8Sjob 
156530399e8Sjob void
157714f4e3fSclaudio cert_print(const struct cert *p)
158714f4e3fSclaudio {
159530399e8Sjob 	size_t			 i, j;
160714f4e3fSclaudio 	char			 buf1[64], buf2[64];
161714f4e3fSclaudio 	int			 sockt;
162530399e8Sjob 
163530399e8Sjob 	if (outformats & FORMAT_JSON) {
164530399e8Sjob 		if (p->pubkey != NULL)
165530399e8Sjob 			printf("\t\"type\": \"router_key\",\n");
166530399e8Sjob 		else
167530399e8Sjob 			printf("\t\"type\": \"ca_cert\",\n");
168530399e8Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
169530399e8Sjob 		if (p->aki != NULL)
170530399e8Sjob 			printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
171530399e8Sjob 		x509_print(p->x509);
172530399e8Sjob 		if (p->aia != NULL)
173530399e8Sjob 			printf("\t\"aia\": \"%s\",\n", p->aia);
174530399e8Sjob 		if (p->mft != NULL)
175530399e8Sjob 			printf("\t\"manifest\": \"%s\",\n", p->mft);
176530399e8Sjob 		if (p->repo != NULL)
177530399e8Sjob 			printf("\t\"carepository\": \"%s\",\n", p->repo);
178530399e8Sjob 		if (p->notify != NULL)
179530399e8Sjob 			printf("\t\"notify_url\": \"%s\",\n", p->notify);
180530399e8Sjob 		if (p->pubkey != NULL)
181530399e8Sjob 			printf("\t\"router_key\": \"%s\",\n", p->pubkey);
182530399e8Sjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
183530399e8Sjob 		printf("\t\"subordinate_resources\": [\n");
184530399e8Sjob 	} else {
185714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
186714f4e3fSclaudio 		if (p->aki != NULL)
18767642cb5Stb 			printf("Authority key identifier: %s\n",
18867642cb5Stb 			    pretty_key_id(p->aki));
189530399e8Sjob 		x509_print(p->x509);
190714f4e3fSclaudio 		if (p->aia != NULL)
191714f4e3fSclaudio 			printf("Authority info access:    %s\n", p->aia);
192714f4e3fSclaudio 		if (p->mft != NULL)
193714f4e3fSclaudio 			printf("Manifest:                 %s\n", p->mft);
194714f4e3fSclaudio 		if (p->repo != NULL)
195714f4e3fSclaudio 			printf("caRepository:             %s\n", p->repo);
196714f4e3fSclaudio 		if (p->notify != NULL)
197714f4e3fSclaudio 			printf("Notify URL:               %s\n", p->notify);
19814d83341Sjob 		if (p->pubkey != NULL) {
1996530cf17Sjob 			printf("BGPsec ECDSA public key:  %s\n",
20067642cb5Stb 			    p->pubkey);
20128439bbcSjob 			printf("Router key valid until:   %s\n",
20228439bbcSjob 			    time2str(p->expires));
20314d83341Sjob 		} else
20428439bbcSjob 			printf("Certificate valid until:  %s\n",
20528439bbcSjob 			    time2str(p->expires));
2068aa0cba1Sjob 		printf("Subordinate resources:\n");
207530399e8Sjob 	}
208714f4e3fSclaudio 
209530399e8Sjob 	for (i = 0; i < p->asz; i++) {
210714f4e3fSclaudio 		switch (p->as[i].type) {
211714f4e3fSclaudio 		case CERT_AS_ID:
212530399e8Sjob 			if (outformats & FORMAT_JSON)
213530399e8Sjob 				printf("\t\t{ \"asid\": %u }", p->as[i].id);
214530399e8Sjob 			else
215530399e8Sjob 				printf("%5zu: AS: %u", i + 1, p->as[i].id);
216714f4e3fSclaudio 			break;
217714f4e3fSclaudio 		case CERT_AS_INHERIT:
218530399e8Sjob 			if (outformats & FORMAT_JSON)
219530399e8Sjob 				printf("\t\t{ \"asid_inherit\": \"true\" }");
220530399e8Sjob 			else
221530399e8Sjob 				printf("%5zu: AS: inherit", i + 1);
222714f4e3fSclaudio 			break;
223714f4e3fSclaudio 		case CERT_AS_RANGE:
224530399e8Sjob 			if (outformats & FORMAT_JSON)
225530399e8Sjob 				printf("\t\t{ \"asrange\": { \"min\": %u, "
226530399e8Sjob 				    "\"max\": %u }}", p->as[i].range.min,
227530399e8Sjob 				    p->as[i].range.max);
228530399e8Sjob 			else
229530399e8Sjob 				printf("%5zu: AS: %u -- %u", i + 1,
230714f4e3fSclaudio 				    p->as[i].range.min, p->as[i].range.max);
231714f4e3fSclaudio 			break;
232714f4e3fSclaudio 		}
233530399e8Sjob 		if (outformats & FORMAT_JSON && i + 1 < p->asz + p->ipsz)
234530399e8Sjob 			printf(",\n");
235530399e8Sjob 		else
236530399e8Sjob 			printf("\n");
237714f4e3fSclaudio 	}
238714f4e3fSclaudio 
239530399e8Sjob 	for (j = 0; j < p->ipsz; j++) {
240530399e8Sjob 		switch (p->ips[j].type) {
241530399e8Sjob 		case CERT_IP_INHERIT:
242530399e8Sjob 			if (outformats & FORMAT_JSON)
243530399e8Sjob 				printf("\t\t{ \"ip_inherit\": \"true\" }");
244530399e8Sjob 			else
245530399e8Sjob 				printf("%5zu: IP: inherit", i + j + 1);
246530399e8Sjob 			break;
247530399e8Sjob 		case CERT_IP_ADDR:
248530399e8Sjob 			ip_addr_print(&p->ips[j].ip,
249530399e8Sjob 			    p->ips[j].afi, buf1, sizeof(buf1));
250530399e8Sjob 			if (outformats & FORMAT_JSON)
251530399e8Sjob 				printf("\t\t{ \"ip_prefix\": \"%s\" }", buf1);
252530399e8Sjob 			else
253530399e8Sjob 				printf("%5zu: IP: %s", i + j + 1, buf1);
254530399e8Sjob 			break;
255530399e8Sjob 		case CERT_IP_RANGE:
256530399e8Sjob 			sockt = (p->ips[j].afi == AFI_IPV4) ?
257530399e8Sjob 			    AF_INET : AF_INET6;
258530399e8Sjob 			inet_ntop(sockt, p->ips[j].min, buf1, sizeof(buf1));
259530399e8Sjob 			inet_ntop(sockt, p->ips[j].max, buf2, sizeof(buf2));
260530399e8Sjob 			if (outformats & FORMAT_JSON)
261530399e8Sjob 				printf("\t\t{ \"ip_range\": { \"min\": \"%s\""
262530399e8Sjob 				    ", \"max\": \"%s\" }}", buf1, buf2);
263530399e8Sjob 			else
264530399e8Sjob 				printf("%5zu: IP: %s -- %s", i + j + 1, buf1,
265530399e8Sjob 				    buf2);
266530399e8Sjob 			break;
267530399e8Sjob 		}
268530399e8Sjob 		if (outformats & FORMAT_JSON && i + j + 1 < p->asz + p->ipsz)
269530399e8Sjob 			printf(",\n");
270530399e8Sjob 		else
271530399e8Sjob 			printf("\n");
272530399e8Sjob 	}
273530399e8Sjob 
274530399e8Sjob 	if (outformats & FORMAT_JSON)
275530399e8Sjob 		printf("\t],\n");
276714f4e3fSclaudio }
277714f4e3fSclaudio 
278714f4e3fSclaudio void
279220c707cSclaudio crl_print(const struct crl *p)
280220c707cSclaudio {
281220c707cSclaudio 	STACK_OF(X509_REVOKED)	*revlist;
282220c707cSclaudio 	X509_REVOKED *rev;
2837cdd491fSclaudio 	ASN1_INTEGER *crlnum;
284e0b87278Sjob 	X509_NAME *xissuer;
285220c707cSclaudio 	int i;
286e0b87278Sjob 	char *issuer, *serial;
287220c707cSclaudio 	time_t t;
288220c707cSclaudio 
289530399e8Sjob 	if (outformats & FORMAT_JSON) {
290530399e8Sjob 		printf("\t\"type\": \"crl\",\n");
291530399e8Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
292530399e8Sjob 	} else
293220c707cSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
2947cdd491fSclaudio 
295e0b87278Sjob 	xissuer = X509_CRL_get_issuer(p->x509_crl);
296e0b87278Sjob 	issuer = X509_NAME_oneline(xissuer, NULL, 0);
2977cdd491fSclaudio 	crlnum = X509_CRL_get_ext_d2i(p->x509_crl, NID_crl_number, NULL, NULL);
2987cdd491fSclaudio 	serial = x509_convert_seqnum(__func__, crlnum);
299e0b87278Sjob 	if (issuer != NULL && serial != NULL) {
300e0b87278Sjob 		if (outformats & FORMAT_JSON) {
301e0b87278Sjob 			printf("\t\"crl_issuer\": \"%s\",\n", issuer);
302530399e8Sjob 			printf("\t\"crl_serial\": \"%s\",\n", serial);
303e0b87278Sjob 		} else {
304e0b87278Sjob 			printf("CRL issuer:               %s\n", issuer);
305e0b87278Sjob 			printf("CRL serial number:        %s\n", serial);
306530399e8Sjob 		}
307e0b87278Sjob 	}
308e0b87278Sjob 	free(issuer);
3097cdd491fSclaudio 	free(serial);
3107cdd491fSclaudio 	ASN1_INTEGER_free(crlnum);
3117cdd491fSclaudio 
312530399e8Sjob 	if (outformats & FORMAT_JSON) {
313530399e8Sjob 		printf("\t\"valid_since\": %lld,\n", (long long)p->issued);
314530399e8Sjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
315530399e8Sjob 		printf("\t\"revoked_certs\": [\n");
316530399e8Sjob 	} else {
317220c707cSclaudio 		printf("CRL valid since:          %s\n", time2str(p->issued));
318220c707cSclaudio 		printf("CRL valid until:          %s\n", time2str(p->expires));
319530399e8Sjob 		printf("Revoked Certificates:\n");
320530399e8Sjob 	}
321220c707cSclaudio 
322220c707cSclaudio 	revlist = X509_CRL_get_REVOKED(p->x509_crl);
323220c707cSclaudio 	for (i = 0; i < sk_X509_REVOKED_num(revlist); i++) {
324220c707cSclaudio 		rev = sk_X509_REVOKED_value(revlist, i);
3257cdd491fSclaudio 		serial = x509_convert_seqnum(__func__,
3267cdd491fSclaudio 		    X509_REVOKED_get0_serialNumber(rev));
327220c707cSclaudio 		x509_get_time(X509_REVOKED_get0_revocationDate(rev), &t);
328530399e8Sjob 		if (serial != NULL) {
329530399e8Sjob 			if (outformats & FORMAT_JSON) {
330530399e8Sjob 				printf("\t\t{ \"serial\": \"%s\"", serial);
331530399e8Sjob 				printf(", \"date\": \"%s\" }", time2str(t));
332530399e8Sjob 				if (i + 1 < sk_X509_REVOKED_num(revlist))
333530399e8Sjob 					printf(",");
334530399e8Sjob 				printf("\n");
335530399e8Sjob 			} else
336530399e8Sjob 				printf("    Serial: %8s   Revocation Date: %s"
337530399e8Sjob 				    "\n", serial, time2str(t));
338530399e8Sjob 		}
3397cdd491fSclaudio 		free(serial);
340220c707cSclaudio 	}
341530399e8Sjob 
342530399e8Sjob 	if (outformats & FORMAT_JSON)
343530399e8Sjob 		printf("\t],\n");
344530399e8Sjob 	else if (i == 0)
345220c707cSclaudio 		printf("No Revoked Certificates\n");
346220c707cSclaudio }
347220c707cSclaudio 
348220c707cSclaudio void
349530399e8Sjob mft_print(const X509 *x, const struct mft *p)
350714f4e3fSclaudio {
351714f4e3fSclaudio 	size_t i;
352714f4e3fSclaudio 	char *hash;
353714f4e3fSclaudio 
354530399e8Sjob 	if (outformats & FORMAT_JSON) {
355530399e8Sjob 		printf("\t\"type\": \"manifest\",\n");
356530399e8Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
357530399e8Sjob 		x509_print(x);
358530399e8Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
359530399e8Sjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
3602cf0e122Sjob 		printf("\t\"sia\": \"%s\",\n", p->sia);
361530399e8Sjob 		printf("\t\"manifest_number\": \"%s\",\n", p->seqnum);
362530399e8Sjob 		printf("\t\"valid_since\": %lld,\n", (long long)p->valid_since);
363530399e8Sjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->valid_until);
364530399e8Sjob 	} else {
365714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
366714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
367530399e8Sjob 		x509_print(x);
368714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
3692cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
370714f4e3fSclaudio 		printf("Manifest Number:          %s\n", p->seqnum);
371530399e8Sjob 		printf("Manifest valid since:     %s\n", time2str(p->valid_since));
372530399e8Sjob 		printf("Manifest valid until:     %s\n", time2str(p->valid_until));
3736530cf17Sjob 		printf("Files and hashes:\n");
374530399e8Sjob 	}
375530399e8Sjob 
376714f4e3fSclaudio 	for (i = 0; i < p->filesz; i++) {
377530399e8Sjob 		if (i == 0 && outformats & FORMAT_JSON)
378530399e8Sjob 			printf("\t\"filesandhashes\": [\n");
379530399e8Sjob 
380714f4e3fSclaudio 		if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash),
381714f4e3fSclaudio 		    &hash) == -1)
382714f4e3fSclaudio 			errx(1, "base64_encode failure");
383530399e8Sjob 
384530399e8Sjob 		if (outformats & FORMAT_JSON) {
385530399e8Sjob 			printf("\t\t{ \"filename\": \"%s\",", p->files[i].file);
386530399e8Sjob 			printf(" \"hash\": \"%s\" }", hash);
387530399e8Sjob 			if (i + 1 < p->filesz)
388530399e8Sjob 				printf(",");
389530399e8Sjob 			printf("\n");
390530399e8Sjob 		} else {
391714f4e3fSclaudio 			printf("%5zu: %s\n", i + 1, p->files[i].file);
392714f4e3fSclaudio 			printf("\thash %s\n", hash);
393530399e8Sjob 		}
394530399e8Sjob 
395714f4e3fSclaudio 		free(hash);
396714f4e3fSclaudio 	}
397530399e8Sjob 
398530399e8Sjob 	if (outformats & FORMAT_JSON)
399530399e8Sjob 		printf("\t],\n");
400714f4e3fSclaudio }
401714f4e3fSclaudio 
402714f4e3fSclaudio void
403530399e8Sjob roa_print(const X509 *x, const struct roa *p)
404714f4e3fSclaudio {
405714f4e3fSclaudio 	char	 buf[128];
406714f4e3fSclaudio 	size_t	 i;
407714f4e3fSclaudio 
408530399e8Sjob 	if (outformats & FORMAT_JSON) {
409530399e8Sjob 		printf("\t\"type\": \"roa\",\n");
410530399e8Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
411530399e8Sjob 		x509_print(x);
412530399e8Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
413530399e8Sjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
4142cf0e122Sjob 		printf("\t\"sia\": \"%s\",\n", p->sia);
415530399e8Sjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
416530399e8Sjob 	} else {
417714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
418530399e8Sjob 		x509_print(x);
419714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
420714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
4212cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
422220c707cSclaudio 		printf("ROA valid until:          %s\n", time2str(p->expires));
423714f4e3fSclaudio 		printf("asID:                     %u\n", p->asid);
4242cf0e122Sjob 		printf("IP address blocks:\n");
425530399e8Sjob 	}
426530399e8Sjob 
427714f4e3fSclaudio 	for (i = 0; i < p->ipsz; i++) {
428530399e8Sjob 		if (i == 0 && outformats & FORMAT_JSON)
429530399e8Sjob 			printf("\t\"vrps\": [\n");
430530399e8Sjob 
431714f4e3fSclaudio 		ip_addr_print(&p->ips[i].addr,
432714f4e3fSclaudio 		    p->ips[i].afi, buf, sizeof(buf));
433530399e8Sjob 
434530399e8Sjob 		if (outformats & FORMAT_JSON) {
435530399e8Sjob 			printf("\t\t{ \"prefix\": \"%s\",", buf);
436530399e8Sjob 			printf(" \"asid\": %u,", p->asid);
437530399e8Sjob 			printf(" \"maxlen\": %hhu }", p->ips[i].maxlength);
438530399e8Sjob 			if (i + 1 < p->ipsz)
439530399e8Sjob 				printf(",");
440530399e8Sjob 			printf("\n");
441530399e8Sjob 		} else
442530399e8Sjob 			printf("%5zu: %s maxlen: %hhu\n", i + 1, buf,
443530399e8Sjob 			    p->ips[i].maxlength);
444714f4e3fSclaudio 	}
445530399e8Sjob 
446530399e8Sjob 	if (outformats & FORMAT_JSON)
447530399e8Sjob 		printf("\t],\n");
448714f4e3fSclaudio }
449714f4e3fSclaudio 
450714f4e3fSclaudio void
451530399e8Sjob gbr_print(const X509 *x, const struct gbr *p)
452714f4e3fSclaudio {
453530399e8Sjob 	size_t	i;
454530399e8Sjob 
455530399e8Sjob 	if (outformats & FORMAT_JSON) {
456530399e8Sjob 		printf("\t\"type\": \"gbr\",\n");
457530399e8Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
458530399e8Sjob 		x509_print(x);
459530399e8Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
460530399e8Sjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
4612cf0e122Sjob 		printf("\t\"sia\": \"%s\",\n", p->sia);
462530399e8Sjob 		printf("\t\"vcard\": \"");
463530399e8Sjob 		for (i = 0; i < strlen(p->vcard); i++) {
464530399e8Sjob 			if (p->vcard[i] == '"')
465530399e8Sjob 				printf("\\\"");
466530399e8Sjob 			if (p->vcard[i] == '\r')
467530399e8Sjob 				continue;
468530399e8Sjob 			if (p->vcard[i] == '\n')
469530399e8Sjob 				printf("\\r\\n");
470530399e8Sjob 			else
471530399e8Sjob 				putchar(p->vcard[i]);
472530399e8Sjob 		}
473530399e8Sjob 		printf("\",\n");
474530399e8Sjob 	} else {
475714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
476530399e8Sjob 		x509_print(x);
477714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
478714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
4792cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
480714f4e3fSclaudio 		printf("vcard:\n%s", p->vcard);
481714f4e3fSclaudio 	}
482530399e8Sjob }
48304834fbdSjob 
48404834fbdSjob void
48504834fbdSjob rsc_print(const X509 *x, const struct rsc *p)
48604834fbdSjob {
48728439bbcSjob 	char	 buf1[64], buf2[64];
48804834fbdSjob 	char	*hash;
48904834fbdSjob 	int	 sockt;
49004834fbdSjob 	size_t	 i, j;
49104834fbdSjob 
49204834fbdSjob 	if (outformats & FORMAT_JSON) {
49304834fbdSjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
49404834fbdSjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
49504834fbdSjob 		x509_print(x);
49604834fbdSjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
49704834fbdSjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
49804834fbdSjob 		printf("\t\"signed_with_resources\": [\n");
49904834fbdSjob 	} else {
50004834fbdSjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
50104834fbdSjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
50204834fbdSjob 		x509_print(x);
50304834fbdSjob 		printf("Authority info access:    %s\n", p->aia);
50428439bbcSjob 		printf("RSC valid until:          %s\n", time2str(p->expires));
50504834fbdSjob 		printf("Signed with resources:\n");
50604834fbdSjob 	}
50704834fbdSjob 
50804834fbdSjob 	for (i = 0; i < p->asz; i++) {
50904834fbdSjob 		switch (p->as[i].type) {
51004834fbdSjob 		case CERT_AS_ID:
51104834fbdSjob 			if (outformats & FORMAT_JSON)
51204834fbdSjob 				printf("\t\t{ \"asid\": %u }", p->as[i].id);
51304834fbdSjob 			else
51404834fbdSjob 				printf("%5zu: AS: %u", i + 1, p->as[i].id);
51504834fbdSjob 			break;
51604834fbdSjob 		case CERT_AS_RANGE:
51704834fbdSjob 			if (outformats & FORMAT_JSON)
51804834fbdSjob 				printf("\t\t{ \"asrange\": { \"min\": %u, "
51904834fbdSjob 				    "\"max\": %u }}", p->as[i].range.min,
52004834fbdSjob 				    p->as[i].range.max);
52104834fbdSjob 			else
52204834fbdSjob 				printf("%5zu: AS: %u -- %u", i + 1,
52304834fbdSjob 				    p->as[i].range.min, p->as[i].range.max);
52404834fbdSjob 			break;
52504834fbdSjob 		case CERT_AS_INHERIT:
52604834fbdSjob 			/* inheritance isn't possible in RSC */
52704834fbdSjob 			break;
52804834fbdSjob 		}
52904834fbdSjob 		if (outformats & FORMAT_JSON && i + 1 < p->asz + p->ipsz)
53004834fbdSjob 			printf(",\n");
53104834fbdSjob 		else
53204834fbdSjob 			printf("\n");
53304834fbdSjob 	}
53404834fbdSjob 
53504834fbdSjob 	for (j = 0; j < p->ipsz; j++) {
53604834fbdSjob 		switch (p->ips[j].type) {
53704834fbdSjob 		case CERT_IP_ADDR:
53804834fbdSjob 			ip_addr_print(&p->ips[j].ip,
53904834fbdSjob 			    p->ips[j].afi, buf1, sizeof(buf1));
54004834fbdSjob 			if (outformats & FORMAT_JSON)
54104834fbdSjob 				printf("\t\t{ \"ip_prefix\": \"%s\" }", buf1);
54204834fbdSjob 			else
54304834fbdSjob 				printf("%5zu: IP: %s", i + j + 1, buf1);
54404834fbdSjob 			break;
54504834fbdSjob 		case CERT_IP_RANGE:
54604834fbdSjob 			sockt = (p->ips[j].afi == AFI_IPV4) ?
54704834fbdSjob 			    AF_INET : AF_INET6;
54804834fbdSjob 			inet_ntop(sockt, p->ips[j].min, buf1, sizeof(buf1));
54904834fbdSjob 			inet_ntop(sockt, p->ips[j].max, buf2, sizeof(buf2));
55004834fbdSjob 			if (outformats & FORMAT_JSON)
55104834fbdSjob 				printf("\t\t{ \"ip_range\": { \"min\": \"%s\""
55204834fbdSjob 				    ", \"max\": \"%s\" }}", buf1, buf2);
55304834fbdSjob 			else
55404834fbdSjob 				printf("%5zu: IP: %s -- %s", i + j + 1, buf1,
55504834fbdSjob 				    buf2);
55604834fbdSjob 			break;
55704834fbdSjob 		case CERT_IP_INHERIT:
55804834fbdSjob 			/* inheritance isn't possible in RSC */
55904834fbdSjob 			break;
56004834fbdSjob 		}
56104834fbdSjob 		if (outformats & FORMAT_JSON && i + j + 1 < p->asz + p->ipsz)
56204834fbdSjob 			printf(",\n");
56304834fbdSjob 		else
56404834fbdSjob 			printf("\n");
56504834fbdSjob 	}
56604834fbdSjob 
56704834fbdSjob 	if (outformats & FORMAT_JSON) {
56804834fbdSjob 		printf("\t],\n");
56904834fbdSjob 		printf("\t\"filenamesandhashes\": [\n");
57004834fbdSjob 	} else
57104834fbdSjob 		printf("Filenames and hashes:\n");
57204834fbdSjob 
57304834fbdSjob 	for (i = 0; i < p->filesz; i++) {
57404834fbdSjob 		if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash),
57504834fbdSjob 		    &hash) == -1)
57604834fbdSjob 			errx(1, "base64_encode failure");
57704834fbdSjob 
57804834fbdSjob 		if (outformats & FORMAT_JSON) {
57904834fbdSjob 			printf("\t\t{ \"filename\": \"%s\",",
58004834fbdSjob 			    p->files[i].filename ? p->files[i].filename : "");
58104834fbdSjob 			printf(" \"hash_digest\": \"%s\" }", hash);
58204834fbdSjob 			if (i + 1 < p->filesz)
58304834fbdSjob 				printf(",");
58404834fbdSjob 			printf("\n");
58504834fbdSjob 		} else {
58604834fbdSjob 			printf("%5zu: %s\n", i + 1, p->files[i].filename
58704834fbdSjob 			    ? p->files[i].filename : "no filename");
58804834fbdSjob 			printf("\thash %s\n", hash);
58904834fbdSjob 		}
59004834fbdSjob 
59104834fbdSjob 		free(hash);
59204834fbdSjob 	}
59304834fbdSjob 
59404834fbdSjob 	if (outformats & FORMAT_JSON)
59504834fbdSjob 		printf("\t],\n");
59604834fbdSjob }
597a29ddfd5Sjob 
598a29ddfd5Sjob void
599a29ddfd5Sjob aspa_print(const X509 *x, const struct aspa *p)
600a29ddfd5Sjob {
601a29ddfd5Sjob 	size_t	i;
602a29ddfd5Sjob 
603a29ddfd5Sjob 	if (outformats & FORMAT_JSON) {
604a29ddfd5Sjob 		printf("\t\"type\": \"aspa\",\n");
605a29ddfd5Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
606a29ddfd5Sjob 		x509_print(x);
607a29ddfd5Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
608a29ddfd5Sjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
6092cf0e122Sjob 		printf("\t\"sia\": \"%s\",\n", p->sia);
61028439bbcSjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
611a29ddfd5Sjob 		printf("\t\"customer_asid\": %u,\n", p->custasid);
612a29ddfd5Sjob 		printf("\t\"provider_set\": [\n");
613a29ddfd5Sjob 		for (i = 0; i < p->providersz; i++) {
614a29ddfd5Sjob 			printf("\t\t{ \"asid\": %u", p->providers[i].as);
615a29ddfd5Sjob 			if (p->providers[i].afi == AFI_IPV4)
616a29ddfd5Sjob 				printf(", \"afi_limit\": \"ipv4\"");
617a29ddfd5Sjob 			if (p->providers[i].afi == AFI_IPV6)
618a29ddfd5Sjob 				printf(", \"afi_limit\": \"ipv6\"");
619a29ddfd5Sjob 			printf(" }");
620a29ddfd5Sjob 			if (i + 1 < p->providersz)
621a29ddfd5Sjob 				printf(",");
622a29ddfd5Sjob 			printf("\n");
623a29ddfd5Sjob 		}
624a29ddfd5Sjob 		printf("\t],\n");
625a29ddfd5Sjob 	} else {
626a29ddfd5Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
627a29ddfd5Sjob 		x509_print(x);
628a29ddfd5Sjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
629a29ddfd5Sjob 		printf("Authority info access:    %s\n", p->aia);
6302cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
63128439bbcSjob 		printf("ASPA valid until:         %s\n", time2str(p->expires));
632a29ddfd5Sjob 		printf("Customer AS:              %u\n", p->custasid);
633a29ddfd5Sjob 		printf("Provider Set:\n");
634a29ddfd5Sjob 		for (i = 0; i < p->providersz; i++) {
635a29ddfd5Sjob 			printf("%5zu: AS: %d", i + 1, p->providers[i].as);
636a29ddfd5Sjob 			switch (p->providers[i].afi) {
637a29ddfd5Sjob 			case AFI_IPV4:
638a29ddfd5Sjob 				printf(" (IPv4 only)");
639a29ddfd5Sjob 				break;
640a29ddfd5Sjob 			case AFI_IPV6:
641a29ddfd5Sjob 				printf(" (IPv6 only)");
642a29ddfd5Sjob 				break;
643a29ddfd5Sjob 			default:
644a29ddfd5Sjob 				break;
645a29ddfd5Sjob 			}
646a29ddfd5Sjob 			printf("\n");
647a29ddfd5Sjob 		}
648a29ddfd5Sjob 	}
649a29ddfd5Sjob }
650ee2a33daSjob 
651ee2a33daSjob static void
652ee2a33daSjob takey_print(char *name, const struct takey *t)
653ee2a33daSjob {
654ee2a33daSjob 	char	*spki = NULL;
655ee2a33daSjob 	size_t	 i, j = 0;
656ee2a33daSjob 
657ee2a33daSjob 	if (base64_encode(t->pubkey, t->pubkeysz, &spki) != 0)
658ee2a33daSjob 		errx(1, "base64_encode failed in %s", __func__);
659ee2a33daSjob 
660ee2a33daSjob 	if (outformats & FORMAT_JSON) {
661ee2a33daSjob 		printf("\t\t{\n\t\t\t\"name\": \"%s\",\n", name);
662ee2a33daSjob 		printf("\t\t\t\"comments\": [");
663ee2a33daSjob 		for (i = 0; i < t->commentsz; i++) {
664ee2a33daSjob 			printf("\"%s\"", t->comments[i]);
665ee2a33daSjob 			if (i + 1 < t->commentsz)
666ee2a33daSjob 				printf(", ");
667ee2a33daSjob 		}
668ee2a33daSjob 		printf("],\n");
669ee2a33daSjob 		printf("\t\t\t\"uris\": [");
670ee2a33daSjob 		for (i = 0; i < t->urisz; i++) {
671ee2a33daSjob 			printf("\"%s\"", t->uris[i]);
672ee2a33daSjob 			if (i + 1 < t->urisz)
673ee2a33daSjob 				printf(", ");
674ee2a33daSjob 		}
675ee2a33daSjob 		printf("],\n");
676ee2a33daSjob 		printf("\t\t\t\"spki\": \"%s\"\n\t\t}", spki);
677ee2a33daSjob 	} else {
678ee2a33daSjob 		printf("TAL derived from the '%s' Trust Anchor Key:\n\n", name);
679ee2a33daSjob 
680ee2a33daSjob 		for (i = 0; i < t->commentsz; i++) {
681ee2a33daSjob 			printf("\t# %s\n", t->comments[i]);
682ee2a33daSjob 		}
683ee2a33daSjob 
684ee2a33daSjob 		for (i = 0; i < t->urisz; i++) {
685ee2a33daSjob 			printf("\t%s\n\n\t", t->uris[i]);
686ee2a33daSjob 		}
687ee2a33daSjob 
688ee2a33daSjob 		for (i = 0; i < strlen(spki); i++) {
689ee2a33daSjob 			printf("%c", spki[i]);
690ee2a33daSjob 			j++;
691ee2a33daSjob 			if (j == 64) {
692ee2a33daSjob 				printf("\n\t");
693ee2a33daSjob 				j = 0;
694ee2a33daSjob 			}
695ee2a33daSjob 		}
696ee2a33daSjob 
697ee2a33daSjob 		printf("\n\n");
698ee2a33daSjob 	}
699ee2a33daSjob 
700ee2a33daSjob 	free(spki);
701ee2a33daSjob }
702ee2a33daSjob 
703ee2a33daSjob void
704ee2a33daSjob tak_print(const X509 *x, const struct tak *p)
705ee2a33daSjob {
706ee2a33daSjob 	if (outformats & FORMAT_JSON) {
707ee2a33daSjob 		printf("\t\"type\": \"tak\",\n");
708ee2a33daSjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
709ee2a33daSjob 		x509_print(x);
710ee2a33daSjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
711ee2a33daSjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
7122cf0e122Sjob 		printf("\t\"sia\": \"%s\",\n", p->sia);
713ee2a33daSjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
714ee2a33daSjob 		printf("\t\"takeys\": [\n");
715ee2a33daSjob 	} else {
716ee2a33daSjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
717ee2a33daSjob 		x509_print(x);
718ee2a33daSjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
719ee2a33daSjob 		printf("Authority info access:    %s\n", p->aia);
7202cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
72128439bbcSjob 		printf("TAK valid until:          %s\n", time2str(p->expires));
722ee2a33daSjob 	}
723ee2a33daSjob 
724ee2a33daSjob 	takey_print("current", p->current);
725ee2a33daSjob 
726ee2a33daSjob 	if (p->predecessor != NULL) {
727ee2a33daSjob 		if (outformats & FORMAT_JSON)
728ee2a33daSjob 			printf(",\n");
729ee2a33daSjob 		takey_print("predecessor", p->predecessor);
730ee2a33daSjob 	}
731ee2a33daSjob 
732ee2a33daSjob 	if (p->successor != NULL) {
733ee2a33daSjob 		if (outformats & FORMAT_JSON)
734ee2a33daSjob 			printf(",\n");
735ee2a33daSjob 		takey_print("successor", p->successor);
736ee2a33daSjob 	}
737ee2a33daSjob 
738ee2a33daSjob 	if (outformats & FORMAT_JSON)
739ee2a33daSjob 		printf("\n\t],\n");
740ee2a33daSjob }
741ef3f6f56Sjob 
742ef3f6f56Sjob void
743ef3f6f56Sjob geofeed_print(const X509 *x, const struct geofeed *p)
744ef3f6f56Sjob {
745ef3f6f56Sjob 	char	 buf[128];
746ef3f6f56Sjob 	size_t	 i;
747ef3f6f56Sjob 
748ef3f6f56Sjob 	if (outformats & FORMAT_JSON) {
749ef3f6f56Sjob 		printf("\t\"type\": \"geofeed\",\n");
750ef3f6f56Sjob 		printf("\t\"ski\": \"%s\",\n", pretty_key_id(p->ski));
751ef3f6f56Sjob 		x509_print(x);
752ef3f6f56Sjob 		printf("\t\"aki\": \"%s\",\n", pretty_key_id(p->aki));
753ef3f6f56Sjob 		printf("\t\"aia\": \"%s\",\n", p->aia);
754ef3f6f56Sjob 		printf("\t\"valid_until\": %lld,\n", (long long)p->expires);
755ef3f6f56Sjob 		printf("\t\"records\": [\n");
756ef3f6f56Sjob 	} else {
757ef3f6f56Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
758ef3f6f56Sjob 		x509_print(x);
759ef3f6f56Sjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
760ef3f6f56Sjob 		printf("Authority info access:    %s\n", p->aia);
761ef3f6f56Sjob 		printf("Geofeed valid until:      %s\n", time2str(p->expires));
762ef3f6f56Sjob 		printf("Geofeed CSV records:\n");
763ef3f6f56Sjob 	}
764ef3f6f56Sjob 
765ef3f6f56Sjob 	for (i = 0; i < p->geoipsz; i++) {
766ef3f6f56Sjob 		if (p->geoips[i].ip->type != CERT_IP_ADDR)
767ef3f6f56Sjob 			continue;
768ef3f6f56Sjob 
769ef3f6f56Sjob 		ip_addr_print(&p->geoips[i].ip->ip, p->geoips[i].ip->afi, buf,
770ef3f6f56Sjob 		    sizeof(buf));
771ef3f6f56Sjob 		if (outformats & FORMAT_JSON)
772ef3f6f56Sjob 			printf("\t\t{ \"prefix\": \"%s\", \"location\": \"%s\""
773ef3f6f56Sjob 			    "}", buf, p->geoips[i].loc);
774ef3f6f56Sjob 		else
775ef3f6f56Sjob 			printf("%5zu: IP: %s (%s)", i + 1, buf,
776ef3f6f56Sjob 			    p->geoips[i].loc);
777ef3f6f56Sjob 
778ef3f6f56Sjob 		if (outformats & FORMAT_JSON && i + 1 < p->geoipsz)
779ef3f6f56Sjob 			printf(",\n");
780ef3f6f56Sjob 		else
781ef3f6f56Sjob 			printf("\n");
782ef3f6f56Sjob 	}
783ef3f6f56Sjob 
784ef3f6f56Sjob 	if (outformats & FORMAT_JSON)
785ef3f6f56Sjob 		printf("\t],\n");
786ef3f6f56Sjob }
787