1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 30 31 #include <stdio.h> 32 #include <limits.h> 33 #include <stdlib.h> 34 #include <unistd.h> 35 #include <string.h> 36 #include <pkglocs.h> 37 #include <locale.h> 38 #include <libintl.h> 39 #include <libgen.h> 40 #include <signal.h> 41 #include <sys/stat.h> 42 #include <sys/statvfs.h> 43 #include <sys/types.h> 44 #include <fcntl.h> 45 #include <dirent.h> 46 #include <boot_http.h> 47 #include <errno.h> 48 #include <ctype.h> 49 #include <openssl/pkcs7.h> 50 #include <openssl/ocsp.h> 51 #include <openssl/pkcs12.h> 52 #include <openssl/err.h> 53 #include <openssl/x509.h> 54 #include <openssl/pem.h> 55 #include <openssl/evp.h> 56 #include <openssl/rand.h> 57 #include <openssl/x509v3.h> 58 #include "pkglib.h" 59 #include "pkglibmsgs.h" 60 #include "pkglocale.h" 61 #include "keystore.h" 62 #include "pkgweb.h" 63 #include "pkgerr.h" 64 #include "p12lib.h" 65 66 /* fixed format when making an OCSP request */ 67 #define OCSP_REQUEST_FORMAT \ 68 "POST %s HTTP/1.0\r\n" \ 69 "Content-Type: application/ocsp-request\r\n" \ 70 "Content-Length: %d\r\n\r\n" 71 72 /* 73 * no security is afforded by using this phrase to "encrypt" CA certificates, 74 * but it might aid in debugging and has to be non-null 75 */ 76 #define WEB_CA_PHRASE "schizophrenic" 77 78 /* This one needs the ': ' at the end */ 79 #define CONTENT_TYPE_HDR "Content-Type" 80 #define CONTENT_DISPOSITION_HDR "Content-Disposition" 81 #define CONTENT_OCSP_RESP "application/ocsp-response" 82 #define CONTENT_LENGTH_HDR "Content-Length" 83 #define LAST_MODIFIED_HDR "Last-Modified" 84 #define OCSP_BUFSIZ 1024 85 86 /* 87 * default amount of time that is allowed for error when checking 88 * OCSP response validity. 89 * For example, if this is set to 5 minutes, then if a response 90 * is issued that is valid from 12:00 to 1:00, then we will 91 * accept it if the local time is between 11:55 and 1:05. 92 * This takes care of not-quite-synchronized server and client clocks. 93 */ 94 #define OCSP_VALIDITY_PERIOD (5 * 60) 95 96 /* this value is defined by getpassphrase(3c) manpage */ 97 #define MAX_PHRASELEN 257 98 99 /* Max length of "enter password again" prompt message */ 100 #define MAX_VERIFY_MSGLEN 1024 101 102 /* local prototypes */ 103 static boolean_t remove_dwnld_file(char *); 104 static boolean_t get_ENV_proxyport(PKG_ERR *, ushort_t *); 105 static boolean_t make_link(char *, char *); 106 static WebStatus web_send_request(PKG_ERR *, int, int, int); 107 static boolean_t web_eval_headers(PKG_ERR *); 108 static WebStatus web_get_file(PKG_ERR *, char *, int, char **); 109 static boolean_t ck_dwnld_dir_space(PKG_ERR *, char *, ulong_t); 110 static WebStatus web_connect(PKG_ERR *); 111 static boolean_t web_setup(PKG_ERR *); 112 static boolean_t check_dwnld_dir(PKG_ERR *, char *); 113 static boolean_t parse_url_proxy(PKG_ERR *, char *, char *, ushort_t); 114 static boolean_t web_disconnect(void); 115 static char *get_unique_filename(char *, char *); 116 static boolean_t get_ENV_proxy(PKG_ERR *, char **); 117 static char *condense_lastmodified(char *); 118 static int web_verify(int, X509_STORE_CTX *); 119 static int get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); 120 static boolean_t get_ocsp_uri(X509 *, char **); 121 static OCSPStatus ocsp_verify(PKG_ERR *, X509 *, X509 *, char *, url_hport_t *, 122 STACK_OF(X509) *); 123 static char *get_time_string(ASN1_GENERALIZEDTIME *); 124 static char *write_ca_file(PKG_ERR *, char *, STACK_OF(X509) *, char *); 125 static boolean_t _get_random_info(void *, int); 126 static boolean_t init_session(void); 127 static void progress_setup(int, ulong_t); 128 static void progress_report(int, ulong_t); 129 static void progress_finish(int); 130 static char *replace_token(char *, char, char); 131 static void dequote(char *); 132 static void trim(char *); 133 134 135 /* 136 * structure used to hold data passed back to the 137 * X509 verify callback routine in validate_signature() 138 */ 139 typedef struct { 140 url_hport_t *proxy; 141 PKG_ERR *err; 142 STACK_OF(X509) *cas; 143 } verify_cb_data_t; 144 145 /* Progress bar variables */ 146 static ulong_t const_increment, const_divider, completed, const_completed; 147 148 /* current network backoff wait period */ 149 static int cur_backoff = 0; 150 151 /* download session context handle */ 152 static WEB_SESSION *ps; 153 154 static int webpkg_install = 0; 155 static char *prompt = NULL; 156 static char *passarg = NULL; 157 158 159 /* ~~~~~~~~~~~~~~ Public Functions ~~~~~~~~~~~~~~~~~~~ */ 160 161 /* 162 * Name: set_prompt 163 * Description: Specifies the prompt to use with the pkglib 164 * passphrase callback routine. 165 * 166 * Arguments: newprompt - The prompt to display 167 * 168 * Returns : NONE 169 */ 170 void 171 set_passphrase_prompt(char *newprompt) 172 { 173 prompt = newprompt; 174 } 175 176 /* 177 * Name: set_passarg 178 * Description: Specifies the passphrase retrieval method 179 * to use with the pkglib 180 * passphrase callback routine. 181 * 182 * Arguments: newpassarg - The new password retrieval arg 183 * 184 * Returns : NONE 185 */ 186 void 187 set_passphrase_passarg(char *newpassarg) 188 { 189 passarg = newpassarg; 190 } 191 192 /* 193 * Name: get_proxy_port 194 * Description: Resolves proxy specification 195 * 196 * Arguments: err - where to record any errors. 197 * proxy - Location to store result - if *proxy is not 198 * null, then it will be validated, but not changed 199 * 200 * Returns : B_TRUE - success, B_FALSE otherwise 201 * on success, *proxy and *port are set to either 202 * the user-supplied proxy and port, or the 203 * ones found in the environment variables 204 * HTTPPROXY and/or HTTPROXYPORT 205 */ 206 boolean_t 207 get_proxy_port(PKG_ERR *err, char **proxy, ushort_t *port) 208 { 209 if (*proxy != NULL) { 210 if (!path_valid(*proxy)) { 211 /* bad proxy supplied */ 212 pkgerr_add(err, PKGERR_WEB, 213 gettext(ERR_BAD_PROXY), *proxy); 214 return (B_FALSE); 215 } 216 if (!get_ENV_proxyport(err, port)) { 217 /* env set, but bad */ 218 return (B_FALSE); 219 } 220 } else { 221 if (!get_ENV_proxy(err, proxy)) { 222 /* environment variable set, but bad */ 223 return (B_FALSE); 224 } 225 if ((*proxy != NULL) && !path_valid(*proxy)) { 226 /* env variable set, but bad */ 227 pkgerr_add(err, PKGERR_WEB, 228 gettext(ERR_BAD_PROXY), *proxy); 229 return (B_FALSE); 230 } 231 if (!get_ENV_proxyport(err, port)) { 232 /* env variable set, but bad */ 233 return (B_FALSE); 234 } 235 } 236 return (B_TRUE); 237 } 238 239 /* 240 * Name: path_valid 241 * Description: Checks a string for being a valid path 242 * 243 * Arguments: path - path to validate 244 * 245 * Returns : B_TRUE - success, B_FALSE otherwise. 246 * B_FALSE means path was null, too long (>PATH_MAX), 247 * or too short (<1) 248 */ 249 boolean_t 250 path_valid(char *path) 251 { 252 if (path == NULL) { 253 return (B_FALSE); 254 } else if (strlen(path) > PATH_MAX) { 255 return (B_FALSE); 256 } else if (strlen(path) >= 1) { 257 return (B_TRUE); 258 } else { 259 /* path < 1 */ 260 return (B_FALSE); 261 } 262 } 263 264 /* 265 * Name: web_cleanup 266 * Description: Deletes temp files, closes, frees memory taken 267 * by 'ps' static structure 268 * 269 * Arguments: none 270 * 271 * Returns : none 272 */ 273 void 274 web_cleanup(void) 275 { 276 PKG_ERR *err; 277 278 if (ps == NULL) 279 return; 280 281 err = pkgerr_new(); 282 283 if (ps->keystore) { 284 (void) close_keystore(err, ps->keystore, NULL); 285 } 286 287 ps->keystore = NULL; 288 289 pkgerr_free(err); 290 291 if (ps->uniqfile) { 292 (void) remove_dwnld_file(ps->uniqfile); 293 free(ps->uniqfile); 294 ps->uniqfile = NULL; 295 } 296 if (ps->link) { 297 (void) remove_dwnld_file(ps->link); 298 free(ps->link); 299 ps->link = NULL; 300 } 301 if (ps->dwnld_dir) { 302 (void) rmdir(ps->dwnld_dir); 303 ps->dwnld_dir = NULL; 304 } 305 if (ps->errstr) { 306 free(ps->errstr); 307 ps->errstr = NULL; 308 } 309 310 if (ps->content) { 311 free(ps->content); 312 ps->content = NULL; 313 } 314 315 if (ps->resp) { 316 http_free_respinfo(ps->resp); 317 ps->resp = NULL; 318 } 319 320 if (ps) { 321 free(ps); 322 ps = NULL; 323 } 324 } 325 326 /* 327 * Name: web_session_control 328 * Description: Downloads an arbitrary URL and saves to disk. 329 * 330 * Arguments: err - where to record any errors. 331 * url - URL pointing to content to download - can be 332 * http:// or https:// 333 * dwnld_dir - Directory to download into 334 * keystore - keystore to use for accessing trusted 335 * certs when downloading using SSL 336 * proxy - HTTP proxy to use, or NULL for no proxy 337 * proxy_port - HTTP proxy port to use, ignored 338 * if proxy is NULL 339 * passarg - method to retrieve password 340 * retries - # of times to retry download before 341 * giving up 342 * timeout - how long to wait before retrying, 343 * when download is interrupted 344 * nointeract - if non-zero, do not output 345 * download progress to screen 346 * 347 * Returns : B_TRUE - success, B_FALSE otherwise 348 */ 349 boolean_t 350 web_session_control(PKG_ERR *err, char *url, char *dwnld_dir, 351 keystore_handle_t keystore, char *proxy, ushort_t proxy_port, 352 int retries, int timeout, int nointeract, char **fname) 353 { 354 int i; 355 boolean_t ret = B_TRUE; 356 boolean_t retrieved = B_FALSE; 357 358 if (!init_session()) { 359 ret = B_FALSE; 360 goto cleanup; 361 } 362 363 if (!parse_url_proxy(err, url, proxy, proxy_port)) { 364 ret = B_FALSE; 365 goto cleanup; 366 } 367 368 ps->timeout = timeout; 369 370 if (keystore != NULL) 371 ps->keystore = keystore; 372 373 if (dwnld_dir != NULL) 374 ps->dwnld_dir = xstrdup(dwnld_dir); 375 else { 376 pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_DWNLD_DIR)); 377 ret = B_FALSE; 378 goto cleanup; 379 } 380 381 if (!check_dwnld_dir(err, dwnld_dir)) { 382 ret = B_FALSE; 383 goto cleanup; 384 } 385 386 for (i = 0; i < retries && !retrieved; i++) { 387 if (!web_setup(err)) { 388 ret = B_FALSE; 389 goto cleanup; 390 } 391 392 switch (web_connect(err)) { 393 /* time out and wait a little bit for these failures */ 394 case WEB_OK: 395 /* were able to connect */ 396 reset_backoff(); 397 break; 398 case WEB_TIMEOUT: 399 echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); 400 (void) web_disconnect(); 401 backoff(); 402 continue; 403 404 case WEB_CONNREFUSED: 405 echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), 406 ps->url.hport.hostname); 407 (void) web_disconnect(); 408 backoff(); 409 continue; 410 case WEB_HOSTDOWN: 411 echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), 412 ps->url.hport.hostname); 413 (void) web_disconnect(); 414 backoff(); 415 continue; 416 417 default: 418 /* every other failure is a hard failure, so bail */ 419 ret = B_FALSE; 420 goto cleanup; 421 } 422 423 switch (web_send_request(err, HTTP_REQ_TYPE_HEAD, 424 ps->data.cur_pos, ps->data.content_length)) { 425 case WEB_OK: 426 /* were able to connect */ 427 reset_backoff(); 428 break; 429 case WEB_TIMEOUT: 430 echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); 431 (void) web_disconnect(); 432 backoff(); 433 continue; 434 435 case WEB_CONNREFUSED: 436 echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), 437 ps->url.hport.hostname); 438 (void) web_disconnect(); 439 backoff(); 440 continue; 441 case WEB_HOSTDOWN: 442 echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), 443 ps->url.hport.hostname); 444 (void) web_disconnect(); 445 backoff(); 446 continue; 447 default: 448 /* every other case is failure, so bail */ 449 ret = B_FALSE; 450 goto cleanup; 451 } 452 453 if (!web_eval_headers(err)) { 454 ret = B_FALSE; 455 goto cleanup; 456 } 457 458 switch (web_get_file(err, dwnld_dir, nointeract, fname)) { 459 case WEB_OK: 460 /* were able to retrieve file */ 461 retrieved = B_TRUE; 462 reset_backoff(); 463 break; 464 465 case WEB_TIMEOUT: 466 echo_out(nointeract, gettext(MSG_DWNLD_TIMEOUT)); 467 (void) web_disconnect(); 468 backoff(); 469 continue; 470 471 case WEB_CONNREFUSED: 472 echo_out(nointeract, gettext(MSG_DWNLD_CONNREF), 473 ps->url.hport.hostname); 474 (void) web_disconnect(); 475 backoff(); 476 continue; 477 case WEB_HOSTDOWN: 478 echo_out(nointeract, gettext(MSG_DWNLD_HOSTDWN), 479 ps->url.hport.hostname); 480 (void) web_disconnect(); 481 backoff(); 482 continue; 483 default: 484 /* every other failure is a hard failure, so bail */ 485 ret = B_FALSE; 486 goto cleanup; 487 } 488 } 489 490 if (!retrieved) { 491 /* max retries attempted */ 492 pkgerr_add(err, PKGERR_WEB, 493 gettext(ERR_DWNLD_FAILED), retries); 494 ret = B_FALSE; 495 } 496 cleanup: 497 (void) web_disconnect(); 498 if (!ret) { 499 pkgerr_add(err, PKGERR_WEB, gettext(ERR_DWNLD), url); 500 } 501 return (ret); 502 } 503 504 /* 505 * Name: get_signature 506 * Description: retrieves signature from signed package. 507 * 508 * Arguments: err - where to record any errors. 509 * ids_name - name of package stream, for error reporting 510 * devp - Device on which package resides that we 511 * result - where to store resulting PKCS7 signature 512 * 513 * Returns : B_TRUE - package is signed and signature returned OR 514 * package is not signed, in which case result is NULL 515 * 516 * B_FALSE - there were problems accessing signature, 517 * and it is unknown whether it is signed or not. Errors 518 * recorded in 'err'. 519 */ 520 boolean_t 521 get_signature(PKG_ERR *err, char *ids_name, struct pkgdev *devp, PKCS7 **result) 522 { 523 char path[PATH_MAX]; 524 int len, fd = -1; 525 struct stat buf; 526 FILE *fp = NULL; 527 boolean_t ret = B_TRUE; 528 BIO *sig_in = NULL; 529 PKCS7 *p7 = NULL; 530 531 /* 532 * look for signature. If one was in the stream, 533 * it is now extracted 534 */ 535 if (((len = snprintf(path, PATH_MAX, "%s/%s", devp->dirname, 536 SIGNATURE_FILENAME)) >= PATH_MAX) || (len < 0)) { 537 pkgerr_add(err, PKGERR_WEB, gettext(ERR_LEN), ids_name); 538 ret = B_FALSE; 539 goto cleanup; 540 } 541 542 if ((fd = open(path, O_RDONLY|O_NONBLOCK)) == -1) { 543 /* 544 * only if the signature is non-existant 545 * do we "pass" 546 */ 547 if (errno != ENOENT) { 548 pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), 549 strerror(errno)); 550 ret = B_FALSE; 551 goto cleanup; 552 } 553 } else { 554 /* found sig file. parse it. */ 555 if (fstat(fd, &buf) == -1) { 556 pkgerr_add(err, PKGERR_WEB, 557 gettext(ERR_OPENSIG), strerror(errno)); 558 ret = B_FALSE; 559 goto cleanup; 560 } 561 562 if (!S_ISREG(buf.st_mode)) { 563 pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPENSIG), 564 (gettext(ERR_NOT_REG))); 565 ret = B_FALSE; 566 goto cleanup; 567 } 568 569 if ((fp = fdopen(fd, "r")) == NULL) { 570 pkgerr_add(err, PKGERR_WEB, 571 gettext(ERR_OPENSIG), strerror(errno)); 572 ret = B_FALSE; 573 goto cleanup; 574 } 575 576 /* 577 * read in signature. If it's invalid, we 578 * punt, unless we're ignoring it 579 */ 580 if ((sig_in = BIO_new_fp(fp, BIO_NOCLOSE)) == NULL) { 581 pkgerr_add(err, PKGERR_WEB, 582 gettext(ERR_OPENSIG), strerror(errno)); 583 goto cleanup; 584 } 585 586 if ((p7 = PEM_read_bio_PKCS7(sig_in, 587 NULL, NULL, NULL)) == NULL) { 588 pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), 589 ids_name); 590 ret = B_FALSE; 591 goto cleanup; 592 } 593 *result = p7; 594 p7 = NULL; 595 } 596 597 cleanup: 598 if (sig_in) 599 (void) BIO_free(sig_in); 600 if (fp) 601 (void) fclose(fp); 602 if (fd != -1) 603 (void) close(fd); 604 if (p7) 605 (void) PKCS7_free(p7); 606 607 return (ret); 608 } 609 610 /* 611 * Name: echo_out 612 * Description: Conditionally output a message to stdout 613 * 614 * Arguments: nointeract - if non-zero, do not output anything 615 * fmt - print format 616 * ... - print arguments 617 * 618 * Returns : none 619 */ 620 void 621 echo_out(int nointeract, char *fmt, ...) 622 { 623 va_list ap; 624 625 va_start(ap, fmt); 626 627 if (nointeract) 628 return; 629 630 (void) vfprintf(stdout, fmt, ap); 631 632 va_end(ap); 633 634 (void) putc('\n', stdout); 635 } 636 637 /* 638 * Name: strip_port 639 * Description: Returns "port" portion of a "hostname:port" string 640 * 641 * Arguments: proxy - full "hostname:port" string pointer 642 * 643 * Returns : the "port" portion of a "hostname:port" string, 644 * converted to a decimal integer, or (int)0 645 * if string contains no :port suffix. 646 */ 647 ushort_t 648 strip_port(char *proxy) 649 { 650 char *tmp_port; 651 652 if ((tmp_port = strpbrk(proxy, ":")) != NULL) 653 return (atoi(tmp_port)); 654 else 655 return (0); 656 } 657 658 /* 659 * Name: set_web_install 660 * Description: Sets flag indicating we are doing a web-based install 661 * 662 * Arguments: none 663 * 664 * Returns : none 665 */ 666 void 667 set_web_install(void) 668 { 669 webpkg_install++; 670 } 671 672 /* 673 * Name: is_web_install 674 * Description: Determines whether we are doing a web-based install 675 * 676 * Arguments: none 677 * 678 * Returns : non-zero if we are doing a web-based install, 0 otherwise 679 */ 680 int 681 is_web_install(void) 682 { 683 return (webpkg_install); 684 } 685 686 /* ~~~~~~~~~~~~~~ Private Functions ~~~~~~~~~~~~~~~~~~~ */ 687 688 /* 689 * Name: web_disconnect 690 * Description: Disconnects connection to web server 691 * 692 * Arguments: none 693 * 694 * Returns : B_TRUE - successful disconnect, B_FALSE otherwise 695 * Temp certificiate files are deleted, 696 * if one was used to initiate the connection 697 * (such as when using SSL) 698 */ 699 static boolean_t 700 web_disconnect(void) 701 { 702 if (ps->certfile) { 703 (void) unlink(ps->certfile); 704 } 705 if (http_srv_disconnect(ps->hps) == 0) 706 if (http_srv_close(ps->hps) == 0) 707 return (B_TRUE); 708 709 return (B_FALSE); 710 } 711 712 /* 713 * Name: check_dwnld_dir 714 * Description: Creates temp download directory 715 * 716 * Arguments: err - where to record any errors. 717 * dwnld_dir - name of directory to create 718 * 719 * Returns : B_TRUE - success, B_FALSE otherwise 720 * on success, directory is created with 721 * safe permissions 722 */ 723 static boolean_t 724 check_dwnld_dir(PKG_ERR *err, char *dwnld_dir) 725 { 726 DIR *dirp; 727 728 /* 729 * Check the directory passed in. If it doesn't exist, create it 730 * with strict permissions 731 */ 732 if ((dirp = opendir(dwnld_dir)) == NULL) { 733 if (mkdir(dwnld_dir, 0744) == -1) { 734 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), 735 dwnld_dir); 736 return (B_FALSE); 737 } 738 } 739 if (dirp) { 740 (void) closedir(dirp); 741 } 742 return (B_TRUE); 743 } 744 745 /* 746 * Name: ds_validate_signature 747 * Description: Validates signature found in a package datastream 748 * 749 * Arguments: err - where to record any errors. 750 * pkgdev - Package context handle of package to verify 751 * pkgs - Null-terminated List of package name to verify 752 * ids_name - Pathname to stream to validate 753 * p7 - PKCS7 signature decoded from stream header 754 * cas - List of trusted CA certificates 755 * proxy - Proxy to use when doing online validation (OCSP) 756 * nointeract - if non-zero, do not output to screen 757 * 758 * Returns : B_TRUE - success, B_FALSE otherwise 759 * success means signature was completely validated, 760 * and contents of stream checked against signature. 761 */ 762 boolean_t 763 ds_validate_signature(PKG_ERR *err, struct pkgdev *pkgdev, char **pkgs, 764 char *ids_name, PKCS7 *p7, STACK_OF(X509) *cas, 765 url_hport_t *proxy, int nointeract) 766 { 767 BIO *p7_bio; 768 boolean_t ret = B_TRUE; 769 770 /* make sure it's a Signed PKCS7 message */ 771 if (!PKCS7_type_is_signed(p7)) { 772 pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_TYPE), 773 ids_name); 774 ret = B_FALSE; 775 goto cleanup; 776 } 777 778 /* initialize PKCS7 object to be filled in */ 779 if (!PKCS7_get_detached(p7)) { 780 pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG_DT), 781 ids_name); 782 ret = B_FALSE; 783 goto cleanup; 784 } 785 786 /* dump header and packages into BIO to calculate the message digest */ 787 if ((p7_bio = PKCS7_dataInit(p7, NULL)) == NULL) { 788 pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), 789 ids_name); 790 ret = B_FALSE; 791 goto cleanup; 792 } 793 794 if ((BIO_ds_dump_header(err, p7_bio) != 0) || 795 (BIO_ds_dump(err, ids_name, p7_bio) != 0)) { 796 ret = B_FALSE; 797 goto cleanup; 798 } 799 (void) BIO_flush(p7_bio); 800 801 /* validate the stream and its signature */ 802 if (!validate_signature(err, ids_name, p7_bio, p7, cas, 803 proxy, nointeract)) { 804 ret = B_FALSE; 805 goto cleanup; 806 } 807 808 /* reset device stream (really bad performance for tapes) */ 809 (void) ds_close(1); 810 (void) ds_init(ids_name, pkgs, pkgdev->norewind); 811 812 cleanup: 813 return (ret); 814 } 815 816 817 /* 818 * Name: validate_signature 819 * Description: Validates signature of an arbitrary stream of bits 820 * 821 * Arguments: err - where to record any errors. 822 * name - Descriptive name of object being validated, 823 * for good error reporting messages 824 * indata - BIO object to read stream bits from 825 * p7 - PKCS7 signature of stream 826 * cas - List of trusted CA certificates 827 * proxy - Proxy to use when doing online validation (OCSP) 828 * nointeract - if non-zero, do not output to screen 829 * 830 * Returns : B_TRUE - success, B_FALSE otherwise 831 * success means signature was completely validated, 832 * and contents of stream checked against signature. 833 */ 834 boolean_t 835 validate_signature(PKG_ERR *err, char *name, BIO *indata, PKCS7 *p7, 836 STACK_OF(X509) *cas, url_hport_t *proxy, int nointeract) 837 { 838 STACK_OF(PKCS7_SIGNER_INFO) *sec_sinfos = NULL; 839 840 PKCS7_SIGNER_INFO *signer = NULL; 841 X509_STORE *sec_truststore = NULL; 842 X509_STORE_CTX *ctx = NULL; 843 X509 *signer_cert = NULL, *issuer = NULL; 844 STACK_OF(X509) *chaincerts = NULL; 845 int i, k; 846 unsigned long errcode; 847 const char *err_data = NULL; 848 const char *err_reason = NULL; 849 char *err_string; 850 int err_flags; 851 verify_cb_data_t verify_data; 852 char *signer_sname; 853 char *signer_iname; 854 PKCS7_ISSUER_AND_SERIAL *ias; 855 boolean_t ret = B_TRUE; 856 857 /* only support signed PKCS7 signatures */ 858 if (!PKCS7_type_is_signed(p7)) { 859 PKCS7err(PKCS7_F_PKCS7_DATAVERIFY, PKCS7_R_WRONG_PKCS7_TYPE); 860 ret = B_FALSE; 861 goto cleanup; 862 } 863 864 /* initialize temporary internal trust store used for verification */ 865 sec_truststore = X509_STORE_new(); 866 867 for (i = 0; i < sk_X509_num(cas); i++) { 868 if (X509_STORE_add_cert(sec_truststore, 869 sk_X509_value(cas, i)) == 0) { 870 pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); 871 ret = B_FALSE; 872 goto cleanup; 873 } 874 } 875 876 /* get signers from the signature */ 877 if ((sec_sinfos = PKCS7_get_signer_info(p7)) == NULL) { 878 pkgerr_add(err, PKGERR_WEB, gettext(ERR_CORRUPTSIG), name); 879 ret = B_FALSE; 880 goto cleanup; 881 } 882 883 /* verify each signer found in the PKCS7 signature */ 884 for (k = 0; k < sk_PKCS7_SIGNER_INFO_num(sec_sinfos); k++) { 885 signer = sk_PKCS7_SIGNER_INFO_value(sec_sinfos, k); 886 signer_cert = PKCS7_cert_from_signer_info(p7, signer); 887 signer_sname = get_subject_display_name(signer_cert); 888 signer_iname = get_issuer_display_name(signer_cert); 889 890 echo_out(nointeract, gettext(MSG_VERIFY), signer_sname); 891 892 /* find the issuer of the current cert */ 893 chaincerts = p7->d.sign->cert; 894 ias = signer->issuer_and_serial; 895 issuer = X509_find_by_issuer_and_serial(chaincerts, 896 ias->issuer, ias->serial); 897 898 /* were we not able to find the issuer cert */ 899 if (issuer == NULL) { 900 pkgerr_add(err, PKGERR_WEB, 901 gettext(ERR_VERIFY_ISSUER), 902 signer_iname, signer_sname); 903 ret = B_FALSE; 904 goto cleanup; 905 } 906 907 /* Lets verify */ 908 if ((ctx = X509_STORE_CTX_new()) == NULL) { 909 pkgerr_add(err, PKGERR_VERIFY, gettext(ERR_MEM)); 910 ret = B_FALSE; 911 goto cleanup; 912 } 913 (void) X509_STORE_CTX_init(ctx, sec_truststore, 914 issuer, chaincerts); 915 (void) X509_STORE_CTX_set_purpose(ctx, 916 X509_PURPOSE_ANY); 917 918 /* callback will perform OCSP on certificates with OCSP data */ 919 X509_STORE_CTX_set_verify_cb(ctx, web_verify); 920 921 /* pass needed data into callback through the app_data handle */ 922 verify_data.proxy = proxy; 923 verify_data.cas = cas; 924 verify_data.err = err; 925 (void) X509_STORE_CTX_set_app_data(ctx, &verify_data); 926 927 /* first verify the certificate chain */ 928 i = X509_verify_cert(ctx); 929 if (i <= 0 && ctx->error != X509_V_ERR_CERT_HAS_EXPIRED) { 930 signer_sname = 931 get_subject_display_name(ctx->current_cert); 932 signer_iname = 933 get_issuer_display_name(ctx->current_cert); 934 /* if the verify context holds an error, print it */ 935 if (ctx->error != X509_V_OK) { 936 pkgerr_add(err, PKGERR_VERIFY, 937 gettext(ERR_VERIFY_SIG), signer_sname, 938 signer_iname, 939 (char *)X509_verify_cert_error_string(ctx->error)); 940 } else { 941 /* some other error. print them all. */ 942 while ((errcode = ERR_get_error_line_data(NULL, 943 NULL, &err_data, &err_flags)) != 0) { 944 err_reason = 945 ERR_reason_error_string(errcode); 946 if (err_reason == NULL) { 947 err_reason = 948 gettext(ERR_SIG_INT); 949 } 950 951 if (!(err_flags & ERR_TXT_STRING)) { 952 err_data = 953 gettext(ERR_SIG_INT); 954 } 955 err_string = 956 xmalloc(strlen(err_reason) + 957 strlen(err_data) + 3); 958 (void) sprintf(err_string, "%s: %s", 959 err_reason, err_data); 960 pkgerr_add(err, PKGERR_VERIFY, 961 gettext(ERR_VERIFY_SIG), 962 signer_sname, signer_iname, 963 err_string); 964 free(err_string); 965 } 966 } 967 ret = B_FALSE; 968 goto cleanup; 969 } 970 971 /* now verify the signature */ 972 i = PKCS7_signatureVerify(indata, p7, signer, issuer); 973 974 if (i <= 0) { 975 /* print out any OpenSSL-specific errors */ 976 signer_sname = 977 get_subject_display_name(ctx->current_cert); 978 signer_iname = 979 get_subject_display_name(ctx->current_cert); 980 while ((errcode = ERR_get_error_line_data(NULL, 981 NULL, &err_data, &err_flags)) != 0) { 982 err_reason = 983 ERR_reason_error_string(errcode); 984 if (err_reason == NULL) { 985 err_reason = 986 gettext(ERR_SIG_INT); 987 } 988 989 if (!(err_flags & ERR_TXT_STRING)) { 990 err_data = 991 gettext(ERR_SIG_INT); 992 } 993 pkgerr_add(err, PKGERR_VERIFY, 994 gettext(ERR_VERIFY_SIG), signer_sname, 995 signer_iname, err_reason); 996 pkgerr_add(err, PKGERR_VERIFY, 997 gettext(ERR_VERIFY_SIG), signer_sname, 998 signer_iname, err_data); 999 } 1000 ret = B_FALSE; 1001 goto cleanup; 1002 } 1003 1004 echo_out(nointeract, gettext(MSG_VERIFY_OK), signer_sname); 1005 } 1006 1007 /* signature(s) verified successfully */ 1008 cleanup: 1009 if (ctx) 1010 X509_STORE_CTX_cleanup(ctx); 1011 return (ret); 1012 } 1013 1014 /* 1015 * Name: web_verify 1016 * Description: Callback used by PKCS7_dataVerify when 1017 * verifying a certificate chain. 1018 * 1019 * Arguments: err - where to record any errors. 1020 * ctx - The context handle of the current verification operation 1021 * 1022 * Returns : B_TRUE - success, B_FALSE otherwise 1023 * if it's '0' (not OK) we simply return it, since the 1024 * verification operation has already determined that the 1025 * cert is invalid. if 'ok' is non-zero, then we do our 1026 * checks, and return 0 or 1 based on if the cert is 1027 * invalid or valid. 1028 */ 1029 static int 1030 web_verify(int ok, X509_STORE_CTX *ctx) 1031 { 1032 X509 *curr_cert; 1033 X509 *curr_issuer; 1034 char *uri; 1035 url_hport_t *proxy; 1036 PKG_ERR *err = NULL; 1037 STACK_OF(X509) *cas; 1038 if (!ok) { 1039 /* don't override a verify failure */ 1040 return (ok); 1041 } 1042 1043 1044 /* get app data supplied through callback context */ 1045 err = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->err; 1046 proxy = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->proxy; 1047 cas = ((verify_cb_data_t *)X509_STORE_CTX_get_app_data(ctx))->cas; 1048 1049 /* Check revocation status */ 1050 curr_cert = X509_STORE_CTX_get_current_cert(ctx); 1051 1052 /* this shouldn't happen */ 1053 if (curr_cert == NULL) { 1054 pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), 1055 __FILE__, __LINE__); 1056 return (0); 1057 } 1058 1059 /* don't perform OCSP unless cert has required OCSP extensions */ 1060 if (get_ocsp_uri(curr_cert, &uri)) { 1061 if (get_issuer(&curr_issuer, ctx, curr_cert) <= 0) { 1062 /* no issuer! */ 1063 pkgerr_add(err, PKGERR_INTERNAL, 1064 gettext(ERR_PKG_INTERNAL), 1065 __FILE__, __LINE__); 1066 return (0); 1067 } 1068 1069 /* 1070 * ok we have the current cert 1071 * and its issuer. Do the OCSP check 1072 */ 1073 1074 /* 1075 * OCSP extensions are, by, RFC 2459, never critical 1076 * extensions, therefore, we only fail if we were able 1077 * to explicitly contact an OCSP responder, and that 1078 * responder did not indicate the cert was valid. We 1079 * also fail if user-supplied data could not be parsed 1080 * or we run out of memory. We succeeed for "soft" 1081 * failures, such as not being able to connect to the 1082 * OCSP responder, or trying to use if the OCSP URI 1083 * indicates SSL must be used (which we do not 1084 * support) 1085 */ 1086 switch (ocsp_verify(err, curr_cert, curr_issuer, 1087 uri, proxy, cas)) { 1088 case OCSPMem: /* Ran out of memory */ 1089 case OCSPInternal: /* Some internal error */ 1090 case OCSPVerify: /* OCSP responder indicated fail */ 1091 return (0); 1092 } 1093 /* all other cases are success, or soft failures */ 1094 pkgerr_clear(err); 1095 } 1096 1097 return (ok); 1098 } 1099 1100 /* 1101 * Name: get_time_string 1102 * Description: Generates a human-readable string from an ASN1_GENERALIZED_TIME 1103 * 1104 * Arguments: intime - The time to convert 1105 * 1106 * Returns : A pointer to a static string representing the passed-in time. 1107 */ 1108 static char 1109 *get_time_string(ASN1_GENERALIZEDTIME *intime) 1110 { 1111 1112 static char time[ATTR_MAX]; 1113 BIO *mem; 1114 char *p; 1115 1116 if (intime == NULL) { 1117 return (NULL); 1118 } 1119 if ((mem = BIO_new(BIO_s_mem())) == NULL) { 1120 return (NULL); 1121 } 1122 1123 if (ASN1_GENERALIZEDTIME_print(mem, intime) == 0) { 1124 (void) BIO_free(mem); 1125 return (NULL); 1126 } 1127 1128 if (BIO_gets(mem, time, ATTR_MAX) <= 0) { 1129 (void) BIO_free(mem); 1130 return (NULL); 1131 } 1132 1133 (void) BIO_free(mem); 1134 1135 /* trim the end of the string */ 1136 for (p = time + strlen(time) - 1; isspace(*p); p--) { 1137 *p = '\0'; 1138 } 1139 1140 return (time); 1141 } 1142 1143 /* 1144 * Name: get_ocsp_uri 1145 * Description: Examines an X509 certificate and retrieves the embedded 1146 * OCSP Responder URI if one exists. 1147 * 1148 * Arguments: cert - The cert to inspect 1149 * uri - pointer where the newly-allocated URI is placed, if found 1150 * 1151 * Returns : Success if the URI was found. Appropriate status otherwise. 1152 */ 1153 static boolean_t 1154 get_ocsp_uri(X509 *cert, char **uri) 1155 { 1156 AUTHORITY_INFO_ACCESS *aia; 1157 ACCESS_DESCRIPTION *ad; 1158 int i; 1159 1160 if (getenv("PKGWEB_TEST_OCSP")) { 1161 *uri = xstrdup(getenv("PKGWEB_TEST_OCSP")); 1162 return (B_TRUE); 1163 } 1164 1165 /* get the X509v3 extension holding the OCSP URI */ 1166 if ((aia = X509_get_ext_d2i(cert, NID_info_access, 1167 NULL, NULL)) != NULL) { 1168 for (i = 0; i < sk_ACCESS_DESCRIPTION_num(aia); i++) { 1169 ad = sk_ACCESS_DESCRIPTION_value(aia, i); 1170 if (OBJ_obj2nid(ad->method) == NID_ad_OCSP) { 1171 if (ad->location->type == GEN_URI) { 1172 *uri = 1173 xstrdup((char *)ASN1_STRING_data(ad->location->d.ia5)); 1174 return (B_TRUE); 1175 } 1176 } 1177 } 1178 } 1179 1180 /* no URI was found */ 1181 return (B_FALSE); 1182 } 1183 1184 /* 1185 * Name: ocsp_verify 1186 * Description: Attempts to contact an OCSP Responder and ascertain the validity 1187 * of an X509 certificate. 1188 * 1189 * Arguments: err - Error object to add error messages to 1190 * cert - The cert to validate 1191 * issuer - The certificate of the issuer of 'cert' 1192 * uri - The OCSP Responder URI 1193 * cas - The trusted CA certificates used to verify the 1194 * signed OCSP response 1195 * Returns : Success - The OCSP Responder reported a 'good' 1196 * status for the cert otherwise, appropriate 1197 * error is returned. 1198 */ 1199 static OCSPStatus 1200 ocsp_verify(PKG_ERR *err, X509 *cert, X509 *issuer, 1201 char *uri, url_hport_t *proxy, STACK_OF(X509) *cas) 1202 { 1203 OCSP_CERTID *id; 1204 OCSP_REQUEST *req; 1205 OCSP_RESPONSE *resp; 1206 OCSP_BASICRESP *bs; 1207 BIO *cbio, *mem; 1208 char ocspbuf[OCSP_BUFSIZ]; 1209 char *host = NULL, *portstr = NULL, *path = "/", *p, *q, *r; 1210 int port, status, reason; 1211 int len, retval, respcode, use_ssl = 0; 1212 ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd; 1213 char *subjname; 1214 time_t currtime; 1215 char currtimestr[ATTR_MAX]; 1216 unsigned long errcode; 1217 const char *err_reason; 1218 1219 subjname = get_subject_display_name(cert); 1220 1221 /* parse the URI into its constituent parts */ 1222 if (OCSP_parse_url(uri, &host, &portstr, &path, &use_ssl) == NULL) { 1223 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_PARSE), uri); 1224 return (OCSPParse); 1225 } 1226 1227 /* we don't currently support SSL-based OCSP Responders */ 1228 if (use_ssl) { 1229 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_UNSUP), uri); 1230 return (OCSPUnsupported); 1231 } 1232 1233 /* default port if none specified */ 1234 if (portstr == NULL) { 1235 port = (int)URL_DFLT_SRVR_PORT; 1236 } else { 1237 port = (int)strtoul(portstr, &r, 10); 1238 if (*r != '\0') { 1239 pkgerr_add(err, PKGERR_PARSE, 1240 gettext(ERR_OCSP_PARSE), uri); 1241 return (OCSPParse); 1242 } 1243 } 1244 1245 /* allocate new request structure */ 1246 if ((req = OCSP_REQUEST_new()) == NULL) { 1247 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); 1248 return (OCSPMem); 1249 } 1250 1251 /* convert cert and issuer fields into OCSP request data */ 1252 if ((id = OCSP_cert_to_id(NULL, cert, issuer)) == NULL) { 1253 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), 1254 __FILE__, __LINE__); 1255 return (OCSPInternal); 1256 } 1257 1258 /* fill out request structure with request data */ 1259 if ((OCSP_request_add0_id(req, id)) == NULL) { 1260 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_PKG_INTERNAL), 1261 __FILE__, __LINE__); 1262 return (OCSPInternal); 1263 } 1264 1265 /* add nonce */ 1266 OCSP_request_add1_nonce(req, NULL, -1); 1267 1268 /* connect to host, or proxy */ 1269 if (proxy != NULL) { 1270 if ((cbio = BIO_new_connect(proxy->hostname)) == NULL) { 1271 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); 1272 return (OCSPMem); 1273 } 1274 1275 /* 1276 * BIO_set_conn_int_port takes an int *, so let's give it one 1277 * rather than an ushort_t * 1278 */ 1279 port = proxy->port; 1280 (void) BIO_set_conn_int_port(cbio, &port); 1281 if (BIO_do_connect(cbio) <= 0) { 1282 pkgerr_add(err, PKGERR_PARSE, 1283 gettext(ERR_OCSP_CONNECT), 1284 proxy->hostname, port); 1285 return (OCSPConnect); 1286 } 1287 } else { 1288 if ((cbio = BIO_new_connect(host)) == NULL) { 1289 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); 1290 return (OCSPMem); 1291 } 1292 1293 (void) BIO_set_conn_int_port(cbio, &port); 1294 if (BIO_do_connect(cbio) <= 0) { 1295 pkgerr_add(err, PKGERR_PARSE, 1296 gettext(ERR_OCSP_CONNECT), 1297 host, port); 1298 return (OCSPConnect); 1299 } 1300 } 1301 1302 /* calculate length of binary request data */ 1303 len = i2d_OCSP_REQUEST(req, NULL); 1304 1305 /* send the request headers */ 1306 if (proxy != NULL) { 1307 retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, uri, len); 1308 } else { 1309 retval = BIO_printf(cbio, OCSP_REQUEST_FORMAT, path, len); 1310 } 1311 1312 if (retval <= 0) { 1313 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); 1314 return (OCSPRequest); 1315 } 1316 1317 /* send the request binary data */ 1318 if (i2d_OCSP_REQUEST_bio(cbio, req) <= 0) { 1319 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_SEND), host); 1320 return (OCSPRequest); 1321 } 1322 1323 /* 1324 * read the response into a memory BIO, so we can 'gets' 1325 * (socket bio's don't support BIO_gets) 1326 */ 1327 if ((mem = BIO_new(BIO_s_mem())) == NULL) { 1328 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); 1329 return (OCSPMem); 1330 } 1331 1332 while ((len = BIO_read(cbio, ocspbuf, OCSP_BUFSIZ))) { 1333 if (len < 0) { 1334 pkgerr_add(err, PKGERR_PARSE, 1335 gettext(ERR_OCSP_READ), host); 1336 return (OCSPRequest); 1337 } 1338 if (BIO_write(mem, ocspbuf, len) != len) { 1339 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_MEM)); 1340 return (OCSPMem); 1341 } 1342 } 1343 1344 /* now get the first line of the response */ 1345 if (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) <= 0) { 1346 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_PARSE)); 1347 return (OCSPRequest); 1348 } 1349 1350 /* parse the header response */ 1351 /* it should look like "HTTP/x.x 200 OK" */ 1352 1353 /* skip past the protocol info */ 1354 for (p = ocspbuf; (*p != '\0') && !isspace(*p); p++) 1355 continue; 1356 1357 /* skip past whitespace betwen protocol and start of response code */ 1358 while ((*p != '\0') && isspace(*p)) { 1359 p++; 1360 } 1361 1362 if (*p == '\0') { 1363 /* premature end */ 1364 pkgerr_add(err, PKGERR_PARSE, 1365 gettext(ERR_OCSP_RESP_PARSE), ocspbuf); 1366 return (OCSPRequest); 1367 } 1368 1369 /* find end of response code */ 1370 for (q = p; (*q != NULL) && !isspace(*q); q++) 1371 continue; 1372 1373 /* mark end of response code */ 1374 *q++ = '\0'; 1375 1376 /* parse response code */ 1377 respcode = strtoul(p, &r, 10); 1378 if (*r != '\0') { 1379 pkgerr_add(err, PKGERR_PARSE, 1380 gettext(ERR_OCSP_RESP_PARSE), ocspbuf); 1381 return (OCSPRequest); 1382 } 1383 1384 /* now find beginning of the response string */ 1385 while ((*q != NULL) && isspace(*q)) { 1386 q++; 1387 } 1388 1389 /* trim whitespace from end of message */ 1390 for (r = (q + strlen(q) - 1); isspace(*r); r--) { 1391 *r = '\0'; 1392 } 1393 1394 /* response must be OK */ 1395 if (respcode != 200) { 1396 pkgerr_add(err, PKGERR_PARSE, 1397 gettext(ERR_OCSP_RESP_NOTOK), 200, 1398 respcode, q); 1399 return (OCSPRequest); 1400 } 1401 1402 /* read headers, looking for content-type or a blank line */ 1403 while (BIO_gets(mem, ocspbuf, OCSP_BUFSIZ) > 0) { 1404 1405 /* if we get a content type, make sure it's the right type */ 1406 if (ci_strneq(ocspbuf, CONTENT_TYPE_HDR, 1407 strlen(CONTENT_TYPE_HDR))) { 1408 1409 /* look for the delimiting : */ 1410 p = strchr(ocspbuf + strlen(CONTENT_TYPE_HDR), ':'); 1411 1412 if (p == NULL) { 1413 pkgerr_add(err, PKGERR_PARSE, 1414 gettext(ERR_OCSP_RESP_PARSE), ocspbuf); 1415 return (OCSPResponder); 1416 } 1417 1418 /* skip over ':' */ 1419 p++; 1420 1421 /* find beginning of the content type */ 1422 while ((*p != NULL) && isspace(*p)) { 1423 p++; 1424 } 1425 1426 if (!ci_strneq(p, CONTENT_OCSP_RESP, 1427 strlen(CONTENT_OCSP_RESP))) { 1428 /* response is not right type */ 1429 pkgerr_add(err, PKGERR_PARSE, 1430 gettext(ERR_OCSP_RESP_TYPE), 1431 p, CONTENT_OCSP_RESP); 1432 return (OCSPResponder); 1433 } 1434 1435 /* continue with next header line */ 1436 continue; 1437 } 1438 1439 /* scan looking for a character */ 1440 for (p = ocspbuf; (*p != '\0') && isspace(*p); p++) { 1441 continue; 1442 } 1443 /* 1444 * if we got to the end of the line with 1445 * no chars, then this is a blank line 1446 */ 1447 if (*p == '\0') { 1448 break; 1449 } 1450 } 1451 1452 1453 if (*p != '\0') { 1454 /* last line was not blank */ 1455 pkgerr_add(err, PKGERR_PARSE, 1456 gettext(ERR_OCSP_RESP_PARSE), ocspbuf); 1457 return (OCSPResponder); 1458 } 1459 1460 /* now read in the binary response */ 1461 if ((resp = d2i_OCSP_RESPONSE_bio(mem, NULL)) == NULL) { 1462 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); 1463 return (OCSPResponder); 1464 } 1465 1466 /* free temp BIOs */ 1467 (void) BIO_free(mem); 1468 (void) BIO_free_all(cbio); 1469 cbio = NULL; 1470 1471 /* make sure request was successful */ 1472 if (OCSP_response_status(resp) != OCSP_RESPONSE_STATUS_SUCCESSFUL) { 1473 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_RESP_NOTOK), 1474 OCSP_RESPONSE_STATUS_SUCCESSFUL, 1475 OCSP_response_status(resp), 1476 OCSP_response_status_str(OCSP_response_status(resp))); 1477 return (OCSPResponder); 1478 } 1479 1480 /* parse binary response into internal structure */ 1481 if ((bs = OCSP_response_get1_basic(resp)) == NULL) { 1482 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_READ), host); 1483 return (OCSPParse); 1484 } 1485 1486 /* 1487 * From here to the end of the code, the return values 1488 * should be hard failures 1489 */ 1490 1491 /* verify the response, warn if no nonce */ 1492 if (OCSP_check_nonce(req, bs) <= 0) { 1493 logerr(pkg_gt(WRN_OCSP_RESP_NONCE)); 1494 } 1495 1496 if (OCSP_basic_verify(bs, cas, NULL, OCSP_TRUSTOTHER) <= 0) { 1497 while ((errcode = ERR_get_error()) != NULL) { 1498 err_reason = ERR_reason_error_string(errcode); 1499 if (err_reason == NULL) { 1500 err_reason = 1501 gettext(ERR_SIG_INT); 1502 } 1503 pkgerr_add(err, PKGERR_PARSE, (char *)err_reason); 1504 } 1505 pkgerr_add(err, PKGERR_PARSE, gettext(ERR_OCSP_VERIFY_FAIL), 1506 uri); 1507 return (OCSPVerify); 1508 } 1509 1510 /* check the validity of our certificate */ 1511 if (OCSP_resp_find_status(bs, id, &status, &reason, 1512 &rev, &thisupd, &nextupd) == NULL) { 1513 pkgerr_add(err, PKGERR_PARSE, 1514 gettext(ERR_OCSP_VERIFY_NO_STATUS), subjname); 1515 return (OCSPVerify); 1516 } 1517 1518 if ((currtime = time(NULL)) == (time_t)-1) { 1519 pkgerr_add(err, PKGERR_PARSE, 1520 gettext(ERR_OCSP_VERIFY_NOTIME)); 1521 return (OCSPVerify); 1522 } 1523 1524 (void) strlcpy(currtimestr, ctime(&currtime), ATTR_MAX); 1525 1526 /* trim end */ 1527 for (r = currtimestr + strlen(currtimestr) - 1; 1528 isspace(*r); r--) { 1529 *r = '\0'; 1530 } 1531 1532 if (!OCSP_check_validity(thisupd, nextupd, 1533 OCSP_VALIDITY_PERIOD, -1)) { 1534 if (nextupd != NULL) { 1535 pkgerr_add(err, PKGERR_PARSE, 1536 gettext(ERR_OCSP_VERIFY_VALIDITY), 1537 get_time_string(thisupd), get_time_string(nextupd), 1538 currtimestr); 1539 } else { 1540 pkgerr_add(err, PKGERR_PARSE, 1541 gettext(ERR_OCSP_VERIFY_VALIDITY), 1542 get_time_string(thisupd), 1543 currtimestr); 1544 } 1545 return (OCSPVerify); 1546 } 1547 1548 if (status != V_OCSP_CERTSTATUS_GOOD) { 1549 pkgerr_add(err, PKGERR_PARSE, 1550 gettext(ERR_OCSP_VERIFY_STATUS), subjname, 1551 OCSP_cert_status_str(status)); 1552 return (OCSPVerify); 1553 } 1554 1555 /* everythign checks out */ 1556 return (OCSPSuccess); 1557 } 1558 1559 /* 1560 * Name: get_issuer 1561 * Description: Attempts to find the issuing certificate for a given certificate 1562 * This will look in both the list of trusted certificates found in 1563 * the X509_STORE_CTX structure, as well as the list of untrusted 1564 * chain certificates found in the X509_STORE_CTX structure. 1565 * Arguments: 1566 * issuer - The resulting issuer cert is placed here, if found 1567 * ctx - The current verification context 1568 * x - The certificate whose issuer we are looking for 1569 * Returns : Success - The issuer cert was found and placed in *issuer. 1570 * otherwise, appropriate error is returned. 1571 */ 1572 static int 1573 get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x) 1574 { 1575 int i, ok; 1576 1577 /* 1578 * first look in the list of trusted 1579 * certs, using the context's method to do so 1580 */ 1581 if ((ok = ctx->get_issuer(issuer, ctx, x)) > 0) { 1582 return (ok); 1583 } 1584 1585 if (ctx->untrusted != NULL) { 1586 /* didn't find it in trusted certs, look through untrusted */ 1587 for (i = 0; i < sk_X509_num(ctx->untrusted); i++) { 1588 if (X509_check_issued(sk_X509_value(ctx->untrusted, i), 1589 x) == X509_V_OK) { 1590 *issuer = sk_X509_value(ctx->untrusted, i); 1591 return (1); 1592 } 1593 } 1594 } 1595 *issuer = NULL; 1596 return (0); 1597 } 1598 1599 /* 1600 * Name: parse_url_proxy 1601 * Description: Parses URL and optional proxy specification, populates static 1602 * 'ps' structure 1603 * 1604 * Arguments: err - where to record any errors. 1605 * url - URL to parse 1606 * proxy - proxy to parse, or NULL for no proxy 1607 * proxy_port - Default proxy port to use if no proxy 1608 * port specified in 'proxy' 1609 * 1610 * Returns : B_TRUE - success, B_FALSE otherwise 1611 * on success, 'ps->url' and 'ps->proxy' are populated 1612 * with parsed data. 1613 */ 1614 static boolean_t 1615 parse_url_proxy(PKG_ERR *err, char *url, char *proxy, ushort_t proxy_port) 1616 { 1617 boolean_t ret = B_TRUE; 1618 if (!path_valid(url)) { 1619 ret = B_FALSE; 1620 goto cleanup; 1621 } 1622 1623 if (url_parse(url, &ps->url) != URL_PARSE_SUCCESS) { 1624 pkgerr_add(err, PKGERR_WEB, gettext(ERR_PARSE_URL), url); 1625 ret = B_FALSE; 1626 goto cleanup; 1627 } 1628 1629 if (proxy != NULL) { 1630 if (url_parse_hostport(proxy, &ps->proxy, proxy_port) 1631 != URL_PARSE_SUCCESS) { 1632 pkgerr_add(err, PKGERR_WEB, 1633 gettext(ERR_BAD_PROXY), proxy); 1634 ret = B_FALSE; 1635 goto cleanup; 1636 } 1637 } 1638 1639 cleanup: 1640 return (ret); 1641 } 1642 1643 /* 1644 * Name: web_setup 1645 * Description: Initializes http library settings 1646 * 1647 * Arguments: err - where to record any errors. 1648 * 1649 * Returns : B_TRUE - success, B_FALSE otherwise 1650 */ 1651 static boolean_t 1652 web_setup(PKG_ERR *err) 1653 { 1654 boolean_t ret = B_TRUE; 1655 static boolean_t keepalive = B_TRUE; 1656 1657 if ((ps->hps = http_srv_init(&ps->url)) == NULL) { 1658 pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); 1659 ret = B_FALSE; 1660 goto cleanup; 1661 } 1662 1663 if (getenv("WEBPKG_DEBUG") != NULL) { 1664 http_set_verbose(B_TRUE); 1665 } 1666 1667 if (ps->proxy.hostname[0] != '\0' && 1668 http_set_proxy(ps->hps, &ps->proxy) != 0) { 1669 pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); 1670 ret = B_FALSE; 1671 goto cleanup; 1672 } 1673 if (http_set_keepalive(ps->hps, keepalive) != 0) { 1674 pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); 1675 ret = B_FALSE; 1676 goto cleanup; 1677 } 1678 if (http_set_socket_read_timeout(ps->hps, ps->timeout) != 0) { 1679 pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); 1680 ret = B_FALSE; 1681 goto cleanup; 1682 } 1683 if (http_set_random_file(ps->hps, RANDOM) != 0) { 1684 pkgerr_add(err, PKGERR_WEB, gettext(ERR_INIT_SESS), ps->url); 1685 ret = B_FALSE; 1686 goto cleanup; 1687 } 1688 1689 (void) http_set_p12_format(B_TRUE); 1690 1691 cleanup: 1692 return (ret); 1693 } 1694 1695 /* 1696 * Name: web_connect 1697 * Description: Makes connection with URL stored in static 'ps' structure. 1698 * 1699 * Arguments: err - where to record any errors. 1700 * 1701 * Returns : WEB_OK - connection successful 1702 * WEB_VERIFY_SETUP - Unable to complete necessary 1703 * SSL setup 1704 * WEB_CONNREFUSED - Connection was refused to web site 1705 * WEB_HOSTDOWN - Host was not responding to request 1706 * WEB_NOCONNECT - Some other connection failure 1707 */ 1708 static WebStatus 1709 web_connect(PKG_ERR *err) 1710 { 1711 STACK_OF(X509) *sec_cas = NULL; 1712 char *path; 1713 WebStatus ret = WEB_OK; 1714 ulong_t errcode; 1715 uint_t errsrc; 1716 int my_errno = 0; 1717 const char *libhttperr = NULL; 1718 1719 if (ps->url.https == B_TRUE) { 1720 /* get CA certificates */ 1721 if (find_ca_certs(err, ps->keystore, &sec_cas) != 0) { 1722 ret = WEB_VERIFY_SETUP; 1723 goto cleanup; 1724 } 1725 1726 if (sk_X509_num(sec_cas) < 1) { 1727 /* no trusted websites */ 1728 pkgerr_add(err, PKGERR_WEB, 1729 gettext(ERR_KEYSTORE_NOTRUST)); 1730 ret = WEB_VERIFY_SETUP; 1731 goto cleanup; 1732 } 1733 1734 /* 1735 * write out all CA certs to temp file. libwanboot should 1736 * have an interface for giving it a list of trusted certs 1737 * through an in-memory structure, but currently that does 1738 * not exist 1739 */ 1740 if ((path = write_ca_file(err, ps->dwnld_dir, sec_cas, 1741 WEB_CA_PHRASE)) == NULL) { 1742 ret = WEB_VERIFY_SETUP; 1743 goto cleanup; 1744 } 1745 1746 ps->certfile = path; 1747 if (http_set_password(ps->hps, WEB_CA_PHRASE) != 0) { 1748 pkgerr_add(err, PKGERR_WEB, 1749 gettext(ERR_HTTPS_PASSWD)); 1750 ret = WEB_VERIFY_SETUP; 1751 goto cleanup; 1752 } 1753 1754 if (http_set_certificate_authority_file(path) != 0) { 1755 pkgerr_add(err, PKGERR_WEB, 1756 gettext(ERR_HTTPS_CA)); 1757 ret = WEB_VERIFY_SETUP; 1758 goto cleanup; 1759 } 1760 } 1761 1762 if (http_srv_connect(ps->hps) != 0) { 1763 while ((errcode = http_get_lasterr(ps->hps, &errsrc)) != 0) { 1764 /* Have an error - is it EINTR? */ 1765 if (errsrc == ERRSRC_SYSTEM) { 1766 my_errno = errcode; 1767 break; 1768 } else if (libhttperr == NULL) { 1769 /* save the first non-system error message */ 1770 libhttperr = http_errorstr(errsrc, errcode); 1771 } 1772 } 1773 switch (my_errno) { 1774 case EINTR: 1775 case ETIMEDOUT: 1776 /* Timed out. Try, try again */ 1777 ret = WEB_TIMEOUT; 1778 break; 1779 case ECONNREFUSED: 1780 ret = WEB_CONNREFUSED; 1781 break; 1782 case EHOSTDOWN: 1783 ret = WEB_HOSTDOWN; 1784 break; 1785 default: 1786 /* some other fatal error */ 1787 ret = WEB_NOCONNECT; 1788 if (libhttperr == NULL) { 1789 pkgerr_add(err, PKGERR_WEB, 1790 gettext(ERR_INIT_CONN), 1791 ps->url.hport.hostname); 1792 } else { 1793 pkgerr_add(err, PKGERR_WEB, 1794 gettext(ERR_HTTP), libhttperr); 1795 } 1796 break; 1797 } 1798 } 1799 cleanup: 1800 return (ret); 1801 } 1802 1803 /* 1804 * Name: write_ca_file 1805 * Description: Writes out a PKCS12 file containing all trusted certs 1806 * found in keystore recorded in static 'ps' structure 1807 * 1808 * This routine is used because the libwanboot library's 1809 * HTTPS routines cannot accept trusted certificates 1810 * through an in-memory structure, when initiating an 1811 * SSL connection. They must be in a PKCS12, which is 1812 * admittedly a poor interface. 1813 * 1814 * Arguments: err - where to record any errors. 1815 * tmpdir - Directory to write certificate file in 1816 * cacerts - Certs to write out 1817 * passwd - password used to encrypt certs 1818 * 1819 * Returns : path to resulting file, if successfullly written, 1820 * otherwise NULL. 1821 */ 1822 static char 1823 *write_ca_file(PKG_ERR *err, char *tmpdir, STACK_OF(X509) *cacerts, 1824 char *passwd) 1825 { 1826 int fd, len; 1827 FILE *fp; 1828 PKCS12 *p12 = NULL; 1829 char *ret = NULL; 1830 static char tmp_file[PATH_MAX] = ""; 1831 struct stat buf; 1832 1833 if (!path_valid(tmpdir)) { 1834 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); 1835 goto cleanup; 1836 } 1837 1838 /* mkstemp replaces XXXXXX with a unique string */ 1839 if (((len = snprintf(tmp_file, PATH_MAX, "%s/%sXXXXXX", tmpdir, 1840 "cert")) < 0) || 1841 (len >= PATH_MAX)) { 1842 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), tmpdir); 1843 goto cleanup; 1844 } 1845 1846 if ((fd = mkstemp(tmp_file)) == -1) { 1847 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); 1848 goto cleanup; 1849 } 1850 1851 if (fstat(fd, &buf) == -1) { 1852 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); 1853 goto cleanup; 1854 } 1855 1856 if (!S_ISREG(buf.st_mode)) { 1857 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); 1858 goto cleanup; 1859 } 1860 1861 if ((fp = fdopen(fd, "w")) == NULL) { 1862 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTMPFIL), tmp_file); 1863 goto cleanup; 1864 } 1865 1866 if ((p12 = sunw_PKCS12_create(passwd, NULL, NULL, cacerts)) == NULL) { 1867 pkgerr_add(err, PKGERR_WEB, 1868 gettext(ERR_KEYSTORE_FORM), tmp_file); 1869 goto cleanup; 1870 } 1871 1872 if (i2d_PKCS12_fp(fp, p12) == 0) { 1873 pkgerr_add(err, PKGERR_WEB, 1874 gettext(ERR_KEYSTORE_FORM), tmp_file); 1875 goto cleanup; 1876 } 1877 1878 (void) fflush(fp); 1879 (void) fclose(fp); 1880 (void) close(fd); 1881 fp = NULL; 1882 fd = -1; 1883 ret = tmp_file; 1884 1885 cleanup: 1886 if (p12 != NULL) 1887 PKCS12_free(p12); 1888 if (fp != NULL) 1889 (void) fclose(fp); 1890 if (fd != -1) { 1891 (void) close(fd); 1892 (void) unlink(tmp_file); 1893 } 1894 1895 return (ret); 1896 } 1897 1898 /* 1899 * Name: web_send_request 1900 * Description: Sends an HTTP request for a file to the 1901 * web server being communicated with in the static 1902 * 'ps' structure 1903 * 1904 * Arguments: err - where to record any errors. 1905 * request_type - HTTP_REQ_TYPE_HEAD to send an HTTP HEAD request, 1906 * or HTTP_REQ_TYPE_GET to send an HTTP GET request 1907 * cp - 1908 * Returns : WEB_OK - request sent successfully 1909 * WEB_CONNREFUSED - Connection was refused to web site 1910 * WEB_HOSTDOWN - Host was not responding to request 1911 * WEB_NOCONNECT - Some other connection failure 1912 */ 1913 static WebStatus 1914 web_send_request(PKG_ERR *err, int request_type, int cp, int ep) 1915 { 1916 WebStatus ret = WEB_OK; 1917 ulong_t errcode; 1918 uint_t errsrc; 1919 int my_errno = 0; 1920 const char *libhttperr = NULL; 1921 switch (request_type) { 1922 case HTTP_REQ_TYPE_HEAD: 1923 if ((http_head_request(ps->hps, ps->url.abspath)) != 0) { 1924 while ((errcode = http_get_lasterr(ps->hps, 1925 &errsrc)) != 0) { 1926 /* Have an error - is it EINTR? */ 1927 if (errsrc == ERRSRC_SYSTEM) { 1928 my_errno = errcode; 1929 break; 1930 } else if (libhttperr == NULL) { 1931 /* save first non-system error message */ 1932 libhttperr = 1933 http_errorstr(errsrc, errcode); 1934 } 1935 } 1936 switch (my_errno) { 1937 case EINTR: 1938 case ETIMEDOUT: 1939 /* Timed out. Try, try again */ 1940 ret = WEB_TIMEOUT; 1941 break; 1942 case ECONNREFUSED: 1943 ret = WEB_CONNREFUSED; 1944 break; 1945 case EHOSTDOWN: 1946 ret = WEB_HOSTDOWN; 1947 break; 1948 default: 1949 /* some other fatal error */ 1950 ret = WEB_NOCONNECT; 1951 if (libhttperr == NULL) { 1952 pkgerr_add(err, PKGERR_WEB, 1953 gettext(ERR_INIT_CONN), 1954 ps->url.hport.hostname); 1955 } else { 1956 pkgerr_add(err, PKGERR_WEB, 1957 gettext(ERR_HTTP), libhttperr); 1958 } 1959 break; 1960 } 1961 goto cleanup; 1962 } 1963 break; 1964 1965 case HTTP_REQ_TYPE_GET: 1966 if (cp && ep) { 1967 if (http_get_range_request(ps->hps, ps->url.abspath, 1968 cp, ep - cp) != 0) { 1969 while ((errcode = http_get_lasterr(ps->hps, 1970 &errsrc)) != 0) { 1971 /* Have an error - is it EINTR? */ 1972 if (errsrc == ERRSRC_SYSTEM) { 1973 my_errno = errcode; 1974 break; 1975 } else { 1976 /* 1977 * save first non-system 1978 * error message 1979 */ 1980 libhttperr = 1981 http_errorstr(errsrc, 1982 errcode); 1983 } 1984 } 1985 switch (my_errno) { 1986 case EINTR: 1987 case ETIMEDOUT: 1988 /* Timed out. Try, try again */ 1989 ret = WEB_TIMEOUT; 1990 break; 1991 case ECONNREFUSED: 1992 ret = WEB_CONNREFUSED; 1993 break; 1994 case EHOSTDOWN: 1995 ret = WEB_HOSTDOWN; 1996 break; 1997 default: 1998 /* some other fatal error */ 1999 ret = WEB_NOCONNECT; 2000 if (libhttperr == NULL) { 2001 pkgerr_add(err, PKGERR_WEB, 2002 gettext(ERR_INIT_CONN), 2003 ps->url.hport.hostname); 2004 } else { 2005 pkgerr_add(err, PKGERR_WEB, 2006 gettext(ERR_HTTP), 2007 libhttperr); 2008 } 2009 break; 2010 } 2011 goto cleanup; 2012 } 2013 2014 if (!web_eval_headers(err)) { 2015 ret = WEB_NOCONNECT; 2016 goto cleanup; 2017 } 2018 } else { 2019 if ((http_get_request(ps->hps, ps->url.abspath)) 2020 != 0) { 2021 while ((errcode = http_get_lasterr(ps->hps, 2022 &errsrc)) != 0) { 2023 /* Have an error - is it EINTR? */ 2024 if (errsrc == ERRSRC_SYSTEM) { 2025 my_errno = errcode; 2026 break; 2027 } else { 2028 /* 2029 * save the first non-system 2030 * error message 2031 */ 2032 libhttperr = 2033 http_errorstr(errsrc, 2034 errcode); 2035 } 2036 } 2037 switch (my_errno) { 2038 case EINTR: 2039 case ETIMEDOUT: 2040 /* Timed out. Try, try again */ 2041 ret = WEB_TIMEOUT; 2042 break; 2043 case ECONNREFUSED: 2044 ret = WEB_CONNREFUSED; 2045 break; 2046 case EHOSTDOWN: 2047 ret = WEB_HOSTDOWN; 2048 break; 2049 default: 2050 /* some other fatal error */ 2051 ret = WEB_NOCONNECT; 2052 if (libhttperr == NULL) { 2053 pkgerr_add(err, PKGERR_WEB, 2054 gettext(ERR_INIT_CONN), 2055 ps->url.hport.hostname); 2056 } else { 2057 pkgerr_add(err, PKGERR_WEB, 2058 gettext(ERR_HTTP), 2059 libhttperr); 2060 } 2061 break; 2062 } 2063 goto cleanup; 2064 } 2065 2066 if (!web_eval_headers(err)) { 2067 ret = WEB_NOCONNECT; 2068 goto cleanup; 2069 } 2070 } 2071 break; 2072 default: 2073 pkgerr_add(err, PKGERR_INTERNAL, gettext(ERR_PKG_INTERNAL), 2074 __FILE__, __LINE__); 2075 } 2076 2077 cleanup: 2078 return (ret); 2079 } 2080 2081 /* 2082 * Name: web_eval_headers 2083 * Description: Evaluates HTTP headers returned during an HTTP request. 2084 * This must be called before calling 2085 * http_get_header_value(). 2086 * 2087 * Arguments: err - where to record any errors. 2088 * 2089 * Returns : B_TRUE - success, B_FALSE otherwise 2090 */ 2091 static boolean_t 2092 web_eval_headers(PKG_ERR *err) 2093 { 2094 const char *http_err; 2095 ulong_t herr; 2096 uint_t errsrc; 2097 2098 if (http_process_headers(ps->hps, &ps->resp) != 0) { 2099 if ((ps->resp != NULL) && (ps->resp->statusmsg != NULL)) { 2100 pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), 2101 ps->resp->statusmsg); 2102 } 2103 2104 herr = http_get_lasterr(ps->hps, &errsrc); 2105 http_err = http_errorstr(errsrc, herr); 2106 pkgerr_add(err, PKGERR_WEB, gettext(ERR_HTTP), 2107 http_err); 2108 return (B_FALSE); 2109 } 2110 return (B_TRUE); 2111 } 2112 2113 /* 2114 * Name: web_get_file 2115 * Description: Downloads the file URL from the website, all of 2116 * which are recorded in the static 'ps' struct 2117 * 2118 * Arguments: err - where to record any errors. 2119 * dwnld_dir - Directory to download file into 2120 * device - Where to store path to resulting 2121 * file 2122 * nointeract - if non-zero, do not output 2123 * progress 2124 * fname - name of downloaded file link in the dwnld_dir 2125 * 2126 * Returns : WEB_OK - download successful 2127 * WEB_CONNREFUSED - Connection was refused to web site 2128 * WEB_HOSTDOWN - Host was not responding to request 2129 * WEB_GET_FAIL - Unable to initialize download 2130 * state (temp file creation, header parsing, etc) 2131 * WEB_NOCONNECT - Some other connection failure 2132 */ 2133 static WebStatus 2134 web_get_file(PKG_ERR *err, char *dwnld_dir, int nointeract, char **fname) 2135 { 2136 int i, fd; 2137 int n = 0; 2138 ulong_t abs_pos = 0; 2139 char *head_val = NULL; 2140 char *lastmod_val = NULL; 2141 char *bname = NULL; 2142 struct stat status; 2143 WebStatus ret = WEB_OK; 2144 WebStatus req_ret; 2145 ulong_t errcode; 2146 uint_t errsrc; 2147 int my_errno = 0; 2148 const char *libhttperr = NULL; 2149 char *disp; 2150 char tmp_file[PATH_MAX]; 2151 int len; 2152 2153 ps->data.prev_cont_length = 2154 ps->data.content_length = 2155 ps->data.cur_pos = 0; 2156 2157 if ((head_val = http_get_header_value(ps->hps, 2158 CONTENT_LENGTH_HDR)) != NULL) { 2159 ps->data.content_length = atol(head_val); 2160 } else { 2161 pkgerr_add(err, PKGERR_WEB, gettext(ERR_NO_HEAD_VAL), 2162 CONTENT_LENGTH_HDR); 2163 ret = WEB_GET_FAIL; 2164 goto cleanup; 2165 } 2166 2167 free(head_val); 2168 head_val = NULL; 2169 2170 if ((head_val = http_get_header_value(ps->hps, 2171 CONTENT_DISPOSITION_HDR)) != NULL) { 2172 /* "inline; parm=val; parm=val */ 2173 if ((disp = strtok(head_val, "; \t\n\f\r")) != NULL) { 2174 /* disp = "inline" */ 2175 while ((disp = strtok(NULL, "; \t\n\f\r")) != NULL) { 2176 /* disp = "parm=val" */ 2177 if (ci_strneq(disp, "filename=", 9)) { 2178 bname = xstrdup(basename(disp + 9)); 2179 trim(bname); 2180 dequote(bname); 2181 } 2182 } 2183 } 2184 free(head_val); 2185 head_val = NULL; 2186 } 2187 2188 if (bname == NULL) { 2189 /* 2190 * couldn't determine filename from header value, 2191 * so take basename of URL 2192 */ 2193 if ((bname = get_endof_string(ps->url.abspath, '/')) == NULL) { 2194 /* URL is bad */ 2195 pkgerr_add(err, PKGERR_PARSE, 2196 gettext(ERR_PARSE_URL), ps->url.abspath); 2197 ret = WEB_GET_FAIL; 2198 goto cleanup; 2199 } 2200 } 2201 2202 *fname = bname; 2203 2204 if ((head_val = http_get_header_value(ps->hps, LAST_MODIFIED_HDR)) 2205 != NULL) { 2206 2207 if ((lastmod_val = condense_lastmodified(head_val)) == NULL) { 2208 pkgerr_add(err, PKGERR_WEB, gettext(ERR_BAD_HEAD_VAL), 2209 LAST_MODIFIED_HDR, head_val); 2210 ret = WEB_GET_FAIL; 2211 goto cleanup; 2212 } 2213 free(head_val); 2214 head_val = NULL; 2215 2216 if ((ps->uniqfile = get_unique_filename(dwnld_dir, 2217 lastmod_val)) == NULL) { 2218 pkgerr_add(err, PKGERR_WEB, gettext(ERR_OPEN_TMP)); 2219 ret = WEB_GET_FAIL; 2220 goto cleanup; 2221 } 2222 2223 free(lastmod_val); 2224 lastmod_val = NULL; 2225 2226 if ((fd = open(ps->uniqfile, 2227 O_NONBLOCK|O_RDWR|O_APPEND|O_CREAT|O_EXCL, 2228 640)) == -1) { 2229 2230 /* 2231 * A partial downloaded file 2232 * already exists, so open it. 2233 */ 2234 if ((fd = open(ps->uniqfile, 2235 O_NONBLOCK|O_RDWR|O_APPEND)) != -1) { 2236 if (fstat(fd, &status) == -1 || 2237 !S_ISREG(status.st_mode)) { 2238 pkgerr_add(err, PKGERR_WEB, 2239 gettext(ERR_DWNLD_NO_CONT), 2240 ps->uniqfile); 2241 ret = WEB_GET_FAIL; 2242 goto cleanup; 2243 } else { 2244 echo_out(nointeract, 2245 gettext(MSG_DWNLD_PART), 2246 ps->uniqfile, 2247 status.st_size); 2248 ps->data.prev_cont_length = 2249 status.st_size; 2250 } 2251 } else { 2252 /* unable to open partial file */ 2253 pkgerr_add(err, PKGERR_WEB, 2254 gettext(ERR_DWNLD_NO_CONT), 2255 ps->uniqfile); 2256 ret = WEB_GET_FAIL; 2257 goto cleanup; 2258 } 2259 } 2260 } else { 2261 /* 2262 * no "Last-Modified" header, so this file is not eligible for 2263 * spooling and "resuming last download" operations 2264 */ 2265 ps->spool = B_FALSE; 2266 2267 /* mkstemp replaces XXXXXX with a unique string */ 2268 if (((len = snprintf(tmp_file, PATH_MAX, 2269 "%s/%sXXXXXX", dwnld_dir, "stream")) < 0) || 2270 (len >= PATH_MAX)) { 2271 pkgerr_add(err, PKGERR_WEB, 2272 gettext(MSG_NOTEMP), dwnld_dir); 2273 ret = WEB_GET_FAIL; 2274 goto cleanup; 2275 } 2276 2277 if ((fd = mkstemp(tmp_file)) == -1) { 2278 pkgerr_add(err, PKGERR_WEB, 2279 gettext(MSG_NOTMPFIL), tmp_file); 2280 ret = WEB_GET_FAIL; 2281 goto cleanup; 2282 } 2283 2284 if (fstat(fd, &status) == -1 || 2285 !S_ISREG(status.st_mode)) { 2286 pkgerr_add(err, PKGERR_WEB, 2287 gettext(ERR_DWNLD_NO_CONT), 2288 ps->uniqfile); 2289 ret = WEB_GET_FAIL; 2290 goto cleanup; 2291 } 2292 2293 ps->data.prev_cont_length = 0; 2294 ps->uniqfile = xstrdup(tmp_file); 2295 } 2296 2297 /* File has already been completely downloaded */ 2298 if (ps->data.prev_cont_length == ps->data.content_length) { 2299 echo_out(nointeract, gettext(MSG_DWNLD_PREV), ps->uniqfile); 2300 ps->data.cur_pos = ps->data.prev_cont_length; 2301 if (!make_link(dwnld_dir, bname)) { 2302 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), 2303 dwnld_dir); 2304 ret = WEB_GET_FAIL; 2305 goto cleanup; 2306 } 2307 /* we're done, so cleanup and return success */ 2308 goto cleanup; 2309 } else if (ps->data.prev_cont_length != 0) { 2310 ps->data.cur_pos = ps->data.prev_cont_length; 2311 } 2312 2313 if (!ck_dwnld_dir_space(err, dwnld_dir, 2314 (ps->data.prev_cont_length != 0) ? 2315 (ps->data.content_length - ps->data.cur_pos) : 2316 ps->data.content_length)) { 2317 ret = WEB_GET_FAIL; 2318 goto cleanup; 2319 } 2320 2321 if ((req_ret = web_send_request(err, HTTP_REQ_TYPE_GET, 2322 ps->data.cur_pos, ps->data.content_length)) != WEB_OK) { 2323 ret = req_ret; 2324 goto cleanup; 2325 } 2326 2327 if (ps->data.prev_cont_length != 0) 2328 echo_out(nointeract, gettext(MSG_DWNLD_CONT)); 2329 else 2330 echo_out(nointeract, gettext(MSG_DWNLD)); 2331 2332 progress_setup(nointeract, ps->data.content_length); 2333 2334 /* Download the file a BLOCK at a time */ 2335 while (ps->data.cur_pos < ps->data.content_length) { 2336 progress_report(nointeract, abs_pos); 2337 i = ((ps->data.content_length - ps->data.cur_pos) < BLOCK) ? 2338 (ps->data.content_length - ps->data.cur_pos) 2339 : BLOCK; 2340 if ((n = http_read_body(ps->hps, ps->content, i)) <= 0) { 2341 while ((errcode = http_get_lasterr(ps->hps, 2342 &errsrc)) != 0) { 2343 /* Have an error - is it EINTR? */ 2344 if (errsrc == ERRSRC_SYSTEM) { 2345 my_errno = errcode; 2346 break; 2347 } else { 2348 /* 2349 * save first non-system 2350 * error message 2351 */ 2352 libhttperr = 2353 http_errorstr(errsrc, errcode); 2354 } 2355 } 2356 switch (my_errno) { 2357 case EINTR: 2358 case ETIMEDOUT: 2359 /* Timed out. Try, try again */ 2360 ret = WEB_TIMEOUT; 2361 break; 2362 case ECONNREFUSED: 2363 ret = WEB_CONNREFUSED; 2364 break; 2365 case EHOSTDOWN: 2366 ret = WEB_HOSTDOWN; 2367 break; 2368 default: 2369 /* some other fatal error */ 2370 ret = WEB_NOCONNECT; 2371 if (libhttperr == NULL) { 2372 pkgerr_add(err, PKGERR_WEB, 2373 gettext(ERR_INIT_CONN), 2374 ps->url.hport.hostname); 2375 } else { 2376 pkgerr_add(err, PKGERR_WEB, 2377 gettext(ERR_HTTP), libhttperr); 2378 } 2379 break; 2380 } 2381 goto cleanup; 2382 } 2383 if ((n = write(fd, ps->content, n)) == 0) { 2384 pkgerr_add(err, PKGERR_WEB, gettext(ERR_WRITE), 2385 ps->uniqfile, strerror(errno)); 2386 ret = WEB_GET_FAIL; 2387 goto cleanup; 2388 } 2389 ps->data.cur_pos += n; 2390 abs_pos += n; 2391 } 2392 2393 progress_finish(nointeract); 2394 echo_out(nointeract, gettext(MSG_DWNLD_COMPLETE)); 2395 2396 if (!make_link(dwnld_dir, bname)) { 2397 pkgerr_add(err, PKGERR_WEB, gettext(MSG_NOTEMP), 2398 dwnld_dir); 2399 ret = WEB_GET_FAIL; 2400 goto cleanup; 2401 } 2402 2403 cleanup: 2404 sync(); 2405 if (fd != -1) { 2406 (void) close(fd); 2407 } 2408 2409 if (head_val != NULL) 2410 free(head_val); 2411 2412 if (lastmod_val != NULL) 2413 free(lastmod_val); 2414 2415 return (ret); 2416 } 2417 2418 /* 2419 * Name: make_link 2420 * Description: Create new link to file being downloaded 2421 * 2422 * Arguments: dwnld_dir - directory in which downloaded file exists 2423 * bname - name of link 2424 * 2425 * Returns : B_TRUE - success, B_FALSE otherwise 2426 */ 2427 static boolean_t 2428 make_link(char *dwnld_dir, char *bname) 2429 { 2430 int len; 2431 2432 if ((ps->link = (char *)xmalloc(PATH_MAX)) == NULL) 2433 return (B_FALSE); 2434 if (((len = snprintf(ps->link, PATH_MAX, "%s/%s", 2435 dwnld_dir, bname)) < 0) || 2436 len >= PATH_MAX) 2437 return (B_FALSE); 2438 2439 (void) link(ps->uniqfile, ps->link); 2440 2441 return (B_TRUE); 2442 } 2443 2444 /* 2445 * Name: get_startof_string 2446 * Description: searches string for token, returns a newly-allocated 2447 * substring of the given string up to, but not 2448 * including, token. for example 2449 * get_startof_string("abcd", 'c') will return "ab" 2450 * 2451 * Arguments: path - path to split 2452 * token - character to split on 2453 * 2454 * Returns : substring of 'path', up to, but not including, 2455 * token, if token appears in path. Otherwise, 2456 * returns NULL. 2457 */ 2458 char * 2459 get_startof_string(char *path, char token) 2460 { 2461 char *p, *p2; 2462 2463 if (path == NULL) 2464 return (NULL); 2465 2466 p = xstrdup(path); 2467 2468 p2 = strchr(p, token); 2469 if (p2 == NULL) { 2470 free(p); 2471 return (NULL); 2472 } else { 2473 *p2 = '\0'; 2474 return (p); 2475 } 2476 } 2477 2478 /* 2479 * Name: get_endof_string 2480 * Description: searches string for token, returns a 2481 * newly-allocated substring of the given string, 2482 * starting at character following token, to end of 2483 * string. 2484 * 2485 * for example get_end_string("abcd", 'c') 2486 * will return "d" 2487 * 2488 * Arguments: path - path to split 2489 * token - character to split on 2490 * 2491 * Returns : substring of 'path', beginning at character 2492 * following token, to end of string, if 2493 * token appears in path. Otherwise, 2494 * returns NULL. 2495 */ 2496 char * 2497 get_endof_string(char *path, char token) 2498 { 2499 char *p, *p2; 2500 2501 if (path == NULL) 2502 return (NULL); 2503 2504 p = xstrdup(path); 2505 2506 if ((p2 = strrchr(p, token)) == NULL) { 2507 return (NULL); 2508 } 2509 2510 return (p2 + 1); 2511 } 2512 2513 /* 2514 * Name: progress_setup 2515 * Description: Initialize session for reporting progress 2516 * 2517 * Arguments: nointeract - if non-zero, do not do anything 2518 * ulong_t - size of job to report progress for 2519 * 2520 * Returns : none 2521 */ 2522 static void 2523 progress_setup(int nointeract, ulong_t size_of_load) 2524 { 2525 ulong_t divisor; 2526 ulong_t term_width = TERM_WIDTH; 2527 2528 if (nointeract) 2529 return; 2530 2531 if (size_of_load > MED_DWNLD && size_of_load < LARGE_DWNLD) 2532 divisor = MED_DIVISOR; 2533 else if (size_of_load > LARGE_DWNLD) { 2534 term_width = TERM_WIDTH - 8; 2535 divisor = LARGE_DIVISOR; 2536 } else 2537 divisor = SMALL_DIVISOR; 2538 2539 const_increment = size_of_load / term_width; 2540 const_divider = size_of_load / divisor; 2541 const_completed = 100 / divisor; 2542 } 2543 2544 /* 2545 * Name: progress_report 2546 * Description: Report progress for current progress context, 2547 * to stderr 2548 * 2549 * Arguments: nointeract - if non-zero, do not do anything 2550 * position - how far along in the job to report. 2551 * This should be <= size used during progress_setup 2552 * 2553 * Returns : none 2554 */ 2555 static void 2556 progress_report(int nointeract, ulong_t position) 2557 { 2558 static ulong_t increment; 2559 static ulong_t divider; 2560 2561 if (nointeract) 2562 return; 2563 2564 if (position == 0) { 2565 increment = const_increment; 2566 divider = const_divider; 2567 } 2568 if (position > increment && position < divider) { 2569 (void) putc('.', stderr); 2570 increment += const_increment; 2571 } else if (position > divider) { 2572 completed += const_completed; 2573 (void) fprintf(stderr, "%ld%c", completed, '%'); 2574 increment += const_increment; 2575 divider += const_divider; 2576 } 2577 } 2578 2579 /* 2580 * Name: progress_finish 2581 * Description: Finalize session for reporting progress. 2582 * "100%" is reported to screen 2583 * 2584 * Arguments: nointeract - if non-zero, do not do anything 2585 * 2586 * Returns : none 2587 */ 2588 static void 2589 progress_finish(int nointeract) 2590 { 2591 if (nointeract) 2592 return; 2593 2594 (void) fprintf(stderr, "%d%c\n", 100, '%'); 2595 } 2596 2597 /* 2598 * Name: init_session 2599 * Description: Initializes static 'ps' structure with default 2600 * values 2601 * 2602 * Arguments: none 2603 * 2604 * Returns : B_TRUE - success, B_FALSE otherwise 2605 */ 2606 static boolean_t 2607 init_session(void) 2608 { 2609 if ((ps = (WEB_SESSION *) 2610 xmalloc(sizeof (WEB_SESSION))) == NULL) { 2611 return (B_FALSE); 2612 } 2613 (void) memset(ps, 0, sizeof (*ps)); 2614 2615 if ((ps->content = (char *)xmalloc(BLOCK)) == NULL) { 2616 return (B_FALSE); 2617 } 2618 2619 (void) memset(ps->content, 0, BLOCK); 2620 2621 ps->data.cur_pos = 0UL; 2622 ps->data.content_length = 0UL; 2623 ps->url.https = B_FALSE; 2624 ps->uniqfile = NULL; 2625 ps->link = NULL; 2626 ps->dwnld_dir = NULL; 2627 ps->spool = B_TRUE; 2628 ps->errstr = NULL; 2629 ps->keystore = NULL; 2630 2631 return (B_TRUE); 2632 } 2633 2634 /* 2635 * Name: ck_downld_dir_space 2636 * Description: Verify enough space exists in directory to hold file 2637 * 2638 * Arguments: err - where to record any errors. 2639 * dwnld_dir - Directory to check available space in 2640 * bytes_needed - How many bytes are need 2641 * 2642 * Returns : B_TRUE - enough space exists in dwnld_dir to hold 2643 * bytes_needed bytes, otherwise B_FALSE 2644 */ 2645 static boolean_t 2646 ck_dwnld_dir_space(PKG_ERR *err, char *dwnld_dir, ulong_t bytes_needed) 2647 { 2648 u_longlong_t bytes_avail; 2649 u_longlong_t block_pad; 2650 struct statvfs64 status; 2651 2652 if (statvfs64(dwnld_dir, &status)) { 2653 pkgerr_add(err, PKGERR_WEB, gettext(ERR_TMPDIR), dwnld_dir); 2654 return (B_FALSE); 2655 } 2656 2657 block_pad = (status.f_frsize ? status.f_frsize : status.f_bsize); 2658 bytes_avail = status.f_bavail * block_pad; 2659 2660 if ((((u_longlong_t)bytes_needed) + block_pad) > bytes_avail) { 2661 pkgerr_add(err, PKGERR_WEB, gettext(ERR_DISK_SPACE), 2662 dwnld_dir, 2663 (((u_longlong_t)bytes_needed) + block_pad) / 1024ULL, 2664 bytes_avail / 1024ULL); 2665 return (B_FALSE); 2666 } 2667 2668 return (B_TRUE); 2669 } 2670 2671 /* 2672 * Description: 2673 * This function returns a unique file name based on the parts of the 2674 * URI. This is done to enable partially downloaded files to be resumed. 2675 * Arguments: 2676 * dir - The directory that should contain the filename. 2677 * last_modified - A string representing the date of last modification, 2678 * used as part of generating unique name 2679 * Returns: 2680 * A valid filename or NULL. 2681 */ 2682 2683 static char * 2684 get_unique_filename(char *dir, char *last_modified) 2685 { 2686 char *buf, *buf2, *beg_str; 2687 int len; 2688 2689 if ((buf = (char *)xmalloc(PATH_MAX)) == NULL) { 2690 return (NULL); 2691 } 2692 if ((buf2 = (char *)xmalloc(PATH_MAX)) == NULL) { 2693 return (NULL); 2694 } 2695 2696 /* prepare strings for being cat'ed onto */ 2697 buf[0] = buf2[0] = '\0'; 2698 /* 2699 * No validation of the path is done here. We just construct the path 2700 * and it must be validated later 2701 */ 2702 2703 if (dir) { 2704 if (((len = snprintf(buf2, PATH_MAX, "%s/", dir)) < 0) || 2705 (len >= PATH_MAX)) 2706 return (NULL); 2707 } else { 2708 return (NULL); 2709 } 2710 2711 if (ps->url.abspath) 2712 if (strlcat(buf, ps->url.abspath, PATH_MAX) >= PATH_MAX) 2713 return (NULL); 2714 if (ps->url.hport.hostname) 2715 if (isdigit((int)ps->url.hport.hostname[0])) { 2716 if (strlcat(buf, ps->url.hport.hostname, PATH_MAX) 2717 >= PATH_MAX) 2718 return (NULL); 2719 } else { 2720 if ((beg_str = 2721 get_startof_string(ps->url.hport.hostname, '.')) 2722 != NULL) 2723 if (strlcat(buf, beg_str, PATH_MAX) >= PATH_MAX) 2724 return (NULL); 2725 } 2726 if (last_modified != NULL) 2727 if (strlcat(buf, last_modified, PATH_MAX) >= PATH_MAX) 2728 return (NULL); 2729 2730 if ((buf = replace_token(buf, '/', '_')) != NULL) { 2731 if (strlcat(buf2, buf, PATH_MAX) >= PATH_MAX) { 2732 return (NULL); 2733 } else { 2734 if (buf) free(buf); 2735 return (buf2); 2736 } 2737 } else { 2738 if (buf) free(buf); 2739 if (buf2) free(buf2); 2740 return (NULL); 2741 } 2742 } 2743 2744 /* 2745 * Description: 2746 * Removes token(s) consisting of one character from any path. 2747 * Arguments: 2748 * path - The path to search for the token in. 2749 * token - The token to search for 2750 * Returns: 2751 * The path with all tokens removed or NULL. 2752 */ 2753 static char * 2754 replace_token(char *path, char oldtoken, char newtoken) 2755 { 2756 char *newpath, *p; 2757 2758 if ((path == NULL) || (oldtoken == '\0') || (newtoken == '\0')) { 2759 return (NULL); 2760 } 2761 2762 newpath = xstrdup(path); 2763 2764 for (p = newpath; *p != '\0'; p++) { 2765 if (*p == oldtoken) { 2766 *p = newtoken; 2767 } 2768 } 2769 2770 return (newpath); 2771 } 2772 2773 /* 2774 * Name: trim 2775 * Description: Trims whitespace from a string 2776 * has been registered) 2777 * Scope: private 2778 * Arguments: string - string to trim. It is assumed 2779 * this string is writable up to it's entire 2780 * length. 2781 * Returns: none 2782 */ 2783 static void 2784 trim(char *str) 2785 { 2786 int len, i; 2787 if (str == NULL) { 2788 return; 2789 } 2790 2791 len = strlen(str); 2792 /* strip from front */ 2793 while (isspace(*str)) { 2794 for (i = 0; i < len; i++) { 2795 str[i] = str[i+1]; 2796 } 2797 } 2798 2799 /* strip from back */ 2800 len = strlen(str); 2801 while (isspace(str[len-1])) { 2802 len--; 2803 } 2804 str[len] = '\0'; 2805 } 2806 2807 /* 2808 * Description: 2809 * Resolves double quotes 2810 * Arguments: 2811 * str - The string to resolve 2812 * Returns: 2813 * None 2814 */ 2815 static void 2816 dequote(char *str) 2817 { 2818 char *cp; 2819 2820 if ((str == NULL) || (str[0] != '"')) { 2821 /* no quotes */ 2822 return; 2823 } 2824 2825 /* remove first quote */ 2826 memmove(str, str + 1, strlen(str) - 1); 2827 2828 /* 2829 * scan string looking for ending quote. 2830 * escaped quotes like \" don't count 2831 */ 2832 cp = str; 2833 2834 while (*cp != '\0') { 2835 switch (*cp) { 2836 case '\\': 2837 /* found an escaped character */ 2838 /* make sure end of string is not '\' */ 2839 if (*++cp != '\0') { 2840 cp++; 2841 } 2842 break; 2843 2844 case '"': 2845 *cp = '\0'; 2846 break; 2847 default: 2848 cp++; 2849 } 2850 } 2851 } 2852 2853 /* 2854 * Name: get_ENV_proxy 2855 * Description: Retrieves setting of proxy env variable 2856 * 2857 * Arguments: err - where to record any errors. 2858 * proxy - where to store proxy 2859 * 2860 * Returns : B_TRUE - http proxy was found and valid, stored in proxy 2861 * B_FALSE - error, errors recorded in err 2862 */ 2863 static boolean_t 2864 get_ENV_proxy(PKG_ERR *err, char **proxy) 2865 { 2866 char *buf; 2867 2868 if ((buf = getenv("HTTPPROXY")) != NULL) { 2869 if (!path_valid(buf)) { 2870 pkgerr_add(err, PKGERR_WEB, 2871 gettext(ERR_ILL_ENV), "HTTPPROXY", buf); 2872 return (B_FALSE); 2873 } else { 2874 *proxy = buf; 2875 return (B_TRUE); 2876 } 2877 } else { 2878 /* try the other env variable */ 2879 if ((buf = getenv("http_proxy")) != NULL) { 2880 if (!path_valid(buf)) { 2881 pkgerr_add(err, PKGERR_WEB, 2882 gettext(ERR_ILL_ENV), "http_proxy", buf); 2883 return (B_FALSE); 2884 } 2885 if (!strneq(buf, "http://", 7)) { 2886 pkgerr_add(err, PKGERR_WEB, 2887 gettext(ERR_ILL_ENV), "http_proxy", buf); 2888 return (B_FALSE); 2889 } 2890 2891 /* skip over the http:// part of the proxy "url" */ 2892 *proxy = buf + 7; 2893 return (B_TRUE); 2894 } 2895 } 2896 2897 /* either the env variable(s) were set and valid, or not set */ 2898 return (B_TRUE); 2899 } 2900 2901 /* 2902 * Name: get_ENV_proxyport 2903 * Description: Retrieves setting of PROXYPORT env variable 2904 * 2905 * Arguments: err - where to record any errors. 2906 * port - where to store resulting port 2907 * 2908 * Returns : B_TRUE - string found in PROXYPORT variable, converted 2909 * to decimal integer, if it exists 2910 * and is valid. Or, PROXYPORT not set, port set to 1. 2911 * B_FALSE - env variable set, but invalid 2912 * (not a number for example) 2913 */ 2914 static boolean_t 2915 get_ENV_proxyport(PKG_ERR *err, ushort_t *port) 2916 { 2917 char *buf; 2918 ushort_t newport; 2919 buf = getenv("HTTPPROXYPORT"); 2920 if (buf != NULL) { 2921 if (!path_valid(buf)) { 2922 pkgerr_add(err, PKGERR_WEB, 2923 gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); 2924 return (B_FALSE); 2925 } 2926 if ((newport = atoi(buf)) == 0) { 2927 pkgerr_add(err, PKGERR_WEB, 2928 gettext(ERR_ILL_ENV), "HTTPPROXYPORT", buf); 2929 return (B_FALSE); 2930 } 2931 *port = newport; 2932 return (B_TRUE); 2933 } else { 2934 *port = 1; 2935 return (B_TRUE); 2936 } 2937 } 2938 2939 /* 2940 * Name: remove_dwnld_file 2941 * Description: Removes newly-downloaded file if completely downloaded. 2942 * 2943 * Arguments: path - path to file to remove 2944 * 2945 * Returns : B_TRUE - success, B_FALSE otherwise 2946 * if it's '0' (not OK) we simply return it, since the 2947 * verification operation has already determined that the 2948 * cert is invalid. if 'ok' is non-zero, then we do our 2949 * checks, and return 0 or 1 based on if the cert is 2950 * invalid or valid. 2951 */ 2952 static boolean_t 2953 remove_dwnld_file(char *path) 2954 { 2955 if (path && path != NULL) { 2956 /* 2957 * Only remove the downloaded file if it has been completely 2958 * downloaded, or is not eligible for spooling 2959 */ 2960 if ((!ps->spool) || 2961 (ps->data.cur_pos >= ps->data.content_length)) { 2962 (void) unlink(path); 2963 } 2964 } else { 2965 return (B_FALSE); 2966 } 2967 return (B_TRUE); 2968 } 2969 2970 /* 2971 * Name: condense_lastmodifided 2972 * Description: generates a substring of a last-modified string, 2973 * and removes colons. 2974 * 2975 * Arguments: last_modified - string of the form 2976 * "Wed, 23 Oct 2002 21:59:45 GMT" 2977 * 2978 * Returns : 2979 * new string, consisting of hours/minutes/seconds only, 2980 * sans any colons. 2981 */ 2982 char * 2983 condense_lastmodified(char *last_modified) 2984 { 2985 char *p, *p2; 2986 2987 /* 2988 * Last-Modified: Wed, 23 Oct 2002 21:59:45 GMT 2989 * Strip the hours, minutes and seconds, without the ':'s, from 2990 * the above string, void of the ':". 2991 */ 2992 2993 if (last_modified == NULL) 2994 return (NULL); 2995 2996 if ((p = xstrdup(last_modified)) == NULL) 2997 return (NULL); 2998 p2 = (strstr(p, ":") - 2); 2999 p2[8] = '\0'; 3000 return (replace_token(p2, ':', '_')); 3001 } 3002 3003 /* 3004 * Name: backoff 3005 * Description: sleeps for a certain # of seconds after a network 3006 * failure. 3007 * Scope: public 3008 * Arguments: none 3009 * Returns: none 3010 */ 3011 void 3012 backoff() 3013 { 3014 static boolean_t initted = B_FALSE; 3015 int backoff; 3016 long seed; 3017 3018 if (!initted) { 3019 /* seed the rng */ 3020 (void) _get_random_info(&seed, sizeof (seed)); 3021 srand48(seed); 3022 initted = B_TRUE; 3023 } 3024 3025 backoff = drand48() * (double)cur_backoff; 3026 (void) sleep(backoff); 3027 if (cur_backoff < MAX_BACKOFF) { 3028 /* 3029 * increase maximum time we might wait 3030 * next time so as to fall off over 3031 * time. 3032 */ 3033 cur_backoff *= BACKOFF_FACTOR; 3034 } 3035 } 3036 3037 /* 3038 * Name: reset_backoff 3039 * Description: notifies the backoff service that whatever was 3040 * being backoff succeeded. 3041 * Scope: public 3042 * Arguments: none 3043 * Returns: none 3044 */ 3045 void 3046 reset_backoff() 3047 { 3048 cur_backoff = MIN_BACKOFF; 3049 } 3050 3051 /* 3052 * Name: _get_random_info 3053 * Description: generate an amount of random bits. Currently 3054 * only a small amount (a long long) can be 3055 * generated at one time. 3056 * Scope: private 3057 * Arguments: buf - [RO, *RW] (char *) 3058 * Buffer to copy bits into 3059 * size - amount to copy 3060 * Returns: B_TRUE on success, B_FALSE otherwise. The buffer is filled 3061 * with the amount of bytes of random data specified. 3062 */ 3063 static boolean_t 3064 _get_random_info(void *buf, int size) 3065 { 3066 struct timeval tv; 3067 typedef struct { 3068 long low_time; 3069 long hostid; 3070 } randomness; 3071 randomness r; 3072 3073 /* if the RANDOM file exists, use it */ 3074 if (access(RANDOM, R_OK) == 0) { 3075 if ((RAND_load_file(RANDOM, 1024 * 1024)) > 0) { 3076 if (RAND_bytes((uchar_t *)buf, size) == 1) { 3077 /* success */ 3078 return (B_TRUE); 3079 } 3080 } 3081 } 3082 3083 /* couldn't use RANDOM file, so fallback to time of day and hostid */ 3084 (void) gettimeofday(&tv, (struct timezone *)0); 3085 3086 /* Wouldn't it be nice if we could hash these */ 3087 r.low_time = tv.tv_usec; 3088 r.hostid = gethostid(); 3089 3090 if (sizeof (r) < size) { 3091 /* 3092 * Can't copy correctly 3093 */ 3094 return (B_FALSE); 3095 } 3096 (void) memcpy(buf, &r, size); 3097 return (B_TRUE); 3098 } 3099 3100 /* 3101 * Name: pkg_passphrase_cb 3102 * Description: Default callback that applications can use when 3103 * a passphrase is needed. This routine collects 3104 * a passphrase from the user using the given 3105 * passphrase retrieval method set with 3106 * set_passphrase_passarg(). If the method 3107 * indicates an interactive prompt, then the 3108 * prompt set with set_passphrase_prompt() 3109 * is displayed. 3110 * 3111 * Arguments: buf - Buffer to copy passphrase into 3112 * size - Max amount to copy to buf 3113 * rw - Whether this passphrase is needed 3114 * to read something off disk, or 3115 * write something to disk. Applications 3116 * typically want to ask twice when getting 3117 * a passphrase for writing something. 3118 * data - application-specific data. In this 3119 * callback, data is a pointer to 3120 * a keystore_passphrase_data structure. 3121 * 3122 * Returns: Length of passphrase collected, or -1 on error. 3123 * Errors recorded in 'err' object in the *data. 3124 */ 3125 int 3126 pkg_passphrase_cb(char *buf, int size, int rw, void *data) 3127 { 3128 BIO *pwdbio = NULL; 3129 char passphrase_copy[MAX_PHRASELEN + 1]; 3130 PKG_ERR *err; 3131 int passlen; 3132 char *ws; 3133 char prompt_copy[MAX_VERIFY_MSGLEN]; 3134 char *passphrase; 3135 char *arg; 3136 3137 err = ((keystore_passphrase_data *)data)->err; 3138 3139 if (passarg == NULL) { 3140 arg = "console"; 3141 } else { 3142 arg = passarg; 3143 } 3144 3145 /* default method of collecting password is by prompting */ 3146 if (ci_streq(arg, "console")) { 3147 if ((passphrase = getpassphrase(prompt)) == NULL) { 3148 pkgerr_add(err, PKGERR_BADPASS, 3149 gettext(MSG_NOPASS), arg); 3150 return (-1); 3151 } 3152 3153 if (rw) { 3154 /* 3155 * if the password is being supplied for 3156 * writing something to disk, verify it first 3157 */ 3158 3159 /* make a copy (getpassphrase overwrites) */ 3160 strlcpy(passphrase_copy, passphrase, 3161 MAX_PHRASELEN + 1); 3162 3163 if (((passlen = snprintf(prompt_copy, 3164 MAX_VERIFY_MSGLEN, "%s: %s", 3165 gettext(MSG_PASSWD_AGAIN), 3166 prompt)) < 0) || 3167 (passlen >= (MAX_PHRASELEN + 1))) { 3168 pkgerr_add(err, PKGERR_BADPASS, 3169 gettext(MSG_NOPASS), arg); 3170 return (-1); 3171 } 3172 3173 if ((passphrase = 3174 getpassphrase(prompt_copy)) == NULL) { 3175 pkgerr_add(err, PKGERR_BADPASS, 3176 gettext(MSG_NOPASS), arg); 3177 return (-1); 3178 } 3179 3180 if (!streq(passphrase_copy, passphrase)) { 3181 pkgerr_add(err, PKGERR_READ, 3182 gettext(MSG_PASSWD_NOMATCH)); 3183 return (-1); 3184 } 3185 } 3186 } else if (ci_strneq(arg, "pass:", 5)) { 3187 passphrase = arg + 5; 3188 } else if (ci_strneq(arg, "env:", 4)) { 3189 passphrase = getenv(arg + 4); 3190 } else if (ci_strneq(arg, "file:", 5)) { 3191 3192 /* open file for reading */ 3193 if ((pwdbio = BIO_new_file(arg + 5, "r")) == NULL) { 3194 pkgerr_add(err, PKGERR_EXIST, 3195 gettext(MSG_PASSWD_FILE), arg + 5); 3196 return (-1); 3197 } 3198 3199 /* read first line */ 3200 if (((passlen = BIO_gets(pwdbio, buf, size)) < 1) || 3201 (passlen > size)) { 3202 pkgerr_add(err, PKGERR_READ, gettext(MSG_PASSWD_FILE), 3203 arg + 5); 3204 return (-1); 3205 } 3206 BIO_free_all(pwdbio); 3207 pwdbio = NULL; 3208 3209 if (passlen == size) { 3210 /* 3211 * password was maximum length, so there is 3212 * no null terminator. null-terminate it 3213 */ 3214 buf[size - 1] = '\0'; 3215 } 3216 3217 /* first newline found is end of passwd, so nuke it */ 3218 if ((ws = strchr(buf, '\n')) != NULL) { 3219 *ws = '\0'; 3220 } 3221 return (strlen(buf)); 3222 } else { 3223 /* unrecognized passphrase */ 3224 pkgerr_add(err, PKGERR_BADPASS, 3225 gettext(MSG_BADPASSARG), arg); 3226 return (-1); 3227 } 3228 3229 if (passphrase == NULL) { 3230 /* unable to collect passwd from given source */ 3231 pkgerr_add(err, PKGERR_BADPASS, 3232 gettext(MSG_NOPASS), arg); 3233 return (-1); 3234 } 3235 3236 strlcpy(buf, passphrase, size); 3237 return (strlen(buf)); 3238 } 3239