/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* this must be included after ssl.h to avoid re-defining 'offsetof' */ #include #include #include #include #include "bootlog.h" #define BOOT_HTTP_MAJOR_VERSION 1 #define BOOT_HTTP_MINOR_VERSION 0 #define BOOT_HTTP_MICRO_VERSION 0 static boot_http_ver_t boot_http_ver = { BOOT_HTTP_MAJOR_VERSION, BOOT_HTTP_MINOR_VERSION, BOOT_HTTP_MICRO_VERSION }; static int early_err; /* Error from before error occurred */ static boolean_t verbosemode = B_FALSE; static char *cipher_list = NULL; /* Ciphers supported (if not default) */ typedef struct { int i; /* current position in buffer */ int n; /* number of bytes in buffer */ char buf[512]; /* buffer */ } buf_struct_t; typedef struct { uint_t errsrc; /* Source of this error */ ulong_t error; /* Which error? */ } errent_t; typedef enum { HTTP_REQ_TYPE_HEAD = 1, HTTP_REQ_TYPE_GET } http_req_t; #define FAILSAFE 20 /* Max # empty lines to accept */ #define DEFAULT_TIMEOUT 10 /* Default socket read timeout value */ #define HTTP_CONN_INFO 0x90919293 /* Identifies a http_conn_t struct */ #define ESTACK_SIZE 20 /* Size of the stack */ typedef struct http_conn_t { uint_t signature; /* Cookie indicating this is a handle */ int fd; /* Connection's fd... */ SSL_CTX *ctx; void *ssl; /* Handle to ssl data structure */ int read_timeout; /* Timeout to use on read requests in sec */ char *basic_auth_userid; /* Basic authentication user ID */ char *basic_auth_password; /* and password */ char is_multipart; /* B_TRUE if doing multipart/mixed download */ char is_firstpart; /* B_TRUE if first part in a multipart xfer */ char is_firstchunk; /* B_TRUE if first chunk in chunked xfer */ char is_chunked; /* B_TRUE if message body is chunked */ boolean_t keepalive; struct sockaddr_in host_addr; /* Address of host */ url_t uri; /* The current URI */ url_hport_t proxy; /* The proxy info */ boolean_t proxied; /* Connection is proxied */ char *random_file; /* File with seed info for pseudo random */ /* number generator */ char *client_cert_file; /* File holding client's certificate */ char *private_key_file; /* File with the private key */ char *file_password; /* file with password to key or pkcs12 file. */ http_respinfo_t resp; /* Response summary info */ char **resphdr; /* Array of header response lines */ buf_struct_t inbuf; char *boundary; /* Boundary text (multipart downloads only) */ uint_t boundary_len; /* Length of boundary string */ uint_t numerrs; uint_t nexterr; /* Next error to return */ ssize_t body_size; /* Size of message body or chunk */ ssize_t body_read; /* # of bytes of body_size processed */ ssize_t body_size_tot; /* Total message body size */ ssize_t body_read_tot; /* # of bytes of body_size_tot processed */ errent_t errs[ESTACK_SIZE]; /* stack of errors on the last request */ /* (libssl can return multiple errors on one */ /* operation) */ } http_conn_t; /* * Convenient macros for accessing fields in connection structure. */ #define CONN_HOSTNAME c_id->uri.hport.hostname #define CONN_PORT c_id->uri.hport.port #define CONN_ABSPATH c_id->uri.abspath #define CONN_HTTPS c_id->uri.https #define CONN_PROXY_HOSTNAME c_id->proxy.hostname #define CONN_PROXY_PORT c_id->proxy.port #define RESET_ERR(c_id) (c_id)->numerrs = 0, (c_id)->nexterr = 0 #define SET_ERR(c_id, src, err) if ((c_id)->numerrs < ESTACK_SIZE) \ (c_id)->errs[(c_id)->numerrs].errsrc = (src), \ (c_id)->errs[(c_id)->numerrs ++].error = (err) #define GET_ERR(c_id, e_src, e_code) \ if ((c_id)->nexterr < (c_id)->numerrs) \ (e_src) = (c_id)->errs[((c_id)->nexterr)].errsrc, \ (e_code) = (c_id)->errs[((c_id)->nexterr)++].error; \ else \ (e_src) = 0, (e_code) = 0 /* * Macro used to increment message body read counters */ #define INC_BREAD_CNT(bool, bcnt) \ if (bool) { \ bcnt--; \ c_id->body_read++;\ c_id->body_read_tot++; \ } static int ssl_init = 0; /* 1 when ssl has been initialized */ static char *ca_verify_file; /* List of trusted CA's */ static int verify_depth = 16; /* Certificate chain depth to verify */ static int p12_format = 0; /* Default to PEM format */ /* prototypes for local functions */ static int http_req(http_handle_t, const char *, http_req_t, offset_t, offset_t); static boolean_t http_check_conn(http_conn_t *); static SSL_CTX *initialize_ctx(http_conn_t *); static int tcp_connect(http_conn_t *, const char *, uint16_t); static int readline(http_conn_t *, int, char *, int); static int proxy_connect(http_conn_t *); static int check_cert_chain(http_conn_t *, char *); static void print_ciphers(SSL *); static int read_headerlines(http_conn_t *, boolean_t); static void free_response(http_conn_t *, int); static int free_ctx_ssl(http_conn_t *); static int get_chunk_header(http_conn_t *); static int init_bread(http_conn_t *); static int get_msgcnt(http_conn_t *, ssize_t *); static int getaline(http_conn_t *, char *, int, boolean_t); static int getbytes(http_conn_t *, char *, int); static int http_srv_send(http_conn_t *, const void *, size_t); static int http_srv_recv(http_conn_t *, void *, size_t); static void handle_ssl_error(http_conn_t *, int); static int count_digits(int); static int hexdigit(char); static char *eat_ws(const char *); static boolean_t startswith(const char **strp, const char *starts); /* ---------------------- public functions ----------------------- */ /* * http_set_p12_format - Set flag indicating that certs & keys will be in * pkcs12 format. * * Default is PEM certs. When this is called, the default can be changed to * pcs12 format. */ void http_set_p12_format(int on_off) { p12_format = on_off; } /* * http_get_version - Get current boot http support version * * pVer = http_get_version(); * * Arguments: * None. * * Returns: * Pointer to struct with version information. * * Returns the version of the http support in the current library. This * is a struct with unsigned integsrs for , and * version numbers. changes when an incompatible change * is made. changes when an upwardly-compatible API change is * made. consists of bug fixes, etc. */ boot_http_ver_t const * http_get_version(void) { return (&boot_http_ver); } /* * http_set_verbose - Turn verbose on/off * * http_set_verbose(on_off); * * Arguments: * on_off - When TRUE, turn verbose mode one. When FALSE, turn * verbose off. * * Returns: * None. * * When enabled, information is logged to bootlog (or the Solaris equivalent). */ void http_set_verbose(boolean_t on_off) { verbosemode = on_off; } /* * http_set_cipher_list - Change the list of ciphers that can be used. * * ret = http_set_cipher_list(handle, list); * * Arguments: * list - List of ciphers that can be used. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ int http_set_cipher_list(const char *list) { early_err = 0; if (list != NULL) { list = strdup(list); if (list == NULL) { early_err = EHTTP_NOMEM; return (-1); } } free(cipher_list); cipher_list = (char *)list; return (0); } /* * http_srv_init - Set up a structure for a connection. * * handle = http_srv_init(url); * * Arguments: * url - the structure that contains the URI. * * Returns: * != NULL - A handle for referring to this connection. * == NULL - An error occurred. Get the exact error from * http_get_lasterr(). */ http_handle_t http_srv_init(const url_t *url) { http_conn_t *c_id; early_err = 0; if (url == NULL) { early_err = EHTTP_BADARG; return (NULL); } if ((c_id = malloc(sizeof (*c_id))) == NULL) { early_err = EHTTP_NOMEM; return (NULL); } bzero(c_id, sizeof (*c_id)); c_id->uri = *url; c_id->proxied = B_FALSE; c_id->read_timeout = DEFAULT_TIMEOUT; c_id->keepalive = B_TRUE; c_id->fd = -1; /* Do this at the end, just in case.... */ c_id->signature = HTTP_CONN_INFO; return (c_id); } /* * http_conn_is_https - Determine whether the scheme is http or https. * * B_TRUE - Connection is an SSL connection. * B_FALSE - Connection isn't SSL. * * ret = http_conn_is_https(handle, boolean_t *bool); * * Arguments: * handle - Handle associated with the desired connection * bool - Ptr to boolean in which to place result * * Returns: * 0 - Success * -1 - Some error occurred. */ int http_conn_is_https(http_handle_t handle, boolean_t *bool) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); *bool = CONN_HTTPS; return (0); } /* * http_set_proxy - Establish the proxy name/port. * * ret = http_set_proxy(handle, proxy); * * Arguments: * handle - Handle associated with the desired connection * proxy - The hostport definition for the proxy. If NULL, * The next connect will not use a proxy. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ int http_set_proxy(http_handle_t handle, const url_hport_t *proxy) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (proxy != NULL) { c_id->proxy = *proxy; c_id->proxied = B_TRUE; } else { CONN_PROXY_HOSTNAME[0] = '\0'; c_id->proxied = B_FALSE; } return (0); } /* * http_set_keepalive - Set keepalive for this connection. * * http_set_keepalive(handle, on_off); * * Arguments: * handle - Handle associated with the desired connection * on_off - Boolean turning keepalive on (TRUE) or off (FALSE) * * Returns: * 0 - Success. * -1 - An error occurred. Check http_get_lasterr(). * * This setting takes effect next time a connection is opened using this * handle. */ int http_set_keepalive(http_handle_t handle, boolean_t on_off) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); c_id->keepalive = on_off; return (0); } /* * http_set_socket_read_timeout - Set the timeout reads * * http_set_socket_read_timeout(handle, timeout); * * Arguments: * handle - Handle associated with the desired connection * timeout - Timeout, in seconds. Zero will default to 10 second * timeouts. * * Returns: * 0 - Success. * -1 - An error occurred. Check http_get_lasterr(). * * This setting takes effect beginning with the next read operation on this * connection. */ int http_set_socket_read_timeout(http_handle_t handle, uint_t timout) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); c_id->read_timeout = (timout) ? timout : DEFAULT_TIMEOUT; return (0); } /* * http_set_basic_auth - Set the basic authorization user ID and password * * ret = http_set_basic_auth(handle, userid, password); * * Arguments: * handle - Handle associated with the desired connection * userid - ID to pass as part of http/https request * password- Password which goes with the user ID * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before a https connection is made. */ int http_set_basic_auth(http_handle_t handle, const char *userid, const char *password) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (password == NULL || userid == NULL || userid[0] == '\0') { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } userid = strdup(userid); password = strdup(password); if (userid == NULL || password == NULL) { free((void *)userid); free((void *)password); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } free(c_id->basic_auth_userid); c_id->basic_auth_userid = (char *)userid; free(c_id->basic_auth_password); c_id->basic_auth_password = (char *)password; return (0); } /* * http_set_random_file - See the pseudo random number generator with file data * * ret = http_set_random_file(handle, filename); * * Arguments: * handle - Handle associated with the desired connection * filename * - filename (including path) with random number seed. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before a https connection is made. */ int http_set_random_file(http_handle_t handle, const char *fname) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (fname != NULL) { fname = strdup(fname); if (fname == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } } free(c_id->random_file); c_id->random_file = (char *)fname; return (0); } /* * http_set_certificate_authority_file - Set the CA file. * * ret = http_set_certificate_authority_file(filename); * * Arguments: * filename- File with the certificate authority certs * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before https connections to the servers is done. */ int http_set_certificate_authority_file(const char *fname) { early_err = 0; if (fname != NULL) { fname = strdup(fname); if (fname == NULL) { early_err = EHTTP_NOMEM; return (-1); } } free(ca_verify_file); ca_verify_file = (char *)fname; return (0); } /* * http_set_client_certificate_file - Set the file containing the PKCS#12 * client certificate and optionally its certificate chain. * * ret = http_set_client_certificate_file(handle, filename); * * Arguments: * handle - Handle associated with the desired connection * filename- File (including path) containing certificate, etc. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before the handle is used to make a https connection * which will require a client certificate. */ int http_set_client_certificate_file(http_handle_t handle, const char *fname) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (fname != NULL) { fname = strdup(fname); if (fname == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } } free(c_id->client_cert_file); c_id->client_cert_file = (char *)fname; return (0); } /* * http_set_password - Set the password for the private key or pkcs12 file. * * ret = http_set_password(handle, password); * * Arguments: * handle - Handle associated with the desired connection * password- Password for the client's private key file or pkcs12 file. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before the handle is used to make a https connection. */ int http_set_password(http_handle_t handle, const char *password) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (password != NULL) { password = strdup(password); if (password == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } } free(c_id->file_password); c_id->file_password = (char *)password; return (0); } /* * http_set_key_file_password - Set the password for the private key * file. * * ret = http_set_key_file_password(handle, password); * * Arguments: * handle - Handle associated with the desired connection * password- Password for the client's private key file. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before the handle is used to make a https connection. */ int http_set_key_file_password(http_handle_t handle, const char *password) { return (http_set_password(handle, password)); } /* * http_set_private_key_file - Set the file containing the PKCS#12 * private key for this client. * * ret = http_set_private_key_file(handle, filename); * * Arguments: * handle - Handle associated with the desired connection * filename- File (including path) containing the private key. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). * * This must be set before the handle is used to make a https connection. */ int http_set_private_key_file(http_handle_t handle, const char *fname) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (fname != NULL) { fname = strdup(fname); if (fname == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } } free(c_id->private_key_file); c_id->private_key_file = (char *)fname; return (0); } /* * http_srv_connect - Establish a connection to the server * * ret = http_srv_connect(handle); * * Arguments: * handle - Handle associated with the desired connection * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr() for specifics. */ int http_srv_connect(http_handle_t handle) { http_conn_t *c_id = handle; SSL_CTX *ctx = NULL; int retval; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); if (CONN_HTTPS) { /* Build our SSL context (this function sets any errors) */ ctx = initialize_ctx(c_id); if (ctx == NULL) { libbootlog(BOOTLOG_CRIT, "http_srv_connect: initialize_ctx returned NULL"); return (-1); } } /* Connect the TCP socket */ if (c_id->proxied) { c_id->fd = proxy_connect(c_id); } else { c_id->fd = tcp_connect(c_id, CONN_HOSTNAME, CONN_PORT); } if (c_id->fd < 0) { if (ctx != NULL) SSL_CTX_free(ctx); libbootlog(BOOTLOG_CRIT, "http_srv_connect: %s returned %d", (c_id->proxied) ? "proxy_connect" : "tcp_connect", c_id->fd); return (-1); } if (CONN_HTTPS) { /* Connect the SSL socket */ if ((c_id->ssl = SSL_new(ctx)) == NULL) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "http_srv_connect: SSL_new returned " "NULL"); (void) free_ctx_ssl(c_id); return (-1); } if (verbosemode) print_ciphers(c_id->ssl); /* Ensure automatic negotiations will do things right */ SSL_set_connect_state(c_id->ssl); if (SSL_set_fd(c_id->ssl, c_id->fd) == 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "http_srv_connect: SSL_set_fd returned 0"); (void) free_ctx_ssl(c_id); return (-1); } if ((retval = SSL_connect(c_id->ssl)) <= 0) { handle_ssl_error(c_id, retval); libbootlog(BOOTLOG_CRIT, "http_srv_connect: SSL_connect"); (void) free_ctx_ssl(c_id); return (-1); } if (check_cert_chain(c_id, CONN_HOSTNAME) != 0) { (void) free_ctx_ssl(c_id); return (-1); } if (verbosemode) print_ciphers(c_id->ssl); } return (0); } /* * http_head_request - Issue http HEAD request * * ret = http_head_request(handle, abs_path); * * Arguments: * handle - Handle associated with the desired connection * abs_path- File name portion of the URI, beginning with a /. Query, * segment, etc are allowed. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ int http_head_request(http_handle_t handle, const char *abs_path) { return (http_req(handle, abs_path, HTTP_REQ_TYPE_HEAD, 0, 0)); } /* * http_get_request - Issue http GET request without a range. * * ret = http_get_request(handle, abs_path); * * Arguments: * handle - Handle associated with the desired connection * abs_path- File name portion of the URI, beginning with a /. Query, * segment, etc are allowed. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ int http_get_request(http_handle_t handle, const char *abs_path) { return (http_req(handle, abs_path, HTTP_REQ_TYPE_GET, -1, 0)); } /* * http_get_range_request - Issue http GET request using a range. * * ret = http_get_range_request(handle, abs_path, curpos, len); * * Arguments: * handle - Handle associated with the desired connection * abs_path- File name portion of the URI, beginning with a /. Query, * segment, etc are allowed. * curpos - >=0 - Beginning of range * len - = 0 - Range ends at the end of the file * > 0 - Length of range. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ int http_get_range_request(http_handle_t handle, const char *abs_path, offset_t curpos, offset_t len) { http_conn_t *c_id = handle; if (!http_check_conn(c_id)) return (-1); if (curpos < 0) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } return (http_req(handle, abs_path, HTTP_REQ_TYPE_GET, curpos, len)); } /* * http_free_respinfo - Free a respinfo structure * * ret = http_free_respinfo(resp); * * Arguments: * resp - respinfo structure presumably allocated by * http_process_headers() or http_process_part_headers() * * Note that if resp is NULL, then this results in a NOOP. * */ void http_free_respinfo(http_respinfo_t *resp) { if (resp == NULL) { return; } if (resp->statusmsg != NULL) { free(resp->statusmsg); } free(resp); } /* * http_process_headers - Read in the header lines from the response * * ret = http_process_headers(handle, resp); * * Arguments: * handle - Handle associated with the connection where the request * was made. * resp - Summary information about the response. * * Returns: * 0 - Success * < 0 - An error occurred. Specifics of the error can * be gotten using http_get_lasterr(). * * Process the HTTP headers in the response. Check for a valid response * status line. Allocate and return response information via the 'resp' * argument. Header lines are stored locally, are are returned using calls * to http_get_response_header() and http_get_header_value(). * * Note that the errors will be set in the http_conn_t struct before the * function which detected the error returns. * * Note that if resp is non-NULL, then upon a successful return, information * about the status line, the code in the status line and the number of * header lines are returned in the http_respinfo_t structure. The caller is * responsible for freeing the resources allocated to this structure via * http_free_respinfo(). * * Note that the counters used to read message bodies are initialized here. * * Calling this function replaces the header information which is * queried using http_get_response_header() and http_get_header_value(). * Once this function is called, headers read by the previous call * to http_process_headers() or http_process_part_headers() is lost. */ int http_process_headers(http_handle_t handle, http_respinfo_t **resp) { http_conn_t *c_id = handle; http_respinfo_t *lresp; char line[MAXHOSTNAMELEN]; char *ptr; int i; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); if (resp != NULL) { if ((lresp = malloc(sizeof (http_respinfo_t))) == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } bzero(lresp, sizeof (http_respinfo_t)); } /* * check the response status line, expecting * HTTP/1.1 200 OK */ i = getaline(c_id, line, sizeof (line), B_FALSE); if (i == 0) { if (resp != NULL) { *resp = lresp; } return (0); } if (i < 0) { /* * Cause of I/O error was already put into * error stack. This is an additional error. */ http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NODATA); return (-1); } free_response(c_id, B_TRUE); if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "http_process_headers: %s", line); ptr = line; if (strncmp(ptr, "HTTP/1.1", 8) != 0) { http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOT_1_1); return (-1); } /* skip to the code */ ptr += 8; while (isspace(*ptr)) ptr++; /* make sure it's three digits */ i = 0; while (isdigit(ptr[i])) i++; if (i != 3) { http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADHDR); return (-1); } c_id->resp.code = strtol(ptr, NULL, 10); /* skip to the message */ ptr += 3; while (isspace(*ptr)) ptr++; /* save the message */ c_id->resp.statusmsg = malloc(strlen(ptr) + 1); if (c_id->resp.statusmsg == NULL) { http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } (void) strcpy(c_id->resp.statusmsg, ptr); if ((i = read_headerlines(c_id, B_FALSE)) < 0) { /* * Error stack was already set at a lower level. * 'statusmsg' will be cleaned up next time * headers are read. */ http_free_respinfo(lresp); return (-1); } /* * See if there is a 'content-type: multipart/mixed' line in the * headers. If so, get the boundary string. */ ptr = http_get_header_value(handle, "Content-Type"); if (ptr != NULL) { char *ptr2; ptr2 = ptr; while (isspace(*ptr2)) ptr2 ++; if (startswith((const char **)&ptr2, "Multipart/Mixed;")) { while (isspace(*ptr2)) ptr2 ++; if (startswith((const char **)&ptr2, "Boundary=")) { if (ptr2[0] == '"') { ptr2 ++; if (ptr2[strlen(ptr2) - 1] == '"') ptr2[strlen(ptr2) - 1] = '\0'; } c_id->boundary = strdup(ptr2); if (c_id->boundary == NULL) { free(ptr); http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } c_id->boundary_len = strlen(c_id->boundary); c_id->is_multipart = B_TRUE; c_id->is_firstpart = B_TRUE; } } free(ptr); } /* * Initialize the counters used to process message bodies. */ if (init_bread(c_id) != 0) { /* * Error stack was already set at a lower level. */ http_free_respinfo(lresp); return (-1); } /* Copy fields to the caller's structure */ if (resp != NULL) { lresp->code = c_id->resp.code; lresp->nresphdrs = c_id->resp.nresphdrs; lresp->statusmsg = strdup(c_id->resp.statusmsg); if (lresp->statusmsg == NULL) { http_free_respinfo(lresp); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } *resp = lresp; } return (0); } /* * http_process_part_headers - Read in part boundary and header lines for the * next part of a multipart message. * * ret = http_process_part_headers(handle, resp); * * Arguments: * handle - Handle associated with the connection where the request * was made. * resp - Return address for summary information about the * header block. * * Returns: * = 1 - The end part was found. * = 0 - Success, with header info returned in 'resp' * = -1 - An error occurred. Specifics of the error can * be gotten using http_get_lasterr(). * * This function reads any \r\n sequences (empty lines) and expects to get * a boundary line as the next non-empty line. It then reads header lines * (content-length, etc) until it gets another empty lines, which ends the * header section. * * Note that if resp is non-NULL, then upon a successful return, information * about the the number of header lines is returned in the http_respinfo_t * structure. The caller is responsible for freeing the resources allocated * to this structure via http_free_respinfo(). * * Headers values can be returned using http_get_response_header() and * http_get_header_value(). * * Calling this function replaces the header information which is * queried using http_get_response_header() and http_get_header_value(). * Once this function is called, information returned by the previous call * to http_process_headers() or http_process_part_headers() is gone. */ int http_process_part_headers(http_handle_t handle, http_respinfo_t **resp) { http_conn_t *c_id = handle; char line[MAXHOSTNAMELEN]; int count; int limit; int i; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); if (c_id->is_multipart == 0) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOTMULTI); return (-1); } /* * Figure out how many empty lines to allow. Before the first * boundary of the transmission, there can be any number of * empty lines (from 0 up). Limit these to some reasonable * failsafe. * * For the 2nd and later boundaries, there is supposed to be * one crlf pair. However, many implementations don't require * it. So don't require it. */ if (c_id->is_firstpart) { limit = FAILSAFE; c_id->is_firstpart = B_FALSE; } else limit = 1; /* Look for the boundary line. */ count = 0; while ((i = getaline(c_id, line, sizeof (line), B_TRUE)) == 0 && count < FAILSAFE) count ++; if (i < 0 || count > limit) { /* * If I/O error, cause was already put into * error stack. This is an additional error. */ SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOBOUNDARY); return (-1); } free_response(c_id, B_FALSE); if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "http_process_part_headers: %s", line); /* Look for boundary line - '-- */ if (line[0] != '-' || line[1] != '-' || strncmp(&line[2], c_id->boundary, c_id->boundary_len) != 0) { /* No boundary line.... */ SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOBOUNDARY); return (-1); } /* Is this the end-of-parts boundary (ends with a trailing '--') */ if (strcmp(&line[c_id->boundary_len + 2], "--") == 0) { return (1); } free_response(c_id, B_FALSE); if (read_headerlines(c_id, B_TRUE) < 0) { /* Error stack was already set at a lower level. */ return (-1); } /* Copy fields to the caller's structure */ if (resp != NULL) { if ((*resp = malloc(sizeof (http_respinfo_t))) == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } bzero(*resp, sizeof (http_respinfo_t)); (*resp)->code = ' '; (*resp)->nresphdrs = c_id->resp.nresphdrs; } return (0); } /* * http_get_response_header - Get a line from the response header * * ret = http_get_response_header(handle, whichline); * * Arguments: * handle - Handle associated with the desired connection * whichline - Which line of the header to return. This must be between * zero and resp.nresphdrs which was returned by the call to * http_process_headers(). * * Returns: * ptr - Points to a copy of the header line. * NULL - An error occurred. Check http_get_lasterr(). */ char * http_get_response_header(http_handle_t handle, uint_t which) { http_conn_t *c_id = handle; char *res; if (!http_check_conn(c_id)) return (NULL); if (which >= c_id->resp.nresphdrs) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_OORANGE); return (NULL); } res = strdup(c_id->resphdr[which]); if (res == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (NULL); } return (res); } /* * http_get_header_value - Get the value of a header line. * * ret = http_get_header_value(handle, what); * * Arguments: * handle - Handle associated with the desired connection * what - The field name to look up. * * Returns: * ptr - Points to a copy of the header value. * NULL - An error occurred. Check http_get_lasterr(). */ char * http_get_header_value(http_handle_t handle, const char *field_name) { http_conn_t *c_id = handle; char *ptr; char *res; int i; int n; if (!http_check_conn(c_id)) return (NULL); if (field_name == NULL || field_name[0] == '\0') { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (NULL); } for (i = 0; i < c_id->resp.nresphdrs; i++) { ptr = c_id->resphdr[i]; n = strlen(field_name); if (strncasecmp(field_name, ptr, n) == 0 && ptr[n] == ':') { ptr += n + 1; while (isspace(*ptr)) ptr++; res = strdup(ptr); if (res == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (NULL); } return (res); } } SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMATCH); return (NULL); } /* * http_read_body - Read the HTTP response body. * * ret = http_read_body(handle, recv_buf_ptr, recv_buf_size); * * Arguments: * handle - Handle associated with the relevant connection * recv_buf_ptr - Points to buffer to receive buffer * recv_buf_size - Length in bytes of buffer. * * Returns: * n - Number of bytes read.. * < 0 - An error occurred. This is (the number of bytes gotten + 1), * negated. In other words, if 'n' bytes were read and then an * error occurred, this will return (-(n+1)). So zero bytes * were read and then an error occurs, this will return -1. If * 1 byte was read, it will return -2, etc. Specifics of the * error can be gotten using http_get_lasterr(). * * Note that the errors will be set in the http_conn_t struct before the * function which detected the error returns. */ int http_read_body(http_handle_t handle, char *recv_buf_ptr, size_t recv_buf_size) { http_conn_t *c_id = handle; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); if (recv_buf_ptr == NULL || recv_buf_size == 0) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } return (getbytes(c_id, recv_buf_ptr, recv_buf_size)); } /* * http_srv_disconnect - Get rid of the connection to the server without * freeing the http_conn_t structure. * * ret = http_srv_disconnect(handle); * * Arguments: * handle - Handle associated with the connection * * Returns: * 0 - Success * -1 - An error occurred. Specifics of the error can * be gotten using http_get_lasterr(). */ int http_srv_disconnect(http_handle_t handle) { http_conn_t *c_id = handle; int err_ret; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); err_ret = free_ctx_ssl(c_id); bzero(&c_id->inbuf, sizeof (c_id->inbuf)); free_response(c_id, B_TRUE); return (err_ret); } /* * http_srv_close - Close the connection and clean up the http_conn_t * structure. * * http_srv_close(handle); * * Arguments: * handle - Handle associated with the desired connection * * Returns: * 0 - Success * -1 - An error occurred. Specifics of the error can * be gotten using http_get_lasterr(). */ int http_srv_close(http_handle_t handle) { http_conn_t *c_id = handle; int err_ret = 0; if (!http_check_conn(c_id)) return (-1); if (c_id->ctx != NULL || c_id->ssl != NULL || c_id->fd != -1) err_ret = http_srv_disconnect(handle); free(c_id->basic_auth_userid); free(c_id->basic_auth_password); free(c_id->resp.statusmsg); free(c_id->client_cert_file); free(c_id->private_key_file); free(c_id->random_file); free(c_id->file_password); c_id->signature = 0; free(c_id); return (err_ret); } /* * http_get_conn_info - Return current information about the connection * * err = http_get_conn_info(handle); * * Arguments: * handle - Handle associated with the connection in question * * Returns: * non_NULL- Points to structure * NULL - An error exists. Check http_get_lasterr(). */ http_conninfo_t * http_get_conn_info(http_handle_t handle) { http_conn_t *c_id = handle; http_conninfo_t *info; if (!http_check_conn(c_id)) return (NULL); info = malloc(sizeof (*info)); if (info == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (NULL); } bzero(info, sizeof (*info)); info->uri = c_id->uri; info->proxy = c_id->proxy; info->keepalive = c_id->keepalive; info->read_timeout = c_id->read_timeout; return (info); } /* * http_get_lasterr - Return the next error on the last operation * * err = http_get_lasterr(handle, errsrc); * * Arguments: * handle - Handle associated with the connection in question * If no valid handle exists yet, this can be NULL. * However, it must be checked with the very next call. * errsrc - Returns the Sources of errors (ERRSRC_* values). * * Returns: * 0 - No error exists * <> 0 - The error. */ ulong_t http_get_lasterr(http_handle_t handle, uint_t *errsrc) { http_conn_t *c_id = handle; ulong_t src; ulong_t err; if (c_id == NULL || c_id->signature != HTTP_CONN_INFO) { if (errsrc) *errsrc = ERRSRC_LIBHTTP; err = early_err; early_err = 0; return (err); } GET_ERR(c_id, src, err); if (src == 0 && err == 0) { if (errsrc) *errsrc = ERRSRC_LIBHTTP; err = early_err; early_err = 0; return (err); } if (errsrc) *errsrc = src; return (err); } /* * http_decode_err - Decode a libssl error * * err = http_decode_err(err, errlib, errfunc, errcode); * * Arguments: * err - libssl/libcrypto error returned. * errlib - returns libssl/libcrypto sublibrary that caused the error * errfunc - returns function in that library * errcode - returns error code * * Returns: * None other than the above. */ void http_decode_err(ulong_t err, int *errlib, int *errfunc, int *errcode) { if (errlib) *errlib = ERR_GET_LIB(err); if (errfunc) *errfunc = ERR_GET_FUNC(err); if (errcode) *errcode = ERR_GET_REASON(err); } /* ---------------------- private functions ----------------------- */ /* * http_req - Issue http request (either HEAD or GET) * * ret = http_req(handle, abs_path, reqtype, curpos, len); * * Arguments: * handle - Handle associated with the desired connection * abs_path- File name portion of the URI, beginning with a /. Query, * segment, etc are allowed. * type - HTTP_REQ_TYPE_HEAD or HTTP_REQ_TYPE_GET * * In the case of GET requests, * curpos- -1 - Range not used * >=0 - Beginning of range * len - 0 - Range ends at the end of the file * >0 - Length of range. * * Returns: * 0 - Success * -1 - An error occurred. Check http_get_lasterr(). */ static int http_req(http_handle_t handle, const char *abs_path, http_req_t type, offset_t curpos, offset_t len) { http_conn_t *c_id = handle; char *request; char *reqtypename; char *newreq; int requestlen; int retval; int j; ERR_clear_error(); if (!http_check_conn(c_id)) return (-1); if (abs_path == NULL || abs_path[0] == '\0') { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } /* Determine the name for the request type */ switch (type) { case HTTP_REQ_TYPE_GET: reqtypename = "GET"; if (curpos < 0 && curpos != -1) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } break; case HTTP_REQ_TYPE_HEAD: reqtypename = "HEAD"; break; default: SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); return (-1); } /* Do rudimentary checks on the absolute path */ if (abs_path == NULL || *abs_path != '/') { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADARG); libbootlog(BOOTLOG_CRIT, "http_req: invalid file path"); if (abs_path != NULL) libbootlog(BOOTLOG_CRIT, " %s", abs_path); return (-1); } (void) strlcpy(CONN_ABSPATH, abs_path, MAXHOSTNAMELEN); /* * Size the request. * * With proxy: * reqtypename + " http://" + host + ":" + port + path + * " HTTP/1.1\r\n" + * Without proxy: * reqtypename + " " + path + " HTTP/1.1\r\n" + */ requestlen = strlen(reqtypename) + 8 + strlen(CONN_HOSTNAME) + 1 + count_digits(CONN_PORT) + strlen(CONN_ABSPATH) + 11; /* * Plus the rest: * "Host: " + targethost + ":" + count_digits(port) + "\r\n" + * "Connection: Keep-Alive\r\n" plus trailing "\r\n\0" */ requestlen += 6 + strlen(CONN_HOSTNAME) + 1 + count_digits(CONN_PORT) + 2 + 24 + 3; if ((request = malloc(requestlen)) == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } /* The request line */ if (c_id->proxied && c_id->ssl == NULL) { j = snprintf(request, requestlen, "%s http://%s:%d%s HTTP/1.1\r\n", reqtypename, CONN_HOSTNAME, CONN_PORT, CONN_ABSPATH); } else { j = snprintf(request, requestlen, "%s %s HTTP/1.1\r\n", reqtypename, CONN_ABSPATH); } /* Ancillary headers */ j += snprintf(&request[j], requestlen - j, "Host: %s:%d\r\n", CONN_HOSTNAME, CONN_PORT); if (!c_id->keepalive) j += snprintf(&request[j], requestlen - j, "Connection: close\r\n"); else j += snprintf(&request[j], requestlen - j, "Connection: Keep-Alive\r\n"); /* * We only send the range header on GET requests * * "Range: bytes=" + from + "-" + end + "\r\n" or * "Range: bytes=" + from + "-" "\r\n" */ if (type == HTTP_REQ_TYPE_GET && curpos >= 0) { offset_t endpos; requestlen += 13 + count_digits(curpos) + 1 + 2; if (len > 0) { endpos = curpos + len - 1; requestlen += count_digits(endpos); } if ((newreq = realloc(request, requestlen)) == NULL) { free(request); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } request = newreq; j += sprintf(&request[j], "Range: bytes=%lld-", curpos); if (len > 0) j += sprintf(&request[j], "%lld", endpos); j += sprintf(&request[j], "\r\n"); } /* * Authorization is added only if provided (RFC 2617, Section 2) * * "Authorization: Basic " + authencstr + "\r\n" */ if (c_id->basic_auth_userid && c_id->basic_auth_password) { char *authstr; char *authencstr; int authlen; /* * Allow for concat(basic_auth_userid ":" basic_auth_password) */ authlen = strlen(c_id->basic_auth_userid) + 2 + strlen(c_id->basic_auth_password); if ((authstr = malloc(authlen + 1)) == NULL) { free(request); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } (void) snprintf(authstr, authlen + 1, "%s:%s", c_id->basic_auth_userid, c_id->basic_auth_password); /* 3 bytes encoded as 4 (round up) with null termination */ if ((authencstr = malloc((authlen + 2) / 3 * 4 + 1)) == NULL) { free(authstr); free(request); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } (void) EVP_EncodeBlock((unsigned char *)authencstr, (unsigned char *)authstr, authlen); /* * Finally do concat(Authorization: Basic " authencstr "\r\n") */ requestlen += 21 + strlen(authencstr) + 2; if ((newreq = realloc(request, requestlen)) == NULL) { free(authencstr); free(authstr); free(request); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } request = newreq; j += snprintf(&request[j], requestlen - j, "Authorization: Basic %s\r\n", authencstr); free(authencstr); free(authstr); } j += sprintf(&request[j], "\r\n"); if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "%s", request); /* send the HTTP request */ retval = http_srv_send(c_id, request, j); free(request); if (retval != j) { /* Assume error in was set by send request. */ return (-1); } return (0); } /* * password_cb - Callback to get private key password and return it * to SSL. (Used for PEM certificates only.) * * len = passwd_cb(buf, buflen, rwflag, userdata); * * Arguments: * buf - Buffer for the password * buflen - Length of 'buf' * rwflag - password will be used for reading/decryption (== 0) * or writing/encryption (== 1). * userdata - Points to connection-specific information. * * Returns: * > 0 - Length of password that was put into 'buf'. * 0 - No password was returned (usually error occurred) * * NOTE: The password code is not thread safe */ /* ARGSUSED */ static int password_cb(char *buf, int buflen, int rwflag, void *userdata) { http_conn_t *c_id = userdata; if (c_id == NULL || c_id->signature != HTTP_CONN_INFO) return (0); if (c_id->file_password == NULL || buflen < strlen(c_id->file_password) + 1) return (0); return (strlcpy(buf, c_id->file_password, buflen)); } /* * initialize_ctx - Initialize the context for a connection. * * ctx = initialize_ctx(c_id); * * Arguments: * None. * * Returns: * non-NULL - Points to ctx structure. * NULL - An error occurred. Any cleanup is done and error * information is in the error stack. */ static SSL_CTX * initialize_ctx(http_conn_t *c_id) { SSL_METHOD *meth; SSL_CTX *ctx; ERR_clear_error(); /* Global system initialization */ if (ssl_init == 0) { sunw_crypto_init(); SSL_load_error_strings(); ssl_init = 1; } /* Create our context */ meth = SSLv3_client_method(); if ((ctx = SSL_CTX_new(meth)) == NULL) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: SSL_CTX_new returned NULL"); return (NULL); } /* * Ensure that any renegotiations for blocking connections will * be done automatically. (The alternative is to return partial * reads to the caller and let it oversee the renegotiations.) */ if (SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY) == 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: SSL_CTX_set_mode returned 0"); (void) SSL_CTX_free(ctx); return (NULL); } /* set cipher list if provided */ if (cipher_list != NULL) { if (!SSL_CTX_set_cipher_list(ctx, cipher_list)) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Error in cipher list"); SSL_CTX_free(ctx); return (NULL); } } /* * We attempt to use the client_certificate_file for the private * key input scheme *only* in the absence of private_key_file. In * this instance the scheme will be the same as that used for the * certificate input. */ /* Load our certificates */ if (c_id->client_cert_file != NULL) { if (p12_format) { /* Load pkcs12-formated files */ if (sunw_p12_use_certfile(ctx, c_id->client_cert_file, c_id->file_password) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read " "PKCS12 certificate file"); SSL_CTX_free(ctx); return (NULL); } } else { /* Load PEM-formated files */ if (SSL_CTX_use_certificate_file(ctx, c_id->client_cert_file, SSL_FILETYPE_PEM) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read " "PEM certificate file"); SSL_CTX_free(ctx); return (NULL); } } if (c_id->private_key_file == NULL) c_id->private_key_file = c_id->client_cert_file; } /* Load our keys */ if (p12_format) { /* Load pkcs12-formated files */ if (c_id->private_key_file != NULL) { if (sunw_p12_use_keyfile(ctx, c_id->private_key_file, c_id->file_password) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read " "PKCS12 key file"); SSL_CTX_free(ctx); return (NULL); } } } else { /* Load PEM-formated files */ SSL_CTX_set_default_passwd_cb(ctx, password_cb); SSL_CTX_set_default_passwd_cb_userdata(ctx, c_id); if (c_id->private_key_file != NULL) { if (SSL_CTX_use_PrivateKey_file(ctx, c_id->private_key_file, SSL_FILETYPE_PEM) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read " "PEM key file"); SSL_CTX_free(ctx); return (NULL); } } } /* Load the CAs we trust */ if (ca_verify_file != NULL) { if (p12_format) { if (sunw_p12_use_trustfile(ctx, ca_verify_file, c_id->file_password) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read " "PKCS12 CA list file"); SSL_CTX_free(ctx); return (NULL); } } else { if (SSL_CTX_load_verify_locations(ctx, ca_verify_file, NULL) == 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't read PEM" " CA list file"); SSL_CTX_free(ctx); return (NULL); } } } SSL_CTX_set_verify_depth(ctx, verify_depth); /* Load randomness */ if (c_id->random_file != NULL && RAND_load_file(c_id->random_file, 1024 * 1024) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: Couldn't load random file"); SSL_CTX_free(ctx); return (NULL); } if (RAND_status() <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); libbootlog(BOOTLOG_CRIT, "initialize_ctx: PRNG not seeded"); SSL_CTX_free(ctx); return (NULL); } return (ctx); } /* * tcp_connect - Set up a TCP connection. * * sock = tcp_connect(c_id, hostname, port); * * Arguments: * c_id - Structure associated with the desired connection * hostname - the host to connect to * port - the port to connect to * * Returns: * >= 0 - Socket number. * -1 - Error occurred. Error information is set in the * error stack. Any cleanup is done. * * This function established a connection to the target host. When * it returns, the connection is ready for a HEAD or GET request. */ static int tcp_connect(http_conn_t *c_id, const char *hostname, uint16_t port) { struct hostent *hp; struct sockaddr_in addr; int sock; int status; if ((hp = gethostbyname(hostname)) == NULL) { SET_ERR(c_id, ERRSRC_RESOLVE, h_errno); return (-1); } bzero(&addr, sizeof (addr)); /* LINTED */ addr.sin_addr = *(struct in_addr *)hp->h_addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); return (-1); } status = connect(sock, (struct sockaddr *)&addr, sizeof (addr)); if (status < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); (void) socket_close(sock); return (-1); } c_id->host_addr = addr; /* save for future sendto calls */ c_id->fd = sock; return (sock); } /* * readline - Get a line from the socket. Discard the end-of-line * (CR or CR/LF or LF). * * ret = readline(c_id, sock, buf, len); * * Arguments: * c_id - Structure associated with the desired connection * sock - Socket to read * buf - Buffer for the line * len - Length of the buffer * * Returns: * 0 - Success. 'buf' contains the line. * -1 - Error occurred. Error information is set in the * error stack. */ static int readline(http_conn_t *c_id, int sock, char *buf, int len) { int n, r; char *ptr = buf; for (n = 0; n < len; n++) { r = socket_read(sock, ptr, 1, c_id->read_timeout); if (r < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); return (-1); } else if (r == 0) { libbootlog(BOOTLOG_WARNING, "Readline: no data"); return (0); } if (*ptr == '\n') { *ptr = '\0'; /* Strip off the CR if it's there */ if (buf[n-1] == '\r') { buf[n-1] = '\0'; n--; } return (n); } ptr++; } libbootlog(BOOTLOG_WARNING, "readline: Buffer too short\n"); return (0); } /* * proxy_connect - Set up a proxied TCP connection to the target host. * * sock = proxy_connect(c_id); * * Arguments: * c_id - Structure associated with the desired connection * * Returns: * >= 0 - Socket number. * -1 - Error occurred. Error information is set in the * error stack. Any cleanup is done. * * This function established a connection to the proxy and then sends * the request to connect to the target host. It reads the response * (the status line and any headers). When it returns, the connection * is ready for a HEAD or GET request. */ static int proxy_connect(http_conn_t *c_id) { struct sockaddr_in addr; int sock; char buf[1024]; char *ptr; int i; if ((sock = tcp_connect(c_id, CONN_PROXY_HOSTNAME, CONN_PROXY_PORT)) < 0) { return (-1); } if (!CONN_HTTPS) { return (sock); } /* Now that we're connected, do the proxy request */ (void) snprintf(buf, sizeof (buf), "CONNECT %s:%d HTTP/1.0\r\n\r\n", CONN_HOSTNAME, CONN_PORT); /* socket_write sets the errors */ if (socket_write(sock, buf, strlen(buf), &addr) <= 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); (void) socket_close(sock); return (-1); } /* And read the response */ i = readline(c_id, sock, buf, sizeof (buf)); if (i <= 0) { if (i == 0) SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NORESP); libbootlog(BOOTLOG_CRIT, "proxy_connect: Empty response from proxy"); (void) socket_close(sock); return (-1); } ptr = buf; if (strncmp(ptr, "HTTP", 4) != 0) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOT_1_1); libbootlog(BOOTLOG_CRIT, "proxy_connect: Unrecognized protocol"); (void) socket_close(sock); return (-1); } /* skip to the code */ ptr += 4; while (*ptr != ' ' && *ptr != '\0') ptr++; while (*ptr == ' ' && *ptr != '\0') ptr++; /* make sure it's three digits */ if (strncmp(ptr, "200", 3) != 0) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADRESP); libbootlog(BOOTLOG_CRIT, "proxy_connect: Received error from proxy server"); (void) socket_close(sock); return (-1); } ptr += 3; if (isdigit(*ptr)) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADRESP); (void) socket_close(sock); return (-1); } /* Look for the blank line that signals end of proxy header */ while ((i = readline(c_id, sock, buf, sizeof (buf))) > 0) ; if (i < 0) { (void) socket_close(sock); return (-1); } return (sock); } /* * check_cert_chain - Check if we have a valid certificate chain. * * ret = check_cert_chain(c_id, host); * * Arguments: * c_id - Connection info. * host - Name to compare with the common name in the certificate. * * Returns: * 0 - Certificate chain and common name are both OK. * -1 - Certificate chain and/or common name is not valid. */ static int check_cert_chain(http_conn_t *c_id, char *host) { X509 *peer; char peer_CN[256]; long verify_err; if ((verify_err = SSL_get_verify_result(c_id->ssl)) != X509_V_OK) { SET_ERR(c_id, ERRSRC_VERIFERR, verify_err); libbootlog(BOOTLOG_CRIT, "check_cert_chain: Certificate doesn't verify"); return (-1); } /* * Check the cert chain. The chain length * is automatically checked by OpenSSL when we * set the verify depth in the ctx * * All we need to do here is check that the CN * matches */ /* Check the common name */ if ((peer = SSL_get_peer_certificate(c_id->ssl)) == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOCERT); libbootlog(BOOTLOG_CRIT, "check_cert_chain: Peer did not present a certificate"); return (-1); } (void) X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, peer_CN, 256); if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "server cert's peer_CN is %s, host is %s", peer_CN, host); if (strcasecmp(peer_CN, host)) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMATCH); libbootlog(BOOTLOG_CRIT, "check_cert_chain: Common name doesn't match host name"); libbootlog(BOOTLOG_CRIT, "peer_CN = %s, host = %s", peer_CN, host); return (-1); } return (0); } /* * print_ciphers - Print the list of ciphers for debugging. * * print_ciphers(ssl); * * Arguments: * ssl - SSL connection. * * Returns: * none */ static void print_ciphers(SSL *ssl) { SSL_CIPHER *c; STACK_OF(SSL_CIPHER) *sk; int i; const char *name; if (ssl == NULL) return; sk = SSL_get_ciphers(ssl); if (sk == NULL) return; for (i = 0; i < sk_SSL_CIPHER_num(sk); i++) { /* LINTED */ c = sk_SSL_CIPHER_value(sk, i); libbootlog(BOOTLOG_VERBOSE, "%08lx %s", c->id, c->name); } name = SSL_get_cipher_name(ssl); if (name == NULL) name = ""; libbootlog(BOOTLOG_VERBOSE, "Current cipher = %s", name); } /* * read_headerlines - Get the header lines from the server. This reads * lines until it gets a empty line indicating end of headers. * * ret = read_headerlines(c_id); * * Arguments: * c_id - Info about the connection being read. * bread - TRUE if the headerlines are part of the message body. * * Returns: * 0 - Header lines were read. * -1 - Error occurred. The errors information is already in * the error stack. * * Read the lines. If the current line begins with a space or tab, it is * a continuation. Take the new line and append it to the end of the * previous line rather than making an entry for another line in * c_id->resphdr. * * Note that I/O errors are put into the error stack by http_srv_recv(), * which is called by getaline(). */ static int read_headerlines(http_conn_t *c_id, boolean_t bread) { char line[MAXHOSTNAMELEN]; char **new_buf; char *ptr; int next; int cur; int n; /* process headers, stop when we get to an empty line */ cur = 0; next = 0; while ((n = getaline(c_id, line, sizeof (line), bread)) > 0) { if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "read_headerlines: %s", line); /* * See if this is a continuation line (first col is a * space or a tab) */ if (line[0] != ' ' && line[0] != ' ') { cur = next; next ++; new_buf = realloc(c_id->resphdr, (cur + 1) * sizeof (void *)); if (new_buf == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } c_id->resphdr = new_buf; c_id->resphdr[cur] = strdup(line); if (c_id->resphdr[cur] == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } } else { ptr = line; while (isspace(*ptr)) ptr ++; c_id->resphdr[cur] = realloc(c_id->resphdr[cur], strlen(c_id->resphdr[cur]) + strlen(ptr) + 1); if (c_id->resphdr[cur] == NULL) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOMEM); return (-1); } (void) strcat(c_id->resphdr[cur], ptr); } ptr = &(c_id->resphdr[cur][strlen(c_id->resphdr[cur]) - 1]); while (ptr > c_id->resphdr[cur] && isspace(*ptr)) ptr --; } c_id->resp.nresphdrs = next; /* Cause of any I/O error was already put into error stack. */ return (n >= 0 ? 0 : -1); } static void free_response(http_conn_t *c_id, int free_boundary) { int i; /* free memory from previous calls */ if (c_id->resp.statusmsg != NULL) { free(c_id->resp.statusmsg); c_id->resp.statusmsg = NULL; } for (i = 0; i < c_id->resp.nresphdrs; i++) { free(c_id->resphdr[i]); c_id->resphdr[i] = NULL; } c_id->resp.nresphdrs = 0; if (c_id->resphdr != NULL) { free(c_id->resphdr); c_id->resphdr = NULL; } if (free_boundary && c_id->boundary) { free(c_id->boundary); c_id->boundary = NULL; c_id->is_multipart = B_FALSE; } } static int free_ctx_ssl(http_conn_t *c_id) { int err_ret = 0; if (c_id->ssl != NULL) { if (SSL_shutdown(c_id->ssl) <= 0) { ulong_t err; while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); err_ret = -1; } SSL_free(c_id->ssl); c_id->ssl = NULL; } if (c_id->fd != -1 && socket_close(c_id->fd) < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); err_ret = -1; } c_id->fd = -1; if (c_id->ctx != NULL) { SSL_CTX_free(c_id->ctx); c_id->ctx = NULL; } return (err_ret); } /* * get_chunk_header - Get a chunk header line * * Arguments: * c_id - Structure describing the connection in question. * * Returns: * >=0 - Length of next chunk * -1 - Error occurred. The error information is in the error stack. */ static int get_chunk_header(http_conn_t *c_id) { char line[MAXHOSTNAMELEN]; char *ptr; int value; int ok; int i; /* * Determine whether an extra crlf pair will precede the * chunk header. For the first one, there is no preceding * crlf. For later chunks, there is one crlf. */ if (c_id->is_firstchunk) { ok = 1; c_id->is_firstchunk = B_FALSE; } else { ok = ((i = getaline(c_id, line, sizeof (line), B_FALSE)) == 0); } if (ok) i = getaline(c_id, line, sizeof (line), B_FALSE); if (!ok || i < 0) { /* * If I/O error, the Cause was already put into * error stack. This is an additional error. */ SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_NOHEADER); return (-1); } if (verbosemode) libbootlog(BOOTLOG_VERBOSE, "get_chunk_header: <%s>", line); /* * The first (and probably only) field in the line is the hex * length of the chunk. */ ptr = line; value = 0; while (*ptr != '\0' && (i = hexdigit(*ptr)) >= 0) { value = (value << 4) + i; ptr ++; } return (value); } /* * init_bread - Initialize the counters used to read message bodies. * * Arguments: * c_id - Structure describing the connection in question. * * Returns: * 0 - Success * -1 - Error occurred. The error information is in the error stack. * * This routine will determine whether the message body being received is * chunked or non-chunked. Once determined, the counters used to read * message bodies will be initialized. */ static int init_bread(http_conn_t *c_id) { char *hdr; char *ptr; boolean_t sized = B_FALSE; /* * Assume non-chunked reads until proven otherwise. */ c_id->is_chunked = B_FALSE; c_id->is_firstchunk = B_FALSE; hdr = http_get_header_value(c_id, "Content-Length"); if (hdr != NULL) { c_id->body_size = strtol(hdr, NULL, 10); if (c_id->body_size == 0 && errno != 0) { free(hdr); SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADSIZE); return (-1); } free(hdr); sized = B_TRUE; } /* * If size was not determined above, then see if this is a * chunked message. Keep in mind that the first chunk size is * "special". */ if (!sized) { hdr = http_get_header_value(c_id, "Transfer-Encoding"); if (hdr != NULL) { ptr = eat_ws(hdr); if (startswith((const char **)&ptr, "chunked;") || strcasecmp(ptr, "chunked") == 0) { c_id->is_firstchunk = B_TRUE; c_id->is_chunked = B_TRUE; } free(hdr); if (c_id->is_chunked) { c_id->body_size = get_chunk_header(c_id); if (c_id->body_size == -1) { /* * Error stack was already set at a * lower level. */ return (-1); } sized = B_TRUE; } } } /* * Well, isn't this a fine predicament? It wasn't chunked or * non-chunked as far as we can tell. */ if (!sized) { SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_BADSIZE); return (-1); } c_id->body_read = 0; c_id->body_size_tot = c_id->body_size; c_id->body_read_tot = 0; return (0); } /* * get_msgcnt - Get the number of bytes left in the message body or chunk. * * Arguments: * c_id - Structure describing the connection in question. * msgcnt - Where to store the message count. * * Returns: * 0 - Success * -1 - Error occurred. The error information is in the error stack. * * Note that if the message being read is not chunked, then the byte count * is simply the message size minus the bytes read thus far. In the case of * chunked messages, the byte count returned will be the number of bytes * left in the chunk. If the current chunk has been exhausted, then this * routine will determine the size of the next chunk. When the next chunk * size is zero, the message has been read in its entirety. */ static int get_msgcnt(http_conn_t *c_id, ssize_t *msgcnt) { /* * If there are more bytes in the message, then return. */ *msgcnt = c_id->body_size - c_id->body_read; if (*msgcnt != 0) { return (0); } /* * If this is not a chunked message and the body has been * read, then we're done. */ if (!c_id->is_chunked) { return (0); } /* * We're looking at a chunked message whose immediate * chunk has been totally processed. See if there is * another chunk. */ c_id->body_size = get_chunk_header(c_id); if (c_id->body_size == -1) { /* * Error stack was already set at a * lower level. */ return (-1); } /* * No bytes of this chunk have been processed yet. */ c_id->body_read = 0; /* * A zero length chunk signals the end of the * message body and chunking. */ if (c_id->body_size == 0) { c_id->is_chunked = B_FALSE; return (0); } /* * There is another chunk. */ c_id->body_size_tot += c_id->body_size; *msgcnt = c_id->body_size - c_id->body_read; return (0); } /* * getaline - Get lines of data from the HTTP response, up to 'len' bytes. * NOTE: the line will not end with a NULL if all 'len' bytes * were read. * * Arguments: * c_id - Structure describing the connection in question. * line - Where to store the data. * len - Maximum number of bytes in the line. * bread - TRUE if the lines are part of the message body. * * Returns: * >=0 - The number of bytes successfully read. * <0 - An error occurred. This is (the number of bytes gotten + 1), * negated. In other words, if 'n' bytes were read and then an * error occurred, this will return (-(n+1)). So zero bytes read * and then an error occurs, this will return -1. If 1 bytes * was read, it will return -2, etc. * * Specifics of the error can be gotten using http_get_lasterr(); * * Note that I/O errors are put into the error stack by http_srv_recv().1 */ static int getaline(http_conn_t *c_id, char *line, int len, boolean_t bread) { int i = 0; ssize_t msgcnt = 0; ssize_t cnt; while (i < len) { /* * Special processing required for message body reads. */ if (bread) { /* * See if there is another chunk. Obviously, in the * case of non-chunked messages, there won't be. * But in either case, chunked or not, if msgcnt * is still zero after the call to get_msgcnt(), * then we're done. */ if (msgcnt == 0) { if (get_msgcnt(c_id, &msgcnt) == -1) { return (-(i+1)); } if (msgcnt == 0) { break; } } cnt = MIN(msgcnt, sizeof (c_id->inbuf.buf)); } else { cnt = sizeof (c_id->inbuf.buf); } /* read more data if buffer empty */ if (c_id->inbuf.i == c_id->inbuf.n) { c_id->inbuf.i = 0; c_id->inbuf.n = http_srv_recv(c_id, c_id->inbuf.buf, cnt); if (c_id->inbuf.n == 0) { return (i); } if (c_id->inbuf.n < 0) { return (-(i+1)); } } /* skip CR */ if (c_id->inbuf.buf[c_id->inbuf.i] == '\r') { INC_BREAD_CNT(bread, msgcnt); c_id->inbuf.i++; continue; } if (c_id->inbuf.buf[c_id->inbuf.i] == '\n') { INC_BREAD_CNT(bread, msgcnt); c_id->inbuf.i++; line[i] = '\0'; return (i); } /* copy buf from internal buffer */ INC_BREAD_CNT(bread, msgcnt); line[i++] = c_id->inbuf.buf[c_id->inbuf.i++]; } return (i); } /* * getbytes - Get a block from the HTTP response. Used for the HTTP body. * * Arguments: * c_id - Structure describing the connection in question. * line - Where to store the data. * len - Maximum number of bytes in the block. * * Returns: * >=0 - The number of bytes successfully read. * <0 - An error occurred. This is (the number of bytes gotten + 1), * negated. In other words, if 'n' bytes were read and then an * error occurred, this will return (-(n+1)). So zero bytes read * and then an error occurs, this will return -1. If 1 bytes * was read, it will return -2, etc. * * Specifics of the error can be gotten using http_get_lasterr(); * * Note that all reads performed here assume that a message body is being * read. If this changes in the future, then the logic should more closely * resemble getaline(). * * Note that I/O errors are put into the error stack by http_srv_recv(). */ static int getbytes(http_conn_t *c_id, char *line, int len) { int i = 0; ssize_t msgcnt = 0; ssize_t cnt; int nbytes; while (i < len) { /* * See if there is another chunk. Obviously, in the * case of non-chunked messages, there won't be. * But in either case, chunked or not, if msgcnt * is still zero after the call to get_msgcnt(), then * we're done. */ if (msgcnt == 0) { if (get_msgcnt(c_id, &msgcnt) == -1) { return (-(i+1)); } if (msgcnt == 0) { break; } } cnt = MIN(msgcnt, len - i); if (c_id->inbuf.n != c_id->inbuf.i) { nbytes = (int)MIN(cnt, c_id->inbuf.n - c_id->inbuf.i); (void) memcpy(line, &c_id->inbuf.buf[c_id->inbuf.i], nbytes); c_id->inbuf.i += nbytes; } else { nbytes = http_srv_recv(c_id, line, cnt); if (nbytes == 0) { return (i); } if (nbytes < 0) { return (-(i+1)); } } i += nbytes; line += nbytes; msgcnt -= nbytes; c_id->body_read += nbytes; c_id->body_read_tot += nbytes; } return (i); } static int http_srv_send(http_conn_t *c_id, const void *buf, size_t nbyte) { int retval; if (c_id->ssl != NULL) { if ((retval = SSL_write(c_id->ssl, buf, nbyte)) <= 0) { handle_ssl_error(c_id, retval); } return (retval); } else { retval = socket_write(c_id->fd, buf, nbyte, &c_id->host_addr); if (retval < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); return (-1); } return (retval); } } static int http_srv_recv(http_conn_t *c_id, void *buf, size_t nbyte) { int retval; if (c_id->ssl != NULL) { if ((retval = SSL_read(c_id->ssl, buf, nbyte)) <= 0) { handle_ssl_error(c_id, retval); } return (retval); } else { retval = socket_read(c_id->fd, buf, nbyte, c_id->read_timeout); if (retval < 0) { SET_ERR(c_id, ERRSRC_SYSTEM, errno); return (-1); } return (retval); } } static boolean_t http_check_conn(http_conn_t *c_id) { early_err = 0; if (c_id == NULL || c_id->signature != HTTP_CONN_INFO) { early_err = EHTTP_BADARG; return (B_FALSE); } RESET_ERR(c_id); return (B_TRUE); } static void handle_ssl_error(http_conn_t *c_id, int retval) { ulong_t err; err = SSL_get_error(c_id->ssl, retval); switch (err) { case SSL_ERROR_NONE: return; case SSL_ERROR_ZERO_RETURN: SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_CONCLOSED); return; case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_CONNECT: case SSL_ERROR_WANT_X509_LOOKUP: SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_UNEXPECTED); return; case SSL_ERROR_SYSCALL: err = ERR_get_error(); if (err == 0) SET_ERR(c_id, ERRSRC_LIBHTTP, EHTTP_EOFERR); else if (err == (ulong_t)-1) SET_ERR(c_id, ERRSRC_SYSTEM, errno); else { SET_ERR(c_id, ERRSRC_LIBSSL, err); while ((err = ERR_get_error()) != 0) SET_ERR(c_id, ERRSRC_LIBSSL, err); } return; case SSL_ERROR_SSL: while ((err = ERR_get_error()) != 0) { SET_ERR(c_id, ERRSRC_LIBSSL, err); } return; } } static int count_digits(int value) { int count = 1; if (value < 0) { count++; value = -value; } while (value > 9) { value /= 10; count++; } return (count); } static int hexdigit(char ch) { if (ch >= '0' && ch <= '9') return (ch - '0'); if (ch >= 'A' && ch <= 'F') return (ch - 'A' + 10); if (ch >= 'a' && ch <= 'f') return (ch - 'a' + 10); return (-1); } static char * eat_ws(const char *buf) { char *ptr = (char *)buf; while (isspace(*ptr)) ptr++; return (ptr); } static boolean_t startswith(const char **strp, const char *starts) { int len = strlen(starts); if (strncasecmp(*strp, starts, len) == 0) { *strp += len; return (B_TRUE); } return (B_FALSE); }