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