1/*	$OpenBSD: rrdp_delta.c,v 1.14 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#include <openssl/sha.h>
28
29#include "extern.h"
30#include "rrdp.h"
31
32enum delta_scope {
33	DELTA_SCOPE_NONE,
34	DELTA_SCOPE_EMPTY_DELTA,
35	DELTA_SCOPE_DELTA,
36	DELTA_SCOPE_PUBLISH,
37	DELTA_SCOPE_END
38};
39
40struct delta_xml {
41	XML_Parser		 parser;
42	struct rrdp_session	*current;
43	struct rrdp		*rrdp;
44	struct publish_xml	*pxml;
45	char			*session_id;
46	long long		 serial;
47	int			 version;
48	enum delta_scope	 scope;
49};
50
51static void
52start_delta_elem(struct delta_xml *dxml, const char **attr)
53{
54	XML_Parser p = dxml->parser;
55	int has_xmlns = 0;
56	int i;
57
58	if (dxml->scope != DELTA_SCOPE_NONE)
59		PARSE_FAIL(p,
60		    "parse failed - entered delta elem unexpectedely");
61	for (i = 0; attr[i]; i += 2) {
62		const char *errstr;
63		if (strcmp("xmlns", attr[i]) == 0 &&
64		    strcmp(RRDP_XMLNS, attr[i + 1]) == 0) {
65			has_xmlns = 1;
66			continue;
67		}
68		if (strcmp("version", attr[i]) == 0) {
69			dxml->version = strtonum(attr[i + 1],
70			    1, MAX_VERSION, &errstr);
71			if (errstr == NULL)
72				continue;
73		}
74		if (strcmp("session_id", attr[i]) == 0 &&
75		    valid_uuid(attr[i + 1])) {
76			dxml->session_id = xstrdup(attr[i + 1]);
77			continue;
78		}
79		if (strcmp("serial", attr[i]) == 0) {
80			dxml->serial = strtonum(attr[i + 1],
81			    1, LLONG_MAX, &errstr);
82			if (errstr == NULL)
83				continue;
84		}
85		PARSE_FAIL(p, "parse failed - non conforming "
86		    "attribute '%s' found in delta elem", attr[i]);
87	}
88	if (!(has_xmlns && dxml->version && dxml->session_id && dxml->serial))
89		PARSE_FAIL(p, "parse failed - incomplete delta attributes");
90	if (strcmp(dxml->current->session_id, dxml->session_id) != 0)
91		PARSE_FAIL(p, "parse failed - session_id mismatch");
92	if (dxml->current->serial != dxml->serial)
93		PARSE_FAIL(p, "parse failed - serial mismatch");
94
95	dxml->scope = DELTA_SCOPE_EMPTY_DELTA;
96}
97
98static void
99end_delta_elem(struct delta_xml *dxml)
100{
101	XML_Parser p = dxml->parser;
102
103	if (dxml->scope == DELTA_SCOPE_EMPTY_DELTA)
104		PARSE_FAIL(p, "parse failed - empty delta");
105	if (dxml->scope != DELTA_SCOPE_DELTA)
106		PARSE_FAIL(p, "parse failed - exited delta "
107		    "elem unexpectedely");
108	dxml->scope = DELTA_SCOPE_END;
109}
110
111static void
112start_publish_withdraw_elem(struct delta_xml *dxml, const char **attr,
113    int withdraw)
114{
115	XML_Parser p = dxml->parser;
116	char *uri = NULL, hash[SHA256_DIGEST_LENGTH];
117	int i, hasUri = 0, hasHash = 0;
118	enum publish_type pub = PUB_UPD;
119
120	if (dxml->scope != DELTA_SCOPE_EMPTY_DELTA &&
121	    dxml->scope != DELTA_SCOPE_DELTA)
122		PARSE_FAIL(p, "parse failed - entered publish/withdraw "
123		    "elem unexpectedely");
124	for (i = 0; attr[i]; i += 2) {
125		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
126			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
127			    RSYNC_PROTO)) {
128				uri = xstrdup(attr[i + 1]);
129				continue;
130			}
131		}
132		if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) {
133			if (hex_decode(attr[i + 1], hash, sizeof(hash)) == 0)
134				continue;
135		}
136		PARSE_FAIL(p, "parse failed - non conforming "
137		    "attribute '%s' found in publish/withdraw elem", attr[i]);
138	}
139	if (hasUri != 1)
140		PARSE_FAIL(p,
141		    "parse failed - incomplete publish/withdraw attributes");
142	if (withdraw && hasHash != 1)
143		PARSE_FAIL(p, "parse failed - incomplete withdraw attributes");
144
145	if (withdraw)
146		pub = PUB_DEL;
147	else if (hasHash == 0)
148		pub = PUB_ADD;
149	dxml->pxml = new_publish_xml(pub, uri, hash,
150	    hasHash ? sizeof(hash) : 0);
151	dxml->scope = DELTA_SCOPE_PUBLISH;
152}
153
154static void
155end_publish_withdraw_elem(struct delta_xml *dxml, int withdraw)
156{
157	XML_Parser p = dxml->parser;
158
159	if (dxml->scope != DELTA_SCOPE_PUBLISH)
160		PARSE_FAIL(p, "parse failed - exited publish/withdraw "
161		    "elem unexpectedely");
162
163	if (publish_done(dxml->rrdp, dxml->pxml) != 0)
164		PARSE_FAIL(p, "parse failed - bad publish/withdraw elem");
165	dxml->pxml = NULL;
166
167	dxml->scope = DELTA_SCOPE_DELTA;
168}
169
170static void
171delta_xml_elem_start(void *data, const char *el, const char **attr)
172{
173	struct delta_xml *dxml = data;
174	XML_Parser p = dxml->parser;
175
176	/*
177	 * Can only enter here once as we should have no ways to get back to
178	 * NONE scope
179	 */
180	if (strcmp("delta", el) == 0)
181		start_delta_elem(dxml, attr);
182	/*
183	 * Will enter here multiple times, BUT never nested. will start
184	 * collecting character data in that handler
185	 * mem is cleared in end block, (TODO or on parse failure)
186	 */
187	else if (strcmp("publish", el) == 0)
188		start_publish_withdraw_elem(dxml, attr, 0);
189	else if (strcmp("withdraw", el) == 0)
190		start_publish_withdraw_elem(dxml, attr, 1);
191	else
192		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
193}
194
195static void
196delta_xml_elem_end(void *data, const char *el)
197{
198	struct delta_xml *dxml = data;
199	XML_Parser p = dxml->parser;
200
201	if (strcmp("delta", el) == 0)
202		end_delta_elem(dxml);
203	/*
204	 * TODO does this allow <publish></withdraw> or is that caught by basic
205	 * xml parsing
206	 */
207	else if (strcmp("publish", el) == 0)
208		end_publish_withdraw_elem(dxml, 0);
209	else if (strcmp("withdraw", el) == 0)
210		end_publish_withdraw_elem(dxml, 1);
211	else
212		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
213}
214
215static void
216delta_content_handler(void *data, const char *content, int length)
217{
218	struct delta_xml *dxml = data;
219	XML_Parser p = dxml->parser;
220
221	if (dxml->scope == DELTA_SCOPE_PUBLISH)
222		if (publish_add_content(dxml->pxml, content, length) == -1)
223			PARSE_FAIL(p, "parse failed, delta element for %s too "
224			    "big", dxml->pxml->uri);
225}
226
227static void
228delta_doctype_handler(void *data, const char *doctypeName,
229    const char *sysid, const char *pubid, int subset)
230{
231	struct delta_xml *dxml = data;
232	XML_Parser p = dxml->parser;
233
234	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
235}
236
237struct delta_xml *
238new_delta_xml(XML_Parser p, struct rrdp_session *rs, struct rrdp *r)
239{
240	struct delta_xml *dxml;
241
242	if ((dxml = calloc(1, sizeof(*dxml))) == NULL)
243		err(1, "%s", __func__);
244	dxml->parser = p;
245	dxml->current = rs;
246	dxml->rrdp = r;
247
248	if (XML_ParserReset(dxml->parser, "US-ASCII") != XML_TRUE)
249		errx(1, "%s: XML_ParserReset failed", __func__);
250
251	XML_SetElementHandler(dxml->parser, delta_xml_elem_start,
252	    delta_xml_elem_end);
253	XML_SetCharacterDataHandler(dxml->parser, delta_content_handler);
254	XML_SetUserData(dxml->parser, dxml);
255	XML_SetDoctypeDeclHandler(dxml->parser, delta_doctype_handler, NULL);
256
257	return dxml;
258}
259
260void
261free_delta_xml(struct delta_xml *dxml)
262{
263	if (dxml == NULL)
264		return;
265
266	free(dxml->session_id);
267	free_publish_xml(dxml->pxml);
268	free(dxml);
269}
270
271/* Used in regress. */
272void
273log_delta_xml(struct delta_xml *dxml)
274{
275	logx("version: %d", dxml->version);
276	logx("session_id: %s serial: %lld", dxml->session_id, dxml->serial);
277}
278