xref: /src/usr.sbin/pkg/config.c (revision abf911af22729858cc876231e7970eff6aefbb9d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2014-2025 Baptiste Daroussin <bapt@FreeBSD.org>
5  * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/queue.h>
31 #include <sys/utsname.h>
32 #include <sys/sysctl.h>
33 
34 #include <dirent.h>
35 #include <ucl.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <libutil.h>
39 #include <paths.h>
40 #include <stdbool.h>
41 #include <unistd.h>
42 #include <ctype.h>
43 
44 #include "config.h"
45 
46 struct config_value {
47 	char *value;
48 	STAILQ_ENTRY(config_value) next;
49 };
50 
51 struct config_entry {
52 	uint8_t type;
53 	const char *key;
54 	const char *val;
55 	char *value;
56 	STAILQ_HEAD(, config_value) *list;
57 	bool envset;
58 	bool main_only;				/* Only set in pkg.conf. */
59 };
60 
61 static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories);
62 
63 static struct config_entry c[] = {
64 	[PACKAGESITE] = {
65 		PKG_CONFIG_STRING,
66 		"PACKAGESITE",
67 		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
68 		NULL,
69 		NULL,
70 		false,
71 		false,
72 	},
73 	[ABI] = {
74 		PKG_CONFIG_STRING,
75 		"ABI",
76 		NULL,
77 		NULL,
78 		NULL,
79 		false,
80 		true,
81 	},
82 	[MIRROR_TYPE] = {
83 		PKG_CONFIG_STRING,
84 		"MIRROR_TYPE",
85 		"SRV",
86 		NULL,
87 		NULL,
88 		false,
89 		false,
90 	},
91 	[ASSUME_ALWAYS_YES] = {
92 		PKG_CONFIG_BOOL,
93 		"ASSUME_ALWAYS_YES",
94 		"NO",
95 		NULL,
96 		NULL,
97 		false,
98 		true,
99 	},
100 	[SIGNATURE_TYPE] = {
101 		PKG_CONFIG_STRING,
102 		"SIGNATURE_TYPE",
103 		NULL,
104 		NULL,
105 		NULL,
106 		false,
107 		false,
108 	},
109 	[FINGERPRINTS] = {
110 		PKG_CONFIG_STRING,
111 		"FINGERPRINTS",
112 		NULL,
113 		NULL,
114 		NULL,
115 		false,
116 		false,
117 	},
118 	[REPOS_DIR] = {
119 		PKG_CONFIG_LIST,
120 		"REPOS_DIR",
121 		NULL,
122 		NULL,
123 		NULL,
124 		false,
125 		true,
126 	},
127 	[PUBKEY] = {
128 		PKG_CONFIG_STRING,
129 		"PUBKEY",
130 		NULL,
131 		NULL,
132 		NULL,
133 		false,
134 		false
135 	},
136 	[PKG_ENV] = {
137 		PKG_CONFIG_OBJECT,
138 		"PKG_ENV",
139 		NULL,
140 		NULL,
141 		NULL,
142 		false,
143 		false,
144 	}
145 };
146 
147 static char *
pkg_get_myabi(void)148 pkg_get_myabi(void)
149 {
150 	struct utsname uts;
151 	char machine_arch[255];
152 	char *abi;
153 	size_t len;
154 	int error;
155 
156 	error = uname(&uts);
157 	if (error)
158 		return (NULL);
159 
160 	len = sizeof(machine_arch);
161 	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
162 	if (error)
163 		return (NULL);
164 	machine_arch[len] = '\0';
165 
166 	/*
167 	 * Use __FreeBSD_version rather than kernel version (uts.release) for
168 	 * use in jails. This is equivalent to the value of uname -U.
169 	 */
170 	error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
171 	    machine_arch);
172 	if (error < 0)
173 		return (NULL);
174 
175 	return (abi);
176 }
177 
178 static void
subst_packagesite(const char * abi)179 subst_packagesite(const char *abi)
180 {
181 	char *newval;
182 	const char *variable_string;
183 	const char *oldval;
184 
185 	if (c[PACKAGESITE].value != NULL)
186 		oldval = c[PACKAGESITE].value;
187 	else
188 		oldval = c[PACKAGESITE].val;
189 
190 	if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
191 		return;
192 
193 	asprintf(&newval, "%.*s%s%s",
194 	    (int)(variable_string - oldval), oldval, abi,
195 	    variable_string + strlen("${ABI}"));
196 	if (newval == NULL)
197 		errx(EXIT_FAILURE, "asprintf");
198 
199 	free(c[PACKAGESITE].value);
200 	c[PACKAGESITE].value = newval;
201 }
202 
203 static int
boolstr_to_bool(const char * str)204 boolstr_to_bool(const char *str)
205 {
206 	if (str != NULL && (strcasecmp(str, "true") == 0 ||
207 	    strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
208 	    str[0] == '1'))
209 		return (true);
210 
211 	return (false);
212 }
213 
214 static void
config_parse(const ucl_object_t * obj)215 config_parse(const ucl_object_t *obj)
216 {
217 	FILE *buffp;
218 	char *buf = NULL;
219 	size_t bufsz = 0;
220 	const ucl_object_t *cur, *seq, *tmp;
221 	ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL;
222 	struct config_entry *temp_config;
223 	struct config_value *cv;
224 	const char *key, *evkey;
225 	int i;
226 	size_t j;
227 
228 	/* Temporary config for configs that may be disabled. */
229 	temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
230 	buffp = open_memstream(&buf, &bufsz);
231 	if (buffp == NULL)
232 		err(EXIT_FAILURE, "open_memstream()");
233 
234 	while ((cur = ucl_iterate_object(obj, &it, true))) {
235 		key = ucl_object_key(cur);
236 		if (key == NULL)
237 			continue;
238 		if (buf != NULL)
239 			memset(buf, 0, bufsz);
240 		rewind(buffp);
241 
242 			for (j = 0; j < strlen(key); ++j)
243 				fputc(toupper(key[j]), buffp);
244 			fflush(buffp);
245 
246 		for (i = 0; i < CONFIG_SIZE; i++) {
247 			if (strcmp(buf, c[i].key) == 0)
248 				break;
249 		}
250 
251 		/* Silently skip unknown keys to be future compatible. */
252 		if (i == CONFIG_SIZE)
253 			continue;
254 
255 		/* env has priority over config file */
256 		if (c[i].envset)
257 			continue;
258 
259 		/* Parse sequence value ["item1", "item2"] */
260 		switch (c[i].type) {
261 		case PKG_CONFIG_LIST:
262 			if (cur->type != UCL_ARRAY) {
263 				warnx("Skipping invalid array "
264 				    "value for %s.\n", c[i].key);
265 				continue;
266 			}
267 			temp_config[i].list =
268 			    malloc(sizeof(*temp_config[i].list));
269 			STAILQ_INIT(temp_config[i].list);
270 
271 			while ((seq = ucl_iterate_object(cur, &itseq, true))) {
272 				if (seq->type != UCL_STRING)
273 					continue;
274 				cv = malloc(sizeof(struct config_value));
275 				cv->value =
276 				    strdup(ucl_object_tostring(seq));
277 				STAILQ_INSERT_TAIL(temp_config[i].list, cv,
278 				    next);
279 			}
280 			break;
281 		case PKG_CONFIG_BOOL:
282 			temp_config[i].value =
283 			    strdup(ucl_object_toboolean(cur) ? "yes" : "no");
284 			break;
285 		case PKG_CONFIG_OBJECT:
286 			if (strcmp(c[i].key, "PKG_ENV") == 0) {
287 				while ((tmp =
288 				    ucl_iterate_object(cur, &it_obj, true))) {
289 					evkey = ucl_object_key(tmp);
290 					if (evkey != NULL && *evkey != '\0') {
291 						setenv(evkey, ucl_object_tostring_forced(tmp), 1);
292 					}
293 				}
294 			}
295 			break;
296 		default:
297 			/* Normal string value. */
298 			temp_config[i].value = strdup(ucl_object_tostring(cur));
299 			break;
300 		}
301 	}
302 
303 	/* Repo is enabled, copy over all settings from temp_config. */
304 	for (i = 0; i < CONFIG_SIZE; i++) {
305 		if (c[i].envset)
306 			continue;
307 		/* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
308 		if (c[i].main_only == true)
309 			continue;
310 		switch (c[i].type) {
311 		case PKG_CONFIG_LIST:
312 			c[i].list = temp_config[i].list;
313 			break;
314 		default:
315 			c[i].value = temp_config[i].value;
316 			break;
317 		}
318 	}
319 
320 	free(temp_config);
321 	fclose(buffp);
322 	free(buf);
323 }
324 
325 
326 static void
parse_mirror_type(struct repository * r,const char * mt)327 parse_mirror_type(struct repository *r, const char *mt)
328 {
329 	if (strcasecmp(mt, "srv") == 0)
330 		r->mirror_type = MIRROR_SRV;
331 	else
332 		r->mirror_type = MIRROR_NONE;
333 }
334 
335 static void
repo_free(struct repository * r)336 repo_free(struct repository *r)
337 {
338 	free(r->name);
339 	free(r->url);
340 	free(r->fingerprints);
341 	free(r->pubkey);
342 	free(r);
343 }
344 
345 static bool
parse_signature_type(struct repository * repo,const char * st)346 parse_signature_type(struct repository *repo, const char *st)
347 {
348 	if (strcasecmp(st, "FINGERPRINTS") == 0)
349 		repo->signature_type = SIGNATURE_FINGERPRINT;
350 	else if (strcasecmp(st, "PUBKEY") == 0)
351 		repo->signature_type = SIGNATURE_PUBKEY;
352 	else if (strcasecmp(st, "NONE") == 0)
353 		repo->signature_type = SIGNATURE_NONE;
354 	else {
355 		warnx("Signature type %s is not supported for bootstrapping,"
356 		    " ignoring repository %s", st, repo->name);
357 		return (false);
358 	}
359 	return (true);
360 }
361 
362 static struct repository *
find_repository(const char * name)363 find_repository(const char *name)
364 {
365 	struct repository *repo;
366 	STAILQ_FOREACH(repo, &repositories, next) {
367 		if (strcmp(repo->name, name) == 0)
368 			return (repo);
369 	}
370 	return (NULL);
371 }
372 
373 static void
parse_repo(const ucl_object_t * o)374 parse_repo(const ucl_object_t *o)
375 {
376 	const ucl_object_t *cur;
377 	const char *key, *reponame;
378 	ucl_object_iter_t it = NULL;
379 	bool newrepo = false;
380 	struct repository *repo;
381 
382 	reponame = ucl_object_key(o);
383 	repo = find_repository(reponame);
384 	if (repo == NULL) {
385 		repo = calloc(1, sizeof(struct repository));
386 		if (repo == NULL)
387 			err(EXIT_FAILURE, "calloc");
388 
389 		repo->name = strdup(reponame);
390 		if (repo->name == NULL)
391 			err(EXIT_FAILURE, "strdup");
392 		newrepo = true;
393 	}
394 	while ((cur = ucl_iterate_object(o, &it, true))) {
395 		key = ucl_object_key(cur);
396 		if (key == NULL)
397 			continue;
398 		if (strcasecmp(key, "url") == 0) {
399 			free(repo->url);
400 			repo->url = strdup(ucl_object_tostring(cur));
401 			if (repo->url == NULL)
402 				err(EXIT_FAILURE, "strdup");
403 		} else if (strcasecmp(key, "mirror_type") == 0) {
404 			parse_mirror_type(repo, ucl_object_tostring(cur));
405 		} else if (strcasecmp(key, "signature_type") == 0) {
406 			if (!parse_signature_type(repo, ucl_object_tostring(cur))) {
407 				if (newrepo)
408 					repo_free(repo);
409 				else
410 					STAILQ_REMOVE(&repositories, repo, repository, next);
411 				return;
412 			}
413 		} else if (strcasecmp(key, "fingerprints") == 0) {
414 			free(repo->fingerprints);
415 			repo->fingerprints = strdup(ucl_object_tostring(cur));
416 			if (repo->fingerprints == NULL)
417 				err(EXIT_FAILURE, "strdup");
418 		} else if (strcasecmp(key, "pubkey") == 0) {
419 			free(repo->pubkey);
420 			repo->pubkey = strdup(ucl_object_tostring(cur));
421 			if (repo->pubkey == NULL)
422 				err(EXIT_FAILURE, "strdup");
423 		} else if (strcasecmp(key, "enabled") == 0) {
424 			if ((cur->type != UCL_BOOLEAN) ||
425 			    !ucl_object_toboolean(cur)) {
426 				if (newrepo)
427 					repo_free(repo);
428 				else
429 					STAILQ_REMOVE(&repositories, repo, repository, next);
430 				return;
431 			}
432 		}
433 	}
434 	/* At least we need an url */
435 	if (repo->url == NULL) {
436 		repo_free(repo);
437 		return;
438 	}
439 	if (newrepo)
440 		STAILQ_INSERT_TAIL(&repositories, repo, next);
441 	return;
442 }
443 
444 /*-
445  * Parse new repo style configs in style:
446  * Name:
447  *   URL:
448  *   MIRROR_TYPE:
449  * etc...
450  */
451 static void
parse_repo_file(ucl_object_t * obj,const char * requested_repo)452 parse_repo_file(ucl_object_t *obj, const char *requested_repo)
453 {
454 	ucl_object_iter_t it = NULL;
455 	const ucl_object_t *cur;
456 	const char *key;
457 
458 	while ((cur = ucl_iterate_object(obj, &it, true))) {
459 		key = ucl_object_key(cur);
460 
461 		if (key == NULL)
462 			continue;
463 
464 		if (cur->type != UCL_OBJECT)
465 			continue;
466 
467 		if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
468 			continue;
469 		parse_repo(cur);
470 	}
471 }
472 
473 
474 static int
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)475 read_conf_file(const char *confpath, const char *requested_repo,
476     pkg_conf_file_t conftype)
477 {
478 	struct ucl_parser *p;
479 	ucl_object_t *obj = NULL;
480 	char *abi = pkg_get_myabi(), *major, *minor;
481 	struct utsname uts;
482 	int ret;
483 
484 	if (uname(&uts))
485 		err(EXIT_FAILURE, "uname");
486 	if (abi == NULL)
487 		errx(EXIT_FAILURE, "Failed to determine ABI");
488 
489 	p = ucl_parser_new(0);
490 	asprintf(&major, "%d",  __FreeBSD_version/100000);
491 	if (major == NULL)
492 		err(EXIT_FAILURE, "asprintf");
493 	asprintf(&minor, "%d",  (__FreeBSD_version / 1000) % 100);
494 	if (minor == NULL)
495 		err(EXIT_FAILURE, "asprintf");
496 	ucl_parser_register_variable(p, "ABI", abi);
497 	ucl_parser_register_variable(p, "OSNAME", uts.sysname);
498 	ucl_parser_register_variable(p, "RELEASE", major);
499 	ucl_parser_register_variable(p, "VERSION_MAJOR", major);
500 	ucl_parser_register_variable(p, "VERSION_MINOR", minor);
501 
502 	if (!ucl_parser_add_file(p, confpath)) {
503 		if (errno != ENOENT)
504 			errx(EXIT_FAILURE, "Unable to parse configuration "
505 			    "file %s: %s", confpath, ucl_parser_get_error(p));
506 		/* no configuration present */
507 		ret = 1;
508 		goto out;
509 	}
510 
511 	obj = ucl_parser_get_object(p);
512 	if (obj->type != UCL_OBJECT)
513 		warnx("Invalid configuration format, ignoring the "
514 		    "configuration file %s", confpath);
515 	else {
516 		if (conftype == CONFFILE_PKG)
517 			config_parse(obj);
518 		else if (conftype == CONFFILE_REPO)
519 			parse_repo_file(obj, requested_repo);
520 	}
521 	ucl_object_unref(obj);
522 
523 	ret = 0;
524 out:
525 	ucl_parser_free(p);
526 	free(abi);
527 	free(major);
528 	free(minor);
529 
530 	return (ret);
531 }
532 
533 static void
load_repositories(const char * repodir,const char * requested_repo)534 load_repositories(const char *repodir, const char *requested_repo)
535 {
536 	struct dirent *ent;
537 	DIR *d;
538 	char *p;
539 	size_t n;
540 	char path[MAXPATHLEN];
541 
542 	if ((d = opendir(repodir)) == NULL)
543 		return;
544 
545 	while ((ent = readdir(d))) {
546 		/* Trim out 'repos'. */
547 		if ((n = strlen(ent->d_name)) <= 5)
548 			continue;
549 		p = &ent->d_name[n - 5];
550 		if (strcmp(p, ".conf") == 0) {
551 			snprintf(path, sizeof(path), "%s%s%s",
552 			    repodir,
553 			    repodir[strlen(repodir) - 1] == '/' ? "" : "/",
554 			    ent->d_name);
555 			if (access(path, F_OK) != 0)
556 				continue;
557 			if (read_conf_file(path, requested_repo,
558 			    CONFFILE_REPO)) {
559 				goto cleanup;
560 			}
561 		}
562 	}
563 
564 cleanup:
565 	closedir(d);
566 }
567 
568 int
config_init(const char * requested_repo)569 config_init(const char *requested_repo)
570 {
571 	char *val;
572 	int i;
573 	const char *localbase;
574 	char *abi, *env_list_item;
575 	char confpath[MAXPATHLEN];
576 	struct config_value *cv;
577 
578 	for (i = 0; i < CONFIG_SIZE; i++) {
579 		val = getenv(c[i].key);
580 		if (val != NULL) {
581 			c[i].envset = true;
582 			switch (c[i].type) {
583 			case PKG_CONFIG_LIST:
584 				/* Split up comma-separated items from env. */
585 				c[i].list = malloc(sizeof(*c[i].list));
586 				STAILQ_INIT(c[i].list);
587 				for (env_list_item = strtok(val, ",");
588 				    env_list_item != NULL;
589 				    env_list_item = strtok(NULL, ",")) {
590 					cv =
591 					    malloc(sizeof(struct config_value));
592 					cv->value =
593 					    strdup(env_list_item);
594 					STAILQ_INSERT_TAIL(c[i].list, cv,
595 					    next);
596 				}
597 				break;
598 			default:
599 				c[i].val = val;
600 				break;
601 			}
602 		}
603 	}
604 
605 	/* Read LOCALBASE/etc/pkg.conf first. */
606 	localbase = getlocalbase();
607 	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
608 
609 	if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
610 	    CONFFILE_PKG))
611 		goto finalize;
612 
613 	/* Then read in all repos from REPOS_DIR list of directories. */
614 	if (c[REPOS_DIR].list == NULL) {
615 		c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
616 		STAILQ_INIT(c[REPOS_DIR].list);
617 		cv = malloc(sizeof(struct config_value));
618 		cv->value = strdup("/etc/pkg");
619 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
620 		cv = malloc(sizeof(struct config_value));
621 		if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
622 			goto finalize;
623 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
624 	}
625 
626 	STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
627 		load_repositories(cv->value, requested_repo);
628 
629 finalize:
630 	if (c[ABI].val == NULL && c[ABI].value == NULL) {
631 		abi = pkg_get_myabi();
632 		if (abi == NULL)
633 			errx(EXIT_FAILURE, "Failed to determine the system "
634 			    "ABI");
635 		c[ABI].val = abi;
636 	}
637 
638 	return (0);
639 }
640 
641 int
config_string(pkg_config_key k,const char ** val)642 config_string(pkg_config_key k, const char **val)
643 {
644 	if (c[k].type != PKG_CONFIG_STRING)
645 		return (-1);
646 
647 	if (c[k].value != NULL)
648 		*val = c[k].value;
649 	else
650 		*val = c[k].val;
651 
652 	return (0);
653 }
654 
655 int
config_bool(pkg_config_key k,bool * val)656 config_bool(pkg_config_key k, bool *val)
657 {
658 	const char *value;
659 
660 	if (c[k].type != PKG_CONFIG_BOOL)
661 		return (-1);
662 
663 	*val = false;
664 
665 	if (c[k].value != NULL)
666 		value = c[k].value;
667 	else
668 		value = c[k].val;
669 
670 	if (boolstr_to_bool(value))
671 		*val = true;
672 
673 	return (0);
674 }
675 
676 struct repositories *
config_get_repositories(void)677 config_get_repositories(void)
678 {
679 	if (STAILQ_EMPTY(&repositories)) {
680 		/* Fall back to PACKAGESITE - deprecated - */
681 		struct repository *r = calloc(1, sizeof(*r));
682 		if (r == NULL)
683 			err(EXIT_FAILURE, "calloc");
684 		r->name = strdup("fallback");
685 		if (r->name == NULL)
686 			err(EXIT_FAILURE, "strdup");
687 		subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
688 		r->url = c[PACKAGESITE].value;
689 		if (c[SIGNATURE_TYPE].value != NULL)
690 			if (!parse_signature_type(r, c[SIGNATURE_TYPE].value))
691 				exit(EXIT_FAILURE);
692 		if (c[MIRROR_TYPE].value != NULL)
693 			parse_mirror_type(r, c[MIRROR_TYPE].value);
694 		if (c[PUBKEY].value != NULL)
695 			r->pubkey = c[PUBKEY].value;
696 		if (c[FINGERPRINTS].value != NULL)
697 			r->fingerprints = c[FINGERPRINTS].value;
698 		STAILQ_INSERT_TAIL(&repositories, r, next);
699 	}
700 	return (&repositories);
701 }
702 
703 void
config_finish(void)704 config_finish(void) {
705 	int i;
706 
707 	for (i = 0; i < CONFIG_SIZE; i++)
708 		free(c[i].value);
709 }
710