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