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