diff options
Diffstat (limited to 'usr/src/lib/libpkg/common/pkgweb.c')
-rw-r--r-- | usr/src/lib/libpkg/common/pkgweb.c | 3240 |
1 files changed, 0 insertions, 3240 deletions
diff --git a/usr/src/lib/libpkg/common/pkgweb.c b/usr/src/lib/libpkg/common/pkgweb.c deleted file mode 100644 index bcda3e53f2..0000000000 --- a/usr/src/lib/libpkg/common/pkgweb.c +++ /dev/null @@ -1,3240 +0,0 @@ -/* - * CDDL HEADER START - * - * The contents of this file are subject to the terms of the - * Common Development and Distribution License (the "License"). - * You may not use this file except in compliance with the License. - * - * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE - * or http://www.opensolaris.org/os/licensing. - * See the License for the specific language governing permissions - * and limitations under the License. - * - * When distributing Covered Code, include this CDDL HEADER in each - * file and include the License file at usr/src/OPENSOLARIS.LICENSE. - * If applicable, add the following below this CDDL HEADER, with the - * fields enclosed by brackets "[]" replaced with your own identifying - * information: Portions Copyright [yyyy] [name of copyright owner] - * - * CDDL HEADER END - */ - -/* - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. - * Use is subject to license terms. - */ - -/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ -/* All Rights Reserved */ - - -#include <stdio.h> -#include <limits.h> -#include <stdlib.h> -#include <unistd.h> -#include <string.h> -#include <pkglocs.h> -#include <locale.h> -#include <libintl.h> -#include <libgen.h> -#include <signal.h> -#include <sys/stat.h> -#include <sys/statvfs.h> -#include <sys/types.h> -#include <fcntl.h> -#include <dirent.h> -#include <boot_http.h> -#include <errno.h> -#include <ctype.h> -#include <openssl/pkcs7.h> -#include <openssl/ocsp.h> -#include <openssl/pkcs12.h> -#include <openssl/err.h> -#include <openssl/x509.h> -#include <openssl/pem.h> -#include <openssl/evp.h> -#include <openssl/rand.h> -#include <openssl/x509v3.h> -#include "pkglib.h" -#include "pkglibmsgs.h" -#include "pkglocale.h" -#include "keystore.h" -#include "pkgweb.h" -#include "pkgerr.h" -#include "p12lib.h" - -/* fixed format when making an OCSP request */ -#define OCSP_REQUEST_FORMAT \ - "POST %s HTTP/1.0\r\n" \ - "Content-Type: application/ocsp-request\r\n" \ - "Content-Length: %d\r\n\r\n" - -/* - * no security is afforded by using this phrase to "encrypt" CA certificates, - * but it might aid in debugging and has to be non-null - */ -#define WEB_CA_PHRASE "schizophrenic" - -/* This one needs the ': ' at the end */ -#define CONTENT_TYPE_HDR "Content-Type" -#define CONTENT_DISPOSITION_HDR "Content-Disposition" -#define CONTENT_OCSP_RESP "application/ocsp-response" -#define CONTENT_LENGTH_HDR "Content-Length" -#define LAST_MODIFIED_HDR "Last-Modified" -#define OCSP_BUFSIZ 1024 - -/* - * default amount of time that is allowed for error when checking - * OCSP response validity. - * For example, if this is set to 5 minutes, then if a response - * is issued that is valid from 12:00 to 1:00, then we will - * accept it if the local time is between 11:55 and 1:05. - * This takes care of not-quite-synchronized server and client clocks. - */ -#define OCSP_VALIDITY_PERIOD (5 * 60) - -/* this value is defined by getpassphrase(3c) manpage */ -#define MAX_PHRASELEN 257 - -/* Max length of "enter password again" prompt message */ -#define MAX_VERIFY_MSGLEN 1024 - -/* local prototypes */ -static boolean_t remove_dwnld_file(char *); -static boolean_t get_ENV_proxyport(PKG_ERR *, ushort_t *); -static boolean_t make_link(char *, char *); -static WebStatus web_send_request(PKG_ERR *, int, int, int); -static boolean_t web_eval_headers(PKG_ERR *); -static WebStatus web_get_file(PKG_ERR *, char *, int, char **); -static boolean_t ck_dwnld_dir_space(PKG_ERR *, char *, ulong_t); -static WebStatus web_connect(PKG_ERR *); -static boolean_t web_setup(PKG_ERR *); -static boolean_t check_dwnld_dir(PKG_ERR *, char *); -static boolean_t parse_url_proxy(PKG_ERR *, char *, char *, ushort_t); -static boolean_t web_disconnect(void); -static char *get_unique_filename(char *, char *); -static boolean_t get_ENV_proxy(PKG_ERR *, char **); -static char *condense_lastmodified(char *); -static int web_verify(int, X509_STORE_CTX *); -static int get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); -static boolean_t get_ocsp_uri(X509 *, char **); -static OCSPStatus ocsp_verify(PKG_ERR *, X509 *, X509 *, char *, url_hport_t *, - STACK_OF(X509) *); -static char *get_time_string(ASN1_GENERALIZEDTIME *); -static char *write_ca_file(PKG_ERR *, char *, STACK_OF(X509) *, char *); -static boolean_t _get_random_info(void *, int); -static boolean_t init_session(void); -static void progress_setup(int, ulong_t); -static void progress_report(int, ulong_t); -static void progress_finish(int); -static char *replace_token(char *, char, char); -static void dequote(char *); -static void trim(char *); - - -/* - * structure used to hold data passed back to the - * X509 verify callback routine in validate_signature() - */ -typedef struct { - url_hport_t *proxy; - PKG_ERR *err; - STACK_OF(X509) *cas; -} verify_cb_data_t; - -/* Progress bar variables */ -static ulong_t const_increment, const_divider, completed, const_completed; - -/* current network backoff wait period */ -static int cur_backoff = 0; - -/* download session context handle */ -static WEB_SESSION *ps; - -static int webpkg_install = 0; -static char *prompt = NULL; -static char *passarg = NULL; - - -/* ~~~~~~~~~~~~~~ Public Functions ~~~~~~~~~~~~~~~~~~~ */ - -/* - * Name: set_prompt - * Description: Specifies the prompt to use with the pkglib - * passphrase callback routine. - * - * Arguments: newprompt - The prompt to display - * - * Returns : NONE - */ -void -set_passphrase_prompt(char *newprompt) -{ - prompt = newprompt; -} - -/* - * Name: set_passarg - * Description: Specifies the passphrase retrieval method - * to use with the pkglib - * passphrase callback routine. - * - * Arguments: newpassarg - The new password retrieval arg - * - * Returns : NONE - */ -void -set_passphrase_passarg(char *newpassarg) -{ - passarg = newpassarg; -} - -/* - * Name: get_proxy_port - * Description: Resolves proxy specification - * - * Arguments: err - where to record any errors. - * proxy - Location to store result - if *proxy is not - * null, then it will be validated, but not changed - * - * Returns : B_TRUE - success, B_FALSE otherwise - * on success, *proxy and *port are set to either - * the user-supplied proxy and port, or the - * ones found in the environment variables - * HTTPPROXY and/or HTTPROXYPORT - */ -boolean_t -get_proxy_port(PKG_ERR *err, char **proxy, ushort_t *port) -{ - if (*proxy != NULL) { - if (!path_valid(*proxy)) { - /* bad proxy supplied */ - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_BAD_PROXY), *proxy); - return (B_FALSE); - } - if (!get_ENV_proxyport(err, port)) { - /* env set, but bad */ - return (B_FALSE); - } - } else { - if (!get_ENV_proxy(err, proxy)) { - /* environment variable set, but bad */ - return (B_FALSE); - } - if ((*proxy != NULL) && !path_valid(*proxy)) { - /* env variable set, but bad */ - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_BAD_PROXY), *proxy); - return (B_FALSE); - } - if (!get_ENV_proxyport(err, port)) { - /* env variable set, but bad */ - return (B_FALSE); - } - } - return (B_TRUE); -} - -/* - * Name: path_valid - * Description: Checks a string for being a valid path - * - * Arguments: path - path to validate - * - * Returns : B_TRUE - success, B_FALSE otherwise. - * B_FALSE means path was null, too long (>PATH_MAX), - * or too short (<1) - */ -boolean_t -path_valid(char *path) -{ - if (path == NULL) { - return (B_FALSE); - } else if (strlen(path) > PATH_MAX) { - return (B_FALSE); - } else if (strlen(path) >= 1) { - return (B_TRUE); - } else { - /* path < 1 */ - return (B_FALSE); - } -} - -/* - * Name: web_cleanup - * Description: Deletes temp files, closes, frees memory taken - * by 'ps' static structure - * - * Arguments: none - * - * Returns : none - */ -void -web_cleanup(void) -{ - PKG_ERR *err; - - if (ps == NULL) - return; - - err = pkgerr_new(); - - if (ps->keystore) { - (void) close_keystore(err, ps->keystore, NULL); - } - - ps->keystore = NULL; - - pkgerr_free(err); - - if (ps->uniqfile) { - (void) remove_dwnld_file(ps->uniqfile); - free(ps->uniqfile); - ps->uniqfile = NULL; - } - if (ps->link) { - (void) remove_dwnld_file(ps->link); - free(ps->link); - ps->link = NULL; - } - if (ps->dwnld_dir) { - (void) rmdir(ps->dwnld_dir); - ps->dwnld_dir = NULL; - } - if (ps->errstr) { - free(ps->errstr); - ps->errstr = NULL; - } - - if (ps->content) { - free(ps->content); - ps->content = NULL; - } - - if (ps->resp) { - http_free_respinfo(ps->resp); - ps->resp = NULL; - } - - if (ps) { - free(ps); - ps = NULL; - } -} - -/* - * Name: web_session_control - * Description: Downloads an arbitrary URL and saves to disk. - * - * Arguments: err - where to record any errors. - * url - URL pointing to content to download - can be - * http:// or https:// - * dwnld_dir - Directory to download into - * keystore - keystore to use for accessing trusted - * certs when downloading using SSL - * proxy - HTTP proxy to use, or NULL for no proxy - * proxy_port - HTTP proxy port to use, ignored - * if proxy is NULL - * passarg - method to retrieve password - * retries - # of times to retry download before - * giving up - * timeout - how long to wait before retrying, - * when download is interrupted - * nointeract - if non-zero, do not output - * download progress to screen - * - * Returns : B_TRUE - success, B_FALSE otherwise - */ -boolean_t -web_session_control(PKG_ERR *err, char *url, char *dwnld_dir, - keystore_handle_t keystore, char *proxy, ushort_t proxy_port, - int retries, int timeout, int nointeract, char **fname) -{ - int i; - boolean_t ret = B_TRUE; - boolean_t retrieved = B_FALSE; - - if (!init_session()) { - ret = B_FALSE; - goto cleanup; - } - - if (!parse_url_proxy(err, url, proxy, proxy_port)) { - ret = B_FALSE; - goto cleanup; - } - - ps->timeout = timeout; - - if (keystore != NULL) - ps->keystore = keystore; - - if (dwnld_dir != NULL) - ps->dwnld_dir = xstrdup(dwnld_dir); - else { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_DWNLD_DIR)); - ret = B_FALSE; - goto cleanup; - } - - if (!check_dwnld_dir(err, dwnld_dir)) { - ret = B_FALSE; - goto cleanup; - } - - for (i = 0; i < retries && !retrieved; i++) { - if (!web_setup(err)) { - ret = B_FALSE; - goto cleanup; - } - - switch (web_connect(err)) { - /* time out and wait a little bit for these failures */ - case WEB_OK: - /* were able to connect */ - reset_backoff(); - break; - case WEB_TIMEOUT: - echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); - (void) web_disconnect(); - backoff(); - continue; - - case WEB_CONNREFUSED: - echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - case WEB_HOSTDOWN: - echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - - default: - /* every other failure is a hard failure, so bail */ - ret = B_FALSE; - goto cleanup; - } - - switch (web_send_request(err, HTTP_REQ_TYPE_HEAD, - ps->data.cur_pos, ps->data.content_length)) { - case WEB_OK: - /* were able to connect */ - reset_backoff(); - break; - case WEB_TIMEOUT: - echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); - (void) web_disconnect(); - backoff(); - continue; - - case WEB_CONNREFUSED: - echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - case WEB_HOSTDOWN: - echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - default: - /* every other case is failure, so bail */ - ret = B_FALSE; - goto cleanup; - } - - if (!web_eval_headers(err)) { - ret = B_FALSE; - goto cleanup; - } - - switch (web_get_file(err, dwnld_dir, nointeract, fname)) { - case WEB_OK: - /* were able to retrieve file */ - retrieved = B_TRUE; - reset_backoff(); - break; - - case WEB_TIMEOUT: - echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); - (void) web_disconnect(); - backoff(); - continue; - - case WEB_CONNREFUSED: - echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - case WEB_HOSTDOWN: - echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), - ps->url.hport.hostname); - (void) web_disconnect(); - backoff(); - continue; - default: - /* every other failure is a hard failure, so bail */ - ret = B_FALSE; - goto cleanup; - } - } - - if (!retrieved) { - /* max retries attempted */ - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_DWNLD_FAILED), retries); - ret = B_FALSE; - } -cleanup: - (void) web_disconnect(); - if (!ret) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_DWNLD), url); - } - return (ret); -} - -/* - * Name: get_signature - * Description: retrieves signature from signed package. - * - * Arguments: err - where to record any errors. - * ids_name - name of package stream, for error reporting - * devp - Device on which package resides that we - * result - where to store resulting PKCS7 signature - * - * Returns : B_TRUE - package is signed and signature returned OR - * package is not signed, in which case result is NULL - * - * B_FALSE - there were problems accessing signature, - * and it is unknown whether it is signed or not. Errors - * recorded in 'err'. - */ -boolean_t -get_signature(PKG_ERR *err, char *ids_name, struct pkgdev *devp, PKCS7 **result) -{ - char path[PATH_MAX]; - int len, fd = -1; - struct stat buf; - FILE *fp = NULL; - boolean_t ret = B_TRUE; - BIO *sig_in = NULL; - PKCS7 *p7 = NULL; - - /* - * look for signature. If one was in the stream, - * it is now extracted - */ - if (((len = snprintf(path, PATH_MAX, "%s/%s", devp->dirname, - SIGNATURE_FILENAME)) >= PATH_MAX) || (len < 0)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), ids_name); - ret = B_FALSE; - goto cleanup; - } - - if ((fd = open(path, O_RDONLY|O_NONBLOCK)) == -1) { - /* - * only if the signature is non-existant - * do we "pass" - */ - if (errno != ENOENT) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), - strerror(errno)); - ret = B_FALSE; - goto cleanup; - } - } else { - /* found sig file. parse it. */ - if (fstat(fd, &buf) == -1) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_OPENSIG), strerror(errno)); - ret = B_FALSE; - goto cleanup; - } - - if (!S_ISREG(buf.st_mode)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), - (gettext(ERR_NOT_REG))); - ret = B_FALSE; - goto cleanup; - } - - if ((fp = fdopen(fd, "r")) == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_OPENSIG), strerror(errno)); - ret = B_FALSE; - goto cleanup; - } - - /* - * read in signature. If it's invalid, we - * punt, unless we're ignoring it - */ - if ((sig_in = BIO_new_fp(fp, BIO_NOCLOSE)) == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_OPENSIG), strerror(errno)); - goto cleanup; - } - - if ((p7 = PEM_read_bio_PKCS7(sig_in, - NULL, NULL, NULL)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), - ids_name); - ret = B_FALSE; - goto cleanup; - } - *result = p7; - p7 = NULL; - } - -cleanup: - if (sig_in) - (void) BIO_free(sig_in); - if (fp) - (void) fclose(fp); - if (fd != -1) - (void) close(fd); - if (p7) - (void) PKCS7_free(p7); - - return (ret); -} - -/* - * Name: echo_out - * Description: Conditionally output a message to stdout - * - * Arguments: nointeract - if non-zero, do not output anything - * fmt - print format - * ... - print arguments - * - * Returns : none - */ -/*PRINTFLIKE2*/ -void -echo_out(int nointeract, char *fmt, ...) -{ - va_list ap; - - va_start(ap, fmt); - - if (nointeract) - return; - - (void) vfprintf(stdout, fmt, ap); - - va_end(ap); - - (void) putc('\n', stdout); -} - -/* - * Name: strip_port - * Description: Returns "port" portion of a "hostname:port" string - * - * Arguments: proxy - full "hostname:port" string pointer - * - * Returns : the "port" portion of a "hostname:port" string, - * converted to a decimal integer, or (int)0 - * if string contains no :port suffix. - */ -ushort_t -strip_port(char *proxy) -{ - char *tmp_port; - - if ((tmp_port = strpbrk(proxy, ":")) != NULL) - return (atoi(tmp_port)); - else - return (0); -} - -/* - * Name: set_web_install - * Description: Sets flag indicating we are doing a web-based install - * - * Arguments: none - * - * Returns : none - */ -void -set_web_install(void) -{ - webpkg_install++; -} - -/* - * Name: is_web_install - * Description: Determines whether we are doing a web-based install - * - * Arguments: none - * - * Returns : non-zero if we are doing a web-based install, 0 otherwise - */ -int -is_web_install(void) -{ - return (webpkg_install); -} - -/* ~~~~~~~~~~~~~~ Private Functions ~~~~~~~~~~~~~~~~~~~ */ - -/* - * Name: web_disconnect - * Description: Disconnects connection to web server - * - * Arguments: none - * - * Returns : B_TRUE - successful disconnect, B_FALSE otherwise - * Temp certificiate files are deleted, - * if one was used to initiate the connection - * (such as when using SSL) - */ -static boolean_t -web_disconnect(void) -{ - if (ps->certfile) { - (void) unlink(ps->certfile); - } - if (http_srv_disconnect(ps->hps) == 0) - if (http_srv_close(ps->hps) == 0) - return (B_TRUE); - - return (B_FALSE); -} - -/* - * Name: check_dwnld_dir - * Description: Creates temp download directory - * - * Arguments: err - where to record any errors. - * dwnld_dir - name of directory to create - * - * Returns : B_TRUE - success, B_FALSE otherwise - * on success, directory is created with - * safe permissions - */ -static boolean_t -check_dwnld_dir(PKG_ERR *err, char *dwnld_dir) -{ - DIR *dirp; - - /* - * Check the directory passed in. If it doesn't exist, create it - * with strict permissions - */ - if ((dirp = opendir(dwnld_dir)) == NULL) { - if (mkdir(dwnld_dir, 0744) == -1) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), - dwnld_dir); - return (B_FALSE); - } - } - if (dirp) { - (void) closedir(dirp); - } - return (B_TRUE); -} - -/* - * Name: ds_validate_signature - * Description: Validates signature found in a package datastream - * - * Arguments: err - where to record any errors. - * pkgdev - Package context handle of package to verify - * pkgs - Null-terminated List of package name to verify - * ids_name - Pathname to stream to validate - * p7 - PKCS7 signature decoded from stream header - * cas - List of trusted CA certificates - * proxy - Proxy to use when doing online validation (OCSP) - * nointeract - if non-zero, do not output to screen - * - * Returns : B_TRUE - success, B_FALSE otherwise - * success means signature was completely validated, - * and contents of stream checked against signature. - */ -boolean_t -ds_validate_signature(PKG_ERR *err, struct pkgdev *pkgdev, char **pkgs, - char *ids_name, PKCS7 *p7, STACK_OF(X509) *cas, - url_hport_t *proxy, int nointeract) -{ - BIO *p7_bio; - boolean_t ret = B_TRUE; - - /* make sure it's a Signed PKCS7 message */ - if (!PKCS7_type_is_signed(p7)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_TYPE), - ids_name); - ret = B_FALSE; - goto cleanup; - } - - /* initialize PKCS7 object to be filled in */ - if (!PKCS7_get_detached(p7)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_DT), - ids_name); - ret = B_FALSE; - goto cleanup; - } - - /* dump header and packages into BIO to calculate the message digest */ - if ((p7_bio = PKCS7_dataInit(p7, NULL)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), - ids_name); - ret = B_FALSE; - goto cleanup; - } - - if ((BIO_ds_dump_header(err, p7_bio) != 0) || - (BIO_ds_dump(err, ids_name, p7_bio) != 0)) { - ret = B_FALSE; - goto cleanup; - } - (void) BIO_flush(p7_bio); - - /* validate the stream and its signature */ - if (!validate_signature(err, ids_name, p7_bio, p7, cas, - proxy, nointeract)) { - ret = B_FALSE; - goto cleanup; - } - - /* reset device stream (really bad performance for tapes) */ - (void) ds_close(1); - (void) ds_init(ids_name, pkgs, pkgdev->norewind); - -cleanup: - return (ret); -} - - -/* - * Name: validate_signature - * Description: Validates signature of an arbitrary stream of bits - * - * Arguments: err - where to record any errors. - * name - Descriptive name of object being validated, - * for good error reporting messages - * indata - BIO object to read stream bits from - * p7 - PKCS7 signature of stream - * cas - List of trusted CA certificates - * proxy - Proxy to use when doing online validation (OCSP) - * nointeract - if non-zero, do not output to screen - * - * Returns : B_TRUE - success, B_FALSE otherwise - * success means signature was completely validated, - * and contents of stream checked against signature. - */ -boolean_t -validate_signature(PKG_ERR *err, char *name, BIO *indata, PKCS7 *p7, - STACK_OF(X509) *cas, url_hport_t *proxy, int nointeract) -{ - STACK_OF(PKCS7_SIGNER_INFO) *sec_sinfos = NULL; - - PKCS7_SIGNER_INFO *signer = NULL; - X509_STORE *sec_truststore = NULL; - X509_STORE_CTX *ctx = NULL; - X509 *signer_cert = NULL, *issuer = NULL; - STACK_OF(X509) *chaincerts = NULL; - int i, k; - unsigned long errcode; - const char *err_data = NULL; - const char *err_reason = NULL; - char *err_string; - int err_flags; - verify_cb_data_t verify_data; - char *signer_sname; - char *signer_iname; - PKCS7_ISSUER_AND_SERIAL *ias; - boolean_t ret = B_TRUE; - - /* only support signed PKCS7 signatures */ - if (!PKCS7_type_is_signed(p7)) { - PKCS7err(PKCS7_F_PKCS7_DATAVERIFY, PKCS7_R_WRONG_PKCS7_TYPE); - ret = B_FALSE; - goto cleanup; - } - - /* initialize temporary internal trust store used for verification */ - sec_truststore = X509_STORE_new(); - - for (i = 0; i < sk_X509_num(cas); i++) { - if (X509_STORE_add_cert(sec_truststore, - sk_X509_value(cas, i)) == 0) { - pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); - ret = B_FALSE; - goto cleanup; - } - } - - /* get signers from the signature */ - if ((sec_sinfos = PKCS7_get_signer_info(p7)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), name); - ret = B_FALSE; - goto cleanup; - } - - /* verify each signer found in the PKCS7 signature */ - for (k = 0; k < sk_PKCS7_SIGNER_INFO_num(sec_sinfos); k++) { - signer = sk_PKCS7_SIGNER_INFO_value(sec_sinfos, k); - signer_cert = PKCS7_cert_from_signer_info(p7, signer); - signer_sname = get_subject_display_name(signer_cert); - signer_iname = get_issuer_display_name(signer_cert); - - echo_out(nointeract, gettext(MSG_VERIFY), signer_sname); - - /* find the issuer of the current cert */ - chaincerts = p7->d.sign->cert; - ias = signer->issuer_and_serial; - issuer = X509_find_by_issuer_and_serial(chaincerts, - ias->issuer, ias->serial); - - /* were we not able to find the issuer cert */ - if (issuer == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_VERIFY_ISSUER), - signer_iname, signer_sname); - ret = B_FALSE; - goto cleanup; - } - - /* Lets verify */ - if ((ctx = X509_STORE_CTX_new()) == NULL) { - pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); - ret = B_FALSE; - goto cleanup; - } - (void) X509_STORE_CTX_init(ctx, sec_truststore, - issuer, chaincerts); - (void) X509_STORE_CTX_set_purpose(ctx, - X509_PURPOSE_ANY); - - /* callback will perform OCSP on certificates with OCSP data */ - X509_STORE_CTX_set_verify_cb(ctx, web_verify); - - /* pass needed data into callback through the app_data handle */ - verify_data.proxy = proxy; - verify_data.cas = cas; - verify_data.err = err; - (void) X509_STORE_CTX_set_app_data(ctx, &verify_data); - - /* first verify the certificate chain */ - i = X509_verify_cert(ctx); - if (i <= 0 && ctx->error != X509_V_ERR_CERT_HAS_EXPIRED) { - signer_sname = - get_subject_display_name(ctx->current_cert); - signer_iname = - get_issuer_display_name(ctx->current_cert); - /* if the verify context holds an error, print it */ - if (ctx->error != X509_V_OK) { - pkgerr_add(err, PKGERR_VERIFY, - gettext(ERR_VERIFY_SIG), signer_sname, - signer_iname, - (char *)X509_verify_cert_error_string(ctx->error)); - } else { - /* some other error. print them all. */ - while ((errcode = ERR_get_error_line_data(NULL, - NULL, &err_data, &err_flags)) != 0) { - size_t errsz; - err_reason = - ERR_reason_error_string(errcode); - if (err_reason == NULL) { - err_reason = - gettext(ERR_SIG_INT); - } - - if (!(err_flags & ERR_TXT_STRING)) { - err_data = - gettext(ERR_SIG_INT); - } - errsz = strlen(err_reason) + - strlen(err_data) + 3; - err_string = xmalloc(errsz); - (void) snprintf(err_string, errsz, - "%s: %s", err_reason, err_data); - pkgerr_add(err, PKGERR_VERIFY, - gettext(ERR_VERIFY_SIG), - signer_sname, signer_iname, - err_string); - free(err_string); - } - } - ret = B_FALSE; - goto cleanup; - } - - /* now verify the signature */ - i = PKCS7_signatureVerify(indata, p7, signer, issuer); - - if (i <= 0) { - /* print out any OpenSSL-specific errors */ - signer_sname = - get_subject_display_name(ctx->current_cert); - signer_iname = - get_subject_display_name(ctx->current_cert); - while ((errcode = ERR_get_error_line_data(NULL, - NULL, &err_data, &err_flags)) != 0) { - err_reason = - ERR_reason_error_string(errcode); - if (err_reason == NULL) { - err_reason = - gettext(ERR_SIG_INT); - } - - if (!(err_flags & ERR_TXT_STRING)) { - err_data = - gettext(ERR_SIG_INT); - } - pkgerr_add(err, PKGERR_VERIFY, - gettext(ERR_VERIFY_SIG), signer_sname, - signer_iname, err_reason); - pkgerr_add(err, PKGERR_VERIFY, - gettext(ERR_VERIFY_SIG), signer_sname, - signer_iname, err_data); - } - ret = B_FALSE; - goto cleanup; - } - - echo_out(nointeract, gettext(MSG_VERIFY_OK), signer_sname); - } - - /* signature(s) verified successfully */ -cleanup: - if (ctx) - X509_STORE_CTX_cleanup(ctx); - return (ret); -} - -/* - * Name: web_verify - * Description: Callback used by PKCS7_dataVerify when - * verifying a certificate chain. - * - * Arguments: err - where to record any errors. - * ctx - The context handle of the current verification operation - * - * Returns : B_TRUE - success, B_FALSE otherwise - * if it's '0' (not OK) we simply return it, since the - * verification operation has already determined that the - * cert is invalid. if 'ok' is non-zero, then we do our - * checks, and return 0 or 1 based on if the cert is - * invalid or valid. - */ -static int -web_verify(int ok, X509_STORE_CTX *ctx) -{ - X509 *curr_cert; - X509 *curr_issuer; - char *uri; - url_hport_t *proxy; - PKG_ERR *err = NULL; - STACK_OF(X509) *cas; - if (!ok) { - /* don't override a verify failure */ - return (ok); - } - - - /* get app data supplied through callback context */ - err = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->err; - proxy = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->proxy; - cas = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->cas; - - /* Check revocation status */ - curr_cert = X509_STORE_CTX_get_current_cert(ctx); - - /* this shouldn't happen */ - if (curr_cert == NULL) { - pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), - __FILE__, __LINE__); - return (0); - } - - /* don't perform OCSP unless cert has required OCSP extensions */ - if (get_ocsp_uri(curr_cert, &uri)) { - if (get_issuer(&curr_issuer, ctx, curr_cert) <= 0) { - /* no issuer! */ - pkgerr_add(err, PKGERR_INTERNAL, - gettext(ERR_PKG_INTERNAL), - __FILE__, __LINE__); - return (0); - } - - /* - * ok we have the current cert - * and its issuer. Do the OCSP check - */ - - /* - * OCSP extensions are, by, RFC 2459, never critical - * extensions, therefore, we only fail if we were able - * to explicitly contact an OCSP responder, and that - * responder did not indicate the cert was valid. We - * also fail if user-supplied data could not be parsed - * or we run out of memory. We succeeed for "soft" - * failures, such as not being able to connect to the - * OCSP responder, or trying to use if the OCSP URI - * indicates SSL must be used (which we do not - * support) - */ - switch (ocsp_verify(err, curr_cert, curr_issuer, - uri, proxy, cas)) { - case OCSPMem: /* Ran out of memory */ - case OCSPInternal: /* Some internal error */ - case OCSPVerify: /* OCSP responder indicated fail */ - return (0); - } - /* all other cases are success, or soft failures */ - pkgerr_clear(err); - } - - return (ok); -} - -/* - * Name: get_time_string - * Description: Generates a human-readable string from an ASN1_GENERALIZED_TIME - * - * Arguments: intime - The time to convert - * - * Returns : A pointer to a static string representing the passed-in time. - */ -static char -*get_time_string(ASN1_GENERALIZEDTIME *intime) -{ - - static char time[ATTR_MAX]; - BIO *mem; - char *p; - - if (intime == NULL) { - return (NULL); - } - if ((mem = BIO_new(BIO_s_mem())) == NULL) { - return (NULL); - } - - if (ASN1_GENERALIZEDTIME_print(mem, intime) == 0) { - (void) BIO_free(mem); - return (NULL); - } - - if (BIO_gets(mem, time, ATTR_MAX) <= 0) { - (void) BIO_free(mem); - return (NULL); - } - - (void) BIO_free(mem); - - /* trim the end of the string */ - for (p = time + strlen(time) - 1; isspace(*p); p--) { - *p = '\0'; - } - - return (time); -} - -/* - * Name: get_ocsp_uri - * Description: Examines an X509 certificate and retrieves the embedded - * OCSP Responder URI if one exists. - * - * Arguments: cert - The cert to inspect - * uri - pointer where the newly-allocated URI is placed, if found - * - * Returns : Success if the URI was found. Appropriate status otherwise. - */ -static boolean_t -get_ocsp_uri(X509 *cert, char **uri) -{ - AUTHORITY_INFO_ACCESS *aia; - ACCESS_DESCRIPTION *ad; - int i; - - if (getenv("PKGWEB_TEST_OCSP")) { - *uri = xstrdup(getenv("PKGWEB_TEST_OCSP")); - return (B_TRUE); - } - - /* get the X509v3 extension holding the OCSP URI */ - if ((aia = X509_get_ext_d2i(cert, NID_info_access, - NULL, NULL)) != NULL) { - for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { - ad = sk_ACCESS_DESCRIPTION_value(aia, i); - if (OBJ_obj2nid(ad->method) == NID_ad_OCSP) { - if (ad->location->type == GEN_URI) { - *uri = - xstrdup((char *)ASN1_STRING_data(ad->location->d.ia5)); - return (B_TRUE); - } - } - } - } - - /* no URI was found */ - return (B_FALSE); -} - -/* - * Name: ocsp_verify - * Description: Attempts to contact an OCSP Responder and ascertain the validity - * of an X509 certificate. - * - * Arguments: err - Error object to add error messages to - * cert - The cert to validate - * issuer - The certificate of the issuer of 'cert' - * uri - The OCSP Responder URI - * cas - The trusted CA certificates used to verify the - * signed OCSP response - * Returns : Success - The OCSP Responder reported a 'good' - * status for the cert otherwise, appropriate - * error is returned. - */ -static OCSPStatus -ocsp_verify(PKG_ERR *err, X509 *cert, X509 *issuer, - char *uri, url_hport_t *proxy, STACK_OF(X509) *cas) -{ - OCSP_CERTID *id; - OCSP_REQUEST *req; - OCSP_RESPONSE *resp; - OCSP_BASICRESP *bs; - BIO *cbio, *mem; - char ocspbuf[OCSP_BUFSIZ]; - char *host = NULL, *portstr = NULL, *path = "/", *p, *q, *r; - int port, status, reason; - int len, retval, respcode, use_ssl = 0; - ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; - char *subjname; - time_t currtime; - char currtimestr[ATTR_MAX]; - unsigned long errcode; - const char *err_reason; - - subjname = get_subject_display_name(cert); - - /* parse the URI into its constituent parts */ - if (OCSP_parse_url(uri, &host, &portstr, &path, &use_ssl) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_PARSE), uri); - return (OCSPParse); - } - - /* we don't currently support SSL-based OCSP Responders */ - if (use_ssl) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_UNSUP), uri); - return (OCSPUnsupported); - } - - /* default port if none specified */ - if (portstr == NULL) { - port = (int)URL_DFLT_SRVR_PORT; - } else { - port = (int)strtoul(portstr, &r, 10); - if (*r != '\0') { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_PARSE), uri); - return (OCSPParse); - } - } - - /* allocate new request structure */ - if ((req = OCSP_REQUEST_new()) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); - return (OCSPMem); - } - - /* convert cert and issuer fields into OCSP request data */ - if ((id = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), - __FILE__, __LINE__); - return (OCSPInternal); - } - - /* fill out request structure with request data */ - if ((OCSP_request_add0_id(req, id)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), - __FILE__, __LINE__); - return (OCSPInternal); - } - - /* add nonce */ - (void) OCSP_request_add1_nonce(req, NULL, -1); - - /* connect to host, or proxy */ - if (proxy != NULL) { - if ((cbio = BIO_new_connect(proxy->hostname)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); - return (OCSPMem); - } - - /* - * BIO_set_conn_int_port takes an int *, so let's give it one - * rather than an ushort_t * - */ - port = proxy->port; - (void) BIO_set_conn_int_port(cbio, &port); - if (BIO_do_connect(cbio) <= 0) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_CONNECT), - proxy->hostname, port); - return (OCSPConnect); - } - } else { - if ((cbio = BIO_new_connect(host)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); - return (OCSPMem); - } - - (void) BIO_set_conn_int_port(cbio, &port); - if (BIO_do_connect(cbio) <= 0) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_CONNECT), - host, port); - return (OCSPConnect); - } - } - - /* calculate length of binary request data */ - len = i2d_OCSP_REQUEST(req, NULL); - - /* send the request headers */ - if (proxy != NULL) { - retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, uri, len); - } else { - retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, path, len); - } - - if (retval <= 0) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); - return (OCSPRequest); - } - - /* send the request binary data */ - if (i2d_OCSP_REQUEST_bio(cbio, req) <= 0) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); - return (OCSPRequest); - } - - /* - * read the response into a memory BIO, so we can 'gets' - * (socket bio's don't support BIO_gets) - */ - if ((mem = BIO_new(BIO_s_mem())) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); - return (OCSPMem); - } - - while ((len = BIO_read(cbio, ocspbuf, OCSP_BUFSIZ))) { - if (len < 0) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_READ), host); - return (OCSPRequest); - } - if (BIO_write(mem, ocspbuf, len) != len) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); - return (OCSPMem); - } - } - - /* now get the first line of the response */ - if (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) <= 0) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_PARSE)); - return (OCSPRequest); - } - - /* parse the header response */ - /* it should look like "HTTP/x.x 200 OK" */ - - /* skip past the protocol info */ - for (p = ocspbuf; (*p != '\0') && !isspace(*p); p++) - continue; - - /* skip past whitespace betwen protocol and start of response code */ - while ((*p != '\0') && isspace(*p)) { - p++; - } - - if (*p == '\0') { - /* premature end */ - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_PARSE), ocspbuf); - return (OCSPRequest); - } - - /* find end of response code */ - for (q = p; (*q != NULL) && !isspace(*q); q++) - continue; - - /* mark end of response code */ - *q++ = '\0'; - - /* parse response code */ - respcode = strtoul(p, &r, 10); - if (*r != '\0') { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_PARSE), ocspbuf); - return (OCSPRequest); - } - - /* now find beginning of the response string */ - while ((*q != NULL) && isspace(*q)) { - q++; - } - - /* trim whitespace from end of message */ - for (r = (q + strlen(q) - 1); isspace(*r); r--) { - *r = '\0'; - } - - /* response must be OK */ - if (respcode != 200) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_NOTOK), 200, - respcode, q); - return (OCSPRequest); - } - - /* read headers, looking for content-type or a blank line */ - while (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) > 0) { - - /* if we get a content type, make sure it's the right type */ - if (ci_strneq(ocspbuf, CONTENT_TYPE_HDR, - strlen(CONTENT_TYPE_HDR))) { - - /* look for the delimiting : */ - p = strchr(ocspbuf + strlen(CONTENT_TYPE_HDR), ':'); - - if (p == NULL) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_PARSE), ocspbuf); - return (OCSPResponder); - } - - /* skip over ':' */ - p++; - - /* find beginning of the content type */ - while ((*p != NULL) && isspace(*p)) { - p++; - } - - if (!ci_strneq(p, CONTENT_OCSP_RESP, - strlen(CONTENT_OCSP_RESP))) { - /* response is not right type */ - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_TYPE), - p, CONTENT_OCSP_RESP); - return (OCSPResponder); - } - - /* continue with next header line */ - continue; - } - - /* scan looking for a character */ - for (p = ocspbuf; (*p != '\0') && isspace(*p); p++) { - continue; - } - /* - * if we got to the end of the line with - * no chars, then this is a blank line - */ - if (*p == '\0') { - break; - } - } - - - if (*p != '\0') { - /* last line was not blank */ - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_RESP_PARSE), ocspbuf); - return (OCSPResponder); - } - - /* now read in the binary response */ - if ((resp = d2i_OCSP_RESPONSE_bio(mem, NULL)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); - return (OCSPResponder); - } - - /* free temp BIOs */ - (void) BIO_free(mem); - (void) BIO_free_all(cbio); - cbio = NULL; - - /* make sure request was successful */ - if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_NOTOK), - OCSP_RESPONSE_STATUS_SUCCESSFUL, - OCSP_response_status(resp), - OCSP_response_status_str(OCSP_response_status(resp))); - return (OCSPResponder); - } - - /* parse binary response into internal structure */ - if ((bs = OCSP_response_get1_basic(resp)) == NULL) { - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); - return (OCSPParse); - } - - /* - * From here to the end of the code, the return values - * should be hard failures - */ - - /* verify the response, warn if no nonce */ - if (OCSP_check_nonce(req, bs) <= 0) { - logerr(pkg_gt(WRN_OCSP_RESP_NONCE)); - } - - if (OCSP_basic_verify(bs, cas, NULL, OCSP_TRUSTOTHER) <= 0) { - while ((errcode = ERR_get_error()) != NULL) { - err_reason = ERR_reason_error_string(errcode); - if (err_reason == NULL) { - err_reason = - gettext(ERR_SIG_INT); - } - pkgerr_add(err, PKGERR_PARSE, (char *)err_reason); - } - pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_VERIFY_FAIL), - uri); - return (OCSPVerify); - } - - /* check the validity of our certificate */ - if (OCSP_resp_find_status(bs, id, &status, &reason, - &rev, &thisupd, &nextupd) == NULL) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_VERIFY_NO_STATUS), subjname); - return (OCSPVerify); - } - - if ((currtime = time(NULL)) == (time_t)-1) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_VERIFY_NOTIME)); - return (OCSPVerify); - } - - (void) strlcpy(currtimestr, ctime(&currtime), ATTR_MAX); - - /* trim end */ - for (r = currtimestr + strlen(currtimestr) - 1; - isspace(*r); r--) { - *r = '\0'; - } - - if (!OCSP_check_validity(thisupd, nextupd, - OCSP_VALIDITY_PERIOD, -1)) { - if (nextupd != NULL) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_VERIFY_VALIDITY), - get_time_string(thisupd), get_time_string(nextupd), - currtimestr); - } else { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_VERIFY_VALIDITY), - get_time_string(thisupd), - currtimestr); - } - return (OCSPVerify); - } - - if (status != V_OCSP_CERTSTATUS_GOOD) { - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_OCSP_VERIFY_STATUS), subjname, - OCSP_cert_status_str(status)); - return (OCSPVerify); - } - - /* everythign checks out */ - return (OCSPSuccess); -} - -/* - * Name: get_issuer - * Description: Attempts to find the issuing certificate for a given certificate - * This will look in both the list of trusted certificates found in - * the X509_STORE_CTX structure, as well as the list of untrusted - * chain certificates found in the X509_STORE_CTX structure. - * Arguments: - * issuer - The resulting issuer cert is placed here, if found - * ctx - The current verification context - * x - The certificate whose issuer we are looking for - * Returns : Success - The issuer cert was found and placed in *issuer. - * otherwise, appropriate error is returned. - */ -static int -get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x) -{ - int i, ok; - - /* - * first look in the list of trusted - * certs, using the context's method to do so - */ - if ((ok = ctx->get_issuer(issuer, ctx, x)) > 0) { - return (ok); - } - - if (ctx->untrusted != NULL) { - /* didn't find it in trusted certs, look through untrusted */ - for (i = 0; i < sk_X509_num(ctx->untrusted); i++) { - if (X509_check_issued(sk_X509_value(ctx->untrusted, i), - x) == X509_V_OK) { - *issuer = sk_X509_value(ctx->untrusted, i); - return (1); - } - } - } - *issuer = NULL; - return (0); -} - -/* - * Name: parse_url_proxy - * Description: Parses URL and optional proxy specification, populates static - * 'ps' structure - * - * Arguments: err - where to record any errors. - * url - URL to parse - * proxy - proxy to parse, or NULL for no proxy - * proxy_port - Default proxy port to use if no proxy - * port specified in 'proxy' - * - * Returns : B_TRUE - success, B_FALSE otherwise - * on success, 'ps->url' and 'ps->proxy' are populated - * with parsed data. - */ -static boolean_t -parse_url_proxy(PKG_ERR *err, char *url, char *proxy, ushort_t proxy_port) -{ - boolean_t ret = B_TRUE; - if (!path_valid(url)) { - ret = B_FALSE; - goto cleanup; - } - - if (url_parse(url, &ps->url) != URL_PARSE_SUCCESS) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_PARSE_URL), url); - ret = B_FALSE; - goto cleanup; - } - - if (proxy != NULL) { - if (url_parse_hostport(proxy, &ps->proxy, proxy_port) - != URL_PARSE_SUCCESS) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_BAD_PROXY), proxy); - ret = B_FALSE; - goto cleanup; - } - } - -cleanup: - return (ret); -} - -/* - * Name: web_setup - * Description: Initializes http library settings - * - * Arguments: err - where to record any errors. - * - * Returns : B_TRUE - success, B_FALSE otherwise - */ -static boolean_t -web_setup(PKG_ERR *err) -{ - boolean_t ret = B_TRUE; - static boolean_t keepalive = B_TRUE; - - if ((ps->hps = http_srv_init(&ps->url)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); - ret = B_FALSE; - goto cleanup; - } - - if (getenv("WEBPKG_DEBUG") != NULL) { - http_set_verbose(B_TRUE); - } - - if (ps->proxy.hostname[0] != '\0' && - http_set_proxy(ps->hps, &ps->proxy) != 0) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); - ret = B_FALSE; - goto cleanup; - } - if (http_set_keepalive(ps->hps, keepalive) != 0) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); - ret = B_FALSE; - goto cleanup; - } - if (http_set_socket_read_timeout(ps->hps, ps->timeout) != 0) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); - ret = B_FALSE; - goto cleanup; - } - if (http_set_random_file(ps->hps, RANDOM) != 0) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); - ret = B_FALSE; - goto cleanup; - } - - (void) http_set_p12_format(B_TRUE); - -cleanup: - return (ret); -} - -/* - * Name: web_connect - * Description: Makes connection with URL stored in static 'ps' structure. - * - * Arguments: err - where to record any errors. - * - * Returns : WEB_OK - connection successful - * WEB_VERIFY_SETUP - Unable to complete necessary - * SSL setup - * WEB_CONNREFUSED - Connection was refused to web site - * WEB_HOSTDOWN - Host was not responding to request - * WEB_NOCONNECT - Some other connection failure - */ -static WebStatus -web_connect(PKG_ERR *err) -{ - STACK_OF(X509) *sec_cas = NULL; - char *path; - WebStatus ret = WEB_OK; - ulong_t errcode; - uint_t errsrc; - int my_errno = 0; - const char *libhttperr = NULL; - - if (ps->url.https == B_TRUE) { - /* get CA certificates */ - if (find_ca_certs(err, ps->keystore, &sec_cas) != 0) { - ret = WEB_VERIFY_SETUP; - goto cleanup; - } - - if (sk_X509_num(sec_cas) < 1) { - /* no trusted websites */ - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_KEYSTORE_NOTRUST)); - ret = WEB_VERIFY_SETUP; - goto cleanup; - } - - /* - * write out all CA certs to temp file. libwanboot should - * have an interface for giving it a list of trusted certs - * through an in-memory structure, but currently that does - * not exist - */ - if ((path = write_ca_file(err, ps->dwnld_dir, sec_cas, - WEB_CA_PHRASE)) == NULL) { - ret = WEB_VERIFY_SETUP; - goto cleanup; - } - - ps->certfile = path; - if (http_set_password(ps->hps, WEB_CA_PHRASE) != 0) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTPS_PASSWD)); - ret = WEB_VERIFY_SETUP; - goto cleanup; - } - - if (http_set_certificate_authority_file(path) != 0) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTPS_CA)); - ret = WEB_VERIFY_SETUP; - goto cleanup; - } - } - - if (http_srv_connect(ps->hps) != 0) { - while ((errcode = http_get_lasterr(ps->hps, &errsrc)) != 0) { - /* Have an error - is it EINTR? */ - if (errsrc == ERRSRC_SYSTEM) { - my_errno = errcode; - break; - } else if (libhttperr == NULL) { - /* save the first non-system error message */ - libhttperr = http_errorstr(errsrc, errcode); - } - } - switch (my_errno) { - case EINTR: - case ETIMEDOUT: - /* Timed out. Try, try again */ - ret = WEB_TIMEOUT; - break; - case ECONNREFUSED: - ret = WEB_CONNREFUSED; - break; - case EHOSTDOWN: - ret = WEB_HOSTDOWN; - break; - default: - /* some other fatal error */ - ret = WEB_NOCONNECT; - if (libhttperr == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_INIT_CONN), - ps->url.hport.hostname); - } else { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTP), libhttperr); - } - break; - } - } -cleanup: - return (ret); -} - -/* - * Name: write_ca_file - * Description: Writes out a PKCS12 file containing all trusted certs - * found in keystore recorded in static 'ps' structure - * - * This routine is used because the libwanboot library's - * HTTPS routines cannot accept trusted certificates - * through an in-memory structure, when initiating an - * SSL connection. They must be in a PKCS12, which is - * admittedly a poor interface. - * - * Arguments: err - where to record any errors. - * tmpdir - Directory to write certificate file in - * cacerts - Certs to write out - * passwd - password used to encrypt certs - * - * Returns : path to resulting file, if successfullly written, - * otherwise NULL. - */ -static char -*write_ca_file(PKG_ERR *err, char *tmpdir, STACK_OF(X509) *cacerts, - char *passwd) -{ - int fd, len; - FILE *fp; - PKCS12 *p12 = NULL; - char *ret = NULL; - static char tmp_file[PATH_MAX] = ""; - struct stat buf; - - if (!path_valid(tmpdir)) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); - goto cleanup; - } - - /* mkstemp replaces XXXXXX with a unique string */ - if (((len = snprintf(tmp_file, PATH_MAX, "%s/%sXXXXXX", tmpdir, - "cert")) < 0) || - (len >= PATH_MAX)) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); - goto cleanup; - } - - if ((fd = mkstemp(tmp_file)) == -1) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); - goto cleanup; - } - - if (fstat(fd, &buf) == -1) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); - goto cleanup; - } - - if (!S_ISREG(buf.st_mode)) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); - goto cleanup; - } - - if ((fp = fdopen(fd, "w")) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); - goto cleanup; - } - - if ((p12 = sunw_PKCS12_create(passwd, NULL, NULL, cacerts)) == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_KEYSTORE_FORM), tmp_file); - goto cleanup; - } - - if (i2d_PKCS12_fp(fp, p12) == 0) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_KEYSTORE_FORM), tmp_file); - goto cleanup; - } - - (void) fflush(fp); - (void) fclose(fp); - (void) close(fd); - fp = NULL; - fd = -1; - ret = tmp_file; - -cleanup: - if (p12 != NULL) - PKCS12_free(p12); - if (fp != NULL) - (void) fclose(fp); - if (fd != -1) { - (void) close(fd); - (void) unlink(tmp_file); - } - - return (ret); -} - -/* - * Name: web_send_request - * Description: Sends an HTTP request for a file to the - * web server being communicated with in the static - * 'ps' structure - * - * Arguments: err - where to record any errors. - * request_type - HTTP_REQ_TYPE_HEAD to send an HTTP HEAD request, - * or HTTP_REQ_TYPE_GET to send an HTTP GET request - * cp - - * Returns : WEB_OK - request sent successfully - * WEB_CONNREFUSED - Connection was refused to web site - * WEB_HOSTDOWN - Host was not responding to request - * WEB_NOCONNECT - Some other connection failure - */ -static WebStatus -web_send_request(PKG_ERR *err, int request_type, int cp, int ep) -{ - WebStatus ret = WEB_OK; - ulong_t errcode; - uint_t errsrc; - int my_errno = 0; - const char *libhttperr = NULL; - switch (request_type) { - case HTTP_REQ_TYPE_HEAD: - if ((http_head_request(ps->hps, ps->url.abspath)) != 0) { - while ((errcode = http_get_lasterr(ps->hps, - &errsrc)) != 0) { - /* Have an error - is it EINTR? */ - if (errsrc == ERRSRC_SYSTEM) { - my_errno = errcode; - break; - } else if (libhttperr == NULL) { - /* save first non-system error message */ - libhttperr = - http_errorstr(errsrc, errcode); - } - } - switch (my_errno) { - case EINTR: - case ETIMEDOUT: - /* Timed out. Try, try again */ - ret = WEB_TIMEOUT; - break; - case ECONNREFUSED: - ret = WEB_CONNREFUSED; - break; - case EHOSTDOWN: - ret = WEB_HOSTDOWN; - break; - default: - /* some other fatal error */ - ret = WEB_NOCONNECT; - if (libhttperr == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_INIT_CONN), - ps->url.hport.hostname); - } else { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTP), libhttperr); - } - break; - } - goto cleanup; - } - break; - - case HTTP_REQ_TYPE_GET: - if (cp && ep) { - if (http_get_range_request(ps->hps, ps->url.abspath, - cp, ep - cp) != 0) { - while ((errcode = http_get_lasterr(ps->hps, - &errsrc)) != 0) { - /* Have an error - is it EINTR? */ - if (errsrc == ERRSRC_SYSTEM) { - my_errno = errcode; - break; - } else { - /* - * save first non-system - * error message - */ - libhttperr = - http_errorstr(errsrc, - errcode); - } - } - switch (my_errno) { - case EINTR: - case ETIMEDOUT: - /* Timed out. Try, try again */ - ret = WEB_TIMEOUT; - break; - case ECONNREFUSED: - ret = WEB_CONNREFUSED; - break; - case EHOSTDOWN: - ret = WEB_HOSTDOWN; - break; - default: - /* some other fatal error */ - ret = WEB_NOCONNECT; - if (libhttperr == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_INIT_CONN), - ps->url.hport.hostname); - } else { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTP), - libhttperr); - } - break; - } - goto cleanup; - } - - if (!web_eval_headers(err)) { - ret = WEB_NOCONNECT; - goto cleanup; - } - } else { - if ((http_get_request(ps->hps, ps->url.abspath)) - != 0) { - while ((errcode = http_get_lasterr(ps->hps, - &errsrc)) != 0) { - /* Have an error - is it EINTR? */ - if (errsrc == ERRSRC_SYSTEM) { - my_errno = errcode; - break; - } else { - /* - * save the first non-system - * error message - */ - libhttperr = - http_errorstr(errsrc, - errcode); - } - } - switch (my_errno) { - case EINTR: - case ETIMEDOUT: - /* Timed out. Try, try again */ - ret = WEB_TIMEOUT; - break; - case ECONNREFUSED: - ret = WEB_CONNREFUSED; - break; - case EHOSTDOWN: - ret = WEB_HOSTDOWN; - break; - default: - /* some other fatal error */ - ret = WEB_NOCONNECT; - if (libhttperr == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_INIT_CONN), - ps->url.hport.hostname); - } else { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTP), - libhttperr); - } - break; - } - goto cleanup; - } - - if (!web_eval_headers(err)) { - ret = WEB_NOCONNECT; - goto cleanup; - } - } - break; - default: - pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), - __FILE__, __LINE__); - } - -cleanup: - return (ret); -} - -/* - * Name: web_eval_headers - * Description: Evaluates HTTP headers returned during an HTTP request. - * This must be called before calling - * http_get_header_value(). - * - * Arguments: err - where to record any errors. - * - * Returns : B_TRUE - success, B_FALSE otherwise - */ -static boolean_t -web_eval_headers(PKG_ERR *err) -{ - const char *http_err; - ulong_t herr; - uint_t errsrc; - - if (http_process_headers(ps->hps, &ps->resp) != 0) { - if ((ps->resp != NULL) && (ps->resp->statusmsg != NULL)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), - ps->resp->statusmsg); - } - - herr = http_get_lasterr(ps->hps, &errsrc); - http_err = http_errorstr(errsrc, herr); - pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), - http_err); - return (B_FALSE); - } - return (B_TRUE); -} - -/* - * Name: web_get_file - * Description: Downloads the file URL from the website, all of - * which are recorded in the static 'ps' struct - * - * Arguments: err - where to record any errors. - * dwnld_dir - Directory to download file into - * device - Where to store path to resulting - * file - * nointeract - if non-zero, do not output - * progress - * fname - name of downloaded file link in the dwnld_dir - * - * Returns : WEB_OK - download successful - * WEB_CONNREFUSED - Connection was refused to web site - * WEB_HOSTDOWN - Host was not responding to request - * WEB_GET_FAIL - Unable to initialize download - * state (temp file creation, header parsing, etc) - * WEB_NOCONNECT - Some other connection failure - */ -static WebStatus -web_get_file(PKG_ERR *err, char *dwnld_dir, int nointeract, char **fname) -{ - int i, fd; - int n = 0; - ulong_t abs_pos = 0; - char *head_val = NULL; - char *lastmod_val = NULL; - char *bname = NULL; - struct stat status; - WebStatus ret = WEB_OK; - WebStatus req_ret; - ulong_t errcode; - uint_t errsrc; - int my_errno = 0; - const char *libhttperr = NULL; - char *disp; - char tmp_file[PATH_MAX]; - int len; - - ps->data.prev_cont_length = - ps->data.content_length = - ps->data.cur_pos = 0; - - if ((head_val = http_get_header_value(ps->hps, - CONTENT_LENGTH_HDR)) != NULL) { - ps->data.content_length = atol(head_val); - } else { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_HEAD_VAL), - CONTENT_LENGTH_HDR); - ret = WEB_GET_FAIL; - goto cleanup; - } - - free(head_val); - head_val = NULL; - - if ((head_val = http_get_header_value(ps->hps, - CONTENT_DISPOSITION_HDR)) != NULL) { - /* "inline; parm=val; parm=val */ - if ((disp = strtok(head_val, "; \t\n\f\r")) != NULL) { - /* disp = "inline" */ - while ((disp = strtok(NULL, "; \t\n\f\r")) != NULL) { - /* disp = "parm=val" */ - if (ci_strneq(disp, "filename=", 9)) { - bname = xstrdup(basename(disp + 9)); - trim(bname); - dequote(bname); - } - } - } - free(head_val); - head_val = NULL; - } - - if (bname == NULL) { - /* - * couldn't determine filename from header value, - * so take basename of URL - */ - if ((bname = get_endof_string(ps->url.abspath, '/')) == NULL) { - /* URL is bad */ - pkgerr_add(err, PKGERR_PARSE, - gettext(ERR_PARSE_URL), ps->url.abspath); - ret = WEB_GET_FAIL; - goto cleanup; - } - } - - *fname = bname; - - if ((head_val = http_get_header_value(ps->hps, LAST_MODIFIED_HDR)) - != NULL) { - - if ((lastmod_val = condense_lastmodified(head_val)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_BAD_HEAD_VAL), - LAST_MODIFIED_HDR, head_val); - ret = WEB_GET_FAIL; - goto cleanup; - } - free(head_val); - head_val = NULL; - - if ((ps->uniqfile = get_unique_filename(dwnld_dir, - lastmod_val)) == NULL) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPEN_TMP)); - ret = WEB_GET_FAIL; - goto cleanup; - } - - free(lastmod_val); - lastmod_val = NULL; - - if ((fd = open(ps->uniqfile, - O_NONBLOCK|O_RDWR|O_APPEND|O_CREAT|O_EXCL, - 640)) == -1) { - - /* - * A partial downloaded file - * already exists, so open it. - */ - if ((fd = open(ps->uniqfile, - O_NONBLOCK|O_RDWR|O_APPEND)) != -1) { - if (fstat(fd, &status) == -1 || - !S_ISREG(status.st_mode)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_DWNLD_NO_CONT), - ps->uniqfile); - ret = WEB_GET_FAIL; - goto cleanup; - } else { - echo_out(nointeract, - gettext(MSG_DWNLD_PART), - ps->uniqfile, - status.st_size); - ps->data.prev_cont_length = - status.st_size; - } - } else { - /* unable to open partial file */ - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_DWNLD_NO_CONT), - ps->uniqfile); - ret = WEB_GET_FAIL; - goto cleanup; - } - } - } else { - /* - * no "Last-Modified" header, so this file is not eligible for - * spooling and "resuming last download" operations - */ - ps->spool = B_FALSE; - - /* mkstemp replaces XXXXXX with a unique string */ - if (((len = snprintf(tmp_file, PATH_MAX, - "%s/%sXXXXXX", dwnld_dir, "stream")) < 0) || - (len >= PATH_MAX)) { - pkgerr_add(err, PKGERR_WEB, - gettext(MSG_NOTEMP), dwnld_dir); - ret = WEB_GET_FAIL; - goto cleanup; - } - - if ((fd = mkstemp(tmp_file)) == -1) { - pkgerr_add(err, PKGERR_WEB, - gettext(MSG_NOTMPFIL), tmp_file); - ret = WEB_GET_FAIL; - goto cleanup; - } - - if (fstat(fd, &status) == -1 || - !S_ISREG(status.st_mode)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_DWNLD_NO_CONT), - ps->uniqfile); - ret = WEB_GET_FAIL; - goto cleanup; - } - - ps->data.prev_cont_length = 0; - ps->uniqfile = xstrdup(tmp_file); - } - - /* File has already been completely downloaded */ - if (ps->data.prev_cont_length == ps->data.content_length) { - echo_out(nointeract, gettext(MSG_DWNLD_PREV), ps->uniqfile); - ps->data.cur_pos = ps->data.prev_cont_length; - if (!make_link(dwnld_dir, bname)) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), - dwnld_dir); - ret = WEB_GET_FAIL; - goto cleanup; - } - /* we're done, so cleanup and return success */ - goto cleanup; - } else if (ps->data.prev_cont_length != 0) { - ps->data.cur_pos = ps->data.prev_cont_length; - } - - if (!ck_dwnld_dir_space(err, dwnld_dir, - (ps->data.prev_cont_length != 0) ? - (ps->data.content_length - ps->data.cur_pos) : - ps->data.content_length)) { - ret = WEB_GET_FAIL; - goto cleanup; - } - - if ((req_ret = web_send_request(err, HTTP_REQ_TYPE_GET, - ps->data.cur_pos, ps->data.content_length)) != WEB_OK) { - ret = req_ret; - goto cleanup; - } - - if (ps->data.prev_cont_length != 0) - echo_out(nointeract, gettext(MSG_DWNLD_CONT)); - else - echo_out(nointeract, gettext(MSG_DWNLD)); - - progress_setup(nointeract, ps->data.content_length); - - /* Download the file a BLOCK at a time */ - while (ps->data.cur_pos < ps->data.content_length) { - progress_report(nointeract, abs_pos); - i = ((ps->data.content_length - ps->data.cur_pos) < BLOCK) ? - (ps->data.content_length - ps->data.cur_pos) - : BLOCK; - if ((n = http_read_body(ps->hps, ps->content, i)) <= 0) { - while ((errcode = http_get_lasterr(ps->hps, - &errsrc)) != 0) { - /* Have an error - is it EINTR? */ - if (errsrc == ERRSRC_SYSTEM) { - my_errno = errcode; - break; - } else { - /* - * save first non-system - * error message - */ - libhttperr = - http_errorstr(errsrc, errcode); - } - } - switch (my_errno) { - case EINTR: - case ETIMEDOUT: - /* Timed out. Try, try again */ - ret = WEB_TIMEOUT; - break; - case ECONNREFUSED: - ret = WEB_CONNREFUSED; - break; - case EHOSTDOWN: - ret = WEB_HOSTDOWN; - break; - default: - /* some other fatal error */ - ret = WEB_NOCONNECT; - if (libhttperr == NULL) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_INIT_CONN), - ps->url.hport.hostname); - } else { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_HTTP), libhttperr); - } - break; - } - goto cleanup; - } - if ((n = write(fd, ps->content, n)) == 0) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_WRITE), - ps->uniqfile, strerror(errno)); - ret = WEB_GET_FAIL; - goto cleanup; - } - ps->data.cur_pos += n; - abs_pos += n; - } - - progress_finish(nointeract); - echo_out(nointeract, gettext(MSG_DWNLD_COMPLETE)); - - if (!make_link(dwnld_dir, bname)) { - pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), - dwnld_dir); - ret = WEB_GET_FAIL; - goto cleanup; - } - -cleanup: - sync(); - if (fd != -1) { - (void) close(fd); - } - - if (head_val != NULL) - free(head_val); - - if (lastmod_val != NULL) - free(lastmod_val); - - return (ret); -} - -/* - * Name: make_link - * Description: Create new link to file being downloaded - * - * Arguments: dwnld_dir - directory in which downloaded file exists - * bname - name of link - * - * Returns : B_TRUE - success, B_FALSE otherwise - */ -static boolean_t -make_link(char *dwnld_dir, char *bname) -{ - int len; - - if ((ps->link = (char *)xmalloc(PATH_MAX)) == NULL) - return (B_FALSE); - if (((len = snprintf(ps->link, PATH_MAX, "%s/%s", - dwnld_dir, bname)) < 0) || - len >= PATH_MAX) - return (B_FALSE); - - (void) link(ps->uniqfile, ps->link); - - return (B_TRUE); -} - -/* - * Name: get_startof_string - * Description: searches string for token, returns a newly-allocated - * substring of the given string up to, but not - * including, token. for example - * get_startof_string("abcd", 'c') will return "ab" - * - * Arguments: path - path to split - * token - character to split on - * - * Returns : substring of 'path', up to, but not including, - * token, if token appears in path. Otherwise, - * returns NULL. - */ -char * -get_startof_string(char *path, char token) -{ - char *p, *p2; - - if (path == NULL) - return (NULL); - - p = xstrdup(path); - - p2 = strchr(p, token); - if (p2 == NULL) { - free(p); - return (NULL); - } else { - *p2 = '\0'; - return (p); - } -} - -/* - * Name: get_endof_string - * Description: searches string for token, returns a - * newly-allocated substring of the given string, - * starting at character following token, to end of - * string. - * - * for example get_end_string("abcd", 'c') - * will return "d" - * - * Arguments: path - path to split - * token - character to split on - * - * Returns : substring of 'path', beginning at character - * following token, to end of string, if - * token appears in path. Otherwise, - * returns NULL. - */ -char * -get_endof_string(char *path, char token) -{ - char *p, *p2; - - if (path == NULL) - return (NULL); - - p = xstrdup(path); - - if ((p2 = strrchr(p, token)) == NULL) { - return (NULL); - } - - return (p2 + 1); -} - -/* - * Name: progress_setup - * Description: Initialize session for reporting progress - * - * Arguments: nointeract - if non-zero, do not do anything - * ulong_t - size of job to report progress for - * - * Returns : none - */ -static void -progress_setup(int nointeract, ulong_t size_of_load) -{ - ulong_t divisor; - ulong_t term_width = TERM_WIDTH; - - if (nointeract) - return; - - if (size_of_load > MED_DWNLD && size_of_load < LARGE_DWNLD) - divisor = MED_DIVISOR; - else if (size_of_load > LARGE_DWNLD) { - term_width = TERM_WIDTH - 8; - divisor = LARGE_DIVISOR; - } else - divisor = SMALL_DIVISOR; - - const_increment = size_of_load / term_width; - const_divider = size_of_load / divisor; - const_completed = 100 / divisor; -} - -/* - * Name: progress_report - * Description: Report progress for current progress context, - * to stderr - * - * Arguments: nointeract - if non-zero, do not do anything - * position - how far along in the job to report. - * This should be <= size used during progress_setup - * - * Returns : none - */ -static void -progress_report(int nointeract, ulong_t position) -{ - static ulong_t increment; - static ulong_t divider; - - if (nointeract) - return; - - if (position == 0) { - increment = const_increment; - divider = const_divider; - } - if (position > increment && position < divider) { - (void) putc('.', stderr); - increment += const_increment; - } else if (position > divider) { - completed += const_completed; - (void) fprintf(stderr, "%ld%c", completed, '%'); - increment += const_increment; - divider += const_divider; - } -} - -/* - * Name: progress_finish - * Description: Finalize session for reporting progress. - * "100%" is reported to screen - * - * Arguments: nointeract - if non-zero, do not do anything - * - * Returns : none - */ -static void -progress_finish(int nointeract) -{ - if (nointeract) - return; - - (void) fprintf(stderr, "%d%c\n", 100, '%'); -} - -/* - * Name: init_session - * Description: Initializes static 'ps' structure with default - * values - * - * Arguments: none - * - * Returns : B_TRUE - success, B_FALSE otherwise - */ -static boolean_t -init_session(void) -{ - if ((ps = (WEB_SESSION *) - xmalloc(sizeof (WEB_SESSION))) == NULL) { - return (B_FALSE); - } - (void) memset(ps, 0, sizeof (*ps)); - - if ((ps->content = (char *)xmalloc(BLOCK)) == NULL) { - return (B_FALSE); - } - - (void) memset(ps->content, 0, BLOCK); - - ps->data.cur_pos = 0UL; - ps->data.content_length = 0UL; - ps->url.https = B_FALSE; - ps->uniqfile = NULL; - ps->link = NULL; - ps->dwnld_dir = NULL; - ps->spool = B_TRUE; - ps->errstr = NULL; - ps->keystore = NULL; - - return (B_TRUE); -} - -/* - * Name: ck_downld_dir_space - * Description: Verify enough space exists in directory to hold file - * - * Arguments: err - where to record any errors. - * dwnld_dir - Directory to check available space in - * bytes_needed - How many bytes are need - * - * Returns : B_TRUE - enough space exists in dwnld_dir to hold - * bytes_needed bytes, otherwise B_FALSE - */ -static boolean_t -ck_dwnld_dir_space(PKG_ERR *err, char *dwnld_dir, ulong_t bytes_needed) -{ - u_longlong_t bytes_avail; - u_longlong_t block_pad; - struct statvfs64 status; - - if (statvfs64(dwnld_dir, &status)) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_TMPDIR), dwnld_dir); - return (B_FALSE); - } - - block_pad = (status.f_frsize ? status.f_frsize : status.f_bsize); - bytes_avail = status.f_bavail * block_pad; - - if ((((u_longlong_t)bytes_needed) + block_pad) > bytes_avail) { - pkgerr_add(err, PKGERR_WEB, gettext(ERR_DISK_SPACE), - dwnld_dir, - (((u_longlong_t)bytes_needed) + block_pad) / 1024ULL, - bytes_avail / 1024ULL); - return (B_FALSE); - } - - return (B_TRUE); -} - -/* - * Description: - * This function returns a unique file name based on the parts of the - * URI. This is done to enable partially downloaded files to be resumed. - * Arguments: - * dir - The directory that should contain the filename. - * last_modified - A string representing the date of last modification, - * used as part of generating unique name - * Returns: - * A valid filename or NULL. - */ - -static char * -get_unique_filename(char *dir, char *last_modified) -{ - char *buf, *buf2, *beg_str; - int len; - - if ((buf = (char *)xmalloc(PATH_MAX)) == NULL) { - return (NULL); - } - if ((buf2 = (char *)xmalloc(PATH_MAX)) == NULL) { - return (NULL); - } - - /* prepare strings for being cat'ed onto */ - buf[0] = buf2[0] = '\0'; - /* - * No validation of the path is done here. We just construct the path - * and it must be validated later - */ - - if (dir) { - if (((len = snprintf(buf2, PATH_MAX, "%s/", dir)) < 0) || - (len >= PATH_MAX)) - return (NULL); - } else { - return (NULL); - } - - if (ps->url.abspath) - if (strlcat(buf, ps->url.abspath, PATH_MAX) >= PATH_MAX) - return (NULL); - if (ps->url.hport.hostname) - if (isdigit((int)ps->url.hport.hostname[0])) { - if (strlcat(buf, ps->url.hport.hostname, PATH_MAX) - >= PATH_MAX) - return (NULL); - } else { - if ((beg_str = - get_startof_string(ps->url.hport.hostname, '.')) - != NULL) - if (strlcat(buf, beg_str, PATH_MAX) >= PATH_MAX) - return (NULL); - } - if (last_modified != NULL) - if (strlcat(buf, last_modified, PATH_MAX) >= PATH_MAX) - return (NULL); - - if ((buf = replace_token(buf, '/', '_')) != NULL) { - if (strlcat(buf2, buf, PATH_MAX) >= PATH_MAX) { - return (NULL); - } else { - if (buf) free(buf); - return (buf2); - } - } else { - if (buf) free(buf); - if (buf2) free(buf2); - return (NULL); - } -} - -/* - * Description: - * Removes token(s) consisting of one character from any path. - * Arguments: - * path - The path to search for the token in. - * token - The token to search for - * Returns: - * The path with all tokens removed or NULL. - */ -static char * -replace_token(char *path, char oldtoken, char newtoken) -{ - char *newpath, *p; - - if ((path == NULL) || (oldtoken == '\0') || (newtoken == '\0')) { - return (NULL); - } - - newpath = xstrdup(path); - - for (p = newpath; *p != '\0'; p++) { - if (*p == oldtoken) { - *p = newtoken; - } - } - - return (newpath); -} - -/* - * Name: trim - * Description: Trims whitespace from a string - * has been registered) - * Scope: private - * Arguments: string - string to trim. It is assumed - * this string is writable up to it's entire - * length. - * Returns: none - */ -static void -trim(char *str) -{ - int len, i; - if (str == NULL) { - return; - } - - len = strlen(str); - /* strip from front */ - while (isspace(*str)) { - for (i = 0; i < len; i++) { - str[i] = str[i+1]; - } - } - - /* strip from back */ - len = strlen(str); - while (isspace(str[len-1])) { - len--; - } - str[len] = '\0'; -} - -/* - * Description: - * Resolves double quotes - * Arguments: - * str - The string to resolve - * Returns: - * None - */ -static void -dequote(char *str) -{ - char *cp; - - if ((str == NULL) || (str[0] != '"')) { - /* no quotes */ - return; - } - - /* remove first quote */ - (void) memmove(str, str + 1, strlen(str) - 1); - - /* - * scan string looking for ending quote. - * escaped quotes like \" don't count - */ - cp = str; - - while (*cp != '\0') { - switch (*cp) { - case '\\': - /* found an escaped character */ - /* make sure end of string is not '\' */ - if (*++cp != '\0') { - cp++; - } - break; - - case '"': - *cp = '\0'; - break; - default: - cp++; - } - } -} - -/* - * Name: get_ENV_proxy - * Description: Retrieves setting of proxy env variable - * - * Arguments: err - where to record any errors. - * proxy - where to store proxy - * - * Returns : B_TRUE - http proxy was found and valid, stored in proxy - * B_FALSE - error, errors recorded in err - */ -static boolean_t -get_ENV_proxy(PKG_ERR *err, char **proxy) -{ - char *buf; - - if ((buf = getenv("HTTPPROXY")) != NULL) { - if (!path_valid(buf)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_ILL_ENV), "HTTPPROXY", buf); - return (B_FALSE); - } else { - *proxy = buf; - return (B_TRUE); - } - } else { - /* try the other env variable */ - if ((buf = getenv("http_proxy")) != NULL) { - if (!path_valid(buf)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_ILL_ENV), "http_proxy", buf); - return (B_FALSE); - } - if (!strneq(buf, "http://", 7)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_ILL_ENV), "http_proxy", buf); - return (B_FALSE); - } - - /* skip over the http:// part of the proxy "url" */ - *proxy = buf + 7; - return (B_TRUE); - } - } - - /* either the env variable(s) were set and valid, or not set */ - return (B_TRUE); -} - -/* - * Name: get_ENV_proxyport - * Description: Retrieves setting of PROXYPORT env variable - * - * Arguments: err - where to record any errors. - * port - where to store resulting port - * - * Returns : B_TRUE - string found in PROXYPORT variable, converted - * to decimal integer, if it exists - * and is valid. Or, PROXYPORT not set, port set to 1. - * B_FALSE - env variable set, but invalid - * (not a number for example) - */ -static boolean_t -get_ENV_proxyport(PKG_ERR *err, ushort_t *port) -{ - char *buf; - ushort_t newport; - buf = getenv("HTTPPROXYPORT"); - if (buf != NULL) { - if (!path_valid(buf)) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); - return (B_FALSE); - } - if ((newport = atoi(buf)) == 0) { - pkgerr_add(err, PKGERR_WEB, - gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); - return (B_FALSE); - } - *port = newport; - return (B_TRUE); - } else { - *port = 1; - return (B_TRUE); - } -} - -/* - * Name: remove_dwnld_file - * Description: Removes newly-downloaded file if completely downloaded. - * - * Arguments: path - path to file to remove - * - * Returns : B_TRUE - success, B_FALSE otherwise - * if it's '0' (not OK) we simply return it, since the - * verification operation has already determined that the - * cert is invalid. if 'ok' is non-zero, then we do our - * checks, and return 0 or 1 based on if the cert is - * invalid or valid. - */ -static boolean_t -remove_dwnld_file(char *path) -{ - if (path && path != NULL) { - /* - * Only remove the downloaded file if it has been completely - * downloaded, or is not eligible for spooling - */ - if ((!ps->spool) || - (ps->data.cur_pos >= ps->data.content_length)) { - (void) unlink(path); - } - } else { - return (B_FALSE); - } - return (B_TRUE); -} - -/* - * Name: condense_lastmodifided - * Description: generates a substring of a last-modified string, - * and removes colons. - * - * Arguments: last_modified - string of the form - * "Wed, 23 Oct 2002 21:59:45 GMT" - * - * Returns : - * new string, consisting of hours/minutes/seconds only, - * sans any colons. - */ -char * -condense_lastmodified(char *last_modified) -{ - char *p, *p2; - - /* - * Last-Modified: Wed, 23 Oct 2002 21:59:45 GMT - * Strip the hours, minutes and seconds, without the ':'s, from - * the above string, void of the ':". - */ - - if (last_modified == NULL) - return (NULL); - - if ((p = xstrdup(last_modified)) == NULL) - return (NULL); - p2 = (strstr(p, ":") - 2); - p2[8] = '\0'; - return (replace_token(p2, ':', '_')); -} - -/* - * Name: backoff - * Description: sleeps for a certain # of seconds after a network - * failure. - * Scope: public - * Arguments: none - * Returns: none - */ -void -backoff() -{ - static boolean_t initted = B_FALSE; - int backoff; - long seed; - - if (!initted) { - /* seed the rng */ - (void) _get_random_info(&seed, sizeof (seed)); - srand48(seed); - initted = B_TRUE; - } - - backoff = (int)(drand48() * (double)cur_backoff); - (void) sleep(backoff); - if (cur_backoff < MAX_BACKOFF) { - /* - * increase maximum time we might wait - * next time so as to fall off over - * time. - */ - cur_backoff *= BACKOFF_FACTOR; - } -} - -/* - * Name: reset_backoff - * Description: notifies the backoff service that whatever was - * being backoff succeeded. - * Scope: public - * Arguments: none - * Returns: none - */ -void -reset_backoff() -{ - cur_backoff = MIN_BACKOFF; -} - -/* - * Name: _get_random_info - * Description: generate an amount of random bits. Currently - * only a small amount (a long long) can be - * generated at one time. - * Scope: private - * Arguments: buf - [RO, *RW] (char *) - * Buffer to copy bits into - * size - amount to copy - * Returns: B_TRUE on success, B_FALSE otherwise. The buffer is filled - * with the amount of bytes of random data specified. - */ -static boolean_t -_get_random_info(void *buf, int size) -{ - struct timeval tv; - typedef struct { - long low_time; - long hostid; - } randomness; - randomness r; - - /* if the RANDOM file exists, use it */ - if (access(RANDOM, R_OK) == 0) { - if ((RAND_load_file(RANDOM, 1024 * 1024)) > 0) { - if (RAND_bytes((uchar_t *)buf, size) == 1) { - /* success */ - return (B_TRUE); - } - } - } - - /* couldn't use RANDOM file, so fallback to time of day and hostid */ - (void) gettimeofday(&tv, (struct timezone *)0); - - /* Wouldn't it be nice if we could hash these */ - r.low_time = tv.tv_usec; - r.hostid = gethostid(); - - if (sizeof (r) < size) { - /* - * Can't copy correctly - */ - return (B_FALSE); - } - (void) memcpy(buf, &r, size); - return (B_TRUE); -} - -/* - * Name: pkg_passphrase_cb - * Description: Default callback that applications can use when - * a passphrase is needed. This routine collects - * a passphrase from the user using the given - * passphrase retrieval method set with - * set_passphrase_passarg(). If the method - * indicates an interactive prompt, then the - * prompt set with set_passphrase_prompt() - * is displayed. - * - * Arguments: buf - Buffer to copy passphrase into - * size - Max amount to copy to buf - * rw - Whether this passphrase is needed - * to read something off disk, or - * write something to disk. Applications - * typically want to ask twice when getting - * a passphrase for writing something. - * data - application-specific data. In this - * callback, data is a pointer to - * a keystore_passphrase_data structure. - * - * Returns: Length of passphrase collected, or -1 on error. - * Errors recorded in 'err' object in the *data. - */ -int -pkg_passphrase_cb(char *buf, int size, int rw, void *data) -{ - BIO *pwdbio = NULL; - char passphrase_copy[MAX_PHRASELEN + 1]; - PKG_ERR *err; - int passlen; - char *ws; - char prompt_copy[MAX_VERIFY_MSGLEN]; - char *passphrase; - char *arg; - - err = ((keystore_passphrase_data *)data)->err; - - if (passarg == NULL) { - arg = "console"; - } else { - arg = passarg; - } - - /* default method of collecting password is by prompting */ - if (ci_streq(arg, "console")) { - if ((passphrase = getpassphrase(prompt)) == NULL) { - pkgerr_add(err, PKGERR_BADPASS, - gettext(MSG_NOPASS), arg); - return (-1); - } - - if (rw) { - /* - * if the password is being supplied for - * writing something to disk, verify it first - */ - - /* make a copy (getpassphrase overwrites) */ - (void) strlcpy(passphrase_copy, passphrase, - MAX_PHRASELEN + 1); - - if (((passlen = snprintf(prompt_copy, - MAX_VERIFY_MSGLEN, "%s: %s", - gettext(MSG_PASSWD_AGAIN), - prompt)) < 0) || - (passlen >= (MAX_PHRASELEN + 1))) { - pkgerr_add(err, PKGERR_BADPASS, - gettext(MSG_NOPASS), arg); - return (-1); - } - - if ((passphrase = - getpassphrase(prompt_copy)) == NULL) { - pkgerr_add(err, PKGERR_BADPASS, - gettext(MSG_NOPASS), arg); - return (-1); - } - - if (!streq(passphrase_copy, passphrase)) { - pkgerr_add(err, PKGERR_READ, - gettext(MSG_PASSWD_NOMATCH)); - return (-1); - } - } - } else if (ci_strneq(arg, "pass:", 5)) { - passphrase = arg + 5; - } else if (ci_strneq(arg, "env:", 4)) { - passphrase = getenv(arg + 4); - } else if (ci_strneq(arg, "file:", 5)) { - - /* open file for reading */ - if ((pwdbio = BIO_new_file(arg + 5, "r")) == NULL) { - pkgerr_add(err, PKGERR_EXIST, - gettext(MSG_PASSWD_FILE), arg + 5); - return (-1); - } - - /* read first line */ - if (((passlen = BIO_gets(pwdbio, buf, size)) < 1) || - (passlen > size)) { - pkgerr_add(err, PKGERR_READ, gettext(MSG_PASSWD_FILE), - arg + 5); - return (-1); - } - BIO_free_all(pwdbio); - pwdbio = NULL; - - if (passlen == size) { - /* - * password was maximum length, so there is - * no null terminator. null-terminate it - */ - buf[size - 1] = '\0'; - } - - /* first newline found is end of passwd, so nuke it */ - if ((ws = strchr(buf, '\n')) != NULL) { - *ws = '\0'; - } - return (strlen(buf)); - } else { - /* unrecognized passphrase */ - pkgerr_add(err, PKGERR_BADPASS, - gettext(MSG_BADPASSARG), arg); - return (-1); - } - - if (passphrase == NULL) { - /* unable to collect passwd from given source */ - pkgerr_add(err, PKGERR_BADPASS, - gettext(MSG_NOPASS), arg); - return (-1); - } - - (void) strlcpy(buf, passphrase, size); - return (strlen(buf)); -} |