diff --git a/usr.sbin/pkg/pkg.7 b/usr.sbin/pkg/pkg.7 index 467f62d5747b..d2246f74a3fc 100644 --- a/usr.sbin/pkg/pkg.7 +++ b/usr.sbin/pkg/pkg.7 @@ -1,335 +1,336 @@ .\" 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. .\" -.Dd March 30, 2025 +.Dd April 29, 2025 .Dt PKG 7 .Os .Sh NAME .Nm pkg .Nd a utility for manipulating packages .Sh SYNOPSIS .Nm .Op Fl d .Ar command ... .Nm +.Op Fl d .Cm add -.Op Fl dfy +.Op Fl fy .Op Fl r Ar reponame .Ar pkg.pkg .Nm .Fl N .Nm .Op Fl 46d .Cm bootstrap .Op Fl fy .Op Fl r Ar reponame .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 .It Nm Ar command ... 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 Cm add Oo Fl fy Oc Oo Fl r Ar reponame Oc Ar pkg.pkg 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 46 Oc Cm bootstrap Oo Fl fy Oc \ Oo Fl r Ar reponame 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 OPTIONS The following options are supported by .Nm : .Bl -tag -width indent .It Fl d, Fl -debug Show debug information. May be specified more than once to increase the level of detail. When specified twice, .Xr fetch 3 debug output is enabled. .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 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 .Ev 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 .Xr pkg-autoremove 8 from removing it: .Dl % pkg set -A 0 perl .Pp Change a package from non-automatic to automatic, which will make .Xr pkg-autoremove 8 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 .Pp Fetch a package for a different .Fx version, along with all its dependencies: .Dl # pkg -o ABI=FreeBSD:15:amd64 -o IGNORE_OSVERSION=yes fetch -o destdir -d perl .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 92fdbf0ebff8..b62eecfc6dce 100644 --- a/usr.sbin/pkg/pkg.c +++ b/usr.sbin/pkg/pkg.c @@ -1,1270 +1,1264 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pkg.h" #include "dns_utils.h" #include "config.h" #include "hash.h" #define PKGSIGN_MARKER "$PKGSIGN:" static const struct pkgsign_impl { const char *pi_name; const struct pkgsign_ops *pi_ops; } pkgsign_builtins[] = { { .pi_name = "rsa", .pi_ops = &pkgsign_rsa, }, { .pi_name = "ecc", .pi_ops = &pkgsign_ecc, }, { .pi_name = "ecdsa", .pi_ops = &pkgsign_ecc, }, { .pi_name = "eddsa", .pi_ops = &pkgsign_ecc, }, }; typedef enum { HASH_UNKNOWN, HASH_SHA256, } hash_t; struct fingerprint { hash_t type; char *name; char hash[BUFSIZ]; STAILQ_ENTRY(fingerprint) next; }; static const char *bootstrap_name = "pkg.pkg"; STAILQ_HEAD(fingerprint_list, fingerprint); static int debug; static int pkgsign_new(const char *name, struct pkgsign_ctx **ctx) { const struct pkgsign_impl *impl; const struct pkgsign_ops *ops; struct pkgsign_ctx *nctx; size_t ctx_size; int ret; assert(*ctx == NULL); ops = NULL; for (size_t i = 0; i < nitems(pkgsign_builtins); i++) { impl = &pkgsign_builtins[i]; if (strcmp(name, impl->pi_name) == 0) { ops = impl->pi_ops; break; } } if (ops == NULL) return (ENOENT); ctx_size = ops->pkgsign_ctx_size; if (ctx_size == 0) ctx_size = sizeof(*nctx); assert(ctx_size >= sizeof(*nctx)); nctx = calloc(1, ctx_size); if (nctx == NULL) err(EXIT_FAILURE, "calloc"); nctx->impl = impl; ret = 0; if (ops->pkgsign_new != NULL) ret = (*ops->pkgsign_new)(name, nctx); if (ret != 0) { free(nctx); return (ret); } *ctx = nctx; return (0); } static bool pkgsign_verify_cert(const struct pkgsign_ctx *ctx, int fd, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { return ((*ctx->impl->pi_ops->pkgsign_verify_cert)(ctx, fd, sigfile, key, keylen, sig, siglen)); } static bool pkgsign_verify_data(const struct pkgsign_ctx *ctx, const char *data, size_t datasz, const char *sigfile, const unsigned char *key, int keylen, unsigned char *sig, int siglen) { return ((*ctx->impl->pi_ops->pkgsign_verify_data)(ctx, data, datasz, sigfile, key, keylen, sig, siglen)); } 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(struct repository *repo, 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]; max_retry = 3; current = mirrors = NULL; remote = NULL; 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 && repo->mirror_type == MIRROR_SRV) { 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) { 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); } char * pkg_read_fd(int fd, size_t *osz) { char *obuf; char buf[4096]; FILE *fp; ssize_t r; obuf = NULL; *osz = 0; fp = open_memstream(&obuf, osz); if (fp == NULL) err(EXIT_FAILURE, "open_memstream()"); while ((r = read(fd, buf, sizeof(buf))) >0) { fwrite(buf, 1, r, fp); } if (ferror(fp)) errx(EXIT_FAILURE, "reading file"); fclose(fp); return (obuf); } /* * Returns a copy of the signature type stored on the heap, and advances *bufp * past the type. */ static char * parse_sigtype(char **bufp, size_t *bufszp) { char *buf = *bufp; char *endp; char *sigtype; size_t bufsz = *bufszp; if (bufsz <= sizeof(PKGSIGN_MARKER) - 1 || strncmp(buf, PKGSIGN_MARKER, sizeof(PKGSIGN_MARKER) - 1) != 0) goto dflt; buf += sizeof(PKGSIGN_MARKER) - 1; endp = strchr(buf, '$'); if (endp == NULL) goto dflt; sigtype = strndup(buf, endp - buf); *bufp = endp + 1; *bufszp -= *bufp - buf; return (sigtype); dflt: return (strdup("rsa")); } static struct pubkey * read_pubkey(int fd) { struct pubkey *pk; char *osigb, *sigb, *sigtype; size_t sigsz; if (lseek(fd, 0, 0) == -1) { warn("lseek"); return (NULL); } osigb = sigb = pkg_read_fd(fd, &sigsz); sigtype = parse_sigtype(&sigb, &sigsz); pk = calloc(1, sizeof(struct pubkey)); pk->siglen = sigsz; pk->sig = calloc(1, pk->siglen); memcpy(pk->sig, sigb, pk->siglen); pk->sigtype = sigtype; free(osigb); return (pk); } static struct sig_cert * parse_cert(int fd) { int my_fd; struct sig_cert *sc; FILE *fp, *sigfp, *certfp, *tmpfp, *typefp; char *line; char *sig, *cert, *type; size_t linecap, sigsz, certsz, typesz; ssize_t linelen; bool end_seen; sc = NULL; line = NULL; linecap = 0; sig = cert = type = NULL; sigfp = certfp = tmpfp = typefp = NULL; 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); } sigsz = certsz = typesz = 0; sigfp = open_memstream(&sig, &sigsz); if (sigfp == NULL) err(EXIT_FAILURE, "open_memstream()"); certfp = open_memstream(&cert, &certsz); if (certfp == NULL) err(EXIT_FAILURE, "open_memstream()"); typefp = open_memstream(&type, &typesz); if (typefp == NULL) err(EXIT_FAILURE, "open_memstream()"); end_seen = false; while ((linelen = getline(&line, &linecap, fp)) > 0) { if (strcmp(line, "SIGNATURE\n") == 0) { tmpfp = sigfp; continue; } else if (strcmp(line, "TYPE\n") == 0) { tmpfp = typefp; continue; } else if (strcmp(line, "CERT\n") == 0) { tmpfp = certfp; continue; } else if (strcmp(line, "END\n") == 0) { end_seen = true; break; } if (tmpfp != NULL) fwrite(line, 1, linelen, tmpfp); } fclose(fp); fclose(sigfp); fclose(certfp); fclose(typefp); sc = calloc(1, sizeof(struct sig_cert)); sc->siglen = sigsz -1; /* Trim out unrelated trailing newline */ sc->sig = sig; if (typesz == 0) { sc->type = strdup("rsa"); free(type); } else { assert(type[typesz - 1] == '\n'); type[typesz - 1] = '\0'; sc->type = type; } /* * cert could be DER-encoded rather than PEM, so strip off any trailing * END marker if we ran over it. */ if (!end_seen && certsz > 4 && strcmp(&cert[certsz - 4], "END\n") == 0) certsz -= 4; sc->certlen = certsz; sc->cert = cert; return (sc); } static bool verify_pubsignature(int fd_pkg, int fd_sig, struct repository *r) { struct pubkey *pk; char *data; struct pkgsign_ctx *sctx; size_t datasz; bool ret; const char *pubkey; pk = NULL; sctx = NULL; data = NULL; ret = false; if (r != NULL) { if (r->pubkey == NULL) { warnx("No CONFIG_PUBKEY defined for %s", r->name); goto cleanup; } pubkey = r->pubkey; } else { 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; } if (lseek(fd_pkg, 0, SEEK_SET) == -1) { warn("lseek"); goto cleanup; } if (strcmp(pk->sigtype, "rsa") == 0) { /* Future types shouldn't do this. */ if ((data = sha256_fd(fd_pkg)) == NULL) { warnx("Error creating SHA256 hash for package"); goto cleanup; } datasz = strlen(data); } else { if ((data = pkg_read_fd(fd_pkg, &datasz)) == NULL) { warnx("Failed to read package data"); goto cleanup; } } if (pkgsign_new(pk->sigtype, &sctx) != 0) { warnx("Failed to fetch '%s' signer", pk->sigtype); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with public key %s.a.. ", r->pubkey); if (pkgsign_verify_data(sctx, data, datasz, r->pubkey, NULL, 0, pk->sig, pk->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: free(data); if (pk) { free(pk->sig); free(pk); } return (ret); } static bool verify_signature(int fd_pkg, int fd_sig, struct repository *r) { struct fingerprint_list *trusted, *revoked; struct fingerprint *fingerprint; struct sig_cert *sc; struct pkgsign_ctx *sctx; bool ret; int trusted_count, revoked_count; const char *fingerprints; char path[MAXPATHLEN]; char *hash; hash = NULL; sc = NULL; sctx = NULL; trusted = revoked = NULL; ret = false; /* Read and parse fingerprints. */ if (r != NULL) { if (r->fingerprints == NULL) { warnx("No FINGERPRINTS defined for %s", r->name); goto cleanup; } fingerprints = r->fingerprints; } else { if (config_string(FINGERPRINTS, &fingerprints) != 0) { warnx("No 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 */ hash = sha256_buf(sc->cert, sc->certlen); /* 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; } if (pkgsign_new(sc->type, &sctx) != 0) { fprintf(stderr, "Failed to fetch 'rsa' signer\n"); goto cleanup; } /* Verify the signature. */ printf("Verifying signature with trusted certificate %s... ", sc->name); if (pkgsign_verify_cert(sctx, fd_pkg, NULL, sc->cert, sc->certlen, sc->sig, sc->siglen) == false) { fprintf(stderr, "Signature is not valid\n"); goto cleanup; } ret = true; cleanup: free(hash); 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, struct repository *repo) { int fd_pkg, fd_sig; int ret; char url[MAXPATHLEN]; char tmppkg[MAXPATHLEN]; char tmpsig[MAXPATHLEN]; const char *packagesite; char pkgstatic[MAXPATHLEN]; fd_sig = -1; ret = -1; printf("Bootstrapping pkg from %s, please wait...\n", repo->url); /* 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. */ packagesite = repo->url; if (strncmp(URL_SCHEME_PREFIX, packagesite, strlen(URL_SCHEME_PREFIX)) == 0) packagesite += strlen(URL_SCHEME_PREFIX); snprintf(url, MAXPATHLEN, "%s/Latest/%s", packagesite, bootstrap_name); snprintf(tmppkg, MAXPATHLEN, "%s/%s.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, bootstrap_name); if ((fd_pkg = fetch_to_fd(repo, url, tmppkg, fetchOpts)) == -1) goto fetchfail; if (repo->signature_type == SIGNATURE_FINGERPRINT) { snprintf(tmpsig, MAXPATHLEN, "%s/%s.sig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, bootstrap_name); snprintf(url, MAXPATHLEN, "%s/Latest/%s.sig", packagesite, bootstrap_name); if ((fd_sig = fetch_to_fd(repo, url, tmpsig, fetchOpts)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_signature(fd_pkg, fd_sig, repo) == false) goto cleanup; } else if (repo->signature_type == SIGNATURE_PUBKEY) { snprintf(tmpsig, MAXPATHLEN, "%s/%s.pubkeysig.XXXXXX", getenv("TMPDIR") ? getenv("TMPDIR") : _PATH_TMP, bootstrap_name); snprintf(url, MAXPATHLEN, "%s/Latest/%s.pubkeysig", repo->url, bootstrap_name); if ((fd_sig = fetch_to_fd(repo, url, tmpsig, fetchOpts)) == -1) { fprintf(stderr, "Signature for pkg not " "available.\n"); goto fetchfail; } if (verify_pubsignature(fd_pkg, fd_sig, repo) == false) 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); } else { fprintf(stderr, "A pre-built version of pkg could not be found for " "your system.\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.pkg}\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, NULL) == 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, NULL) == 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); } #define PKG_NAME "pkg" #define PKG_DEVEL_NAME PKG_NAME "-devel" #define PKG_PKG PKG_NAME "." static bool pkg_is_pkg_pkg(const char *pkg) { char *vstart, *basename; size_t namelen; /* Strip path. */ if ((basename = strrchr(pkg, '/')) != NULL) pkg = basename + 1; /* * Chop off the final "-" (version delimiter) and check the name that * precedes it. If we didn't have a version delimiter, it must be the * pkg.$archive short form but we'll check it anyways. pkg-devel short * form will look like a pkg archive with 'devel' version, but that's * OK. We otherwise assumed that non-pkg packages will always have a * version component. */ vstart = strrchr(pkg, '-'); if (vstart == NULL) { return (strlen(pkg) > sizeof(PKG_PKG) - 1 && strncmp(pkg, PKG_PKG, sizeof(PKG_PKG) - 1) == 0); } namelen = vstart - pkg; if (namelen == sizeof(PKG_NAME) - 1 && strncmp(pkg, PKG_NAME, sizeof(PKG_NAME) - 1) == 0) return (true); if (namelen == sizeof(PKG_DEVEL_NAME) - 1 && strncmp(pkg, PKG_DEVEL_NAME, sizeof(PKG_DEVEL_NAME) - 1) == 0) return (true); return (false); } int main(int argc, char *argv[]) { char pkgpath[MAXPATHLEN]; + char **original_argv; const char *pkgarg, *repo_name; bool activation_test, add_pkg, bootstrap_only, force, yes; signed char ch; const char *fetchOpts; - char *command; struct repositories *repositories; activation_test = false; add_pkg = false; bootstrap_only = false; - command = NULL; fetchOpts = ""; force = false; + original_argv = argv; pkgarg = NULL; repo_name = NULL; yes = false; struct option longopts[] = { { "debug", no_argument, NULL, 'd' }, - { "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, "-:dfr::yN46", longopts, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) { switch (ch) { case 'd': debug++; break; - 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]; - } + default: + break; + } + } + if (debug > 1) + fetchDebug = 1; - if (repo_name == NULL || *repo_name == '\0') { - fprintf(stderr, - "Must specify a repository with -r!\n"); - exit(EXIT_FAILURE); - } + argc -= optind; + argv += optind; + + if (argc >= 1) { + if (strcmp(argv[0], "bootstrap") == 0) { + bootstrap_only = true; + } else if (strcmp(argv[0], "add") == 0) { + add_pkg = true; + } - if (optarg == NULL) { - /* Advance past repo name. */ - optreset = 1; - optind++; + optreset = 1; + optind = 1; + if (bootstrap_only || add_pkg) { + struct option sub_longopts[] = { + { "force", no_argument, NULL, 'f' }, + { "yes", no_argument, NULL, 'y' }, + { NULL, 0, NULL, 0 }, + }; + while ((ch = getopt_long(argc, argv, "+:fr:y", + sub_longopts, NULL)) != -1) { + switch (ch) { + case 'f': + force = true; + break; + case 'r': + repo_name = optarg; + break; + case 'y': + yes = true; + break; + case ':': + fprintf(stderr, "Option -%c requires an argument\n", optopt); + exit(EXIT_FAILURE); + break; + default: + break; } } - 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; + } else { + /* + * Parse -y and --yes regardless of the pkg subcommand + * specified. This is necessary to make, for example, + * `pkg install -y foobar` work as expected when pkg is + * not yet bootstrapped. + */ + struct option sub_longopts[] = { + { "yes", no_argument, NULL, 'y' }, + { NULL, 0, NULL, 0 }, + }; + while ((ch = getopt_long(argc, argv, "+y", + sub_longopts, NULL)) != -1) { + switch (ch) { + case 'y': + yes = true; + break; + default: + break; } } - // bootstrap doesn't accept other arguments - else if (bootstrap_only) { - fprintf(stderr, args_bootstrap_message); + + } + argc -= optind; + argv += optind; + + if (bootstrap_only && argc > 0) { + fprintf(stderr, args_bootstrap_message); + exit(EXIT_FAILURE); + } + + if (add_pkg) { + if (argc < 1) { + fprintf(stderr, "Path to pkg.pkg required\n"); exit(EXIT_FAILURE); - } - else if (add_pkg && pkgarg != NULL) { + } else if (argc == 1 && pkg_is_pkg_pkg(argv[0])) { + pkgarg = argv[0]; + } else { /* - * Additional arguments also means it's not a - * local bootstrap request. + * If the target package is not pkg.pkg + * or there is more than one target package, + * this is not a local bootstrap request. */ add_pkg = false; } - else if (add_pkg) { - /* - * If it's not a request for pkg or pkg-devel, - * then we must assume they were trying to - * install some other local package and we - * should try to bootstrap from the repo. - */ - if (!pkg_is_pkg_pkg(argv[optind-1])) { - add_pkg = false; - } else { - pkgarg = argv[optind-1]; - } - } - break; - default: - break; } } - if (debug > 1) - fetchDebug = 1; if ((bootstrap_only && force) || access(pkgpath, X_OK) == -1) { struct repository *repo; int ret = 0; /* * 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 option is passed. */ if (activation_test) errx(EXIT_FAILURE, "pkg is not installed"); config_init(repo_name); if (add_pkg) { - if (pkgarg == NULL) { - fprintf(stderr, "Path to pkg.pkg required\n"); - exit(EXIT_FAILURE); - } + assert(pkgarg != NULL); 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); } repositories = config_get_repositories(); STAILQ_FOREACH(repo, repositories, next) { if ((ret = bootstrap_pkg(force, fetchOpts, repo)) == 0) break; } if (ret != 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); + execv(pkgpath, original_argv); /* NOT REACHED */ return (EXIT_FAILURE); }