xref: /openbsd-src/regress/usr.sbin/rpki-client/test-rrdp.c (revision 5af821209fa3336ed87da6b82b004b70a12d2594)
1 /*	$OpenBSD: test-rrdp.c,v 1.10 2024/04/22 05:54:01 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 #include <sys/queue.h>
19 #include <sys/stat.h>
20 
21 #include <assert.h>
22 #include <ctype.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <poll.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sha2.h>
31 
32 #include <expat.h>
33 #include <openssl/sha.h>
34 
35 #include "extern.h"
36 #include "rrdp.h"
37 
38 int filemode;
39 int outformats;
40 int verbose;
41 int experimental;
42 
43 #define REGRESS_NOTIFY_URI	"https://rpki.example.com/notify.xml"
44 
45 #define MAX_SESSIONS	12
46 #define	READ_BUF_SIZE	(32 * 1024)
47 
48 #define RRDP_STATE_REQ		0x01
49 #define RRDP_STATE_WAIT		0x02
50 #define RRDP_STATE_PARSE	0x04
51 #define RRDP_STATE_PARSE_ERROR	0x08
52 #define RRDP_STATE_PARSE_DONE	0x10
53 #define RRDP_STATE_HTTP_DONE	0x20
54 #define RRDP_STATE_DONE		(RRDP_STATE_PARSE_DONE | RRDP_STATE_HTTP_DONE)
55 
56 struct rrdp {
57 	TAILQ_ENTRY(rrdp)	 entry;
58 	unsigned int		 id;
59 	char			*notifyuri;
60 	char			*local;
61 	char			*last_mod;
62 
63 	struct pollfd		*pfd;
64 	int			 infd;
65 	int			 state;
66 	unsigned int		 file_pending;
67 	unsigned int		 file_failed;
68 	enum http_result	 res;
69 	enum rrdp_task		 task;
70 
71 	char			 hash[SHA256_DIGEST_LENGTH];
72 	SHA256_CTX		 ctx;
73 
74 	struct rrdp_session	 repository;
75 	struct rrdp_session	 current;
76 	XML_Parser		 parser;
77 	struct notification_xml	*nxml;
78 	struct snapshot_xml	*sxml;
79 	struct delta_xml	*dxml;
80 };
81 
82 void
logx(const char * fmt,...)83 logx(const char *fmt, ...)
84 {
85 	va_list ap;
86 
87 	va_start(ap, fmt);
88 	vwarnx(fmt, ap);
89 	va_end(ap);
90 }
91 
92 char *
xstrdup(const char * s)93 xstrdup(const char *s)
94 {
95 	char *r;
96 	if ((r = strdup(s)) == NULL)
97 		err(1, "strdup");
98 	return r;
99 }
100 
101 /*
102  * Send a blob of data to the main process to store it in the repository.
103  */
104 void
rrdp_publish_file(struct rrdp * s,struct publish_xml * pxml,unsigned char * data,size_t datasz)105 rrdp_publish_file(struct rrdp *s, struct publish_xml *pxml,
106     unsigned char *data, size_t datasz)
107 {
108 	char buf[SHA256_DIGEST_STRING_LENGTH];
109 	char *hash = NULL;
110 
111 	switch (pxml->type) {
112 	case PUB_ADD:
113 		logx("type: %s", "add");
114 		break;
115 	case PUB_UPD:
116 		logx("type: %s", "update");
117 		hash = hex_encode(pxml->hash, sizeof(pxml->hash));
118 		break;
119 	case PUB_DEL:
120 		logx("type: %s", "delete");
121 		hash = hex_encode(pxml->hash, sizeof(pxml->hash));
122 		break;
123 	default:
124 		errx(1, "unknown publish type");
125 	}
126 	logx("uri: %s", pxml->uri);
127 	SHA256Data(data, datasz, buf);
128 	logx("data: %s", buf);
129 
130 	if (hash)
131 		logx("hash: %s", hash);
132 	free(hash);
133 }
134 
135 static struct rrdp *
rrdp_new(unsigned int id,char * local,char * notify,char * session_id,long long serial,char * last_mod)136 rrdp_new(unsigned int id, char *local, char *notify, char *session_id,
137     long long serial, char *last_mod)
138 {
139 	struct rrdp *s;
140 
141 	if ((s = calloc(1, sizeof(*s))) == NULL)
142 		err(1, NULL);
143 
144 	s->infd = 0; /* stdin */
145 	s->id = id;
146 	s->local = local;
147 	s->notifyuri = notify;
148 	s->repository.session_id = session_id;
149 	s->repository.serial = serial;
150 	s->repository.last_mod = last_mod;
151 
152 	s->state = RRDP_STATE_REQ;
153 	if ((s->parser = XML_ParserCreate("US-ASCII")) == NULL)
154 		err(1, "XML_ParserCreate");
155 
156 	return s;
157 }
158 
159 static void
rrdp_free(struct rrdp * s)160 rrdp_free(struct rrdp *s)
161 {
162 	if (s == NULL)
163 		return;
164 
165 	free_notification_xml(s->nxml);
166 	free_snapshot_xml(s->sxml);
167 	free_delta_xml(s->dxml);
168 
169 	if (s->parser)
170 		XML_ParserFree(s->parser);
171 	if (s->infd != -1)
172 		close(s->infd);
173 	free(s->notifyuri);
174 	free(s->local);
175 	free(s->last_mod);
176 	free(s->repository.last_mod);
177 	free(s->repository.session_id);
178 	free(s->current.last_mod);
179 	free(s->current.session_id);
180 
181 	free(s);
182 }
183 
184 static void
rrdp_finished(struct rrdp * s)185 rrdp_finished(struct rrdp *s)
186 {
187 	XML_Parser p = s->parser;
188 	unsigned int id = s->id;
189 
190 	if (s->state & RRDP_STATE_PARSE_ERROR)
191 		return;
192 
193 	/*
194 	 * Finalize parsing on success to be sure that
195 	 * all of the XML is correct. Needs to be done here
196 	 * since the call would most probably fail for non
197 	 * successful data fetches.
198 	 */
199 	if (XML_Parse(p, NULL, 0, 1) != XML_STATUS_OK) {
200 		warnx("%s: XML error at line %llu: %s", s->local,
201 		    (unsigned long long)XML_GetCurrentLineNumber(p),
202 		    XML_ErrorString(XML_GetErrorCode(p)));
203 		return;
204 	}
205 
206 	switch (s->task) {
207 	case NOTIFICATION:
208 		notification_done(s->nxml, NULL);
209 		log_notification_xml(s->nxml);
210 		break;
211 	case SNAPSHOT:
212 		log_snapshot_xml(s->sxml);
213 		break;
214 	case DELTA:
215 		log_delta_xml(s->dxml);
216 		break;
217 	}
218 }
219 
220 static void
rrdp_data_handler(struct rrdp * s)221 rrdp_data_handler(struct rrdp *s)
222 {
223 	char buf[READ_BUF_SIZE];
224 	XML_Parser p = s->parser;
225 	ssize_t len;
226 
227 	len = read(s->infd, buf, sizeof(buf));
228 	if (len == -1) {
229 		s->state |= RRDP_STATE_PARSE_ERROR;
230 		warn("%s: read failure", s->local);
231 		return;
232 	}
233 	if ((s->state & RRDP_STATE_PARSE) == 0)
234 		errx(1, "%s: bad parser state", s->local);
235 	if (len == 0) {
236 		/* parser stage finished */
237 		close(s->infd);
238 		s->infd = -1;
239 
240 		if (s->task != NOTIFICATION) {
241 			char h[SHA256_DIGEST_LENGTH];
242 
243 			SHA256_Final(h, &s->ctx);
244 			if (memcmp(s->hash, h, sizeof(s->hash)) != 0) {
245 				s->state |= RRDP_STATE_PARSE_ERROR;
246 				warnx("%s: bad message digest", s->local);
247 			}
248 		}
249 
250 		s->state |= RRDP_STATE_PARSE_DONE;
251 		rrdp_finished(s);
252 		return;
253 	}
254 
255 	/* parse and maybe hash the bytes just read */
256 	if (s->task != NOTIFICATION)
257 		SHA256_Update(&s->ctx, buf, len);
258 	if ((s->state & RRDP_STATE_PARSE_ERROR) == 0 &&
259 	    XML_Parse(p, buf, len, 0) != XML_STATUS_OK) {
260 		warnx("%s: parse error at line %llu: %s", s->local,
261 		    (unsigned long long)XML_GetCurrentLineNumber(p),
262 		    XML_ErrorString(XML_GetErrorCode(p)));
263 		s->state |= RRDP_STATE_PARSE_ERROR;
264 	}
265 }
266 
267 int
main(int argc,char ** argv)268 main(int argc, char **argv)
269 {
270 	struct rrdp *s = NULL;
271 	const char *e;
272 	char *session_id = NULL;
273 	char hash[SHA256_DIGEST_LENGTH];
274 	long long serial = 0;
275 	int c;
276 
277 
278 	while ((c = getopt(argc, argv, "dH:N:nS:s")) != -1)
279 		switch (c) {
280 		case 'd':
281 			if (s)
282 				goto usage;
283 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
284 			    session_id, serial, NULL);
285 			s->dxml = new_delta_xml(s->parser,
286 			    &s->repository, s);
287 			s->task = DELTA;
288 			SHA256_Init(&s->ctx);
289 			memcpy(s->hash, hash, sizeof(s->hash));
290 			break;
291 		case 'H':
292 			if (hex_decode(optarg, hash, sizeof(hash)) == -1)
293 				errx(1, "bad hash");
294 			break;
295 		case 'N':
296 			serial = strtonum(optarg, LLONG_MIN, LLONG_MAX, &e);
297 			if (e != NULL)
298 				errx(1, "serial is %s: %s", e, optarg);
299 			break;
300 		case 'n':
301 			if (s)
302 				goto usage;
303 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
304 			    session_id, serial, NULL);
305 			s->nxml = new_notification_xml(s->parser,
306 			    &s->repository, &s->current, s->notifyuri);
307 			s->task = NOTIFICATION;
308 			break;
309 		case 'S':
310 			session_id = optarg;
311 			break;
312 		case 's':
313 			if (s)
314 				goto usage;
315 			s = rrdp_new(0, "stdin", REGRESS_NOTIFY_URI,
316 			    session_id, serial, NULL);
317 			s->sxml = new_snapshot_xml(s->parser,
318 			    &s->repository, s);
319 			s->task = SNAPSHOT;
320 			SHA256_Init(&s->ctx);
321 			memcpy(s->hash, hash, sizeof(s->hash));
322 			break;
323 		default:
324 			goto usage;
325 		}
326 
327 	s->state = RRDP_STATE_PARSE;
328 
329 	while (!(s->state & RRDP_STATE_PARSE_DONE)) {
330 		rrdp_data_handler(s);
331 	}
332 
333 	if ((s->state & RRDP_STATE_PARSE_ERROR) == 0) {
334 		printf("OK\n");
335 		return 0;
336 	} else {
337 		return 1;
338 	}
339 
340 usage:
341 	fprintf(stderr, "usage: %s [-S session_id] [-N serial] [-H hash] "
342 	    "-d | -n | -s\n", "test-rrdp");
343 	exit(1);
344 }
345 
346 time_t
get_current_time(void)347 get_current_time(void)
348 {
349 	return time(NULL);
350 }
351