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 __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 struct config_value { 51 char *value; 52 STAILQ_ENTRY(config_value) next; 53 }; 54 55 struct config_entry { 56 uint8_t type; 57 const char *key; 58 const char *val; 59 char *value; 60 STAILQ_HEAD(, config_value) *list; 61 bool envset; 62 bool main_only; /* Only set in pkg.conf. */ 63 }; 64 65 static struct config_entry c[] = { 66 [PACKAGESITE] = { 67 PKG_CONFIG_STRING, 68 "PACKAGESITE", 69 URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", 70 NULL, 71 NULL, 72 false, 73 false, 74 }, 75 [ABI] = { 76 PKG_CONFIG_STRING, 77 "ABI", 78 NULL, 79 NULL, 80 NULL, 81 false, 82 true, 83 }, 84 [MIRROR_TYPE] = { 85 PKG_CONFIG_STRING, 86 "MIRROR_TYPE", 87 "SRV", 88 NULL, 89 NULL, 90 false, 91 false, 92 }, 93 [ASSUME_ALWAYS_YES] = { 94 PKG_CONFIG_BOOL, 95 "ASSUME_ALWAYS_YES", 96 "NO", 97 NULL, 98 NULL, 99 false, 100 true, 101 }, 102 [SIGNATURE_TYPE] = { 103 PKG_CONFIG_STRING, 104 "SIGNATURE_TYPE", 105 NULL, 106 NULL, 107 NULL, 108 false, 109 false, 110 }, 111 [FINGERPRINTS] = { 112 PKG_CONFIG_STRING, 113 "FINGERPRINTS", 114 NULL, 115 NULL, 116 NULL, 117 false, 118 false, 119 }, 120 [REPOS_DIR] = { 121 PKG_CONFIG_LIST, 122 "REPOS_DIR", 123 NULL, 124 NULL, 125 NULL, 126 false, 127 true, 128 }, 129 [PUBKEY] = { 130 PKG_CONFIG_STRING, 131 "PUBKEY", 132 NULL, 133 NULL, 134 NULL, 135 false, 136 false 137 }, 138 [PKG_ENV] = { 139 PKG_CONFIG_OBJECT, 140 "PKG_ENV", 141 NULL, 142 NULL, 143 NULL, 144 false, 145 false, 146 } 147 }; 148 149 static int 150 pkg_get_myabi(char *dest, size_t sz) 151 { 152 struct utsname uts; 153 char machine_arch[255]; 154 size_t len; 155 int error; 156 157 error = uname(&uts); 158 if (error) 159 return (errno); 160 161 len = sizeof(machine_arch); 162 error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0); 163 if (error) 164 return (errno); 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 snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000, 172 machine_arch); 173 174 return (error); 175 } 176 177 static void 178 subst_packagesite(const char *abi) 179 { 180 char *newval; 181 const char *variable_string; 182 const char *oldval; 183 184 if (c[PACKAGESITE].value != NULL) 185 oldval = c[PACKAGESITE].value; 186 else 187 oldval = c[PACKAGESITE].val; 188 189 if ((variable_string = strstr(oldval, "${ABI}")) == NULL) 190 return; 191 192 asprintf(&newval, "%.*s%s%s", 193 (int)(variable_string - oldval), oldval, abi, 194 variable_string + strlen("${ABI}")); 195 if (newval == NULL) 196 errx(EXIT_FAILURE, "asprintf"); 197 198 free(c[PACKAGESITE].value); 199 c[PACKAGESITE].value = newval; 200 } 201 202 static int 203 boolstr_to_bool(const char *str) 204 { 205 if (str != NULL && (strcasecmp(str, "true") == 0 || 206 strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 || 207 str[0] == '1')) 208 return (true); 209 210 return (false); 211 } 212 213 static void 214 config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype) 215 { 216 FILE *buffp; 217 char *buf = NULL; 218 size_t bufsz = 0; 219 const ucl_object_t *cur, *seq, *tmp; 220 ucl_object_iter_t it = NULL, itseq = NULL, it_obj = NULL; 221 struct config_entry *temp_config; 222 struct config_value *cv; 223 const char *key, *evkey; 224 int i; 225 size_t j; 226 227 /* Temporary config for configs that may be disabled. */ 228 temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry)); 229 buffp = open_memstream(&buf, &bufsz); 230 if (buffp == NULL) 231 err(EXIT_FAILURE, "open_memstream()"); 232 233 while ((cur = ucl_iterate_object(obj, &it, true))) { 234 key = ucl_object_key(cur); 235 if (key == NULL) 236 continue; 237 if (buf != NULL) 238 memset(buf, 0, bufsz); 239 rewind(buffp); 240 241 if (conftype == CONFFILE_PKG) { 242 for (j = 0; j < strlen(key); ++j) 243 fputc(toupper(key[j]), buffp); 244 fflush(buffp); 245 } else if (conftype == CONFFILE_REPO) { 246 if (strcasecmp(key, "url") == 0) 247 fputs("PACKAGESITE", buffp); 248 else if (strcasecmp(key, "mirror_type") == 0) 249 fputs("MIRROR_TYPE", buffp); 250 else if (strcasecmp(key, "signature_type") == 0) 251 fputs("SIGNATURE_TYPE", buffp); 252 else if (strcasecmp(key, "fingerprints") == 0) 253 fputs("FINGERPRINTS", buffp); 254 else if (strcasecmp(key, "pubkey") == 0) 255 fputs("PUBKEY", buffp); 256 else if (strcasecmp(key, "enabled") == 0) { 257 if ((cur->type != UCL_BOOLEAN) || 258 !ucl_object_toboolean(cur)) 259 goto cleanup; 260 } else 261 continue; 262 fflush(buffp); 263 } 264 265 for (i = 0; i < CONFIG_SIZE; i++) { 266 if (strcmp(buf, c[i].key) == 0) 267 break; 268 } 269 270 /* Silently skip unknown keys to be future compatible. */ 271 if (i == CONFIG_SIZE) 272 continue; 273 274 /* env has priority over config file */ 275 if (c[i].envset) 276 continue; 277 278 /* Parse sequence value ["item1", "item2"] */ 279 switch (c[i].type) { 280 case PKG_CONFIG_LIST: 281 if (cur->type != UCL_ARRAY) { 282 warnx("Skipping invalid array " 283 "value for %s.\n", c[i].key); 284 continue; 285 } 286 temp_config[i].list = 287 malloc(sizeof(*temp_config[i].list)); 288 STAILQ_INIT(temp_config[i].list); 289 290 while ((seq = ucl_iterate_object(cur, &itseq, true))) { 291 if (seq->type != UCL_STRING) 292 continue; 293 cv = malloc(sizeof(struct config_value)); 294 cv->value = 295 strdup(ucl_object_tostring(seq)); 296 STAILQ_INSERT_TAIL(temp_config[i].list, cv, 297 next); 298 } 299 break; 300 case PKG_CONFIG_BOOL: 301 temp_config[i].value = 302 strdup(ucl_object_toboolean(cur) ? "yes" : "no"); 303 break; 304 case PKG_CONFIG_OBJECT: 305 if (strcmp(c[i].key, "PKG_ENV") == 0) { 306 while ((tmp = 307 ucl_iterate_object(cur, &it_obj, true))) { 308 evkey = ucl_object_key(tmp); 309 if (evkey != NULL && *evkey != '\0') { 310 setenv(evkey, ucl_object_tostring_forced(tmp), 1); 311 } 312 } 313 } 314 break; 315 default: 316 /* Normal string value. */ 317 temp_config[i].value = strdup(ucl_object_tostring(cur)); 318 break; 319 } 320 } 321 322 /* Repo is enabled, copy over all settings from temp_config. */ 323 for (i = 0; i < CONFIG_SIZE; i++) { 324 if (c[i].envset) 325 continue; 326 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */ 327 if (conftype != CONFFILE_PKG && c[i].main_only == true) 328 continue; 329 switch (c[i].type) { 330 case PKG_CONFIG_LIST: 331 c[i].list = temp_config[i].list; 332 break; 333 default: 334 c[i].value = temp_config[i].value; 335 break; 336 } 337 } 338 339 cleanup: 340 free(temp_config); 341 fclose(buffp); 342 free(buf); 343 } 344 345 /*- 346 * Parse new repo style configs in style: 347 * Name: 348 * URL: 349 * MIRROR_TYPE: 350 * etc... 351 */ 352 static void 353 parse_repo_file(ucl_object_t *obj, const char *requested_repo) 354 { 355 ucl_object_iter_t it = NULL; 356 const ucl_object_t *cur; 357 const char *key; 358 359 while ((cur = ucl_iterate_object(obj, &it, true))) { 360 key = ucl_object_key(cur); 361 362 if (key == NULL) 363 continue; 364 365 if (cur->type != UCL_OBJECT) 366 continue; 367 368 if (requested_repo != NULL && strcmp(requested_repo, key) != 0) 369 continue; 370 371 config_parse(cur, CONFFILE_REPO); 372 } 373 } 374 375 376 static int 377 read_conf_file(const char *confpath, const char *requested_repo, 378 pkg_conf_file_t conftype) 379 { 380 struct ucl_parser *p; 381 ucl_object_t *obj = NULL; 382 383 p = ucl_parser_new(0); 384 385 if (!ucl_parser_add_file(p, confpath)) { 386 if (errno != ENOENT) 387 errx(EXIT_FAILURE, "Unable to parse configuration " 388 "file %s: %s", confpath, ucl_parser_get_error(p)); 389 ucl_parser_free(p); 390 /* no configuration present */ 391 return (1); 392 } 393 394 obj = ucl_parser_get_object(p); 395 if (obj->type != UCL_OBJECT) 396 warnx("Invalid configuration format, ignoring the " 397 "configuration file %s", confpath); 398 else { 399 if (conftype == CONFFILE_PKG) 400 config_parse(obj, conftype); 401 else if (conftype == CONFFILE_REPO) 402 parse_repo_file(obj, requested_repo); 403 } 404 405 ucl_object_unref(obj); 406 ucl_parser_free(p); 407 408 return (0); 409 } 410 411 static int 412 load_repositories(const char *repodir, const char *requested_repo) 413 { 414 struct dirent *ent; 415 DIR *d; 416 char *p; 417 size_t n; 418 char path[MAXPATHLEN]; 419 int ret; 420 421 ret = 0; 422 423 if ((d = opendir(repodir)) == NULL) 424 return (1); 425 426 while ((ent = readdir(d))) { 427 /* Trim out 'repos'. */ 428 if ((n = strlen(ent->d_name)) <= 5) 429 continue; 430 p = &ent->d_name[n - 5]; 431 if (strcmp(p, ".conf") == 0) { 432 snprintf(path, sizeof(path), "%s%s%s", 433 repodir, 434 repodir[strlen(repodir) - 1] == '/' ? "" : "/", 435 ent->d_name); 436 if (access(path, F_OK) != 0) 437 continue; 438 if (read_conf_file(path, requested_repo, 439 CONFFILE_REPO)) { 440 ret = 1; 441 goto cleanup; 442 } 443 } 444 } 445 446 cleanup: 447 closedir(d); 448 449 return (ret); 450 } 451 452 int 453 config_init(const char *requested_repo) 454 { 455 char *val; 456 int i; 457 const char *localbase; 458 char *env_list_item; 459 char confpath[MAXPATHLEN]; 460 struct config_value *cv; 461 char abi[BUFSIZ]; 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 if (pkg_get_myabi(abi, BUFSIZ) != 0) 518 errx(EXIT_FAILURE, "Failed to determine the system " 519 "ABI"); 520 c[ABI].val = abi; 521 } 522 523 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val); 524 525 return (0); 526 } 527 528 int 529 config_string(pkg_config_key k, const char **val) 530 { 531 if (c[k].type != PKG_CONFIG_STRING) 532 return (-1); 533 534 if (c[k].value != NULL) 535 *val = c[k].value; 536 else 537 *val = c[k].val; 538 539 return (0); 540 } 541 542 int 543 config_bool(pkg_config_key k, bool *val) 544 { 545 const char *value; 546 547 if (c[k].type != PKG_CONFIG_BOOL) 548 return (-1); 549 550 *val = false; 551 552 if (c[k].value != NULL) 553 value = c[k].value; 554 else 555 value = c[k].val; 556 557 if (boolstr_to_bool(value)) 558 *val = true; 559 560 return (0); 561 } 562 563 void 564 config_finish(void) { 565 int i; 566 567 for (i = 0; i < CONFIG_SIZE; i++) 568 free(c[i].value); 569 } 570