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