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/12/usr.sbin/pkg/config.c 369731 2021-05-05 08:26:52Z bapt $");
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
48 struct config_value {
49 char *value;
50 STAILQ_ENTRY(config_value) next;
51 };
52
53 struct 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
63 static 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
147 static int
pkg_get_myabi(char * dest,size_t sz)148 pkg_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
175 static void
subst_packagesite(const char * abi)176 subst_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
200 static int
boolstr_to_bool(const char * str)201 boolstr_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
211 static void
config_parse(const ucl_object_t * obj,pkg_conf_file_t conftype)212 config_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
337 cleanup:
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 */
350 static void
parse_repo_file(ucl_object_t * obj,const char * requested_repo)351 parse_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
374 static int
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)375 read_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
409 static int
load_repositories(const char * repodir,const char * requested_repo)410 load_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
444 cleanup:
445 closedir(d);
446
447 return (ret);
448 }
449
450 int
config_init(const char * requested_repo)451 config_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
514 finalize:
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
527 int
config_string(pkg_config_key k,const char ** val)528 config_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
541 int
config_bool(pkg_config_key k,bool * val)542 config_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
562 void
config_finish(void)563 config_finish(void) {
564 int i;
565
566 for (i = 0; i < CONFIG_SIZE; i++)
567 free(c[i].value);
568 }
569