hs: Improve warning for bad service version
[tor.git] / src / feature / hs / hs_config.c
blobf8d71674ded1dbad06ee3dc17de9ff606fd325e5
1 /* Copyright (c) 2017-2020, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
4 /**
5 * \file hs_config.c
6 * \brief Implement hidden service configuration subsystem.
8 * \details
10 * This file has basically one main entry point: hs_config_service_all(). It
11 * takes the torrc options and configure hidden service from it. In validate
12 * mode, nothing is added to the global service list or keys are not generated
13 * nor loaded.
15 * A service is configured in two steps. It is first created using the tor
16 * options and then put in a staging list. It will stay there until
17 * hs_service_load_all_keys() is called. That function is responsible to
18 * load/generate the keys for the service in the staging list and if
19 * successful, transferred the service to the main global service list where
20 * at that point it is ready to be used.
22 * Configuration functions are per-version and there is a main generic one for
23 * every option that is common to all version (config_generic_service).
24 **/
26 #include "feature/hs/hs_common.h"
27 #include "feature/hs/hs_config.h"
28 #include "feature/hs/hs_client.h"
29 #include "feature/hs/hs_ob.h"
30 #include "feature/hs/hs_service.h"
31 #include "feature/rend/rendclient.h"
32 #include "feature/rend/rendservice.h"
33 #include "lib/encoding/confline.h"
34 #include "lib/conf/confdecl.h"
35 #include "lib/confmgt/confmgt.h"
37 #include "feature/hs/hs_opts_st.h"
38 #include "app/config/or_options_st.h"
40 /* Declare the table mapping hs options to hs_opts_t */
41 #define CONF_CONTEXT TABLE
42 #include "feature/hs/hs_options.inc"
43 #undef CONF_CONTEXT
45 /** Magic number for hs_opts_t. */
46 #define HS_OPTS_MAGIC 0x6f6e796e
48 static const config_format_t hs_opts_fmt = {
49 .size = sizeof(hs_opts_t),
50 .magic = { "hs_opts_t",
51 HS_OPTS_MAGIC,
52 offsetof(hs_opts_t, magic) },
53 .vars = hs_opts_t_vars,
56 /** Global configuration manager to handle HS sections*/
57 static config_mgr_t *hs_opts_mgr = NULL;
59 /**
60 * Return a configuration manager for the hs_opts_t configuration type.
61 **/
62 static const config_mgr_t *
63 get_hs_opts_mgr(void)
65 if (PREDICT_UNLIKELY(hs_opts_mgr == NULL)) {
66 hs_opts_mgr = config_mgr_new(&hs_opts_fmt);
67 config_mgr_freeze(hs_opts_mgr);
69 return hs_opts_mgr;
72 /**
73 * Allocate, initialize, and return a new hs_opts_t.
74 **/
75 static hs_opts_t *
76 hs_opts_new(void)
78 const config_mgr_t *mgr = get_hs_opts_mgr();
79 hs_opts_t *r = config_new(mgr);
80 tor_assert(r);
81 config_init(mgr, r);
82 return r;
85 /**
86 * Free an hs_opts_t.
87 **/
88 #define hs_opts_free(opts) \
89 config_free(get_hs_opts_mgr(), (opts))
91 /** Using the given list of services, stage them into our global state. Every
92 * service version are handled. This function can remove entries in the given
93 * service_list.
95 * Staging a service means that we take all services in service_list and we
96 * put them in the staging list (global) which acts as a temporary list that
97 * is used by the service loading key process. In other words, staging a
98 * service puts it in a list to be considered when loading the keys and then
99 * moved to the main global list. */
100 static void
101 stage_services(smartlist_t *service_list)
103 tor_assert(service_list);
105 /* This is v2 specific. Trigger service pruning which will make sure the
106 * just configured services end up in the main global list. It should only
107 * be done in non validation mode because v2 subsystem handles service
108 * object differently. */
109 rend_service_prune_list();
111 /* Cleanup v2 service from the list, we don't need those object anymore
112 * because we validated them all against the others and we want to stage
113 * only >= v3 service. And remember, v2 has a different object type which is
114 * shadow copied from an hs_service_t type. */
115 SMARTLIST_FOREACH_BEGIN(service_list, hs_service_t *, s) {
116 if (s->config.version == HS_VERSION_TWO) {
117 SMARTLIST_DEL_CURRENT(service_list, s);
118 hs_service_free(s);
120 } SMARTLIST_FOREACH_END(s);
122 /* This is >= v3 specific. Using the newly configured service list, stage
123 * them into our global state. Every object ownership is lost after. */
124 hs_service_stage_services(service_list);
127 /** Validate the given service against all service in the given list. If the
128 * service is ephemeral, this function ignores it. Services with the same
129 * directory path aren't allowed and will return an error. If a duplicate is
130 * found, 1 is returned else 0 if none found. */
131 static int
132 service_is_duplicate_in_list(const smartlist_t *service_list,
133 const hs_service_t *service)
135 int ret = 0;
137 tor_assert(service_list);
138 tor_assert(service);
140 /* Ephemeral service don't have a directory configured so no need to check
141 * for a service in the list having the same path. */
142 if (service->config.is_ephemeral) {
143 goto end;
146 /* XXX: Validate if we have any service that has the given service dir path.
147 * This has two problems:
149 * a) It's O(n^2), but the same comment from the bottom of
150 * rend_config_services() should apply.
152 * b) We only compare directory paths as strings, so we can't
153 * detect two distinct paths that specify the same directory
154 * (which can arise from symlinks, case-insensitivity, bind
155 * mounts, etc.).
157 * It also can't detect that two separate Tor instances are trying
158 * to use the same HiddenServiceDir; for that, we would need a
159 * lock file. But this is enough to detect a simple mistake that
160 * at least one person has actually made. */
161 SMARTLIST_FOREACH_BEGIN(service_list, const hs_service_t *, s) {
162 if (!strcmp(s->config.directory_path, service->config.directory_path)) {
163 log_warn(LD_REND, "Another hidden service is already configured "
164 "for directory %s",
165 escaped(service->config.directory_path));
166 ret = 1;
167 goto end;
169 } SMARTLIST_FOREACH_END(s);
171 end:
172 return ret;
175 /** Check whether an integer <b>i</b> is out of bounds (not between <b>low</b>
176 * and <b>high</b> incusive). If it is, then log a warning about the option
177 * <b>name</b>, and return true. Otherwise return false. */
178 static bool
179 check_value_oob(int i, const char *name, int low, int high)
181 if (i < low || i > high) {
182 if (low == high) {
183 log_warn(LD_CONFIG, "%s must be %d, not %d.", name, low, i);
184 } else {
185 log_warn(LD_CONFIG, "%s must be between %d and %d, not %d.",
186 name, low, high, i);
188 return true;
190 return false;
194 * Helper: check whether the integer value called <b>name</b> in <b>opts</b>
195 * is out-of-bounds.
197 #define CHECK_OOB(opts, name, low, high) \
198 check_value_oob((opts)->name, #name, (low), (high))
200 /** Helper function: Given a configuration option and its value, parse the
201 * value as a hs_circuit_id_protocol_t. On success, ok is set to 1 and ret is
202 * the parse value. On error, ok is set to 0 and the "none"
203 * hs_circuit_id_protocol_t is returned. This function logs on error. */
204 static hs_circuit_id_protocol_t
205 helper_parse_circuit_id_protocol(const char *key, const char *value, int *ok)
207 tor_assert(value);
208 tor_assert(ok);
210 hs_circuit_id_protocol_t ret = HS_CIRCUIT_ID_PROTOCOL_NONE;
211 *ok = 0;
213 if (! strcasecmp(value, "haproxy")) {
214 *ok = 1;
215 ret = HS_CIRCUIT_ID_PROTOCOL_HAPROXY;
216 } else if (! strcasecmp(value, "none")) {
217 *ok = 1;
218 ret = HS_CIRCUIT_ID_PROTOCOL_NONE;
219 } else {
220 log_warn(LD_CONFIG, "%s must be 'haproxy' or 'none'.", key);
221 goto err;
224 err:
225 return ret;
228 /** Return the service version by trying to learn it from the key on disk if
229 * any. If nothing is found, the current service configured version is
230 * returned. */
231 static int
232 config_learn_service_version(hs_service_t *service)
234 int version;
236 tor_assert(service);
238 version = hs_service_get_version_from_key(service);
239 if (version < 0) {
240 version = service->config.version;
243 return version;
247 * Header key indicating the start of a new hidden service configuration
248 * block.
250 static const char SECTION_HEADER[] = "HiddenServiceDir";
252 /** Return true iff the given options starting at line_ for a hidden service
253 * contains at least one invalid option. Each hidden service option don't
254 * apply to all versions so this function can find out. The line_ MUST start
255 * right after the HiddenServiceDir line of this service.
257 * This is mainly for usability so we can inform the user of any invalid
258 * option for the hidden service version instead of silently ignoring. */
259 static int
260 config_has_invalid_options(const config_line_t *line_,
261 const hs_service_t *service)
263 int ret = 0;
264 const char **optlist;
265 const config_line_t *line;
267 tor_assert(service);
268 tor_assert(service->config.version <= HS_VERSION_MAX);
270 /* List of options that a v3 service doesn't support thus must exclude from
271 * its configuration. */
272 const char *opts_exclude_v3[] = {
273 "HiddenServiceAuthorizeClient",
274 NULL /* End marker. */
277 const char *opts_exclude_v2[] = {
278 "HiddenServiceExportCircuitID",
279 "HiddenServiceEnableIntroDoSDefense",
280 "HiddenServiceEnableIntroDoSRatePerSec",
281 "HiddenServiceEnableIntroDoSBurstPerSec",
282 "HiddenServiceOnionBalanceInstance",
283 NULL /* End marker. */
286 /* Defining the size explicitly allows us to take advantage of the compiler
287 * which warns us if we ever bump the max version but forget to grow this
288 * array. The plus one is because we have a version 0 :). */
289 struct {
290 const char **list;
291 } exclude_lists[HS_VERSION_MAX + 1] = {
292 { NULL }, /* v0. */
293 { NULL }, /* v1. */
294 { opts_exclude_v2 }, /* v2 */
295 { opts_exclude_v3 }, /* v3. */
298 optlist = exclude_lists[service->config.version].list;
299 if (optlist == NULL) {
300 /* No exclude options to look at for this version. */
301 goto end;
303 for (int i = 0; optlist[i]; i++) {
304 const char *opt = optlist[i];
305 for (line = line_; line; line = line->next) {
306 if (!strcasecmp(line->key, SECTION_HEADER)) {
307 /* We just hit the next hidden service, stop right now.
308 * (This shouldn't be possible, now that we have partitioned the list
309 * into sections.) */
310 tor_assert_nonfatal_unreached();
311 goto end;
313 if (!strcasecmp(line->key, opt)) {
314 log_warn(LD_CONFIG, "Hidden service option %s is incompatible with "
315 "version %" PRIu32 " of service in %s",
316 opt, service->config.version,
317 service->config.directory_path);
319 if (!strcasecmp(line->key, "HiddenServiceAuthorizeClient")) {
320 /* Special case this v2 option so that we can offer alternatives.
321 * If more such special cases appear, it would be good to
322 * generalize the exception mechanism here. */
323 log_warn(LD_CONFIG, "For v3 onion service client authorization, "
324 "please read the 'CLIENT AUTHORIZATION' section in the "
325 "manual.");
328 ret = 1;
329 /* Continue the loop so we can find all possible options. */
330 continue;
334 end:
335 return ret;
338 /** Validate service configuration. This is used when loading the configuration
339 * and once we've setup a service object, it's config object is passed to this
340 * function for further validation. This does not validate service key
341 * material. Return 0 if valid else -1 if invalid. */
342 static int
343 config_validate_service(const hs_service_config_t *config)
345 tor_assert(config);
347 /* Amount of ports validation. */
348 if (!config->ports || smartlist_len(config->ports) == 0) {
349 log_warn(LD_CONFIG, "Hidden service (%s) with no ports configured.",
350 escaped(config->directory_path));
351 goto invalid;
354 /* DoS validation values. */
355 if (config->has_dos_defense_enabled &&
356 (config->intro_dos_burst_per_sec < config->intro_dos_rate_per_sec)) {
357 log_warn(LD_CONFIG, "Hidden service DoS defenses burst (%" PRIu32 ") can "
358 "not be smaller than the rate value (%" PRIu32 ").",
359 config->intro_dos_burst_per_sec, config->intro_dos_rate_per_sec);
360 goto invalid;
363 /* Valid. */
364 return 0;
365 invalid:
366 return -1;
369 /** Configuration function for a version 3 service. The given service
370 * object must be already allocated and passed through
371 * config_generic_service() prior to calling this function.
373 * Return 0 on success else a negative value. */
374 static int
375 config_service_v3(const hs_opts_t *hs_opts,
376 hs_service_config_t *config)
378 tor_assert(config);
379 tor_assert(hs_opts);
381 /* Number of introduction points. */
382 if (CHECK_OOB(hs_opts, HiddenServiceNumIntroductionPoints,
383 NUM_INTRO_POINTS_DEFAULT,
384 HS_CONFIG_V3_MAX_INTRO_POINTS)) {
385 goto err;
387 config->num_intro_points = hs_opts->HiddenServiceNumIntroductionPoints;
389 /* Circuit ID export setting. */
390 if (hs_opts->HiddenServiceExportCircuitID) {
391 int ok;
392 config->circuit_id_protocol =
393 helper_parse_circuit_id_protocol("HiddenServcieExportCircuitID",
394 hs_opts->HiddenServiceExportCircuitID,
395 &ok);
396 if (!ok) {
397 goto err;
401 /* Is the DoS defense enabled? */
402 config->has_dos_defense_enabled =
403 hs_opts->HiddenServiceEnableIntroDoSDefense;
405 /* Rate for DoS defense */
406 if (CHECK_OOB(hs_opts, HiddenServiceEnableIntroDoSRatePerSec,
407 HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN,
408 HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX)) {
409 goto err;
411 config->intro_dos_rate_per_sec =
412 hs_opts->HiddenServiceEnableIntroDoSRatePerSec;
413 log_info(LD_REND, "Service INTRO2 DoS defenses rate set to: %" PRIu32,
414 config->intro_dos_rate_per_sec);
416 if (CHECK_OOB(hs_opts, HiddenServiceEnableIntroDoSBurstPerSec,
417 HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN,
418 HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX)) {
419 goto err;
421 config->intro_dos_burst_per_sec =
422 hs_opts->HiddenServiceEnableIntroDoSBurstPerSec;
423 log_info(LD_REND, "Service INTRO2 DoS defenses burst set to: %" PRIu32,
424 config->intro_dos_burst_per_sec);
426 /* Is this an onionbalance instance? */
427 if (hs_opts->HiddenServiceOnionBalanceInstance) {
428 /* Option is enabled, parse config file. */
429 if (! hs_ob_parse_config_file(config)) {
430 goto err;
434 /* We do not load the key material for the service at this stage. This is
435 * done later once tor can confirm that it is in a running state. */
437 /* We are about to return a fully configured service so do one last pass of
438 * validation at it. */
439 if (config_validate_service(config) < 0) {
440 goto err;
443 return 0;
444 err:
445 return -1;
448 /** Configure a service using the given options in hs_opts and options. This is
449 * called for any service regardless of its version which means that all
450 * directives in this function are generic to any service version. This
451 * function will also check the validity of the service directory path.
453 * The line_ must be pointing to the directive directly after a
454 * HiddenServiceDir. That way, when hitting the next HiddenServiceDir line or
455 * reaching the end of the list of lines, we know that we have to stop looking
456 * for more options.
458 * Return 0 on success else -1. */
459 static int
460 config_generic_service(const hs_opts_t *hs_opts,
461 const or_options_t *options,
462 hs_service_t *service)
464 hs_service_config_t *config;
466 tor_assert(hs_opts);
467 tor_assert(options);
468 tor_assert(service);
470 /* Makes thing easier. */
471 config = &service->config;
473 /* Directory where the service's keys are stored. */
474 tor_assert(hs_opts->HiddenServiceDir);
475 config->directory_path = tor_strdup(hs_opts->HiddenServiceDir);
476 log_info(LD_CONFIG, "%s=%s. Configuring...",
477 SECTION_HEADER, escaped(config->directory_path));
479 /* Protocol version for the service. */
480 if (hs_opts->HiddenServiceVersion == -1) {
481 /* No value was set; stay with the default. */
482 } else if (CHECK_OOB(hs_opts, HiddenServiceVersion,
483 HS_VERSION_MIN, HS_VERSION_MAX)) {
484 goto err;
485 } else {
486 config->hs_version_explicitly_set = 1;
487 config->version = hs_opts->HiddenServiceVersion;
490 /* Virtual port. */
491 for (const config_line_t *portline = hs_opts->HiddenServicePort;
492 portline; portline = portline->next) {
493 char *err_msg = NULL;
494 /* XXX: Can we rename this? */
495 rend_service_port_config_t *portcfg =
496 rend_service_parse_port_config(portline->value, " ", &err_msg);
497 if (!portcfg) {
498 if (err_msg) {
499 log_warn(LD_CONFIG, "%s", err_msg);
501 tor_free(err_msg);
502 goto err;
504 tor_assert(!err_msg);
505 smartlist_add(config->ports, portcfg);
506 log_info(LD_CONFIG, "HiddenServicePort=%s for %s",
507 portline->value, escaped(config->directory_path));
510 /* Do we allow unknown ports? */
511 config->allow_unknown_ports = hs_opts->HiddenServiceAllowUnknownPorts;
513 /* Directory group readable. */
514 config->dir_group_readable = hs_opts->HiddenServiceDirGroupReadable;
516 /* Maximum streams per circuit. */
517 if (CHECK_OOB(hs_opts, HiddenServiceMaxStreams,
518 0, HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT)) {
519 goto err;
521 config->max_streams_per_rdv_circuit = hs_opts->HiddenServiceMaxStreams;
523 /* Maximum amount of streams before we close the circuit. */
524 config->max_streams_close_circuit =
525 hs_opts->HiddenServiceMaxStreamsCloseCircuit;
527 /* Check if we are configured in non anonymous mode meaning every service
528 * becomes a single onion service. */
529 if (rend_service_non_anonymous_mode_enabled(options)) {
530 config->is_single_onion = 1;
533 /* Success */
534 return 0;
535 err:
536 return -1;
539 /** Configure a service using the given line and options. This function will
540 * call the corresponding configuration function for a specific service
541 * version and validate the service against the other ones. On success, add
542 * the service to the given list and return 0. On error, nothing is added to
543 * the list and a negative value is returned. */
544 static int
545 config_service(config_line_t *line, const or_options_t *options,
546 smartlist_t *service_list)
548 int ret;
549 hs_service_t *service = NULL;
550 hs_opts_t *hs_opts = NULL;
551 char *msg = NULL;
553 tor_assert(line);
554 tor_assert(options);
555 tor_assert(service_list);
557 /* We have a new hidden service. */
558 service = hs_service_new(options);
560 /* Try to validate and parse the configuration lines into 'hs_opts' */
561 hs_opts = hs_opts_new();
562 ret = config_assign(get_hs_opts_mgr(), hs_opts, line, 0, &msg);
563 if (ret < 0) {
564 log_warn(LD_REND, "Can't parse configuration for onion service: %s", msg);
565 goto err;
567 tor_assert_nonfatal(msg == NULL);
568 validation_status_t vs = config_validate(get_hs_opts_mgr(), NULL,
569 hs_opts, &msg);
570 if (vs < 0) {
571 log_warn(LD_REND, "Bad configuration for onion service: %s", msg);
572 goto err;
574 tor_assert_nonfatal(msg == NULL);
576 /* We'll configure that service as a generic one and then pass it to a
577 * specific function according to the configured version number. */
578 if (config_generic_service(hs_opts, options, service) < 0) {
579 goto err;
582 tor_assert(service->config.version <= HS_VERSION_MAX);
584 /* Check permission on service directory that was just parsed. And this must
585 * be done regardless of the service version. Do not ask for the directory
586 * to be created, this is done when the keys are loaded because we could be
587 * in validation mode right now. */
588 if (hs_check_service_private_dir(options->User,
589 service->config.directory_path,
590 service->config.dir_group_readable,
591 0) < 0) {
592 goto err;
595 /* We'll try to learn the service version here by loading the key(s) if
596 * present and we did not set HiddenServiceVersion. Depending on the key
597 * format, we can figure out the service version. */
598 if (!service->config.hs_version_explicitly_set) {
599 service->config.version = config_learn_service_version(service);
602 /* We make sure that this set of options for a service are valid that is for
603 * instance an option only for v2 is not used for v3. */
604 if (config_has_invalid_options(line->next, service)) {
605 goto err;
608 /* Different functions are in charge of specific options for a version. We
609 * start just after the service directory line so once we hit another
610 * directory line, the function knows that it has to stop parsing. */
611 switch (service->config.version) {
612 case HS_VERSION_TWO:
613 ret = rend_config_service(hs_opts, options, &service->config);
614 break;
615 case HS_VERSION_THREE:
616 ret = config_service_v3(hs_opts, &service->config);
617 break;
618 default:
619 /* We do validate before if we support the parsed version. */
620 tor_assert_nonfatal_unreached();
621 goto err;
623 if (ret < 0) {
624 goto err;
627 /* We'll check if this service can be kept depending on the others
628 * configured previously. */
629 if (service_is_duplicate_in_list(service_list, service)) {
630 goto err;
633 /* Passes, add it to the given list. */
634 smartlist_add(service_list, service);
635 hs_opts_free(hs_opts);
637 return 0;
639 err:
640 hs_service_free(service);
641 hs_opts_free(hs_opts);
642 tor_free(msg);
643 return -1;
646 /** From a set of <b>options</b>, setup every hidden service found. Return 0 on
647 * success or -1 on failure. If <b>validate_only</b> is set, parse, warn and
648 * return as normal, but don't actually change the configured services. */
650 hs_config_service_all(const or_options_t *options, int validate_only)
652 int ret = -1;
653 config_line_t *remaining = NULL;
654 smartlist_t *new_service_list = NULL;
656 tor_assert(options);
658 /* Newly configured service are put in that list which is then used for
659 * validation and staging for >= v3. */
660 new_service_list = smartlist_new();
662 /* We need to start with a HiddenServiceDir line */
663 if (options->RendConfigLines &&
664 strcasecmp(options->RendConfigLines->key, SECTION_HEADER)) {
665 log_warn(LD_CONFIG, "%s with no preceding %s directive",
666 options->RendConfigLines->key, SECTION_HEADER);
667 goto err;
670 remaining = config_lines_dup(options->RendConfigLines);
671 while (remaining) {
672 config_line_t *section = remaining;
673 remaining = config_lines_partition(section, SECTION_HEADER);
675 /* Try to configure this service now. On success, it will be added to the
676 * list and validated against the service in that same list. */
677 int rv = config_service(section, options, new_service_list);
678 config_free_lines(section);
679 if (rv < 0) {
680 goto err;
684 /* In non validation mode, we'll stage those services we just successfully
685 * configured. Service ownership is transferred from the list to the global
686 * state. If any service is invalid, it will be removed from the list and
687 * freed. All versions are handled in that function. */
688 if (!validate_only) {
689 stage_services(new_service_list);
690 } else {
691 /* We've just validated that we were able to build a clean working list of
692 * services. We don't need those objects anymore. */
693 SMARTLIST_FOREACH(new_service_list, hs_service_t *, s,
694 hs_service_free(s));
695 /* For the v2 subsystem, the configuration function adds the service
696 * object to the staging list and it is transferred in the main list
697 * through the prunning process. In validation mode, we thus have to purge
698 * the staging list so it's not kept in memory as valid service. */
699 rend_service_free_staging_list();
702 /* Success. Note that the service list has no ownership of its content. */
703 ret = 0;
704 goto end;
706 err:
707 SMARTLIST_FOREACH(new_service_list, hs_service_t *, s, hs_service_free(s));
709 end:
710 smartlist_free(new_service_list);
711 /* Tor main should call the free all function on error. */
712 return ret;
715 /** From a set of <b>options</b>, setup every client authorization found.
716 * Return 0 on success or -1 on failure. If <b>validate_only</b> is set,
717 * parse, warn and return as normal, but don't actually change the
718 * configured state. */
720 hs_config_client_auth_all(const or_options_t *options, int validate_only)
722 int ret = -1;
724 /* Configure v2 authorization. */
725 if (rend_parse_service_authorization(options, validate_only) < 0) {
726 goto done;
729 /* Configure v3 authorization. */
730 if (hs_config_client_authorization(options, validate_only) < 0) {
731 goto done;
734 /* Success. */
735 ret = 0;
736 done:
737 return ret;
741 * Free all resources held by the hs_config.c module.
743 void
744 hs_config_free_all(void)
746 config_mgr_free(hs_opts_mgr);