1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 __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
pkg_get_myabi(char * dest,size_t sz)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
subst_packagesite(const char * abi)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
boolstr_to_bool(const char * str)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
config_parse(const ucl_object_t * obj,pkg_conf_file_t conftype)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
parse_repo_file(ucl_object_t * obj,const char * requested_repo)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
read_conf_file(const char * confpath,const char * requested_repo,pkg_conf_file_t conftype)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
load_repositories(const char * repodir,const char * requested_repo)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
config_init(const char * requested_repo)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
config_string(pkg_config_key k,const char ** val)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
config_bool(pkg_config_key k,bool * val)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
config_finish(void)564 config_finish(void) {
565 int i;
566
567 for (i = 0; i < CONFIG_SIZE; i++)
568 free(c[i].value);
569 }
570