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