/* * Soft: Keepalived is a failover program for the LVS project * . It monitor & manipulate * a loadbalanced server pool using multi-layer checks. * * Part: SSL GET CHECK. Perform an ssl get query to a specified * url, compute a MD5 over this result and match it to the * expected value. * * Authors: Alexandre Cassen, * Jan Holmberg, * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. * * Copyright (C) 2001-2017 Alexandre Cassen, */ #include "config.h" #include #include #include "check_ssl.h" #include "check_api.h" #include "check_http.h" #include "logger.h" #ifdef THREAD_DUMP #include "scheduler.h" #endif /* SSL primitives */ /* Free an SSL context */ void clear_ssl(ssl_data_t *ssl) { if (ssl && ssl->ctx) { SSL_CTX_free(ssl->ctx); ssl->ctx = NULL; } } /* PEM password callback function */ static int password_cb(char *buf, int num, __attribute__((unused)) int rwflag, void *userdata) { ssl_data_t *ssl = (ssl_data_t *) userdata; size_t plen = strlen(ssl->password); if ((unsigned)num < plen + 1) return (0); strcpy(buf, ssl->password); return (int)plen; } /* Inititalize global SSL context */ static bool build_ssl_ctx(void) { ssl_data_t *ssl; /* Library initialization */ #if HAVE_OPENSSL_INIT_CRYPTO if (!OPENSSL_init_crypto(OPENSSL_INIT_NO_LOAD_CONFIG, NULL)) log_message(LOG_INFO, "OPENSSL_init_crypto failed"); #else SSL_library_init(); SSL_load_error_strings(); #endif if (!check_data->ssl) ssl = (ssl_data_t *) MALLOC(sizeof(ssl_data_t)); else ssl = check_data->ssl; /* Initialize SSL context */ #if HAVE_TLS_METHOD ssl->meth = TLS_method(); #else ssl->meth = SSLv23_method(); #endif if (!(ssl->ctx = SSL_CTX_new(ssl->meth))) { log_message(LOG_INFO, "SSL error: cannot create new SSL context"); return false; } /* return for autogen context */ if (!check_data->ssl) { check_data->ssl = ssl; goto end; } /* Load our keys and certificates */ if (check_data->ssl->certfile) if (! (SSL_CTX_use_certificate_chain_file (ssl->ctx, check_data->ssl->certfile))) { log_message(LOG_INFO, "SSL error : Cant load certificate file..."); return false; } /* Handle password callback using userdata ssl */ if (check_data->ssl->password) { SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, check_data->ssl); SSL_CTX_set_default_passwd_cb(ssl->ctx, password_cb); } if (check_data->ssl->keyfile) if (! (SSL_CTX_use_PrivateKey_file (ssl->ctx, check_data->ssl->keyfile, SSL_FILETYPE_PEM))) { log_message(LOG_INFO, "SSL error : Cant load key file..."); return false; } /* Load the CAs we trust */ if (check_data->ssl->cafile) if (! (SSL_CTX_load_verify_locations (ssl->ctx, check_data->ssl->cafile, 0))) { log_message(LOG_INFO, "SSL error : Cant load CA file..."); return false; } end: #if HAVE_SSL_CTX_SET_VERIFY_DEPTH SSL_CTX_set_verify_depth(ssl->ctx, 1); #endif return true; } /* * Initialize the SSL context, with or without specific * configuration files. */ bool init_ssl_ctx(void) { ssl_data_t *ssl = check_data->ssl; if (!build_ssl_ctx()) { log_message(LOG_INFO, "Error Initialize SSL, ctx Instance"); log_message(LOG_INFO, " SSL keyfile:%s", ssl->keyfile); log_message(LOG_INFO, " SSL password:%s", ssl->password); log_message(LOG_INFO, " SSL cafile:%s", ssl->cafile); log_message(LOG_INFO, "Terminate..."); clear_ssl(ssl); return false; } return true; } /* Display SSL error to readable string */ int ssl_printerr(int err) { switch (err) { case SSL_ERROR_ZERO_RETURN: log_message(LOG_INFO, " SSL error: (zero return)"); break; case SSL_ERROR_WANT_READ: log_message(LOG_INFO, " SSL error: (read error)"); break; case SSL_ERROR_WANT_WRITE: log_message(LOG_INFO, " SSL error: (write error)"); break; case SSL_ERROR_WANT_CONNECT: log_message(LOG_INFO, " SSL error: (connect error)"); break; case SSL_ERROR_WANT_X509_LOOKUP: log_message(LOG_INFO, " SSL error: (X509 lookup error)"); break; case SSL_ERROR_SYSCALL: log_message(LOG_INFO, " SSL error: (syscall error)"); break; case SSL_ERROR_SSL: /* Note: the following is not thread safe. Use MALLOC(256) and ERR_error_string_n if need thread safety */ log_message(LOG_INFO, " SSL error: (%s)", ERR_error_string(ERR_get_error(), NULL)); break; } return 0; } int ssl_connect(thread_t * thread, int new_req) { checker_t *checker = THREAD_ARG(thread); http_checker_t *http_get_check = CHECKER_ARG(checker); request_t *req = http_get_check->req; #ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_ url_t *url = list_element(http_get_check->url, http_get_check->url_it); char* vhost = NULL; #endif int ret = 0; /* First round, create SSL context */ if (new_req) { int bio_fd; if (!(req->ssl = SSL_new(check_data->ssl->ctx))) { log_message(LOG_INFO, "Unable to establish ssl connection - SSL_new() failed"); return 0; } if (!(req->bio = BIO_new_socket(thread->u.fd, BIO_NOCLOSE))) { log_message(LOG_INFO, "Unable to establish ssl connection - BIO_new_socket() failed"); return 0; } BIO_get_fd(req->bio, &bio_fd); fcntl(bio_fd, F_SETFD, fcntl(bio_fd, F_GETFD) | FD_CLOEXEC); #if HAVE_SSL_SET0_RBIO BIO_up_ref(req->bio); SSL_set0_rbio(req->ssl, req->bio); SSL_set0_wbio(req->ssl, req->bio); #else SSL_set_bio(req->ssl, req->bio, req->bio); #endif #ifdef _HAVE_SSL_SET_TLSEXT_HOST_NAME_ if (http_get_check->enable_sni) { if (url && url->virtualhost) vhost = url->virtualhost; else if (http_get_check->virtualhost) vhost = http_get_check->virtualhost; else if (checker->vs->virtualhost) vhost = checker->vs->virtualhost; if (vhost) SSL_set_tlsext_host_name(req->ssl, vhost); } #endif } ret = SSL_connect(req->ssl); return ret; } bool ssl_send_request(SSL * ssl, char *str_request, int request_len) { int err, r = 0; while (true) { err = 1; r = SSL_write(ssl, str_request, request_len); if (SSL_ERROR_NONE != SSL_get_error(ssl, r)) break; err++; if (request_len != r) break; err++; break; } return (err == 3); } /* Asynchronous SSL stream reader */ int ssl_read_thread(thread_t * thread) { checker_t *checker = THREAD_ARG(thread); http_checker_t *http_get_check = CHECKER_ARG(checker); request_t *req = http_get_check->req; url_t *url = list_element(http_get_check->url, http_get_check->url_it); unsigned timeout = checker->co->connection_to; unsigned char digest[MD5_DIGEST_LENGTH]; int r = 0; /* Handle read timeout */ if (thread->type == THREAD_READ_TIMEOUT && !req->extracted) return timeout_epilog(thread, "Timeout SSL read"); /* read the SSL stream */ r = SSL_read(req->ssl, req->buffer + req->len, (int)(MAX_BUFFER_LENGTH - req->len)); req->error = SSL_get_error(req->ssl, r); if (req->error == SSL_ERROR_WANT_READ) { /* async read unfinished */ thread_add_read(thread->master, ssl_read_thread, checker, thread->u.fd, timeout); } else if (r > 0 && req->error == 0) { /* Handle response stream */ http_process_response(req, (size_t)r, url); /* * Register next ssl stream reader. * Register itself to not perturbe global I/O multiplexer. */ thread_add_read(thread->master, ssl_read_thread, checker, thread->u.fd, timeout); } else if (req->error) { /* All the SSL streal has been parsed */ if (url->digest) MD5_Final(digest, &req->context); SSL_set_quiet_shutdown(req->ssl, 1); r = (req->error == SSL_ERROR_ZERO_RETURN) ? SSL_shutdown(req->ssl) : 0; if (r && !req->extracted) { return timeout_epilog(thread, "SSL read error from"); } /* Handle response stream */ http_handle_response(thread, digest, !req->extracted); } return 0; } #ifdef THREAD_DUMP void register_check_ssl_addresses(void) { register_thread_address("ssl_read_thread", ssl_read_thread); } #endif