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 * 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 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 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 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 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 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 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 * 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 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 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 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 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 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 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 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 * 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 704 config_finish(void) { 705 int i; 706 707 for (i = 0; i < CONFIG_SIZE; i++) 708 free(c[i].value); 709 } 710