jcs's openbsd hax
openbsd
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