1 /* 2 * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com> 3 * Copyright (c) 2021 Claudio Jeker <claudio@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/stat.h> 19 20 #include <assert.h> 21 #include <err.h> 22 #include <errno.h> 23 #include <limits.h> 24 #include <fcntl.h> 25 #include <string.h> 26 #include <unistd.h> 27 28 #include <expat.h> 29 #include <openssl/sha.h> 30 31 #include "extern.h" 32 #include "rrdp.h" 33 34 enum notification_scope { 35 NOTIFICATION_SCOPE_START, 36 NOTIFICATION_SCOPE_NOTIFICATION, 37 NOTIFICATION_SCOPE_SNAPSHOT, 38 NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT, 39 NOTIFICATION_SCOPE_DELTA, 40 NOTIFICATION_SCOPE_END 41 }; 42 43 struct delta_item { 44 char *uri; 45 char hash[SHA256_DIGEST_LENGTH]; 46 long long serial; 47 TAILQ_ENTRY(delta_item) q; 48 }; 49 50 TAILQ_HEAD(delta_q, delta_item); 51 52 struct notification_xml { 53 XML_Parser parser; 54 struct rrdp_session *repository; 55 struct rrdp_session *current; 56 char *session_id; 57 char *snapshot_uri; 58 char snapshot_hash[SHA256_DIGEST_LENGTH]; 59 struct delta_q delta_q; 60 long long serial; 61 int version; 62 enum notification_scope scope; 63 }; 64 65 static void free_delta(struct delta_item *); 66 67 static int 68 add_delta(struct notification_xml *nxml, const char *uri, 69 const char hash[SHA256_DIGEST_LENGTH], long long serial) 70 { 71 struct delta_item *d, *n; 72 73 if ((d = calloc(1, sizeof(struct delta_item))) == NULL) 74 err(1, "%s - calloc", __func__); 75 76 d->serial = serial; 77 d->uri = xstrdup(uri); 78 memcpy(d->hash, hash, sizeof(d->hash)); 79 80 /* optimise for a sorted input */ 81 n = TAILQ_LAST(&nxml->delta_q, delta_q); 82 if (n == NULL) 83 TAILQ_INSERT_HEAD(&nxml->delta_q, d, q); 84 else if (n->serial < serial) 85 TAILQ_INSERT_TAIL(&nxml->delta_q, d, q); 86 else 87 TAILQ_FOREACH(n, &nxml->delta_q, q) { 88 if (n->serial == serial) { 89 warnx("duplicate delta serial %lld ", serial); 90 free_delta(d); 91 return 0; 92 } 93 if (n->serial > serial) { 94 TAILQ_INSERT_BEFORE(n, d, q); 95 break; 96 } 97 } 98 99 return 1; 100 } 101 102 static void 103 free_delta(struct delta_item *d) 104 { 105 free(d->uri); 106 free(d); 107 } 108 109 static void 110 start_notification_elem(struct notification_xml *nxml, const char **attr) 111 { 112 XML_Parser p = nxml->parser; 113 int has_xmlns = 0; 114 size_t i; 115 116 if (nxml->scope != NOTIFICATION_SCOPE_START) 117 PARSE_FAIL(p, 118 "parse failed - entered notification elem unexpectedely"); 119 for (i = 0; attr[i]; i += 2) { 120 const char *errstr; 121 if (strcmp("xmlns", attr[i]) == 0) { 122 has_xmlns = 1; 123 continue; 124 } 125 if (strcmp("session_id", attr[i]) == 0) { 126 nxml->session_id = xstrdup(attr[i + 1]); 127 continue; 128 } 129 if (strcmp("version", attr[i]) == 0) { 130 nxml->version = strtonum(attr[i + 1], 131 1, MAX_VERSION, &errstr); 132 if (errstr == NULL) 133 continue; 134 } 135 if (strcmp("serial", attr[i]) == 0) { 136 nxml->serial = strtonum(attr[i + 1], 137 1, LLONG_MAX, &errstr); 138 if (errstr == NULL) 139 continue; 140 } 141 PARSE_FAIL(p, "parse failed - non conforming " 142 "attribute found in notification elem"); 143 } 144 if (!(has_xmlns && nxml->version && nxml->session_id && nxml->serial)) 145 PARSE_FAIL(p, "parse failed - incomplete " 146 "notification attributes"); 147 148 nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION; 149 } 150 151 static void 152 end_notification_elem(struct notification_xml *nxml) 153 { 154 XML_Parser p = nxml->parser; 155 156 if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT) 157 PARSE_FAIL(p, "parse failed - exited notification " 158 "elem unexpectedely"); 159 nxml->scope = NOTIFICATION_SCOPE_END; 160 } 161 162 static void 163 start_snapshot_elem(struct notification_xml *nxml, const char **attr) 164 { 165 XML_Parser p = nxml->parser; 166 int i, hasUri = 0, hasHash = 0; 167 168 if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION) 169 PARSE_FAIL(p, 170 "parse failed - entered snapshot elem unexpectedely"); 171 for (i = 0; attr[i]; i += 2) { 172 if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) { 173 if (valid_uri(attr[i + 1], strlen(attr[i + 1]), 174 "https://")) { 175 nxml->snapshot_uri = xstrdup(attr[i + 1]); 176 continue; 177 } 178 } 179 if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) { 180 if (hex_decode(attr[i + 1], nxml->snapshot_hash, 181 sizeof(nxml->snapshot_hash)) == 0) 182 continue; 183 } 184 PARSE_FAIL(p, "parse failed - non conforming " 185 "attribute found in snapshot elem"); 186 } 187 if (hasUri != 1 || hasHash != 1) 188 PARSE_FAIL(p, "parse failed - incomplete snapshot attributes"); 189 190 nxml->scope = NOTIFICATION_SCOPE_SNAPSHOT; 191 } 192 193 static void 194 end_snapshot_elem(struct notification_xml *nxml) 195 { 196 XML_Parser p = nxml->parser; 197 198 if (nxml->scope != NOTIFICATION_SCOPE_SNAPSHOT) 199 PARSE_FAIL(p, "parse failed - exited snapshot " 200 "elem unexpectedely"); 201 nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT; 202 } 203 204 static void 205 start_delta_elem(struct notification_xml *nxml, const char **attr) 206 { 207 XML_Parser p = nxml->parser; 208 int i, hasUri = 0, hasHash = 0; 209 const char *delta_uri = NULL; 210 char delta_hash[SHA256_DIGEST_LENGTH]; 211 long long delta_serial = 0; 212 213 if (nxml->scope != NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT) 214 PARSE_FAIL(p, "parse failed - entered delta " 215 "elem unexpectedely"); 216 for (i = 0; attr[i]; i += 2) { 217 if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) { 218 if (valid_uri(attr[i + 1], strlen(attr[i + 1]), 219 "https://")) { 220 delta_uri = attr[i + 1]; 221 continue; 222 } 223 } 224 if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) { 225 if (hex_decode(attr[i + 1], delta_hash, 226 sizeof(delta_hash)) == 0) 227 continue; 228 } 229 if (strcmp("serial", attr[i]) == 0 && delta_serial == 0) { 230 const char *errstr; 231 232 delta_serial = strtonum(attr[i + 1], 233 1, LLONG_MAX, &errstr); 234 if (errstr == NULL) 235 continue; 236 } 237 PARSE_FAIL(p, "parse failed - non conforming " 238 "attribute found in snapshot elem"); 239 } 240 /* Only add to the list if we are relevant */ 241 if (hasUri != 1 || hasHash != 1 || delta_serial == 0) 242 PARSE_FAIL(p, "parse failed - incomplete delta attributes"); 243 244 /* optimisation, add only deltas that could be interesting */ 245 if (nxml->repository->serial != 0 && 246 nxml->repository->serial < delta_serial && 247 nxml->repository->session_id != NULL && 248 strcmp(nxml->session_id, nxml->repository->session_id) == 0) { 249 if (add_delta(nxml, delta_uri, delta_hash, delta_serial) == 0) 250 PARSE_FAIL(p, "parse failed - adding delta failed"); 251 } 252 253 nxml->scope = NOTIFICATION_SCOPE_DELTA; 254 } 255 256 static void 257 end_delta_elem(struct notification_xml *nxml) 258 { 259 XML_Parser p = nxml->parser; 260 261 if (nxml->scope != NOTIFICATION_SCOPE_DELTA) 262 PARSE_FAIL(p, "parse failed - exited delta elem unexpectedely"); 263 nxml->scope = NOTIFICATION_SCOPE_NOTIFICATION_POST_SNAPSHOT; 264 } 265 266 static void 267 notification_xml_elem_start(void *data, const char *el, const char **attr) 268 { 269 struct notification_xml *nxml = data; 270 XML_Parser p = nxml->parser; 271 272 /* 273 * Can only enter here once as we should have no ways to get back to 274 * START scope 275 */ 276 if (strcmp("notification", el) == 0) 277 start_notification_elem(nxml, attr); 278 /* 279 * Will enter here multiple times, BUT never nested. will start 280 * collecting character data in that handler 281 * mem is cleared in end block, (TODO or on parse failure) 282 */ 283 else if (strcmp("snapshot", el) == 0) 284 start_snapshot_elem(nxml, attr); 285 else if (strcmp("delta", el) == 0) 286 start_delta_elem(nxml, attr); 287 else 288 PARSE_FAIL(p, "parse failed - unexpected elem exit found"); 289 } 290 291 static void 292 notification_xml_elem_end(void *data, const char *el) 293 { 294 struct notification_xml *nxml = data; 295 XML_Parser p = nxml->parser; 296 297 if (strcmp("notification", el) == 0) 298 end_notification_elem(nxml); 299 else if (strcmp("snapshot", el) == 0) 300 end_snapshot_elem(nxml); 301 else if (strcmp("delta", el) == 0) 302 end_delta_elem(nxml); 303 else 304 PARSE_FAIL(p, "parse failed - unexpected elem exit found"); 305 } 306 307 struct notification_xml * 308 new_notification_xml(XML_Parser p, struct rrdp_session *repository, 309 struct rrdp_session *current) 310 { 311 struct notification_xml *nxml; 312 313 if ((nxml = calloc(1, sizeof(*nxml))) == NULL) 314 err(1, "%s", __func__); 315 TAILQ_INIT(&(nxml->delta_q)); 316 nxml->parser = p; 317 nxml->repository = repository; 318 nxml->current = current; 319 320 XML_SetElementHandler(nxml->parser, notification_xml_elem_start, 321 notification_xml_elem_end); 322 XML_SetUserData(nxml->parser, nxml); 323 324 return nxml; 325 } 326 327 void 328 free_notification_xml(struct notification_xml *nxml) 329 { 330 if (nxml == NULL) 331 return; 332 333 free(nxml->session_id); 334 free(nxml->snapshot_uri); 335 while (!TAILQ_EMPTY(&nxml->delta_q)) { 336 struct delta_item *d = TAILQ_FIRST(&nxml->delta_q); 337 TAILQ_REMOVE(&nxml->delta_q, d, q); 338 free_delta(d); 339 } 340 free(nxml); 341 } 342 343 /* 344 * Finalize notification step, decide if a delta update is possible 345 * if either the session_id changed or the delta files fail to cover 346 * all the steps up to the new serial fall back to a snapshot. 347 * Return SNAPSHOT or DELTA for snapshot or delta processing. 348 * Return NOTIFICATION if repository is up to date. 349 */ 350 enum rrdp_task 351 notification_done(struct notification_xml *nxml, char *last_mod) 352 { 353 struct delta_item *d; 354 long long s, last_s = 0; 355 356 nxml->current->last_mod = last_mod; 357 nxml->current->session_id = xstrdup(nxml->session_id); 358 359 /* check the that the session_id was valid and still the same */ 360 if (nxml->repository->session_id == NULL || 361 strcmp(nxml->session_id, nxml->repository->session_id) != 0) 362 goto snapshot; 363 364 /* if repository serial is 0 fall back to snapshot */ 365 if (nxml->repository->serial == 0) 366 goto snapshot; 367 368 if (nxml->repository->serial == nxml->serial) { 369 nxml->current->serial = nxml->serial; 370 return NOTIFICATION; 371 } 372 373 /* check that all needed deltas are available */ 374 s = nxml->repository->serial + 1; 375 TAILQ_FOREACH(d, &nxml->delta_q, q) { 376 if (d->serial != s++) 377 goto snapshot; 378 last_s = d->serial; 379 } 380 if (last_s != nxml->serial) 381 goto snapshot; 382 383 /* update via delta possible */ 384 nxml->current->serial = nxml->repository->serial; 385 nxml->repository->serial = nxml->serial; 386 return DELTA; 387 388 snapshot: 389 /* update via snapshot download */ 390 nxml->current->serial = nxml->serial; 391 return SNAPSHOT; 392 } 393 394 const char * 395 notification_get_next(struct notification_xml *nxml, char *hash, size_t hlen, 396 enum rrdp_task task) 397 { 398 struct delta_item *d; 399 400 switch (task) { 401 case SNAPSHOT: 402 assert(hlen == sizeof(nxml->snapshot_hash)); 403 memcpy(hash, nxml->snapshot_hash, hlen); 404 /* 405 * Ensure that the serial is correct in case a previous 406 * delta request failed. 407 */ 408 nxml->current->serial = nxml->serial; 409 return nxml->snapshot_uri; 410 case DELTA: 411 /* first bump serial, then use first delta */ 412 nxml->current->serial += 1; 413 d = TAILQ_FIRST(&nxml->delta_q); 414 assert(d->serial == nxml->current->serial); 415 assert(hlen == sizeof(d->hash)); 416 memcpy(hash, d->hash, hlen); 417 return d->uri; 418 default: 419 errx(1, "%s: bad task", __func__); 420 } 421 } 422 423 /* 424 * Pop first element from the delta queue. Return non-0 if this was the last 425 * delta to fetch. 426 */ 427 int 428 notification_delta_done(struct notification_xml *nxml) 429 { 430 struct delta_item *d; 431 432 d = TAILQ_FIRST(&nxml->delta_q); 433 assert(d->serial == nxml->current->serial); 434 TAILQ_REMOVE(&nxml->delta_q, d, q); 435 free_delta(d); 436 437 assert(!TAILQ_EMPTY(&nxml->delta_q) || 438 nxml->serial == nxml->current->serial); 439 return TAILQ_EMPTY(&nxml->delta_q); 440 } 441 442 void 443 log_notification_xml(struct notification_xml *nxml) 444 { 445 logx("session_id: %s, serial: %lld", nxml->session_id, nxml->serial); 446 logx("snapshot_uri: %s", nxml->snapshot_uri); 447 } 448