1/*	$OpenBSD: rrdp_snapshot.c,v 1.10 2024/05/30 09:54:59 job Exp $ */
2/*
3 * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com>
4 * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <err.h>
20#include <limits.h>
21#include <stdlib.h>
22#include <string.h>
23#include <stdio.h>
24#include <unistd.h>
25
26#include <expat.h>
27
28#include "extern.h"
29#include "rrdp.h"
30
31enum snapshot_scope {
32	SNAPSHOT_SCOPE_NONE,
33	SNAPSHOT_SCOPE_SNAPSHOT,
34	SNAPSHOT_SCOPE_PUBLISH,
35	SNAPSHOT_SCOPE_END
36};
37
38struct snapshot_xml {
39	XML_Parser		 parser;
40	struct rrdp_session	*current;
41	struct rrdp		*rrdp;
42	struct publish_xml	*pxml;
43	char			*session_id;
44	long long		 serial;
45	int			 version;
46	enum snapshot_scope	 scope;
47};
48
49static void
50start_snapshot_elem(struct snapshot_xml *sxml, const char **attr)
51{
52	XML_Parser p = sxml->parser;
53	int has_xmlns = 0;
54	int i;
55
56	if (sxml->scope != SNAPSHOT_SCOPE_NONE)
57		PARSE_FAIL(p,
58		    "parse failed - entered snapshot elem unexpectedely");
59	for (i = 0; attr[i]; i += 2) {
60		const char *errstr;
61		if (strcmp("xmlns", attr[i]) == 0 &&
62		    strcmp(RRDP_XMLNS, attr[i + 1]) == 0) {
63			has_xmlns = 1;
64			continue;
65		}
66		if (strcmp("version", attr[i]) == 0) {
67			sxml->version = strtonum(attr[i + 1],
68			    1, MAX_VERSION, &errstr);
69			if (errstr == NULL)
70				continue;
71		}
72		if (strcmp("session_id", attr[i]) == 0 &&
73		    valid_uuid(attr[i + 1])) {
74			sxml->session_id = xstrdup(attr[i + 1]);
75			continue;
76		}
77		if (strcmp("serial", attr[i]) == 0) {
78			sxml->serial = strtonum(attr[i + 1],
79			    1, LLONG_MAX, &errstr);
80			if (errstr == NULL)
81				continue;
82		}
83		PARSE_FAIL(p,
84		    "parse failed - non conforming "
85		    "attribute '%s' found in snapshot elem", attr[i]);
86	}
87	if (!(has_xmlns && sxml->version && sxml->session_id && sxml->serial))
88		PARSE_FAIL(p,
89		    "parse failed - incomplete snapshot attributes");
90	if (strcmp(sxml->current->session_id, sxml->session_id) != 0)
91		PARSE_FAIL(p, "parse failed - session_id mismatch");
92	if (sxml->current->serial != sxml->serial)
93		PARSE_FAIL(p, "parse failed - serial mismatch");
94
95	sxml->scope = SNAPSHOT_SCOPE_SNAPSHOT;
96}
97
98static void
99end_snapshot_elem(struct snapshot_xml *sxml)
100{
101	XML_Parser p = sxml->parser;
102
103	if (sxml->scope != SNAPSHOT_SCOPE_SNAPSHOT)
104		PARSE_FAIL(p, "parse failed - exited snapshot "
105		    "elem unexpectedely");
106	sxml->scope = SNAPSHOT_SCOPE_END;
107}
108
109static void
110start_publish_elem(struct snapshot_xml *sxml, const char **attr)
111{
112	XML_Parser p = sxml->parser;
113	char *uri = NULL;
114	int i, hasUri = 0;
115
116	if (sxml->scope != SNAPSHOT_SCOPE_SNAPSHOT)
117		PARSE_FAIL(p,
118		    "parse failed - entered publish elem unexpectedely");
119	for (i = 0; attr[i]; i += 2) {
120		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
121			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
122			    RSYNC_PROTO)) {
123				uri = xstrdup(attr[i + 1]);
124				continue;
125			}
126		}
127		/*
128		 * XXX it seems people can not write proper XML, ignore
129		 * bogus xmlns attribute on publish elements.
130		 */
131		if (strcmp("xmlns", attr[i]) == 0)
132			continue;
133		PARSE_FAIL(p, "parse failed - non conforming"
134		    " attribute '%s' found in publish elem", attr[i]);
135	}
136	if (hasUri != 1)
137		PARSE_FAIL(p, "parse failed - incomplete publish attributes");
138	sxml->pxml = new_publish_xml(PUB_ADD, uri, NULL, 0);
139	sxml->scope = SNAPSHOT_SCOPE_PUBLISH;
140}
141
142static void
143end_publish_elem(struct snapshot_xml *sxml)
144{
145	XML_Parser p = sxml->parser;
146
147	if (sxml->scope != SNAPSHOT_SCOPE_PUBLISH)
148		PARSE_FAIL(p, "parse failed - exited publish "
149		    "elem unexpectedely");
150
151	if (publish_done(sxml->rrdp, sxml->pxml) != 0)
152		PARSE_FAIL(p, "parse failed - bad publish elem");
153	sxml->pxml = NULL;
154
155	sxml->scope = SNAPSHOT_SCOPE_SNAPSHOT;
156}
157
158static void
159snapshot_xml_elem_start(void *data, const char *el, const char **attr)
160{
161	struct snapshot_xml *sxml = data;
162	XML_Parser p = sxml->parser;
163
164	/*
165	 * Can only enter here once as we should have no ways to get back to
166	 * NONE scope
167	 */
168	if (strcmp("snapshot", el) == 0)
169		start_snapshot_elem(sxml, attr);
170	/*
171	 * Will enter here multiple times, BUT never nested. will start
172	 * collecting character data in that handler mem is cleared in end
173	 * block, (TODO or on parse failure)
174	 */
175	else if (strcmp("publish", el) == 0)
176		start_publish_elem(sxml, attr);
177	else
178		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
179}
180
181static void
182snapshot_xml_elem_end(void *data, const char *el)
183{
184	struct snapshot_xml *sxml = data;
185	XML_Parser p = sxml->parser;
186
187	if (strcmp("snapshot", el) == 0)
188		end_snapshot_elem(sxml);
189	else if (strcmp("publish", el) == 0)
190		end_publish_elem(sxml);
191	else
192		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
193}
194
195static void
196snapshot_content_handler(void *data, const char *content, int length)
197{
198	struct snapshot_xml *sxml = data;
199	XML_Parser p = sxml->parser;
200
201	if (sxml->scope == SNAPSHOT_SCOPE_PUBLISH)
202		if (publish_add_content(sxml->pxml, content, length) == -1)
203			PARSE_FAIL(p, "parse failed, snapshot element for %s "
204			    "too big", sxml->pxml->uri);
205}
206
207static void
208snapshot_doctype_handler(void *data, const char *doctypeName,
209    const char *sysid, const char *pubid, int subset)
210{
211	struct snapshot_xml *sxml = data;
212	XML_Parser p = sxml->parser;
213
214	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
215}
216
217struct snapshot_xml *
218new_snapshot_xml(XML_Parser p, struct rrdp_session *rs, struct rrdp *r)
219{
220	struct snapshot_xml *sxml;
221
222	if ((sxml = calloc(1, sizeof(*sxml))) == NULL)
223		err(1, "%s", __func__);
224	sxml->parser = p;
225	sxml->current = rs;
226	sxml->rrdp = r;
227
228	if (XML_ParserReset(sxml->parser, "US-ASCII") != XML_TRUE)
229		errx(1, "%s: XML_ParserReset failed", __func__);
230
231	XML_SetElementHandler(sxml->parser, snapshot_xml_elem_start,
232	    snapshot_xml_elem_end);
233	XML_SetCharacterDataHandler(sxml->parser, snapshot_content_handler);
234	XML_SetUserData(sxml->parser, sxml);
235	XML_SetDoctypeDeclHandler(sxml->parser, snapshot_doctype_handler,
236	    NULL);
237
238	return sxml;
239}
240
241void
242free_snapshot_xml(struct snapshot_xml *sxml)
243{
244	if (sxml == NULL)
245		return;
246
247	free(sxml->session_id);
248	free_publish_xml(sxml->pxml);
249	free(sxml);
250}
251
252/* Used in regress. */
253void
254log_snapshot_xml(struct snapshot_xml *sxml)
255{
256	logx("scope: %d", sxml->scope);
257	logx("version: %d", sxml->version);
258	logx("session_id: %s serial: %lld", sxml->session_id, sxml->serial);
259}
260