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