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: stable/11/usr.sbin/pkg/config.c 330449 2018-03-05 07:26:05Z eadler $");
32
33#include <sys/param.h>
34#include <sys/queue.h>
35#include <sys/sbuf.h>
36#include <sys/utsname.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 <stdbool.h>
44#include <unistd.h>
45
46#include "config.h"
47
48#define roundup2(x, y)	(((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */
49
50struct config_value {
51       char *value;
52       STAILQ_ENTRY(config_value) next;
53};
54
55struct 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
65static 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
140static int
141pkg_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
168static void
169subst_packagesite(const char *abi)
170{
171	struct sbuf *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	newval = sbuf_new_auto();
184	sbuf_bcat(newval, oldval, variable_string - oldval);
185	sbuf_cat(newval, abi);
186	sbuf_cat(newval, variable_string + strlen("${ABI}"));
187	sbuf_finish(newval);
188
189	free(c[PACKAGESITE].value);
190	c[PACKAGESITE].value = strdup(sbuf_data(newval));
191}
192
193static int
194boolstr_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
204static void
205config_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
312cleanup:
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 */
324static void
325parse_repo_file(ucl_object_t *obj)
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		config_parse(cur, CONFFILE_REPO);
341	}
342}
343
344
345static int
346read_conf_file(const char *confpath, pkg_conf_file_t conftype)
347{
348	struct ucl_parser *p;
349	ucl_object_t *obj = NULL;
350
351	p = ucl_parser_new(0);
352
353	if (!ucl_parser_add_file(p, confpath)) {
354		if (errno != ENOENT)
355			errx(EXIT_FAILURE, "Unable to parse configuration "
356			    "file %s: %s", confpath, ucl_parser_get_error(p));
357		ucl_parser_free(p);
358		/* no configuration present */
359		return (1);
360	}
361
362	obj = ucl_parser_get_object(p);
363	if (obj->type != UCL_OBJECT)
364		warnx("Invalid configuration format, ignoring the "
365		    "configuration file %s", confpath);
366	else {
367		if (conftype == CONFFILE_PKG)
368			config_parse(obj, conftype);
369		else if (conftype == CONFFILE_REPO)
370			parse_repo_file(obj);
371	}
372
373	ucl_object_unref(obj);
374	ucl_parser_free(p);
375
376	return (0);
377}
378
379static int
380load_repositories(const char *repodir)
381{
382	struct dirent *ent;
383	DIR *d;
384	char *p;
385	size_t n;
386	char path[MAXPATHLEN];
387	int ret;
388
389	ret = 0;
390
391	if ((d = opendir(repodir)) == NULL)
392		return (1);
393
394	while ((ent = readdir(d))) {
395		/* Trim out 'repos'. */
396		if ((n = strlen(ent->d_name)) <= 5)
397			continue;
398		p = &ent->d_name[n - 5];
399		if (strcmp(p, ".conf") == 0) {
400			snprintf(path, sizeof(path), "%s%s%s",
401			    repodir,
402			    repodir[strlen(repodir) - 1] == '/' ? "" : "/",
403			    ent->d_name);
404			if (access(path, F_OK) == 0 &&
405			    read_conf_file(path, CONFFILE_REPO)) {
406				ret = 1;
407				goto cleanup;
408			}
409		}
410	}
411
412cleanup:
413	closedir(d);
414
415	return (ret);
416}
417
418int
419config_init(void)
420{
421	char *val;
422	int i;
423	const char *localbase;
424	char *env_list_item;
425	char confpath[MAXPATHLEN];
426	struct config_value *cv;
427	char abi[BUFSIZ];
428
429	for (i = 0; i < CONFIG_SIZE; i++) {
430		val = getenv(c[i].key);
431		if (val != NULL) {
432			c[i].envset = true;
433			switch (c[i].type) {
434			case PKG_CONFIG_LIST:
435				/* Split up comma-separated items from env. */
436				c[i].list = malloc(sizeof(*c[i].list));
437				STAILQ_INIT(c[i].list);
438				for (env_list_item = strtok(val, ",");
439				    env_list_item != NULL;
440				    env_list_item = strtok(NULL, ",")) {
441					cv =
442					    malloc(sizeof(struct config_value));
443					cv->value =
444					    strdup(env_list_item);
445					STAILQ_INSERT_TAIL(c[i].list, cv,
446					    next);
447				}
448				break;
449			default:
450				c[i].val = val;
451				break;
452			}
453		}
454	}
455
456	/* Read LOCALBASE/etc/pkg.conf first. */
457	localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE;
458	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf",
459	    localbase);
460
461	if (access(confpath, F_OK) == 0 && read_conf_file(confpath,
462	    CONFFILE_PKG))
463		goto finalize;
464
465	/* Then read in all repos from REPOS_DIR list of directories. */
466	if (c[REPOS_DIR].list == NULL) {
467		c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
468		STAILQ_INIT(c[REPOS_DIR].list);
469		cv = malloc(sizeof(struct config_value));
470		cv->value = strdup("/etc/pkg");
471		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
472		cv = malloc(sizeof(struct config_value));
473		if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
474			goto finalize;
475		STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
476	}
477
478	STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
479		if (load_repositories(cv->value))
480			goto finalize;
481
482finalize:
483	if (c[ABI].val == NULL && c[ABI].value == NULL) {
484		if (pkg_get_myabi(abi, BUFSIZ) != 0)
485			errx(EXIT_FAILURE, "Failed to determine the system "
486			    "ABI");
487		c[ABI].val = abi;
488	}
489
490	subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
491
492	return (0);
493}
494
495int
496config_string(pkg_config_key k, const char **val)
497{
498	if (c[k].type != PKG_CONFIG_STRING)
499		return (-1);
500
501	if (c[k].value != NULL)
502		*val = c[k].value;
503	else
504		*val = c[k].val;
505
506	return (0);
507}
508
509int
510config_bool(pkg_config_key k, bool *val)
511{
512	const char *value;
513
514	if (c[k].type != PKG_CONFIG_BOOL)
515		return (-1);
516
517	*val = false;
518
519	if (c[k].value != NULL)
520		value = c[k].value;
521	else
522		value = c[k].val;
523
524	if (boolstr_to_bool(value))
525		*val = true;
526
527	return (0);
528}
529
530void
531config_finish(void) {
532	int i;
533
534	for (i = 0; i < CONFIG_SIZE; i++)
535		free(c[i].value);
536}
537