xref: /openbsd-src/usr.sbin/rpki-client/rrdp_notification.c (revision 222e275fb89ffb67abe0726dee2b107220092dc3)
1*222e275fSjob /*	$OpenBSD: rrdp_notification.c,v 1.21 2024/04/12 11:50:29 job Exp $ */
28ecbadc1Sclaudio /*
38ecbadc1Sclaudio  * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com>
48ecbadc1Sclaudio  * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
58ecbadc1Sclaudio  *
68ecbadc1Sclaudio  * Permission to use, copy, modify, and distribute this software for any
78ecbadc1Sclaudio  * purpose with or without fee is hereby granted, provided that the above
88ecbadc1Sclaudio  * copyright notice and this permission notice appear in all copies.
98ecbadc1Sclaudio  *
108ecbadc1Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
118ecbadc1Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
128ecbadc1Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
138ecbadc1Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
148ecbadc1Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
158ecbadc1Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
168ecbadc1Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
178ecbadc1Sclaudio  */
188ecbadc1Sclaudio 
198ecbadc1Sclaudio #include <sys/stat.h>
208ecbadc1Sclaudio 
218ecbadc1Sclaudio #include <assert.h>
228ecbadc1Sclaudio #include <err.h>
238ecbadc1Sclaudio #include <errno.h>
248ecbadc1Sclaudio #include <limits.h>
258ecbadc1Sclaudio #include <fcntl.h>
268ecbadc1Sclaudio #include <string.h>
278ecbadc1Sclaudio #include <unistd.h>
288ecbadc1Sclaudio 
298ecbadc1Sclaudio #include <expat.h>
308ecbadc1Sclaudio #include <openssl/sha.h>
318ecbadc1Sclaudio 
328ecbadc1Sclaudio #include "extern.h"
338ecbadc1Sclaudio #include "rrdp.h"
348ecbadc1Sclaudio 
358ecbadc1Sclaudio enum notification_scope {
368ecbadc1Sclaudio 	NOTIFICATION_SCOPE_START,
378ecbadc1Sclaudio 	NOTIFICATION_SCOPE_NOTIFICATION,
388ecbadc1Sclaudio 	NOTIFICATION_SCOPE_SNAPSHOT,
398ecbadc1Sclaudio 	NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT,
408ecbadc1Sclaudio 	NOTIFICATION_SCOPE_DELTA,
418ecbadc1Sclaudio 	NOTIFICATION_SCOPE_END
428ecbadc1Sclaudio };
438ecbadc1Sclaudio 
448ecbadc1Sclaudio struct delta_item {
458ecbadc1Sclaudio 	char			*uri;
468ecbadc1Sclaudio 	char			 hash[SHA256_DIGEST_LENGTH];
478ecbadc1Sclaudio 	long long		 serial;
488ecbadc1Sclaudio 	TAILQ_ENTRY(delta_item)	 q;
498ecbadc1Sclaudio };
508ecbadc1Sclaudio 
518ecbadc1Sclaudio TAILQ_HEAD(delta_q, delta_item);
528ecbadc1Sclaudio 
538ecbadc1Sclaudio struct notification_xml {
548ecbadc1Sclaudio 	XML_Parser		 parser;
558ecbadc1Sclaudio 	struct rrdp_session	*repository;
568ecbadc1Sclaudio 	struct rrdp_session	*current;
5793d9375cSclaudio 	const char		*notifyuri;
588ecbadc1Sclaudio 	char			*session_id;
598ecbadc1Sclaudio 	char			*snapshot_uri;
608ecbadc1Sclaudio 	char			 snapshot_hash[SHA256_DIGEST_LENGTH];
618ecbadc1Sclaudio 	struct delta_q		 delta_q;
628ecbadc1Sclaudio 	long long		 serial;
63b268327aSclaudio 	long long		 min_serial;
648ecbadc1Sclaudio 	int			 version;
658ecbadc1Sclaudio 	enum notification_scope	 scope;
668ecbadc1Sclaudio };
678ecbadc1Sclaudio 
68a6271a62Stb static void	free_delta(struct delta_item *);
69a6271a62Stb 
708ecbadc1Sclaudio static int
add_delta(struct notification_xml * nxml,const char * uri,const char hash[SHA256_DIGEST_LENGTH],long long serial)718ecbadc1Sclaudio add_delta(struct notification_xml *nxml, const char *uri,
728ecbadc1Sclaudio     const char hash[SHA256_DIGEST_LENGTH], long long serial)
738ecbadc1Sclaudio {
748ecbadc1Sclaudio 	struct delta_item *d, *n;
758ecbadc1Sclaudio 
768ecbadc1Sclaudio 	if ((d = calloc(1, sizeof(struct delta_item))) == NULL)
778ecbadc1Sclaudio 		err(1, "%s - calloc", __func__);
788ecbadc1Sclaudio 
798ecbadc1Sclaudio 	d->serial = serial;
808ecbadc1Sclaudio 	d->uri = xstrdup(uri);
818ecbadc1Sclaudio 	memcpy(d->hash, hash, sizeof(d->hash));
828ecbadc1Sclaudio 
838ecbadc1Sclaudio 	/* optimise for a sorted input */
848ecbadc1Sclaudio 	n = TAILQ_LAST(&nxml->delta_q, delta_q);
858ecbadc1Sclaudio 	if (n == NULL)
868ecbadc1Sclaudio 		TAILQ_INSERT_HEAD(&nxml->delta_q, d, q);
878ecbadc1Sclaudio 	else if (n->serial < serial)
888ecbadc1Sclaudio 		TAILQ_INSERT_TAIL(&nxml->delta_q, d, q);
898ecbadc1Sclaudio 	else
908ecbadc1Sclaudio 		TAILQ_FOREACH(n, &nxml->delta_q, q) {
918ecbadc1Sclaudio 			if (n->serial == serial) {
928ecbadc1Sclaudio 				warnx("duplicate delta serial %lld ", serial);
93a6271a62Stb 				free_delta(d);
948ecbadc1Sclaudio 				return 0;
958ecbadc1Sclaudio 			}
968ecbadc1Sclaudio 			if (n->serial > serial) {
978ecbadc1Sclaudio 				TAILQ_INSERT_BEFORE(n, d, q);
988ecbadc1Sclaudio 				break;
998ecbadc1Sclaudio 			}
1008ecbadc1Sclaudio 		}
1018ecbadc1Sclaudio 
1028ecbadc1Sclaudio 	return 1;
1038ecbadc1Sclaudio }
1048ecbadc1Sclaudio 
105b268327aSclaudio /* check that there are no holes in the list */
106b268327aSclaudio static int
check_delta(struct notification_xml * nxml)107b268327aSclaudio check_delta(struct notification_xml *nxml)
108b268327aSclaudio {
109b268327aSclaudio 	struct delta_item *d;
110b268327aSclaudio 	long long serial = 0;
111b268327aSclaudio 
112b268327aSclaudio 	TAILQ_FOREACH(d, &nxml->delta_q, q) {
113b268327aSclaudio 		if (serial != 0 && serial + 1 != d->serial)
114b268327aSclaudio 			return 0;
115b268327aSclaudio 		serial = d->serial;
116b268327aSclaudio 	}
117b268327aSclaudio 	return 1;
118b268327aSclaudio }
119b268327aSclaudio 
1208ecbadc1Sclaudio static void
free_delta(struct delta_item * d)1218ecbadc1Sclaudio free_delta(struct delta_item *d)
1228ecbadc1Sclaudio {
1238ecbadc1Sclaudio 	free(d->uri);
1248ecbadc1Sclaudio 	free(d);
1258ecbadc1Sclaudio }
1268ecbadc1Sclaudio 
127b268327aSclaudio /*
128b268327aSclaudio  * Parse a delta serial and hash line at idx from the rrdp session state.
129b268327aSclaudio  * Return the serial or 0 on error. If hash is non-NULL, it is set to the
130b268327aSclaudio  * start of the hash string on success.
131b268327aSclaudio  */
132b268327aSclaudio static long long
delta_parse(struct rrdp_session * s,size_t idx,char ** hash)133b268327aSclaudio delta_parse(struct rrdp_session *s, size_t idx, char **hash)
134b268327aSclaudio {
135b268327aSclaudio 	long long serial;
136b268327aSclaudio 	char *line, *ep;
137b268327aSclaudio 
138b268327aSclaudio 	if (hash != NULL)
139b268327aSclaudio 		*hash = NULL;
140b268327aSclaudio 	if (idx < 0 || idx >= sizeof(s->deltas) / sizeof(s->deltas[0]))
141b268327aSclaudio 		return 0;
142b268327aSclaudio 	if ((line = s->deltas[idx]) == NULL)
143b268327aSclaudio 		return 0;
144b268327aSclaudio 
145b268327aSclaudio 	errno = 0;
146b268327aSclaudio 	serial = strtoll(line, &ep, 10);
147b268327aSclaudio 	if (line[0] == '\0' || *ep != ' ')
148b268327aSclaudio 		return 0;
149b268327aSclaudio 	if (serial <= 0 || (errno == ERANGE && serial == LLONG_MAX))
150b268327aSclaudio 		return 0;
151b268327aSclaudio 
152b268327aSclaudio 	if (hash != NULL)
153b268327aSclaudio 		*hash = ep + 1;
154b268327aSclaudio 	return serial;
155b268327aSclaudio }
156b268327aSclaudio 
1578ecbadc1Sclaudio static void
start_notification_elem(struct notification_xml * nxml,const char ** attr)1588ecbadc1Sclaudio start_notification_elem(struct notification_xml *nxml, const char **attr)
1598ecbadc1Sclaudio {
1608ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
1618ecbadc1Sclaudio 	int has_xmlns = 0;
1628ecbadc1Sclaudio 	size_t i;
1638ecbadc1Sclaudio 
1648ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_START)
1658ecbadc1Sclaudio 		PARSE_FAIL(p,
1668ecbadc1Sclaudio 		    "parse failed - entered notification elem unexpectedely");
1678ecbadc1Sclaudio 	for (i = 0; attr[i]; i += 2) {
1688ecbadc1Sclaudio 		const char *errstr;
1698e901cb8Sclaudio 		if (strcmp("xmlns", attr[i]) == 0 &&
1708e901cb8Sclaudio 		    strcmp(RRDP_XMLNS, attr[i + 1]) == 0) {
1718ecbadc1Sclaudio 			has_xmlns = 1;
1728ecbadc1Sclaudio 			continue;
1738ecbadc1Sclaudio 		}
17445735addSclaudio 		if (strcmp("session_id", attr[i]) == 0 &&
17545735addSclaudio 		    valid_uuid(attr[i + 1])) {
1768ecbadc1Sclaudio 			nxml->session_id = xstrdup(attr[i + 1]);
1778ecbadc1Sclaudio 			continue;
1788ecbadc1Sclaudio 		}
1798ecbadc1Sclaudio 		if (strcmp("version", attr[i]) == 0) {
1808ecbadc1Sclaudio 			nxml->version = strtonum(attr[i + 1],
1818ecbadc1Sclaudio 			    1, MAX_VERSION, &errstr);
1828ecbadc1Sclaudio 			if (errstr == NULL)
1838ecbadc1Sclaudio 				continue;
1848ecbadc1Sclaudio 		}
1858ecbadc1Sclaudio 		if (strcmp("serial", attr[i]) == 0) {
1868ecbadc1Sclaudio 			nxml->serial = strtonum(attr[i + 1],
1878ecbadc1Sclaudio 			    1, LLONG_MAX, &errstr);
1888ecbadc1Sclaudio 			if (errstr == NULL)
1898ecbadc1Sclaudio 				continue;
1908ecbadc1Sclaudio 		}
1918ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - non conforming "
192da205960Sclaudio 		    "attribute '%s' found in notification elem", attr[i]);
1938ecbadc1Sclaudio 	}
1948ecbadc1Sclaudio 	if (!(has_xmlns && nxml->version && nxml->session_id && nxml->serial))
1958ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - incomplete "
1968ecbadc1Sclaudio 		    "notification attributes");
1978ecbadc1Sclaudio 
198b268327aSclaudio 	/* Limit deltas to the ones which matter for us. */
199b268327aSclaudio 	if (nxml->min_serial == 0 && nxml->serial > MAX_RRDP_DELTAS)
200b268327aSclaudio 		nxml->min_serial = nxml->serial - MAX_RRDP_DELTAS;
201b268327aSclaudio 
2028ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION;
2038ecbadc1Sclaudio }
2048ecbadc1Sclaudio 
2058ecbadc1Sclaudio static void
end_notification_elem(struct notification_xml * nxml)2068ecbadc1Sclaudio end_notification_elem(struct notification_xml *nxml)
2078ecbadc1Sclaudio {
2088ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
2098ecbadc1Sclaudio 
2108ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT)
2118ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - exited notification "
2128ecbadc1Sclaudio 		    "elem unexpectedely");
2138ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_END;
214b268327aSclaudio 
215b268327aSclaudio 	if (!check_delta(nxml))
216b268327aSclaudio 		PARSE_FAIL(p, "parse failed - delta list has holes");
2178ecbadc1Sclaudio }
2188ecbadc1Sclaudio 
2198ecbadc1Sclaudio static void
start_snapshot_elem(struct notification_xml * nxml,const char ** attr)2208ecbadc1Sclaudio start_snapshot_elem(struct notification_xml *nxml, const char **attr)
2218ecbadc1Sclaudio {
2228ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
2238ecbadc1Sclaudio 	int i, hasUri = 0, hasHash = 0;
2248ecbadc1Sclaudio 
2258ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION)
2268ecbadc1Sclaudio 		PARSE_FAIL(p,
2278ecbadc1Sclaudio 		    "parse failed - entered snapshot elem unexpectedely");
2288ecbadc1Sclaudio 	for (i = 0; attr[i]; i += 2) {
2298ecbadc1Sclaudio 		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
2308ecbadc1Sclaudio 			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
2310610060dSjob 			    HTTPS_PROTO) &&
23293d9375cSclaudio 			    valid_origin(attr[i + 1], nxml->notifyuri)) {
2338ecbadc1Sclaudio 				nxml->snapshot_uri = xstrdup(attr[i + 1]);
2348ecbadc1Sclaudio 				continue;
2358ecbadc1Sclaudio 			}
2368ecbadc1Sclaudio 		}
2378ecbadc1Sclaudio 		if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) {
2388ecbadc1Sclaudio 			if (hex_decode(attr[i + 1], nxml->snapshot_hash,
2398ecbadc1Sclaudio 			    sizeof(nxml->snapshot_hash)) == 0)
2408ecbadc1Sclaudio 				continue;
2418ecbadc1Sclaudio 		}
2428ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - non conforming "
243da205960Sclaudio 		    "attribute '%s' found in snapshot elem", attr[i]);
2448ecbadc1Sclaudio 	}
2458ecbadc1Sclaudio 	if (hasUri != 1 || hasHash != 1)
2468ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - incomplete snapshot attributes");
2478ecbadc1Sclaudio 
2488ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_SNAPSHOT;
2498ecbadc1Sclaudio }
2508ecbadc1Sclaudio 
2518ecbadc1Sclaudio static void
end_snapshot_elem(struct notification_xml * nxml)2528ecbadc1Sclaudio end_snapshot_elem(struct notification_xml *nxml)
2538ecbadc1Sclaudio {
2548ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
2558ecbadc1Sclaudio 
2568ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_SNAPSHOT)
2578ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - exited snapshot "
2588ecbadc1Sclaudio 		    "elem unexpectedely");
2598ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT;
2608ecbadc1Sclaudio }
2618ecbadc1Sclaudio 
2628ecbadc1Sclaudio static void
start_delta_elem(struct notification_xml * nxml,const char ** attr)2638ecbadc1Sclaudio start_delta_elem(struct notification_xml *nxml, const char **attr)
2648ecbadc1Sclaudio {
2658ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
2668ecbadc1Sclaudio 	int i, hasUri = 0, hasHash = 0;
2678ecbadc1Sclaudio 	const char *delta_uri = NULL;
2688ecbadc1Sclaudio 	char delta_hash[SHA256_DIGEST_LENGTH];
2698ecbadc1Sclaudio 	long long delta_serial = 0;
2708ecbadc1Sclaudio 
2718ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT)
2728ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - entered delta "
2738ecbadc1Sclaudio 		    "elem unexpectedely");
2748ecbadc1Sclaudio 	for (i = 0; attr[i]; i += 2) {
2758ecbadc1Sclaudio 		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
2768ecbadc1Sclaudio 			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
2770610060dSjob 			    HTTPS_PROTO) &&
27893d9375cSclaudio 			    valid_origin(attr[i + 1], nxml->notifyuri)) {
2798ecbadc1Sclaudio 				delta_uri = attr[i + 1];
2808ecbadc1Sclaudio 				continue;
2818ecbadc1Sclaudio 			}
2828ecbadc1Sclaudio 		}
2838ecbadc1Sclaudio 		if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) {
2848ecbadc1Sclaudio 			if (hex_decode(attr[i + 1], delta_hash,
2858ecbadc1Sclaudio 			    sizeof(delta_hash)) == 0)
2868ecbadc1Sclaudio 				continue;
2878ecbadc1Sclaudio 		}
2888ecbadc1Sclaudio 		if (strcmp("serial", attr[i]) == 0 && delta_serial == 0) {
2898ecbadc1Sclaudio 			const char *errstr;
2908ecbadc1Sclaudio 
2918ecbadc1Sclaudio 			delta_serial = strtonum(attr[i + 1],
2928ecbadc1Sclaudio 			    1, LLONG_MAX, &errstr);
2938ecbadc1Sclaudio 			if (errstr == NULL)
2948ecbadc1Sclaudio 				continue;
2958ecbadc1Sclaudio 		}
2968ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - non conforming "
297*222e275fSjob 		    "attribute '%s' found in delta elem", attr[i]);
2988ecbadc1Sclaudio 	}
2998ecbadc1Sclaudio 	/* Only add to the list if we are relevant */
3008ecbadc1Sclaudio 	if (hasUri != 1 || hasHash != 1 || delta_serial == 0)
3018ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - incomplete delta attributes");
3028ecbadc1Sclaudio 
303b268327aSclaudio 	/* Delta serial must be smaller or equal to the notification serial */
304b268327aSclaudio 	if (nxml->serial < delta_serial)
305b268327aSclaudio 		PARSE_FAIL(p, "parse failed - bad delta serial");
306b268327aSclaudio 
3078ecbadc1Sclaudio 	/* optimisation, add only deltas that could be interesting */
308b268327aSclaudio 	if (nxml->min_serial < delta_serial) {
3098ecbadc1Sclaudio 		if (add_delta(nxml, delta_uri, delta_hash, delta_serial) == 0)
3108ecbadc1Sclaudio 			PARSE_FAIL(p, "parse failed - adding delta failed");
3118ecbadc1Sclaudio 	}
3128ecbadc1Sclaudio 
3138ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_DELTA;
3148ecbadc1Sclaudio }
3158ecbadc1Sclaudio 
3168ecbadc1Sclaudio static void
end_delta_elem(struct notification_xml * nxml)3178ecbadc1Sclaudio end_delta_elem(struct notification_xml *nxml)
3188ecbadc1Sclaudio {
3198ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
3208ecbadc1Sclaudio 
3218ecbadc1Sclaudio 	if (nxml->scope != NOTIFICATION_SCOPE_DELTA)
3228ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - exited delta elem unexpectedely");
3238ecbadc1Sclaudio 	nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT;
3248ecbadc1Sclaudio }
3258ecbadc1Sclaudio 
3268ecbadc1Sclaudio static void
notification_xml_elem_start(void * data,const char * el,const char ** attr)3278ecbadc1Sclaudio notification_xml_elem_start(void *data, const char *el, const char **attr)
3288ecbadc1Sclaudio {
3298ecbadc1Sclaudio 	struct notification_xml *nxml = data;
3308ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
3318ecbadc1Sclaudio 
3328ecbadc1Sclaudio 	/*
3338ecbadc1Sclaudio 	 * Can only enter here once as we should have no ways to get back to
3348ecbadc1Sclaudio 	 * START scope
3358ecbadc1Sclaudio 	 */
3368ecbadc1Sclaudio 	if (strcmp("notification", el) == 0)
3378ecbadc1Sclaudio 		start_notification_elem(nxml, attr);
3388ecbadc1Sclaudio 	/*
3398ecbadc1Sclaudio 	 * Will enter here multiple times, BUT never nested. will start
3408ecbadc1Sclaudio 	 * collecting character data in that handler
3418ecbadc1Sclaudio 	 * mem is cleared in end block, (TODO or on parse failure)
3428ecbadc1Sclaudio 	 */
3438ecbadc1Sclaudio 	else if (strcmp("snapshot", el) == 0)
3448ecbadc1Sclaudio 		start_snapshot_elem(nxml, attr);
3458ecbadc1Sclaudio 	else if (strcmp("delta", el) == 0)
3468ecbadc1Sclaudio 		start_delta_elem(nxml, attr);
3478ecbadc1Sclaudio 	else
3488ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
3498ecbadc1Sclaudio }
3508ecbadc1Sclaudio 
3518ecbadc1Sclaudio static void
notification_xml_elem_end(void * data,const char * el)3528ecbadc1Sclaudio notification_xml_elem_end(void *data, const char *el)
3538ecbadc1Sclaudio {
3548ecbadc1Sclaudio 	struct notification_xml *nxml = data;
3558ecbadc1Sclaudio 	XML_Parser p = nxml->parser;
3568ecbadc1Sclaudio 
3578ecbadc1Sclaudio 	if (strcmp("notification", el) == 0)
3588ecbadc1Sclaudio 		end_notification_elem(nxml);
3598ecbadc1Sclaudio 	else if (strcmp("snapshot", el) == 0)
3608ecbadc1Sclaudio 		end_snapshot_elem(nxml);
3618ecbadc1Sclaudio 	else if (strcmp("delta", el) == 0)
3628ecbadc1Sclaudio 		end_delta_elem(nxml);
3638ecbadc1Sclaudio 	else
3648ecbadc1Sclaudio 		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
3658ecbadc1Sclaudio }
3668ecbadc1Sclaudio 
3678487774dSclaudio static void
notification_doctype_handler(void * data,const char * doctypeName,const char * sysid,const char * pubid,int subset)3688487774dSclaudio notification_doctype_handler(void *data, const char *doctypeName,
3698487774dSclaudio     const char *sysid, const char *pubid, int subset)
3708487774dSclaudio {
3718487774dSclaudio 	struct notification_xml *nxml = data;
3728487774dSclaudio 	XML_Parser p = nxml->parser;
3738487774dSclaudio 
3748487774dSclaudio 	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
3758487774dSclaudio }
3768487774dSclaudio 
3778ecbadc1Sclaudio struct notification_xml *
new_notification_xml(XML_Parser p,struct rrdp_session * repository,struct rrdp_session * current,const char * notifyuri)3788ecbadc1Sclaudio new_notification_xml(XML_Parser p, struct rrdp_session *repository,
37993d9375cSclaudio     struct rrdp_session *current, const char *notifyuri)
3808ecbadc1Sclaudio {
3818ecbadc1Sclaudio 	struct notification_xml *nxml;
3828ecbadc1Sclaudio 
3838ecbadc1Sclaudio 	if ((nxml = calloc(1, sizeof(*nxml))) == NULL)
3848ecbadc1Sclaudio 		err(1, "%s", __func__);
3858ecbadc1Sclaudio 	TAILQ_INIT(&(nxml->delta_q));
3868ecbadc1Sclaudio 	nxml->parser = p;
3878ecbadc1Sclaudio 	nxml->repository = repository;
3888ecbadc1Sclaudio 	nxml->current = current;
38993d9375cSclaudio 	nxml->notifyuri = notifyuri;
390b268327aSclaudio 	nxml->min_serial = delta_parse(repository, 0, NULL);
3918ecbadc1Sclaudio 
3928ecbadc1Sclaudio 	XML_SetElementHandler(nxml->parser, notification_xml_elem_start,
3938ecbadc1Sclaudio 	    notification_xml_elem_end);
3948ecbadc1Sclaudio 	XML_SetUserData(nxml->parser, nxml);
3958487774dSclaudio 	XML_SetDoctypeDeclHandler(nxml->parser, notification_doctype_handler,
3968487774dSclaudio 	    NULL);
3978ecbadc1Sclaudio 
3988ecbadc1Sclaudio 	return nxml;
3998ecbadc1Sclaudio }
4008ecbadc1Sclaudio 
401b268327aSclaudio static void
free_delta_queue(struct notification_xml * nxml)402b268327aSclaudio free_delta_queue(struct notification_xml *nxml)
403b268327aSclaudio {
404b268327aSclaudio 	while (!TAILQ_EMPTY(&nxml->delta_q)) {
405b268327aSclaudio 		struct delta_item *d = TAILQ_FIRST(&nxml->delta_q);
406b268327aSclaudio 		TAILQ_REMOVE(&nxml->delta_q, d, q);
407b268327aSclaudio 		free_delta(d);
408b268327aSclaudio 	}
409b268327aSclaudio }
410b268327aSclaudio 
4118ecbadc1Sclaudio void
free_notification_xml(struct notification_xml * nxml)4128ecbadc1Sclaudio free_notification_xml(struct notification_xml *nxml)
4138ecbadc1Sclaudio {
4148ecbadc1Sclaudio 	if (nxml == NULL)
4158ecbadc1Sclaudio 		return;
4168ecbadc1Sclaudio 
4178ecbadc1Sclaudio 	free(nxml->session_id);
4188ecbadc1Sclaudio 	free(nxml->snapshot_uri);
419b268327aSclaudio 	free_delta_queue(nxml);
420b268327aSclaudio 	free(nxml);
421b268327aSclaudio }
422b268327aSclaudio 
423b268327aSclaudio /*
424b268327aSclaudio  * Collect a list of deltas to store in the repository state.
425b268327aSclaudio  */
426b268327aSclaudio static void
notification_collect_deltas(struct notification_xml * nxml)427b268327aSclaudio notification_collect_deltas(struct notification_xml *nxml)
428b268327aSclaudio {
429b268327aSclaudio 	struct delta_item *d;
430b268327aSclaudio 	long long keep_serial = 0;
431b268327aSclaudio 	size_t cur_idx = 0, max_deltas;
432b268327aSclaudio 	char *hash;
433b268327aSclaudio 
434b268327aSclaudio 	max_deltas =
435b268327aSclaudio 	    sizeof(nxml->current->deltas) / sizeof(nxml->current->deltas[0]);
436b268327aSclaudio 
437b268327aSclaudio 	if (nxml->serial > (long long)max_deltas)
438b268327aSclaudio 		keep_serial = nxml->serial - max_deltas + 1;
439b268327aSclaudio 
440b268327aSclaudio 	TAILQ_FOREACH(d, &nxml->delta_q, q) {
441b268327aSclaudio 		if (d->serial >= keep_serial) {
442b268327aSclaudio 			assert(cur_idx < max_deltas);
443b268327aSclaudio 			hash = hex_encode(d->hash, sizeof(d->hash));
444b268327aSclaudio 			if (asprintf(&nxml->current->deltas[cur_idx++],
445b268327aSclaudio 			    "%lld %s", d->serial, hash) == -1)
446b268327aSclaudio 				err(1, NULL);
447b268327aSclaudio 			free(hash);
448b268327aSclaudio 		}
449b268327aSclaudio 	}
450b268327aSclaudio }
451b268327aSclaudio 
452b268327aSclaudio /*
453b268327aSclaudio  * Validate the delta list with the information from the repository state.
454b268327aSclaudio  * Remove all obsolete deltas so that the list starts with the delta with
455b268327aSclaudio  * serial nxml->repository->serial + 1.
456b268327aSclaudio  * Returns 1 if all deltas were valid and 0 on failure.
457b268327aSclaudio  */
458b268327aSclaudio static int
notification_check_deltas(struct notification_xml * nxml)459b268327aSclaudio notification_check_deltas(struct notification_xml *nxml)
460b268327aSclaudio {
461b268327aSclaudio 	struct delta_item *d, *nextd;
462b268327aSclaudio 	char *hash, *exp_hash;
463b268327aSclaudio 	long long exp_serial, new_serial;
464b268327aSclaudio 	size_t exp_idx = 0;
465b268327aSclaudio 
466b268327aSclaudio 	exp_serial = delta_parse(nxml->repository, exp_idx++, &exp_hash);
467b268327aSclaudio 	new_serial = nxml->repository->serial + 1;
468b268327aSclaudio 
469b268327aSclaudio 	/* compare hash of delta against repository state info */
470b268327aSclaudio 	TAILQ_FOREACH_SAFE(d, &nxml->delta_q, q, nextd) {
471b268327aSclaudio 		while (exp_serial != 0  && exp_serial < d->serial) {
472b268327aSclaudio 			exp_serial = delta_parse(nxml->repository,
473b268327aSclaudio 			    exp_idx++, &exp_hash);
474b268327aSclaudio 		}
475b268327aSclaudio 
476b268327aSclaudio 		if (d->serial == exp_serial) {
477b268327aSclaudio 			hash = hex_encode(d->hash, sizeof(d->hash));
478b268327aSclaudio 			if (strcmp(hash, exp_hash) != 0) {
479b268327aSclaudio 				warnx("%s: %s#%lld unexpected delta "
480b268327aSclaudio 				    "mutation (expected %s, got %s)",
481b268327aSclaudio 				    nxml->notifyuri, nxml->session_id,
482b268327aSclaudio 				    exp_serial, hash, exp_hash);
483b268327aSclaudio 				free(hash);
484b268327aSclaudio 				return 0;
485b268327aSclaudio 			}
486b268327aSclaudio 			free(hash);
487b268327aSclaudio 			exp_serial = delta_parse(nxml->repository,
488b268327aSclaudio 			    exp_idx++, &exp_hash);
489b268327aSclaudio 		}
490b268327aSclaudio 
491b268327aSclaudio 		/* is this delta needed? */
492b268327aSclaudio 		if (d->serial < new_serial) {
4938ecbadc1Sclaudio 			TAILQ_REMOVE(&nxml->delta_q, d, q);
4948ecbadc1Sclaudio 			free_delta(d);
4958ecbadc1Sclaudio 		}
496b268327aSclaudio 	}
497b268327aSclaudio 
498b268327aSclaudio 	return 1;
4998ecbadc1Sclaudio }
5008ecbadc1Sclaudio 
5018ecbadc1Sclaudio /*
5028ecbadc1Sclaudio  * Finalize notification step, decide if a delta update is possible
5038ecbadc1Sclaudio  * if either the session_id changed or the delta files fail to cover
5048ecbadc1Sclaudio  * all the steps up to the new serial fall back to a snapshot.
5058ecbadc1Sclaudio  * Return SNAPSHOT or DELTA for snapshot or delta processing.
5068ecbadc1Sclaudio  * Return NOTIFICATION if repository is up to date.
5078ecbadc1Sclaudio  */
5088ecbadc1Sclaudio enum rrdp_task
notification_done(struct notification_xml * nxml,char * last_mod)5098ecbadc1Sclaudio notification_done(struct notification_xml *nxml, char *last_mod)
5108ecbadc1Sclaudio {
5118ecbadc1Sclaudio 	nxml->current->last_mod = last_mod;
5128ecbadc1Sclaudio 	nxml->current->session_id = xstrdup(nxml->session_id);
513b268327aSclaudio 	notification_collect_deltas(nxml);
5148ecbadc1Sclaudio 
5158ecbadc1Sclaudio 	/* check the that the session_id was valid and still the same */
5168ecbadc1Sclaudio 	if (nxml->repository->session_id == NULL ||
5178ecbadc1Sclaudio 	    strcmp(nxml->session_id, nxml->repository->session_id) != 0)
5188ecbadc1Sclaudio 		goto snapshot;
5198ecbadc1Sclaudio 
5208ecbadc1Sclaudio 	/* if repository serial is 0 fall back to snapshot */
5218ecbadc1Sclaudio 	if (nxml->repository->serial == 0)
5228ecbadc1Sclaudio 		goto snapshot;
5238ecbadc1Sclaudio 
524b268327aSclaudio 	/* check that all needed deltas are available and valid */
525b268327aSclaudio 	if (!notification_check_deltas(nxml))
526b268327aSclaudio 		goto snapshot;
527b268327aSclaudio 
52879ab6f25Sclaudio 	if (nxml->repository->serial > nxml->serial)
52979ab6f25Sclaudio 		warnx("%s: serial number decreased from %lld to %lld",
53079ab6f25Sclaudio 		    nxml->notifyuri, nxml->repository->serial, nxml->serial);
53179ab6f25Sclaudio 
53279ab6f25Sclaudio 	/* if our serial is equal or plus 2, the repo is up to date */
53379ab6f25Sclaudio 	if (nxml->repository->serial >= nxml->serial &&
53479ab6f25Sclaudio 	    nxml->repository->serial - nxml->serial <= 2) {
535573c72b7Sclaudio 		nxml->current->serial = nxml->repository->serial;
5368ecbadc1Sclaudio 		return NOTIFICATION;
5378ecbadc1Sclaudio 	}
5388ecbadc1Sclaudio 
5395b4fd51cSclaudio 	/* it makes no sense to process too many deltas */
5409363012eSjob 	if (nxml->serial - nxml->repository->serial > MAX_RRDP_DELTAS)
5415b4fd51cSclaudio 		goto snapshot;
5425b4fd51cSclaudio 
543b268327aSclaudio 	/* no deltas queued */
544b268327aSclaudio 	if (TAILQ_EMPTY(&nxml->delta_q))
5458ecbadc1Sclaudio 		goto snapshot;
546b268327aSclaudio 
547b268327aSclaudio 	/* first possible delta is no match */
548b268327aSclaudio 	if (nxml->repository->serial + 1 != TAILQ_FIRST(&nxml->delta_q)->serial)
5498ecbadc1Sclaudio 		goto snapshot;
5508ecbadc1Sclaudio 
5518ecbadc1Sclaudio 	/* update via delta possible */
5528ecbadc1Sclaudio 	nxml->current->serial = nxml->repository->serial;
5538ecbadc1Sclaudio 	nxml->repository->serial = nxml->serial;
5548ecbadc1Sclaudio 	return DELTA;
5558ecbadc1Sclaudio 
5568ecbadc1Sclaudio snapshot:
5578ecbadc1Sclaudio 	/* update via snapshot download */
558b268327aSclaudio 	free_delta_queue(nxml);
5598ecbadc1Sclaudio 	nxml->current->serial = nxml->serial;
5608ecbadc1Sclaudio 	return SNAPSHOT;
5618ecbadc1Sclaudio }
5628ecbadc1Sclaudio 
5638ecbadc1Sclaudio const char *
notification_get_next(struct notification_xml * nxml,char * hash,size_t hlen,enum rrdp_task task)5648ecbadc1Sclaudio notification_get_next(struct notification_xml *nxml, char *hash, size_t hlen,
5658ecbadc1Sclaudio     enum rrdp_task task)
5668ecbadc1Sclaudio {
5678ecbadc1Sclaudio 	struct delta_item *d;
5688ecbadc1Sclaudio 
5698ecbadc1Sclaudio 	switch (task) {
5708ecbadc1Sclaudio 	case SNAPSHOT:
5718ecbadc1Sclaudio 		assert(hlen == sizeof(nxml->snapshot_hash));
5728ecbadc1Sclaudio 		memcpy(hash, nxml->snapshot_hash, hlen);
5738ecbadc1Sclaudio 		/*
5748ecbadc1Sclaudio 		 * Ensure that the serial is correct in case a previous
5758ecbadc1Sclaudio 		 * delta request failed.
5768ecbadc1Sclaudio 		 */
5778ecbadc1Sclaudio 		nxml->current->serial = nxml->serial;
5788ecbadc1Sclaudio 		return nxml->snapshot_uri;
5798ecbadc1Sclaudio 	case DELTA:
5808ecbadc1Sclaudio 		/* first bump serial, then use first delta */
5818ecbadc1Sclaudio 		nxml->current->serial += 1;
5828ecbadc1Sclaudio 		d = TAILQ_FIRST(&nxml->delta_q);
5838ecbadc1Sclaudio 		assert(d->serial == nxml->current->serial);
5848ecbadc1Sclaudio 		assert(hlen == sizeof(d->hash));
5858ecbadc1Sclaudio 		memcpy(hash, d->hash, hlen);
5868ecbadc1Sclaudio 		return d->uri;
5878ecbadc1Sclaudio 	default:
5888ecbadc1Sclaudio 		errx(1, "%s: bad task", __func__);
5898ecbadc1Sclaudio 	}
5908ecbadc1Sclaudio }
5918ecbadc1Sclaudio 
5928ecbadc1Sclaudio /*
5938ecbadc1Sclaudio  * Pop first element from the delta queue. Return non-0 if this was the last
5948ecbadc1Sclaudio  * delta to fetch.
5958ecbadc1Sclaudio  */
5968ecbadc1Sclaudio int
notification_delta_done(struct notification_xml * nxml)5978ecbadc1Sclaudio notification_delta_done(struct notification_xml *nxml)
5988ecbadc1Sclaudio {
5998ecbadc1Sclaudio 	struct delta_item *d;
6008ecbadc1Sclaudio 
6018ecbadc1Sclaudio 	d = TAILQ_FIRST(&nxml->delta_q);
6028ecbadc1Sclaudio 	assert(d->serial == nxml->current->serial);
6038ecbadc1Sclaudio 	TAILQ_REMOVE(&nxml->delta_q, d, q);
6048ecbadc1Sclaudio 	free_delta(d);
6058ecbadc1Sclaudio 
6068ecbadc1Sclaudio 	assert(!TAILQ_EMPTY(&nxml->delta_q) ||
6078ecbadc1Sclaudio 	    nxml->serial == nxml->current->serial);
6088ecbadc1Sclaudio 	return TAILQ_EMPTY(&nxml->delta_q);
6098ecbadc1Sclaudio }
6108ecbadc1Sclaudio 
61158d5f018Stb /* Used in regress. */
6128ecbadc1Sclaudio void
log_notification_xml(struct notification_xml * nxml)6138ecbadc1Sclaudio log_notification_xml(struct notification_xml *nxml)
6148ecbadc1Sclaudio {
615aef00ae0Sclaudio 	struct delta_item *d;
616aef00ae0Sclaudio 	char *hash;
617aef00ae0Sclaudio 
6188ecbadc1Sclaudio 	logx("session_id: %s, serial: %lld", nxml->session_id, nxml->serial);
6198ecbadc1Sclaudio 	logx("snapshot_uri: %s", nxml->snapshot_uri);
620aef00ae0Sclaudio 	hash = hex_encode(nxml->snapshot_hash, sizeof(nxml->snapshot_hash));
621aef00ae0Sclaudio 	logx("snapshot hash: %s", hash);
622aef00ae0Sclaudio 	free(hash);
623aef00ae0Sclaudio 
624aef00ae0Sclaudio 	TAILQ_FOREACH(d, &nxml->delta_q, q) {
625aef00ae0Sclaudio 		logx("delta serial %lld uri: %s", d->serial, d->uri);
626aef00ae0Sclaudio 		hash = hex_encode(d->hash, sizeof(d->hash));
627aef00ae0Sclaudio 		logx("delta hash: %s", hash);
628aef00ae0Sclaudio 		free(hash);
629aef00ae0Sclaudio 	}
6308ecbadc1Sclaudio }
631