1 /* Copyright (c) 2016-2017, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
6 * \brief Common code to parse and validate various type of descriptors.
9 #include "parsecommon.h"
11 #include "util_format.h"
13 #define MIN_ANNOTATION A_PURPOSE
14 #define MAX_ANNOTATION A_UNKNOWN_
16 #define ALLOC_ZERO(sz) memarea_alloc_zero(area,sz)
17 #define ALLOC(sz) memarea_alloc(area,sz)
18 #define STRDUP(str) memarea_strdup(area,str)
19 #define STRNDUP(str,n) memarea_strndup(area,(str),(n))
21 #define RET_ERR(msg) \
23 if (tok) token_clear(tok); \
24 tok = ALLOC_ZERO(sizeof(directory_token_t)); \
26 tok->error = STRDUP(msg); \
27 goto done_tokenizing; \
30 /** Free all resources allocated for <b>tok</b> */
32 token_clear(directory_token_t
*tok
)
35 crypto_pk_free(tok
->key
);
38 /** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
39 * them to <b>out</b>. Parse according to the token rules in <b>table</b>.
40 * Caller must free tokens in <b>out</b>. If <b>end</b> is NULL, use the
44 tokenize_string(memarea_t
*area
,
45 const char *start
, const char *end
, smartlist_t
*out
,
46 token_rule_t
*table
, int flags
)
49 directory_token_t
*tok
= NULL
;
52 int first_nonannotation
;
53 int prev_len
= smartlist_len(out
);
58 end
= start
+strlen(start
);
60 /* it's only meaningful to check for nuls if we got an end-of-string ptr */
61 if (memchr(start
, '\0', end
-start
)) {
62 log_warn(LD_DIR
, "parse error: internal NUL character.");
66 for (i
= 0; i
< NIL_
; ++i
)
69 SMARTLIST_FOREACH(out
, const directory_token_t
*, t
, ++counts
[t
->tp
]);
71 while (*s
< end
&& (!tok
|| tok
->tp
!= EOF_
)) {
72 tok
= get_next_token(area
, s
, end
, table
);
73 if (tok
->tp
== ERR_
) {
74 log_warn(LD_DIR
, "parse error: %s", tok
->error
);
79 smartlist_add(out
, tok
);
80 *s
= eat_whitespace_eos(*s
, end
);
83 if (flags
& TS_NOCHECK
)
86 if ((flags
& TS_ANNOTATIONS_OK
)) {
87 first_nonannotation
= -1;
88 for (i
= 0; i
< smartlist_len(out
); ++i
) {
89 tok
= smartlist_get(out
, i
);
90 if (tok
->tp
< MIN_ANNOTATION
|| tok
->tp
> MAX_ANNOTATION
) {
91 first_nonannotation
= i
;
95 if (first_nonannotation
< 0) {
96 log_warn(LD_DIR
, "parse error: item contains only annotations");
99 for (i
=first_nonannotation
; i
< smartlist_len(out
); ++i
) {
100 tok
= smartlist_get(out
, i
);
101 if (tok
->tp
>= MIN_ANNOTATION
&& tok
->tp
<= MAX_ANNOTATION
) {
102 log_warn(LD_DIR
, "parse error: Annotations mixed with keywords");
106 if ((flags
& TS_NO_NEW_ANNOTATIONS
)) {
107 if (first_nonannotation
!= prev_len
) {
108 log_warn(LD_DIR
, "parse error: Unexpected annotations.");
113 for (i
=0; i
< smartlist_len(out
); ++i
) {
114 tok
= smartlist_get(out
, i
);
115 if (tok
->tp
>= MIN_ANNOTATION
&& tok
->tp
<= MAX_ANNOTATION
) {
116 log_warn(LD_DIR
, "parse error: no annotations allowed.");
120 first_nonannotation
= 0;
122 for (i
= 0; table
[i
].t
; ++i
) {
123 if (counts
[table
[i
].v
] < table
[i
].min_cnt
) {
124 log_warn(LD_DIR
, "Parse error: missing %s element.", table
[i
].t
);
127 if (counts
[table
[i
].v
] > table
[i
].max_cnt
) {
128 log_warn(LD_DIR
, "Parse error: too many %s elements.", table
[i
].t
);
131 if (table
[i
].pos
& AT_START
) {
132 if (smartlist_len(out
) < 1 ||
133 (tok
= smartlist_get(out
, first_nonannotation
))->tp
!= table
[i
].v
) {
134 log_warn(LD_DIR
, "Parse error: first item is not %s.", table
[i
].t
);
138 if (table
[i
].pos
& AT_END
) {
139 if (smartlist_len(out
) < 1 ||
140 (tok
= smartlist_get(out
, smartlist_len(out
)-1))->tp
!= table
[i
].v
) {
141 log_warn(LD_DIR
, "Parse error: last item is not %s.", table
[i
].t
);
149 /** Helper: parse space-separated arguments from the string <b>s</b> ending at
150 * <b>eol</b>, and store them in the args field of <b>tok</b>. Store the
151 * number of parsed elements into the n_args field of <b>tok</b>. Allocate
152 * all storage in <b>area</b>. Return the number of arguments parsed, or
153 * return -1 if there was an insanely high number of arguments. */
155 get_token_arguments(memarea_t
*area
, directory_token_t
*tok
,
156 const char *s
, const char *eol
)
158 /** Largest number of arguments we'll accept to any token, ever. */
160 char *mem
= memarea_strndup(area
, s
, eol
-s
);
163 char *args
[MAX_ARGS
];
164 memset(args
, 0, sizeof(args
));
169 cp
= (char*)find_whitespace(cp
);
171 break; /* End of the line. */
173 cp
= (char*)eat_whitespace(cp
);
176 tok
->args
= memarea_memdup(area
, args
, j
*sizeof(char*));
181 /** Helper: make sure that the token <b>tok</b> with keyword <b>kwd</b> obeys
182 * the object syntax of <b>o_syn</b>. Allocate all storage in <b>area</b>.
183 * Return <b>tok</b> on success, or a new ERR_ token if the token didn't
184 * conform to the syntax we wanted.
186 static inline directory_token_t
*
187 token_check_object(memarea_t
*area
, const char *kwd
,
188 directory_token_t
*tok
, obj_syntax o_syn
)
193 /* No object is allowed for this token. */
194 if (tok
->object_body
) {
195 tor_snprintf(ebuf
, sizeof(ebuf
), "Unexpected object for %s", kwd
);
199 tor_snprintf(ebuf
, sizeof(ebuf
), "Unexpected public key for %s", kwd
);
204 /* There must be a (non-key) object. */
205 if (!tok
->object_body
) {
206 tor_snprintf(ebuf
, sizeof(ebuf
), "Missing object for %s", kwd
);
210 case NEED_KEY_1024
: /* There must be a 1024-bit public key. */
211 case NEED_SKEY_1024
: /* There must be a 1024-bit private key. */
212 if (tok
->key
&& crypto_pk_num_bits(tok
->key
) != PK_BYTES
*8) {
213 tor_snprintf(ebuf
, sizeof(ebuf
), "Wrong size on key for %s: %d bits",
214 kwd
, crypto_pk_num_bits(tok
->key
));
218 case NEED_KEY
: /* There must be some kind of key. */
220 tor_snprintf(ebuf
, sizeof(ebuf
), "Missing public key for %s", kwd
);
223 if (o_syn
!= NEED_SKEY_1024
) {
224 if (crypto_pk_key_is_private(tok
->key
)) {
225 tor_snprintf(ebuf
, sizeof(ebuf
),
226 "Private key given for %s, which wants a public key", kwd
);
229 } else { /* o_syn == NEED_SKEY_1024 */
230 if (!crypto_pk_key_is_private(tok
->key
)) {
231 tor_snprintf(ebuf
, sizeof(ebuf
),
232 "Public key given for %s, which wants a private key", kwd
);
238 /* Anything goes with this token. */
246 /** Helper function: read the next token from *s, advance *s to the end of the
247 * token, and return the parsed token. Parse *<b>s</b> according to the list
248 * of tokens in <b>table</b>.
251 get_next_token(memarea_t
*area
,
252 const char **s
, const char *eos
, token_rule_t
*table
)
254 /** Reject any object at least this big; it is probably an overflow, an
255 * attack, a bug, or some other nonsense. */
256 #define MAX_UNPARSED_OBJECT_SIZE (128*1024)
257 /** Reject any line at least this big; it is probably an overflow, an
258 * attack, a bug, or some other nonsense. */
259 #define MAX_LINE_LENGTH (128*1024)
261 const char *next
, *eol
, *obstart
;
264 directory_token_t
*tok
;
265 obj_syntax o_syn
= NO_OBJ
;
267 const char *kwd
= "";
270 tok
= ALLOC_ZERO(sizeof(directory_token_t
));
273 /* Set *s to first token, eol to end-of-line, next to after first token */
274 *s
= eat_whitespace_eos(*s
, eos
); /* eat multi-line whitespace */
275 tor_assert(eos
>= *s
);
276 eol
= memchr(*s
, '\n', eos
-*s
);
279 if (eol
- *s
> MAX_LINE_LENGTH
) {
280 RET_ERR("Line far too long");
283 next
= find_whitespace_eos(*s
, eol
);
285 if (!strcmp_len(*s
, "opt", next
-*s
)) {
286 /* Skip past an "opt" at the start of the line. */
287 *s
= eat_whitespace_eos_no_nl(next
, eol
);
288 next
= find_whitespace_eos(*s
, eol
);
289 } else if (*s
== eos
) { /* If no "opt", and end-of-line, line is invalid */
290 RET_ERR("Unexpected EOF");
293 /* Search the table for the appropriate entry. (I tried a binary search
294 * instead, but it wasn't any faster.) */
295 for (i
= 0; table
[i
].t
; ++i
) {
296 if (!strcmp_len(*s
, table
[i
].t
, next
-*s
)) {
297 /* We've found the keyword. */
299 tok
->tp
= table
[i
].v
;
301 *s
= eat_whitespace_eos_no_nl(next
, eol
);
302 /* We go ahead whether there are arguments or not, so that tok->args is
303 * always set if we want arguments. */
304 if (table
[i
].concat_args
) {
305 /* The keyword takes the line as a single argument */
306 tok
->args
= ALLOC(sizeof(char*));
307 tok
->args
[0] = STRNDUP(*s
,eol
-*s
); /* Grab everything on line */
310 /* This keyword takes multiple arguments. */
311 if (get_token_arguments(area
, tok
, *s
, eol
)<0) {
312 tor_snprintf(ebuf
, sizeof(ebuf
),"Far too many arguments to %s", kwd
);
317 if (tok
->n_args
< table
[i
].min_args
) {
318 tor_snprintf(ebuf
, sizeof(ebuf
), "Too few arguments to %s", kwd
);
320 } else if (tok
->n_args
> table
[i
].max_args
) {
321 tor_snprintf(ebuf
, sizeof(ebuf
), "Too many arguments to %s", kwd
);
328 if (tok
->tp
== ERR_
) {
329 /* No keyword matched; call it an "K_opt" or "A_unrecognized" */
330 if (*s
< eol
&& **s
== '@')
331 tok
->tp
= A_UNKNOWN_
;
334 tok
->args
= ALLOC(sizeof(char*));
335 tok
->args
[0] = STRNDUP(*s
, eol
-*s
);
340 /* Check whether there's an object present */
341 *s
= eat_whitespace_eos(eol
, eos
); /* Scan from end of first line */
342 tor_assert(eos
>= *s
);
343 eol
= memchr(*s
, '\n', eos
-*s
);
344 if (!eol
|| eol
-*s
<11 || strcmpstart(*s
, "-----BEGIN ")) /* No object. */
347 obstart
= *s
; /* Set obstart to start of object spec */
348 if (*s
+16 >= eol
|| memchr(*s
+11,'\0',eol
-*s
-16) || /* no short lines, */
349 strcmp_len(eol
-5, "-----", 5) || /* nuls or invalid endings */
350 (eol
-*s
) > MAX_UNPARSED_OBJECT_SIZE
) { /* name too long */
351 RET_ERR("Malformed object: bad begin line");
353 tok
->object_type
= STRNDUP(*s
+11, eol
-*s
-16);
354 obname_len
= eol
-*s
-16; /* store objname length here to avoid a strlen() */
355 *s
= eol
+1; /* Set *s to possible start of object data (could be eos) */
357 /* Go to the end of the object */
358 next
= tor_memstr(*s
, eos
-*s
, "-----END ");
360 RET_ERR("Malformed object: missing object end line");
362 tor_assert(eos
>= next
);
363 eol
= memchr(next
, '\n', eos
-next
);
364 if (!eol
) /* end-of-line marker, or eos if there's no '\n' */
366 /* Validate the ending tag, which should be 9 + NAME + 5 + eol */
367 if ((size_t)(eol
-next
) != 9+obname_len
+5 ||
368 strcmp_len(next
+9, tok
->object_type
, obname_len
) ||
369 strcmp_len(eol
-5, "-----", 5)) {
370 tor_snprintf(ebuf
, sizeof(ebuf
), "Malformed object: mismatched end tag %s",
372 ebuf
[sizeof(ebuf
)-1] = '\0';
375 if (next
- *s
> MAX_UNPARSED_OBJECT_SIZE
)
376 RET_ERR("Couldn't parse object: missing footer or object much too big.");
378 if (!strcmp(tok
->object_type
, "RSA PUBLIC KEY")) { /* If it's a public key */
379 tok
->key
= crypto_pk_new();
380 if (crypto_pk_read_public_key_from_string(tok
->key
, obstart
, eol
-obstart
))
381 RET_ERR("Couldn't parse public key.");
382 } else if (!strcmp(tok
->object_type
, "RSA PRIVATE KEY")) { /* private key */
383 tok
->key
= crypto_pk_new();
384 if (crypto_pk_read_private_key_from_string(tok
->key
, obstart
, eol
-obstart
))
385 RET_ERR("Couldn't parse private key.");
386 } else { /* If it's something else, try to base64-decode it */
388 tok
->object_body
= ALLOC(next
-*s
); /* really, this is too much RAM. */
389 r
= base64_decode(tok
->object_body
, next
-*s
, *s
, next
-*s
);
391 RET_ERR("Malformed object: bad base64-encoded data");
392 tok
->object_size
= r
;
397 tok
= token_check_object(area
, kwd
, tok
, o_syn
);
409 /** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; fail
410 * with an assert if no such keyword is found.
413 find_by_keyword_(smartlist_t
*s
, directory_keyword keyword
,
414 const char *keyword_as_string
)
416 directory_token_t
*tok
= find_opt_by_keyword(s
, keyword
);
417 if (PREDICT_UNLIKELY(!tok
)) {
418 log_err(LD_BUG
, "Missing %s [%d] in directory object that should have "
419 "been validated. Internal error.", keyword_as_string
, (int)keyword
);
425 /** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
426 * NULL if no such keyword is found.
429 find_opt_by_keyword(smartlist_t
*s
, directory_keyword keyword
)
431 SMARTLIST_FOREACH(s
, directory_token_t
*, t
, if (t
->tp
== keyword
) return t
);
435 /** If there are any directory_token_t entries in <b>s</b> whose keyword is
436 * <b>k</b>, return a newly allocated smartlist_t containing all such entries,
437 * in the same order in which they occur in <b>s</b>. Otherwise return
440 find_all_by_keyword(const smartlist_t
*s
, directory_keyword k
)
442 smartlist_t
*out
= NULL
;
443 SMARTLIST_FOREACH(s
, directory_token_t
*, t
,
446 out
= smartlist_new();
447 smartlist_add(out
, t
);