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