1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014-2025 Baptiste Daroussin <bapt@FreeBSD.org>
5 * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/param.h>
30 #include <sys/queue.h>
31 #include <sys/utsname.h>
32 #include <sys/sysctl.h>
33
34 #include <dirent.h>
35 #include <ucl.h>
36 #include <err.h>
37 #include <errno.h>
38 #include <libutil.h>
39 #include <paths.h>
40 #include <stdbool.h>
41 #include <unistd.h>
42 #include <ctype.h>
43
44 #include "config.h"
45
46 struct config_value {
47 char *value;
48 STAILQ_ENTRY(config_value) next;
49 };
50
51 struct config_entry {
52 uint8_t type;
53 const char *key;
54 const char *val;
55 char *value;
56 STAILQ_HEAD(, config_value) *list;
57 bool envset;
58 bool main_only; /* Only set in pkg.conf. */
59 };
60
61 static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories);
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 char *
pkg_get_myabi(void)148 pkg_get_myabi(void)
149 {
150 struct utsname uts;
151 char machine_arch[255];
152 char *abi;
153 size_t len;
154 int error;
155
156 error = uname(&uts);
157 if (error)
158 return (NULL);
159
160 len = sizeof(machine_arch);
161 error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
162 if (error)
163 return (NULL);
164 machine_arch[len] = '\0';
165
166 /*
167 * Use __FreeBSD_version rather than kernel version (uts.release) for
168 * use in jails. This is equivalent to the value of uname -U.
169 */
170 error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
171 machine_arch);
172 if (error < 0)
173 return (NULL);
174
175 return (abi);
176 }
177
178 static void
subst_packagesite(const char * abi)179 subst_packagesite(const char *abi)
180 {
181 char *newval;
182 const char *variable_string;
183 const char *oldval;
184
185 if (c[PACKAGESITE].value != NULL)
186 oldval = c[PACKAGESITE].value;
187 else
188 oldval = c[PACKAGESITE].val;
189
190 if ((variable_string = strstr(oldval, "${ABI}")) == NULL)
191 return;
192
193 asprintf(&newval, "%.*s%s%s",
194 (int)(variable_string - oldval), oldval, abi,
195 variable_string + strlen("${ABI}"));
196 if (newval == NULL)
197 errx(EXIT_FAILURE, "asprintf");
198
199 free(c[PACKAGESITE].value);
200 c[PACKAGESITE].value = newval;
201 }
202
203 static int
boolstr_to_bool(const char * str)204 boolstr_to_bool(const char *str)
205 {
206 if (str != NULL && (strcasecmp(str, "true") == 0 ||
207 strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 ||
208 str[0] == '1'))
209 return (true);
210
211 return (false);
212 }
213
214 static void
config_parse(const ucl_object_t * obj)215 config_parse(const ucl_object_t *obj)
216 {
217 FILE *buffp;
218 char *buf = NULL;
219 size_t bufsz = 0;
220 const ucl_object_t *cur, *seq, *tmp;
221 ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL;
222 struct config_entry *temp_config;
223 struct config_value *cv;
224 const char *key, *evkey;
225 int i;
226 size_t j;
227
228 /* Temporary config for configs that may be disabled. */
229 temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry));
230 buffp = open_memstream(&buf, &bufsz);
231 if (buffp == NULL)
232 err(EXIT_FAILURE, "open_memstream()");
233
234 while ((cur = ucl_iterate_object(obj, &it, true))) {
235 key = ucl_object_key(cur);
236 if (key == NULL)
237 continue;
238 if (buf != NULL)
239 memset(buf, 0, bufsz);
240 rewind(buffp);
241
242 for (j = 0; j < strlen(key); ++j)
243 fputc(toupper(key[j]), buffp);
244 fflush(buffp);
245
246 for (i = 0; i < CONFIG_SIZE; i++) {
247 if (strcmp(buf, c[i].key) == 0)
248 break;
249 }
250
251 /* Silently skip unknown keys to be future compatible. */
252 if (i == CONFIG_SIZE)
253 continue;
254
255 /* env has priority over config file */
256 if (c[i].envset)
257 continue;
258
259 /* Parse sequence value ["item1", "item2"] */
260 switch (c[i].type) {
261 case PKG_CONFIG_LIST:
262 if (cur->type != UCL_ARRAY) {
263 warnx("Skipping invalid array "
264 "value for %s.\n", c[i].key);
265 continue;
266 }
267 temp_config[i].list =
268 malloc(sizeof(*temp_config[i].list));
269 STAILQ_INIT(temp_config[i].list);
270
271 while ((seq = ucl_iterate_object(cur, &itseq, true))) {
272 if (seq->type != UCL_STRING)
273 continue;
274 cv = malloc(sizeof(struct config_value));
275 cv->value =
276 strdup(ucl_object_tostring(seq));
277 STAILQ_INSERT_TAIL(temp_config[i].list, cv,
278 next);
279 }
280 break;
281 case PKG_CONFIG_BOOL:
282 temp_config[i].value =
283 strdup(ucl_object_toboolean(cur) ? "yes" : "no");
284 break;
285 case PKG_CONFIG_OBJECT:
286 if (strcmp(c[i].key, "PKG_ENV") == 0) {
287 while ((tmp =
288 ucl_iterate_object(cur, &it_obj, true))) {
289 evkey = ucl_object_key(tmp);
290 if (evkey != NULL && *evkey != '\0') {
291 setenv(evkey, ucl_object_tostring_forced(tmp), 1);
292 }
293 }
294 }
295 break;
296 default:
297 /* Normal string value. */
298 temp_config[i].value = strdup(ucl_object_tostring(cur));
299 break;
300 }
301 }
302
303 /* Repo is enabled, copy over all settings from temp_config. */
304 for (i = 0; i < CONFIG_SIZE; i++) {
305 if (c[i].envset)
306 continue;
307 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
308 if (c[i].main_only == true)
309 continue;
310 switch (c[i].type) {
311 case PKG_CONFIG_LIST:
312 c[i].list = temp_config[i].list;
313 break;
314 default:
315 c[i].value = temp_config[i].value;
316 break;
317 }
318 }
319
320 free(temp_config);
321 fclose(buffp);
322 free(buf);
323 }
324
325
326 static void
parse_mirror_type(struct repository * r,const char * mt)327 parse_mirror_type(struct repository *r, const char *mt)
328 {
329 if (strcasecmp(mt, "srv") == 0)
330 r->mirror_type = MIRROR_SRV;
331 else
332 r->mirror_type = MIRROR_NONE;
333 }
334
335 static void
repo_free(struct repository * r)336 repo_free(struct repository *r)
337 {
338 free(r->name);
339 free(r->url);
340 free(r->fingerprints);
341 free(r->pubkey);
342 free(r);
343 }
344
345 static bool
parse_signature_type(struct repository * repo,const char * st)346 parse_signature_type(struct repository *repo, const char *st)
347 {
348 if (strcasecmp(st, "FINGERPRINTS") == 0)
349 repo->signature_type = SIGNATURE_FINGERPRINT;
350 else if (strcasecmp(st, "PUBKEY") == 0)
351 repo->signature_type = SIGNATURE_PUBKEY;
352 else if (strcasecmp(st, "NONE") == 0)
353 repo->signature_type = SIGNATURE_NONE;
354 else {
355 warnx("Signature type %s is not supported for bootstrapping,"
356 " ignoring repository %s", st, repo->name);
357 return (false);
358 }
359 return (true);
360 }
361
362 static struct repository *
find_repository(const char * name)363 find_repository(const char *name)
364 {
365 struct repository *repo;
366 STAILQ_FOREACH(repo, &repositories, next) {
367 if (strcmp(repo->name, name) == 0)
368 return (repo);
369 }
370 return (NULL);
371 }
372
373 static void
parse_repo(const ucl_object_t * o)374 parse_repo(const ucl_object_t *o)
375 {
376 const ucl_object_t *cur;
377 const char *key, *reponame;
378 ucl_object_iter_t it = NULL;
379 bool newrepo = false;
380 struct repository *repo;
381
382 reponame = ucl_object_key(o);
383 repo = find_repository(reponame);
384 if (repo == NULL) {
385 repo = calloc(1, sizeof(struct repository));
386 if (repo == NULL)
387 err(EXIT_FAILURE, "calloc");
388
389 repo->name = strdup(reponame);
390 if (repo->name == NULL)
391 err(EXIT_FAILURE, "strdup");
392 newrepo = true;
393 }
394 while ((cur = ucl_iterate_object(o, &it, true))) {
395 key = ucl_object_key(cur);
396 if (key == NULL)
397 continue;
398 if (strcasecmp(key, "url") == 0) {
399 free(repo->url);
400 repo->url = strdup(ucl_object_tostring(cur));
401 if (repo->url == NULL)
402 err(EXIT_FAILURE, "strdup");
403 } else if (strcasecmp(key, "mirror_type") == 0) {
404 parse_mirror_type(repo, ucl_object_tostring(cur));
405 } else if (strcasecmp(key, "signature_type") == 0) {
406 if (!parse_signature_type(repo, ucl_object_tostring(cur))) {
407 if (newrepo)
408 repo_free(repo);
409 else
410 STAILQ_REMOVE(&repositories, repo, repository, next);
411 return;
412 }
413 } else if (strcasecmp(key, "fingerprints") == 0) {
414 free(repo->fingerprints);
415 repo->fingerprints = strdup(ucl_object_tostring(cur));
416 if (repo->fingerprints == NULL)
417 err(EXIT_FAILURE, "strdup");
418 } else if (strcasecmp(key, "pubkey") == 0) {
419 free(repo->pubkey);
420 repo->pubkey = strdup(ucl_object_tostring(cur));
421 if (repo->pubkey == NULL)
422 err(EXIT_FAILURE, "strdup");
423 } else if (strcasecmp(key, "enabled") == 0) {
424 if ((cur->type != UCL_BOOLEAN) ||
425 !ucl_object_toboolean(cur)) {
426 if (newrepo)
427 repo_free(repo);
428 else
429 STAILQ_REMOVE(&repositories, repo, repository, next);
430 return;
431 }
432 }
433 }
434 /* At least we need an url */
435 if (repo->url == NULL) {
436 repo_free(repo);
437 return;
438 }
439 if (newrepo)
440 STAILQ_INSERT_TAIL(&repositories, repo, next);
441 return;
442 }
443
444 /*-
445 * Parse new repo style configs in style:
446 * Name:
447 * URL:
448 * MIRROR_TYPE:
449 * etc...
450 */
451 static void
parse_repo_file(ucl_object_t * obj,const char * requested_repo)452 parse_repo_file(ucl_object_t *obj, const char *requested_repo)
453 {
454 ucl_object_iter_t it = NULL;
455 const ucl_object_t *cur;
456 const char *key;
457
458 while ((cur = ucl_iterate_object(obj, &it, true))) {
459 key = ucl_object_key(cur);
460
461 if (key == NULL)
462 continue;
463
464 if (cur->type != UCL_OBJECT)
465 continue;
466
467 if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
468 continue;
469 parse_repo(cur);
470 }
471 }
472
473
474 static int
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)475 read_conf_file(const char *confpath, const char *requested_repo,
476 pkg_conf_file_t conftype)
477 {
478 struct ucl_parser *p;
479 ucl_object_t *obj = NULL;
480 char *abi = pkg_get_myabi(), *major, *minor;
481 struct utsname uts;
482 int ret;
483
484 if (uname(&uts))
485 err(EXIT_FAILURE, "uname");
486 if (abi == NULL)
487 errx(EXIT_FAILURE, "Failed to determine ABI");
488
489 p = ucl_parser_new(0);
490 asprintf(&major, "%d", __FreeBSD_version/100000);
491 if (major == NULL)
492 err(EXIT_FAILURE, "asprintf");
493 asprintf(&minor, "%d", (__FreeBSD_version / 1000) % 100);
494 if (minor == NULL)
495 err(EXIT_FAILURE, "asprintf");
496 ucl_parser_register_variable(p, "ABI", abi);
497 ucl_parser_register_variable(p, "OSNAME", uts.sysname);
498 ucl_parser_register_variable(p, "RELEASE", major);
499 ucl_parser_register_variable(p, "VERSION_MAJOR", major);
500 ucl_parser_register_variable(p, "VERSION_MINOR", minor);
501
502 if (!ucl_parser_add_file(p, confpath)) {
503 if (errno != ENOENT)
504 errx(EXIT_FAILURE, "Unable to parse configuration "
505 "file %s: %s", confpath, ucl_parser_get_error(p));
506 /* no configuration present */
507 ret = 1;
508 goto out;
509 }
510
511 obj = ucl_parser_get_object(p);
512 if (obj->type != UCL_OBJECT)
513 warnx("Invalid configuration format, ignoring the "
514 "configuration file %s", confpath);
515 else {
516 if (conftype == CONFFILE_PKG)
517 config_parse(obj);
518 else if (conftype == CONFFILE_REPO)
519 parse_repo_file(obj, requested_repo);
520 }
521 ucl_object_unref(obj);
522
523 ret = 0;
524 out:
525 ucl_parser_free(p);
526 free(abi);
527 free(major);
528 free(minor);
529
530 return (ret);
531 }
532
533 static void
load_repositories(const char * repodir,const char * requested_repo)534 load_repositories(const char *repodir, const char *requested_repo)
535 {
536 struct dirent *ent;
537 DIR *d;
538 char *p;
539 size_t n;
540 char path[MAXPATHLEN];
541
542 if ((d = opendir(repodir)) == NULL)
543 return;
544
545 while ((ent = readdir(d))) {
546 /* Trim out 'repos'. */
547 if ((n = strlen(ent->d_name)) <= 5)
548 continue;
549 p = &ent->d_name[n - 5];
550 if (strcmp(p, ".conf") == 0) {
551 snprintf(path, sizeof(path), "%s%s%s",
552 repodir,
553 repodir[strlen(repodir) - 1] == '/' ? "" : "/",
554 ent->d_name);
555 if (access(path, F_OK) != 0)
556 continue;
557 if (read_conf_file(path, requested_repo,
558 CONFFILE_REPO)) {
559 goto cleanup;
560 }
561 }
562 }
563
564 cleanup:
565 closedir(d);
566 }
567
568 int
config_init(const char * requested_repo)569 config_init(const char *requested_repo)
570 {
571 char *val;
572 int i;
573 const char *localbase;
574 char *abi, *env_list_item;
575 char confpath[MAXPATHLEN];
576 struct config_value *cv;
577
578 for (i = 0; i < CONFIG_SIZE; i++) {
579 val = getenv(c[i].key);
580 if (val != NULL) {
581 c[i].envset = true;
582 switch (c[i].type) {
583 case PKG_CONFIG_LIST:
584 /* Split up comma-separated items from env. */
585 c[i].list = malloc(sizeof(*c[i].list));
586 STAILQ_INIT(c[i].list);
587 for (env_list_item = strtok(val, ",");
588 env_list_item != NULL;
589 env_list_item = strtok(NULL, ",")) {
590 cv =
591 malloc(sizeof(struct config_value));
592 cv->value =
593 strdup(env_list_item);
594 STAILQ_INSERT_TAIL(c[i].list, cv,
595 next);
596 }
597 break;
598 default:
599 c[i].val = val;
600 break;
601 }
602 }
603 }
604
605 /* Read LOCALBASE/etc/pkg.conf first. */
606 localbase = getlocalbase();
607 snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
608
609 if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
610 CONFFILE_PKG))
611 goto finalize;
612
613 /* Then read in all repos from REPOS_DIR list of directories. */
614 if (c[REPOS_DIR].list == NULL) {
615 c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
616 STAILQ_INIT(c[REPOS_DIR].list);
617 cv = malloc(sizeof(struct config_value));
618 cv->value = strdup("/etc/pkg");
619 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
620 cv = malloc(sizeof(struct config_value));
621 if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
622 goto finalize;
623 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
624 }
625
626 STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
627 load_repositories(cv->value, requested_repo);
628
629 finalize:
630 if (c[ABI].val == NULL && c[ABI].value == NULL) {
631 abi = pkg_get_myabi();
632 if (abi == NULL)
633 errx(EXIT_FAILURE, "Failed to determine the system "
634 "ABI");
635 c[ABI].val = abi;
636 }
637
638 return (0);
639 }
640
641 int
config_string(pkg_config_key k,const char ** val)642 config_string(pkg_config_key k, const char **val)
643 {
644 if (c[k].type != PKG_CONFIG_STRING)
645 return (-1);
646
647 if (c[k].value != NULL)
648 *val = c[k].value;
649 else
650 *val = c[k].val;
651
652 return (0);
653 }
654
655 int
config_bool(pkg_config_key k,bool * val)656 config_bool(pkg_config_key k, bool *val)
657 {
658 const char *value;
659
660 if (c[k].type != PKG_CONFIG_BOOL)
661 return (-1);
662
663 *val = false;
664
665 if (c[k].value != NULL)
666 value = c[k].value;
667 else
668 value = c[k].val;
669
670 if (boolstr_to_bool(value))
671 *val = true;
672
673 return (0);
674 }
675
676 struct repositories *
config_get_repositories(void)677 config_get_repositories(void)
678 {
679 if (STAILQ_EMPTY(&repositories)) {
680 /* Fall back to PACKAGESITE - deprecated - */
681 struct repository *r = calloc(1, sizeof(*r));
682 if (r == NULL)
683 err(EXIT_FAILURE, "calloc");
684 r->name = strdup("fallback");
685 if (r->name == NULL)
686 err(EXIT_FAILURE, "strdup");
687 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
688 r->url = c[PACKAGESITE].value;
689 if (c[SIGNATURE_TYPE].value != NULL)
690 if (!parse_signature_type(r, c[SIGNATURE_TYPE].value))
691 exit(EXIT_FAILURE);
692 if (c[MIRROR_TYPE].value != NULL)
693 parse_mirror_type(r, c[MIRROR_TYPE].value);
694 if (c[PUBKEY].value != NULL)
695 r->pubkey = c[PUBKEY].value;
696 if (c[FINGERPRINTS].value != NULL)
697 r->fingerprints = c[FINGERPRINTS].value;
698 STAILQ_INSERT_TAIL(&repositories, r, next);
699 }
700 return (&repositories);
701 }
702
703 void
config_finish(void)704 config_finish(void) {
705 int i;
706
707 for (i = 0; i < CONFIG_SIZE; i++)
708 free(c[i].value);
709 }
710