1 /*- 2 * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org> 3 * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org> 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include <sys/param.h> 32 #include <sys/queue.h> 33 #include <sys/sbuf.h> 34 #include <sys/utsname.h> 35 #include <sys/sysctl.h> 36 37 #include <dirent.h> 38 #include <ucl.h> 39 #include <err.h> 40 #include <errno.h> 41 #include <stdbool.h> 42 #include <unistd.h> 43 44 #include "config.h" 45 46 #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ 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 }; 137 138 static int 139 pkg_get_myabi(char *dest, size_t sz) 140 { 141 struct utsname uts; 142 char machine_arch[255]; 143 size_t len; 144 int error; 145 146 error = uname(&uts); 147 if (error) 148 return (errno); 149 150 len = sizeof(machine_arch); 151 error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0); 152 if (error) 153 return (errno); 154 machine_arch[len] = '\0'; 155 156 /* 157 * Use __FreeBSD_version rather than kernel version (uts.release) for 158 * use in jails. This is equivalent to the value of uname -U. 159 */ 160 snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000, 161 machine_arch); 162 163 return (error); 164 } 165 166 static void 167 subst_packagesite(const char *abi) 168 { 169 struct sbuf *newval; 170 const char *variable_string; 171 const char *oldval; 172 173 if (c[PACKAGESITE].value != NULL) 174 oldval = c[PACKAGESITE].value; 175 else 176 oldval = c[PACKAGESITE].val; 177 178 if ((variable_string = strstr(oldval, "${ABI}")) == NULL) 179 return; 180 181 newval = sbuf_new_auto(); 182 sbuf_bcat(newval, oldval, variable_string - oldval); 183 sbuf_cat(newval, abi); 184 sbuf_cat(newval, variable_string + strlen("${ABI}")); 185 sbuf_finish(newval); 186 187 free(c[PACKAGESITE].value); 188 c[PACKAGESITE].value = strdup(sbuf_data(newval)); 189 } 190 191 static int 192 boolstr_to_bool(const char *str) 193 { 194 if (str != NULL && (strcasecmp(str, "true") == 0 || 195 strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 || 196 str[0] == '1')) 197 return (true); 198 199 return (false); 200 } 201 202 static void 203 config_parse(const ucl_object_t *obj, pkg_conf_file_t conftype) 204 { 205 struct sbuf *buf = sbuf_new_auto(); 206 const ucl_object_t *cur, *seq; 207 ucl_object_iter_t it = NULL, itseq = NULL; 208 struct config_entry *temp_config; 209 struct config_value *cv; 210 const char *key; 211 int i; 212 size_t j; 213 214 /* Temporary config for configs that may be disabled. */ 215 temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry)); 216 217 while ((cur = ucl_iterate_object(obj, &it, true))) { 218 key = ucl_object_key(cur); 219 if (key == NULL) 220 continue; 221 sbuf_clear(buf); 222 223 if (conftype == CONFFILE_PKG) { 224 for (j = 0; j < strlen(key); ++j) 225 sbuf_putc(buf, key[j]); 226 sbuf_finish(buf); 227 } else if (conftype == CONFFILE_REPO) { 228 if (strcasecmp(key, "url") == 0) 229 sbuf_cpy(buf, "PACKAGESITE"); 230 else if (strcasecmp(key, "mirror_type") == 0) 231 sbuf_cpy(buf, "MIRROR_TYPE"); 232 else if (strcasecmp(key, "signature_type") == 0) 233 sbuf_cpy(buf, "SIGNATURE_TYPE"); 234 else if (strcasecmp(key, "fingerprints") == 0) 235 sbuf_cpy(buf, "FINGERPRINTS"); 236 else if (strcasecmp(key, "pubkey") == 0) 237 sbuf_cpy(buf, "PUBKEY"); 238 else if (strcasecmp(key, "enabled") == 0) { 239 if ((cur->type != UCL_BOOLEAN) || 240 !ucl_object_toboolean(cur)) 241 goto cleanup; 242 } else 243 continue; 244 sbuf_finish(buf); 245 } 246 247 for (i = 0; i < CONFIG_SIZE; i++) { 248 if (strcmp(sbuf_data(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 default: 287 /* Normal string value. */ 288 temp_config[i].value = strdup(ucl_object_tostring(cur)); 289 break; 290 } 291 } 292 293 /* Repo is enabled, copy over all settings from temp_config. */ 294 for (i = 0; i < CONFIG_SIZE; i++) { 295 if (c[i].envset) 296 continue; 297 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */ 298 if (conftype != CONFFILE_PKG && c[i].main_only == true) 299 continue; 300 switch (c[i].type) { 301 case PKG_CONFIG_LIST: 302 c[i].list = temp_config[i].list; 303 break; 304 default: 305 c[i].value = temp_config[i].value; 306 break; 307 } 308 } 309 310 cleanup: 311 free(temp_config); 312 sbuf_delete(buf); 313 } 314 315 /*- 316 * Parse new repo style configs in style: 317 * Name: 318 * URL: 319 * MIRROR_TYPE: 320 * etc... 321 */ 322 static void 323 parse_repo_file(ucl_object_t *obj) 324 { 325 ucl_object_iter_t it = NULL; 326 const ucl_object_t *cur; 327 const char *key; 328 329 while ((cur = ucl_iterate_object(obj, &it, true))) { 330 key = ucl_object_key(cur); 331 332 if (key == NULL) 333 continue; 334 335 if (cur->type != UCL_OBJECT) 336 continue; 337 338 config_parse(cur, CONFFILE_REPO); 339 } 340 } 341 342 343 static int 344 read_conf_file(const char *confpath, pkg_conf_file_t conftype) 345 { 346 struct ucl_parser *p; 347 ucl_object_t *obj = NULL; 348 349 p = ucl_parser_new(0); 350 351 if (!ucl_parser_add_file(p, confpath)) { 352 if (errno != ENOENT) 353 errx(EXIT_FAILURE, "Unable to parse configuration " 354 "file %s: %s", confpath, ucl_parser_get_error(p)); 355 ucl_parser_free(p); 356 /* no configuration present */ 357 return (1); 358 } 359 360 obj = ucl_parser_get_object(p); 361 if (obj->type != UCL_OBJECT) 362 warnx("Invalid configuration format, ignoring the " 363 "configuration file %s", confpath); 364 else { 365 if (conftype == CONFFILE_PKG) 366 config_parse(obj, conftype); 367 else if (conftype == CONFFILE_REPO) 368 parse_repo_file(obj); 369 } 370 371 ucl_object_unref(obj); 372 ucl_parser_free(p); 373 374 return (0); 375 } 376 377 static int 378 load_repositories(const char *repodir) 379 { 380 struct dirent *ent; 381 DIR *d; 382 char *p; 383 size_t n; 384 char path[MAXPATHLEN]; 385 int ret; 386 387 ret = 0; 388 389 if ((d = opendir(repodir)) == NULL) 390 return (1); 391 392 while ((ent = readdir(d))) { 393 /* Trim out 'repos'. */ 394 if ((n = strlen(ent->d_name)) <= 5) 395 continue; 396 p = &ent->d_name[n - 5]; 397 if (strcmp(p, ".conf") == 0) { 398 snprintf(path, sizeof(path), "%s%s%s", 399 repodir, 400 repodir[strlen(repodir) - 1] == '/' ? "" : "/", 401 ent->d_name); 402 if (access(path, F_OK) == 0 && 403 read_conf_file(path, CONFFILE_REPO)) { 404 ret = 1; 405 goto cleanup; 406 } 407 } 408 } 409 410 cleanup: 411 closedir(d); 412 413 return (ret); 414 } 415 416 int 417 config_init(void) 418 { 419 char *val; 420 int i; 421 const char *localbase; 422 char *env_list_item; 423 char confpath[MAXPATHLEN]; 424 struct config_value *cv; 425 char abi[BUFSIZ]; 426 427 for (i = 0; i < CONFIG_SIZE; i++) { 428 val = getenv(c[i].key); 429 if (val != NULL) { 430 c[i].envset = true; 431 switch (c[i].type) { 432 case PKG_CONFIG_LIST: 433 /* Split up comma-separated items from env. */ 434 c[i].list = malloc(sizeof(*c[i].list)); 435 STAILQ_INIT(c[i].list); 436 for (env_list_item = strtok(val, ","); 437 env_list_item != NULL; 438 env_list_item = strtok(NULL, ",")) { 439 cv = 440 malloc(sizeof(struct config_value)); 441 cv->value = 442 strdup(env_list_item); 443 STAILQ_INSERT_TAIL(c[i].list, cv, 444 next); 445 } 446 break; 447 default: 448 c[i].val = val; 449 break; 450 } 451 } 452 } 453 454 /* Read LOCALBASE/etc/pkg.conf first. */ 455 localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE; 456 snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", 457 localbase); 458 459 if (access(confpath, F_OK) == 0 && read_conf_file(confpath, 460 CONFFILE_PKG)) 461 goto finalize; 462 463 /* Then read in all repos from REPOS_DIR list of directories. */ 464 if (c[REPOS_DIR].list == NULL) { 465 c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list)); 466 STAILQ_INIT(c[REPOS_DIR].list); 467 cv = malloc(sizeof(struct config_value)); 468 cv->value = strdup("/etc/pkg"); 469 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 470 cv = malloc(sizeof(struct config_value)); 471 if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0) 472 goto finalize; 473 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); 474 } 475 476 STAILQ_FOREACH(cv, c[REPOS_DIR].list, next) 477 if (load_repositories(cv->value)) 478 goto finalize; 479 480 finalize: 481 if (c[ABI].val == NULL && c[ABI].value == NULL) { 482 if (pkg_get_myabi(abi, BUFSIZ) != 0) 483 errx(EXIT_FAILURE, "Failed to determine the system " 484 "ABI"); 485 c[ABI].val = abi; 486 } 487 488 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val); 489 490 return (0); 491 } 492 493 int 494 config_string(pkg_config_key k, const char **val) 495 { 496 if (c[k].type != PKG_CONFIG_STRING) 497 return (-1); 498 499 if (c[k].value != NULL) 500 *val = c[k].value; 501 else 502 *val = c[k].val; 503 504 return (0); 505 } 506 507 int 508 config_bool(pkg_config_key k, bool *val) 509 { 510 const char *value; 511 512 if (c[k].type != PKG_CONFIG_BOOL) 513 return (-1); 514 515 *val = false; 516 517 if (c[k].value != NULL) 518 value = c[k].value; 519 else 520 value = c[k].val; 521 522 if (boolstr_to_bool(value)) 523 *val = true; 524 525 return (0); 526 } 527 528 void 529 config_finish(void) { 530 int i; 531 532 for (i = 0; i < CONFIG_SIZE; i++) 533 free(c[i].value); 534 } 535