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