1/* $OpenBSD: rrdp_delta.c,v 1.14 2024/05/30 09:54:59 job 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 19#include <err.h> 20#include <limits.h> 21#include <stdlib.h> 22#include <string.h> 23#include <stdio.h> 24#include <unistd.h> 25 26#include <expat.h> 27#include <openssl/sha.h> 28 29#include "extern.h" 30#include "rrdp.h" 31 32enum delta_scope { 33 DELTA_SCOPE_NONE, 34 DELTA_SCOPE_EMPTY_DELTA, 35 DELTA_SCOPE_DELTA, 36 DELTA_SCOPE_PUBLISH, 37 DELTA_SCOPE_END 38}; 39 40struct delta_xml { 41 XML_Parser parser; 42 struct rrdp_session *current; 43 struct rrdp *rrdp; 44 struct publish_xml *pxml; 45 char *session_id; 46 long long serial; 47 int version; 48 enum delta_scope scope; 49}; 50 51static void 52start_delta_elem(struct delta_xml *dxml, const char **attr) 53{ 54 XML_Parser p = dxml->parser; 55 int has_xmlns = 0; 56 int i; 57 58 if (dxml->scope != DELTA_SCOPE_NONE) 59 PARSE_FAIL(p, 60 "parse failed - entered delta elem unexpectedely"); 61 for (i = 0; attr[i]; i += 2) { 62 const char *errstr; 63 if (strcmp("xmlns", attr[i]) == 0 && 64 strcmp(RRDP_XMLNS, attr[i + 1]) == 0) { 65 has_xmlns = 1; 66 continue; 67 } 68 if (strcmp("version", attr[i]) == 0) { 69 dxml->version = strtonum(attr[i + 1], 70 1, MAX_VERSION, &errstr); 71 if (errstr == NULL) 72 continue; 73 } 74 if (strcmp("session_id", attr[i]) == 0 && 75 valid_uuid(attr[i + 1])) { 76 dxml->session_id = xstrdup(attr[i + 1]); 77 continue; 78 } 79 if (strcmp("serial", attr[i]) == 0) { 80 dxml->serial = strtonum(attr[i + 1], 81 1, LLONG_MAX, &errstr); 82 if (errstr == NULL) 83 continue; 84 } 85 PARSE_FAIL(p, "parse failed - non conforming " 86 "attribute '%s' found in delta elem", attr[i]); 87 } 88 if (!(has_xmlns && dxml->version && dxml->session_id && dxml->serial)) 89 PARSE_FAIL(p, "parse failed - incomplete delta attributes"); 90 if (strcmp(dxml->current->session_id, dxml->session_id) != 0) 91 PARSE_FAIL(p, "parse failed - session_id mismatch"); 92 if (dxml->current->serial != dxml->serial) 93 PARSE_FAIL(p, "parse failed - serial mismatch"); 94 95 dxml->scope = DELTA_SCOPE_EMPTY_DELTA; 96} 97 98static void 99end_delta_elem(struct delta_xml *dxml) 100{ 101 XML_Parser p = dxml->parser; 102 103 if (dxml->scope == DELTA_SCOPE_EMPTY_DELTA) 104 PARSE_FAIL(p, "parse failed - empty delta"); 105 if (dxml->scope != DELTA_SCOPE_DELTA) 106 PARSE_FAIL(p, "parse failed - exited delta " 107 "elem unexpectedely"); 108 dxml->scope = DELTA_SCOPE_END; 109} 110 111static void 112start_publish_withdraw_elem(struct delta_xml *dxml, const char **attr, 113 int withdraw) 114{ 115 XML_Parser p = dxml->parser; 116 char *uri = NULL, hash[SHA256_DIGEST_LENGTH]; 117 int i, hasUri = 0, hasHash = 0; 118 enum publish_type pub = PUB_UPD; 119 120 if (dxml->scope != DELTA_SCOPE_EMPTY_DELTA && 121 dxml->scope != DELTA_SCOPE_DELTA) 122 PARSE_FAIL(p, "parse failed - entered publish/withdraw " 123 "elem unexpectedely"); 124 for (i = 0; attr[i]; i += 2) { 125 if (strcmp("uri", attr[i]) == 0 && hasUri++ == 0) { 126 if (valid_uri(attr[i + 1], strlen(attr[i + 1]), 127 RSYNC_PROTO)) { 128 uri = xstrdup(attr[i + 1]); 129 continue; 130 } 131 } 132 if (strcmp("hash", attr[i]) == 0 && hasHash++ == 0) { 133 if (hex_decode(attr[i + 1], hash, sizeof(hash)) == 0) 134 continue; 135 } 136 PARSE_FAIL(p, "parse failed - non conforming " 137 "attribute '%s' found in publish/withdraw elem", attr[i]); 138 } 139 if (hasUri != 1) 140 PARSE_FAIL(p, 141 "parse failed - incomplete publish/withdraw attributes"); 142 if (withdraw && hasHash != 1) 143 PARSE_FAIL(p, "parse failed - incomplete withdraw attributes"); 144 145 if (withdraw) 146 pub = PUB_DEL; 147 else if (hasHash == 0) 148 pub = PUB_ADD; 149 dxml->pxml = new_publish_xml(pub, uri, hash, 150 hasHash ? sizeof(hash) : 0); 151 dxml->scope = DELTA_SCOPE_PUBLISH; 152} 153 154static void 155end_publish_withdraw_elem(struct delta_xml *dxml, int withdraw) 156{ 157 XML_Parser p = dxml->parser; 158 159 if (dxml->scope != DELTA_SCOPE_PUBLISH) 160 PARSE_FAIL(p, "parse failed - exited publish/withdraw " 161 "elem unexpectedely"); 162 163 if (publish_done(dxml->rrdp, dxml->pxml) != 0) 164 PARSE_FAIL(p, "parse failed - bad publish/withdraw elem"); 165 dxml->pxml = NULL; 166 167 dxml->scope = DELTA_SCOPE_DELTA; 168} 169 170static void 171delta_xml_elem_start(void *data, const char *el, const char **attr) 172{ 173 struct delta_xml *dxml = data; 174 XML_Parser p = dxml->parser; 175 176 /* 177 * Can only enter here once as we should have no ways to get back to 178 * NONE scope 179 */ 180 if (strcmp("delta", el) == 0) 181 start_delta_elem(dxml, attr); 182 /* 183 * Will enter here multiple times, BUT never nested. will start 184 * collecting character data in that handler 185 * mem is cleared in end block, (TODO or on parse failure) 186 */ 187 else if (strcmp("publish", el) == 0) 188 start_publish_withdraw_elem(dxml, attr, 0); 189 else if (strcmp("withdraw", el) == 0) 190 start_publish_withdraw_elem(dxml, attr, 1); 191 else 192 PARSE_FAIL(p, "parse failed - unexpected elem exit found"); 193} 194 195static void 196delta_xml_elem_end(void *data, const char *el) 197{ 198 struct delta_xml *dxml = data; 199 XML_Parser p = dxml->parser; 200 201 if (strcmp("delta", el) == 0) 202 end_delta_elem(dxml); 203 /* 204 * TODO does this allow <publish></withdraw> or is that caught by basic 205 * xml parsing 206 */ 207 else if (strcmp("publish", el) == 0) 208 end_publish_withdraw_elem(dxml, 0); 209 else if (strcmp("withdraw", el) == 0) 210 end_publish_withdraw_elem(dxml, 1); 211 else 212 PARSE_FAIL(p, "parse failed - unexpected elem exit found"); 213} 214 215static void 216delta_content_handler(void *data, const char *content, int length) 217{ 218 struct delta_xml *dxml = data; 219 XML_Parser p = dxml->parser; 220 221 if (dxml->scope == DELTA_SCOPE_PUBLISH) 222 if (publish_add_content(dxml->pxml, content, length) == -1) 223 PARSE_FAIL(p, "parse failed, delta element for %s too " 224 "big", dxml->pxml->uri); 225} 226 227static void 228delta_doctype_handler(void *data, const char *doctypeName, 229 const char *sysid, const char *pubid, int subset) 230{ 231 struct delta_xml *dxml = data; 232 XML_Parser p = dxml->parser; 233 234 PARSE_FAIL(p, "parse failed - DOCTYPE not allowed"); 235} 236 237struct delta_xml * 238new_delta_xml(XML_Parser p, struct rrdp_session *rs, struct rrdp *r) 239{ 240 struct delta_xml *dxml; 241 242 if ((dxml = calloc(1, sizeof(*dxml))) == NULL) 243 err(1, "%s", __func__); 244 dxml->parser = p; 245 dxml->current = rs; 246 dxml->rrdp = r; 247 248 if (XML_ParserReset(dxml->parser, "US-ASCII") != XML_TRUE) 249 errx(1, "%s: XML_ParserReset failed", __func__); 250 251 XML_SetElementHandler(dxml->parser, delta_xml_elem_start, 252 delta_xml_elem_end); 253 XML_SetCharacterDataHandler(dxml->parser, delta_content_handler); 254 XML_SetUserData(dxml->parser, dxml); 255 XML_SetDoctypeDeclHandler(dxml->parser, delta_doctype_handler, NULL); 256 257 return dxml; 258} 259 260void 261free_delta_xml(struct delta_xml *dxml) 262{ 263 if (dxml == NULL) 264 return; 265 266 free(dxml->session_id); 267 free_publish_xml(dxml->pxml); 268 free(dxml); 269} 270 271/* Used in regress. */ 272void 273log_delta_xml(struct delta_xml *dxml) 274{ 275 logx("version: %d", dxml->version); 276 logx("session_id: %s serial: %lld", dxml->session_id, dxml->serial); 277} 278