1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
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/param.h>
31#include <sys/queue.h>
32#include <sys/utsname.h>
33#include <sys/sysctl.h>
34
35#include <dirent.h>
36#include <ucl.h>
37#include <err.h>
38#include <errno.h>
39#include <libutil.h>
40#include <paths.h>
41#include <stdbool.h>
42#include <unistd.h>
43#include <ctype.h>
44
45#include "config.h"
46
47struct config_value {
48	char *value;
49	STAILQ_ENTRY(config_value) next;
50};
51
52struct config_entry {
53	uint8_t type;
54	const char *key;
55	const char *val;
56	char *value;
57	STAILQ_HEAD(, config_value) *list;
58	bool envset;
59	bool main_only;				/* Only set in pkg.conf. */
60};
61
62static struct config_entry c[] = {
63	[PACKAGESITE] = {
64		PKG_CONFIG_STRING,
65		"PACKAGESITE",
66		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
67		NULL,
68		NULL,
69		false,
70		false,
71	},
72	[ABI] = {
73		PKG_CONFIG_STRING,
74		"ABI",
75		NULL,
76		NULL,
77		NULL,
78		false,
79		true,
80	},
81	[MIRROR_TYPE] = {
82		PKG_CONFIG_STRING,
83		"MIRROR_TYPE",
84		"SRV",
85		NULL,
86		NULL,
87		false,
88		false,
89	},
90	[ASSUME_ALWAYS_YES] = {
91		PKG_CONFIG_BOOL,
92		"ASSUME_ALWAYS_YES",
93		"NO",
94		NULL,
95		NULL,
96		false,
97		true,
98	},
99	[SIGNATURE_TYPE] = {
100		PKG_CONFIG_STRING,
101		"SIGNATURE_TYPE",
102		NULL,
103		NULL,
104		NULL,
105		false,
106		false,
107	},
108	[FINGERPRINTS] = {
109		PKG_CONFIG_STRING,
110		"FINGERPRINTS",
111		NULL,
112		NULL,
113		NULL,
114		false,
115		false,
116	},
117	[REPOS_DIR] = {
118		PKG_CONFIG_LIST,
119		"REPOS_DIR",
120		NULL,
121		NULL,
122		NULL,
123		false,
124		true,
125	},
126	[PUBKEY] = {
127		PKG_CONFIG_STRING,
128		"PUBKEY",
129		NULL,
130		NULL,
131		NULL,
132		false,
133		false
134	},
135	[PKG_ENV] = {
136		PKG_CONFIG_OBJECT,
137		"PKG_ENV",
138		NULL,
139		NULL,
140		NULL,
141		false,
142		false,
143	}
144};
145
146static char *
147pkg_get_myabi(void)
148{
149	struct utsname uts;
150	char machine_arch[255];
151	char *abi;
152	size_t len;
153	int error;
154
155	error = uname(&uts);
156	if (error)
157		return (NULL);
158
159	len = sizeof(machine_arch);
160	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
161	if (error)
162		return (NULL);
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	error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
170	    machine_arch);
171	if (error < 0)
172		return (NULL);
173
174	return (abi);
175}
176
177static void
178subst_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
202static int
203boolstr_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
213static void
214config_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
339cleanup:
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 */
352static void
353parse_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
376static int
377read_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
411static int
412load_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
446cleanup:
447	closedir(d);
448
449	return (ret);
450}
451
452int
453config_init(const char *requested_repo)
454{
455	char *val;
456	int i;
457	const char *localbase;
458	char *abi, *env_list_item;
459	char confpath[MAXPATHLEN];
460	struct config_value *cv;
461
462	for (i = 0; i < CONFIG_SIZE; i++) {
463		val = getenv(c[i].key);
464		if (val != NULL) {
465			c[i].envset = true;
466			switch (c[i].type) {
467			case PKG_CONFIG_LIST:
468				/* Split up comma-separated items from env. */
469				c[i].list = malloc(sizeof(*c[i].list));
470				STAILQ_INIT(c[i].list);
471				for (env_list_item = strtok(val, ",");
472				    env_list_item != NULL;
473				    env_list_item = strtok(NULL, ",")) {
474					cv =
475					    malloc(sizeof(struct config_value));
476					cv->value =
477					    strdup(env_list_item);
478					STAILQ_INSERT_TAIL(c[i].list, cv,
479					    next);
480				}
481				break;
482			default:
483				c[i].val = val;
484				break;
485			}
486		}
487	}
488
489	/* Read LOCALBASE/etc/pkg.conf first. */
490	localbase = getlocalbase();
491	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", 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		abi = pkg_get_myabi();
517		if (abi == NULL)
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
528int
529config_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
542int
543config_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
563void
564config_finish(void) {
565	int i;
566
567	for (i = 0; i < CONFIG_SIZE; i++)
568		free(c[i].value);
569}
570