diff options
Diffstat (limited to 'usr.sbin/pkg/pkg.c')
| -rw-r--r-- | usr.sbin/pkg/pkg.c | 1264 |
1 files changed, 1264 insertions, 0 deletions
diff --git a/usr.sbin/pkg/pkg.c b/usr.sbin/pkg/pkg.c new file mode 100644 index 000000000000..7b0a67e69a4c --- /dev/null +++ b/usr.sbin/pkg/pkg.c @@ -0,0 +1,1264 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2012-2014 Baptiste Daroussin <bapt@FreeBSD.org> + * Copyright (c) 2013 Bryan Drewery <bdrewery@FreeBSD.org> + * 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 <sys/param.h> +#include <sys/queue.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <archive.h> +#include <archive_entry.h> +#include <assert.h> +#include <dirent.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <fetch.h> +#include <getopt.h> +#include <libutil.h> +#include <paths.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ucl.h> + +#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", + 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_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 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; + struct repositories *repositories; + + activation_test = false; + add_pkg = false; + bootstrap_only = false; + fetchOpts = ""; + force = false; + original_argv = argv; + pkgarg = NULL; + repo_name = NULL; + yes = false; + + struct option longopts[] = { + { "debug", no_argument, NULL, 'd' }, + { "only-ipv4", no_argument, NULL, '4' }, + { "only-ipv6", no_argument, NULL, '6' }, + { NULL, 0, NULL, 0 }, + }; + + snprintf(pkgpath, MAXPATHLEN, "%s/sbin/pkg", getlocalbase()); + + while ((ch = getopt_long(argc, argv, "+:dN46", longopts, NULL)) != -1) { + switch (ch) { + case 'd': + debug++; + break; + case 'N': + activation_test = true; + break; + case '4': + fetchOpts = "4"; + break; + case '6': + fetchOpts = "6"; + break; + default: + break; + } + } + if (debug > 1) + fetchDebug = 1; + + 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; + } + + 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; + } + } + } 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; + } + } + + } + 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 (argc == 1 && pkg_is_pkg_pkg(argv[0])) { + pkgarg = argv[0]; + } else { + /* + * 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; + } + } + } + + 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) { + 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, original_argv); + + /* NOT REACHED */ + return (EXIT_FAILURE); +} |
