1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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/param.h> 31 #include <sys/queue.h> 32 #include <sys/utsname.h> 33 #include <sys/sysctl.h> 34 35 #include <dirent.h> 36 #include <ucl.h> 37 #include <err.h> 38 #include <errno.h> 39 #include <libutil.h> 40 #include <paths.h> 41 #include <stdbool.h> 42 #include <unistd.h> 43 #include <ctype.h> 44 45 #include "config.h" 46 47 struct config_value { 48 char *value; 49 STAILQ_ENTRY(config_value) next; 50 }; 51 52 struct config_entry { 53 uint8_t type; 54 const char *key; 55 const char *val; 56 char *value; 57 STAILQ_HEAD(, config_value) *list; 58 bool envset; 59 bool main_only; /* Only set in pkg.conf. */ 60 }; 61 62 static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories); 63 64 static struct config_entry c[] = { 65 [PACKAGESITE] = { 66 PKG_CONFIG_STRING, 67 "PACKAGESITE", 68 URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", 69 NULL, 70 NULL, 71 false, 72 false, 73 }, 74 [ABI] = { 75 PKG_CONFIG_STRING, 76 "ABI", 77 NULL, 78 NULL, 79 NULL, 80 false, 81 true, 82 }, 83 [MIRROR_TYPE] = { 84 PKG_CONFIG_STRING, 85 "MIRROR_TYPE", 86 "SRV", 87 NULL, 88 NULL, 89 false, 90 false, 91 }, 92 [ASSUME_ALWAYS_YES] = { 93 PKG_CONFIG_BOOL, 94 "ASSUME_ALWAYS_YES", 95 "NO", 96 NULL, 97 NULL, 98 false, 99 true, 100 }, 101 [SIGNATURE_TYPE] = { 102 PKG_CONFIG_STRING, 103 "SIGNATURE_TYPE", 104 NULL, 105 NULL, 106 NULL, 107 false, 108 false, 109 }, 110 [FINGERPRINTS] = { 111 PKG_CONFIG_STRING, 112 "FINGERPRINTS", 113 NULL, 114 NULL, 115 NULL, 116 false, 117 false, 118 }, 119 [REPOS_DIR] = { 120 PKG_CONFIG_LIST, 121 "REPOS_DIR", 122 NULL, 123 NULL, 124 NULL, 125 false, 126 true, 127 }, 128 [PUBKEY] = { 129 PKG_CONFIG_STRING, 130 "PUBKEY", 131 NULL, 132 NULL, 133 NULL, 134 false, 135 false 136 }, 137 [PKG_ENV] = { 138 PKG_CONFIG_OBJECT, 139 "PKG_ENV", 140 NULL, 141 NULL, 142 NULL, 143 false, 144 false, 145 } 146 }; 147 148 static char * 149 pkg_get_myabi(void) 150 { 151 struct utsname uts; 152 char machine_arch[255]; 153 char *abi; 154 size_t len; 155 int error; 156 157 error = uname(&uts); 158 if (error) 159 return (NULL); 160 161 len = sizeof(machine_arch); 162 error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0); 163 if (error) 164 return (NULL); 165 machine_arch[len] = '\0'; 166 167 /* 168 * Use __FreeBSD_version rather than kernel version (uts.release) for 169 * use in jails. This is equivalent to the value of uname -U. 170 */ 171 error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000, 172 machine_arch); 173 if (error < 0) 174 return (NULL); 175 176 return (abi); 177 } 178 179 static void 180 subst_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 204 static int 205 boolstr_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 215 static void 216 config_parse(const ucl_object_t *obj) 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 for (j = 0; j < strlen(key); ++j) 244 fputc(toupper(key[j]), buffp); 245 fflush(buffp); 246 247 for (i = 0; i < CONFIG_SIZE; i++) { 248 if (strcmp(buf, c[i].key) == 0) 249 break; 250 } 251 252 /* Silently skip unknown keys to be future compatible. */ 253 if (i == CONFIG_SIZE) 254 continue; 255 256 /* env has priority over config file */ 257 if (c[i].envset) 258 continue; 259 260 /* Parse sequence value ["item1", "item2"] */ 261 switch (c[i].type) { 262 case PKG_CONFIG_LIST: 263 if (cur->type != UCL_ARRAY) { 264 warnx("Skipping invalid array " 265 "value for %s.\n", c[i].key); 266 continue; 267 } 268 temp_config[i].list = 269 malloc(sizeof(*temp_config[i].list)); 270 STAILQ_INIT(temp_config[i].list); 271 272 while ((seq = ucl_iterate_object(cur, &itseq, true))) { 273 if (seq->type != UCL_STRING) 274 continue; 275 cv = malloc(sizeof(struct config_value)); 276 cv->value = 277 strdup(ucl_object_tostring(seq)); 278 STAILQ_INSERT_TAIL(temp_config[i].list, cv, 279 next); 280 } 281 break; 282 case PKG_CONFIG_BOOL: 283 temp_config[i].value = 284 strdup(ucl_object_toboolean(cur) ? "yes" : "no"); 285 break; 286 case PKG_CONFIG_OBJECT: 287 if (strcmp(c[i].key, "PKG_ENV") == 0) { 288 while ((tmp = 289 ucl_iterate_object(cur, &it_obj, true))) { 290 evkey = ucl_object_key(tmp); 291 if (evkey != NULL && *evkey != '\0') { 292 setenv(evkey, ucl_object_tostring_forced(tmp), 1); 293 } 294 } 295 } 296 break; 297 default: 298 /* Normal string value. */ 299 temp_config[i].value = strdup(ucl_object_tostring(cur)); 300 break; 301 } 302 } 303 304 /* Repo is enabled, copy over all settings from temp_config. */ 305 for (i = 0; i < CONFIG_SIZE; i++) { 306 if (c[i].envset) 307 continue; 308 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */ 309 if (c[i].main_only == true) 310 continue; 311 switch (c[i].type) { 312 case PKG_CONFIG_LIST: 313 c[i].list = temp_config[i].list; 314 break; 315 default: 316 c[i].value = temp_config[i].value; 317 break; 318 } 319 } 320 321 free(temp_config); 322 fclose(buffp); 323 free(buf); 324 } 325 326 327 static void 328 parse_mirror_type(struct repository *r, const char *mt) 329 { 330 if (strcasecmp(mt, "srv") == 0) 331 r->mirror_type = MIRROR_SRV; 332 r->mirror_type = MIRROR_NONE; 333 } 334 335 static bool 336 parse_signature_type(struct repository *repo, const char *st) 337 { 338 if (strcasecmp(st, "FINGERPRINTS") == 0) 339 repo->signature_type = SIGNATURE_FINGERPRINT; 340 else if (strcasecmp(st, "PUBKEY") == 0) 341 repo->signature_type = SIGNATURE_PUBKEY; 342 else if (strcasecmp(st, "NONE") == 0) 343 repo->signature_type = SIGNATURE_NONE; 344 else { 345 warnx("Signature type %s is not supported for bootstrapping," 346 " ignoring repository %s", st, repo->name); 347 free(repo->url); 348 free(repo->name); 349 free(repo->fingerprints); 350 free(repo->pubkey); 351 free(repo); 352 return false; 353 } 354 return (true); 355 } 356 357 static void 358 parse_repo(const ucl_object_t *o) 359 { 360 const ucl_object_t *cur; 361 const char *key; 362 ucl_object_iter_t it = NULL; 363 364 struct repository *repo = calloc(1, sizeof(struct repository)); 365 if (repo == NULL) 366 err(EXIT_FAILURE, "calloc"); 367 368 repo->name = strdup(ucl_object_key(o)); 369 if (repo->name == NULL) 370 err(EXIT_FAILURE, "strdup"); 371 while ((cur = ucl_iterate_object(o, &it, true))) { 372 key = ucl_object_key(cur); 373 if (key == NULL) 374 continue; 375 if (strcasecmp(key, "url") == 0) { 376 repo->url = strdup(ucl_object_tostring(cur)); 377 if (repo->url == NULL) 378 err(EXIT_FAILURE, "strdup"); 379 } else if (strcasecmp(key, "mirror_type") == 0) { 380 parse_mirror_type(repo, ucl_object_tostring(cur)); 381 } else if (strcasecmp(key, "signature_type") == 0) { 382 if (!parse_signature_type(repo, ucl_object_tostring(cur))) 383 return; 384 } else if (strcasecmp(key, "fingerprints") == 0) { 385 repo->fingerprints = strdup(ucl_object_tostring(cur)); 386 if (repo->fingerprints == NULL) 387 err(EXIT_FAILURE, "strdup"); 388 } else if (strcasecmp(key, "pubkey") == 0) { 389 repo->pubkey = strdup(ucl_object_tostring(cur)); 390 if (repo->pubkey == NULL) 391 err(EXIT_FAILURE, "strdup"); 392 } else if (strcasecmp(key, "enabled") == 0) { 393 if ((cur->type != UCL_BOOLEAN) || 394 !ucl_object_toboolean(cur)) { 395 free(repo->url); 396 free(repo->name); 397 free(repo); 398 return; 399 } 400 } 401 } 402 STAILQ_INSERT_TAIL(&repositories, repo, next); 403 return; 404 } 405 406 /*- 407 * Parse new repo style configs in style: 408 * Name: 409 * URL: 410 * MIRROR_TYPE: 411 * etc... 412 */ 413 static void 414 parse_repo_file(ucl_object_t *obj, const char *requested_repo) 415 { 416 ucl_object_iter_t it = NULL; 417 const ucl_object_t *cur; 418 const char *key; 419 420 while ((cur = ucl_iterate_object(obj, &it, true))) { 421 key = ucl_object_key(cur); 422 423 if (key == NULL) 424 continue; 425 426 if (cur->type != UCL_OBJECT) 427 continue; 428 429 if (requested_repo != NULL && strcmp(requested_repo, key) != 0) 430 continue; 431 parse_repo(cur); 432 } 433 } 434 435 436 static int 437 read_conf_file(const char *confpath, const char *requested_repo, 438 pkg_conf_file_t conftype) 439 { 440 struct ucl_parser *p; 441 ucl_object_t *obj = NULL; 442 const char *abi = pkg_get_myabi(); 443 if (abi == NULL) 444 errx(EXIT_FAILURE, "Failed to determine ABI"); 445 446 p = ucl_parser_new(0); 447 ucl_parser_register_variable(p, "ABI", abi); 448 449 if (!ucl_parser_add_file(p, confpath)) { 450 if (errno != ENOENT) 451 errx(EXIT_FAILURE, "Unable to parse configuration " 452 "file %s: %s", confpath, ucl_parser_get_error(p)); 453 ucl_parser_free(p); 454 /* no configuration present */ 455 return (1); 456 } 457 458 obj = ucl_parser_get_object(p); 459 if (obj->type != UCL_OBJECT) 460 warnx("Invalid configuration format, ignoring the " 461 "configuration file %s", confpath); 462 else { 463 if (conftype == CONFFILE_PKG) 464 config_parse(obj); 465 else if (conftype == CONFFILE_REPO) 466 parse_repo_file(obj, requested_repo); 467 } 468 469 ucl_object_unref(obj); 470 ucl_parser_free(p); 471 472 return (0); 473 } 474 475 static void 476 load_repositories(const char *repodir, const char *requested_repo) 477 { 478 struct dirent *ent; 479 DIR *d; 480 char *p; 481 size_t n; 482 char path[MAXPATHLEN]; 483 484 if ((d = opendir(repodir)) == NULL) 485 return; 486 487 while ((ent = readdir(d))) { 488 /* Trim out 'repos'. */ 489 if ((n = strlen(ent->d_name)) <= 5) 490 continue; 491 p = &ent->d_name[n - 5]; 492 if (strcmp(p, ".conf") == 0) { 493 snprintf(path, sizeof(path), "%s%s%s", 494 repodir, 495 repodir[strlen(repodir) - 1] == '/' ? "" : "/", 496 ent->d_name); 497 if (access(path, F_OK) != 0) 498 continue; 499 if (read_conf_file(path, requested_repo, 500 CONFFILE_REPO)) { 501 goto cleanup; 502 } 503 } 504 } 505 506 cleanup: 507 closedir(d); 508 } 509 510 int 511 config_init(const char *requested_repo) 512 { 513 char *val; 514 int i; 515 const char *localbase; 516 char *abi, *env_list_item; 517 char confpath[MAXPATHLEN]; 518 struct config_value *cv; 519 520 for (i = 0; i < CONFIG_SIZE; i++) { 521 val = getenv(c[i].key); 522 if (val != NULL) { 523 c[i].envset = true; 524 switch (c[i].type) { 525 case PKG_CONFIG_LIST: 526 /* Split up comma-separated items from env. */ 527 c[i].list = malloc(sizeof(*c[i].list)); 528 STAILQ_INIT(c[i].list); 529 for (env_list_item = strtok(val, ","); 530 env_list_item != NULL; 531 env_list_item = strtok(NULL, ",")) { 532 cv = 533 malloc(sizeof(struct config_value)); 534 cv->value = 535 strdup(env_list_item); 536 STAILQ_INSERT_TAIL(c[i].list, cv, 537 next); 538 } 539 break; 540 default: 541 c[i].val = val; 542 break; 543 } 544 } 545 } 546 547 /* Read LOCALBASE/etc/pkg.conf first. */ 548 localbase = getlocalbase(); 549 snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase); 550 551 if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL, 552 CONFFILE_PKG)) 553 goto finalize; 554 555 /* Then read in all repos from REPOS_DIR list of directories. */ 556 if (c[REPOS_DIR].list == NULL) { 557 c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list)); 558 STAILQ_INIT(c[REPOS_DIR].list); 559 cv = malloc(sizeof(struct config_value)); 560 cv->value = strdup("/etc/pkg"); 561 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 562 cv = malloc(sizeof(struct config_value)); 563 if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0) 564 goto finalize; 565 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 566 } 567 568 STAILQ_FOREACH(cv, c[REPOS_DIR].list, next) 569 load_repositories(cv->value, requested_repo); 570 571 finalize: 572 if (c[ABI].val == NULL && c[ABI].value == NULL) { 573 abi = pkg_get_myabi(); 574 if (abi == NULL) 575 errx(EXIT_FAILURE, "Failed to determine the system " 576 "ABI"); 577 c[ABI].val = abi; 578 } 579 580 return (0); 581 } 582 583 int 584 config_string(pkg_config_key k, const char **val) 585 { 586 if (c[k].type != PKG_CONFIG_STRING) 587 return (-1); 588 589 if (c[k].value != NULL) 590 *val = c[k].value; 591 else 592 *val = c[k].val; 593 594 return (0); 595 } 596 597 int 598 config_bool(pkg_config_key k, bool *val) 599 { 600 const char *value; 601 602 if (c[k].type != PKG_CONFIG_BOOL) 603 return (-1); 604 605 *val = false; 606 607 if (c[k].value != NULL) 608 value = c[k].value; 609 else 610 value = c[k].val; 611 612 if (boolstr_to_bool(value)) 613 *val = true; 614 615 return (0); 616 } 617 618 struct repositories * 619 config_get_repositories(void) 620 { 621 if (STAILQ_EMPTY(&repositories)) { 622 /* Fall back to PACKAGESITE - deprecated - */ 623 struct repository *r = calloc(1, sizeof(r)); 624 if (r == NULL) 625 err(EXIT_FAILURE, "calloc"); 626 r->name = strdup("fallback"); 627 if (r->name == NULL) 628 err(EXIT_FAILURE, "strdup"); 629 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val); 630 r->url = c[PACKAGESITE].value; 631 if (c[SIGNATURE_TYPE].value != NULL) 632 if (!parse_signature_type(r, c[SIGNATURE_TYPE].value)) 633 exit(EXIT_FAILURE); 634 if (c[MIRROR_TYPE].value != NULL) 635 parse_mirror_type(r, c[MIRROR_TYPE].value); 636 if (c[PUBKEY].value != NULL) 637 r->pubkey = c[PUBKEY].value; 638 if (c[FINGERPRINTS].value != NULL) 639 r->fingerprints = c[FINGERPRINTS].value; 640 STAILQ_INSERT_TAIL(&repositories, r, next); 641 } 642 return (&repositories); 643 } 644 645 void 646 config_finish(void) { 647 int i; 648 649 for (i = 0; i < CONFIG_SIZE; i++) 650 free(c[i].value); 651 } 652