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/param.h>
31 #include <sys/queue.h>
32 #include <sys/utsname.h>
33 #include <sys/sysctl.h>
34
35 #include <dirent.h>
36 #include <ucl.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <libutil.h>
40 #include <paths.h>
41 #include <stdbool.h>
42 #include <unistd.h>
43 #include <ctype.h>
44
45 #include "config.h"
46
47 struct config_value {
48 char *value;
49 STAILQ_ENTRY(config_value) next;
50 };
51
52 struct config_entry {
53 uint8_t type;
54 const char *key;
55 const char *val;
56 char *value;
57 STAILQ_HEAD(, config_value) *list;
58 bool envset;
59 bool main_only; /* Only set in pkg.conf. */
60 };
61
62 static struct repositories repositories = STAILQ_HEAD_INITIALIZER(repositories);
63
64 static struct config_entry c[] = {
65 [PACKAGESITE] = {
66 PKG_CONFIG_STRING,
67 "PACKAGESITE",
68 URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest",
69 NULL,
70 NULL,
71 false,
72 false,
73 },
74 [ABI] = {
75 PKG_CONFIG_STRING,
76 "ABI",
77 NULL,
78 NULL,
79 NULL,
80 false,
81 true,
82 },
83 [MIRROR_TYPE] = {
84 PKG_CONFIG_STRING,
85 "MIRROR_TYPE",
86 "SRV",
87 NULL,
88 NULL,
89 false,
90 false,
91 },
92 [ASSUME_ALWAYS_YES] = {
93 PKG_CONFIG_BOOL,
94 "ASSUME_ALWAYS_YES",
95 "NO",
96 NULL,
97 NULL,
98 false,
99 true,
100 },
101 [SIGNATURE_TYPE] = {
102 PKG_CONFIG_STRING,
103 "SIGNATURE_TYPE",
104 NULL,
105 NULL,
106 NULL,
107 false,
108 false,
109 },
110 [FINGERPRINTS] = {
111 PKG_CONFIG_STRING,
112 "FINGERPRINTS",
113 NULL,
114 NULL,
115 NULL,
116 false,
117 false,
118 },
119 [REPOS_DIR] = {
120 PKG_CONFIG_LIST,
121 "REPOS_DIR",
122 NULL,
123 NULL,
124 NULL,
125 false,
126 true,
127 },
128 [PUBKEY] = {
129 PKG_CONFIG_STRING,
130 "PUBKEY",
131 NULL,
132 NULL,
133 NULL,
134 false,
135 false
136 },
137 [PKG_ENV] = {
138 PKG_CONFIG_OBJECT,
139 "PKG_ENV",
140 NULL,
141 NULL,
142 NULL,
143 false,
144 false,
145 }
146 };
147
148 static char *
pkg_get_myabi(void)149 pkg_get_myabi(void)
150 {
151 struct utsname uts;
152 char machine_arch[255];
153 char *abi;
154 size_t len;
155 int error;
156
157 error = uname(&uts);
158 if (error)
159 return (NULL);
160
161 len = sizeof(machine_arch);
162 error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0);
163 if (error)
164 return (NULL);
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 error = asprintf(&abi, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000,
172 machine_arch);
173 if (error < 0)
174 return (NULL);
175
176 return (abi);
177 }
178
179 static void
subst_packagesite(const char * abi)180 subst_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
204 static int
boolstr_to_bool(const char * str)205 boolstr_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
215 static void
config_parse(const ucl_object_t * obj)216 config_parse(const ucl_object_t *obj)
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 for (j = 0; j < strlen(key); ++j)
244 fputc(toupper(key[j]), buffp);
245 fflush(buffp);
246
247 for (i = 0; i < CONFIG_SIZE; i++) {
248 if (strcmp(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 case PKG_CONFIG_OBJECT:
287 if (strcmp(c[i].key, "PKG_ENV") == 0) {
288 while ((tmp =
289 ucl_iterate_object(cur, &it_obj, true))) {
290 evkey = ucl_object_key(tmp);
291 if (evkey != NULL && *evkey != '\0') {
292 setenv(evkey, ucl_object_tostring_forced(tmp), 1);
293 }
294 }
295 }
296 break;
297 default:
298 /* Normal string value. */
299 temp_config[i].value = strdup(ucl_object_tostring(cur));
300 break;
301 }
302 }
303
304 /* Repo is enabled, copy over all settings from temp_config. */
305 for (i = 0; i < CONFIG_SIZE; i++) {
306 if (c[i].envset)
307 continue;
308 /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */
309 if (c[i].main_only == true)
310 continue;
311 switch (c[i].type) {
312 case PKG_CONFIG_LIST:
313 c[i].list = temp_config[i].list;
314 break;
315 default:
316 c[i].value = temp_config[i].value;
317 break;
318 }
319 }
320
321 free(temp_config);
322 fclose(buffp);
323 free(buf);
324 }
325
326
327 static void
parse_mirror_type(struct repository * r,const char * mt)328 parse_mirror_type(struct repository *r, const char *mt)
329 {
330 if (strcasecmp(mt, "srv") == 0)
331 r->mirror_type = MIRROR_SRV;
332 r->mirror_type = MIRROR_NONE;
333 }
334
335 static bool
parse_signature_type(struct repository * repo,const char * st)336 parse_signature_type(struct repository *repo, const char *st)
337 {
338 if (strcasecmp(st, "FINGERPRINTS") == 0)
339 repo->signature_type = SIGNATURE_FINGERPRINT;
340 else if (strcasecmp(st, "PUBKEY") == 0)
341 repo->signature_type = SIGNATURE_PUBKEY;
342 else if (strcasecmp(st, "NONE") == 0)
343 repo->signature_type = SIGNATURE_NONE;
344 else {
345 warnx("Signature type %s is not supported for bootstrapping,"
346 " ignoring repository %s", st, repo->name);
347 free(repo->url);
348 free(repo->name);
349 free(repo->fingerprints);
350 free(repo->pubkey);
351 free(repo);
352 return false;
353 }
354 return (true);
355 }
356
357 static void
parse_repo(const ucl_object_t * o)358 parse_repo(const ucl_object_t *o)
359 {
360 const ucl_object_t *cur;
361 const char *key;
362 ucl_object_iter_t it = NULL;
363
364 struct repository *repo = calloc(1, sizeof(struct repository));
365 if (repo == NULL)
366 err(EXIT_FAILURE, "calloc");
367
368 repo->name = strdup(ucl_object_key(o));
369 if (repo->name == NULL)
370 err(EXIT_FAILURE, "strdup");
371 while ((cur = ucl_iterate_object(o, &it, true))) {
372 key = ucl_object_key(cur);
373 if (key == NULL)
374 continue;
375 if (strcasecmp(key, "url") == 0) {
376 repo->url = strdup(ucl_object_tostring(cur));
377 if (repo->url == NULL)
378 err(EXIT_FAILURE, "strdup");
379 } else if (strcasecmp(key, "mirror_type") == 0) {
380 parse_mirror_type(repo, ucl_object_tostring(cur));
381 } else if (strcasecmp(key, "signature_type") == 0) {
382 if (!parse_signature_type(repo, ucl_object_tostring(cur)))
383 return;
384 } else if (strcasecmp(key, "fingerprints") == 0) {
385 repo->fingerprints = strdup(ucl_object_tostring(cur));
386 if (repo->fingerprints == NULL)
387 err(EXIT_FAILURE, "strdup");
388 } else if (strcasecmp(key, "pubkey") == 0) {
389 repo->pubkey = strdup(ucl_object_tostring(cur));
390 if (repo->pubkey == NULL)
391 err(EXIT_FAILURE, "strdup");
392 } else if (strcasecmp(key, "enabled") == 0) {
393 if ((cur->type != UCL_BOOLEAN) ||
394 !ucl_object_toboolean(cur)) {
395 free(repo->url);
396 free(repo->name);
397 free(repo);
398 return;
399 }
400 }
401 }
402 STAILQ_INSERT_TAIL(&repositories, repo, next);
403 return;
404 }
405
406 /*-
407 * Parse new repo style configs in style:
408 * Name:
409 * URL:
410 * MIRROR_TYPE:
411 * etc...
412 */
413 static void
parse_repo_file(ucl_object_t * obj,const char * requested_repo)414 parse_repo_file(ucl_object_t *obj, const char *requested_repo)
415 {
416 ucl_object_iter_t it = NULL;
417 const ucl_object_t *cur;
418 const char *key;
419
420 while ((cur = ucl_iterate_object(obj, &it, true))) {
421 key = ucl_object_key(cur);
422
423 if (key == NULL)
424 continue;
425
426 if (cur->type != UCL_OBJECT)
427 continue;
428
429 if (requested_repo != NULL && strcmp(requested_repo, key) != 0)
430 continue;
431 parse_repo(cur);
432 }
433 }
434
435
436 static int
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)437 read_conf_file(const char *confpath, const char *requested_repo,
438 pkg_conf_file_t conftype)
439 {
440 struct ucl_parser *p;
441 ucl_object_t *obj = NULL;
442 const char *abi = pkg_get_myabi();
443 if (abi == NULL)
444 errx(EXIT_FAILURE, "Failed to determine ABI");
445
446 p = ucl_parser_new(0);
447 ucl_parser_register_variable(p, "ABI", abi);
448
449 if (!ucl_parser_add_file(p, confpath)) {
450 if (errno != ENOENT)
451 errx(EXIT_FAILURE, "Unable to parse configuration "
452 "file %s: %s", confpath, ucl_parser_get_error(p));
453 ucl_parser_free(p);
454 /* no configuration present */
455 return (1);
456 }
457
458 obj = ucl_parser_get_object(p);
459 if (obj->type != UCL_OBJECT)
460 warnx("Invalid configuration format, ignoring the "
461 "configuration file %s", confpath);
462 else {
463 if (conftype == CONFFILE_PKG)
464 config_parse(obj);
465 else if (conftype == CONFFILE_REPO)
466 parse_repo_file(obj, requested_repo);
467 }
468
469 ucl_object_unref(obj);
470 ucl_parser_free(p);
471
472 return (0);
473 }
474
475 static void
load_repositories(const char * repodir,const char * requested_repo)476 load_repositories(const char *repodir, const char *requested_repo)
477 {
478 struct dirent *ent;
479 DIR *d;
480 char *p;
481 size_t n;
482 char path[MAXPATHLEN];
483
484 if ((d = opendir(repodir)) == NULL)
485 return;
486
487 while ((ent = readdir(d))) {
488 /* Trim out 'repos'. */
489 if ((n = strlen(ent->d_name)) <= 5)
490 continue;
491 p = &ent->d_name[n - 5];
492 if (strcmp(p, ".conf") == 0) {
493 snprintf(path, sizeof(path), "%s%s%s",
494 repodir,
495 repodir[strlen(repodir) - 1] == '/' ? "" : "/",
496 ent->d_name);
497 if (access(path, F_OK) != 0)
498 continue;
499 if (read_conf_file(path, requested_repo,
500 CONFFILE_REPO)) {
501 goto cleanup;
502 }
503 }
504 }
505
506 cleanup:
507 closedir(d);
508 }
509
510 int
config_init(const char * requested_repo)511 config_init(const char *requested_repo)
512 {
513 char *val;
514 int i;
515 const char *localbase;
516 char *abi, *env_list_item;
517 char confpath[MAXPATHLEN];
518 struct config_value *cv;
519
520 for (i = 0; i < CONFIG_SIZE; i++) {
521 val = getenv(c[i].key);
522 if (val != NULL) {
523 c[i].envset = true;
524 switch (c[i].type) {
525 case PKG_CONFIG_LIST:
526 /* Split up comma-separated items from env. */
527 c[i].list = malloc(sizeof(*c[i].list));
528 STAILQ_INIT(c[i].list);
529 for (env_list_item = strtok(val, ",");
530 env_list_item != NULL;
531 env_list_item = strtok(NULL, ",")) {
532 cv =
533 malloc(sizeof(struct config_value));
534 cv->value =
535 strdup(env_list_item);
536 STAILQ_INSERT_TAIL(c[i].list, cv,
537 next);
538 }
539 break;
540 default:
541 c[i].val = val;
542 break;
543 }
544 }
545 }
546
547 /* Read LOCALBASE/etc/pkg.conf first. */
548 localbase = getlocalbase();
549 snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase);
550
551 if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL,
552 CONFFILE_PKG))
553 goto finalize;
554
555 /* Then read in all repos from REPOS_DIR list of directories. */
556 if (c[REPOS_DIR].list == NULL) {
557 c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list));
558 STAILQ_INIT(c[REPOS_DIR].list);
559 cv = malloc(sizeof(struct config_value));
560 cv->value = strdup("/etc/pkg");
561 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
562 cv = malloc(sizeof(struct config_value));
563 if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0)
564 goto finalize;
565 STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next);
566 }
567
568 STAILQ_FOREACH(cv, c[REPOS_DIR].list, next)
569 load_repositories(cv->value, requested_repo);
570
571 finalize:
572 if (c[ABI].val == NULL && c[ABI].value == NULL) {
573 abi = pkg_get_myabi();
574 if (abi == NULL)
575 errx(EXIT_FAILURE, "Failed to determine the system "
576 "ABI");
577 c[ABI].val = abi;
578 }
579
580 return (0);
581 }
582
583 int
config_string(pkg_config_key k,const char ** val)584 config_string(pkg_config_key k, const char **val)
585 {
586 if (c[k].type != PKG_CONFIG_STRING)
587 return (-1);
588
589 if (c[k].value != NULL)
590 *val = c[k].value;
591 else
592 *val = c[k].val;
593
594 return (0);
595 }
596
597 int
config_bool(pkg_config_key k,bool * val)598 config_bool(pkg_config_key k, bool *val)
599 {
600 const char *value;
601
602 if (c[k].type != PKG_CONFIG_BOOL)
603 return (-1);
604
605 *val = false;
606
607 if (c[k].value != NULL)
608 value = c[k].value;
609 else
610 value = c[k].val;
611
612 if (boolstr_to_bool(value))
613 *val = true;
614
615 return (0);
616 }
617
618 struct repositories *
config_get_repositories(void)619 config_get_repositories(void)
620 {
621 if (STAILQ_EMPTY(&repositories)) {
622 /* Fall back to PACKAGESITE - deprecated - */
623 struct repository *r = calloc(1, sizeof(r));
624 if (r == NULL)
625 err(EXIT_FAILURE, "calloc");
626 r->name = strdup("fallback");
627 if (r->name == NULL)
628 err(EXIT_FAILURE, "strdup");
629 subst_packagesite(c[ABI].value != NULL ? c[ABI].value : c[ABI].val);
630 r->url = c[PACKAGESITE].value;
631 if (c[SIGNATURE_TYPE].value != NULL)
632 if (!parse_signature_type(r, c[SIGNATURE_TYPE].value))
633 exit(EXIT_FAILURE);
634 if (c[MIRROR_TYPE].value != NULL)
635 parse_mirror_type(r, c[MIRROR_TYPE].value);
636 if (c[PUBKEY].value != NULL)
637 r->pubkey = c[PUBKEY].value;
638 if (c[FINGERPRINTS].value != NULL)
639 r->fingerprints = c[FINGERPRINTS].value;
640 STAILQ_INSERT_TAIL(&repositories, r, next);
641 }
642 return (&repositories);
643 }
644
645 void
config_finish(void)646 config_finish(void) {
647 int i;
648
649 for (i = 0; i < CONFIG_SIZE; i++)
650 free(c[i].value);
651 }
652