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