xref: /freebsd/usr.sbin/pkg/config.c (revision 39ee7a7a6bdd1557b1c3532abf60d139798ac88b)
1 /*-
2  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
3  * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/queue.h>
33 #include <sys/sbuf.h>
34 #include <sys/types.h>
35 #include <sys/utsname.h>
36 #include <sys/sysctl.h>
37 
38 #include <assert.h>
39 #include <dirent.h>
40 #include <ucl.h>
41 #include <ctype.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <inttypes.h>
46 #include <paths.h>
47 #include <stdbool.h>
48 #include <string.h>
49 #include <unistd.h>
50 
51 #include "config.h"
52 
53 #define roundup2(x, y)	(((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */
54 
55 struct config_value {
56        char *value;
57        STAILQ_ENTRY(config_value) next;
58 };
59 
60 struct config_entry {
61 	uint8_t type;
62 	const char *key;
63 	const char *val;
64 	char *value;
65 	STAILQ_HEAD(, config_value) *list;
66 	bool envset;
67 	bool main_only;				/* Only set in pkg.conf. */
68 };
69 
70 static struct config_entry c[] = {
71 	[PACKAGESITE] = {
72 		PKG_CONFIG_STRING,
73 		"PACKAGESITE",
74 		URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
75 		NULL,
76 		NULL,
77 		false,
78 		false,
79 	},
80 	[ABI] = {
81 		PKG_CONFIG_STRING,
82 		"ABI",
83 		NULL,
84 		NULL,
85 		NULL,
86 		false,
87 		true,
88 	},
89 	[MIRROR_TYPE] = {
90 		PKG_CONFIG_STRING,
91 		"MIRROR_TYPE",
92 		"SRV",
93 		NULL,
94 		NULL,
95 		false,
96 		false,
97 	},
98 	[ASSUME_ALWAYS_YES] = {
99 		PKG_CONFIG_BOOL,
100 		"ASSUME_ALWAYS_YES",
101 		"NO",
102 		NULL,
103 		NULL,
104 		false,
105 		true,
106 	},
107 	[SIGNATURE_TYPE] = {
108 		PKG_CONFIG_STRING,
109 		"SIGNATURE_TYPE",
110 		NULL,
111 		NULL,
112 		NULL,
113 		false,
114 		false,
115 	},
116 	[FINGERPRINTS] = {
117 		PKG_CONFIG_STRING,
118 		"FINGERPRINTS",
119 		NULL,
120 		NULL,
121 		NULL,
122 		false,
123 		false,
124 	},
125 	[REPOS_DIR] = {
126 		PKG_CONFIG_LIST,
127 		"REPOS_DIR",
128 		NULL,
129 		NULL,
130 		NULL,
131 		false,
132 		true,
133 	},
134 	[PUBKEY] = {
135 		PKG_CONFIG_STRING,
136 		"PUBKEY",
137 		NULL,
138 		NULL,
139 		NULL,
140 		false,
141 		false
142 	}
143 };
144 
145 static int
146 pkg_get_myabi(char *dest, size_t sz)
147 {
148 	struct utsname uts;
149 	char machine_arch[255];
150 	size_t len;
151 	int error;
152 
153 	error = uname(&uts);
154 	if (error)
155 		return (errno);
156 
157 	len = sizeof(machine_arch);
158 	error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
159 	if (error)
160 		return (errno);
161 	machine_arch[len] = '\0';
162 
163 	/*
164 	 * Use __FreeBSD_version rather than kernel version (uts.release) for
165 	 * use in jails. This is equivalent to the value of uname -U.
166 	 */
167 	snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
168 	    machine_arch);
169 
170 	return (error);
171 }
172 
173 static void
174 subst_packagesite(const char *abi)
175 {
176 	struct sbuf *newval;
177 	const char *variable_string;
178 	const char *oldval;
179 
180 	if (c[PACKAGESITE].value != NULL)
181 		oldval = c[PACKAGESITE].value;
182 	else
183 		oldval = c[PACKAGESITE].val;
184 
185 	if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
186 		return;
187 
188 	newval = sbuf_new_auto();
189 	sbuf_bcat(newval, oldval, variable_string - oldval);
190 	sbuf_cat(newval, abi);
191 	sbuf_cat(newval, variable_string + strlen("${ABI}"));
192 	sbuf_finish(newval);
193 
194 	free(c[PACKAGESITE].value);
195 	c[PACKAGESITE].value = strdup(sbuf_data(newval));
196 }
197 
198 static int
199 boolstr_to_bool(const char *str)
200 {
201 	if (str != NULL && (strcasecmp(str, "true") == 0 ||
202 	    strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
203 	    str[0] == '1'))
204 		return (true);
205 
206 	return (false);
207 }
208 
209 static void
210 config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype)
211 {
212 	struct sbuf *buf = sbuf_new_auto();
213 	const ucl_object_t *cur, *seq;
214 	ucl_object_iter_t it = NULL, itseq = NULL;
215 	struct config_entry *temp_config;
216 	struct config_value *cv;
217 	const char *key;
218 	int i;
219 	size_t j;
220 
221 	/* Temporary config for configs that may be disabled. */
222 	temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
223 
224 	while ((cur = ucl_iterate_object(obj, &it, true))) {
225 		key = ucl_object_key(cur);
226 		if (key == NULL)
227 			continue;
228 		sbuf_clear(buf);
229 
230 		if (conftype == CONFFILE_PKG) {
231 			for (j = 0; j < strlen(key); ++j)
232 				sbuf_putc(buf, key[j]);
233 			sbuf_finish(buf);
234 		} else if (conftype == CONFFILE_REPO) {
235 			if (strcasecmp(key, "url") == 0)
236 				sbuf_cpy(buf, "PACKAGESITE");
237 			else if (strcasecmp(key, "mirror_type") == 0)
238 				sbuf_cpy(buf, "MIRROR_TYPE");
239 			else if (strcasecmp(key, "signature_type") == 0)
240 				sbuf_cpy(buf, "SIGNATURE_TYPE");
241 			else if (strcasecmp(key, "fingerprints") == 0)
242 				sbuf_cpy(buf, "FINGERPRINTS");
243 			else if (strcasecmp(key, "pubkey") == 0)
244 				sbuf_cpy(buf, "PUBKEY");
245 			else if (strcasecmp(key, "enabled") == 0) {
246 				if ((cur->type != UCL_BOOLEAN) ||
247 				    !ucl_object_toboolean(cur))
248 					goto cleanup;
249 			} else
250 				continue;
251 			sbuf_finish(buf);
252 		}
253 
254 		for (i = 0; i < CONFIG_SIZE; i++) {
255 			if (strcmp(sbuf_data(buf), c[i].key) == 0)
256 				break;
257 		}
258 
259 		/* Silently skip unknown keys to be future compatible. */
260 		if (i == CONFIG_SIZE)
261 			continue;
262 
263 		/* env has priority over config file */
264 		if (c[i].envset)
265 			continue;
266 
267 		/* Parse sequence value ["item1", "item2"] */
268 		switch (c[i].type) {
269 		case PKG_CONFIG_LIST:
270 			if (cur->type != UCL_ARRAY) {
271 				warnx("Skipping invalid array "
272 				    "value for %s.\n", c[i].key);
273 				continue;
274 			}
275 			temp_config[i].list =
276 			    malloc(sizeof(*temp_config[i].list));
277 			STAILQ_INIT(temp_config[i].list);
278 
279 			while ((seq = ucl_iterate_object(cur, &itseq, true))) {
280 				if (seq->type != UCL_STRING)
281 					continue;
282 				cv = malloc(sizeof(struct config_value));
283 				cv->value =
284 				    strdup(ucl_object_tostring(seq));
285 				STAILQ_INSERT_TAIL(temp_config[i].list, cv,
286 				    next);
287 			}
288 			break;
289 		case PKG_CONFIG_BOOL:
290 			temp_config[i].value =
291 			    strdup(ucl_object_toboolean(cur) ? "yes" : "no");
292 			break;
293 		default:
294 			/* Normal string value. */
295 			temp_config[i].value = strdup(ucl_object_tostring(cur));
296 			break;
297 		}
298 	}
299 
300 	/* Repo is enabled, copy over all settings from temp_config. */
301 	for (i = 0; i < CONFIG_SIZE; i++) {
302 		if (c[i].envset)
303 			continue;
304 		/* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
305 		if (conftype != CONFFILE_PKG && c[i].main_only == true)
306 			continue;
307 		switch (c[i].type) {
308 		case PKG_CONFIG_LIST:
309 			c[i].list = temp_config[i].list;
310 			break;
311 		default:
312 			c[i].value = temp_config[i].value;
313 			break;
314 		}
315 	}
316 
317 cleanup:
318 	free(temp_config);
319 	sbuf_delete(buf);
320 }
321 
322 /*-
323  * Parse new repo style configs in style:
324  * Name:
325  *   URL:
326  *   MIRROR_TYPE:
327  * etc...
328  */
329 static void
330 parse_repo_file(ucl_object_t *obj)
331 {
332 	ucl_object_iter_t it = NULL;
333 	const ucl_object_t *cur;
334 	const char *key;
335 
336 	while ((cur = ucl_iterate_object(obj, &it, true))) {
337 		key = ucl_object_key(cur);
338 
339 		if (key == NULL)
340 			continue;
341 
342 		if (cur->type != UCL_OBJECT)
343 			continue;
344 
345 		config_parse(cur, CONFFILE_REPO);
346 	}
347 }
348 
349 
350 static int
351 read_conf_file(const char *confpath, pkg_conf_file_t conftype)
352 {
353 	struct ucl_parser *p;
354 	ucl_object_t *obj = NULL;
355 
356 	p = ucl_parser_new(0);
357 
358 	if (!ucl_parser_add_file(p, confpath)) {
359 		if (errno != ENOENT)
360 			errx(EXIT_FAILURE, "Unable to parse configuration "
361 			    "file %s: %s", confpath, ucl_parser_get_error(p));
362 		ucl_parser_free(p);
363 		/* no configuration present */
364 		return (1);
365 	}
366 
367 	obj = ucl_parser_get_object(p);
368 	if (obj->type != UCL_OBJECT)
369 		warnx("Invalid configuration format, ignoring the "
370 		    "configuration file %s", confpath);
371 	else {
372 		if (conftype == CONFFILE_PKG)
373 			config_parse(obj, conftype);
374 		else if (conftype == CONFFILE_REPO)
375 			parse_repo_file(obj);
376 	}
377 
378 	ucl_object_unref(obj);
379 	ucl_parser_free(p);
380 
381 	return (0);
382 }
383 
384 static int
385 load_repositories(const char *repodir)
386 {
387 	struct dirent *ent;
388 	DIR *d;
389 	char *p;
390 	size_t n;
391 	char path[MAXPATHLEN];
392 	int ret;
393 
394 	ret = 0;
395 
396 	if ((d = opendir(repodir)) == NULL)
397 		return (1);
398 
399 	while ((ent = readdir(d))) {
400 		/* Trim out 'repos'. */
401 		if ((n = strlen(ent->d_name)) <= 5)
402 			continue;
403 		p = &ent->d_name[n - 5];
404 		if (strcmp(p, ".conf") == 0) {
405 			snprintf(path, sizeof(path), "%s%s%s",
406 			    repodir,
407 			    repodir[strlen(repodir) - 1] == '/' ? "" : "/",
408 			    ent->d_name);
409 			if (access(path, F_OK) == 0 &&
410 			    read_conf_file(path, CONFFILE_REPO)) {
411 				ret = 1;
412 				goto cleanup;
413 			}
414 		}
415 	}
416 
417 cleanup:
418 	closedir(d);
419 
420 	return (ret);
421 }
422 
423 int
424 config_init(void)
425 {
426 	char *val;
427 	int i;
428 	const char *localbase;
429 	char *env_list_item;
430 	char confpath[MAXPATHLEN];
431 	struct config_value *cv;
432 	char abi[BUFSIZ];
433 
434 	for (i = 0; i < CONFIG_SIZE; i++) {
435 		val = getenv(c[i].key);
436 		if (val != NULL) {
437 			c[i].envset = true;
438 			switch (c[i].type) {
439 			case PKG_CONFIG_LIST:
440 				/* Split up comma-separated items from env. */
441 				c[i].list = malloc(sizeof(*c[i].list));
442 				STAILQ_INIT(c[i].list);
443 				for (env_list_item = strtok(val, ",");
444 				    env_list_item != NULL;
445 				    env_list_item = strtok(NULL, ",")) {
446 					cv =
447 					    malloc(sizeof(struct config_value));
448 					cv->value =
449 					    strdup(env_list_item);
450 					STAILQ_INSERT_TAIL(c[i].list, cv,
451 					    next);
452 				}
453 				break;
454 			default:
455 				c[i].val = val;
456 				break;
457 			}
458 		}
459 	}
460 
461 	/* Read LOCALBASE/etc/pkg.conf first. */
462 	localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE;
463 	snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf",
464 	    localbase);
465 
466 	if (access(confpath, F_OK) == 0 && read_conf_file(confpath,
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))
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