/*
* 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));
}