jcs's openbsd hax
openbsd
at jcs 419 lines 13 kB view raw
1/* 2 * format.h 3 * 4 * Copyright (c) 2022, NLnet Labs. All rights reserved. 5 * 6 * SPDX-License-Identifier: BSD-3-Clause 7 * 8 */ 9#ifndef FORMAT_H 10#define FORMAT_H 11 12#define FIELDS(fields) \ 13 { (sizeof(fields)/sizeof(fields[0])), fields } 14 15#define FIELD(name) \ 16 { { { name, sizeof(name) - 1 } } } 17 18#define ENTRY(name, fields) \ 19 { { { name, sizeof(name) - 1 }, 0 }, 0, false, false, fields, 0, 0 } 20 21nonnull_all 22static really_inline int32_t parse_type( 23 parser_t *parser, 24 const type_info_t *type, 25 const rdata_info_t *field, 26 rdata_t *rdata, 27 const token_t *token) 28{ 29 uint16_t code; 30 const mnemonic_t *mnemonic; 31 32 if (scan_type(token->data, token->length, &code, &mnemonic) != 1) 33 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 34 code = htobe16(code); 35 memcpy(rdata->octets, &code, 2); 36 rdata->octets += 2; 37 return 0; 38} 39 40nonnull_all 41static really_inline int32_t parse_name( 42 parser_t *parser, 43 const type_info_t *type, 44 const rdata_info_t *field, 45 rdata_t *rdata, 46 const token_t *token) 47{ 48 size_t length = 0; 49 50 assert(is_contiguous(token)); 51 52 // a freestanding "@" denotes the current origin 53 if (unlikely(token->length == 1 && token->data[0] == '@')) 54 goto relative; 55 switch (scan_name(token->data, token->length, rdata->octets, &length)) { 56 case 0: 57 rdata->octets += length; 58 return 0; 59 case 1: 60 goto relative; 61 } 62 63 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 64 65relative: 66 if (length > 255 - parser->file->origin.length) 67 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 68 memcpy(rdata->octets + length, parser->file->origin.octets, parser->file->origin.length); 69 rdata->octets += length + parser->file->origin.length; 70 return 0; 71} 72 73nonnull_all 74static really_inline int32_t parse_owner( 75 parser_t *parser, 76 const type_info_t *type, 77 const rdata_info_t *field, 78 const token_t *token) 79{ 80 size_t length = 0; 81 uint8_t *octets = parser->file->owner.octets; 82 83 assert(is_contiguous(token)); 84 85 // a freestanding "@" denotes the origin 86 if (unlikely(token->length == 1 && token->data[0] == '@')) 87 goto relative; 88 switch (scan_name(token->data, token->length, octets, &length)) { 89 case 0: 90 parser->file->owner.length = length; 91 parser->owner = &parser->file->owner; 92 return 0; 93 case 1: 94 goto relative; 95 } 96 97 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 98 99relative: 100 if (length > 255 - parser->file->origin.length) 101 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(field), NAME(type)); 102 memcpy(octets+length, parser->file->origin.octets, parser->file->origin.length); 103 parser->file->owner.length = length + parser->file->origin.length; 104 parser->owner = &parser->file->owner; 105 return 0; 106} 107 108nonnull_all 109static really_inline int32_t parse_string( 110 parser_t *parser, 111 const type_info_t *type, 112 const rdata_info_t *field, 113 rdata_t *rdata, 114 const token_t *token) 115{ 116 if (rdata->limit == rdata->octets) 117 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(type), NAME(field)); 118 assert(rdata->limit > rdata->octets); 119 120 int32_t length; 121 uint8_t *octets = rdata->octets + 1; 122 const uint8_t *limit = rdata->limit; 123 124 if (rdata->limit - rdata->octets > (1 + 255)) 125 limit = rdata->octets + 1 + 255; 126 if ((length = scan_string(token->data, token->length, octets, limit)) == -1) 127 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(type), NAME(field)); 128 *rdata->octets = (uint8_t)length; 129 rdata->octets += 1u + (uint32_t)length; 130 return 0; 131} 132 133nonnull_all 134static really_inline int32_t parse_text( 135 parser_t *parser, 136 const type_info_t *type, 137 const rdata_info_t *field, 138 rdata_t *rdata, 139 const token_t *token) 140{ 141 int32_t length; 142 143 if ((length = scan_string(token->data, token->length, rdata->octets, rdata->limit)) == -1) 144 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(type), NAME(field)); 145 rdata->octets += (uint32_t)length; 146 return 0; 147} 148 149nonnull_all 150static really_inline int32_t parse_rr( 151 parser_t *parser, token_t *token) 152{ 153 static const rdata_info_t fields[] = { 154 FIELD("OWNER"), 155 FIELD("TYPE"), 156 FIELD("CLASS"), 157 FIELD("TTL") 158 }; 159 160 static const type_info_t rr = ENTRY("RR", FIELDS(fields)); 161 162 int32_t code; 163 const type_info_t *descriptor; 164 const mnemonic_t *mnemonic; 165 rdata_t rdata = { parser->rdata->octets, parser->rdata->octets + 65535 }; 166 167 parser->file->ttl = parser->file->default_ttl; 168 169 if ((uint8_t)token->data[0] - '0' < 10) { 170 parser->file->ttl = &parser->file->last_ttl; 171 if (!scan_ttl(token->data, token->length, parser->options.pretty_ttls, &parser->file->last_ttl)) 172 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[3]), NAME(&rr)); 173 if (parser->file->last_ttl & (1u << 31)) 174 SEMANTIC_ERROR(parser, "Invalid %s in %s", NAME(&fields[3]), NAME(&rr)); 175 goto class_or_type; 176 } else { 177 switch (scan_type_or_class(token->data, token->length, &parser->file->last_type, &mnemonic)) { 178 case 1: 179 goto rdata; 180 case 2: 181 parser->file->last_class = parser->file->last_type; 182 goto ttl_or_type; 183 default: 184 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[1]), NAME(&rr)); 185 } 186 } 187 188ttl_or_type: 189 if ((code = take_contiguous(parser, &rr, &fields[1], token)) < 0) 190 return code; 191 if ((uint8_t)token->data[0] - '0' < 10) { 192 parser->file->ttl = &parser->file->last_ttl; 193 if (!scan_ttl(token->data, token->length, parser->options.pretty_ttls, &parser->file->last_ttl)) 194 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[3]), NAME(&rr)); 195 if (parser->file->last_ttl & (1u << 31)) 196 SEMANTIC_ERROR(parser, "Invalid %s in %s", NAME(&fields[3]), NAME(&rr)); 197 goto type; 198 } else { 199 if (unlikely(scan_type(token->data, token->length, &parser->file->last_type, &mnemonic) != 1)) 200 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[1]), NAME(&rr)); 201 goto rdata; 202 } 203 204class_or_type: 205 if ((code = take_contiguous(parser, &rr, &fields[1], token)) < 0) 206 return code; 207 switch (scan_type_or_class(token->data, token->length, &parser->file->last_type, &mnemonic)) { 208 case 1: 209 goto rdata; 210 case 2: 211 parser->file->last_class = parser->file->last_type; 212 goto type; 213 default: 214 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[0]), NAME(&rr)); 215 } 216 217type: 218 if ((code = take_contiguous(parser, &rr, &fields[1], token)) < 0) 219 return code; 220 if (unlikely(scan_type(token->data, token->length, &parser->file->last_type, &mnemonic) != 1)) 221 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[1]), NAME(&rr)); 222 223rdata: 224 descriptor = (const type_info_t *)mnemonic; 225 226 // RFC3597 227 // parse generic rdata if rdata starts with "\\#" 228 take(parser, token); 229 if (likely(token->data[0] != '\\')) 230 return descriptor->parse(parser, descriptor, &rdata, token); 231 else if (is_contiguous(token) && strncmp(token->data, "\\#", token->length) == 0) 232 return parse_generic_rdata(parser, descriptor, &rdata, token); 233 else 234 return descriptor->parse(parser, descriptor, &rdata, token); 235} 236 237// RFC1035 section 5.1 238// $INCLUDE <file-name> [<domain-name>] [<comment>] 239nonnull_all 240static really_inline int32_t parse_dollar_include( 241 parser_t *parser, token_t *token) 242{ 243 static const rdata_info_t fields[] = { 244 FIELD("file-name"), 245 FIELD("domain-name") 246 }; 247 248 static const type_info_t include = ENTRY("$INCLUDE", FIELDS(fields)); 249 250 if (parser->options.no_includes) 251 NOT_PERMITTED(parser, "%s is disabled", NAME(&include)); 252 253 int32_t code; 254 file_t *file; 255 if ((code = take_quoted_or_contiguous(parser, &include, &fields[0], token)) < 0) 256 return code; 257 if ((code = zone_open_file(parser, token->data, token->length, &file)) < 0) 258 return code; 259 260 name_buffer_t name; 261 const name_buffer_t *origin = &parser->file->origin; 262 263 // $INCLUDE directive MAY specify an origin 264 take(parser, token); 265 if (is_contiguous(token)) { 266 if (scan_name(token->data, token->length, name.octets, &name.length) != 0) { 267 zone_close_file(parser, file); 268 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[1]), NAME(&include)); 269 } 270 origin = &name; 271 take(parser, token); 272 } 273 274 // store the current owner to restore later if necessary 275 file_t *includer; 276 includer = parser->file; 277 includer->owner = *parser->owner; 278 file->includer = includer; 279 file->owner = *origin; 280 file->origin = *origin; 281 file->last_type = 0; 282 file->last_class = includer->last_class; 283 file->last_ttl = includer->last_ttl; 284 file->line = 1; 285 286 if (!is_delimiter(token)) { 287 zone_close_file(parser, file); 288 return have_delimiter(parser, &include, token); 289 } 290 291 // check for recursive includes 292 for (uint32_t depth = 1; includer; depth++, includer = includer->includer) { 293 if (strcmp(includer->path, file->path) == 0) { 294 zone_error(parser, "Circular include in %s", file->name); 295 zone_close_file(parser, file); 296 return ZONE_SEMANTIC_ERROR; 297 } 298 if (depth > parser->options.include_limit) { 299 zone_error(parser, "Include %s nested too deeply", file->name); 300 zone_close_file(parser, file); 301 return ZONE_SEMANTIC_ERROR; 302 } 303 } 304 305 // signal $INCLUDE to application 306 if (parser->options.include.callback) { 307 code = parser->options.include.callback( 308 parser, file->name, file->path, parser->user_data); 309 if (code) { 310 zone_close_file(parser, file); 311 return code; 312 } 313 } 314 315 adjust_line_count(parser->file); 316 parser->file = file; 317 return 0; 318} 319 320// RFC1035 section 5.1 321// $ORIGIN <domain-name> [<comment>] 322nonnull_all 323static inline int32_t parse_dollar_origin( 324 parser_t *parser, token_t *token) 325{ 326 static const rdata_info_t fields[] = { FIELD("name") }; 327 static const type_info_t origin = ENTRY("$ORIGIN", FIELDS(fields)); 328 int32_t code; 329 330 if ((code = take_contiguous_or_quoted(parser, &origin, &fields[0], token)) < 0) 331 return code; 332 if (scan_name(token->data, token->length, parser->file->origin.octets, &parser->file->origin.length) != 0) 333 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[0]), NAME(&origin)); 334 if ((code = take_delimiter(parser, &origin, token)) < 0) 335 return code; 336 337 adjust_line_count(parser->file); 338 return code; 339} 340 341// RFC2308 section 4 342// $TTL <TTL> [<comment>] 343nonnull_all 344static really_inline int32_t parse_dollar_ttl( 345 parser_t *parser, token_t *token) 346{ 347 static const rdata_info_t fields[] = { FIELD("ttl") }; 348 static const type_info_t ttl = ENTRY("$TTL", FIELDS(fields)); 349 int32_t code; 350 351 if ((code = take_contiguous(parser, &ttl, &fields[0], token)) < 0) 352 return code; 353 if (!scan_ttl(token->data, token->length, parser->options.pretty_ttls, &parser->file->dollar_ttl)) 354 SYNTAX_ERROR(parser, "Invalid %s in %s", NAME(&fields[0]), NAME(&ttl)); 355 if (parser->file->dollar_ttl & (1u << 31)) 356 SEMANTIC_ERROR(parser, "Invalid %s in %s", NAME(&fields[0]), NAME(&ttl)); 357 if ((code = take_delimiter(parser, &ttl, token)) < 0) 358 return code; 359 360 parser->file->ttl = parser->file->default_ttl = &parser->file->dollar_ttl; 361 adjust_line_count(parser->file); 362 return 0; 363} 364 365static inline int32_t parse(parser_t *parser) 366{ 367 static const rdata_info_t fields[] = { FIELD("OWNER") }; 368 static const type_info_t rr = ENTRY("RR", FIELDS(fields)); 369 370 int32_t code = 0; 371 token_t token; 372 373 while (code >= 0) { 374 take(parser, &token); 375 if (likely(is_contiguous(&token))) { 376 if (likely(parser->file->start_of_line)) { 377 // control entry 378 if (unlikely(token.data[0] == '$')) { 379 if (token.length == 4 && memcmp(token.data, "$TTL", 4) == 0) 380 code = parse_dollar_ttl(parser, &token); 381 else if (token.length == 7 && memcmp(token.data, "$ORIGIN", 7) == 0) 382 code = parse_dollar_origin(parser, &token); 383 else if (token.length == 8 && memcmp(token.data, "$INCLUDE", 8) == 0) 384 code = parse_dollar_include(parser, &token); 385 else 386 SYNTAX_ERROR(parser, "Unknown control entry"); 387 continue; 388 } 389 390 if ((code = parse_owner(parser, &rr, &fields[0], &token)) < 0) 391 return code; 392 if ((code = take_contiguous(parser, &rr, &fields[0], &token)) < 0) 393 return code; 394 } else if (unlikely(!parser->owner->length)) { 395 SYNTAX_ERROR(parser, "No last stated owner"); 396 } 397 398 code = parse_rr(parser, &token); 399 } else if (is_end_of_file(&token)) { 400 if (parser->file->end_of_file == NO_MORE_DATA) { 401 if (!parser->file->includer) 402 break; 403 file_t *file = parser->file; 404 parser->file = parser->file->includer; 405 parser->owner = &parser->file->owner; 406 zone_close_file(parser, file); 407 } 408 } else if (is_line_feed(&token)) { 409 assert(token.code == LINE_FEED); 410 adjust_line_count(parser->file); 411 } else { 412 code = have_contiguous(parser, &rr, &fields[0], &token); 413 } 414 } 415 416 return code; 417} 418 419#endif // FORMAT_H