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