1*5af82120Sclaudio /*	$OpenBSD: test-rrdp.c,v 1.10 2024/04/22 05:54:01 claudio Exp $ */
24d27a18aSclaudio /*
34d27a18aSclaudio  * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com>
44d27a18aSclaudio  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
54d27a18aSclaudio  *
64d27a18aSclaudio  * Permission to use, copy, modify, and distribute this software for any
74d27a18aSclaudio  * purpose with or without fee is hereby granted, provided that the above
84d27a18aSclaudio  * copyright notice and this permission notice appear in all copies.
94d27a18aSclaudio  *
104d27a18aSclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
114d27a18aSclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
124d27a18aSclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
134d27a18aSclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144d27a18aSclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154d27a18aSclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164d27a18aSclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
174d27a18aSclaudio  */
184d27a18aSclaudio #include <sys/queue.h>
194d27a18aSclaudio #include <sys/stat.h>
204d27a18aSclaudio 
214d27a18aSclaudio #include <assert.h>
224d27a18aSclaudio #include <ctype.h>
234d27a18aSclaudio #include <err.h>
244d27a18aSclaudio #include <errno.h>
254d27a18aSclaudio #include <fcntl.h>
264d27a18aSclaudio #include <limits.h>
274d27a18aSclaudio #include <poll.h>
284d27a18aSclaudio #include <string.h>
294d27a18aSclaudio #include <unistd.h>
304d27a18aSclaudio #include <sha2.h>
314d27a18aSclaudio 
324d27a18aSclaudio #include <expat.h>
334d27a18aSclaudio #include <openssl/sha.h>
344d27a18aSclaudio 
354d27a18aSclaudio #include "extern.h"
364d27a18aSclaudio #include "rrdp.h"
374d27a18aSclaudio 
38edf59932Sjob int filemode;
3943e17b8aStb int outformats;
4043e17b8aStb int verbose;
41*5af82120Sclaudio int experimental;
42a27b1349Stb 
434d27a18aSclaudio #define REGRESS_NOTIFY_URI	"https://rpki.example.com/notify.xml"
444d27a18aSclaudio 
454d27a18aSclaudio #define MAX_SESSIONS	12
464d27a18aSclaudio #define	READ_BUF_SIZE	(32 * 1024)
474d27a18aSclaudio 
484d27a18aSclaudio #define RRDP_STATE_REQ		0x01
494d27a18aSclaudio #define RRDP_STATE_WAIT		0x02
504d27a18aSclaudio #define RRDP_STATE_PARSE	0x04
514d27a18aSclaudio #define RRDP_STATE_PARSE_ERROR	0x08
524d27a18aSclaudio #define RRDP_STATE_PARSE_DONE	0x10
534d27a18aSclaudio #define RRDP_STATE_HTTP_DONE	0x20
544d27a18aSclaudio #define RRDP_STATE_DONE		(RRDP_STATE_PARSE_DONE | RRDP_STATE_HTTP_DONE)
554d27a18aSclaudio 
564d27a18aSclaudio struct rrdp {
574d27a18aSclaudio 	TAILQ_ENTRY(rrdp)	 entry;
58f4525c43Sclaudio 	unsigned int		 id;
594d27a18aSclaudio 	char			*notifyuri;
604d27a18aSclaudio 	char			*local;
614d27a18aSclaudio 	char			*last_mod;
624d27a18aSclaudio 
634d27a18aSclaudio 	struct pollfd		*pfd;
644d27a18aSclaudio 	int			 infd;
654d27a18aSclaudio 	int			 state;
664d27a18aSclaudio 	unsigned int		 file_pending;
674d27a18aSclaudio 	unsigned int		 file_failed;
684d27a18aSclaudio 	enum http_result	 res;
694d27a18aSclaudio 	enum rrdp_task		 task;
704d27a18aSclaudio 
714d27a18aSclaudio 	char			 hash[SHA256_DIGEST_LENGTH];
724d27a18aSclaudio 	SHA256_CTX		 ctx;
734d27a18aSclaudio 
744d27a18aSclaudio 	struct rrdp_session	 repository;
754d27a18aSclaudio 	struct rrdp_session	 current;
764d27a18aSclaudio 	XML_Parser		 parser;
774d27a18aSclaudio 	struct notification_xml	*nxml;
784d27a18aSclaudio 	struct snapshot_xml	*sxml;
794d27a18aSclaudio 	struct delta_xml	*dxml;
804d27a18aSclaudio };
814d27a18aSclaudio 
824d27a18aSclaudio void
logx(const char * fmt,...)834d27a18aSclaudio logx(const char *fmt, ...)
844d27a18aSclaudio {
854d27a18aSclaudio 	va_list ap;
864d27a18aSclaudio 
874d27a18aSclaudio 	va_start(ap, fmt);
884d27a18aSclaudio 	vwarnx(fmt, ap);
894d27a18aSclaudio 	va_end(ap);
904d27a18aSclaudio }
914d27a18aSclaudio 
924d27a18aSclaudio char *
xstrdup(const char * s)934d27a18aSclaudio xstrdup(const char *s)
944d27a18aSclaudio {
954d27a18aSclaudio 	char *r;
964d27a18aSclaudio 	if ((r = strdup(s)) == NULL)
974d27a18aSclaudio 		err(1, "strdup");
984d27a18aSclaudio 	return r;
994d27a18aSclaudio }
1004d27a18aSclaudio 
1014d27a18aSclaudio /*
1024d27a18aSclaudio  * Send a blob of data to the main process to store it in the repository.
1034d27a18aSclaudio  */
1044d27a18aSclaudio void
rrdp_publish_file(struct rrdp * s,struct publish_xml * pxml,unsigned char * data,size_t datasz)1054d27a18aSclaudio rrdp_publish_file(struct rrdp *s, struct publish_xml *pxml,
1064d27a18aSclaudio     unsigned char *data, size_t datasz)
1074d27a18aSclaudio {
1084d27a18aSclaudio 	char buf[SHA256_DIGEST_STRING_LENGTH];
1094d27a18aSclaudio 	char *hash = NULL;
1104d27a18aSclaudio 
1114d27a18aSclaudio 	switch (pxml->type) {
1124d27a18aSclaudio 	case PUB_ADD:
1134d27a18aSclaudio 		logx("type: %s", "add");
1144d27a18aSclaudio 		break;
1154d27a18aSclaudio 	case PUB_UPD:
1164d27a18aSclaudio 		logx("type: %s", "update");
1174d27a18aSclaudio 		hash = hex_encode(pxml->hash, sizeof(pxml->hash));
1184d27a18aSclaudio 		break;
1194d27a18aSclaudio 	case PUB_DEL:
1204d27a18aSclaudio 		logx("type: %s", "delete");
1214d27a18aSclaudio 		hash = hex_encode(pxml->hash, sizeof(pxml->hash));
1224d27a18aSclaudio 		break;
1234d27a18aSclaudio 	default:
1244d27a18aSclaudio 		errx(1, "unknown publish type");
1254d27a18aSclaudio 	}
1264d27a18aSclaudio 	logx("uri: %s", pxml->uri);
1274d27a18aSclaudio 	SHA256Data(data, datasz, buf);
1284d27a18aSclaudio 	logx("data: %s", buf);
1294d27a18aSclaudio 
1304d27a18aSclaudio 	if (hash)
1314d27a18aSclaudio 		logx("hash: %s", hash);
1324d27a18aSclaudio 	free(hash);
1334d27a18aSclaudio }
1344d27a18aSclaudio 
1354d27a18aSclaudio static struct rrdp *
rrdp_new(unsigned int id,char * local,char * notify,char * session_id,long long serial,char * last_mod)136f4525c43Sclaudio rrdp_new(unsigned int id, char *local, char *notify, char *session_id,
1374d27a18aSclaudio     long long serial, char *last_mod)
1384d27a18aSclaudio {
1394d27a18aSclaudio 	struct rrdp *s;
1404d27a18aSclaudio 
1414d27a18aSclaudio 	if ((s = calloc(1, sizeof(*s))) == NULL)
1424d27a18aSclaudio 		err(1, NULL);
1434d27a18aSclaudio 
1444d27a18aSclaudio 	s->infd = 0; /* stdin */
1454d27a18aSclaudio 	s->id = id;
1464d27a18aSclaudio 	s->local = local;
1474d27a18aSclaudio 	s->notifyuri = notify;
1484d27a18aSclaudio 	s->repository.session_id = session_id;
1494d27a18aSclaudio 	s->repository.serial = serial;
1504d27a18aSclaudio 	s->repository.last_mod = last_mod;
1514d27a18aSclaudio 
1524d27a18aSclaudio 	s->state = RRDP_STATE_REQ;
1534d27a18aSclaudio 	if ((s->parser = XML_ParserCreate("US-ASCII")) == NULL)
1544d27a18aSclaudio 		err(1, "XML_ParserCreate");
1554d27a18aSclaudio 
1564d27a18aSclaudio 	return s;
1574d27a18aSclaudio }
1584d27a18aSclaudio 
1594d27a18aSclaudio static void
rrdp_free(struct rrdp * s)1604d27a18aSclaudio rrdp_free(struct rrdp *s)
1614d27a18aSclaudio {
1624d27a18aSclaudio 	if (s == NULL)
1634d27a18aSclaudio 		return;
1644d27a18aSclaudio 
1654d27a18aSclaudio 	free_notification_xml(s->nxml);
1664d27a18aSclaudio 	free_snapshot_xml(s->sxml);
1674d27a18aSclaudio 	free_delta_xml(s->dxml);
1684d27a18aSclaudio 
1694d27a18aSclaudio 	if (s->parser)
1704d27a18aSclaudio 		XML_ParserFree(s->parser);
1714d27a18aSclaudio 	if (s->infd != -1)
1724d27a18aSclaudio 		close(s->infd);
1734d27a18aSclaudio 	free(s->notifyuri);
1744d27a18aSclaudio 	free(s->local);
1754d27a18aSclaudio 	free(s->last_mod);
1764d27a18aSclaudio 	free(s->repository.last_mod);
1774d27a18aSclaudio 	free(s->repository.session_id);
1784d27a18aSclaudio 	free(s->current.last_mod);
1794d27a18aSclaudio 	free(s->current.session_id);
1804d27a18aSclaudio 
1814d27a18aSclaudio 	free(s);
1824d27a18aSclaudio }
1834d27a18aSclaudio 
1844d27a18aSclaudio static void
rrdp_finished(struct rrdp * s)1854d27a18aSclaudio rrdp_finished(struct rrdp *s)
1864d27a18aSclaudio {
1874d27a18aSclaudio 	XML_Parser p = s->parser;
188f4525c43Sclaudio 	unsigned int id = s->id;
1894d27a18aSclaudio 
1904d27a18aSclaudio 	if (s->state & RRDP_STATE_PARSE_ERROR)
1914d27a18aSclaudio 		return;
1924d27a18aSclaudio 
1934d27a18aSclaudio 	/*
1944d27a18aSclaudio 	 * Finalize parsing on success to be sure that
1954d27a18aSclaudio 	 * all of the XML is correct. Needs to be done here
1964d27a18aSclaudio 	 * since the call would most probably fail for non
1974d27a18aSclaudio 	 * successful data fetches.
1984d27a18aSclaudio 	 */
1994d27a18aSclaudio 	if (XML_Parse(p, NULL, 0, 1) != XML_STATUS_OK) {
2004d27a18aSclaudio 		warnx("%s: XML error at line %llu: %s", s->local,
2014d27a18aSclaudio 		    (unsigned long long)XML_GetCurrentLineNumber(p),
2024d27a18aSclaudio 		    XML_ErrorString(XML_GetErrorCode(p)));
2034d27a18aSclaudio 		return;
2044d27a18aSclaudio 	}
2054d27a18aSclaudio 
2064d27a18aSclaudio 	switch (s->task) {
2074d27a18aSclaudio 	case NOTIFICATION:
208628a100dSclaudio 		notification_done(s->nxml, NULL);
2094d27a18aSclaudio 		log_notification_xml(s->nxml);
2104d27a18aSclaudio 		break;
2114d27a18aSclaudio 	case SNAPSHOT:
2124d27a18aSclaudio 		log_snapshot_xml(s->sxml);
2134d27a18aSclaudio 		break;
2144d27a18aSclaudio 	case DELTA:
2154d27a18aSclaudio 		log_delta_xml(s->dxml);
2164d27a18aSclaudio 		break;
2174d27a18aSclaudio 	}
2184d27a18aSclaudio }
2194d27a18aSclaudio 
2204d27a18aSclaudio static void
rrdp_data_handler(struct rrdp * s)2214d27a18aSclaudio rrdp_data_handler(struct rrdp *s)
2224d27a18aSclaudio {
2234d27a18aSclaudio 	char buf[READ_BUF_SIZE];
2244d27a18aSclaudio 	XML_Parser p = s->parser;
2254d27a18aSclaudio 	ssize_t len;
2264d27a18aSclaudio 
2274d27a18aSclaudio 	len = read(s->infd, buf, sizeof(buf));
2284d27a18aSclaudio 	if (len == -1) {
2294d27a18aSclaudio 		s->state |= RRDP_STATE_PARSE_ERROR;
2304d27a18aSclaudio 		warn("%s: read failure", s->local);
2314d27a18aSclaudio 		return;
2324d27a18aSclaudio 	}
2334d27a18aSclaudio 	if ((s->state & RRDP_STATE_PARSE) == 0)
2344d27a18aSclaudio 		errx(1, "%s: bad parser state", s->local);
2354d27a18aSclaudio 	if (len == 0) {
2364d27a18aSclaudio 		/* parser stage finished */
2374d27a18aSclaudio 		close(s->infd);
2384d27a18aSclaudio 		s->infd = -1;
2394d27a18aSclaudio 
2404d27a18aSclaudio 		if (s->task != NOTIFICATION) {
2414d27a18aSclaudio 			char h[SHA256_DIGEST_LENGTH];
2424d27a18aSclaudio 
2434d27a18aSclaudio 			SHA256_Final(h, &s->ctx);
2444d27a18aSclaudio 			if (memcmp(s->hash, h, sizeof(s->hash)) != 0) {
2454d27a18aSclaudio 				s->state |= RRDP_STATE_PARSE_ERROR;
2464d27a18aSclaudio 				warnx("%s: bad message digest", s->local);
2474d27a18aSclaudio 			}
2484d27a18aSclaudio 		}
2494d27a18aSclaudio 
2504d27a18aSclaudio 		s->state |= RRDP_STATE_PARSE_DONE;
2514d27a18aSclaudio 		rrdp_finished(s);
2524d27a18aSclaudio 		return;
2534d27a18aSclaudio 	}
2544d27a18aSclaudio 
2554d27a18aSclaudio 	/* parse and maybe hash the bytes just read */
2564d27a18aSclaudio 	if (s->task != NOTIFICATION)
2574d27a18aSclaudio 		SHA256_Update(&s->ctx, buf, len);
2584d27a18aSclaudio 	if ((s->state & RRDP_STATE_PARSE_ERROR) == 0 &&
2594d27a18aSclaudio 	    XML_Parse(p, buf, len, 0) != XML_STATUS_OK) {
2604d27a18aSclaudio 		warnx("%s: parse error at line %llu: %s", s->local,
2614d27a18aSclaudio 		    (unsigned long long)XML_GetCurrentLineNumber(p),
2624d27a18aSclaudio 		    XML_ErrorString(XML_GetErrorCode(p)));
2634d27a18aSclaudio 		s->state |= RRDP_STATE_PARSE_ERROR;
2644d27a18aSclaudio 	}
2654d27a18aSclaudio }
2664d27a18aSclaudio 
2674d27a18aSclaudio int
main(int argc,char ** argv)2684d27a18aSclaudio main(int argc, char **argv)
2694d27a18aSclaudio {
2704d27a18aSclaudio 	struct rrdp *s = NULL;
2714d27a18aSclaudio 	const char *e;
2724d27a18aSclaudio 	char *session_id = NULL;
2734d27a18aSclaudio 	char hash[SHA256_DIGEST_LENGTH];
2744d27a18aSclaudio 	long long serial = 0;
2754d27a18aSclaudio 	int c;
2764d27a18aSclaudio 
2774d27a18aSclaudio 
2784d27a18aSclaudio 	while ((c = getopt(argc, argv, "dH:N:nS:s")) != -1)
2794d27a18aSclaudio 		switch (c) {
2804d27a18aSclaudio 		case 'd':
2814d27a18aSclaudio 			if (s)
2824d27a18aSclaudio 				goto usage;
2834d27a18aSclaudio 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
2844d27a18aSclaudio 			    session_id, serial, NULL);
2854d27a18aSclaudio 			s->dxml = new_delta_xml(s->parser,
2864d27a18aSclaudio 			    &s->repository, s);
2874d27a18aSclaudio 			s->task = DELTA;
2884d27a18aSclaudio 			SHA256_Init(&s->ctx);
2894d27a18aSclaudio 			memcpy(s->hash, hash, sizeof(s->hash));
2904d27a18aSclaudio 			break;
2914d27a18aSclaudio 		case 'H':
2924d27a18aSclaudio 			if (hex_decode(optarg, hash, sizeof(hash)) == -1)
2934d27a18aSclaudio 				errx(1, "bad hash");
2944d27a18aSclaudio 			break;
2954d27a18aSclaudio 		case 'N':
2964d27a18aSclaudio 			serial = strtonum(optarg, LLONG_MIN, LLONG_MAX, &e);
2974d27a18aSclaudio 			if (e != NULL)
2984d27a18aSclaudio 				errx(1, "serial is %s: %s", e, optarg);
2994d27a18aSclaudio 			break;
3004d27a18aSclaudio 		case 'n':
3014d27a18aSclaudio 			if (s)
3024d27a18aSclaudio 				goto usage;
3034d27a18aSclaudio 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
3044d27a18aSclaudio 			    session_id, serial, NULL);
3054d27a18aSclaudio 			s->nxml = new_notification_xml(s->parser,
3064d27a18aSclaudio 			    &s->repository, &s->current, s->notifyuri);
3074d27a18aSclaudio 			s->task = NOTIFICATION;
3084d27a18aSclaudio 			break;
3094d27a18aSclaudio 		case 'S':
3104d27a18aSclaudio 			session_id = optarg;
3114d27a18aSclaudio 			break;
3124d27a18aSclaudio 		case 's':
3134d27a18aSclaudio 			if (s)
3144d27a18aSclaudio 				goto usage;
3154d27a18aSclaudio 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
3164d27a18aSclaudio 			    session_id, serial, NULL);
3174d27a18aSclaudio 			s->sxml = new_snapshot_xml(s->parser,
3184d27a18aSclaudio 			    &s->repository, s);
3194d27a18aSclaudio 			s->task = SNAPSHOT;
3204d27a18aSclaudio 			SHA256_Init(&s->ctx);
3214d27a18aSclaudio 			memcpy(s->hash, hash, sizeof(s->hash));
3224d27a18aSclaudio 			break;
3234d27a18aSclaudio 		default:
3244d27a18aSclaudio 			goto usage;
3254d27a18aSclaudio 		}
3264d27a18aSclaudio 
3274d27a18aSclaudio 	s->state = RRDP_STATE_PARSE;
3284d27a18aSclaudio 
3294d27a18aSclaudio 	while (!(s->state & RRDP_STATE_PARSE_DONE)) {
3304d27a18aSclaudio 		rrdp_data_handler(s);
3314d27a18aSclaudio 	}
3324d27a18aSclaudio 
3334d27a18aSclaudio 	if ((s->state & RRDP_STATE_PARSE_ERROR) == 0) {
3344d27a18aSclaudio 		printf("OK\n");
3354d27a18aSclaudio 		return 0;
3364d27a18aSclaudio 	} else {
3374d27a18aSclaudio 		return 1;
3384d27a18aSclaudio 	}
3394d27a18aSclaudio 
3404d27a18aSclaudio usage:
3414d27a18aSclaudio 	fprintf(stderr, "usage: %s [-S session_id] [-N serial] [-H hash] "
3424d27a18aSclaudio 	    "-d | -n | -s\n", "test-rrdp");
3434d27a18aSclaudio 	exit(1);
3444d27a18aSclaudio }
3450876134dSclaudio 
3460876134dSclaudio time_t
get_current_time(void)3470876134dSclaudio get_current_time(void)
3480876134dSclaudio {
3490876134dSclaudio 	return time(NULL);
3500876134dSclaudio }
351