1 /*	$OpenBSD: rrdp_snapshot.c,v 1.5 2021/11/09 11:01:04 claudio 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 
31 enum snapshot_scope {
32 	SNAPSHOT_SCOPE_NONE,
33 	SNAPSHOT_SCOPE_SNAPSHOT,
34 	SNAPSHOT_SCOPE_PUBLISH,
35 	SNAPSHOT_SCOPE_END
36 };
37 
38 struct 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 
49 static void
50 start_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 			has_xmlns = 1;
63 			continue;
64 		}
65 		if (strcmp("version", attr[i]) == 0) {
66 			sxml->version = strtonum(attr[i + 1],
67 			    1, MAX_VERSION, &errstr);
68 			if (errstr == NULL)
69 				continue;
70 		}
71 		if (strcmp("session_id", attr[i]) == 0) {
72 			sxml->session_id = xstrdup(attr[i + 1]);
73 			continue;
74 		}
75 		if (strcmp("serial", attr[i]) == 0) {
76 			sxml->serial = strtonum(attr[i + 1],
77 			    1, LLONG_MAX, &errstr);
78 			if (errstr == NULL)
79 				continue;
80 		}
81 		PARSE_FAIL(p,
82 		    "parse failed - non conforming "
83 		    "attribute '%s' found in snapshot elem", attr[i]);
84 	}
85 	if (!(has_xmlns && sxml->version && sxml->session_id && sxml->serial))
86 		PARSE_FAIL(p,
87 		    "parse failed - incomplete snapshot attributes");
88 	if (strcmp(sxml->current->session_id, sxml->session_id) != 0)
89 		PARSE_FAIL(p, "parse failed - session_id mismatch");
90 	if (sxml->current->serial != sxml->serial)
91 		PARSE_FAIL(p, "parse failed - serial mismatch");
92 
93 	sxml->scope = SNAPSHOT_SCOPE_SNAPSHOT;
94 }
95 
96 static void
97 end_snapshot_elem(struct snapshot_xml *sxml)
98 {
99 	XML_Parser p = sxml->parser;
100 
101 	if (sxml->scope != SNAPSHOT_SCOPE_SNAPSHOT)
102 		PARSE_FAIL(p, "parse failed - exited snapshot "
103 		    "elem unexpectedely");
104 	sxml->scope = SNAPSHOT_SCOPE_END;
105 }
106 
107 static void
108 start_publish_elem(struct snapshot_xml *sxml, const char **attr)
109 {
110 	XML_Parser p = sxml->parser;
111 	char *uri = NULL;
112 	int i, hasUri = 0;
113 
114 	if (sxml->scope != SNAPSHOT_SCOPE_SNAPSHOT)
115 		PARSE_FAIL(p,
116 		    "parse failed - entered publish elem unexpectedely");
117 	for (i = 0; attr[i]; i += 2) {
118 		if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) {
119 			if (valid_uri(attr[i + 1], strlen(attr[i + 1]),
120 			    "rsync://")) {
121 				uri = xstrdup(attr[i + 1]);
122 				continue;
123 			}
124 		}
125 		/*
126 		 * XXX it seems people can not write proper XML, ignore
127 		 * bogus xmlns attribute on publish elements.
128 		 */
129 		if (strcmp("xmlns", attr[i]) == 0)
130 			continue;
131 		PARSE_FAIL(p, "parse failed - non conforming"
132 		    " attribute '%s' found in publish elem", attr[i]);
133 	}
134 	if (hasUri != 1)
135 		PARSE_FAIL(p, "parse failed - incomplete publish attributes");
136 	sxml->pxml = new_publish_xml(PUB_ADD, uri, NULL, 0);
137 	sxml->scope = SNAPSHOT_SCOPE_PUBLISH;
138 }
139 
140 static void
141 end_publish_elem(struct snapshot_xml *sxml)
142 {
143 	XML_Parser p = sxml->parser;
144 
145 	if (sxml->scope != SNAPSHOT_SCOPE_PUBLISH)
146 		PARSE_FAIL(p, "parse failed - exited publish "
147 		    "elem unexpectedely");
148 
149 	if (publish_done(sxml->rrdp, sxml->pxml) != 0)
150 		PARSE_FAIL(p, "parse failed - bad publish elem");
151 	sxml->pxml = NULL;
152 
153 	sxml->scope = SNAPSHOT_SCOPE_SNAPSHOT;
154 }
155 
156 static void
157 snapshot_xml_elem_start(void *data, const char *el, const char **attr)
158 {
159 	struct snapshot_xml *sxml = data;
160 	XML_Parser p = sxml->parser;
161 
162 	/*
163 	 * Can only enter here once as we should have no ways to get back to
164 	 * NONE scope
165 	 */
166 	if (strcmp("snapshot", el) == 0)
167 		start_snapshot_elem(sxml, attr);
168 	/*
169 	 * Will enter here multiple times, BUT never nested. will start
170 	 * collecting character data in that handler mem is cleared in end
171 	 * block, (TODO or on parse failure)
172 	 */
173 	else if (strcmp("publish", el) == 0)
174 		start_publish_elem(sxml, attr);
175 	else
176 		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
177 }
178 
179 static void
180 snapshot_xml_elem_end(void *data, const char *el)
181 {
182 	struct snapshot_xml *sxml = data;
183 	XML_Parser p = sxml->parser;
184 
185 	if (strcmp("snapshot", el) == 0)
186 		end_snapshot_elem(sxml);
187 	else if (strcmp("publish", el) == 0)
188 		end_publish_elem(sxml);
189 	else
190 		PARSE_FAIL(p, "parse failed - unexpected elem exit found");
191 }
192 
193 static void
194 snapshot_content_handler(void *data, const char *content, int length)
195 {
196 	struct snapshot_xml *sxml = data;
197 	XML_Parser p = sxml->parser;
198 
199 	if (sxml->scope == SNAPSHOT_SCOPE_PUBLISH)
200 		if (publish_add_content(sxml->pxml, content, length) == -1)
201 			PARSE_FAIL(p, "parse failed - content too big");
202 }
203 
204 static void
205 snapshot_doctype_handler(void *data, const char *doctypeName,
206     const char *sysid, const char *pubid, int subset)
207 {
208 	struct snapshot_xml *sxml = data;
209 	XML_Parser p = sxml->parser;
210 
211 	PARSE_FAIL(p, "parse failed - DOCTYPE not allowed");
212 }
213 
214 struct snapshot_xml *
215 new_snapshot_xml(XML_Parser p, struct rrdp_session *rs, struct rrdp *r)
216 {
217 	struct snapshot_xml *sxml;
218 
219 	if ((sxml = calloc(1, sizeof(*sxml))) == NULL)
220 		err(1, "%s", __func__);
221 	sxml->parser = p;
222 	sxml->current = rs;
223 	sxml->rrdp = r;
224 
225 	if (XML_ParserReset(sxml->parser, "US-ASCII") != XML_TRUE)
226 		errx(1, "%s: XML_ParserReset failed", __func__);
227 
228 	XML_SetElementHandler(sxml->parser, snapshot_xml_elem_start,
229 	    snapshot_xml_elem_end);
230 	XML_SetCharacterDataHandler(sxml->parser, snapshot_content_handler);
231 	XML_SetUserData(sxml->parser, sxml);
232 	XML_SetDoctypeDeclHandler(sxml->parser, snapshot_doctype_handler,
233 	    NULL);
234 
235 	return sxml;
236 }
237 
238 void
239 free_snapshot_xml(struct snapshot_xml *sxml)
240 {
241 	if (sxml == NULL)
242 		return;
243 
244 	free(sxml->session_id);
245 	free_publish_xml(sxml->pxml);
246 	free(sxml);
247 }
248 
249 void
250 log_snapshot_xml(struct snapshot_xml *sxml)
251 {
252 	logx("scope: %d", sxml->scope);
253 	logx("version: %d", sxml->version);
254 	logx("session_id: %s serial: %lld", sxml->session_id, sxml->serial);
255 }
256