1 /* Copyright (c) 2001 Matej Pfajfar.
2 * Copyright (c) 2001-2004, Roger Dingledine.
3 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 * Copyright (c) 2007-2018, The Tor Project, Inc. */
5 /* See LICENSE for licensing information */
9 * \brief Code to parse and validate v2 hidden service descriptors.
12 #include "core/or/or.h"
13 #include "feature/dirparse/parsecommon.h"
14 #include "feature/dirparse/sigcommon.h"
15 #include "feature/rend/rendcommon.h"
16 #include "feature/rend/rendparse.h"
17 #include "lib/memarea/memarea.h"
19 #include "core/or/extend_info_st.h"
20 #include "feature/rend/rend_authorized_client_st.h"
21 #include "feature/rend/rend_intro_point_st.h"
22 #include "feature/rend/rend_service_descriptor_st.h"
24 /** List of tokens recognized in rendezvous service descriptors */
25 static token_rule_t desc_token_table
[] = {
26 T1_START("rendezvous-service-descriptor", R_RENDEZVOUS_SERVICE_DESCRIPTOR
,
28 T1("version", R_VERSION
, EQ(1), NO_OBJ
),
29 T1("permanent-key", R_PERMANENT_KEY
, NO_ARGS
, NEED_KEY_1024
),
30 T1("secret-id-part", R_SECRET_ID_PART
, EQ(1), NO_OBJ
),
31 T1("publication-time", R_PUBLICATION_TIME
, CONCAT_ARGS
, NO_OBJ
),
32 T1("protocol-versions", R_PROTOCOL_VERSIONS
, EQ(1), NO_OBJ
),
33 T01("introduction-points", R_INTRODUCTION_POINTS
, NO_ARGS
, NEED_OBJ
),
34 T1_END("signature", R_SIGNATURE
, NO_ARGS
, NEED_OBJ
),
38 /** List of tokens recognized in the (encrypted) list of introduction points of
39 * rendezvous service descriptors */
40 static token_rule_t ipo_token_table
[] = {
41 T1_START("introduction-point", R_IPO_IDENTIFIER
, EQ(1), NO_OBJ
),
42 T1("ip-address", R_IPO_IP_ADDRESS
, EQ(1), NO_OBJ
),
43 T1("onion-port", R_IPO_ONION_PORT
, EQ(1), NO_OBJ
),
44 T1("onion-key", R_IPO_ONION_KEY
, NO_ARGS
, NEED_KEY_1024
),
45 T1("service-key", R_IPO_SERVICE_KEY
, NO_ARGS
, NEED_KEY_1024
),
49 /** List of tokens recognized in the (possibly encrypted) list of introduction
50 * points of rendezvous service descriptors */
51 static token_rule_t client_keys_token_table
[] = {
52 T1_START("client-name", C_CLIENT_NAME
, CONCAT_ARGS
, NO_OBJ
),
53 T1("descriptor-cookie", C_DESCRIPTOR_COOKIE
, EQ(1), NO_OBJ
),
54 T01("client-key", C_CLIENT_KEY
, NO_ARGS
, NEED_SKEY_1024
),
58 /** Parse and validate the ASCII-encoded v2 descriptor in <b>desc</b>,
59 * write the parsed descriptor to the newly allocated *<b>parsed_out</b>, the
60 * binary descriptor ID of length DIGEST_LEN to <b>desc_id_out</b>, the
61 * encrypted introduction points to the newly allocated
62 * *<b>intro_points_encrypted_out</b>, their encrypted size to
63 * *<b>intro_points_encrypted_size_out</b>, the size of the encoded descriptor
64 * to *<b>encoded_size_out</b>, and a pointer to the possibly next
65 * descriptor to *<b>next_out</b>; return 0 for success (including validation)
68 * If <b>as_hsdir</b> is 1, we're parsing this as an HSDir, and we should
69 * be strict about time formats.
72 rend_parse_v2_service_descriptor(rend_service_descriptor_t
**parsed_out
,
74 char **intro_points_encrypted_out
,
75 size_t *intro_points_encrypted_size_out
,
76 size_t *encoded_size_out
,
77 const char **next_out
, const char *desc
,
80 rend_service_descriptor_t
*result
=
81 tor_malloc_zero(sizeof(rend_service_descriptor_t
));
82 char desc_hash
[DIGEST_LEN
];
84 smartlist_t
*tokens
= smartlist_new();
85 directory_token_t
*tok
;
86 char secret_id_part
[DIGEST_LEN
];
87 int i
, version
, num_ok
=1;
88 smartlist_t
*versions
;
89 char public_key_hash
[DIGEST_LEN
];
90 char test_desc_id
[DIGEST_LEN
];
91 memarea_t
*area
= NULL
;
92 const int strict_time_fmt
= as_hsdir
;
95 /* Check if desc starts correctly. */
96 if (strcmpstart(desc
, "rendezvous-service-descriptor ")) {
97 log_info(LD_REND
, "Descriptor does not start correctly.");
100 /* Compute descriptor hash for later validation. */
101 if (router_get_hash_impl(desc
, strlen(desc
), desc_hash
,
102 "rendezvous-service-descriptor ",
103 "\nsignature", '\n', DIGEST_SHA1
) < 0) {
104 log_warn(LD_REND
, "Couldn't compute descriptor hash.");
107 /* Determine end of string. */
108 eos
= strstr(desc
, "\nrendezvous-service-descriptor ");
110 eos
= desc
+ strlen(desc
);
114 if (eos
-desc
> REND_DESC_MAX_SIZE
) {
115 /* XXXX+ If we are parsing this descriptor as a server, this
116 * should be a protocol warning. */
117 log_warn(LD_REND
, "Descriptor length is %d which exceeds "
118 "maximum rendezvous descriptor size of %d bytes.",
119 (int)(eos
-desc
), REND_DESC_MAX_SIZE
);
122 /* Tokenize descriptor. */
123 area
= memarea_new();
124 if (tokenize_string(area
, desc
, eos
, tokens
, desc_token_table
, 0)) {
125 log_warn(LD_REND
, "Error tokenizing descriptor.");
128 /* Set next to next descriptor, if available. */
130 /* Set length of encoded descriptor. */
131 *encoded_size_out
= eos
- desc
;
132 /* Check min allowed length of token list. */
133 if (smartlist_len(tokens
) < 7) {
134 log_warn(LD_REND
, "Impossibly short descriptor.");
137 /* Parse base32-encoded descriptor ID. */
138 tok
= find_by_keyword(tokens
, R_RENDEZVOUS_SERVICE_DESCRIPTOR
);
139 tor_assert(tok
== smartlist_get(tokens
, 0));
140 tor_assert(tok
->n_args
== 1);
141 if (!rend_valid_descriptor_id(tok
->args
[0])) {
142 log_warn(LD_REND
, "Invalid descriptor ID: '%s'", tok
->args
[0]);
145 if (base32_decode(desc_id_out
, DIGEST_LEN
,
146 tok
->args
[0], REND_DESC_ID_V2_LEN_BASE32
) < 0) {
147 log_warn(LD_REND
, "Descriptor ID contains illegal characters: %s",
151 /* Parse descriptor version. */
152 tok
= find_by_keyword(tokens
, R_VERSION
);
153 tor_assert(tok
->n_args
== 1);
155 (int) tor_parse_long(tok
->args
[0], 10, 0, INT_MAX
, &num_ok
, NULL
);
156 if (result
->version
!= 2 || !num_ok
) {
157 /* If it's <2, it shouldn't be under this format. If the number
158 * is greater than 2, we bumped it because we broke backward
159 * compatibility. See how version numbers in our other formats
161 log_warn(LD_REND
, "Unrecognized descriptor version: %s",
162 escaped(tok
->args
[0]));
165 /* Parse public key. */
166 tok
= find_by_keyword(tokens
, R_PERMANENT_KEY
);
167 result
->pk
= tok
->key
;
168 tok
->key
= NULL
; /* Prevent free */
169 /* Parse secret ID part. */
170 tok
= find_by_keyword(tokens
, R_SECRET_ID_PART
);
171 tor_assert(tok
->n_args
== 1);
172 if (strlen(tok
->args
[0]) != REND_SECRET_ID_PART_LEN_BASE32
||
173 strspn(tok
->args
[0], BASE32_CHARS
) != REND_SECRET_ID_PART_LEN_BASE32
) {
174 log_warn(LD_REND
, "Invalid secret ID part: '%s'", tok
->args
[0]);
177 if (base32_decode(secret_id_part
, DIGEST_LEN
, tok
->args
[0], 32) < 0) {
178 log_warn(LD_REND
, "Secret ID part contains illegal characters: %s",
182 /* Parse publication time -- up-to-date check is done when storing the
184 tok
= find_by_keyword(tokens
, R_PUBLICATION_TIME
);
185 tor_assert(tok
->n_args
== 1);
186 if (parse_iso_time_(tok
->args
[0], &result
->timestamp
,
187 strict_time_fmt
, 0) < 0) {
188 log_warn(LD_REND
, "Invalid publication time: '%s'", tok
->args
[0]);
191 /* Parse protocol versions. */
192 tok
= find_by_keyword(tokens
, R_PROTOCOL_VERSIONS
);
193 tor_assert(tok
->n_args
== 1);
194 versions
= smartlist_new();
195 smartlist_split_string(versions
, tok
->args
[0], ",",
196 SPLIT_SKIP_SPACE
|SPLIT_IGNORE_BLANK
, 0);
197 for (i
= 0; i
< smartlist_len(versions
); i
++) {
198 version
= (int) tor_parse_long(smartlist_get(versions
, i
),
199 10, 0, INT_MAX
, &num_ok
, NULL
);
200 if (!num_ok
) /* It's a string; let's ignore it. */
202 if (version
>= REND_PROTOCOL_VERSION_BITMASK_WIDTH
)
203 /* Avoid undefined left-shift behaviour. */
205 result
->protocols
|= 1 << version
;
207 SMARTLIST_FOREACH(versions
, char *, cp
, tor_free(cp
));
208 smartlist_free(versions
);
209 /* Parse encrypted introduction points. Don't verify. */
210 tok
= find_opt_by_keyword(tokens
, R_INTRODUCTION_POINTS
);
212 if (strcmp(tok
->object_type
, "MESSAGE")) {
213 log_warn(LD_DIR
, "Bad object type: introduction points should be of "
217 *intro_points_encrypted_out
= tor_memdup(tok
->object_body
,
219 *intro_points_encrypted_size_out
= tok
->object_size
;
221 *intro_points_encrypted_out
= NULL
;
222 *intro_points_encrypted_size_out
= 0;
224 /* Parse and verify signature. */
225 tok
= find_by_keyword(tokens
, R_SIGNATURE
);
226 if (check_signature_token(desc_hash
, DIGEST_LEN
, tok
, result
->pk
, 0,
227 "v2 rendezvous service descriptor") < 0)
229 /* Verify that descriptor ID belongs to public key and secret ID part. */
230 if (crypto_pk_get_digest(result
->pk
, public_key_hash
) < 0) {
231 log_warn(LD_REND
, "Unable to compute rend descriptor public key digest");
234 rend_get_descriptor_id_bytes(test_desc_id
, public_key_hash
,
236 if (tor_memneq(desc_id_out
, test_desc_id
, DIGEST_LEN
)) {
237 log_warn(LD_REND
, "Parsed descriptor ID does not match "
238 "computed descriptor ID.");
243 rend_service_descriptor_free(result
);
247 SMARTLIST_FOREACH(tokens
, directory_token_t
*, t
, token_clear(t
));
248 smartlist_free(tokens
);
251 memarea_drop_all(area
);
252 *parsed_out
= result
;
258 /** Decrypt the encrypted introduction points in <b>ipos_encrypted</b> of
259 * length <b>ipos_encrypted_size</b> using <b>descriptor_cookie</b> and
260 * write the result to a newly allocated string that is pointed to by
261 * <b>ipos_decrypted</b> and its length to <b>ipos_decrypted_size</b>.
262 * Return 0 if decryption was successful and -1 otherwise. */
264 rend_decrypt_introduction_points(char **ipos_decrypted
,
265 size_t *ipos_decrypted_size
,
266 const char *descriptor_cookie
,
267 const char *ipos_encrypted
,
268 size_t ipos_encrypted_size
)
270 tor_assert(ipos_encrypted
);
271 tor_assert(descriptor_cookie
);
272 if (ipos_encrypted_size
< 2) {
273 log_warn(LD_REND
, "Size of encrypted introduction points is too "
277 if (ipos_encrypted
[0] == (int)REND_BASIC_AUTH
) {
278 char iv
[CIPHER_IV_LEN
], client_id
[REND_BASIC_AUTH_CLIENT_ID_LEN
],
279 session_key
[CIPHER_KEY_LEN
], *dec
;
280 int declen
, client_blocks
;
281 size_t pos
= 0, len
, client_entries_len
;
282 crypto_digest_t
*digest
;
283 crypto_cipher_t
*cipher
;
284 client_blocks
= (int) ipos_encrypted
[1];
285 client_entries_len
= client_blocks
* REND_BASIC_AUTH_CLIENT_MULTIPLE
*
286 REND_BASIC_AUTH_CLIENT_ENTRY_LEN
;
287 if (ipos_encrypted_size
< 2 + client_entries_len
+ CIPHER_IV_LEN
+ 1) {
288 log_warn(LD_REND
, "Size of encrypted introduction points is too "
292 memcpy(iv
, ipos_encrypted
+ 2 + client_entries_len
, CIPHER_IV_LEN
);
293 digest
= crypto_digest_new();
294 crypto_digest_add_bytes(digest
, descriptor_cookie
, REND_DESC_COOKIE_LEN
);
295 crypto_digest_add_bytes(digest
, iv
, CIPHER_IV_LEN
);
296 crypto_digest_get_digest(digest
, client_id
,
297 REND_BASIC_AUTH_CLIENT_ID_LEN
);
298 crypto_digest_free(digest
);
299 for (pos
= 2; pos
< 2 + client_entries_len
;
300 pos
+= REND_BASIC_AUTH_CLIENT_ENTRY_LEN
) {
301 if (tor_memeq(ipos_encrypted
+ pos
, client_id
,
302 REND_BASIC_AUTH_CLIENT_ID_LEN
)) {
303 /* Attempt to decrypt introduction points. */
304 cipher
= crypto_cipher_new(descriptor_cookie
);
305 if (crypto_cipher_decrypt(cipher
, session_key
, ipos_encrypted
306 + pos
+ REND_BASIC_AUTH_CLIENT_ID_LEN
,
307 CIPHER_KEY_LEN
) < 0) {
308 log_warn(LD_REND
, "Could not decrypt session key for client.");
309 crypto_cipher_free(cipher
);
312 crypto_cipher_free(cipher
);
314 len
= ipos_encrypted_size
- 2 - client_entries_len
- CIPHER_IV_LEN
;
315 dec
= tor_malloc_zero(len
+ 1);
316 declen
= crypto_cipher_decrypt_with_iv(session_key
, dec
, len
,
317 ipos_encrypted
+ 2 + client_entries_len
,
318 ipos_encrypted_size
- 2 - client_entries_len
);
321 log_warn(LD_REND
, "Could not decrypt introduction point string.");
325 if (fast_memcmpstart(dec
, declen
, "introduction-point ")) {
326 log_warn(LD_REND
, "Decrypted introduction points don't "
327 "look like we could parse them.");
331 *ipos_decrypted
= dec
;
332 *ipos_decrypted_size
= declen
;
336 log_warn(LD_REND
, "Could not decrypt introduction points. Please "
337 "check your authorization for this service!");
339 } else if (ipos_encrypted
[0] == (int)REND_STEALTH_AUTH
) {
342 if (ipos_encrypted_size
< CIPHER_IV_LEN
+ 2) {
343 log_warn(LD_REND
, "Size of encrypted introduction points is too "
347 dec
= tor_malloc_zero(ipos_encrypted_size
- CIPHER_IV_LEN
- 1 + 1);
349 declen
= crypto_cipher_decrypt_with_iv(descriptor_cookie
, dec
,
350 ipos_encrypted_size
-
353 ipos_encrypted_size
- 1);
356 log_warn(LD_REND
, "Decrypting introduction points failed!");
360 *ipos_decrypted
= dec
;
361 *ipos_decrypted_size
= declen
;
364 log_warn(LD_REND
, "Unknown authorization type number: %d",
370 /** Parse the encoded introduction points in <b>intro_points_encoded</b> of
371 * length <b>intro_points_encoded_size</b> and write the result to the
372 * descriptor in <b>parsed</b>; return the number of successfully parsed
373 * introduction points or -1 in case of a failure. */
375 rend_parse_introduction_points(rend_service_descriptor_t
*parsed
,
376 const char *intro_points_encoded
,
377 size_t intro_points_encoded_size
)
379 const char *current_ipo
, *end_of_intro_points
;
380 smartlist_t
*tokens
= NULL
;
381 directory_token_t
*tok
;
382 rend_intro_point_t
*intro
;
384 int result
, num_ok
=1;
385 memarea_t
*area
= NULL
;
387 /** Function may only be invoked once. */
388 tor_assert(!parsed
->intro_nodes
);
389 if (!intro_points_encoded
|| intro_points_encoded_size
== 0) {
390 log_warn(LD_REND
, "Empty or zero size introduction point list");
393 /* Consider one intro point after the other. */
394 current_ipo
= intro_points_encoded
;
395 end_of_intro_points
= intro_points_encoded
+ intro_points_encoded_size
;
396 tokens
= smartlist_new();
397 parsed
->intro_nodes
= smartlist_new();
398 area
= memarea_new();
400 while (!fast_memcmpstart(current_ipo
, end_of_intro_points
-current_ipo
,
401 "introduction-point ")) {
402 /* Determine end of string. */
403 const char *eos
= tor_memstr(current_ipo
, end_of_intro_points
-current_ipo
,
404 "\nintroduction-point ");
406 eos
= end_of_intro_points
;
409 tor_assert(eos
<= intro_points_encoded
+intro_points_encoded_size
);
410 /* Free tokens and clear token list. */
411 SMARTLIST_FOREACH(tokens
, directory_token_t
*, t
, token_clear(t
));
412 smartlist_clear(tokens
);
414 /* Tokenize string. */
415 if (tokenize_string(area
, current_ipo
, eos
, tokens
, ipo_token_table
, 0)) {
416 log_warn(LD_REND
, "Error tokenizing introduction point");
419 /* Advance to next introduction point, if available. */
421 /* Check minimum allowed length of introduction point. */
422 if (smartlist_len(tokens
) < 5) {
423 log_warn(LD_REND
, "Impossibly short introduction point.");
426 /* Allocate new intro point and extend info. */
427 intro
= tor_malloc_zero(sizeof(rend_intro_point_t
));
428 info
= intro
->extend_info
= tor_malloc_zero(sizeof(extend_info_t
));
429 /* Parse identifier. */
430 tok
= find_by_keyword(tokens
, R_IPO_IDENTIFIER
);
431 if (base32_decode(info
->identity_digest
, DIGEST_LEN
,
432 tok
->args
[0], REND_INTRO_POINT_ID_LEN_BASE32
) < 0) {
433 log_warn(LD_REND
, "Identity digest contains illegal characters: %s",
435 rend_intro_point_free(intro
);
438 /* Write identifier to nickname. */
439 info
->nickname
[0] = '$';
440 base16_encode(info
->nickname
+ 1, sizeof(info
->nickname
) - 1,
441 info
->identity_digest
, DIGEST_LEN
);
442 /* Parse IP address. */
443 tok
= find_by_keyword(tokens
, R_IPO_IP_ADDRESS
);
444 if (tor_addr_parse(&info
->addr
, tok
->args
[0])<0) {
445 log_warn(LD_REND
, "Could not parse introduction point address.");
446 rend_intro_point_free(intro
);
449 if (tor_addr_family(&info
->addr
) != AF_INET
) {
450 log_warn(LD_REND
, "Introduction point address was not ipv4.");
451 rend_intro_point_free(intro
);
455 /* Parse onion port. */
456 tok
= find_by_keyword(tokens
, R_IPO_ONION_PORT
);
457 info
->port
= (uint16_t) tor_parse_long(tok
->args
[0],10,1,65535,
459 if (!info
->port
|| !num_ok
) {
460 log_warn(LD_REND
, "Introduction point onion port %s is invalid",
461 escaped(tok
->args
[0]));
462 rend_intro_point_free(intro
);
465 /* Parse onion key. */
466 tok
= find_by_keyword(tokens
, R_IPO_ONION_KEY
);
467 if (!crypto_pk_public_exponent_ok(tok
->key
)) {
469 "Introduction point's onion key had invalid exponent.");
470 rend_intro_point_free(intro
);
473 info
->onion_key
= tok
->key
;
474 tok
->key
= NULL
; /* Prevent free */
475 /* Parse service key. */
476 tok
= find_by_keyword(tokens
, R_IPO_SERVICE_KEY
);
477 if (!crypto_pk_public_exponent_ok(tok
->key
)) {
479 "Introduction point key had invalid exponent.");
480 rend_intro_point_free(intro
);
483 intro
->intro_key
= tok
->key
;
484 tok
->key
= NULL
; /* Prevent free */
485 /* Add extend info to list of introduction points. */
486 smartlist_add(parsed
->intro_nodes
, intro
);
488 result
= smartlist_len(parsed
->intro_nodes
);
495 /* Free tokens and clear token list. */
497 SMARTLIST_FOREACH(tokens
, directory_token_t
*, t
, token_clear(t
));
498 smartlist_free(tokens
);
501 memarea_drop_all(area
);
506 /** Parse the content of a client_key file in <b>ckstr</b> and add
507 * rend_authorized_client_t's for each parsed client to
508 * <b>parsed_clients</b>. Return the number of parsed clients as result
509 * or -1 for failure. */
511 rend_parse_client_keys(strmap_t
*parsed_clients
, const char *ckstr
)
515 directory_token_t
*tok
;
516 const char *current_entry
= NULL
;
517 memarea_t
*area
= NULL
;
518 char *err_msg
= NULL
;
519 if (!ckstr
|| strlen(ckstr
) == 0)
521 tokens
= smartlist_new();
522 /* Begin parsing with first entry, skipping comments or whitespace at the
524 area
= memarea_new();
525 current_entry
= eat_whitespace(ckstr
);
526 while (!strcmpstart(current_entry
, "client-name ")) {
527 rend_authorized_client_t
*parsed_entry
;
528 /* Determine end of string. */
529 const char *eos
= strstr(current_entry
, "\nclient-name ");
531 eos
= current_entry
+ strlen(current_entry
);
534 /* Free tokens and clear token list. */
535 SMARTLIST_FOREACH(tokens
, directory_token_t
*, t
, token_clear(t
));
536 smartlist_clear(tokens
);
538 /* Tokenize string. */
539 if (tokenize_string(area
, current_entry
, eos
, tokens
,
540 client_keys_token_table
, 0)) {
541 log_warn(LD_REND
, "Error tokenizing client keys file.");
544 /* Advance to next entry, if available. */
546 /* Check minimum allowed length of token list. */
547 if (smartlist_len(tokens
) < 2) {
548 log_warn(LD_REND
, "Impossibly short client key entry.");
551 /* Parse client name. */
552 tok
= find_by_keyword(tokens
, C_CLIENT_NAME
);
553 tor_assert(tok
== smartlist_get(tokens
, 0));
554 tor_assert(tok
->n_args
== 1);
556 if (!rend_valid_client_name(tok
->args
[0])) {
557 log_warn(LD_CONFIG
, "Illegal client name: %s. (Length must be "
558 "between 1 and %d, and valid characters are "
559 "[A-Za-z0-9+-_].)", tok
->args
[0], REND_CLIENTNAME_MAX_LEN
);
562 /* Check if client name is duplicate. */
563 if (strmap_get(parsed_clients
, tok
->args
[0])) {
564 log_warn(LD_CONFIG
, "HiddenServiceAuthorizeClient contains a "
565 "duplicate client name: '%s'. Ignoring.", tok
->args
[0]);
568 parsed_entry
= tor_malloc_zero(sizeof(rend_authorized_client_t
));
569 parsed_entry
->client_name
= tor_strdup(tok
->args
[0]);
570 strmap_set(parsed_clients
, parsed_entry
->client_name
, parsed_entry
);
571 /* Parse client key. */
572 tok
= find_opt_by_keyword(tokens
, C_CLIENT_KEY
);
574 parsed_entry
->client_key
= tok
->key
;
575 tok
->key
= NULL
; /* Prevent free */
578 /* Parse descriptor cookie. */
579 tok
= find_by_keyword(tokens
, C_DESCRIPTOR_COOKIE
);
580 tor_assert(tok
->n_args
== 1);
581 if (rend_auth_decode_cookie(tok
->args
[0], parsed_entry
->descriptor_cookie
,
582 NULL
, &err_msg
) < 0) {
584 log_warn(LD_REND
, "%s", err_msg
);
589 result
= strmap_size(parsed_clients
);
594 /* Free tokens and clear token list. */
595 SMARTLIST_FOREACH(tokens
, directory_token_t
*, t
, token_clear(t
));
596 smartlist_free(tokens
);
598 memarea_drop_all(area
);