| /* |
| * https.c |
| * |
| * Copyright (c) 2011 Duo Security |
| * All rights reserved, all wrongs reversed. |
| */ |
| |
| #include "config.h" |
| |
| #include <sys/types.h> |
| #include <sys/socket.h> |
| #include <sys/time.h> |
| |
| #include <netdb.h> |
| #include <poll.h> |
| #include <signal.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| |
| #include <openssl/bio.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/hmac.h> |
| #include <openssl/opensslv.h> |
| #include <openssl/rand.h> |
| #include <openssl/ssl.h> |
| #include <openssl/x509v3.h> |
| |
| #include "cacert.h" |
| #include "http_parser.h" |
| #include "https.h" |
| #include "match.h" |
| |
| #ifdef HAVE_X509_TEA_SET_STATE |
| extern void X509_TEA_set_state(int change); |
| #endif |
| |
| struct https_ctx { |
| SSL_CTX *ssl_ctx; |
| |
| char *proxy; |
| char *proxy_port; |
| char *proxy_auth; |
| |
| const char *errstr; |
| char errbuf[512]; |
| |
| http_parser_settings parse_settings; |
| char parse_buf[4096]; |
| }; |
| |
| struct https_ctx ctx; |
| |
| struct https_request { |
| BIO *cbio; |
| BIO *body; |
| SSL *ssl; |
| |
| char *host; |
| const char *port; |
| |
| http_parser *parser; |
| int done; |
| }; |
| |
| static int |
| __on_body(http_parser *p, const char *buf, size_t len) |
| { |
| struct https_request *req = (struct https_request *)p->data; |
| |
| return (BIO_write(req->body, buf, len) != len); |
| } |
| |
| static int |
| __on_message_complete(http_parser *p) |
| { |
| struct https_request *req = (struct https_request *)p->data; |
| |
| req->done = 1; |
| return (0); |
| } |
| |
| static const char * |
| _SSL_strerror(void) |
| { |
| unsigned long code = ERR_get_error(); |
| const char *p = NULL; |
| |
| if (code == 0x0906D06C) { |
| /* XXX - bad "PEM_read_bio:no start line" alias */ |
| errno = ECONNREFUSED; |
| } else { |
| p = ERR_reason_error_string(code); |
| } |
| return (p ? p : strerror(errno)); |
| } |
| |
| /* Server certificate name check, logic adapted from libcurl */ |
| static int |
| _SSL_check_server_cert(SSL *ssl, const char *hostname) |
| { |
| X509 *cert; |
| X509_NAME *subject; |
| const GENERAL_NAME *altname; |
| STACK_OF(GENERAL_NAME) *altnames; |
| ASN1_STRING *tmp; |
| int i, n, match = -1; |
| const char *p; |
| if (SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE || |
| (cert = SSL_get_peer_certificate(ssl)) == NULL) { |
| return (1); |
| } |
| /* Check subjectAltName */ |
| if ((altnames = X509_get_ext_d2i(cert, NID_subject_alt_name, |
| NULL, NULL)) != NULL) { |
| n = sk_GENERAL_NAME_num(altnames); |
| |
| for (i = 0; i < n && match != 1; i++) { |
| altname = sk_GENERAL_NAME_value(altnames, i); |
| p = (char *)ASN1_STRING_data(altname->d.ia5); |
| if (altname->type == GEN_DNS) { |
| match = (ASN1_STRING_length(altname->d.ia5) == |
| strlen(p) && match_pattern(hostname, p)); |
| } |
| } |
| GENERAL_NAMES_free(altnames); |
| } |
| /* No subjectAltName, try CN */ |
| if (match == -1 && |
| (subject = X509_get_subject_name(cert)) != NULL) { |
| for (i = -1; (n = X509_NAME_get_index_by_NID(subject, |
| NID_commonName, i)) >= 0; ) { |
| i = n; |
| } |
| if (i >= 0) { |
| if ((tmp = X509_NAME_ENTRY_get_data( |
| X509_NAME_get_entry(subject, i))) != NULL && |
| ASN1_STRING_type(tmp) == V_ASN1_UTF8STRING) { |
| p = (char *)ASN1_STRING_data(tmp); |
| match = (ASN1_STRING_length(tmp) == |
| strlen(p) && match_pattern(hostname, p)); |
| } |
| } |
| } |
| X509_free(cert); |
| |
| return (match > 0); |
| } |
| |
| /* Wait msecs milliseconds for the fd to become writable. Return |
| * -1 on error, 0 on timeout, and >0 if the fd is writable. |
| */ |
| static int |
| _fd_wait(int fd, int msecs) |
| { |
| struct pollfd pfd; |
| int result; |
| |
| pfd.fd = fd; |
| pfd.events = POLLOUT | POLLWRBAND; |
| pfd.revents = 0; |
| |
| if (msecs < 0) { |
| msecs = -1; |
| } |
| |
| do { |
| result = poll(&pfd, 1, msecs); |
| } while (result == -1 && errno == EINTR); |
| |
| if (result <= 0) { |
| return result; |
| } |
| if (pfd.revents & POLLERR) { |
| return -1; |
| } |
| return (pfd.revents & pfd.events ? 1 : -1); |
| } |
| |
| /* Return -1 on hard error (abort), 0 on timeout, >= 1 on successful wakeup */ |
| static int |
| _BIO_wait(BIO *cbio, int msecs) |
| { |
| int result; |
| if (!BIO_should_retry(cbio)) { |
| return (-1); |
| } |
| |
| struct pollfd pfd; |
| BIO_get_fd(cbio, &pfd.fd); |
| pfd.events = 0; |
| pfd.revents = 0; |
| |
| if (BIO_should_io_special(cbio)) { |
| pfd.events = POLLOUT | POLLWRBAND; |
| } else if (BIO_should_read(cbio)) { |
| pfd.events = POLLIN | POLLPRI | POLLRDBAND; |
| } else if (BIO_should_write(cbio)) { |
| pfd.events = POLLOUT | POLLWRBAND; |
| } else { |
| return (-1); |
| } |
| |
| if (msecs < 0) { |
| /* POSIX requires -1 for "no timeout" although some libcs |
| accept any negative value. */ |
| msecs = -1; |
| } |
| do { |
| result = poll(&pfd, 1, msecs); |
| } while (result == -1 && errno == EINTR); |
| |
| /* Timeout or poll internal error */ |
| if (result <= 0) { |
| return (result); |
| } |
| if (pfd.revents & POLLERR) { |
| return -1; |
| } |
| |
| /* Return 1 if the event was not an error */ |
| return (pfd.revents & pfd.events ? 1 : -1); |
| } |
| |
| static BIO * |
| _BIO_new_base64(void) |
| { |
| BIO *b64; |
| |
| b64 = BIO_push(BIO_new(BIO_f_base64()), BIO_new(BIO_s_mem())); |
| BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL); |
| return (b64); |
| } |
| |
| /* |
| * Establishes the connection to the Duo server. On successful return, |
| * req->cbio is connected and ready to use. |
| * Return HTTPS_OK on success, error code on failure. |
| */ |
| static HTTPScode |
| _establish_connection(struct https_request * const req, |
| const char * const api_host, |
| const char * const api_port) |
| { |
| #ifndef HAVE_GETADDRINFO |
| /* Systems that don't have getaddrinfo can use the BIO |
| wrappers, but only get IPv4 support. */ |
| int n; |
| |
| if ((req->cbio = BIO_new(BIO_s_connect())) == NULL) { |
| ctx.errstr = _SSL_strerror(); |
| return HTTPS_ERR_LIB; |
| } |
| BIO_set_conn_hostname(req->cbio, api_host); |
| BIO_set_conn_port(req->cbio, api_port); |
| BIO_set_nbio(req->cbio, 1); |
| |
| while (BIO_do_connect(req->cbio) <= 0) { |
| if ((n = _BIO_wait(req->cbio, 10000)) != 1) { |
| ctx.errstr = n ? _SSL_strerror() : "Connection timed out"; |
| return (n ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER); |
| } |
| } |
| |
| return HTTPS_OK; |
| |
| #else /* HAVE_GETADDRINFO */ |
| |
| /* IPv6 Support |
| * BIO wrapped io does not support IPv6 addressing. To work around, |
| * resolve the address and connect the socket manually. Then pass |
| * the connected socket to the BIO wrapper with BIO_new_socket. |
| */ |
| int connected_socket = -1; |
| int socket_error = 0; |
| /* Address Lookup */ |
| struct addrinfo *res = NULL; |
| struct addrinfo *cur_res = NULL; |
| struct addrinfo hints; |
| int error; |
| |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_family = PF_UNSPEC; |
| hints.ai_socktype = SOCK_STREAM; |
| |
| error = getaddrinfo( |
| api_host, |
| api_port, |
| &hints, |
| &res |
| ); |
| if (error) { |
| ctx.errstr = gai_strerror(error); |
| return HTTPS_ERR_SYSTEM; |
| } |
| |
| /* Connect */ |
| for (cur_res = res; cur_res; cur_res = cur_res->ai_next) { |
| int sock_flags; |
| |
| connected_socket = socket( |
| cur_res->ai_family, |
| cur_res->ai_socktype, |
| cur_res->ai_protocol |
| ); |
| if (connected_socket == -1) { |
| continue; |
| } |
| sock_flags = fcntl(connected_socket, F_GETFL, 0); |
| fcntl(connected_socket, F_SETFL, sock_flags|O_NONBLOCK); |
| |
| if (connect(connected_socket, cur_res->ai_addr, cur_res->ai_addrlen) != 0 && |
| errno != EINPROGRESS) { |
| close(connected_socket); |
| connected_socket = -1; |
| continue; |
| } |
| socket_error = _fd_wait(connected_socket, 10000); |
| if (socket_error != 1) { |
| close(connected_socket); |
| connected_socket = -1; |
| continue; |
| } |
| /* Connected! */ |
| break; |
| } |
| cur_res = NULL; |
| freeaddrinfo(res); |
| res = NULL; |
| |
| if (connected_socket == -1) { |
| ctx.errstr = "Failed to connect"; |
| return socket_error ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER; |
| } |
| |
| if ((req->cbio = BIO_new_socket(connected_socket, BIO_CLOSE)) == NULL) { |
| ctx.errstr = _SSL_strerror(); |
| return (HTTPS_ERR_LIB); |
| } |
| BIO_set_conn_hostname(req->cbio, api_host); |
| BIO_set_conn_port(req->cbio, api_port); |
| BIO_set_nbio(req->cbio, 1); |
| |
| return HTTPS_OK; |
| |
| #endif /* HAVE_GETADDRINFO */ |
| } |
| |
| /* Provide implementations for HMAC_CTX_new and HMAC_CTX_free when |
| * building for OpenSSL versions older than 1.1.0 |
| * or LibreSSL versions older than 2.7.0 |
| */ |
| #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2070000fL |
| static HMAC_CTX * |
| HMAC_CTX_new(void) |
| { |
| HMAC_CTX *ctx = OPENSSL_malloc(sizeof(*ctx)); |
| if (ctx != NULL) { |
| HMAC_CTX_init(ctx); |
| } |
| return ctx; |
| } |
| |
| static void |
| HMAC_CTX_free(HMAC_CTX *ctx) |
| { |
| if (ctx != NULL) { |
| HMAC_CTX_cleanup(ctx); |
| OPENSSL_free(ctx); |
| } |
| } |
| #endif |
| |
| HTTPScode |
| https_init(const char *cafile, const char *http_proxy) |
| { |
| X509_STORE *store; |
| X509 *cert; |
| BIO *bio; |
| char *p; |
| |
| /* Initialize SSL context */ |
| #ifdef HAVE_X509_TEA_SET_STATE |
| /* If applicable, disable use of Apple's Trust Evaluation Agent for certificate |
| * validation, to enforce proper CA pinning: |
| * http://www.opensource.apple.com/source/OpenSSL098/OpenSSL098-35.1/src/crypto/x509/x509_vfy_apple.h |
| */ |
| X509_TEA_set_state(0); |
| #endif |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| OpenSSL_add_all_algorithms(); |
| |
| /* XXX - ape openssl s_client -rand for testing on ancient systems */ |
| if (!RAND_status()) { |
| if ((p = getenv("RANDFILE")) != NULL) { |
| RAND_load_file(p, 8192); |
| } else { |
| ctx.errstr = "No /dev/random, EGD, or $RANDFILE"; |
| return (HTTPS_ERR_LIB); |
| } |
| } |
| if ((ctx.ssl_ctx = SSL_CTX_new(SSLv23_client_method())) == NULL) { |
| ctx.errstr = _SSL_strerror(); |
| return (HTTPS_ERR_LIB); |
| } |
| /* Blacklist SSLv23 */ |
| const long blacklist = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; |
| SSL_CTX_set_options(ctx.ssl_ctx, blacklist); |
| /* Set up our CA cert */ |
| if (cafile == NULL) { |
| /* Load default CA cert from memory */ |
| if ((bio = BIO_new_mem_buf((void *)CACERT_PEM, -1)) == NULL || |
| (store = SSL_CTX_get_cert_store(ctx.ssl_ctx)) == NULL) { |
| ctx.errstr = _SSL_strerror(); |
| return (HTTPS_ERR_LIB); |
| } |
| while ((cert = PEM_read_bio_X509(bio, NULL, 0, NULL)) != NULL) { |
| X509_STORE_add_cert(store, cert); |
| X509_free(cert); |
| } |
| BIO_free_all(bio); |
| SSL_CTX_set_verify(ctx.ssl_ctx, SSL_VERIFY_PEER, NULL); |
| } else if (cafile[0] == '\0') { |
| /* Skip verification */ |
| SSL_CTX_set_verify(ctx.ssl_ctx, SSL_VERIFY_NONE, NULL); |
| } else { |
| /* Load CA cert from file */ |
| if (!SSL_CTX_load_verify_locations(ctx.ssl_ctx, |
| cafile, NULL)) { |
| SSL_CTX_free(ctx.ssl_ctx); |
| ctx.errstr = _SSL_strerror(); |
| return (HTTPS_ERR_CLIENT); |
| } |
| SSL_CTX_set_verify(ctx.ssl_ctx, SSL_VERIFY_PEER, NULL); |
| } |
| /* Save our proxy config if any */ |
| if (http_proxy != NULL) { |
| if (strstr(http_proxy, "://") != NULL) { |
| if (strncmp(http_proxy, "http://", 7) != 0) { |
| ctx.errstr = "http_proxy must be HTTP"; |
| return (HTTPS_ERR_CLIENT); |
| } |
| http_proxy += 7; |
| } |
| p = strdup(http_proxy); |
| |
| if ((ctx.proxy = strchr(p, '@')) != NULL) { |
| *ctx.proxy++ = '\0'; |
| ctx.proxy_auth = p; |
| } else { |
| ctx.proxy = p; |
| } |
| strtok(ctx.proxy, "/"); |
| |
| if ((ctx.proxy_port = strchr(ctx.proxy, ':')) != NULL) { |
| *ctx.proxy_port++ = '\0'; |
| } else { |
| ctx.proxy_port = "80"; |
| } |
| } |
| /* Set HTTP parser callbacks */ |
| ctx.parse_settings.on_body = __on_body; |
| ctx.parse_settings.on_message_complete = __on_message_complete; |
| |
| signal(SIGPIPE, SIG_IGN); |
| |
| return (0); |
| } |
| |
| HTTPScode |
| https_open(struct https_request **reqp, const char *host, const char *useragent) |
| { |
| struct https_request *req; |
| BIO *b64, *sbio; |
| char *p; |
| int n; |
| int connection_error = 0; |
| |
| const char *api_host; |
| const char *api_port; |
| /* Set up our handle */ |
| n = 1; |
| if ((req = calloc(1, sizeof(*req))) == NULL || |
| (req->host = strdup(host)) == NULL || |
| (req->parser = malloc(sizeof(http_parser))) == NULL) { |
| ctx.errstr = strerror(errno); |
| https_close(&req); |
| return (HTTPS_ERR_SYSTEM); |
| } |
| if ((p = strchr(req->host, ':')) != NULL) { |
| *p = '\0'; |
| req->port = p + 1; |
| } else { |
| req->port = "443"; |
| } |
| if ((req->body = BIO_new(BIO_s_mem())) == NULL) { |
| ctx.errstr = _SSL_strerror(); |
| https_close(&req); |
| return (HTTPS_ERR_LIB); |
| } |
| http_parser_init(req->parser, HTTP_RESPONSE); |
| req->parser->data = req; |
| |
| /* Connect to server */ |
| if (ctx.proxy) { |
| api_host = ctx.proxy; |
| api_port = ctx.proxy_port; |
| } else { |
| api_host = req->host; |
| api_port = req->port; |
| } |
| |
| connection_error = _establish_connection(req, api_host, api_port); |
| if (connection_error != HTTPS_OK) { |
| https_close(&req); |
| return connection_error; |
| } |
| |
| /* Tunnel through proxy, if specified */ |
| if (ctx.proxy != NULL) { |
| BIO_printf(req->cbio, |
| "CONNECT %s:%s HTTP/1.0\r\n" |
| "User-Agent: %s\r\n", |
| req->host, req->port, useragent |
| ); |
| |
| if (ctx.proxy_auth != NULL) { |
| b64 = _BIO_new_base64(); |
| BIO_write(b64, ctx.proxy_auth, |
| strlen(ctx.proxy_auth)); |
| (void)BIO_flush(b64); |
| n = BIO_get_mem_data(b64, &p); |
| |
| BIO_puts(req->cbio, "Proxy-Authorization: Basic "); |
| BIO_write(req->cbio, p, n); |
| BIO_puts(req->cbio, "\r\n"); |
| BIO_free_all(b64); |
| } |
| BIO_puts(req->cbio, "\r\n"); |
| (void)BIO_flush(req->cbio); |
| |
| while ((n = BIO_read(req->cbio, ctx.parse_buf, |
| sizeof(ctx.parse_buf))) <= 0) { |
| if ((n = _BIO_wait(req->cbio, 5000)) != 1) { |
| if (n == 0) { |
| ctx.errstr = "Proxy connection timed out"; |
| } else { |
| ctx.errstr = "Proxy connection error"; |
| } |
| https_close(&req); |
| return HTTPS_ERR_SERVER; |
| } |
| } |
| /* Tolerate HTTP proxies that respond with an |
| incorrect HTTP version number */ |
| if ((strncmp("HTTP/1.0 200", ctx.parse_buf, 12) != 0) |
| && (strncmp("HTTP/1.1 200", ctx.parse_buf, 12) != 0)) { |
| snprintf(ctx.errbuf, sizeof(ctx.errbuf), |
| "Proxy error: %s", ctx.parse_buf); |
| ctx.errstr = strtok(ctx.errbuf, "\r\n"); |
| https_close(&req); |
| if (n < 12 || atoi(ctx.parse_buf + 9) < 500) { |
| return (HTTPS_ERR_CLIENT); |
| } |
| return (HTTPS_ERR_SERVER); |
| } |
| } |
| /* Establish SSL connection */ |
| if ((sbio = BIO_new_ssl(ctx.ssl_ctx, 1)) == NULL) { |
| https_close(&req); |
| return (HTTPS_ERR_LIB); |
| } |
| |
| req->cbio = BIO_push(sbio, req->cbio); |
| BIO_get_ssl(req->cbio, &req->ssl); |
| |
| #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME |
| /* Enable SNI support */ |
| if ((n = SSL_set_tlsext_host_name(req->ssl, req->host)) != 1) { |
| ctx.errstr = "Setting SNI failed"; |
| https_close(&req); |
| return (HTTPS_ERR_LIB); |
| } |
| #endif |
| |
| while (BIO_do_handshake(req->cbio) <= 0) { |
| if ((n = _BIO_wait(req->cbio, 5000)) != 1) { |
| ctx.errstr = n ? _SSL_strerror() : "SSL handshake timed out"; |
| https_close(&req); |
| return (n ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER); |
| } |
| } |
| /* Validate server certificate name */ |
| if (_SSL_check_server_cert(req->ssl, req->host) != 1) { |
| ctx.errstr = "Certificate name validation failed"; |
| https_close(&req); |
| return (HTTPS_ERR_LIB); |
| } |
| *reqp = req; |
| |
| return (HTTPS_OK); |
| } |
| |
| static int |
| __argv_cmp(const void *a0, const void *b0) |
| { |
| const char **a = (const char **)a0; |
| const char **b = (const char **)b0; |
| |
| return (strcmp(*a, *b)); |
| } |
| |
| static char * |
| _argv_to_qs(int argc, char *argv[]) |
| { |
| BIO *bio; |
| BUF_MEM *bp; |
| char *p; |
| int i; |
| |
| if ((bio = BIO_new(BIO_s_mem())) == NULL) { |
| return (NULL); |
| } |
| qsort(argv, argc, sizeof(argv[0]), __argv_cmp); |
| |
| for (i = 0; i < argc; i++) { |
| BIO_printf(bio, "&%s", argv[i]); |
| } |
| BIO_get_mem_ptr(bio, &bp); |
| if (bp->length && (p = malloc(bp->length)) != NULL) { |
| memcpy(p, bp->data + 1, bp->length - 1); |
| p[bp->length - 1] = '\0'; |
| } else { |
| p = strdup(""); |
| } |
| BIO_free_all(bio); |
| |
| return (p); |
| } |
| |
| HTTPScode |
| https_send(struct https_request *req, const char *method, const char *uri, |
| int argc, char *argv[], const char *ikey, const char *skey, const char *useragent) |
| { |
| BIO *b64; |
| HMAC_CTX *hmac; |
| unsigned char MD[SHA_DIGEST_LENGTH]; |
| char *qs, *p; |
| int i, n, is_get; |
| |
| req->done = 0; |
| |
| /* Generate query string and canonical request to sign */ |
| if ((qs = _argv_to_qs(argc, argv)) == NULL || |
| (asprintf(&p, "%s\n%s\n%s\n%s", method, req->host, uri, qs)) < 0) { |
| free(qs); |
| ctx.errstr = strerror(errno); |
| return (HTTPS_ERR_LIB); |
| } |
| /* Format request */ |
| if ((is_get = (strcmp(method, "GET") == 0))) { |
| BIO_printf(req->cbio, "GET %s?%s HTTP/1.1\r\n", uri, qs); |
| } else { |
| BIO_printf(req->cbio, "%s %s HTTP/1.1\r\n", method, uri); |
| } |
| if (strcmp(req->port, "443") == 0) { |
| BIO_printf(req->cbio, "Host: %s\r\n", req->host); |
| } else { |
| BIO_printf(req->cbio, "Host: %s:%s\r\n", req->host, req->port); |
| } |
| /* Add User-Agent header */ |
| BIO_printf(req->cbio, |
| "User-Agent: %s\r\n", |
| useragent); |
| /* Add signature */ |
| BIO_puts(req->cbio, "Authorization: Basic "); |
| |
| if ((hmac = HMAC_CTX_new()) == NULL) { |
| free(qs); |
| free(p); |
| ctx.errstr = strerror(errno); |
| return (HTTPS_ERR_LIB); |
| } |
| HMAC_Init(hmac, skey, strlen(skey), EVP_sha1()); |
| HMAC_Update(hmac, (unsigned char *)p, strlen(p)); |
| HMAC_Final(hmac, MD, NULL); |
| HMAC_CTX_free(hmac); |
| free(p); |
| |
| b64 = _BIO_new_base64(); |
| BIO_printf(b64, "%s:", ikey); |
| for (i = 0; i < sizeof(MD); i++) { |
| BIO_printf(b64, "%02x", MD[i]); |
| } |
| (void)BIO_flush(b64); |
| n = BIO_get_mem_data(b64, &p); |
| BIO_write(req->cbio, p, n); |
| BIO_free_all(b64); |
| |
| /* Finish request */ |
| if (!is_get) { |
| BIO_printf(req->cbio, |
| "\r\nContent-Type: application/x-www-form-urlencoded\r\n" |
| "Content-Length: %d\r\n\r\n%s", |
| (int)strlen(qs), qs); |
| } else { |
| BIO_puts(req->cbio, "\r\n\r\n"); |
| } |
| /* Send request */ |
| while (BIO_flush(req->cbio) != 1) { |
| if ((n = _BIO_wait(req->cbio, -1)) != 1) { |
| ctx.errstr = n ? _SSL_strerror() : "Write timed out"; |
| return (HTTPS_ERR_SERVER); |
| } |
| } |
| return (HTTPS_OK); |
| } |
| |
| HTTPScode |
| https_recv(struct https_request *req, int *code, const char **body, int *len, |
| int msecs) |
| { |
| int n, err; |
| |
| if (BIO_reset(req->body) != 1) { |
| ctx.errstr = _SSL_strerror(); |
| return (HTTPS_ERR_LIB); |
| } |
| /* Read loop sentinel set by parser in __on_message_done() */ |
| while (!req->done) { |
| while ((n = BIO_read(req->cbio, ctx.parse_buf, |
| sizeof(ctx.parse_buf))) <= 0) { |
| if ((n = _BIO_wait(req->cbio, msecs)) != 1) { |
| ctx.errstr = n ? _SSL_strerror() : "Connection closed"; |
| return (HTTPS_ERR_SERVER); |
| } |
| } |
| if ((err = http_parser_execute(req->parser, |
| &ctx.parse_settings, ctx.parse_buf, n)) != n) { |
| ctx.errstr = http_errno_description(err); |
| return (HTTPS_ERR_SERVER); |
| } |
| } |
| *len = BIO_get_mem_data(req->body, (char **)body); |
| *code = req->parser->status_code; |
| |
| return (HTTPS_OK); |
| } |
| |
| const char * |
| https_geterr(void) |
| { |
| const char *p = ctx.errstr; |
| ctx.errstr = NULL; |
| return (p); |
| } |
| |
| void |
| https_close(struct https_request **reqp) |
| { |
| struct https_request *req = *reqp; |
| |
| if (req != NULL) { |
| if (req->body != NULL) { |
| BIO_free_all(req->body); |
| } |
| if (req->cbio != NULL) { |
| BIO_free_all(req->cbio); |
| } |
| free(req->parser); |
| free(req->host); |
| free(req); |
| *reqp = NULL; |
| } |
| } |