/* * Copyright (C), 2000-2002 by Contributors to the monit codebase. * All Rights Reserved. * * 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. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* client socket clean up -> close */ /* close client socket */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "monitor.h" #include "net.h" #include "ssl.h" /* Private prototypes */ static int ssl_thread_start(void); static int ssl_thread_stop(void); static int unsigned long ssl_thread_id(void); static void ssl_mutex_lock(int mode, int n, const char *file, int line); static int ssl_entropy_start(void); static int ssl_entropy_stop(void); /* To be removed if included in the real monit!!!!!! */ /* void error(const char *format, ...) { */ /* char msg[1024]; */ /* va_list ap; */ /* va_start(ap,format); */ /* vsnprintf(msg, 1024, format, ap); */ /* va_end(ap); */ /* fprintf(stderr,"%s", msg); */ /* fflush(stderr); */ /* } */ static pthread_mutex_t ssl_mutex= PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t * ssl_mutex_table; /** * Number of random bytes to obtain */ #define RANDOM_BYTES 2048 /** * The PRIMARY random device selected for seeding the PRNG. We use a * non-blocking pseudo random device, to generate pseudo entropy. */ #define URANDOM_DEVICE "/dev/urandom" /** * If a non-blocking device is not found on the system a blocking * entropy producer is tried instead. */ #define RANDOM_DEVICE "/dev/random" /** * SSL Socket methods. * * @author Christian Hopp * @author Jan-Henrik Haukeland, * * @version \$Id: ssl.c,v 1.1 2002/09/25 14:54:49 chopp Exp $ * * @file */ /* ------------------------------------------------------------------ Public */ /** * Embeds a socket in a ssl connection. * @param socket the socket to be used. * @return The ssl connection or NULL if an error occured. */ int embed_ssl_socket (ssl_connection * ssl, int socket) { if ( ssl == NULL ) { return FALSE; } if ( socket >= 0 ) { ssl->socket= socket; } else { error("Socket error!\n"); goto sslerror; } if ((ssl->handler= SSL_new (ssl->ctx)) == NULL ) { error("Cannot initilize SSL handler!\n"); goto sslerror; } SSL_set_fd (ssl->handler, ssl->socket); if (SSL_connect (ssl->handler) == -1) { error("Cannot connect with SSL service!\n"); goto sslerror; } ssl->cipher= (char *) SSL_get_cipher(ssl->handler); if ((ssl->cert = SSL_get_peer_certificate (ssl->handler)) == NULL) { error("Cannot get SSL server certificate!\n"); goto sslerror; } ssl->cert_issuer=X509_NAME_oneline (X509_get_issuer_name (ssl->cert),0,0); ssl->cert_subject=X509_NAME_oneline (X509_get_subject_name (ssl->cert),0,0); return TRUE; sslerror: cleanup_ssl_socket(ssl); return FALSE; } /** * Open a socket against hostname:port with the given protocol. * This socket is sent through a ssl connection. * The protocol is normaly either SOCK_STREAM or SOCK_DGRAM. * @param hostname The host to open a socket at * @param port The port number to connect to * @param protocol Socket protocol to use (SOCK_STREAM|SOCK_DGRAM) * @return The ssl connection or NULL if an error occured. */ ssl_connection * create_ssl_socket(char *hostname, int port, int protocol) { int socket; ssl_connection * ssl = new_ssl_connection(); if ((socket= create_socket(hostname, port, protocol)) == -1) { error("Cannot connect!\n"); goto sslerror; } if (! embed_ssl_socket(ssl, socket)) { error("Cannot embed socket!\n"); goto sslerror; } return ssl; sslerror: return NULL; } /** * Closes a ssl connection (ssl socket + net socket) * @param ssl ssl connection * @return TRUE, or FALSE if an error has occured. */ int close_ssl_socket(ssl_connection * ssl) { if(ssl == NULL) { return FALSE; } SSL_shutdown (ssl->handler); close(ssl->socket); cleanup_ssl_socket(ssl); return TRUE; } /** * Garbage collection for non reusabe parts of the ssl connection * @param ssl ssl connection * @return TRUE, or FALSE if an error has occured. */ int cleanup_ssl_socket(ssl_connection *ssl) { if(ssl==NULL) { return FALSE; } if (ssl->cert != NULL) { X509_free(ssl->cert); ssl->cert= NULL; } if (ssl->handler != NULL) { SSL_free(ssl->handler); ssl->handler= NULL; } if (ssl->cert_issuer != NULL) { free(ssl->cert_issuer); ssl->cert_issuer= NULL; } if (ssl->cert_subject != NULL) { free(ssl->cert_subject); ssl->cert_subject= NULL; } return TRUE; } /** * Garbage collection for non-reusable parts a ssl connection * @param ssl ssl connection * @return TRUE, or FALSE if an error has occured. */ int delete_ssl_socket(ssl_connection *ssl) { if(ssl==NULL) { return FALSE; } cleanup_ssl_socket(ssl); if((ssl->ctx != NULL) && (! ssl->accepted)) { SSL_CTX_free(ssl->ctx); } free(ssl); return TRUE; } /* Server code */ /** * Initilizes a ssl connection for server use. * @param pemfilename Filename for the key/cert file * @return An ssl connection, or NULL if an error occured. */ ssl_server_connection * init_ssl_server (char *pemfilename) { ssl_server_connection * ssl_server = new_ssl_server_connection(); /* new_ssl_server_connection()*/ if ((ssl_server->method= SSLv23_server_method()) == NULL ) { error("Cannot initilize SSL method!\n"); goto sslerror; } if ((ssl_server->ctx= SSL_CTX_new(ssl_server->method)) == NULL ) { error("Cannot initilize SSL server certificate handler!\n"); goto sslerror; } if (SSL_CTX_use_certificate_file(ssl_server->ctx, pemfilename, SSL_FILETYPE_PEM) <= 0) { error("Cannot initilize SSL server certificate!\n"); goto sslerror; } if (SSL_CTX_use_PrivateKey_file(ssl_server->ctx, pemfilename, SSL_FILETYPE_PEM) <= 0) { error("Cannot initilize SSL server private key!\n"); goto sslerror; } if (!SSL_CTX_check_private_key(ssl_server->ctx)) { error("Private key does not match the certificate public key!\n"); goto sslerror; } return ssl_server; sslerror: cleanup_ssl_server_socket(ssl_server); return NULL; } /** * Creates a server socket (SOCK_STREAM type) and binds it to the * specified local port number. The socket get a ssl layer for * data transmission. * @param pemfilename Filename for the key/cert file * @param port The localhost port number to open * @param backlog The maximum queue length for incomming connections * @param bindAddr the local address the server will bind to * @return An ssl connection ready for accept, or NULL if an error occured. */ ssl_server_connection * create_ssl_server_socket(char * pemfilename, int port, int backlog, char *bindAddr) { int socket; ssl_server_connection * ssl_server; if ((socket= create_server_socket(port, backlog, bindAddr)) == -1) { error("Cannot connect!\n"); goto sslerror; } if (( ssl_server= init_ssl_server (pemfilename)) == NULL) { goto sslerror; } ssl_server->server_socket=socket; return ssl_server; sslerror: return NULL; } /** * Closes a ssl server connection (ssl socket + net socket) * @param ssl ssl connection * @return TRUE, or FALSE if an error has occured. */ int close_ssl_server_socket(ssl_server_connection * ssl_server) { if (ssl_server==NULL) { return FALSE; } close(ssl_server->server_socket); cleanup_ssl_server_socket(ssl_server); return TRUE; } /** * Garbage collection for a SSL server connection. * @param ssl_server data for ssl server connection * @return TRUE, or FALSE if an error has occured. */ int cleanup_ssl_server_socket(ssl_server_connection *ssl_server) { ssl_connection * ssl; if(ssl_server==NULL) { return FALSE; } while (ssl_server->ssl_conn_list!=NULL) { ssl = ssl_server->ssl_conn_list; ssl_server->ssl_conn_list=ssl_server->ssl_conn_list->next; close_accepted_ssl_socket(ssl_server, ssl); delete_ssl_socket(ssl); } return TRUE; } /** * Deletes a SSL server connection. * @param ssl_server data for ssl server connection * @return TRUE, or FALSE if an error has occured. */ int delete_ssl_server_socket(ssl_server_connection *ssl_server) { if(ssl_server==NULL) { return FALSE; } cleanup_ssl_server_socket(ssl_server); if (ssl_server->ctx != NULL) { SSL_CTX_free(ssl_server->ctx); } free(ssl_server); return TRUE; } /** * Inserts an SSL connection in the connection list of a server. * @param ssl_server data for ssl server connection * @return new SSL connection for the connection, or NULL if failed */ ssl_connection * insert_accepted_ssl_socket (ssl_server_connection * ssl_server) { ssl_connection * ssl = new_ssl_connection(); if(( ssl_server == NULL ) || (ssl == NULL)) { return NULL; } LOCK(ssl_mutex); ssl->prev=NULL; ssl->next=ssl_server->ssl_conn_list; if( ssl->next != NULL ) { ssl->next->prev=ssl; } END_LOCK; ssl_server->ssl_conn_list=ssl; ssl->ctx=ssl_server->ctx; ssl->accepted=TRUE; return ssl; } /** * Closes an accepted SSL server connection and deletes it form the * connection list. * @param ssl_server data for ssl server connection * @param ssl data the connection to be deleted * @return TRUE, or FALSE if an error has occured. */ int close_accepted_ssl_socket(ssl_server_connection * ssl_server, ssl_connection * ssl) { if ((ssl == NULL) || (ssl_server == NULL)) { return FALSE; } return ((close(ssl->socket) >= 0) && delete_accepted_ssl_socket(ssl_server, ssl)); return TRUE; } /** * Deletes an accepted SSL server connection from the connection * list. * @param ssl_server data for ssl server connection * @param ssl data the connection to be deleted * @return TRUE, or FALSE if an error has occured. */ int delete_accepted_ssl_socket (ssl_server_connection * ssl_server, ssl_connection * ssl) { if ((ssl == NULL) || (ssl_server == NULL)) { return FALSE; } LOCK(ssl_mutex); if ( ssl->prev == NULL ) { ssl_server->ssl_conn_list=ssl->next; } else { ssl->prev->next=ssl->next; } END_LOCK; cleanup_ssl_socket(ssl); delete_ssl_socket(ssl); return TRUE; } /** * Embeds an accepted server socket in an existing ssl connection. * @param ssl ssl connection * @param socket the socket to be used. * @return TRUE, or FALSE if an error has occured. */ int embed_accepted_ssl_socket(ssl_connection * ssl, int socket) { ssl->socket=socket; if ((ssl->handler= SSL_new(ssl->ctx)) == NULL ) { error("Cannot initilize SSL handler!\n"); } if ( socket >= 0 ) { SSL_set_fd(ssl->handler, ssl->socket); } else { error("Socket error!\n"); goto sslerror; } if (SSL_accept(ssl->handler) == -1) { error("Accept with SSL service has failed!\n"); goto sslerror; } ssl->cipher= (char *) SSL_get_cipher(ssl->handler); ssl->cert= SSL_get_peer_certificate(ssl->handler); if ( ssl->cert!= NULL ) { ssl->cert_issuer=X509_NAME_oneline(X509_get_issuer_name(ssl->cert), 0, 0); ssl->cert_subject=X509_NAME_oneline(X509_get_subject_name(ssl->cert), 0, 0); } return TRUE; sslerror: return FALSE; } /** * Do "accept" for a ssl server socket * @param ssl ssl connection * @return the ssl_connection of the socket, NULL in case of an error */ ssl_connection * accept_ssl_socket(ssl_server_connection * ssl_server) { int no_crypt_socket; int len= sizeof(struct sockaddr_in); struct sockaddr_in in; no_crypt_socket= accept(ssl_server->server_socket, (struct sockaddr*)&in, &len); if ( no_crypt_socket >= 0 ) { ssl_connection * ssl = insert_accepted_ssl_socket(ssl_server); if ( ssl == NULL ) { return NULL; } if (embed_accepted_ssl_socket(ssl, no_crypt_socket)) { return ssl; } else { close_accepted_ssl_socket(ssl_server, ssl); return NULL; } } else { return NULL; } } /* Generic code */ /** * Send data package though the ssl connection * @param ssl ssl connection * @param buffer array containg the data * @param len size of the data container * @return TRUE, or FALSE if an error has occured. */ int send_ssl_socket(ssl_connection * ssl, void * buffer, int len) { return SSL_write (ssl->handler, (void *) buffer, len); } /** * Receive data package though the ssl connection * @param ssl ssl connection * @param buffer array to hold the data * @param len size of the data container * @return TRUE, or FALSE if an error has occured. */ int recv_ssl_socket(ssl_connection * ssl, void * buffer, int len) { return SSL_read (ssl->handler, (void *) buffer, len); } /** * Start SSL support library. It has to be run before the SSL support * can be used. * @return TRUE, or FALSE if an error has occured. */ int start_ssl() { /* Init SSL stuff */ return (ssl_thread_start() && SSL_library_init() && ssl_entropy_start()); } /** * Stop SSL support library * @return TRUE, or FALSE if an error has occured. */ int stop_ssl() { return (ssl_thread_stop() && ssl_entropy_stop()); } /** * Generate a new ssl connection * @return ssl connection container */ ssl_connection * new_ssl_connection(void) { ssl_connection * ssl = (ssl_connection *) NEW(ssl); ssl->handler= NULL; ssl->cert= NULL; ssl->cipher= NULL; ssl->socket= 0; ssl->next = NULL; ssl->accepted = FALSE; if ((ssl->method= SSLv23_client_method()) == NULL ) { error("Cannot initilize SSL method!\n"); goto sslerror; } if ((ssl->ctx= SSL_CTX_new (ssl->method)) == NULL ) { error("Cannot initilize SSL server certificate handler!\n"); goto sslerror; } return ssl; sslerror: delete_ssl_socket(ssl); return NULL; } /** * Generate a new ssl server connection * @return ssl server connection container */ ssl_server_connection * new_ssl_server_connection(void) { ssl_server_connection * ssl_server = (ssl_server_connection *) NEW(ssl_server); ssl_server->ctx= NULL; ssl_server->method= NULL; ssl_server->server_socket= 0; ssl_server->ssl_conn_list = NULL; return ssl_server; } /* ----------------------------------------------------------------- Private */ /** * Helper function for the SSL threadding support * @return current thread number */ static int unsigned long ssl_thread_id(void) { return ((unsigned long) pthread_self()); } /** * Helper function for the SSL threadding support */ static void ssl_mutex_lock(int mode, int n, const char *file, int line) { if(mode & CRYPTO_LOCK) { pthread_mutex_lock( & ssl_mutex_table[n]); } else { pthread_mutex_unlock( & ssl_mutex_table[n]); } } /** * Initilize threadding support for the SSL library * @return TRUE if successful, FALSE if failed */ static int ssl_thread_start(void) { int i; ssl_mutex_table= xcalloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t)); for(i= 0; i < CRYPTO_num_locks(); i++) pthread_mutex_init(&ssl_mutex_table[i], NULL); CRYPTO_set_id_callback(ssl_thread_id); CRYPTO_set_locking_callback(ssl_mutex_lock); return TRUE; } /** * Garbage colletion for the SSL threadding support * @return TRUE if successful, FALSE if failed */ static int ssl_thread_stop(void) { int i; CRYPTO_set_id_callback(NULL); CRYPTO_set_locking_callback(NULL); for(i= 0; i < CRYPTO_num_locks(); i++) pthread_mutex_destroy(&ssl_mutex_table[i]); free(ssl_mutex_table); ssl_mutex_table= NULL; return TRUE; } /** * Start entropy gathering * @return TRUE if successful, FALSE if failed */ static int ssl_entropy_start(void) { if(exist_file(URANDOM_DEVICE)) { return(RAND_load_file(URANDOM_DEVICE, RANDOM_BYTES)==RANDOM_BYTES); } else if(exist_file(RANDOM_DEVICE)) { fprintf(stdout, "Gathering entropy from the random device. Please wait\n"); fflush(stdout); return(RAND_load_file(RANDOM_DEVICE, RANDOM_BYTES)==RANDOM_BYTES); } // FIXME: log/warn PRNG not seeded properly return FALSE; } /** * Stop entropy gathering * @return TRUE if successful, FALSE if failed */ static int ssl_entropy_stop(void) { RAND_cleanup(); return TRUE; }