xref: /freebsd/usr.sbin/pkg/config.c (revision a4e5e0106ac7145f56eb39a691e302cabb4635be)
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 config_entry c[] = {
63 	[PACKAGESITE] = {
64 		PKG_CONFIG_STRING,
65 		"PACKAGESITE",
66 		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
67 		NULL,
68 		NULL,
69 		false,
70 		false,
71 	},
72 	[ABI] = {
73 		PKG_CONFIG_STRING,
74 		"ABI",
75 		NULL,
76 		NULL,
77 		NULL,
78 		false,
79 		true,
80 	},
81 	[MIRROR_TYPE] = {
82 		PKG_CONFIG_STRING,
83 		"MIRROR_TYPE",
84 		"SRV",
85 		NULL,
86 		NULL,
87 		false,
88 		false,
89 	},
90 	[ASSUME_ALWAYS_YES] = {
91 		PKG_CONFIG_BOOL,
92 		"ASSUME_ALWAYS_YES",
93 		"NO",
94 		NULL,
95 		NULL,
96 		false,
97 		true,
98 	},
99 	[SIGNATURE_TYPE] = {
100 		PKG_CONFIG_STRING,
101 		"SIGNATURE_TYPE",
102 		NULL,
103 		NULL,
104 		NULL,
105 		false,
106 		false,
107 	},
108 	[FINGERPRINTS] = {
109 		PKG_CONFIG_STRING,
110 		"FINGERPRINTS",
111 		NULL,
112 		NULL,
113 		NULL,
114 		false,
115 		false,
116 	},
117 	[REPOS_DIR] = {
118 		PKG_CONFIG_LIST,
119 		"REPOS_DIR",
120 		NULL,
121 		NULL,
122 		NULL,
123 		false,
124 		true,
125 	},
126 	[PUBKEY] = {
127 		PKG_CONFIG_STRING,
128 		"PUBKEY",
129 		NULL,
130 		NULL,
131 		NULL,
132 		false,
133 		false
134 	},
135 	[PKG_ENV] = {
136 		PKG_CONFIG_OBJECT,
137 		"PKG_ENV",
138 		NULL,
139 		NULL,
140 		NULL,
141 		false,
142 		false,
143 	}
144 };
145 
146 static char *
147 pkg_get_myabi(void)
148 {
149 	struct utsname uts;
150 	char machine_arch[255];
151 	char *abi;
152 	size_t len;
153 	int error;
154 
155 	error = uname(&uts);
156 	if (error)
157 		return (NULL);
158 
159 	len = sizeof(machine_arch);
160 	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
161 	if (error)
162 		return (NULL);
163 	machine_arch[len] = '\0';
164 
165 	/*
166 	 * Use __FreeBSD_version rather than kernel version (uts.release) for
167 	 * use in jails. This is equivalent to the value of uname -U.
168 	 */
169 	error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
170 	    machine_arch);
171 	if (error < 0)
172 		return (NULL);
173 
174 	return (abi);
175 }
176 
177 static void
178 subst_packagesite(const char *abi)
179 {
180 	char *newval;
181 	const char *variable_string;
182 	const char *oldval;
183 
184 	if (c[PACKAGESITE].value != NULL)
185 		oldval = c[PACKAGESITE].value;
186 	else
187 		oldval = c[PACKAGESITE].val;
188 
189 	if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
190 		return;
191 
192 	asprintf(&newval, "%.*s%s%s",
193 	    (int)(variable_string - oldval), oldval, abi,
194 	    variable_string + strlen("${ABI}"));
195 	if (newval == NULL)
196 		errx(EXIT_FAILURE, "asprintf");
197 
198 	free(c[PACKAGESITE].value);
199 	c[PACKAGESITE].value = newval;
200 }
201 
202 static int
203 boolstr_to_bool(const char *str)
204 {
205 	if (str != NULL && (strcasecmp(str, "true") == 0 ||
206 	    strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
207 	    str[0] == '1'))
208 		return (true);
209 
210 	return (false);
211 }
212 
213 static void
214 config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype)
215 {
216 	FILE *buffp;
217 	char *buf = NULL;
218 	size_t bufsz = 0;
219 	const ucl_object_t *cur, *seq, *tmp;
220 	ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL;
221 	struct config_entry *temp_config;
222 	struct config_value *cv;
223 	const char *key, *evkey;
224 	int i;
225 	size_t j;
226 
227 	/* Temporary config for configs that may be disabled. */
228 	temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
229 	buffp = open_memstream(&buf, &bufsz);
230 	if (buffp == NULL)
231 		err(EXIT_FAILURE, "open_memstream()");
232 
233 	while ((cur = ucl_iterate_object(obj, &it, true))) {
234 		key = ucl_object_key(cur);
235 		if (key == NULL)
236 			continue;
237 		if (buf != NULL)
238 			memset(buf, 0, bufsz);
239 		rewind(buffp);
240 
241 		if (conftype == CONFFILE_PKG) {
242 			for (j = 0; j < strlen(key); ++j)
243 				fputc(toupper(key[j]), buffp);
244 			fflush(buffp);
245 		} else if (conftype == CONFFILE_REPO) {
246 			if (strcasecmp(key, "url") == 0)
247 				fputs("PACKAGESITE", buffp);
248 			else if (strcasecmp(key, "mirror_type") == 0)
249 				fputs("MIRROR_TYPE", buffp);
250 			else if (strcasecmp(key, "signature_type") == 0)
251 				fputs("SIGNATURE_TYPE", buffp);
252 			else if (strcasecmp(key, "fingerprints") == 0)
253 				fputs("FINGERPRINTS", buffp);
254 			else if (strcasecmp(key, "pubkey") == 0)
255 				fputs("PUBKEY", buffp);
256 			else if (strcasecmp(key, "enabled") == 0) {
257 				if ((cur->type != UCL_BOOLEAN) ||
258 				    !ucl_object_toboolean(cur))
259 					goto cleanup;
260 			} else
261 				continue;
262 			fflush(buffp);
263 		}
264 
265 		for (i = 0; i < CONFIG_SIZE; i++) {
266 			if (strcmp(buf, c[i].key) == 0)
267 				break;
268 		}
269 
270 		/* Silently skip unknown keys to be future compatible. */
271 		if (i == CONFIG_SIZE)
272 			continue;
273 
274 		/* env has priority over config file */
275 		if (c[i].envset)
276 			continue;
277 
278 		/* Parse sequence value ["item1", "item2"] */
279 		switch (c[i].type) {
280 		case PKG_CONFIG_LIST:
281 			if (cur->type != UCL_ARRAY) {
282 				warnx("Skipping invalid array "
283 				    "value for %s.\n", c[i].key);
284 				continue;
285 			}
286 			temp_config[i].list =
287 			    malloc(sizeof(*temp_config[i].list));
288 			STAILQ_INIT(temp_config[i].list);
289 
290 			while ((seq = ucl_iterate_object(cur, &itseq, true))) {
291 				if (seq->type != UCL_STRING)
292 					continue;
293 				cv = malloc(sizeof(struct config_value));
294 				cv->value =
295 				    strdup(ucl_object_tostring(seq));
296 				STAILQ_INSERT_TAIL(temp_config[i].list, cv,
297 				    next);
298 			}
299 			break;
300 		case PKG_CONFIG_BOOL:
301 			temp_config[i].value =
302 			    strdup(ucl_object_toboolean(cur) ? "yes" : "no");
303 			break;
304 		case PKG_CONFIG_OBJECT:
305 			if (strcmp(c[i].key, "PKG_ENV") == 0) {
306 				while ((tmp =
307 				    ucl_iterate_object(cur, &it_obj, true))) {
308 					evkey = ucl_object_key(tmp);
309 					if (evkey != NULL && *evkey != '\0') {
310 						setenv(evkey, ucl_object_tostring_forced(tmp), 1);
311 					}
312 				}
313 			}
314 			break;
315 		default:
316 			/* Normal string value. */
317 			temp_config[i].value = strdup(ucl_object_tostring(cur));
318 			break;
319 		}
320 	}
321 
322 	/* Repo is enabled, copy over all settings from temp_config. */
323 	for (i = 0; i < CONFIG_SIZE; i++) {
324 		if (c[i].envset)
325 			continue;
326 		/* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
327 		if (conftype != CONFFILE_PKG && c[i].main_only == true)
328 			continue;
329 		switch (c[i].type) {
330 		case PKG_CONFIG_LIST:
331 			c[i].list = temp_config[i].list;
332 			break;
333 		default:
334 			c[i].value = temp_config[i].value;
335 			break;
336 		}
337 	}
338 
339 cleanup:
340 	free(temp_config);
341 	fclose(buffp);
342 	free(buf);
343 }
344 
345 /*-
346  * Parse new repo style configs in style:
347  * Name:
348  *   URL:
349  *   MIRROR_TYPE:
350  * etc...
351  */
352 static void
353 parse_repo_file(ucl_object_t *obj, const char *requested_repo)
354 {
355 	ucl_object_iter_t it = NULL;
356 	const ucl_object_t *cur;
357 	const char *key;
358 
359 	while ((cur = ucl_iterate_object(obj, &it, true))) {
360 		key = ucl_object_key(cur);
361 
362 		if (key == NULL)
363 			continue;
364 
365 		if (cur->type != UCL_OBJECT)
366 			continue;
367 
368 		if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
369 			continue;
370 
371 		config_parse(cur, CONFFILE_REPO);
372 	}
373 }
374 
375 
376 static int
377 read_conf_file(const char *confpath, const char *requested_repo,
378     pkg_conf_file_t conftype)
379 {
380 	struct ucl_parser *p;
381 	ucl_object_t *obj = NULL;
382 
383 	p = ucl_parser_new(0);
384 
385 	if (!ucl_parser_add_file(p, confpath)) {
386 		if (errno != ENOENT)
387 			errx(EXIT_FAILURE, "Unable to parse configuration "
388 			    "file %s: %s", confpath, ucl_parser_get_error(p));
389 		ucl_parser_free(p);
390 		/* no configuration present */
391 		return (1);
392 	}
393 
394 	obj = ucl_parser_get_object(p);
395 	if (obj->type != UCL_OBJECT)
396 		warnx("Invalid configuration format, ignoring the "
397 		    "configuration file %s", confpath);
398 	else {
399 		if (conftype == CONFFILE_PKG)
400 			config_parse(obj, conftype);
401 		else if (conftype == CONFFILE_REPO)
402 			parse_repo_file(obj, requested_repo);
403 	}
404 
405 	ucl_object_unref(obj);
406 	ucl_parser_free(p);
407 
408 	return (0);
409 }
410 
411 static int
412 load_repositories(const char *repodir, const char *requested_repo)
413 {
414 	struct dirent *ent;
415 	DIR *d;
416 	char *p;
417 	size_t n;
418 	char path[MAXPATHLEN];
419 	int ret;
420 
421 	ret = 0;
422 
423 	if ((d = opendir(repodir)) == NULL)
424 		return (1);
425 
426 	while ((ent = readdir(d))) {
427 		/* Trim out 'repos'. */
428 		if ((n = strlen(ent->d_name)) <= 5)
429 			continue;
430 		p = &ent->d_name[n - 5];
431 		if (strcmp(p, ".conf") == 0) {
432 			snprintf(path, sizeof(path), "%s%s%s",
433 			    repodir,
434 			    repodir[strlen(repodir) - 1] == '/' ? "" : "/",
435 			    ent->d_name);
436 			if (access(path, F_OK) != 0)
437 				continue;
438 			if (read_conf_file(path, requested_repo,
439 			    CONFFILE_REPO)) {
440 				ret = 1;
441 				goto cleanup;
442 			}
443 		}
444 	}
445 
446 cleanup:
447 	closedir(d);
448 
449 	return (ret);
450 }
451 
452 int
453 config_init(const char *requested_repo)
454 {
455 	char *val;
456 	int i;
457 	const char *localbase;
458 	char *abi, *env_list_item;
459 	char confpath[MAXPATHLEN];
460 	struct config_value *cv;
461 
462 	for (i = 0; i < CONFIG_SIZE; i++) {
463 		val = getenv(c[i].key);
464 		if (val != NULL) {
465 			c[i].envset = true;
466 			switch (c[i].type) {
467 			case PKG_CONFIG_LIST:
468 				/* Split up comma-separated items from env. */
469 				c[i].list = malloc(sizeof(*c[i].list));
470 				STAILQ_INIT(c[i].list);
471 				for (env_list_item = strtok(val, ",");
472 				    env_list_item != NULL;
473 				    env_list_item = strtok(NULL, ",")) {
474 					cv =
475 					    malloc(sizeof(struct config_value));
476 					cv->value =
477 					    strdup(env_list_item);
478 					STAILQ_INSERT_TAIL(c[i].list, cv,
479 					    next);
480 				}
481 				break;
482 			default:
483 				c[i].val = val;
484 				break;
485 			}
486 		}
487 	}
488 
489 	/* Read LOCALBASE/etc/pkg.conf first. */
490 	localbase = getlocalbase();
491 	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
492 
493 	if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
494 	    CONFFILE_PKG))
495 		goto finalize;
496 
497 	/* Then read in all repos from REPOS_DIR list of directories. */
498 	if (c[REPOS_DIR].list == NULL) {
499 		c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
500 		STAILQ_INIT(c[REPOS_DIR].list);
501 		cv = malloc(sizeof(struct config_value));
502 		cv->value = strdup("/etc/pkg");
503 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
504 		cv = malloc(sizeof(struct config_value));
505 		if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
506 			goto finalize;
507 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
508 	}
509 
510 	STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
511 		if (load_repositories(cv->value, requested_repo))
512 			goto finalize;
513 
514 finalize:
515 	if (c[ABI].val == NULL && c[ABI].value == NULL) {
516 		abi = pkg_get_myabi();
517 		if (abi == NULL)
518 			errx(EXIT_FAILURE, "Failed to determine the system "
519 			    "ABI");
520 		c[ABI].val = abi;
521 	}
522 
523 	subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
524 
525 	return (0);
526 }
527 
528 int
529 config_string(pkg_config_key k, const char **val)
530 {
531 	if (c[k].type != PKG_CONFIG_STRING)
532 		return (-1);
533 
534 	if (c[k].value != NULL)
535 		*val = c[k].value;
536 	else
537 		*val = c[k].val;
538 
539 	return (0);
540 }
541 
542 int
543 config_bool(pkg_config_key k, bool *val)
544 {
545 	const char *value;
546 
547 	if (c[k].type != PKG_CONFIG_BOOL)
548 		return (-1);
549 
550 	*val = false;
551 
552 	if (c[k].value != NULL)
553 		value = c[k].value;
554 	else
555 		value = c[k].val;
556 
557 	if (boolstr_to_bool(value))
558 		*val = true;
559 
560 	return (0);
561 }
562 
563 void
564 config_finish(void) {
565 	int i;
566 
567 	for (i = 0; i < CONFIG_SIZE; i++)
568 		free(c[i].value);
569 }
570