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