pkgweb.c revision 9781:ccf49524d5dc
1193326Sed/*
2193326Sed * CDDL HEADER START
3193326Sed *
4193326Sed * The contents of this file are subject to the terms of the
5193326Sed * Common Development and Distribution License (the "License").
6193326Sed * You may not use this file except in compliance with the License.
7193326Sed *
8193326Sed * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9239462Sdim * or http://www.opensolaris.org/os/licensing.
10239462Sdim * See the License for the specific language governing permissions
11239462Sdim * and limitations under the License.
12239462Sdim *
13193326Sed * When distributing Covered Code, include this CDDL HEADER in each
14193326Sed * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15193326Sed * If applicable, add the following below this CDDL HEADER, with the
16193326Sed * fields enclosed by brackets "[]" replaced with your own identifying
17193326Sed * information: Portions Copyright [yyyy] [name of copyright owner]
18239462Sdim *
19193326Sed * CDDL HEADER END
20193326Sed */
21193326Sed
22193326Sed/*
23193326Sed * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24193326Sed * Use is subject to license terms.
25193326Sed */
26193326Sed
27193326Sed/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28193326Sed/* All Rights Reserved */
29193326Sed
30239462Sdim
31193326Sed#include <stdio.h>
32198092Srdivacky#include <limits.h>
33239462Sdim#include <stdlib.h>
34193326Sed#include <unistd.h>
35198092Srdivacky#include <string.h>
36239462Sdim#include <pkglocs.h>
37239462Sdim#include <locale.h>
38193326Sed#include <libintl.h>
39198092Srdivacky#include <libgen.h>
40239462Sdim#include <signal.h>
41193326Sed#include <sys/stat.h>
42198092Srdivacky#include <sys/statvfs.h>
43239462Sdim#include <sys/types.h>
44239462Sdim#include <fcntl.h>
45239462Sdim#include <dirent.h>
46239462Sdim#include <boot_http.h>
47193326Sed#include <errno.h>
48198092Srdivacky#include <ctype.h>
49193326Sed#include <openssl/pkcs7.h>
50193326Sed#include <openssl/ocsp.h>
51193326Sed#include <openssl/pkcs12.h>
52193326Sed#include <openssl/err.h>
53193326Sed#include <openssl/x509.h>
54193326Sed#include <openssl/pem.h>
55193326Sed#include <openssl/evp.h>
56193326Sed#include <openssl/rand.h>
57193326Sed#include <openssl/x509v3.h>
58193326Sed#include "pkglib.h"
59193326Sed#include "pkglibmsgs.h"
60193326Sed#include "pkglocale.h"
61193326Sed#include "keystore.h"
62193326Sed#include "pkgweb.h"
63193326Sed#include "pkgerr.h"
64193326Sed#include "p12lib.h"
65193326Sed
66193326Sed/* fixed format when making an OCSP request */
67193326Sed#define	OCSP_REQUEST_FORMAT \
68193326Sed	"POST %s HTTP/1.0\r\n" \
69193326Sed	"Content-Type: application/ocsp-request\r\n" \
70193326Sed	"Content-Length: %d\r\n\r\n"
71193326Sed
72193326Sed/*
73193326Sed * no security is afforded by using this phrase to "encrypt" CA certificates,
74193326Sed * but it might aid in debugging and has to be non-null
75198092Srdivacky */
76239462Sdim#define	WEB_CA_PHRASE		"schizophrenic"
77193326Sed
78239462Sdim/* This one needs the ': ' at the end */
79239462Sdim#define	CONTENT_TYPE_HDR	"Content-Type"
80239462Sdim#define	CONTENT_DISPOSITION_HDR	"Content-Disposition"
81193326Sed#define	CONTENT_OCSP_RESP	"application/ocsp-response"
82193326Sed#define	CONTENT_LENGTH_HDR	"Content-Length"
83193326Sed#define	LAST_MODIFIED_HDR	"Last-Modified"
84193326Sed#define	OCSP_BUFSIZ	1024
85193326Sed
86198092Srdivacky/*
87239462Sdim * default amount of time that is allowed for error when checking
88239462Sdim * OCSP response validity.
89239462Sdim * For example, if this is set to 5 minutes, then if a response
90193326Sed * is issued that is valid from 12:00 to 1:00, then we will
91193326Sed * accept it if the local time is between 11:55 and 1:05.
92193326Sed * This takes care of not-quite-synchronized server and client clocks.
93198092Srdivacky */
94193326Sed#define	OCSP_VALIDITY_PERIOD	(5 * 60)
95193326Sed
96193326Sed/* this value is defined by getpassphrase(3c) manpage */
97193326Sed#define	MAX_PHRASELEN		257
98193326Sed
99198092Srdivacky/* Max length of "enter password again" prompt message */
100193326Sed#define	MAX_VERIFY_MSGLEN	1024
101198092Srdivacky
102226633Sdim/* local prototypes */
103193326Sedstatic boolean_t remove_dwnld_file(char *);
104193326Sedstatic boolean_t get_ENV_proxyport(PKG_ERR *, ushort_t *);
105193326Sedstatic boolean_t make_link(char *, char *);
106193326Sedstatic WebStatus web_send_request(PKG_ERR *, int, int, int);
107193326Sedstatic boolean_t web_eval_headers(PKG_ERR *);
108193326Sedstatic WebStatus web_get_file(PKG_ERR *, char *, int, char **);
109239462Sdimstatic boolean_t ck_dwnld_dir_space(PKG_ERR *, char *, ulong_t);
110193326Sedstatic WebStatus web_connect(PKG_ERR *);
111239462Sdimstatic boolean_t web_setup(PKG_ERR *);
112193326Sedstatic boolean_t check_dwnld_dir(PKG_ERR *, char *);
113193326Sedstatic boolean_t parse_url_proxy(PKG_ERR *, char *, char *, ushort_t);
114193326Sedstatic boolean_t web_disconnect(void);
115198092Srdivackystatic char *get_unique_filename(char *, char *);
116239462Sdimstatic boolean_t get_ENV_proxy(PKG_ERR *, char **);
117239462Sdimstatic char *condense_lastmodified(char *);
118239462Sdimstatic int web_verify(int, X509_STORE_CTX *);
119239462Sdimstatic int get_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x);
120193326Sedstatic boolean_t get_ocsp_uri(X509 *, char **);
121193326Sedstatic OCSPStatus ocsp_verify(PKG_ERR *, X509 *, X509 *, char *, url_hport_t *,
122239462Sdim    STACK_OF(X509) *);
123193326Sedstatic char	*get_time_string(ASN1_GENERALIZEDTIME *);
124193326Sedstatic char	*write_ca_file(PKG_ERR *, char *, STACK_OF(X509) *, char *);
125193326Sedstatic boolean_t _get_random_info(void *, int);
126193326Sedstatic boolean_t	init_session(void);
127193326Sedstatic void	progress_setup(int, ulong_t);
128239462Sdimstatic void	progress_report(int, ulong_t);
129193326Sedstatic void	progress_finish(int);
130193326Sedstatic char	*replace_token(char *, char, char);
131193326Sedstatic void	dequote(char *);
132193326Sedstatic void	trim(char *);
133193326Sed
134
135/*
136 * structure used to hold data passed back to the
137 * X509 verify callback routine in validate_signature()
138 */
139typedef 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 */
146static ulong_t const_increment, const_divider, completed, const_completed;
147
148/* current network backoff wait period */
149static int cur_backoff = 0;
150
151/* download session context handle */
152static WEB_SESSION *ps;
153
154static int	webpkg_install = 0;
155static char	*prompt = NULL;
156static 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 */
170void
171set_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 */
186void
187set_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 */
206boolean_t
207get_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 */
249boolean_t
250path_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 */
273void
274web_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 */
349boolean_t
350web_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	}
496cleanup:
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 */
520boolean_t
521get_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
597cleanup:
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 */
620void
621echo_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 */
647ushort_t
648strip_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 */
666void
667set_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 */
680int
681is_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 */
699static boolean_t
700web_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 */
723static boolean_t
724check_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 */
762boolean_t
763ds_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
812cleanup:
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 */
834boolean_t
835validate_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 */
1008cleanup:
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 */
1029static int
1030web_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 */
1108static 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 */
1153static boolean_t
1154get_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 */
1199static OCSPStatus
1200ocsp_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 */
1572static int
1573get_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 */
1614static boolean_t
1615parse_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
1639cleanup:
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 */
1651static boolean_t
1652web_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
1691cleanup:
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 */
1708static WebStatus
1709web_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	}
1799cleanup:
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 */
1822static 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
1885cleanup:
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 */
1913static WebStatus
1914web_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
2077cleanup:
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 */
2091static boolean_t
2092web_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 */
2133static WebStatus
2134web_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
2403cleanup:
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 */
2427static boolean_t
2428make_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 */
2458char *
2459get_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 */
2496char *
2497get_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 */
2522static void
2523progress_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 */
2555static void
2556progress_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 */
2588static void
2589progress_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 */
2606static boolean_t
2607init_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 */
2645static boolean_t
2646ck_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
2683static char *
2684get_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 */
2753static char *
2754replace_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 */
2783static void
2784trim(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 */
2815static void
2816dequote(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 */
2863static boolean_t
2864get_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 */
2914static boolean_t
2915get_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 */
2952static boolean_t
2953remove_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 */
2982char *
2983condense_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 */
3011void
3012backoff()
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 */
3045void
3046reset_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 */
3063static 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 */
3125int
3126pkg_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