xref: /openbsd/usr.sbin/rpki-client/print.c (revision ddc87cff)
1*ddc87cffSjob /*	$OpenBSD: print.c,v 1.47 2024/02/13 20:36:42 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"
317aabcda0Sclaudio #include "json.h"
32714f4e3fSclaudio 
33714f4e3fSclaudio static const char *
34254fd372Sclaudio pretty_key_id(const char *hex)
35714f4e3fSclaudio {
36714f4e3fSclaudio 	static char buf[128];	/* bigger than SHA_DIGEST_LENGTH * 3 */
37714f4e3fSclaudio 	size_t i;
38714f4e3fSclaudio 
39714f4e3fSclaudio 	for (i = 0; i < sizeof(buf) && *hex != '\0'; i++) {
40254fd372Sclaudio 		if (i % 3 == 2)
41714f4e3fSclaudio 			buf[i] = ':';
42714f4e3fSclaudio 		else
43714f4e3fSclaudio 			buf[i] = *hex++;
44714f4e3fSclaudio 	}
45714f4e3fSclaudio 	if (i == sizeof(buf))
46714f4e3fSclaudio 		memcpy(buf + sizeof(buf) - 4, "...", 4);
47254fd372Sclaudio 	else
48254fd372Sclaudio 		buf[i] = '\0';
49714f4e3fSclaudio 	return buf;
50714f4e3fSclaudio }
51714f4e3fSclaudio 
52220c707cSclaudio char *
5378de3577Stb nid2str(int nid)
5478de3577Stb {
5578de3577Stb 	static char buf[128];
5678de3577Stb 	const char *name;
5778de3577Stb 
5878de3577Stb 	if ((name = OBJ_nid2ln(nid)) == NULL)
5978de3577Stb 		name = OBJ_nid2sn(nid);
6078de3577Stb 	if (name == NULL)
6178de3577Stb 		name = "unknown";
6278de3577Stb 
6378de3577Stb 	snprintf(buf, sizeof(buf), "nid %d (%s)", nid, name);
6478de3577Stb 
6578de3577Stb 	return buf;
6678de3577Stb }
6778de3577Stb 
6878de3577Stb char *
69220c707cSclaudio time2str(time_t t)
70220c707cSclaudio {
71220c707cSclaudio 	static char buf[64];
72220c707cSclaudio 	struct tm tm;
73220c707cSclaudio 
74220c707cSclaudio 	if (gmtime_r(&t, &tm) == NULL)
75220c707cSclaudio 		return "could not convert time";
76220c707cSclaudio 
77cc6f004eSjob 	strftime(buf, sizeof(buf), "%a %d %b %Y %T %z", &tm);
78cc6f004eSjob 
79220c707cSclaudio 	return buf;
80220c707cSclaudio }
81220c707cSclaudio 
82714f4e3fSclaudio void
83714f4e3fSclaudio tal_print(const struct tal *p)
84714f4e3fSclaudio {
85e911df76Sjob 	char			*ski;
86*ddc87cffSjob 	const unsigned char	*der, *pkey_der;
87*ddc87cffSjob 	X509_PUBKEY		*pubkey;
88*ddc87cffSjob 	ASN1_OBJECT		*obj;
89e911df76Sjob 	unsigned char		 md[SHA_DIGEST_LENGTH];
90*ddc87cffSjob 	int			 nid, der_len;
91714f4e3fSclaudio 	size_t			 i;
92714f4e3fSclaudio 
93*ddc87cffSjob 	pkey_der = p->pkey;
94*ddc87cffSjob 	if ((pubkey = d2i_X509_PUBKEY(NULL, &pkey_der, p->pkeysz)) == NULL)
95*ddc87cffSjob 		errx(1, "d2i_X509_PUBKEY failed");
96e911df76Sjob 
97*ddc87cffSjob 	if (!X509_PUBKEY_get0_param(&obj, &der, &der_len, NULL, pubkey))
98*ddc87cffSjob 		errx(1, "X509_PUBKEY_get0_param failed");
99e911df76Sjob 
100*ddc87cffSjob 	if ((nid = OBJ_obj2nid(obj)) != NID_rsaEncryption)
101*ddc87cffSjob 		errx(1, "RFC 7935: wrong signature algorithm %s, want %s",
102*ddc87cffSjob 		    nid2str(nid), LN_rsaEncryption);
103*ddc87cffSjob 
104*ddc87cffSjob 	if (!EVP_Digest(der, der_len, md, NULL, EVP_sha1(), NULL))
105*ddc87cffSjob 		errx(1, "EVP_Digest failed");
106e911df76Sjob 
107e911df76Sjob 	ski = hex_encode(md, SHA_DIGEST_LENGTH);
108e911df76Sjob 
109530399e8Sjob 	if (outformats & FORMAT_JSON) {
1107aabcda0Sclaudio 		json_do_string("type", "tal");
1117aabcda0Sclaudio 		json_do_string("name", p->descr);
1127aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(ski));
1137aabcda0Sclaudio 		json_do_array("trust_anchor_locations");
1147aabcda0Sclaudio 		for (i = 0; i < p->urisz; i++)
1157aabcda0Sclaudio 			json_do_string("tal", p->uri[i]);
1167aabcda0Sclaudio 		json_do_end();
117530399e8Sjob 	} else {
118530399e8Sjob 		printf("Trust anchor name:        %s\n", p->descr);
119530399e8Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(ski));
1204486d057Sjob 		printf("Trust anchor locations:   ");
1214486d057Sjob 		for (i = 0; i < p->urisz; i++) {
1224486d057Sjob 			if (i > 0)
1234486d057Sjob 				printf("%26s", "");
1244486d057Sjob 			printf("%s\n", p->uri[i]);
1254486d057Sjob 		}
126530399e8Sjob 	}
127e911df76Sjob 
128*ddc87cffSjob 	X509_PUBKEY_free(pubkey);
129e911df76Sjob 	free(ski);
130714f4e3fSclaudio }
131714f4e3fSclaudio 
132714f4e3fSclaudio void
133530399e8Sjob x509_print(const X509 *x)
134530399e8Sjob {
135530399e8Sjob 	const ASN1_INTEGER	*xserial;
136e0b87278Sjob 	const X509_NAME		*xissuer;
137e0b87278Sjob 	char			*issuer = NULL;
138530399e8Sjob 	char			*serial = NULL;
139530399e8Sjob 
140e0b87278Sjob 	if ((xissuer = X509_get_issuer_name(x)) == NULL) {
141e0b87278Sjob 		warnx("X509_get_issuer_name failed");
142530399e8Sjob 		goto out;
143530399e8Sjob 	}
144530399e8Sjob 
145e0b87278Sjob 	if ((issuer = X509_NAME_oneline(xissuer, NULL, 0)) == NULL) {
146e0b87278Sjob 		warnx("X509_NAME_oneline failed");
147530399e8Sjob 		goto out;
148530399e8Sjob 	}
149530399e8Sjob 
150e0b87278Sjob 	if ((xserial = X509_get0_serialNumber(x)) == NULL) {
151e0b87278Sjob 		warnx("X509_get0_serialNumber failed");
152e0b87278Sjob 		goto out;
153e0b87278Sjob 	}
154e0b87278Sjob 
155e0b87278Sjob 	if ((serial = x509_convert_seqnum(__func__, xserial)) == NULL)
156e0b87278Sjob 		goto out;
157e0b87278Sjob 
158530399e8Sjob 	if (outformats & FORMAT_JSON) {
1597aabcda0Sclaudio 		json_do_string("cert_issuer", issuer);
1607aabcda0Sclaudio 		json_do_string("cert_serial", serial);
161530399e8Sjob 	} else {
162e0b87278Sjob 		printf("Certificate issuer:       %s\n", issuer);
163530399e8Sjob 		printf("Certificate serial:       %s\n", serial);
164530399e8Sjob 	}
165530399e8Sjob 
166530399e8Sjob  out:
167e0b87278Sjob 	free(issuer);
168530399e8Sjob 	free(serial);
169530399e8Sjob }
170530399e8Sjob 
1717aabcda0Sclaudio static void
1727aabcda0Sclaudio as_resources_print(struct cert_as *as, size_t asz)
1737aabcda0Sclaudio {
1747aabcda0Sclaudio 	size_t i;
1757aabcda0Sclaudio 
1767aabcda0Sclaudio 	for (i = 0; i < asz; i++) {
1777aabcda0Sclaudio 		if (outformats & FORMAT_JSON)
178a09a3191Sclaudio 			json_do_object("resource", 1);
1797aabcda0Sclaudio 		switch (as[i].type) {
1807aabcda0Sclaudio 		case CERT_AS_ID:
1817aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
1827aabcda0Sclaudio 				json_do_uint("asid", as[i].id);
1837aabcda0Sclaudio 			} else {
1847aabcda0Sclaudio 				if (i > 0)
1857aabcda0Sclaudio 					printf("%26s", "");
1867aabcda0Sclaudio 				printf("AS: %u", as[i].id);
1877aabcda0Sclaudio 			}
1887aabcda0Sclaudio 			break;
1897aabcda0Sclaudio 		case CERT_AS_INHERIT:
1907aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
1917aabcda0Sclaudio 				json_do_bool("asid_inherit", 1);
1927aabcda0Sclaudio 			} else {
1937aabcda0Sclaudio 				if (i > 0)
1947aabcda0Sclaudio 					printf("%26s", "");
1957aabcda0Sclaudio 				printf("AS: inherit");
1967aabcda0Sclaudio 			}
1977aabcda0Sclaudio 			break;
1987aabcda0Sclaudio 		case CERT_AS_RANGE:
1997aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
200a09a3191Sclaudio 				json_do_object("asrange", 1);
2017aabcda0Sclaudio 				json_do_uint("min", as[i].range.min);
2027aabcda0Sclaudio 				json_do_uint("max", as[i].range.max);
2037aabcda0Sclaudio 				json_do_end();
2047aabcda0Sclaudio 			} else {
2057aabcda0Sclaudio 				if (i > 0)
2067aabcda0Sclaudio 					printf("%26s", "");
2077aabcda0Sclaudio 				printf("AS: %u -- %u", as[i].range.min,
2087aabcda0Sclaudio 				    as[i].range.max);
2097aabcda0Sclaudio 			}
2107aabcda0Sclaudio 			break;
2117aabcda0Sclaudio 		}
2127aabcda0Sclaudio 		if (outformats & FORMAT_JSON)
2137aabcda0Sclaudio 			json_do_end();
2147aabcda0Sclaudio 		else
2157aabcda0Sclaudio 			printf("\n");
2167aabcda0Sclaudio 	}
2177aabcda0Sclaudio }
2187aabcda0Sclaudio 
2197aabcda0Sclaudio static void
2207aabcda0Sclaudio ip_resources_print(struct cert_ip *ips, size_t ipsz, size_t asz)
2217aabcda0Sclaudio {
2227aabcda0Sclaudio 	char buf1[64], buf2[64];
2237aabcda0Sclaudio 	size_t i;
2247aabcda0Sclaudio 	int sockt;
2257aabcda0Sclaudio 
2267aabcda0Sclaudio 
2277aabcda0Sclaudio 	for (i = 0; i < ipsz; i++) {
2287aabcda0Sclaudio 		if (outformats & FORMAT_JSON)
229a09a3191Sclaudio 			json_do_object("resource", 1);
2307aabcda0Sclaudio 		switch (ips[i].type) {
2317aabcda0Sclaudio 		case CERT_IP_INHERIT:
2327aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
2337aabcda0Sclaudio 				json_do_bool("ip_inherit", 1);
2347aabcda0Sclaudio 			} else {
2357aabcda0Sclaudio 				if (i > 0 || asz > 0)
2367aabcda0Sclaudio 					printf("%26s", "");
2377aabcda0Sclaudio 				printf("IP: inherit");
2387aabcda0Sclaudio 			}
2397aabcda0Sclaudio 			break;
2407aabcda0Sclaudio 		case CERT_IP_ADDR:
2417aabcda0Sclaudio 			ip_addr_print(&ips[i].ip, ips[i].afi, buf1,
2427aabcda0Sclaudio 			    sizeof(buf1));
2437aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
2447aabcda0Sclaudio 				json_do_string("ip_prefix", buf1);
2457aabcda0Sclaudio 			} else {
2467aabcda0Sclaudio 				if (i > 0 || asz > 0)
2477aabcda0Sclaudio 					printf("%26s", "");
2487aabcda0Sclaudio 				printf("IP: %s", buf1);
2497aabcda0Sclaudio 			}
2507aabcda0Sclaudio 			break;
2517aabcda0Sclaudio 		case CERT_IP_RANGE:
2527aabcda0Sclaudio 			sockt = (ips[i].afi == AFI_IPV4) ?
2537aabcda0Sclaudio 			    AF_INET : AF_INET6;
2547aabcda0Sclaudio 			inet_ntop(sockt, ips[i].min, buf1, sizeof(buf1));
2557aabcda0Sclaudio 			inet_ntop(sockt, ips[i].max, buf2, sizeof(buf2));
2567aabcda0Sclaudio 			if (outformats & FORMAT_JSON) {
257a09a3191Sclaudio 				json_do_object("ip_range", 1);
2587aabcda0Sclaudio 				json_do_string("min", buf1);
2597aabcda0Sclaudio 				json_do_string("max", buf2);
2607aabcda0Sclaudio 				json_do_end();
2617aabcda0Sclaudio 			} else {
2627aabcda0Sclaudio 				if (i > 0 || asz > 0)
2637aabcda0Sclaudio 					printf("%26s", "");
2647aabcda0Sclaudio 				printf("IP: %s -- %s", buf1, buf2);
2657aabcda0Sclaudio 			}
2667aabcda0Sclaudio 			break;
2677aabcda0Sclaudio 		}
2687aabcda0Sclaudio 		if (outformats & FORMAT_JSON)
2697aabcda0Sclaudio 			json_do_end();
2707aabcda0Sclaudio 		else
2717aabcda0Sclaudio 			printf("\n");
2727aabcda0Sclaudio 	}
2737aabcda0Sclaudio }
2747aabcda0Sclaudio 
275530399e8Sjob void
276714f4e3fSclaudio cert_print(const struct cert *p)
277714f4e3fSclaudio {
278530399e8Sjob 	if (outformats & FORMAT_JSON) {
279530399e8Sjob 		if (p->pubkey != NULL)
2807aabcda0Sclaudio 			json_do_string("type", "router_key");
281530399e8Sjob 		else
2827aabcda0Sclaudio 			json_do_string("type", "ca_cert");
2837aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
284530399e8Sjob 		if (p->aki != NULL)
2857aabcda0Sclaudio 			json_do_string("aki", pretty_key_id(p->aki));
286530399e8Sjob 		x509_print(p->x509);
287530399e8Sjob 		if (p->aia != NULL)
2887aabcda0Sclaudio 			json_do_string("aia", p->aia);
289530399e8Sjob 		if (p->mft != NULL)
2907aabcda0Sclaudio 			json_do_string("manifest", p->mft);
291530399e8Sjob 		if (p->repo != NULL)
2927aabcda0Sclaudio 			json_do_string("carepository", p->repo);
293530399e8Sjob 		if (p->notify != NULL)
2947aabcda0Sclaudio 			json_do_string("notify_url", p->notify);
295530399e8Sjob 		if (p->pubkey != NULL)
2967aabcda0Sclaudio 			json_do_string("router_key", p->pubkey);
2977aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
2987aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
299894936b4Sjob 		if (p->expires)
3007aabcda0Sclaudio 			json_do_int("expires", p->expires);
3017aabcda0Sclaudio 		json_do_array("subordinate_resources");
302530399e8Sjob 	} else {
303714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
304714f4e3fSclaudio 		if (p->aki != NULL)
30567642cb5Stb 			printf("Authority key identifier: %s\n",
30667642cb5Stb 			    pretty_key_id(p->aki));
307530399e8Sjob 		x509_print(p->x509);
308714f4e3fSclaudio 		if (p->aia != NULL)
309714f4e3fSclaudio 			printf("Authority info access:    %s\n", p->aia);
310714f4e3fSclaudio 		if (p->mft != NULL)
311714f4e3fSclaudio 			printf("Manifest:                 %s\n", p->mft);
312714f4e3fSclaudio 		if (p->repo != NULL)
313714f4e3fSclaudio 			printf("caRepository:             %s\n", p->repo);
314714f4e3fSclaudio 		if (p->notify != NULL)
315714f4e3fSclaudio 			printf("Notify URL:               %s\n", p->notify);
31614d83341Sjob 		if (p->pubkey != NULL) {
3176530cf17Sjob 			printf("BGPsec ECDSA public key:  %s\n",
31867642cb5Stb 			    p->pubkey);
3194dbb22b8Sjob 			printf("Router key not before:    %s\n",
320f5999ddfSjob 			    time2str(p->notbefore));
3214dbb22b8Sjob 			printf("Router key not after:     %s\n",
3229f544822Sjob 			    time2str(p->notafter));
323f5999ddfSjob 		} else {
3244dbb22b8Sjob 			printf("Certificate not before:   %s\n",
325f5999ddfSjob 			    time2str(p->notbefore));
3264dbb22b8Sjob 			printf("Certificate not after:    %s\n",
3279f544822Sjob 			    time2str(p->notafter));
328f5999ddfSjob 		}
3294486d057Sjob 		printf("Subordinate resources:    ");
330530399e8Sjob 	}
331714f4e3fSclaudio 
3327aabcda0Sclaudio 	as_resources_print(p->as, p->asz);
3337aabcda0Sclaudio 	ip_resources_print(p->ips, p->ipsz, p->asz);
334530399e8Sjob 
335530399e8Sjob 	if (outformats & FORMAT_JSON)
3367aabcda0Sclaudio 		json_do_end();
337714f4e3fSclaudio }
338714f4e3fSclaudio 
339714f4e3fSclaudio void
340220c707cSclaudio crl_print(const struct crl *p)
341220c707cSclaudio {
342220c707cSclaudio 	STACK_OF(X509_REVOKED)	*revlist;
343220c707cSclaudio 	X509_REVOKED *rev;
344e0b87278Sjob 	X509_NAME *xissuer;
345220c707cSclaudio 	int i;
346e0b87278Sjob 	char *issuer, *serial;
347220c707cSclaudio 	time_t t;
348220c707cSclaudio 
349530399e8Sjob 	if (outformats & FORMAT_JSON) {
3507aabcda0Sclaudio 		json_do_string("type", "crl");
3517aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
352530399e8Sjob 	} else
353220c707cSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
3547cdd491fSclaudio 
355e0b87278Sjob 	xissuer = X509_CRL_get_issuer(p->x509_crl);
356e0b87278Sjob 	issuer = X509_NAME_oneline(xissuer, NULL, 0);
3570a722356Stb 	if (issuer != NULL && p->number != NULL) {
358e0b87278Sjob 		if (outformats & FORMAT_JSON) {
3597aabcda0Sclaudio 			json_do_string("crl_issuer", issuer);
3600a722356Stb 			json_do_string("crl_serial", p->number);
361e0b87278Sjob 		} else {
362e0b87278Sjob 			printf("CRL issuer:               %s\n", issuer);
3630a722356Stb 			printf("CRL serial number:        %s\n", p->number);
364530399e8Sjob 		}
365e0b87278Sjob 	}
366e0b87278Sjob 	free(issuer);
3677cdd491fSclaudio 
368530399e8Sjob 	if (outformats & FORMAT_JSON) {
369c527cc7aSjob 		json_do_int("valid_since", p->thisupdate);
3707aabcda0Sclaudio 		json_do_int("valid_until", p->nextupdate);
3717aabcda0Sclaudio 		json_do_array("revoked_certs");
372530399e8Sjob 	} else {
373c527cc7aSjob 		printf("CRL this update:          %s\n",
374c527cc7aSjob 		    time2str(p->thisupdate));
3754dbb22b8Sjob 		printf("CRL next update:          %s\n",
3769f544822Sjob 		    time2str(p->nextupdate));
377530399e8Sjob 		printf("Revoked Certificates:\n");
378530399e8Sjob 	}
379220c707cSclaudio 
380220c707cSclaudio 	revlist = X509_CRL_get_REVOKED(p->x509_crl);
381220c707cSclaudio 	for (i = 0; i < sk_X509_REVOKED_num(revlist); i++) {
382220c707cSclaudio 		rev = sk_X509_REVOKED_value(revlist, i);
3837cdd491fSclaudio 		serial = x509_convert_seqnum(__func__,
3847cdd491fSclaudio 		    X509_REVOKED_get0_serialNumber(rev));
385220c707cSclaudio 		x509_get_time(X509_REVOKED_get0_revocationDate(rev), &t);
386530399e8Sjob 		if (serial != NULL) {
387530399e8Sjob 			if (outformats & FORMAT_JSON) {
388a09a3191Sclaudio 				json_do_object("cert", 1);
3897aabcda0Sclaudio 				json_do_string("serial", serial);
3907aabcda0Sclaudio 				json_do_string("date", time2str(t));
3917aabcda0Sclaudio 				json_do_end();
392530399e8Sjob 			} else
3934486d057Sjob 				printf("%25s Serial: %8s   Revocation Date: %s"
3944486d057Sjob 				    "\n", "", serial, time2str(t));
395530399e8Sjob 		}
3967cdd491fSclaudio 		free(serial);
397220c707cSclaudio 	}
398530399e8Sjob 
399530399e8Sjob 	if (outformats & FORMAT_JSON)
4007aabcda0Sclaudio 		json_do_end();
401530399e8Sjob 	else if (i == 0)
402220c707cSclaudio 		printf("No Revoked Certificates\n");
403220c707cSclaudio }
404220c707cSclaudio 
405220c707cSclaudio void
406530399e8Sjob mft_print(const X509 *x, const struct mft *p)
407714f4e3fSclaudio {
408714f4e3fSclaudio 	size_t i;
409714f4e3fSclaudio 	char *hash;
410714f4e3fSclaudio 
411530399e8Sjob 	if (outformats & FORMAT_JSON) {
4127aabcda0Sclaudio 		json_do_string("type", "manifest");
4137aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
414530399e8Sjob 		x509_print(x);
4157aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
4167aabcda0Sclaudio 		json_do_string("aia", p->aia);
4177aabcda0Sclaudio 		json_do_string("sia", p->sia);
4187aabcda0Sclaudio 		json_do_string("manifest_number", p->seqnum);
4191bb1e509Sjob 		if (p->signtime != 0)
4207aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
4217aabcda0Sclaudio 		json_do_int("valid_since", p->thisupdate);
4227aabcda0Sclaudio 		json_do_int("valid_until", p->nextupdate);
423894936b4Sjob 		if (p->expires)
4247aabcda0Sclaudio 			json_do_int("expires", p->expires);
425530399e8Sjob 	} else {
426714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
427714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
428530399e8Sjob 		x509_print(x);
429714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
4302cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
431489e308aSjob 		printf("Manifest number:          %s\n", p->seqnum);
4321bb1e509Sjob 		if (p->signtime != 0)
4331bb1e509Sjob 			printf("Signing time:             %s\n",
4341bb1e509Sjob 			    time2str(p->signtime));
4354dbb22b8Sjob 		printf("Manifest this update:     %s\n", time2str(p->thisupdate));
4364dbb22b8Sjob 		printf("Manifest next update:     %s\n", time2str(p->nextupdate));
4374486d057Sjob 		printf("Files and hashes:         ");
438530399e8Sjob 	}
439530399e8Sjob 
4407aabcda0Sclaudio 	if (outformats & FORMAT_JSON)
4417aabcda0Sclaudio 		json_do_array("filesandhashes");
442714f4e3fSclaudio 	for (i = 0; i < p->filesz; i++) {
443714f4e3fSclaudio 		if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash),
444714f4e3fSclaudio 		    &hash) == -1)
445714f4e3fSclaudio 			errx(1, "base64_encode failure");
446530399e8Sjob 
447530399e8Sjob 		if (outformats & FORMAT_JSON) {
448a09a3191Sclaudio 			json_do_object("filehash", 1);
4497aabcda0Sclaudio 			json_do_string("filename", p->files[i].file);
4507aabcda0Sclaudio 			json_do_string("hash", hash);
4517aabcda0Sclaudio 			json_do_end();
452530399e8Sjob 		} else {
4534486d057Sjob 			if (i > 0)
4544486d057Sjob 				printf("%26s", "");
4554486d057Sjob 			printf("%zu: %s (hash: %s)\n", i + 1, p->files[i].file,
4564486d057Sjob 			    hash);
457530399e8Sjob 		}
458530399e8Sjob 
459714f4e3fSclaudio 		free(hash);
460714f4e3fSclaudio 	}
461530399e8Sjob 	if (outformats & FORMAT_JSON)
4627aabcda0Sclaudio 		json_do_end();
463714f4e3fSclaudio }
464714f4e3fSclaudio 
465714f4e3fSclaudio void
466530399e8Sjob roa_print(const X509 *x, const struct roa *p)
467714f4e3fSclaudio {
468714f4e3fSclaudio 	char	 buf[128];
469714f4e3fSclaudio 	size_t	 i;
470714f4e3fSclaudio 
471530399e8Sjob 	if (outformats & FORMAT_JSON) {
4727aabcda0Sclaudio 		json_do_string("type", "roa");
4737aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
474530399e8Sjob 		x509_print(x);
4757aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
4767aabcda0Sclaudio 		json_do_string("aia", p->aia);
4777aabcda0Sclaudio 		json_do_string("sia", p->sia);
4781bb1e509Sjob 		if (p->signtime != 0)
4797aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
4807aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
4817aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
4829c1f5d6bSjob 		if (p->expires)
4837aabcda0Sclaudio 			json_do_int("expires", p->expires);
484530399e8Sjob 	} else {
485714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
486530399e8Sjob 		x509_print(x);
487714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
488714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
4892cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
4901bb1e509Sjob 		if (p->signtime != 0)
4911bb1e509Sjob 			printf("Signing time:             %s\n",
4921bb1e509Sjob 			    time2str(p->signtime));
4934dbb22b8Sjob 		printf("ROA not before:           %s\n",
494f5999ddfSjob 		    time2str(p->notbefore));
4954dbb22b8Sjob 		printf("ROA not after:            %s\n", time2str(p->notafter));
496714f4e3fSclaudio 		printf("asID:                     %u\n", p->asid);
4974486d057Sjob 		printf("IP address blocks:        ");
498530399e8Sjob 	}
499530399e8Sjob 
5007aabcda0Sclaudio 	if (outformats & FORMAT_JSON)
5017aabcda0Sclaudio 		json_do_array("vrps");
502714f4e3fSclaudio 	for (i = 0; i < p->ipsz; i++) {
503714f4e3fSclaudio 		ip_addr_print(&p->ips[i].addr,
504714f4e3fSclaudio 		    p->ips[i].afi, buf, sizeof(buf));
505530399e8Sjob 
506530399e8Sjob 		if (outformats & FORMAT_JSON) {
507a09a3191Sclaudio 			json_do_object("vrp", 1);
5087aabcda0Sclaudio 			json_do_string("prefix", buf);
5097aabcda0Sclaudio 			json_do_uint("asid", p->asid);
5107aabcda0Sclaudio 			json_do_uint("maxlen", p->ips[i].maxlength);
5117aabcda0Sclaudio 			json_do_end();
5124486d057Sjob 		} else {
5134486d057Sjob 			if (i > 0)
5144486d057Sjob 				printf("%26s", "");
5154486d057Sjob 			printf("%s maxlen: %hhu\n", buf, p->ips[i].maxlength);
5164486d057Sjob 		}
517714f4e3fSclaudio 	}
518530399e8Sjob 	if (outformats & FORMAT_JSON)
5197aabcda0Sclaudio 		json_do_end();
520714f4e3fSclaudio }
521714f4e3fSclaudio 
522714f4e3fSclaudio void
523530399e8Sjob gbr_print(const X509 *x, const struct gbr *p)
524714f4e3fSclaudio {
525530399e8Sjob 	if (outformats & FORMAT_JSON) {
5267aabcda0Sclaudio 		json_do_string("type", "gbr");
5277aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
528530399e8Sjob 		x509_print(x);
5297aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
5307aabcda0Sclaudio 		json_do_string("aia", p->aia);
5317aabcda0Sclaudio 		json_do_string("sia", p->sia);
5321bb1e509Sjob 		if (p->signtime != 0)
5337aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
5347aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
5357aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
536894936b4Sjob 		if (p->expires)
5377aabcda0Sclaudio 			json_do_int("expires", p->expires);
5387aabcda0Sclaudio 		json_do_string("vcard", p->vcard);
539530399e8Sjob 	} else {
540714f4e3fSclaudio 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
541530399e8Sjob 		x509_print(x);
542714f4e3fSclaudio 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
543714f4e3fSclaudio 		printf("Authority info access:    %s\n", p->aia);
5442cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
5451bb1e509Sjob 		if (p->signtime != 0)
5461bb1e509Sjob 			printf("Signing time:             %s\n",
5471bb1e509Sjob 			    time2str(p->signtime));
5484dbb22b8Sjob 		printf("GBR not before:           %s\n",
549f5999ddfSjob 		    time2str(p->notbefore));
5504dbb22b8Sjob 		printf("GBR not after:            %s\n", time2str(p->notafter));
551714f4e3fSclaudio 		printf("vcard:\n%s", p->vcard);
552714f4e3fSclaudio 	}
553530399e8Sjob }
55404834fbdSjob 
55504834fbdSjob void
55604834fbdSjob rsc_print(const X509 *x, const struct rsc *p)
55704834fbdSjob {
55804834fbdSjob 	char	*hash;
5597aabcda0Sclaudio 	size_t	 i;
56004834fbdSjob 
56104834fbdSjob 	if (outformats & FORMAT_JSON) {
5627aabcda0Sclaudio 		json_do_string("type", "rsc");
5637aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
56404834fbdSjob 		x509_print(x);
5657aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
5667aabcda0Sclaudio 		json_do_string("aia", p->aia);
5671bb1e509Sjob 		if (p->signtime != 0)
5687aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
5697aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
5707aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
571894936b4Sjob 		if (p->expires)
5727aabcda0Sclaudio 			json_do_int("expires", p->expires);
5737aabcda0Sclaudio 		json_do_array("signed_with_resources");
57404834fbdSjob 	} else {
57504834fbdSjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
57604834fbdSjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
57704834fbdSjob 		x509_print(x);
57804834fbdSjob 		printf("Authority info access:    %s\n", p->aia);
5791bb1e509Sjob 		if (p->signtime != 0)
5801bb1e509Sjob 			printf("Signing time:             %s\n",
5811bb1e509Sjob 			    time2str(p->signtime));
5824dbb22b8Sjob 		printf("RSC not before:           %s\n",
583f5999ddfSjob 		    time2str(p->notbefore));
5844dbb22b8Sjob 		printf("RSC not after:            %s\n", time2str(p->notafter));
5854486d057Sjob 		printf("Signed with resources:    ");
58604834fbdSjob 	}
58704834fbdSjob 
5887aabcda0Sclaudio 	as_resources_print(p->as, p->asz);
5897aabcda0Sclaudio 	ip_resources_print(p->ips, p->ipsz, p->asz);
59004834fbdSjob 
59104834fbdSjob 	if (outformats & FORMAT_JSON) {
5927aabcda0Sclaudio 		json_do_end();
5937aabcda0Sclaudio 		json_do_array("filenamesandhashes");
59404834fbdSjob 	} else
5954486d057Sjob 		printf("Filenames and hashes:     ");
59604834fbdSjob 
59704834fbdSjob 	for (i = 0; i < p->filesz; i++) {
59804834fbdSjob 		if (base64_encode(p->files[i].hash, sizeof(p->files[i].hash),
59904834fbdSjob 		    &hash) == -1)
60004834fbdSjob 			errx(1, "base64_encode failure");
60104834fbdSjob 
60204834fbdSjob 		if (outformats & FORMAT_JSON) {
603a09a3191Sclaudio 			json_do_object("filehash", 1);
6047aabcda0Sclaudio 			if (p->files[i].filename)
6057aabcda0Sclaudio 				json_do_string("filename",
6067aabcda0Sclaudio 				    p->files[i].filename);
6077aabcda0Sclaudio 			json_do_string("hash_digest", hash);
6087aabcda0Sclaudio 			json_do_end();
60904834fbdSjob 		} else {
6104486d057Sjob 			if (i > 0)
6114486d057Sjob 				printf("%26s", "");
6124486d057Sjob 			printf("%zu: %s (hash: %s)\n", i + 1,
6134486d057Sjob 			    p->files[i].filename ? p->files[i].filename
6144486d057Sjob 			    : "no filename", hash);
61504834fbdSjob 		}
61604834fbdSjob 
61704834fbdSjob 		free(hash);
61804834fbdSjob 	}
61904834fbdSjob 
62004834fbdSjob 	if (outformats & FORMAT_JSON)
6217aabcda0Sclaudio 		json_do_end();
62204834fbdSjob }
623a29ddfd5Sjob 
624a29ddfd5Sjob void
625a29ddfd5Sjob aspa_print(const X509 *x, const struct aspa *p)
626a29ddfd5Sjob {
6274b5fc138Sjob 	size_t	i;
6284b5fc138Sjob 
629a29ddfd5Sjob 	if (outformats & FORMAT_JSON) {
6307aabcda0Sclaudio 		json_do_string("type", "aspa");
6317aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
632a29ddfd5Sjob 		x509_print(x);
6337aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
6347aabcda0Sclaudio 		json_do_string("aia", p->aia);
6357aabcda0Sclaudio 		json_do_string("sia", p->sia);
6361bb1e509Sjob 		if (p->signtime != 0)
6377aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
6387aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
6397aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
6409c1f5d6bSjob 		if (p->expires)
6417aabcda0Sclaudio 			json_do_int("expires", p->expires);
6427aabcda0Sclaudio 		json_do_uint("customer_asid", p->custasid);
6434a0be80eSjob 		json_do_array("providers");
644a29ddfd5Sjob 	} else {
645a29ddfd5Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
646a29ddfd5Sjob 		x509_print(x);
647a29ddfd5Sjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
648a29ddfd5Sjob 		printf("Authority info access:    %s\n", p->aia);
6492cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
6501bb1e509Sjob 		if (p->signtime != 0)
6511bb1e509Sjob 			printf("Signing time:             %s\n",
6521bb1e509Sjob 			    time2str(p->signtime));
6534dbb22b8Sjob 		printf("ASPA not before:          %s\n",
654f5999ddfSjob 		    time2str(p->notbefore));
6554dbb22b8Sjob 		printf("ASPA not after:           %s\n", time2str(p->notafter));
656ba54bc08Sjob 		printf("Customer ASID:            %u\n", p->custasid);
6574a0be80eSjob 		printf("Providers:                ");
658a29ddfd5Sjob 	}
6597aabcda0Sclaudio 
6604b5fc138Sjob 	for (i = 0; i < p->providersz; i++) {
661b179db0eSjob 		if (outformats & FORMAT_JSON)
662b179db0eSjob 			json_do_uint("asid", p->providers[i]);
663b179db0eSjob 		else {
664b179db0eSjob 			if (i > 0)
6654b5fc138Sjob 				printf("%26s", "");
666b179db0eSjob 			printf("AS: %u\n", p->providers[i]);
667b179db0eSjob 		}
6684b5fc138Sjob 	}
6697aabcda0Sclaudio 
6707aabcda0Sclaudio 	if (outformats & FORMAT_JSON)
6717aabcda0Sclaudio 		json_do_end();
672a29ddfd5Sjob }
673ee2a33daSjob 
674ee2a33daSjob static void
675ee2a33daSjob takey_print(char *name, const struct takey *t)
676ee2a33daSjob {
677ee2a33daSjob 	char	*spki = NULL;
678ee2a33daSjob 	size_t	 i, j = 0;
679ee2a33daSjob 
680ee2a33daSjob 	if (base64_encode(t->pubkey, t->pubkeysz, &spki) != 0)
681ee2a33daSjob 		errx(1, "base64_encode failed in %s", __func__);
682ee2a33daSjob 
683ee2a33daSjob 	if (outformats & FORMAT_JSON) {
684a09a3191Sclaudio 		json_do_object("takey", 0);
6857aabcda0Sclaudio 		json_do_string("name", name);
6867aabcda0Sclaudio 		json_do_array("comments");
6877aabcda0Sclaudio 		for (i = 0; i < t->commentsz; i++)
6887aabcda0Sclaudio 			json_do_string("comment", t->comments[i]);
6897aabcda0Sclaudio 		json_do_end();
6907aabcda0Sclaudio 		json_do_array("uris");
6917aabcda0Sclaudio 		for (i = 0; i < t->urisz; i++)
6927aabcda0Sclaudio 			json_do_string("uri", t->uris[i]);
6937aabcda0Sclaudio 		json_do_end();
6947aabcda0Sclaudio 		json_do_string("spki", spki);
695ee2a33daSjob 	} else {
696ee2a33daSjob 		printf("TAL derived from the '%s' Trust Anchor Key:\n\n", name);
697ee2a33daSjob 
6987aabcda0Sclaudio 		for (i = 0; i < t->commentsz; i++)
699ee2a33daSjob 			printf("\t# %s\n", t->comments[i]);
7007aabcda0Sclaudio 		printf("\n");
7017aabcda0Sclaudio 		for (i = 0; i < t->urisz; i++)
7027aabcda0Sclaudio 			printf("\t%s\n\t", t->uris[i]);
703ee2a33daSjob 		for (i = 0; i < strlen(spki); i++) {
704ee2a33daSjob 			printf("%c", spki[i]);
7057aabcda0Sclaudio 			if ((++j % 64) == 0)
706ee2a33daSjob 				printf("\n\t");
707ee2a33daSjob 		}
708ee2a33daSjob 		printf("\n\n");
709ee2a33daSjob 	}
710ee2a33daSjob 
711ee2a33daSjob 	free(spki);
712ee2a33daSjob }
713ee2a33daSjob 
714ee2a33daSjob void
715ee2a33daSjob tak_print(const X509 *x, const struct tak *p)
716ee2a33daSjob {
717ee2a33daSjob 	if (outformats & FORMAT_JSON) {
7187aabcda0Sclaudio 		json_do_string("type", "tak");
7197aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
720ee2a33daSjob 		x509_print(x);
7217aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
7227aabcda0Sclaudio 		json_do_string("aia", p->aia);
7237aabcda0Sclaudio 		json_do_string("sia", p->sia);
7241bb1e509Sjob 		if (p->signtime != 0)
7257aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
7267aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
7277aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
728894936b4Sjob 		if (p->expires)
7297aabcda0Sclaudio 			json_do_int("expires", p->expires);
7307aabcda0Sclaudio 		json_do_array("takeys");
731ee2a33daSjob 	} else {
732ee2a33daSjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
733ee2a33daSjob 		x509_print(x);
734ee2a33daSjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
735ee2a33daSjob 		printf("Authority info access:    %s\n", p->aia);
7362cf0e122Sjob 		printf("Subject info access:      %s\n", p->sia);
7371bb1e509Sjob 		if (p->signtime != 0)
7381bb1e509Sjob 			printf("Signing time:             %s\n",
7391bb1e509Sjob 			    time2str(p->signtime));
7404dbb22b8Sjob 		printf("TAK not before:           %s\n",
741f5999ddfSjob 		    time2str(p->notbefore));
7424dbb22b8Sjob 		printf("TAK not after:            %s\n", time2str(p->notafter));
743ee2a33daSjob 	}
744ee2a33daSjob 
745ee2a33daSjob 	takey_print("current", p->current);
7467aabcda0Sclaudio 	if (p->predecessor != NULL)
747ee2a33daSjob 		takey_print("predecessor", p->predecessor);
7487aabcda0Sclaudio 	if (p->successor != NULL)
749ee2a33daSjob 		takey_print("successor", p->successor);
750ee2a33daSjob 
751ee2a33daSjob 	if (outformats & FORMAT_JSON)
7527aabcda0Sclaudio 		json_do_end();
753ee2a33daSjob }
754ef3f6f56Sjob 
755ef3f6f56Sjob void
756ef3f6f56Sjob geofeed_print(const X509 *x, const struct geofeed *p)
757ef3f6f56Sjob {
758ef3f6f56Sjob 	char	 buf[128];
759ef3f6f56Sjob 	size_t	 i;
760ef3f6f56Sjob 
761ef3f6f56Sjob 	if (outformats & FORMAT_JSON) {
7627aabcda0Sclaudio 		json_do_string("type", "geofeed");
7637aabcda0Sclaudio 		json_do_string("ski", pretty_key_id(p->ski));
764ef3f6f56Sjob 		x509_print(x);
7657aabcda0Sclaudio 		json_do_string("aki", pretty_key_id(p->aki));
7667aabcda0Sclaudio 		json_do_string("aia", p->aia);
7671bb1e509Sjob 		if (p->signtime != 0)
7687aabcda0Sclaudio 			json_do_int("signing_time", p->signtime);
7697aabcda0Sclaudio 		json_do_int("valid_since", p->notbefore);
7707aabcda0Sclaudio 		json_do_int("valid_until", p->notafter);
7717aabcda0Sclaudio 		if (p->expires)
7727aabcda0Sclaudio 			json_do_int("expires", p->expires);
7737aabcda0Sclaudio 		json_do_array("records");
774ef3f6f56Sjob 	} else {
775ef3f6f56Sjob 		printf("Subject key identifier:   %s\n", pretty_key_id(p->ski));
776ef3f6f56Sjob 		x509_print(x);
777ef3f6f56Sjob 		printf("Authority key identifier: %s\n", pretty_key_id(p->aki));
778ef3f6f56Sjob 		printf("Authority info access:    %s\n", p->aia);
7791bb1e509Sjob 		if (p->signtime != 0)
7801bb1e509Sjob 			printf("Signing time:             %s\n",
7811bb1e509Sjob 			    time2str(p->signtime));
7824dbb22b8Sjob 		printf("Geofeed not before:       %s\n",
783f5999ddfSjob 		    time2str(p->notbefore));
7844dbb22b8Sjob 		printf("Geofeed not after:        %s\n", time2str(p->notafter));
785bd19f13dSjob 		printf("Geofeed CSV records:      ");
786ef3f6f56Sjob 	}
787ef3f6f56Sjob 
788ef3f6f56Sjob 	for (i = 0; i < p->geoipsz; i++) {
789ef3f6f56Sjob 		if (p->geoips[i].ip->type != CERT_IP_ADDR)
790ef3f6f56Sjob 			continue;
791ef3f6f56Sjob 
792ef3f6f56Sjob 		ip_addr_print(&p->geoips[i].ip->ip, p->geoips[i].ip->afi, buf,
793ef3f6f56Sjob 		    sizeof(buf));
7947aabcda0Sclaudio 		if (outformats & FORMAT_JSON) {
795a09a3191Sclaudio 			json_do_object("geoip", 1);
7967aabcda0Sclaudio 			json_do_string("prefix", buf);
7977aabcda0Sclaudio 			json_do_string("location", p->geoips[i].loc);
7987aabcda0Sclaudio 			json_do_end();
7997aabcda0Sclaudio 		} else {
8004486d057Sjob 			if (i > 0)
8014486d057Sjob 				printf("%26s", "");
8027aabcda0Sclaudio 			printf("IP: %s (%s)\n", buf, p->geoips[i].loc);
8034486d057Sjob 		}
804ef3f6f56Sjob 	}
805ef3f6f56Sjob 
806ef3f6f56Sjob 	if (outformats & FORMAT_JSON)
8077aabcda0Sclaudio 		json_do_end();
808ef3f6f56Sjob }
809