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