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