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