xref: /freebsd/usr.sbin/pkg/config.c (revision d4eeb02986980bf33dd56c41ceb9fc5f180c0d47)
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/sysctl.h>
37 
38 #include <dirent.h>
39 #include <ucl.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <libutil.h>
43 #include <paths.h>
44 #include <stdbool.h>
45 #include <unistd.h>
46 #include <ctype.h>
47 
48 #include "config.h"
49 
50 struct config_value {
51 	char *value;
52 	STAILQ_ENTRY(config_value) next;
53 };
54 
55 struct config_entry {
56 	uint8_t type;
57 	const char *key;
58 	const char *val;
59 	char *value;
60 	STAILQ_HEAD(, config_value) *list;
61 	bool envset;
62 	bool main_only;				/* Only set in pkg.conf. */
63 };
64 
65 static struct config_entry c[] = {
66 	[PACKAGESITE] = {
67 		PKG_CONFIG_STRING,
68 		"PACKAGESITE",
69 		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
70 		NULL,
71 		NULL,
72 		false,
73 		false,
74 	},
75 	[ABI] = {
76 		PKG_CONFIG_STRING,
77 		"ABI",
78 		NULL,
79 		NULL,
80 		NULL,
81 		false,
82 		true,
83 	},
84 	[MIRROR_TYPE] = {
85 		PKG_CONFIG_STRING,
86 		"MIRROR_TYPE",
87 		"SRV",
88 		NULL,
89 		NULL,
90 		false,
91 		false,
92 	},
93 	[ASSUME_ALWAYS_YES] = {
94 		PKG_CONFIG_BOOL,
95 		"ASSUME_ALWAYS_YES",
96 		"NO",
97 		NULL,
98 		NULL,
99 		false,
100 		true,
101 	},
102 	[SIGNATURE_TYPE] = {
103 		PKG_CONFIG_STRING,
104 		"SIGNATURE_TYPE",
105 		NULL,
106 		NULL,
107 		NULL,
108 		false,
109 		false,
110 	},
111 	[FINGERPRINTS] = {
112 		PKG_CONFIG_STRING,
113 		"FINGERPRINTS",
114 		NULL,
115 		NULL,
116 		NULL,
117 		false,
118 		false,
119 	},
120 	[REPOS_DIR] = {
121 		PKG_CONFIG_LIST,
122 		"REPOS_DIR",
123 		NULL,
124 		NULL,
125 		NULL,
126 		false,
127 		true,
128 	},
129 	[PUBKEY] = {
130 		PKG_CONFIG_STRING,
131 		"PUBKEY",
132 		NULL,
133 		NULL,
134 		NULL,
135 		false,
136 		false
137 	},
138 	[PKG_ENV] = {
139 		PKG_CONFIG_OBJECT,
140 		"PKG_ENV",
141 		NULL,
142 		NULL,
143 		NULL,
144 		false,
145 		false,
146 	}
147 };
148 
149 static int
150 pkg_get_myabi(char *dest, size_t sz)
151 {
152 	struct utsname uts;
153 	char machine_arch[255];
154 	size_t len;
155 	int error;
156 
157 	error = uname(&uts);
158 	if (error)
159 		return (errno);
160 
161 	len = sizeof(machine_arch);
162 	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
163 	if (error)
164 		return (errno);
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 	snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
172 	    machine_arch);
173 
174 	return (error);
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 *env_list_item;
459 	char confpath[MAXPATHLEN];
460 	struct config_value *cv;
461 	char abi[BUFSIZ];
462 
463 	for (i = 0; i < CONFIG_SIZE; i++) {
464 		val = getenv(c[i].key);
465 		if (val != NULL) {
466 			c[i].envset = true;
467 			switch (c[i].type) {
468 			case PKG_CONFIG_LIST:
469 				/* Split up comma-separated items from env. */
470 				c[i].list = malloc(sizeof(*c[i].list));
471 				STAILQ_INIT(c[i].list);
472 				for (env_list_item = strtok(val, ",");
473 				    env_list_item != NULL;
474 				    env_list_item = strtok(NULL, ",")) {
475 					cv =
476 					    malloc(sizeof(struct config_value));
477 					cv->value =
478 					    strdup(env_list_item);
479 					STAILQ_INSERT_TAIL(c[i].list, cv,
480 					    next);
481 				}
482 				break;
483 			default:
484 				c[i].val = val;
485 				break;
486 			}
487 		}
488 	}
489 
490 	/* Read LOCALBASE/etc/pkg.conf first. */
491 	localbase = getlocalbase();
492 	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
493 
494 	if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
495 	    CONFFILE_PKG))
496 		goto finalize;
497 
498 	/* Then read in all repos from REPOS_DIR list of directories. */
499 	if (c[REPOS_DIR].list == NULL) {
500 		c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
501 		STAILQ_INIT(c[REPOS_DIR].list);
502 		cv = malloc(sizeof(struct config_value));
503 		cv->value = strdup("/etc/pkg");
504 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
505 		cv = malloc(sizeof(struct config_value));
506 		if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
507 			goto finalize;
508 		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
509 	}
510 
511 	STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
512 		if (load_repositories(cv->value, requested_repo))
513 			goto finalize;
514 
515 finalize:
516 	if (c[ABI].val == NULL && c[ABI].value == NULL) {
517 		if (pkg_get_myabi(abi, BUFSIZ) != 0)
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