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/cdefs.h> 31 #include <sys/param.h> 32 #include <sys/queue.h> 33 #include <sys/utsname.h> 34 #include <sys/sysctl.h> 35 36 #include <dirent.h> 37 #include <ucl.h> 38 #include <err.h> 39 #include <errno.h> 40 #include <libutil.h> 41 #include <paths.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 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, pkg_conf_file_t conftype) 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 if (conftype == CONFFILE_PKG) { 243 for (j = 0; j < strlen(key); ++j) 244 fputc(toupper(key[j]), buffp); 245 fflush(buffp); 246 } else if (conftype == CONFFILE_REPO) { 247 if (strcasecmp(key, "url") == 0) 248 fputs("PACKAGESITE", buffp); 249 else if (strcasecmp(key, "mirror_type") == 0) 250 fputs("MIRROR_TYPE", buffp); 251 else if (strcasecmp(key, "signature_type") == 0) 252 fputs("SIGNATURE_TYPE", buffp); 253 else if (strcasecmp(key, "fingerprints") == 0) 254 fputs("FINGERPRINTS", buffp); 255 else if (strcasecmp(key, "pubkey") == 0) 256 fputs("PUBKEY", buffp); 257 else if (strcasecmp(key, "enabled") == 0) { 258 if ((cur->type != UCL_BOOLEAN) || 259 !ucl_object_toboolean(cur)) 260 goto cleanup; 261 } else 262 continue; 263 fflush(buffp); 264 } 265 266 for (i = 0; i < CONFIG_SIZE; i++) { 267 if (strcmp(buf, c[i].key) == 0) 268 break; 269 } 270 271 /* Silently skip unknown keys to be future compatible. */ 272 if (i == CONFIG_SIZE) 273 continue; 274 275 /* env has priority over config file */ 276 if (c[i].envset) 277 continue; 278 279 /* Parse sequence value ["item1", "item2"] */ 280 switch (c[i].type) { 281 case PKG_CONFIG_LIST: 282 if (cur->type != UCL_ARRAY) { 283 warnx("Skipping invalid array " 284 "value for %s.\n", c[i].key); 285 continue; 286 } 287 temp_config[i].list = 288 malloc(sizeof(*temp_config[i].list)); 289 STAILQ_INIT(temp_config[i].list); 290 291 while ((seq = ucl_iterate_object(cur, &itseq, true))) { 292 if (seq->type != UCL_STRING) 293 continue; 294 cv = malloc(sizeof(struct config_value)); 295 cv->value = 296 strdup(ucl_object_tostring(seq)); 297 STAILQ_INSERT_TAIL(temp_config[i].list, cv, 298 next); 299 } 300 break; 301 case PKG_CONFIG_BOOL: 302 temp_config[i].value = 303 strdup(ucl_object_toboolean(cur) ? "yes" : "no"); 304 break; 305 case PKG_CONFIG_OBJECT: 306 if (strcmp(c[i].key, "PKG_ENV") == 0) { 307 while ((tmp = 308 ucl_iterate_object(cur, &it_obj, true))) { 309 evkey = ucl_object_key(tmp); 310 if (evkey != NULL && *evkey != '\0') { 311 setenv(evkey, ucl_object_tostring_forced(tmp), 1); 312 } 313 } 314 } 315 break; 316 default: 317 /* Normal string value. */ 318 temp_config[i].value = strdup(ucl_object_tostring(cur)); 319 break; 320 } 321 } 322 323 /* Repo is enabled, copy over all settings from temp_config. */ 324 for (i = 0; i < CONFIG_SIZE; i++) { 325 if (c[i].envset) 326 continue; 327 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */ 328 if (conftype != CONFFILE_PKG && c[i].main_only == true) 329 continue; 330 switch (c[i].type) { 331 case PKG_CONFIG_LIST: 332 c[i].list = temp_config[i].list; 333 break; 334 default: 335 c[i].value = temp_config[i].value; 336 break; 337 } 338 } 339 340 cleanup: 341 free(temp_config); 342 fclose(buffp); 343 free(buf); 344 } 345 346 /*- 347 * Parse new repo style configs in style: 348 * Name: 349 * URL: 350 * MIRROR_TYPE: 351 * etc... 352 */ 353 static void 354 parse_repo_file(ucl_object_t *obj, const char *requested_repo) 355 { 356 ucl_object_iter_t it = NULL; 357 const ucl_object_t *cur; 358 const char *key; 359 360 while ((cur = ucl_iterate_object(obj, &it, true))) { 361 key = ucl_object_key(cur); 362 363 if (key == NULL) 364 continue; 365 366 if (cur->type != UCL_OBJECT) 367 continue; 368 369 if (requested_repo != NULL && strcmp(requested_repo, key) != 0) 370 continue; 371 372 config_parse(cur, CONFFILE_REPO); 373 } 374 } 375 376 377 static int 378 read_conf_file(const char *confpath, const char *requested_repo, 379 pkg_conf_file_t conftype) 380 { 381 struct ucl_parser *p; 382 ucl_object_t *obj = NULL; 383 384 p = ucl_parser_new(0); 385 386 if (!ucl_parser_add_file(p, confpath)) { 387 if (errno != ENOENT) 388 errx(EXIT_FAILURE, "Unable to parse configuration " 389 "file %s: %s", confpath, ucl_parser_get_error(p)); 390 ucl_parser_free(p); 391 /* no configuration present */ 392 return (1); 393 } 394 395 obj = ucl_parser_get_object(p); 396 if (obj->type != UCL_OBJECT) 397 warnx("Invalid configuration format, ignoring the " 398 "configuration file %s", confpath); 399 else { 400 if (conftype == CONFFILE_PKG) 401 config_parse(obj, conftype); 402 else if (conftype == CONFFILE_REPO) 403 parse_repo_file(obj, requested_repo); 404 } 405 406 ucl_object_unref(obj); 407 ucl_parser_free(p); 408 409 return (0); 410 } 411 412 static int 413 load_repositories(const char *repodir, const char *requested_repo) 414 { 415 struct dirent *ent; 416 DIR *d; 417 char *p; 418 size_t n; 419 char path[MAXPATHLEN]; 420 int ret; 421 422 ret = 0; 423 424 if ((d = opendir(repodir)) == NULL) 425 return (1); 426 427 while ((ent = readdir(d))) { 428 /* Trim out 'repos'. */ 429 if ((n = strlen(ent->d_name)) <= 5) 430 continue; 431 p = &ent->d_name[n - 5]; 432 if (strcmp(p, ".conf") == 0) { 433 snprintf(path, sizeof(path), "%s%s%s", 434 repodir, 435 repodir[strlen(repodir) - 1] == '/' ? "" : "/", 436 ent->d_name); 437 if (access(path, F_OK) != 0) 438 continue; 439 if (read_conf_file(path, requested_repo, 440 CONFFILE_REPO)) { 441 ret = 1; 442 goto cleanup; 443 } 444 } 445 } 446 447 cleanup: 448 closedir(d); 449 450 return (ret); 451 } 452 453 int 454 config_init(const char *requested_repo) 455 { 456 char *val; 457 int i; 458 const char *localbase; 459 char *abi, *env_list_item; 460 char confpath[MAXPATHLEN]; 461 struct config_value *cv; 462 463 for (i = 0; i < CONFIG_SIZE; i++) { 464 val = getenv(c[i].key); 465 if (val != NULL) { 466 c[i].envset = true; 467 switch (c[i].type) { 468 case PKG_CONFIG_LIST: 469 /* Split up comma-separated items from env. */ 470 c[i].list = malloc(sizeof(*c[i].list)); 471 STAILQ_INIT(c[i].list); 472 for (env_list_item = strtok(val, ","); 473 env_list_item != NULL; 474 env_list_item = strtok(NULL, ",")) { 475 cv = 476 malloc(sizeof(struct config_value)); 477 cv->value = 478 strdup(env_list_item); 479 STAILQ_INSERT_TAIL(c[i].list, cv, 480 next); 481 } 482 break; 483 default: 484 c[i].val = val; 485 break; 486 } 487 } 488 } 489 490 /* Read LOCALBASE/etc/pkg.conf first. */ 491 localbase = getlocalbase(); 492 snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase); 493 494 if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL, 495 CONFFILE_PKG)) 496 goto finalize; 497 498 /* Then read in all repos from REPOS_DIR list of directories. */ 499 if (c[REPOS_DIR].list == NULL) { 500 c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list)); 501 STAILQ_INIT(c[REPOS_DIR].list); 502 cv = malloc(sizeof(struct config_value)); 503 cv->value = strdup("/etc/pkg"); 504 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 505 cv = malloc(sizeof(struct config_value)); 506 if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0) 507 goto finalize; 508 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 509 } 510 511 STAILQ_FOREACH(cv, c[REPOS_DIR].list, next) 512 if (load_repositories(cv->value, requested_repo)) 513 goto finalize; 514 515 finalize: 516 if (c[ABI].val == NULL && c[ABI].value == NULL) { 517 abi = pkg_get_myabi(); 518 if (abi == NULL) 519 errx(EXIT_FAILURE, "Failed to determine the system " 520 "ABI"); 521 c[ABI].val = abi; 522 } 523 524 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val); 525 526 return (0); 527 } 528 529 int 530 config_string(pkg_config_key k, const char **val) 531 { 532 if (c[k].type != PKG_CONFIG_STRING) 533 return (-1); 534 535 if (c[k].value != NULL) 536 *val = c[k].value; 537 else 538 *val = c[k].val; 539 540 return (0); 541 } 542 543 int 544 config_bool(pkg_config_key k, bool *val) 545 { 546 const char *value; 547 548 if (c[k].type != PKG_CONFIG_BOOL) 549 return (-1); 550 551 *val = false; 552 553 if (c[k].value != NULL) 554 value = c[k].value; 555 else 556 value = c[k].val; 557 558 if (boolstr_to_bool(value)) 559 *val = true; 560 561 return (0); 562 } 563 564 void 565 config_finish(void) { 566 int i; 567 568 for (i = 0; i < CONFIG_SIZE; i++) 569 free(c[i].value); 570 } 571