1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 Baptiste Daroussin <[email protected]>
5 * Copyright (c) 2013 Bryan Drewery <[email protected]>
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 *
pkg_get_myabi(void)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
subst_packagesite(const char * abi)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
boolstr_to_bool(const char * str)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
config_parse(const ucl_object_t * obj,pkg_conf_file_t conftype)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
parse_repo_file(ucl_object_t * obj,const char * requested_repo)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
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)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
load_repositories(const char * repodir,const char * requested_repo)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
config_init(const char * requested_repo)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
config_string(pkg_config_key k,const char ** val)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
config_bool(pkg_config_key k,bool * val)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
config_finish(void)565 config_finish(void) {
566 int i;
567
568 for (i = 0; i < CONFIG_SIZE; i++)
569 free(c[i].value);
570 }
571