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