diff --git a/usr.sbin/pkg/config.c b/usr.sbin/pkg/config.c index 7402112a967e..6f723254d394 100644 --- a/usr.sbin/pkg/config.c +++ b/usr.sbin/pkg/config.c @@ -1,535 +1,541 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 "config.h" 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; bool main_only; /* Only set in pkg.conf. */ }; static struct config_entry c[] = { [PACKAGESITE] = { PKG_CONFIG_STRING, "PACKAGESITE", URL_SCHEME_PREFIX "http://pkg.FreeBSD.org/${ABI}/latest", NULL, NULL, false, false, }, [ABI] = { PKG_CONFIG_STRING, "ABI", NULL, NULL, NULL, false, true, }, [MIRROR_TYPE] = { PKG_CONFIG_STRING, "MIRROR_TYPE", "SRV", NULL, NULL, false, false, }, [ASSUME_ALWAYS_YES] = { PKG_CONFIG_BOOL, "ASSUME_ALWAYS_YES", "NO", NULL, NULL, false, true, }, [SIGNATURE_TYPE] = { PKG_CONFIG_STRING, "SIGNATURE_TYPE", NULL, NULL, NULL, false, false, }, [FINGERPRINTS] = { PKG_CONFIG_STRING, "FINGERPRINTS", NULL, NULL, NULL, false, false, }, [REPOS_DIR] = { PKG_CONFIG_LIST, "REPOS_DIR", NULL, NULL, NULL, false, true, }, [PUBKEY] = { PKG_CONFIG_STRING, "PUBKEY", NULL, NULL, NULL, false, false } }; static int pkg_get_myabi(char *dest, size_t sz) { struct utsname uts; char machine_arch[255]; size_t len; int error; error = uname(&uts); if (error) return (errno); len = sizeof(machine_arch); error = sysctlbyname("hw.machine_arch", machine_arch, &len, NULL, 0); if (error) return (errno); machine_arch[len] = '\0'; /* * Use __FreeBSD_version rather than kernel version (uts.release) for * use in jails. This is equivalent to the value of uname -U. */ snprintf(dest, sz, "%s:%d:%s", uts.sysname, __FreeBSD_version/100000, machine_arch); return (error); } static void subst_packagesite(const char *abi) { char *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; asprintf(&newval, "%.*s%s%s", (int)(variable_string - oldval), oldval, abi, variable_string + strlen("${ABI}")); if (newval == NULL) errx(EXIT_FAILURE, "asprintf"); free(c[PACKAGESITE].value); c[PACKAGESITE].value = 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(const ucl_object_t *obj, pkg_conf_file_t conftype) { struct sbuf *buf = sbuf_new_auto(); const 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, "pubkey") == 0) sbuf_cpy(buf, "PUBKEY"); 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; case PKG_CONFIG_BOOL: temp_config[i].value = strdup(ucl_object_toboolean(cur) ? "yes" : "no"); 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; /* Prevent overriding ABI, ASSUME_ALWAYS_YES, etc. */ if (conftype != CONFFILE_PKG && c[i].main_only == true) 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) +parse_repo_file(ucl_object_t *obj, const char *requested_repo) { ucl_object_iter_t it = NULL; const 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; + if (requested_repo != NULL && strcmp(requested_repo, key) != 0) + continue; + config_parse(cur, CONFFILE_REPO); } } static int -read_conf_file(const char *confpath, pkg_conf_file_t conftype) +read_conf_file(const char *confpath, const char *requested_repo, + 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); + parse_repo_file(obj, requested_repo); } ucl_object_unref(obj); ucl_parser_free(p); return (0); } static int -load_repositories(const char *repodir) +load_repositories(const char *repodir, const char *requested_repo) { 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)) { + if (access(path, F_OK) != 0) + continue; + if (read_conf_file(path, requested_repo, + CONFFILE_REPO)) { ret = 1; goto cleanup; } } } cleanup: closedir(d); return (ret); } int -config_init(void) +config_init(const char *requested_repo) { 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 = getlocalbase(); snprintf(confpath, sizeof(confpath), "%s/etc/pkg.conf", localbase); - if (access(confpath, F_OK) == 0 && read_conf_file(confpath, + if (access(confpath, F_OK) == 0 && read_conf_file(confpath, NULL, 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)) + if (load_repositories(cv->value, requested_repo)) 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].value != NULL ? c[ABI].value : 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 index f1a2aa227f16..afcd728abd92 100644 --- a/usr.sbin/pkg/config.h +++ b/usr.sbin/pkg/config.h @@ -1,66 +1,66 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 #include #define URL_SCHEME_PREFIX "pkg+" typedef enum { PACKAGESITE = 0, ABI, MIRROR_TYPE, ASSUME_ALWAYS_YES, SIGNATURE_TYPE, FINGERPRINTS, REPOS_DIR, PUBKEY, 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); +int config_init(const char *); 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/pkg.7 b/usr.sbin/pkg/pkg.7 index c672306ac08d..b92f5c9820cd 100644 --- a/usr.sbin/pkg/pkg.7 +++ b/usr.sbin/pkg/pkg.7 @@ -1,297 +1,322 @@ .\" 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 31, 2020 +.Dd February 7, 2021 .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 +.Op Fl r Ar reponame .Op Fl y .Ao Pa pkg.txz Ac .Nm .Fl N .Nm .Op Fl 4 | Fl 6 bootstrap .Op Fl f +.Op Fl r Ar reponame .Op Fl y .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 Oo Fl y Oc Ao Pa pkg.txz Ac +.It Nm Li add Oo Fl f Oc Oo Fl r Ar reponame Oc Oo Fl y Oc Ao Pa pkg.txz Ac Install .Xr pkg 8 from a local package instead of fetching from remote. If signature checking is enabled, then the correct signature file must exist and the signature valid before the package will be installed. If the .Fl f flag is specified, then .Xr pkg 8 will be installed regardless if it is already installed. If the .Fl y flag is specified, no confirmation will be asked when bootstrapping .Xr pkg 8 . +.Pp +If a +.Ar reponame +has been specified, then the signature configuration for that repository will be +used. .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 Oo Fl 4 | Fl 6 Oc Li bootstrap Oo Fl f Oc Oo Fl y Oc +.It Nm Oo Fl 4 | Fl 6 Oc Li bootstrap Oo Fl f Oc \ +Oo Fl r Ar reponame Oc Oo Fl y Oc Attempt to bootstrap and do not forward anything to .Xr pkg 8 after it is installed. With .Fl 4 and .Fl 6 , .Nm will force IPv4 or IPv6 respectively to fetch .Xr pkg 8 and its signatures as needed. If the .Fl f flag is specified, then .Xr pkg 8 will be fetched and installed regardless if it is already installed. If the .Fl y flag is specified, no confirmation will be asked when bootstrapping .Xr pkg 8 . +.Pp +If a +.Ar reponame +has been specified, then the configuration for that repository will be used. .El .Sh CONFIGURATION Configuration varies in whether it is in a repository configuration file or the global configuration file. +The default repository configuration for +.Fx +is stored in +.Pa /etc/pkg/FreeBSD.conf , +and additional repository configuration files will be searched for in +.Ev REPOS_DIR , +or +.Pa /usr/local/etc/pkg/repos +if it is unset. .Pp -Repository configuration can be stored in -.Pa /etc/pkg/FreeBSD.conf -in the following format: +For bootstrapping, +.Nm +will process all repositories that it finds and use the last enabled repository +by default. +.Pp +Repository configuration is stored 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 +.Pp .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 788fdb39ebb9..5e424cf83034 100644 --- a/usr.sbin/pkg/pkg.c +++ b/usr.sbin/pkg/pkg.c @@ -1,1176 +1,1217 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * 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 * 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 #include #include #include #include #include "dns_utils.h" #include "config.h" struct sig_cert { char *name; unsigned char *sig; int siglen; unsigned char *cert; int certlen; bool trusted; }; struct pubkey { unsigned char *sig; int siglen; }; 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) { struct archive *a; struct archive_entry *ae; char *end; int ret, r; ret = -1; a = archive_read_new(); if (a == NULL) { warn("archive_read_new"); return (ret); } archive_read_support_filter_all(a); archive_read_support_format_tar(a); if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if (archive_read_open_fd(a, fd, 4096) != ARCHIVE_OK) { warnx("archive_read_open_fd: %s", archive_error_string(a)); goto cleanup; } ae = NULL; while ((r = archive_read_next_header(a, &ae)) == ARCHIVE_OK) { end = strrchr(archive_entry_pathname(ae), '/'); if (end == NULL) continue; if (strcmp(end, "/pkg-static") == 0) { r = archive_read_extract(a, ae, ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_ACL | ARCHIVE_EXTRACT_FFLAGS | ARCHIVE_EXTRACT_XATTR); strlcpy(p, archive_entry_pathname(ae), sz); break; } } if (r == ARCHIVE_OK) ret = 0; else warnx("failed to extract pkg-static: %s", archive_error_string(a)); cleanup: archive_read_free(a); return (ret); } static int install_pkg_static(const char *path, const char *pkgpath, bool force) { int pstat; pid_t pid; switch ((pid = fork())) { case -1: return (-1); case 0: if (force) execl(path, "pkg-static", "add", "-f", pkgpath, (char *)NULL); else execl(path, "pkg-static", "add", pkgpath, (char *)NULL); _exit(1); default: break; } while (waitpid(pid, &pstat, 0) == -1) if (errno != EINTR) return (-1); if (WEXITSTATUS(pstat)) return (WEXITSTATUS(pstat)); else if (WIFSIGNALED(pstat)) return (128 & (WTERMSIG(pstat))); return (pstat); } static int fetch_to_fd(const char *url, char *path, const char *fetchOpts) { struct url *u; struct dns_srvinfo *mirrors, *current; struct url_stat st; FILE *remote; /* To store _https._tcp. + hostname + \0 */ int fd; int retry, max_retry; ssize_t r; char buf[10240]; char zone[MAXHOSTNAMELEN + 13]; static const char *mirror_type = NULL; max_retry = 3; current = mirrors = NULL; remote = NULL; if (mirror_type == NULL && config_string(MIRROR_TYPE, &mirror_type) != 0) { warnx("No MIRROR_TYPE defined"); return (-1); } if ((fd = mkstemp(path)) == -1) { warn("mkstemp()"); return (-1); } retry = max_retry; if ((u = fetchParseURL(url)) == NULL) { warn("fetchParseURL('%s')", url); return (-1); } while (remote == NULL) { if (retry == max_retry) { if (strcmp(u->scheme, "file") != 0 && strcasecmp(mirror_type, "srv") == 0) { snprintf(zone, sizeof(zone), "_%s._tcp.%s", u->scheme, u->host); mirrors = dns_getsrvinfo(zone); current = mirrors; } } if (mirrors != NULL) { strlcpy(u->host, current->host, sizeof(u->host)); u->port = current->port; } remote = fetchXGet(u, &st, fetchOpts); if (remote == NULL) { --retry; if (retry <= 0) goto fetchfail; if (mirrors == NULL) { sleep(1); } else { current = current->next; if (current == NULL) current = mirrors; } } } while ((r = fread(buf, 1, sizeof(buf), remote)) > 0) { if (write(fd, buf, r) != r) { warn("write()"); goto fetchfail; } } if (r != 0) { warn("An error occurred while fetching pkg(8)"); goto fetchfail; } if (ferror(remote)) goto fetchfail; goto cleanup; fetchfail: if (fd != -1) { close(fd); fd = -1; unlink(path); } cleanup: if (remote != NULL) fclose(remote); return fd; } static struct fingerprint * parse_fingerprint(ucl_object_t *obj) { const 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_unref(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) { free(fingerprints); 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(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 EVP_PKEY * load_public_key_file(const char *file) { EVP_PKEY *pkey; BIO *bp; char errbuf[1024]; bp = BIO_new_file(file, "r"); if (!bp) errx(EXIT_FAILURE, "Unable to read %s", file); if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL) warnx("ici: %s", ERR_error_string(ERR_get_error(), errbuf)); BIO_free(bp); return (pkey); } static EVP_PKEY * load_public_key_buf(const unsigned char *cert, int certlen) { EVP_PKEY *pkey; BIO *bp; char errbuf[1024]; bp = BIO_new_mem_buf(__DECONST(void *, cert), certlen); if ((pkey = PEM_read_bio_PUBKEY(bp, NULL, NULL, NULL)) == NULL) warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); BIO_free(bp); return (pkey); } static bool rsa_verify_cert(int fd, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { EVP_MD_CTX *mdctx; EVP_PKEY *pkey; char sha256[(SHA256_DIGEST_LENGTH * 2) + 2]; char errbuf[1024]; bool ret; pkey = NULL; mdctx = NULL; ret = false; SSL_load_error_strings(); /* Compute SHA256 of the package. */ if (lseek(fd, 0, 0) == -1) { warn("lseek"); goto cleanup; } if ((sha256_fd(fd, sha256)) == -1) { warnx("Error creating SHA256 hash for package"); goto cleanup; } if (sigfile != NULL) { if ((pkey = load_public_key_file(sigfile)) == NULL) { warnx("Error reading public key"); goto cleanup; } } else { if ((pkey = load_public_key_buf(key, keylen)) == NULL) { warnx("Error reading public key"); goto cleanup; } } /* Verify signature of the SHA256(pkg) is valid. */ if ((mdctx = EVP_MD_CTX_create()) == NULL) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyUpdate(mdctx, sha256, strlen(sha256)) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } if (EVP_DigestVerifyFinal(mdctx, sig, siglen) != 1) { warnx("%s", ERR_error_string(ERR_get_error(), errbuf)); goto error; } ret = true; printf("done\n"); goto cleanup; error: printf("failed\n"); cleanup: if (pkey) EVP_PKEY_free(pkey); if (mdctx) EVP_MD_CTX_destroy(mdctx); ERR_free_strings(); return (ret); } static struct pubkey * read_pubkey(int fd) { struct pubkey *pk; struct sbuf *sig; char buf[4096]; int r; if (lseek(fd, 0, 0) == -1) { warn("lseek"); return (NULL); } sig = sbuf_new_auto(); while ((r = read(fd, buf, sizeof(buf))) >0) { sbuf_bcat(sig, buf, r); } sbuf_finish(sig); pk = calloc(1, sizeof(struct pubkey)); pk->siglen = sbuf_len(sig); pk->sig = calloc(1, pk->siglen); memcpy(pk->sig, sbuf_data(sig), pk->siglen); sbuf_delete(sig); return (pk); } 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(); 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_pubsignature(int fd_pkg, int fd_sig) { struct pubkey *pk; const char *pubkey; bool ret; pk = NULL; pubkey = NULL; ret = false; if (config_string(PUBKEY, &pubkey) != 0) { warnx("No CONFIG_PUBKEY defined"); goto cleanup; } if ((pk = read_pubkey(fd_sig)) == NULL) { warnx("Error reading signature"); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with public key %s... ", pubkey); if (rsa_verify_cert(fd_pkg, pubkey, NULL, 0, pk->sig, pk->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: if (pk) { free(pk->sig); free(pk); } return (ret); } 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, NULL, sc->cert, sc->certlen, sc->sig, sc->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } 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, const char *fetchOpts) { 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, fetchOpts)) == -1) goto fetchfail; if (signature_type != NULL && strcasecmp(signature_type, "NONE") != 0) { if (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, fetchOpts)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_signature(fd_pkg, fd_sig) == false) goto cleanup; } else if (strcasecmp(signature_type, "PUBKEY") == 0) { snprintf(tmpsig, MAXPATHLEN, "%s/pkg.txz.pubkeysig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP); snprintf(url, MAXPATHLEN, "%s/Latest/pkg.txz.pubkeysig", packagesite); if ((fd_sig = fetch_to_fd(url, tmpsig, fetchOpts)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_pubsignature(fd_pkg, fd_sig) == false) goto cleanup; } else { warnx("Signature type %s is not supported for " "bootstrapping.", signature_type); goto cleanup; } } 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); if (fetchLastErrCode == FETCH_RESOLV) { fprintf(stderr, "Address resolution failed for %s.\n", packagesite); fprintf(stderr, "Consider changing PACKAGESITE.\n"); } else { 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 (fd_sig != -1) { close(fd_sig); unlink(tmpsig); } if (fd_pkg != -1) { close(fd_pkg); unlink(tmppkg); } return (ret); } 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 const char args_bootstrap_message[] = "Too many arguments\n" "Usage: pkg [-4|-6] bootstrap [-f] [-y]\n"; static const char args_add_message[] = "Too many arguments\n" "Usage: pkg add [-f] [-y] {pkg.txz}\n"; static int pkg_query_yes_no(void) { int ret, c; fflush(stdout); c = getchar(); if (c == 'y' || c == 'Y') ret = 1; else ret = 0; while (c != '\n' && c != EOF) c = getchar(); 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"); goto cleanup; } if (signature_type != NULL && strcasecmp(signature_type, "NONE") != 0) { if (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; } else if (strcasecmp(signature_type, "PUBKEY") == 0) { snprintf(path, sizeof(path), "%s.pubkeysig", pkgpath); if ((fd_sig = open(path, O_RDONLY)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto cleanup; } if (verify_pubsignature(fd_pkg, fd_sig) == false) goto cleanup; } else { warnx("Signature type %s is not supported for " "bootstrapping.", signature_type); 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(int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; - const char *pkgarg; + const char *pkgarg, *repo_name; bool activation_test, add_pkg, bootstrap_only, force, yes; signed char ch; const char *fetchOpts; char *command; activation_test = false; add_pkg = false; bootstrap_only = false; command = NULL; fetchOpts = ""; force = false; pkgarg = NULL; + repo_name = NULL; yes = false; struct option longopts[] = { { "force", no_argument, NULL, 'f' }, { "only-ipv4", no_argument, NULL, '4' }, { "only-ipv6", no_argument, NULL, '6' }, { "yes", no_argument, NULL, 'y' }, { NULL, 0, NULL, 0 }, }; snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase()); - while ((ch = getopt_long(argc, argv, "-:fyN46", longopts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "-:fr::yN46", longopts, NULL)) != -1) { switch (ch) { case 'f': force = true; break; case 'N': activation_test = true; break; case 'y': yes = true; break; case '4': fetchOpts = "4"; break; case '6': fetchOpts = "6"; break; + case 'r': + /* + * The repository can only be specified for an explicit + * bootstrap request at this time, so that we don't + * confuse the user if they're trying to use a verb that + * has some other conflicting meaning but we need to + * bootstrap. + * + * For that reason, we specify that -r has an optional + * argument above and process the next index ourselves. + * This is mostly significant because getopt(3) will + * otherwise eat the next argument, which could be + * something we need to try and make sense of. + * + * At worst this gets us false positives that we ignore + * in other contexts, and we have to do a little fudging + * in order to support separating -r from the reponame + * with a space since it's not actually optional in + * the bootstrap/add sense. + */ + if (add_pkg || bootstrap_only) { + if (optarg != NULL) { + repo_name = optarg; + } else if (optind < argc) { + repo_name = argv[optind]; + } + + if (repo_name == NULL || *repo_name == '\0') { + fprintf(stderr, + "Must specify a repository with -r!\n"); + exit(EXIT_FAILURE); + } + + if (optarg == NULL) { + /* Advance past repo name. */ + optreset = 1; + optind++; + } + } + break; case 1: // Non-option arguments, first one is the command if (command == NULL) { command = argv[optind-1]; if (strcmp(command, "add") == 0) { add_pkg = true; } else if (strcmp(command, "bootstrap") == 0) { bootstrap_only = true; } } // bootstrap doesn't accept other arguments else if (bootstrap_only) { fprintf(stderr, args_bootstrap_message); exit(EXIT_FAILURE); } // For add, we accept exactly one further argument else if (add_pkg && pkgarg != NULL) { fprintf(stderr, args_add_message); exit(EXIT_FAILURE); } else if (add_pkg) { pkgarg = argv[optind-1]; } break; default: break; } } 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 * when that that option is passed. */ if (activation_test) errx(EXIT_FAILURE, "pkg is not installed"); - config_init(); + config_init(repo_name); if (add_pkg) { 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 (!yes) 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(force, fetchOpts) != 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); /* NOT REACHED */ return (EXIT_FAILURE); }