From 65c9c86e9ee852abdfe233db97f453384592f3eb Mon Sep 17 00:00:00 2001 From: bapt Date: Tue, 11 Mar 2014 18:15:05 +0000 Subject: [PATCH] MFC: r241737,r246790,r247060,r247841,r248033,r248133,r255468,r256450,r256770 r256971,r256978,r257051,r257142,r257145,r257146,r257147,r257148,r257149 r257158,r257193,r257377,r257378,r257400,r257505,r257668,r257701,r257945 r258020,r258226,r258348,r258550,r259266,r259773,r259774,r259775,r262400 r262401,r262419 Sync pkg(7) with head: - signature checking - respecting and reading repository configurations - support pkg bootstrap -f Direct modifications: - Support old libarchive - Support old openssl API - Define non yet existing elf macros git-svn-id: svn://svn.freebsd.org/base/stable/9@263038 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- usr.sbin/pkg/Makefile | 12 +- usr.sbin/pkg/config.c | 631 ++++++++++++++++++++++++++ usr.sbin/pkg/config.h | 62 +++ usr.sbin/pkg/dns_utils.c | 90 +++- usr.sbin/pkg/dns_utils.h | 1 + usr.sbin/pkg/elf_tables.h | 27 +- usr.sbin/pkg/pkg.7 | 281 ++++++++++++ usr.sbin/pkg/pkg.c | 903 ++++++++++++++++++++++++++++---------- 8 files changed, 1763 insertions(+), 244 deletions(-) create mode 100644 usr.sbin/pkg/config.c create mode 100644 usr.sbin/pkg/config.h create mode 100644 usr.sbin/pkg/pkg.7 diff --git a/usr.sbin/pkg/Makefile b/usr.sbin/pkg/Makefile index c2ca0a32d..3d3eccc3f 100644 --- a/usr.sbin/pkg/Makefile +++ b/usr.sbin/pkg/Makefile @@ -1,10 +1,14 @@ # $FreeBSD$ PROG= pkg -SRCS= pkg.c dns_utils.c +SRCS= pkg.c dns_utils.c config.c +MAN= pkg.7 -NO_MAN= yes -DPADD= ${LIBARCHIVE} ${LIBELF} ${LIBFETCH} -LDADD= -larchive -lelf -lfetch +CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include +.PATH: ${.CURDIR}/../../contrib/libucl/include +DPADD= ${LIBARCHIVE} ${LIBELF} ${LIBFETCH} ${LIBUCL} ${LIBSBUF} ${LIBSSL} \ + ${LIBCRYPTO} +LDADD= -larchive -lelf -lfetch -lucl -lsbuf -lssl -lcrypto +USEPRIVATELIB= ucl .include diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c new file mode 100644 index 000000000..ad5b14fed --- /dev/null +++ b/usr.sbin/pkg/config.c @@ -0,0 +1,631 @@ +/*- + * Copyright (c) 2014 Baptiste Daroussin + * Copyright (c) 2013 Bryan Drewery + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elf_tables.h" +#include "config.h" + +#define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) /* if y is powers of two */ + +struct config_value { + char *value; + STAILQ_ENTRY(config_value) next; +}; + +struct config_entry { + uint8_t type; + const char *key; + const char *val; + char *value; + STAILQ_HEAD(, config_value) *list; + bool envset; +}; + +static struct config_entry c[] = { + [PACKAGESITE] = { + PKG_CONFIG_STRING, + "PACKAGESITE", + URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", + NULL, + NULL, + false, + }, + [ABI] = { + PKG_CONFIG_STRING, + "ABI", + NULL, + NULL, + NULL, + false, + }, + [MIRROR_TYPE] = { + PKG_CONFIG_STRING, + "MIRROR_TYPE", + "SRV", + NULL, + NULL, + false, + }, + [ASSUME_ALWAYS_YES] = { + PKG_CONFIG_BOOL, + "ASSUME_ALWAYS_YES", + "NO", + NULL, + NULL, + false, + }, + [SIGNATURE_TYPE] = { + PKG_CONFIG_STRING, + "SIGNATURE_TYPE", + NULL, + NULL, + NULL, + false, + }, + [FINGERPRINTS] = { + PKG_CONFIG_STRING, + "FINGERPRINTS", + NULL, + NULL, + NULL, + false, + }, + [REPOS_DIR] = { + PKG_CONFIG_LIST, + "REPOS_DIR", + NULL, + NULL, + NULL, + false, + }, +}; + +static const char * +elf_corres_to_string(struct _elf_corres *m, int e) +{ + int i; + + for (i = 0; m[i].string != NULL; i++) + if (m[i].elf_nb == e) + return (m[i].string); + + return ("unknown"); +} + +static int +pkg_get_myabi(char *dest, size_t sz) +{ + Elf *elf; + Elf_Data *data; + Elf_Note note; + Elf_Scn *scn; + char *src, *osname; + const char *abi; + GElf_Ehdr elfhdr; + GElf_Shdr shdr; + int fd, i, ret; + uint32_t version; + + version = 0; + ret = -1; + scn = NULL; + abi = NULL; + + if (elf_version(EV_CURRENT) == EV_NONE) { + warnx("ELF library initialization failed: %s", + elf_errmsg(-1)); + return (-1); + } + + if ((fd = open(_PATH_BSHELL, O_RDONLY)) < 0) { + warn("open()"); + return (-1); + } + + if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { + ret = -1; + warnx("elf_begin() failed: %s.", elf_errmsg(-1)); + goto cleanup; + } + + if (gelf_getehdr(elf, &elfhdr) == NULL) { + ret = -1; + warn("getehdr() failed: %s.", elf_errmsg(-1)); + goto cleanup; + } + while ((scn = elf_nextscn(elf, scn)) != NULL) { + if (gelf_getshdr(scn, &shdr) != &shdr) { + ret = -1; + warn("getshdr() failed: %s.", elf_errmsg(-1)); + goto cleanup; + } + + if (shdr.sh_type == SHT_NOTE) + break; + } + + if (scn == NULL) { + ret = -1; + warn("failed to get the note section"); + goto cleanup; + } + + data = elf_getdata(scn, NULL); + src = data->d_buf; + for (;;) { + memcpy(¬e, src, sizeof(Elf_Note)); + src += sizeof(Elf_Note); + if (note.n_type == NT_VERSION) + break; + src += note.n_namesz + note.n_descsz; + } + osname = src; + src += roundup2(note.n_namesz, 4); + if (elfhdr.e_ident[EI_DATA] == ELFDATA2MSB) + version = be32dec(src); + else + version = le32dec(src); + + for (i = 0; osname[i] != '\0'; i++) + osname[i] = (char)tolower(osname[i]); + + snprintf(dest, sz, "%s:%d:%s:%s", + osname, version / 100000, + elf_corres_to_string(mach_corres, (int)elfhdr.e_machine), + elf_corres_to_string(wordsize_corres, + (int)elfhdr.e_ident[EI_CLASS])); + + ret = 0; + + switch (elfhdr.e_machine) { + case EM_ARM: + snprintf(dest + strlen(dest), sz - strlen(dest), + ":%s:%s:%s", elf_corres_to_string(endian_corres, + (int)elfhdr.e_ident[EI_DATA]), + (elfhdr.e_flags & EF_ARM_NEW_ABI) > 0 ? + "eabi" : "oabi", + (elfhdr.e_flags & EF_ARM_VFP_FLOAT) > 0 ? + "softfp" : "vfp"); + break; + case EM_MIPS: + /* + * this is taken from binutils sources: + * include/elf/mips.h + * mapping is figured out from binutils: + * gas/config/tc-mips.c + */ + switch (elfhdr.e_flags & EF_MIPS_ABI) { + case E_MIPS_ABI_O32: + abi = "o32"; + break; + case E_MIPS_ABI_N32: + abi = "n32"; + break; + default: + if (elfhdr.e_ident[EI_DATA] == + ELFCLASS32) + abi = "o32"; + else if (elfhdr.e_ident[EI_DATA] == + ELFCLASS64) + abi = "n64"; + break; + } + snprintf(dest + strlen(dest), sz - strlen(dest), + ":%s:%s", elf_corres_to_string(endian_corres, + (int)elfhdr.e_ident[EI_DATA]), abi); + break; + } + +cleanup: + if (elf != NULL) + elf_end(elf); + + close(fd); + return (ret); +} + +static void +subst_packagesite(const char *abi) +{ + struct sbuf *newval; + const char *variable_string; + const char *oldval; + + if (c[PACKAGESITE].value != NULL) + oldval = c[PACKAGESITE].value; + else + oldval = c[PACKAGESITE].val; + + if ((variable_string = strstr(oldval, "${ABI}")) == NULL) + return; + + newval = sbuf_new_auto(); + sbuf_bcat(newval, oldval, variable_string - oldval); + sbuf_cat(newval, abi); + sbuf_cat(newval, variable_string + strlen("${ABI}")); + sbuf_finish(newval); + + free(c[PACKAGESITE].value); + c[PACKAGESITE].value = strdup(sbuf_data(newval)); +} + +static int +boolstr_to_bool(const char *str) +{ + if (str != NULL && (strcasecmp(str, "true") == 0 || + strcasecmp(str, "yes") == 0 || strcasecmp(str, "on") == 0 || + str[0] == '1')) + return (true); + + return (false); +} + +static void +config_parse(ucl_object_t *obj, pkg_conf_file_t conftype) +{ + struct sbuf *buf = sbuf_new_auto(); + ucl_object_t *cur, *seq; + ucl_object_iter_t it = NULL, itseq = NULL; + struct config_entry *temp_config; + struct config_value *cv; + const char *key; + int i; + size_t j; + + /* Temporary config for configs that may be disabled. */ + temp_config = calloc(CONFIG_SIZE, sizeof(struct config_entry)); + + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); + if (key == NULL) + continue; + sbuf_clear(buf); + + if (conftype == CONFFILE_PKG) { + for (j = 0; j < strlen(key); ++j) + sbuf_putc(buf, key[j]); + sbuf_finish(buf); + } else if (conftype == CONFFILE_REPO) { + if (strcasecmp(key, "url") == 0) + sbuf_cpy(buf, "PACKAGESITE"); + else if (strcasecmp(key, "mirror_type") == 0) + sbuf_cpy(buf, "MIRROR_TYPE"); + else if (strcasecmp(key, "signature_type") == 0) + sbuf_cpy(buf, "SIGNATURE_TYPE"); + else if (strcasecmp(key, "fingerprints") == 0) + sbuf_cpy(buf, "FINGERPRINTS"); + else if (strcasecmp(key, "enabled") == 0) { + if ((cur->type != UCL_BOOLEAN) || + !ucl_object_toboolean(cur)) + goto cleanup; + } else + continue; + sbuf_finish(buf); + } + + for (i = 0; i < CONFIG_SIZE; i++) { + if (strcmp(sbuf_data(buf), c[i].key) == 0) + break; + } + + /* Silently skip unknown keys to be future compatible. */ + if (i == CONFIG_SIZE) + continue; + + /* env has priority over config file */ + if (c[i].envset) + continue; + + /* Parse sequence value ["item1", "item2"] */ + switch (c[i].type) { + case PKG_CONFIG_LIST: + if (cur->type != UCL_ARRAY) { + warnx("Skipping invalid array " + "value for %s.\n", c[i].key); + continue; + } + temp_config[i].list = + malloc(sizeof(*temp_config[i].list)); + STAILQ_INIT(temp_config[i].list); + + while ((seq = ucl_iterate_object(cur, &itseq, true))) { + if (seq->type != UCL_STRING) + continue; + cv = malloc(sizeof(struct config_value)); + cv->value = + strdup(ucl_object_tostring(seq)); + STAILQ_INSERT_TAIL(temp_config[i].list, cv, + next); + } + break; + default: + /* Normal string value. */ + temp_config[i].value = strdup(ucl_object_tostring(cur)); + break; + } + } + + /* Repo is enabled, copy over all settings from temp_config. */ + for (i = 0; i < CONFIG_SIZE; i++) { + if (c[i].envset) + continue; + switch (c[i].type) { + case PKG_CONFIG_LIST: + c[i].list = temp_config[i].list; + break; + default: + c[i].value = temp_config[i].value; + break; + } + } + +cleanup: + free(temp_config); + sbuf_delete(buf); +} + +/*- + * Parse new repo style configs in style: + * Name: + * URL: + * MIRROR_TYPE: + * etc... + */ +static void +parse_repo_file(ucl_object_t *obj) +{ + ucl_object_iter_t it = NULL; + ucl_object_t *cur; + const char *key; + + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); + + if (key == NULL) + continue; + + if (cur->type != UCL_OBJECT) + continue; + + config_parse(cur, CONFFILE_REPO); + } +} + + +static int +read_conf_file(const char *confpath, pkg_conf_file_t conftype) +{ + struct ucl_parser *p; + ucl_object_t *obj = NULL; + + p = ucl_parser_new(0); + + if (!ucl_parser_add_file(p, confpath)) { + if (errno != ENOENT) + errx(EXIT_FAILURE, "Unable to parse configuration " + "file %s: %s", confpath, ucl_parser_get_error(p)); + ucl_parser_free(p); + /* no configuration present */ + return (1); + } + + obj = ucl_parser_get_object(p); + if (obj->type != UCL_OBJECT) + warnx("Invalid configuration format, ignoring the " + "configuration file %s", confpath); + else { + if (conftype == CONFFILE_PKG) + config_parse(obj, conftype); + else if (conftype == CONFFILE_REPO) + parse_repo_file(obj); + } + + ucl_object_free(obj); + ucl_parser_free(p); + + return (0); +} + +static int +load_repositories(const char *repodir) +{ + struct dirent *ent; + DIR *d; + char *p; + size_t n; + char path[MAXPATHLEN]; + int ret; + + ret = 0; + + if ((d = opendir(repodir)) == NULL) + return (1); + + while ((ent = readdir(d))) { + /* Trim out 'repos'. */ + if ((n = strlen(ent->d_name)) <= 5) + continue; + p = &ent->d_name[n - 5]; + if (strcmp(p, ".conf") == 0) { + snprintf(path, sizeof(path), "%s%s%s", + repodir, + repodir[strlen(repodir) - 1] == '/' ? "" : "/", + ent->d_name); + if (access(path, F_OK) == 0 && + read_conf_file(path, CONFFILE_REPO)) { + ret = 1; + goto cleanup; + } + } + } + +cleanup: + closedir(d); + + return (ret); +} + +int +config_init(void) +{ + char *val; + int i; + const char *localbase; + char *env_list_item; + char confpath[MAXPATHLEN]; + struct config_value *cv; + char abi[BUFSIZ]; + + for (i = 0; i < CONFIG_SIZE; i++) { + val = getenv(c[i].key); + if (val != NULL) { + c[i].envset = true; + switch (c[i].type) { + case PKG_CONFIG_LIST: + /* Split up comma-separated items from env. */ + c[i].list = malloc(sizeof(*c[i].list)); + STAILQ_INIT(c[i].list); + for (env_list_item = strtok(val, ","); + env_list_item != NULL; + env_list_item = strtok(NULL, ",")) { + cv = + malloc(sizeof(struct config_value)); + cv->value = + strdup(env_list_item); + STAILQ_INSERT_TAIL(c[i].list, cv, + next); + } + break; + default: + c[i].val = val; + break; + } + } + } + + /* Read LOCALBASE/etc/pkg.conf first. */ + localbase = getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE; + snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", + localbase); + + if (access(confpath, F_OK) == 0 && read_conf_file(confpath, + CONFFILE_PKG)) + goto finalize; + + /* Then read in all repos from REPOS_DIR list of directories. */ + if (c[REPOS_DIR].list == NULL) { + c[REPOS_DIR].list = malloc(sizeof(*c[REPOS_DIR].list)); + STAILQ_INIT(c[REPOS_DIR].list); + cv = malloc(sizeof(struct config_value)); + cv->value = strdup("/etc/pkg"); + STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); + cv = malloc(sizeof(struct config_value)); + if (asprintf(&cv->value, "%s/etc/pkg/repos", localbase) < 0) + goto finalize; + STAILQ_INSERT_TAIL(c[REPOS_DIR].list, cv, next); + } + + STAILQ_FOREACH(cv, c[REPOS_DIR].list, next) + if (load_repositories(cv->value)) + goto finalize; + +finalize: + if (c[ABI].val == NULL && c[ABI].value == NULL) { + if (pkg_get_myabi(abi, BUFSIZ) != 0) + errx(EXIT_FAILURE, "Failed to determine the system " + "ABI"); + c[ABI].val = abi; + } + + subst_packagesite(c[ABI].val); + + return (0); +} + +int +config_string(pkg_config_key k, const char **val) +{ + if (c[k].type != PKG_CONFIG_STRING) + return (-1); + + if (c[k].value != NULL) + *val = c[k].value; + else + *val = c[k].val; + + return (0); +} + +int +config_bool(pkg_config_key k, bool *val) +{ + const char *value; + + if (c[k].type != PKG_CONFIG_BOOL) + return (-1); + + *val = false; + + if (c[k].value != NULL) + value = c[k].value; + else + value = c[k].val; + + if (boolstr_to_bool(value)) + *val = true; + + return (0); +} + +void +config_finish(void) { + int i; + + for (i = 0; i < CONFIG_SIZE; i++) + free(c[i].value); +} diff --git a/usr.sbin/pkg/config.h b/usr.sbin/pkg/config.h new file mode 100644 index 000000000..ea0d0005d --- /dev/null +++ b/usr.sbin/pkg/config.h @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 2013 Baptiste Daroussin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _PKG_CONFIG_H +#define _PKG_CONFIG_H + +#define _LOCALBASE "/usr/local" +#define URL_SCHEME_PREFIX "pkg+" + +typedef enum { + PACKAGESITE = 0, + ABI, + MIRROR_TYPE, + ASSUME_ALWAYS_YES, + SIGNATURE_TYPE, + FINGERPRINTS, + REPOS_DIR, + CONFIG_SIZE +} pkg_config_key; + +typedef enum { + PKG_CONFIG_STRING=0, + PKG_CONFIG_BOOL, + PKG_CONFIG_LIST, +} pkg_config_t; + +typedef enum { + CONFFILE_PKG=0, + CONFFILE_REPO, +} pkg_conf_file_t; + +int config_init(void); +void config_finish(void); +int config_string(pkg_config_key, const char **); +int config_bool(pkg_config_key, bool *); + +#endif diff --git a/usr.sbin/pkg/dns_utils.c b/usr.sbin/pkg/dns_utils.c index cbdb3d588..ce88048a4 100644 --- a/usr.sbin/pkg/dns_utils.c +++ b/usr.sbin/pkg/dns_utils.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2012 Baptiste Daroussin + * Copyright (c) 2012-2013 Baptiste Daroussin * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,77 @@ typedef union { unsigned char buf[1024]; } dns_query; +static int +srv_priority_cmp(const void *a, const void *b) +{ + const struct dns_srvinfo *da, *db; + unsigned int r, l; + + da = *(struct dns_srvinfo * const *)a; + db = *(struct dns_srvinfo * const *)b; + + l = da->priority; + r = db->priority; + + return ((l > r) - (l < r)); +} + +static int +srv_final_cmp(const void *a, const void *b) +{ + const struct dns_srvinfo *da, *db; + unsigned int r, l, wr, wl; + int res; + + da = *(struct dns_srvinfo * const *)a; + db = *(struct dns_srvinfo * const *)b; + + l = da->priority; + r = db->priority; + + res = ((l > r) - (l < r)); + + if (res == 0) { + wl = da->finalweight; + wr = db->finalweight; + res = ((wr > wl) - (wr < wl)); + } + + return (res); +} + +static void +compute_weight(struct dns_srvinfo **d, int first, int last) +{ + int i, j, totalweight; + int *chosen; + + chosen = malloc(sizeof(int) * (last - first + 1)); + totalweight = 0; + + for (i = 0; i <= last; i++) + totalweight += d[i]->weight; + + if (totalweight == 0) + return; + + for (i = 0; i <= last; i++) { + for (;;) { + chosen[i] = random() % (d[i]->weight * 100 / totalweight); + for (j = 0; j < i; j++) { + if (chosen[i] == chosen[j]) + break; + } + if (j == i) { + d[i]->finalweight = chosen[i]; + break; + } + } + } + + free(chosen); +} + struct dns_srvinfo * dns_getsrvinfo(const char *zone) { @@ -46,7 +117,7 @@ dns_getsrvinfo(const char *zone) unsigned char *end, *p; char host[MAXHOSTNAMELEN]; dns_query q; - int len, qdcount, ancount, n, i; + int len, qdcount, ancount, n, i, f, l; unsigned int type, class, ttl, priority, weight, port; if ((len = res_query(zone, C_IN, T_SRV, q.buf, sizeof(q.buf))) == -1 || @@ -125,6 +196,21 @@ dns_getsrvinfo(const char *zone) n++; } + qsort(res, n, sizeof(res[0]), srv_priority_cmp); + + priority = f = l = 0; + for (i = 0; i < n; i++) { + if (res[i]->priority != priority) { + if (f != l) + compute_weight(res, f, l); + f = i; + priority = res[i]->priority; + } + l = i; + } + + qsort(res, n, sizeof(res[0]), srv_final_cmp); + for (i = 0; i < n - 1; i++) res[i]->next = res[i + 1]; diff --git a/usr.sbin/pkg/dns_utils.h b/usr.sbin/pkg/dns_utils.h index 0f3367b52..b1418bb2d 100644 --- a/usr.sbin/pkg/dns_utils.h +++ b/usr.sbin/pkg/dns_utils.h @@ -35,6 +35,7 @@ struct dns_srvinfo { unsigned int priority; unsigned int weight; unsigned int port; + unsigned int finalweight; char host[MAXHOSTNAMELEN]; struct dns_srvinfo *next; }; diff --git a/usr.sbin/pkg/elf_tables.h b/usr.sbin/pkg/elf_tables.h index e02a2f765..6eaf9f046 100644 --- a/usr.sbin/pkg/elf_tables.h +++ b/usr.sbin/pkg/elf_tables.h @@ -34,7 +34,7 @@ struct _elf_corres { const char *string; }; -struct _elf_corres mach_corres[] = { +static struct _elf_corres mach_corres[] = { { EM_386, "x86" }, { EM_AMD64, "x86" }, { EM_ARM, "arm" }, @@ -46,33 +46,30 @@ struct _elf_corres mach_corres[] = { { -1, NULL }, }; -struct _elf_corres wordsize_corres[] = { +static struct _elf_corres wordsize_corres[] = { { ELFCLASS32, "32" }, { ELFCLASS64, "64" }, { -1, NULL}, }; -struct _elf_corres endian_corres[] = { +static struct _elf_corres endian_corres[] = { { ELFDATA2MSB, "eb" }, { ELFDATA2LSB, "el" }, { -1, NULL} }; -struct _elf_corres os_corres[] = { - { ELFOSABI_FREEBSD, "freebsd" }, - { -1, NULL } -}; - -#define EF_MIPS_ABI 0x0000F000 +#ifndef EF_ARM_NEW_ABI +#define EF_ARM_NEW_ABI 0x00000080UL +#endif +#ifndef EF_ARM_VFP_FLOAT +#define EF_ARM_VFP_FLOAT 0x00000400UL +#endif +#ifndef EF_MIPS_ABI +#define EF_MIPS_ABI 0x0000f000 +#endif #define E_MIPS_ABI_O32 0x00001000 #define E_MIPS_ABI_N32 0x00000020 -#define EF_ARM_NEW_ABI 0x80 -#define EF_ARM_OLD_ABI 0x100 - -#define EF_ARM_SOFT_FLOAT 0x200 -#define EF_ARM_VFP_FLOAT 0x400 - #define NT_VERSION 1 #define NT_ARCH 2 diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7 new file mode 100644 index 000000000..342c93420 --- /dev/null +++ b/usr.sbin/pkg/pkg.7 @@ -0,0 +1,281 @@ +.\" Copyright (c) 2013 Bryan Drewery +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd December 12, 2013 +.Dt PKG 7 +.Os +.Sh NAME +.Nm pkg +.Nd a utility for manipulating packages +.Sh SYNOPSIS +.Nm +.Ao Ar command Ac +.Nm +add +.Op Fl f +.Ao Pa pkg.txz Ac +.Nm +.Fl N +.Nm +bootstrap +.Op Fl f +.Sh DESCRIPTION +.Nm +is the package management tool. +It is used to manage local packages installed from +.Xr ports 7 +and install/upgrade packages from remote repositories. +.Pp +To avoid backwards incompatibility issues, the actual +.Xr pkg 8 +tool is not installed in the base system. +The first time invoked, +.Nm +will bootstrap the real +.Xr pkg 8 +from a remote repository. +.Bl -tag -width "pkg bootstrap" +.It Nm Ao Ar command Ac +If +.Xr pkg 8 +is not installed yet, it will be fetched, have its signature verified, +installed, and then have the original command forwarded to it. +If already installed, the command requested will be forwarded to the real +.Xr pkg 8 . +.It Nm Li add Oo Fl f Oc Ao Pa pkg.txz Ac +Install +.Xr pkg 8 +from a local package instead of fetching from remote. +If a +.Pa pkg.txz.sig +file exists and +signature checking is enabled, then the signature will be verified +before installing the package. +If the +.Fl f +flag is specified, then +.Xr pkg 8 +will be installed regardless if it is already installed. +.It Nm Fl N +Do not bootstrap, just determine if +.Xr pkg 8 +is actually installed or not. +Returns 0 and the number of packages installed +if it is, otherwise 1. +.It Nm Li bootstrap Op Fl f +Attempt to bootstrap and do not forward anything to +.Xr pkg 8 +after it is installed. +If the +.Fl f +flag is specified, then +.Xr pkg 8 +will be fetched and installed regardless if it is already installed. +.El +.Sh CONFIGURATION +Configuration varies in whether it is in a repository configuration file +or the global configuration file. +.Pp +Repository configuration can be stored in +.Pa /etc/pkg/FreeBSD.conf +in the following format: +.Bd -literal -offset indent +FreeBSD: { + url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest", + mirror_type: "srv", + signature_type: "none", + fingerprints: "/usr/share/keys/pkg", + enabled: yes +} +.Ed +.Bl -tag -width signature_type -compact +.It url +Refer to +.Dv PACKAGESITE +in +.Sx ENVIRONMENT +.It mirror_type +Refer to +.Dv MIRROR_TYPE +in +.Sx ENVIRONMENT +.It signature_type +Refer to +.Dv SIGNATURE_TYPE +in +.Sx ENVIRONMENT +.It fingerprints +Refer to +.Dv FINGERPRINTS +in +.Sx ENVIRONMENT +.It enabled +Defines whether this repository should be used or not. +Valid values are +.Dv yes , +.Dv true , +.Dv 1 , +.Dv no , +.Dv false , +.Dv 0 . +.El +.Pp +Global configuration can be stored in +.Pa /usr/local/etc/pkg.conf +in the following format: +.Bd -literal -offset indent +PACKAGESITE: "pkg+http://pkg.FreeBSD.org/${ABI}/latest", +MIRROR_TYPE: "srv", +SIGNATURE_TYPE: "none", +FINGERPRINTS: "/usr/share/keys/pkg", +ASSUME_ALWAYS_YES: "yes" +REPOS_DIR: ["/etc/pkg", "/usr/local/etc/pkg/repos"] +.Ed +.Pp +Reference +.Sx ENVIRONMENT +for each variable. +.Sh ENVIRONMENT +The following environment variables can be set to override the settings +from the +.Pa pkg.conf +file used. +.Bl -tag -width "ASSUME_ALWAYS_YES" +.It Ev MIRROR_TYPE +This defines which mirror type should be used. +Valid values are +.Dv SRV , +.Dv HTTP , +.Dv NONE . +.It Ev ABI +This defines the ABI for the package to be installed. +Default ABI is determined from +.Pa /bin/sh . +.It Ev ASSUME_ALWAYS_YES +If set, no confirmation will be asked when bootstrapping +.Xr pkg 8 . +.It Ev SIGNATURE_TYPE +If set to +.Dv FINGERPRINTS +then a signature will be required and validated against known +certificate fingerprints when bootstrapping +.Xr pkg 8 . +.It Ev FINGERPRINTS +If +.Sy SIGNATURE_TYPE +is set to +.Dv FINGERPRINTS +this value should be set to the directory path where known fingerprints are +located. +.It Ev PACKAGESITE +The URL that +.Xr pkg 8 +and other packages +will be fetched from. +.It Ev REPOS_DIR +Comma-separated list of directories that should be searched for repository +configuration files. +.El +.Sh FILES +Configuration is read from the files in the listed order. +This path can be changed by setting +.Sy REPOS_DIR . +The last enabled repository is the one used for bootstrapping +.Xr pkg 8 . +.Bl -tag -width "/usr/local/etc/pkg/repos/*.conf" +.It Pa /usr/local/etc/pkg.conf +.It Pa /etc/pkg/FreeBSD.conf +.It Pa /usr/local/etc/pkg/repos/*.conf +.El +.Sh EXAMPLES +Some examples are listed here. +The full list of available commands are available in +.Xr pkg 8 +once it is bootstrapped. +.Pp +Search for a package: +.Dl $ pkg search perl +.Pp +Install a package: +.Dl % pkg install perl +.Pp +List installed packages: +.Dl $ pkg info +.Pp +Upgrade from remote repository: +.Dl % pkg upgrade +.Pp +List non-automatic packages: +.Dl $ pkg query -e '%a = 0' %o +.Pp +List automatic packages: +.Dl $ pkg query -e '%a = 1' %o +.Pp +Delete an installed package: +.Dl % pkg delete perl +.Pp +Remove unneeded dependencies: +.Dl % pkg autoremove +.Pp +Change a package from automatic to non-automatic, which will prevent +.Ic autoremove +from removing it: +.Dl % pkg set -A 0 perl +.Pp +Change a package from non-automatic to automatic, which will make +.Ic autoremove +allow it be removed once nothing depends on it: +.Dl % pkg set -A 1 perl +.Pp +Create package file from an installed package: +.Dl % pkg create -o /usr/ports/packages/All perl +.Pp +Determine which package installed a file: +.Dl $ pkg which /usr/local/bin/perl +.Pp +Audit installed packages for security advisories: +.Dl $ pkg audit +.Pp +Check installed packages for checksum mismatches: +.Dl # pkg check -s -a +.Pp +Check for missing dependencies: +.Dl # pkg check -d -a +.Sh SEE ALSO +.Xr ports 7 , +.Xr pkg 8 +.Sh HISTORY +The +.Nm +command first appeared in +.Fx 9.1 . +It became the default package tool in +.Fx 10.0 , +replacing the +pkg_install suite of tools +.Xr pkg_add 1 , +.Xr pkg_info 1 and +.Xr pkg_create 1 . diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c index 1b3146b2c..9e222cb04 100644 --- a/usr.sbin/pkg/pkg.c +++ b/usr.sbin/pkg/pkg.c @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2012 Baptiste Daroussin + * Copyright (c) 2012-2014 Baptiste Daroussin + * Copyright (c) 2013 Bryan Drewery * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,175 +29,56 @@ __FBSDID("$FreeBSD$"); #include -#include -#include +#include +#include +#include #include +#define _WITH_GETLINE #include #include -#include +#include #include #include #include #include -#include #include +#include #include #include #include #include #include +#include -#include "elf_tables.h" -#include "dns_utils.h" - -#define _LOCALBASE "/usr/local" -#define _PKGS_URL "http://pkg.FreeBSD.org" - -static const char * -elf_corres_to_string(struct _elf_corres *m, int e) -{ - int i; - - for (i = 0; m[i].string != NULL; i++) - if (m[i].elf_nb == e) - return (m[i].string); - - return ("unknown"); -} - -static int -pkg_get_myabi(char *dest, size_t sz) -{ - Elf *elf; - Elf_Data *data; - Elf_Note note; - Elf_Scn *scn; - char *src, *osname; - const char *abi; - GElf_Ehdr elfhdr; - GElf_Shdr shdr; - int fd, i, ret; - uint32_t version; - - version = 0; - ret = -1; - scn = NULL; - abi = NULL; - - if (elf_version(EV_CURRENT) == EV_NONE) { - warnx("ELF library initialization failed: %s", - elf_errmsg(-1)); - return (-1); - } - - if ((fd = open("/bin/sh", O_RDONLY)) < 0) { - warn("open()"); - return (-1); - } - - if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL) { - ret = -1; - warnx("elf_begin() failed: %s.", elf_errmsg(-1)); - goto cleanup; - } - - if (gelf_getehdr(elf, &elfhdr) == NULL) { - ret = -1; - warn("getehdr() failed: %s.", elf_errmsg(-1)); - goto cleanup; - } - - while ((scn = elf_nextscn(elf, scn)) != NULL) { - if (gelf_getshdr(scn, &shdr) != &shdr) { - ret = -1; - warn("getshdr() failed: %s.", elf_errmsg(-1)); - goto cleanup; - } - - if (shdr.sh_type == SHT_NOTE) - break; - } - - if (scn == NULL) { - ret = -1; - warn("failed to get the note section"); - goto cleanup; - } - - data = elf_getdata(scn, NULL); - src = data->d_buf; - for (;;) { - memcpy(¬e, src, sizeof(Elf_Note)); - src += sizeof(Elf_Note); - if (note.n_type == NT_VERSION) - break; - src += note.n_namesz + note.n_descsz; - } - osname = src; - src += note.n_namesz; - if (elfhdr.e_ident[EI_DATA] == ELFDATA2MSB) - version = be32dec(src); - else - version = le32dec(src); - - for (i = 0; osname[i] != '\0'; i++) - osname[i] = (char)tolower(osname[i]); - - snprintf(dest, sz, "%s:%d:%s:%s", - osname, version / 100000, - elf_corres_to_string(mach_corres, (int)elfhdr.e_machine), - elf_corres_to_string(wordsize_corres, - (int)elfhdr.e_ident[EI_CLASS])); +#include +#include - ret = 0; - - switch (elfhdr.e_machine) { - case EM_ARM: - snprintf(dest + strlen(dest), sz - strlen(dest), - ":%s:%s:%s", elf_corres_to_string(endian_corres, - (int)elfhdr.e_ident[EI_DATA]), - (elfhdr.e_flags & EF_ARM_NEW_ABI) > 0 ? - "eabi" : "oabi", - (elfhdr.e_flags & EF_ARM_VFP_FLOAT) > 0 ? - "softfp" : "vfp"); - break; - case EM_MIPS: - /* - * this is taken from binutils sources: - * include/elf/mips.h - * mapping is figured out from binutils: - * gas/config/tc-mips.c - */ - switch (elfhdr.e_flags & EF_MIPS_ABI) { - case E_MIPS_ABI_O32: - abi = "o32"; - break; - case E_MIPS_ABI_N32: - abi = "n32"; - break; - default: - if (elfhdr.e_ident[EI_DATA] == - ELFCLASS32) - abi = "o32"; - else if (elfhdr.e_ident[EI_DATA] == - ELFCLASS64) - abi = "n64"; - break; - } - snprintf(dest + strlen(dest), sz - strlen(dest), - ":%s:%s", elf_corres_to_string(endian_corres, - (int)elfhdr.e_ident[EI_DATA]), abi); - break; - } - -cleanup: - if (elf != NULL) - elf_end(elf); - - close(fd); - return (ret); -} +#include "dns_utils.h" +#include "config.h" + +struct sig_cert { + char *name; + unsigned char *sig; + int siglen; + unsigned char *cert; + int certlen; + bool trusted; +}; + +typedef enum { + HASH_UNKNOWN, + HASH_SHA256, +} hash_t; + +struct fingerprint { + hash_t type; + char *name; + char hash[BUFSIZ]; + STAILQ_ENTRY(fingerprint) next; +}; + +STAILQ_HEAD(fingerprint_list, fingerprint); static int extract_pkg_static(int fd, char *p, int sz) @@ -247,13 +129,13 @@ extract_pkg_static(int fd, char *p, int sz) warnx("fail to extract pkg-static"); cleanup: - archive_read_finish(a); + archive_read_free(a); return (ret); } static int -install_pkg_static(char *path, char *pkgpath) +install_pkg_static(const char *path, const char *pkgpath, bool force) { int pstat; pid_t pid; @@ -262,7 +144,12 @@ install_pkg_static(char *path, char *pkgpath) case -1: return (-1); case 0: - execl(path, "pkg-static", "add", pkgpath, (char *)NULL); + if (force) + execl(path, "pkg-static", "add", "-f", pkgpath, + (char *)NULL); + else + execl(path, "pkg-static", "add", pkgpath, + (char *)NULL); _exit(1); default: break; @@ -280,53 +167,34 @@ install_pkg_static(char *path, char *pkgpath) } static int -bootstrap_pkg(void) +fetch_to_fd(const char *url, char *path) { struct url *u; - FILE *remote; - FILE *config; - char *site; struct dns_srvinfo *mirrors, *current; - /* To store _https._tcp. + hostname + \0 */ - char zone[MAXHOSTNAMELEN + 13]; - char url[MAXPATHLEN]; - char conf[MAXPATHLEN]; - char abi[BUFSIZ]; - char tmppkg[MAXPATHLEN]; - char buf[10240]; - char pkgstatic[MAXPATHLEN]; - int fd, retry, ret, max_retry; struct url_stat st; + FILE *remote; + /* To store _https._tcp. + hostname + \0 */ + int fd; + int retry, max_retry; off_t done, r; - time_t now; - time_t last; + time_t now, last; + char buf[10240]; + char zone[MAXHOSTNAMELEN + 13]; + static const char *mirror_type = NULL; done = 0; last = 0; max_retry = 3; - ret = -1; - remote = NULL; - config = NULL; current = mirrors = NULL; + remote = NULL; - printf("Bootstrapping pkg please wait\n"); - - if (pkg_get_myabi(abi, MAXPATHLEN) != 0) { - warnx("failed to determine the system ABI"); + if (mirror_type == NULL && config_string(MIRROR_TYPE, &mirror_type) + != 0) { + warnx("No MIRROR_TYPE defined"); return (-1); } - if (getenv("PACKAGESITE") != NULL) - snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz", getenv("PACKAGESITE")); - else - snprintf(url, MAXPATHLEN, "%s/%s/latest/Latest/pkg.txz", - getenv("PACKAGEROOT") ? getenv("PACKAGEROOT") : _PKGS_URL, - getenv("ABI") ? getenv("ABI") : abi); - - snprintf(tmppkg, MAXPATHLEN, "%s/pkg.txz.XXXXXX", - getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); - - if ((fd = mkstemp(tmppkg)) == -1) { + if ((fd = mkstemp(path)) == -1) { warn("mkstemp()"); return (-1); } @@ -336,17 +204,19 @@ bootstrap_pkg(void) u = fetchParseURL(url); while (remote == NULL) { if (retry == max_retry) { - if (strcmp(u->scheme, "file") != 0) { + if (strcmp(u->scheme, "file") != 0 && + strcasecmp(mirror_type, "srv") == 0) { snprintf(zone, sizeof(zone), "_%s._tcp.%s", u->scheme, u->host); - printf("%s\n", zone); mirrors = dns_getsrvinfo(zone); current = mirrors; } } - if (mirrors != NULL) + if (mirrors != NULL) { strlcpy(u->host, current->host, sizeof(u->host)); + u->port = current->port; + } remote = fetchXGet(u, &st, ""); if (remote == NULL) { @@ -363,16 +233,13 @@ bootstrap_pkg(void) } } - if (remote == NULL) - goto fetchfail; - while (done < st.size) { if ((r = fread(buf, 1, sizeof(buf), remote)) < 1) break; if (write(fd, buf, r) != r) { warn("write()"); - goto cleanup; + goto fetchfail; } done += r; @@ -384,38 +251,537 @@ bootstrap_pkg(void) if (ferror(remote)) goto fetchfail; - if ((ret = extract_pkg_static(fd, pkgstatic, MAXPATHLEN)) == 0) - ret = install_pkg_static(pkgstatic, tmppkg); + goto cleanup; - snprintf(conf, MAXPATHLEN, "%s/etc/pkg.conf", - getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE); +fetchfail: + if (fd != -1) { + close(fd); + fd = -1; + unlink(path); + } - if (access(conf, R_OK) == -1) { - site = strrchr(url, '/'); - if (site == NULL) - goto cleanup; - site[0] = '\0'; - site = strrchr(url, '/'); - if (site == NULL) - goto cleanup; - site[0] = '\0'; +cleanup: + if (remote != NULL) + fclose(remote); + + return fd; +} + +static struct fingerprint * +parse_fingerprint(ucl_object_t *obj) +{ + ucl_object_t *cur; + ucl_object_iter_t it = NULL; + const char *function, *fp, *key; + struct fingerprint *f; + hash_t fct = HASH_UNKNOWN; + + function = fp = NULL; + + while ((cur = ucl_iterate_object(obj, &it, true))) { + key = ucl_object_key(cur); + if (cur->type != UCL_STRING) + continue; + if (strcasecmp(key, "function") == 0) { + function = ucl_object_tostring(cur); + continue; + } + if (strcasecmp(key, "fingerprint") == 0) { + fp = ucl_object_tostring(cur); + continue; + } + } + + if (fp == NULL || function == NULL) + return (NULL); + + if (strcasecmp(function, "sha256") == 0) + fct = HASH_SHA256; + + if (fct == HASH_UNKNOWN) { + warnx("Unsupported hashing function: %s", function); + return (NULL); + } + + f = calloc(1, sizeof(struct fingerprint)); + f->type = fct; + strlcpy(f->hash, fp, sizeof(f->hash)); + + return (f); +} + +static void +free_fingerprint_list(struct fingerprint_list* list) +{ + struct fingerprint *fingerprint, *tmp; + + STAILQ_FOREACH_SAFE(fingerprint, list, next, tmp) { + free(fingerprint->name); + free(fingerprint); + } + free(list); +} + +static struct fingerprint * +load_fingerprint(const char *dir, const char *filename) +{ + ucl_object_t *obj = NULL; + struct ucl_parser *p = NULL; + struct fingerprint *f; + char path[MAXPATHLEN]; + + f = NULL; + + snprintf(path, MAXPATHLEN, "%s/%s", dir, filename); + + p = ucl_parser_new(0); + if (!ucl_parser_add_file(p, path)) { + warnx("%s: %s", path, ucl_parser_get_error(p)); + ucl_parser_free(p); + return (NULL); + } + + obj = ucl_parser_get_object(p); + + if (obj->type == UCL_OBJECT) + f = parse_fingerprint(obj); + + if (f != NULL) + f->name = strdup(filename); + + ucl_object_free(obj); + ucl_parser_free(p); + + return (f); +} + +static struct fingerprint_list * +load_fingerprints(const char *path, int *count) +{ + DIR *d; + struct dirent *ent; + struct fingerprint *finger; + struct fingerprint_list *fingerprints; + + *count = 0; + + fingerprints = calloc(1, sizeof(struct fingerprint_list)); + if (fingerprints == NULL) + return (NULL); + STAILQ_INIT(fingerprints); + + if ((d = opendir(path)) == NULL) + return (NULL); + + while ((ent = readdir(d))) { + if (strcmp(ent->d_name, ".") == 0 || + strcmp(ent->d_name, "..") == 0) + continue; + finger = load_fingerprint(path, ent->d_name); + if (finger != NULL) { + STAILQ_INSERT_TAIL(fingerprints, finger, next); + ++(*count); + } + } + + closedir(d); + + return (fingerprints); +} + +static void +sha256_hash(unsigned char hash[SHA256_DIGEST_LENGTH], + char out[SHA256_DIGEST_LENGTH * 2 + 1]) +{ + int i; + + for (i = 0; i < SHA256_DIGEST_LENGTH; i++) + sprintf(out + (i * 2), "%02x", hash[i]); + + out[SHA256_DIGEST_LENGTH * 2] = '\0'; +} + +static void +sha256_buf_bin(char *buf, size_t len, char hash[SHA256_DIGEST_LENGTH]) +{ + SHA256_CTX sha256; + + SHA256_Init(&sha256); + SHA256_Update(&sha256, buf, len); + SHA256_Final(hash, &sha256); +} + + +static void +sha256_buf(char *buf, size_t len, char out[SHA256_DIGEST_LENGTH * 2 + 1]) +{ + unsigned char hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX sha256; + + out[0] = '\0'; + + SHA256_Init(&sha256); + SHA256_Update(&sha256, buf, len); + SHA256_Final(hash, &sha256); + sha256_hash(hash, out); +} + +static int +sha256_fd(int fd, char out[SHA256_DIGEST_LENGTH * 2 + 1]) +{ + int my_fd; + FILE *fp; + char buffer[BUFSIZ]; + unsigned char hash[SHA256_DIGEST_LENGTH]; + size_t r; + int ret; + SHA256_CTX sha256; + + my_fd = -1; + fp = NULL; + r = 0; + ret = 1; + + out[0] = '\0'; + + /* Duplicate the fd so that fclose(3) does not close it. */ + if ((my_fd = dup(fd)) == -1) { + warnx("dup"); + goto cleanup; + } + + if ((fp = fdopen(my_fd, "rb")) == NULL) { + warnx("fdopen"); + goto cleanup; + } + + SHA256_Init(&sha256); + + while ((r = fread(buffer, 1, BUFSIZ, fp)) > 0) + SHA256_Update(&sha256, buffer, r); + + if (ferror(fp) != 0) { + warnx("fread"); + goto cleanup; + } + + SHA256_Final(hash, &sha256); + sha256_hash(hash, out); + ret = 0; + +cleanup: + if (fp != NULL) + fclose(fp); + else if (my_fd != -1) + close(my_fd); + (void)lseek(fd, 0, SEEK_SET); + + return (ret); +} + +static RSA * +load_rsa_public_key_buf(unsigned char *cert, int certlen) +{ + RSA *rsa = NULL; + BIO *bp; + char errbuf[1024]; + + bp = BIO_new_mem_buf((void *)cert, certlen); + if (!PEM_read_bio_RSA_PUBKEY(bp, &rsa, NULL, NULL)) { + warn("error reading public key: %s", + ERR_error_string(ERR_get_error(), errbuf)); + BIO_free(bp); + return (NULL); + } + BIO_free(bp); + return (rsa); +} + + +static bool +rsa_verify_cert(int fd, unsigned char *key, int keylen, + unsigned char *sig, int siglen) +{ + char sha256[SHA256_DIGEST_LENGTH *2 +1]; + char hash[SHA256_DIGEST_LENGTH]; + char errbuf[1024]; + RSA *rsa = NULL; + int ret; + + lseek(fd, 0, SEEK_SET); + sha256_fd(fd, sha256); + + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + + sha256_buf_bin(sha256, strlen(sha256), hash); + + rsa = load_rsa_public_key_buf(key, keylen); + if (rsa == NULL) + return (false); + ret = RSA_verify(NID_sha256, hash, sizeof(hash), sig, siglen, rsa); + if (ret == 0) { + warnx("%s: %s", key, ERR_error_string(ERR_get_error(), errbuf)); + return (false); + } + + RSA_free(rsa); + ERR_free_strings(); + + return (true); +} + +static struct sig_cert * +parse_cert(int fd) { + int my_fd; + struct sig_cert *sc; + FILE *fp; + struct sbuf *buf, *sig, *cert; + char *line; + size_t linecap; + ssize_t linelen; + + buf = NULL; + my_fd = -1; + sc = NULL; + line = NULL; + linecap = 0; + + if (lseek(fd, 0, 0) == -1) { + warn("lseek"); + return (NULL); + } + + /* Duplicate the fd so that fclose(3) does not close it. */ + if ((my_fd = dup(fd)) == -1) { + warnx("dup"); + return (NULL); + } + + if ((fp = fdopen(my_fd, "rb")) == NULL) { + warn("fdopen"); + close(my_fd); + return (NULL); + } + + sig = sbuf_new_auto(); + cert = sbuf_new_auto(); - config = fopen(conf, "w+"); - if (config == NULL) + while ((linelen = getline(&line, &linecap, fp)) > 0) { + if (strcmp(line, "SIGNATURE\n") == 0) { + buf = sig; + continue; + } else if (strcmp(line, "CERT\n") == 0) { + buf = cert; + continue; + } else if (strcmp(line, "END\n") == 0) { + break; + } + if (buf != NULL) + sbuf_bcat(buf, line, linelen); + } + + fclose(fp); + + /* Trim out unrelated trailing newline */ + sbuf_setpos(sig, sbuf_len(sig) - 1); + + sbuf_finish(sig); + sbuf_finish(cert); + + sc = calloc(1, sizeof(struct sig_cert)); + sc->siglen = sbuf_len(sig); + sc->sig = calloc(1, sc->siglen); + memcpy(sc->sig, sbuf_data(sig), sc->siglen); + + sc->certlen = sbuf_len(cert); + sc->cert = strdup(sbuf_data(cert)); + + sbuf_delete(sig); + sbuf_delete(cert); + + return (sc); +} + +static bool +verify_signature(int fd_pkg, int fd_sig) +{ + struct fingerprint_list *trusted, *revoked; + struct fingerprint *fingerprint; + struct sig_cert *sc; + bool ret; + int trusted_count, revoked_count; + const char *fingerprints; + char path[MAXPATHLEN]; + char hash[SHA256_DIGEST_LENGTH * 2 + 1]; + + sc = NULL; + trusted = revoked = NULL; + ret = false; + + /* Read and parse fingerprints. */ + if (config_string(FINGERPRINTS, &fingerprints) != 0) { + warnx("No CONFIG_FINGERPRINTS defined"); + goto cleanup; + } + + snprintf(path, MAXPATHLEN, "%s/trusted", fingerprints); + if ((trusted = load_fingerprints(path, &trusted_count)) == NULL) { + warnx("Error loading trusted certificates"); + goto cleanup; + } + + if (trusted_count == 0 || trusted == NULL) { + fprintf(stderr, "No trusted certificates found.\n"); + goto cleanup; + } + + snprintf(path, MAXPATHLEN, "%s/revoked", fingerprints); + if ((revoked = load_fingerprints(path, &revoked_count)) == NULL) { + warnx("Error loading revoked certificates"); + goto cleanup; + } + + /* Read certificate and signature in. */ + if ((sc = parse_cert(fd_sig)) == NULL) { + warnx("Error parsing certificate"); + goto cleanup; + } + /* Explicitly mark as non-trusted until proven otherwise. */ + sc->trusted = false; + + /* Parse signature and pubkey out of the certificate */ + sha256_buf(sc->cert, sc->certlen, hash); + + /* Check if this hash is revoked */ + if (revoked != NULL) { + STAILQ_FOREACH(fingerprint, revoked, next) { + if (strcasecmp(fingerprint->hash, hash) == 0) { + fprintf(stderr, "The package was signed with " + "revoked certificate %s\n", + fingerprint->name); + goto cleanup; + } + } + } + + STAILQ_FOREACH(fingerprint, trusted, next) { + if (strcasecmp(fingerprint->hash, hash) == 0) { + sc->trusted = true; + sc->name = strdup(fingerprint->name); + break; + } + } + + if (sc->trusted == false) { + fprintf(stderr, "No trusted fingerprint found matching " + "package's certificate\n"); + goto cleanup; + } + + /* Verify the signature. */ + printf("Verifying signature with trusted certificate %s... ", sc->name); + if (rsa_verify_cert(fd_pkg, sc->cert, sc->certlen, sc->sig, + sc->siglen) == false) { + printf("failed\n"); + fprintf(stderr, "Signature is not valid\n"); + goto cleanup; + } + printf("done\n"); + + ret = true; + +cleanup: + if (trusted) + free_fingerprint_list(trusted); + if (revoked) + free_fingerprint_list(revoked); + if (sc) { + free(sc->cert); + free(sc->sig); + free(sc->name); + free(sc); + } + + return (ret); +} + +static int +bootstrap_pkg(bool force) +{ + int fd_pkg, fd_sig; + int ret; + char url[MAXPATHLEN]; + char tmppkg[MAXPATHLEN]; + char tmpsig[MAXPATHLEN]; + const char *packagesite; + const char *signature_type; + char pkgstatic[MAXPATHLEN]; + + fd_sig = -1; + ret = -1; + + if (config_string(PACKAGESITE, &packagesite) != 0) { + warnx("No PACKAGESITE defined"); + return (-1); + } + + if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { + warnx("Error looking up SIGNATURE_TYPE"); + return (-1); + } + + printf("Bootstrapping pkg from %s, please wait...\n", packagesite); + + /* Support pkg+http:// for PACKAGESITE which is the new format + in 1.2 to avoid confusion on why http://pkg.FreeBSD.org has + no A record. */ + if (strncmp(URL_SCHEME_PREFIX, packagesite, + strlen(URL_SCHEME_PREFIX)) == 0) + packagesite += strlen(URL_SCHEME_PREFIX); + snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz", packagesite); + + snprintf(tmppkg, MAXPATHLEN, "%s/pkg.txz.XXXXXX", + getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); + + if ((fd_pkg = fetch_to_fd(url, tmppkg)) == -1) + goto fetchfail; + + if (signature_type != NULL && + strcasecmp(signature_type, "FINGERPRINTS") == 0) { + snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.sig.XXXXXX", + getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); + snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.sig", + packagesite); + + if ((fd_sig = fetch_to_fd(url, tmpsig)) == -1) { + fprintf(stderr, "Signature for pkg not available.\n"); + goto fetchfail; + } + + if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; - fprintf(config, "packagesite: %s\n", url); - fclose(config); } + if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) + ret = install_pkg_static(pkgstatic, tmppkg, force); + goto cleanup; fetchfail: warnx("Error fetching %s: %s", url, fetchLastErrString); + fprintf(stderr, "A pre-built version of pkg could not be found for " + "your system.\n"); + fprintf(stderr, "Consider changing PACKAGESITE or installing it from " + "ports: 'ports-mgmt/pkg'.\n"); cleanup: - if (remote != NULL) - fclose(remote); - close(fd); + if (fd_sig != -1) { + close(fd_sig); + unlink(tmpsig); + } + close(fd_pkg); unlink(tmppkg); return (ret); @@ -425,6 +791,11 @@ static const char confirmation_message[] = "The package management tool is not yet installed on your system.\n" "Do you want to fetch and install it now? [y/N]: "; +static const char non_interactive_message[] = +"The package management tool is not yet installed on your system.\n" +"Please set ASSUME_ALWAYS_YES=yes environment variable to be able to bootstrap " +"in non-interactive (stdin not being a tty)\n"; + static int pkg_query_yes_no(void) { @@ -443,15 +814,71 @@ pkg_query_yes_no(void) return (ret); } +static int +bootstrap_pkg_local(const char *pkgpath, bool force) +{ + char path[MAXPATHLEN]; + char pkgstatic[MAXPATHLEN]; + const char *signature_type; + int fd_pkg, fd_sig, ret; + + fd_sig = -1; + ret = -1; + + fd_pkg = open(pkgpath, O_RDONLY); + if (fd_pkg == -1) + err(EXIT_FAILURE, "Unable to open %s", pkgpath); + + if (config_string(SIGNATURE_TYPE, &signature_type) != 0) { + warnx("Error looking up SIGNATURE_TYPE"); + return (-1); + } + if (signature_type != NULL && + strcasecmp(signature_type, "FINGERPRINTS") == 0) { + snprintf(path, sizeof(path), "%s.sig", pkgpath); + + if ((fd_sig = open(path, O_RDONLY)) == -1) { + fprintf(stderr, "Signature for pkg not available.\n"); + goto cleanup; + } + + if (verify_signature(fd_pkg, fd_sig) == false) + goto cleanup; + } + + if ((ret = extract_pkg_static(fd_pkg, pkgstatic, MAXPATHLEN)) == 0) + ret = install_pkg_static(pkgstatic, pkgpath, force); + +cleanup: + close(fd_pkg); + if (fd_sig != -1) + close(fd_sig); + + return (ret); +} + int -main(__unused int argc, char *argv[]) +main(int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; + const char *pkgarg; + bool bootstrap_only, force, yes; + + bootstrap_only = false; + force = false; + pkgarg = NULL; + yes = false; snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getenv("LOCALBASE") ? getenv("LOCALBASE") : _LOCALBASE); - if (access(pkgpath, X_OK) == -1) { + if (argc > 1 && strcmp(argv[1], "bootstrap") == 0) { + bootstrap_only = true; + if (argc == 3 && strcmp(argv[2], "-f") == 0) + force = true; + } + + if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) { /* * To allow 'pkg -N' to be used as a reliable test for whether * a system is configured to use pkg, don't bootstrap pkg @@ -460,21 +887,51 @@ main(__unused int argc, char *argv[]) if (argv[1] != NULL && strcmp(argv[1], "-N") == 0) errx(EXIT_FAILURE, "pkg is not installed"); + config_init(); + + if (argc > 1 && strcmp(argv[1], "add") == 0) { + if (argc > 2 && strcmp(argv[2], "-f") == 0) { + force = true; + pkgarg = argv[3]; + } else + pkgarg = argv[2]; + if (pkgarg == NULL) { + fprintf(stderr, "Path to pkg.txz required\n"); + exit(EXIT_FAILURE); + } + if (access(pkgarg, R_OK) == -1) { + fprintf(stderr, "No such file: %s\n", pkgarg); + exit(EXIT_FAILURE); + } + if (bootstrap_pkg_local(pkgarg, force) != 0) + exit(EXIT_FAILURE); + exit(EXIT_SUCCESS); + } /* * Do not ask for confirmation if either of stdin or stdout is * not tty. Check the environment to see if user has answer * tucked in there already. */ - if (getenv("ASSUME_ALWAYS_YES") == NULL) { - printf("%s", confirmation_message); - if (!isatty(fileno(stdin))) + config_bool(ASSUME_ALWAYS_YES, &yes); + if (!yes) { + if (!isatty(fileno(stdin))) { + fprintf(stderr, non_interactive_message); exit(EXIT_FAILURE); + } + printf("%s", confirmation_message); if (pkg_query_yes_no() == 0) exit(EXIT_FAILURE); } - if (bootstrap_pkg() != 0) + if (bootstrap_pkg(force) != 0) exit(EXIT_FAILURE); + config_finish(); + + if (bootstrap_only) + exit(EXIT_SUCCESS); + } else if (bootstrap_only) { + printf("pkg already bootstrapped at %s\n", pkgpath); + exit(EXIT_SUCCESS); } execv(pkgpath, argv); -- 2.45.0