xref: /titanic_52/usr/src/lib/libpkg/common/pkgweb.c (revision 36e852a172cba914383d7341c988128b2c667fbd)
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