Edit

kc3-lang/libevent/bufferevent_openssl.c

Branch :

  • Show log

    Commit

  • Author : Azat Khuzhin
    Date : 2023-05-16 21:16:33
    Hash : e5941b2c
    Message : Drop unused le_ssl_ops::err_is_ok It is not used anymore since errors are ignored if some progress had been done.

  • bufferevent_openssl.c
  • /*
     * Copyright (c) 2009-2012 Niels Provos and Nick Mathewson
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     * 3. The name of the author may not be used to endorse or promote products
     *    derived from this software without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    
    #include <string.h>
    
    #include <openssl/ssl.h>
    #include <openssl/err.h>
    #include "openssl-compat.h"
    
    #include "event2/bufferevent.h"
    #include "event2/bufferevent_struct.h"
    #include "event2/buffer.h"
    
    #include "ssl-compat.h"
    
    /*
     * Define an OpenSSL bio that targets a bufferevent.
     */
    
    /* --------------------
       A BIO is an OpenSSL abstraction that handles reading and writing data.  The
       library will happily speak SSL over anything that implements a BIO
       interface.
    
       Here we define a BIO implementation that directs its output to a
       bufferevent.  We'll want to use this only when none of OpenSSL's built-in
       IO mechanisms work for us.
       -------------------- */
    
    /* every BIO type needs its own integer type value. */
    #define BIO_TYPE_LIBEVENT 57
    /* ???? Arguably, we should set BIO_TYPE_FILTER or BIO_TYPE_SOURCE_SINK on
     * this. */
    
    #if 0
    static void
    print_err(int val)
    {
    	int err;
    	printf("Error was %d\n", val);
    
    	while ((err = ERR_get_error())) {
    		const char *msg = (const char*)ERR_reason_error_string(err);
    		const char *lib = (const char*)ERR_lib_error_string(err);
    		const char *func = (const char*)ERR_func_error_string(err);
    
    		printf("%s in %s %s\n", msg, lib, func);
    	}
    }
    #else
    static void
    print_err(int val)
    {
    }
    #endif
    
    /* Called to initialize a new BIO */
    static int
    bio_bufferevent_new(BIO *b)
    {
    	BIO_set_init(b, 0);
    	BIO_set_data(b, NULL); /* We'll be putting the bufferevent in this field.*/
    	return 1;
    }
    
    /* Called to uninitialize the BIO. */
    static int
    bio_bufferevent_free(BIO *b)
    {
    	if (!b)
    		return 0;
    	if (BIO_get_shutdown(b)) {
    		if (BIO_get_init(b) && BIO_get_data(b))
    			bufferevent_free(BIO_get_data(b));
    		BIO_free(b);
    	}
    	return 1;
    }
    
    /* Called to extract data from the BIO. */
    static int
    bio_bufferevent_read(BIO *b, char *out, int outlen)
    {
    	int r = 0;
    	struct evbuffer *input;
    
    	BIO_clear_retry_flags(b);
    
    	if (!out)
    		return 0;
    	if (!BIO_get_data(b))
    		return -1;
    
    	input = bufferevent_get_input(BIO_get_data(b));
    	if (evbuffer_get_length(input) == 0) {
    		/* If there's no data to read, say so. */
    		BIO_set_retry_read(b);
    		return -1;
    	} else {
    		r = evbuffer_remove(input, out, outlen);
    	}
    
    	return r;
    }
    
    /* Called to write data into the BIO */
    static int
    bio_bufferevent_write(BIO *b, const char *in, int inlen)
    {
    	struct bufferevent *bufev = BIO_get_data(b);
    	struct evbuffer *output;
    	size_t outlen;
    
    	BIO_clear_retry_flags(b);
    
    	if (!BIO_get_data(b))
    		return -1;
    
    	output = bufferevent_get_output(bufev);
    	outlen = evbuffer_get_length(output);
    
    	/* Copy only as much data onto the output buffer as can fit under the
    	 * high-water mark. */
    	if (bufev->wm_write.high && bufev->wm_write.high <= (outlen + inlen)) {
    		if (bufev->wm_write.high <= outlen) {
    			/* If no data can fit, we'll need to retry later. */
    			BIO_set_retry_write(b);
    			return -1;
    		}
    		inlen = bufev->wm_write.high - outlen;
    	}
    
    	EVUTIL_ASSERT(inlen > 0);
    	evbuffer_add(output, in, inlen);
    	return inlen;
    }
    
    /* Called to handle various requests */
    static long
    bio_bufferevent_ctrl(BIO *b, int cmd, long num, void *ptr)
    {
    	struct bufferevent *bufev = BIO_get_data(b);
    	long ret = 1;
    
    	switch (cmd) {
    	case BIO_CTRL_GET_CLOSE:
    		ret = BIO_get_shutdown(b);
    		break;
    	case BIO_CTRL_SET_CLOSE:
    		BIO_set_shutdown(b, (int)num);
    		break;
    	case BIO_CTRL_PENDING:
    		ret = evbuffer_get_length(bufferevent_get_input(bufev)) != 0;
    		break;
    	case BIO_CTRL_WPENDING:
    		ret = evbuffer_get_length(bufferevent_get_output(bufev)) != 0;
    		break;
    	/* XXXX These two are given a special-case treatment because
    	 * of cargo-cultism.  I should come up with a better reason. */
    	case BIO_CTRL_DUP:
    	case BIO_CTRL_FLUSH:
    		ret = 1;
    		break;
    	default:
    		ret = 0;
    		break;
    	}
    	return ret;
    }
    
    /* Called to write a string to the BIO */
    static int
    bio_bufferevent_puts(BIO *b, const char *s)
    {
    	return bio_bufferevent_write(b, s, strlen(s));
    }
    
    /* Method table for the bufferevent BIO */
    static BIO_METHOD *methods_bufferevent;
    
    /* Return the method table for the bufferevents BIO */
    static BIO_METHOD *
    BIO_s_bufferevent(void)
    {
    	if (methods_bufferevent == NULL) {
    		methods_bufferevent = BIO_meth_new(BIO_TYPE_LIBEVENT, "bufferevent");
    		if (methods_bufferevent == NULL)
    			return NULL;
    		BIO_meth_set_write(methods_bufferevent, bio_bufferevent_write);
    		BIO_meth_set_read(methods_bufferevent, bio_bufferevent_read);
    		BIO_meth_set_puts(methods_bufferevent, bio_bufferevent_puts);
    		BIO_meth_set_ctrl(methods_bufferevent, bio_bufferevent_ctrl);
    		BIO_meth_set_create(methods_bufferevent, bio_bufferevent_new);
    		BIO_meth_set_destroy(methods_bufferevent, bio_bufferevent_free);
    	}
    	return methods_bufferevent;
    }
    
    /* Create a new BIO to wrap communication around a bufferevent.  If close_flag
     * is true, the bufferevent will be freed when the BIO is closed. */
    static BIO *
    BIO_new_bufferevent(struct bufferevent *bufferevent)
    {
    	BIO *result;
    	if (!bufferevent)
    		return NULL;
    	if (!(result = BIO_new(BIO_s_bufferevent())))
    		return NULL;
    	BIO_set_init(result, 1);
    	BIO_set_data(result, bufferevent);
    	/* We don't tell the BIO to close the bufferevent; we do it ourselves on
    	 * be_openssl_destruct() */
    	BIO_set_shutdown(result, 0);
    	return result;
    }
    
    static void
    conn_closed(struct bufferevent_ssl *bev_ssl, int when, int errcode, int ret)
    {
    	int event = BEV_EVENT_ERROR;
    	int dirty_shutdown = 0;
    	unsigned long err;
    
    	switch (errcode) {
    	case SSL_ERROR_ZERO_RETURN:
    		/* Possibly a clean shutdown. */
    		if (SSL_get_shutdown(bev_ssl->ssl) & SSL_RECEIVED_SHUTDOWN)
    			event = BEV_EVENT_EOF;
    		else
    			dirty_shutdown = 1;
    		break;
    	case SSL_ERROR_SYSCALL:
    		/* IO error; possibly a dirty shutdown. */
    		if ((ret == 0 || ret == -1) && ERR_peek_error() == 0)
    			dirty_shutdown = 1;
    		bufferevent_ssl_put_error(bev_ssl, errcode);
    		break;
    	case SSL_ERROR_SSL:
    		/* Protocol error; possibly a dirty shutdown. */
    		if (ret == 0 && SSL_is_init_finished(bev_ssl->ssl) == 0)
    			dirty_shutdown = 1;
    		bufferevent_ssl_put_error(bev_ssl, errcode);
    		break;
    	case SSL_ERROR_WANT_X509_LOOKUP:
    		/* XXXX handle this. */
    		bufferevent_ssl_put_error(bev_ssl, errcode);
    		break;
    	case SSL_ERROR_NONE:
    	case SSL_ERROR_WANT_READ:
    	case SSL_ERROR_WANT_WRITE:
    	case SSL_ERROR_WANT_CONNECT:
    	case SSL_ERROR_WANT_ACCEPT:
    	default:
    		/* should be impossible; treat as normal error. */
    		event_warnx("BUG: Unexpected OpenSSL error code %d", errcode);
    		break;
    	}
    
    	while ((err = ERR_get_error())) {
    		bufferevent_ssl_put_error(bev_ssl, err);
    	}
    
    	if (dirty_shutdown && bev_ssl->flags & BUFFEREVENT_SSL_DIRTY_SHUTDOWN)
    		event = BEV_EVENT_EOF;
    
    	bufferevent_ssl_stop_reading(bev_ssl);
    	bufferevent_ssl_stop_writing(bev_ssl);
    
    	/* when is BEV_EVENT_{READING|WRITING} */
    	event = when | event;
    	bufferevent_run_eventcb_(&bev_ssl->bev.bev, event, 0);
    }
    
    static void
    init_bio_counts(struct bufferevent_ssl *bev_ssl)
    {
    	BIO *rbio, *wbio;
    
    	wbio = SSL_get_wbio(bev_ssl->ssl);
    	bev_ssl->counts.n_written = wbio ? BIO_number_written(wbio) : 0;
    	rbio = SSL_get_rbio(bev_ssl->ssl);
    	bev_ssl->counts.n_read = rbio ? BIO_number_read(rbio) : 0;
    }
    
    static inline void
    decrement_buckets(struct bufferevent_ssl *bev_ssl)
    {
    	unsigned long num_w = BIO_number_written(SSL_get_wbio(bev_ssl->ssl));
    	unsigned long num_r = BIO_number_read(SSL_get_rbio(bev_ssl->ssl));
    	/* These next two subtractions can wrap around. That's okay. */
    	unsigned long w = num_w - bev_ssl->counts.n_written;
    	unsigned long r = num_r - bev_ssl->counts.n_read;
    	if (w)
    		bufferevent_decrement_write_buckets_(&bev_ssl->bev, w);
    	if (r)
    		bufferevent_decrement_read_buckets_(&bev_ssl->bev, r);
    	bev_ssl->counts.n_written = num_w;
    	bev_ssl->counts.n_read = num_r;
    }
    
    static void *
    SSL_init(void *ssl)
    {
    	/* Don't explode if we decide to realloc a chunk we're writing from in
    	 * the output buffer. */
    	SSL_set_mode(ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
    
    	return ssl;
    }
    
    static void
    SSL_context_free(void *ssl, int flags)
    {
    	if (flags & BEV_OPT_CLOSE_ON_FREE)
    		SSL_free(ssl);
    }
    
    static int
    SSL_handshake_is_ok(int err)
    {
    	/* What SSL_do_handshake() return on success */
    	return err == 1;
    }
    
    static int
    SSL_is_want_read(int err)
    {
    	return err == SSL_ERROR_WANT_READ;
    }
    
    static int
    SSL_is_want_write(int err)
    {
    	return err == SSL_ERROR_WANT_WRITE;
    }
    
    static int
    openssl_read(void *ssl, unsigned char *buf, size_t len)
    {
    	return SSL_read(ssl, buf, len);
    }
    
    static int
    openssl_write(void *ssl, const unsigned char *buf, size_t len)
    {
    	return SSL_write(ssl, buf, len);
    }
    
    static evutil_socket_t
    be_openssl_get_fd(struct bufferevent_ssl *bev_ssl)
    {
    	evutil_socket_t fd = EVUTIL_INVALID_SOCKET;
    	BIO *bio = SSL_get_wbio(bev_ssl->ssl);
    	if (bio)
    		fd = BIO_get_fd(bio, NULL);
    	return fd;
    }
    
    static int
    be_openssl_bio_set_fd(struct bufferevent_ssl *bev_ssl, evutil_socket_t fd)
    {
    	if (!bev_ssl->underlying) {
    		BIO *bio;
    		bio = BIO_new_socket((int)fd, 0);
    		SSL_set_bio(bev_ssl->ssl, bio, bio);
    	} else {
    		BIO *bio;
    		if (!(bio = BIO_new_bufferevent(bev_ssl->underlying)))
    			return -1;
    		SSL_set_bio(bev_ssl->ssl, bio, bio);
    	}
    	return 0;
    }
    
    static size_t SSL_pending_wrap(void *ssl)
    {
    	return SSL_pending(ssl);
    }
    
    static struct le_ssl_ops le_openssl_ops = {
    	SSL_init,
    	SSL_context_free,
    	(void (*)(void *))SSL_free,
    	(int (*)(void *))SSL_renegotiate,
    	openssl_write,
    	openssl_read,
    	SSL_pending_wrap,
    	(int (*)(void *))SSL_do_handshake,
    	(int (*)(void *, int))SSL_get_error,
    	ERR_clear_error,
    	(int (*)(void *))SSL_clear,
    	(void (*)(void *))SSL_set_connect_state,
    	(void (*)(void *))SSL_set_accept_state,
    	SSL_handshake_is_ok,
    	SSL_is_want_read,
    	SSL_is_want_write,
    	(int (*)(void *))be_openssl_get_fd,
    	be_openssl_bio_set_fd,
    	init_bio_counts,
    	decrement_buckets,
    	conn_closed,
    	print_err,
    };
    
    struct bufferevent *
    bufferevent_openssl_filter_new(struct event_base *base,
        struct bufferevent *underlying,
        SSL *ssl,
        enum bufferevent_ssl_state state,
        int options)
    {
    	BIO *bio;
    	struct bufferevent *bev;
    
    	if (!underlying)
    		goto err;
    	if (!(bio = BIO_new_bufferevent(underlying)))
    		goto err;
    
    	SSL_set_bio(ssl, bio, bio);
    
    	bev = bufferevent_ssl_new_impl(
    		base, underlying, -1, ssl, state, options, &le_openssl_ops);
    	return bev;
    
    err:
    	if (options & BEV_OPT_CLOSE_ON_FREE)
    		SSL_free(ssl);
    	return NULL;
    }
    
    struct bufferevent *
    bufferevent_openssl_socket_new(struct event_base *base,
        evutil_socket_t fd,
        SSL *ssl,
        enum bufferevent_ssl_state state,
        int options)
    {
    	/* Does the SSL already have an fd? */
    	BIO *bio = SSL_get_wbio(ssl);
    	long have_fd = -1;
    
    	if (bio)
    		have_fd = BIO_get_fd(bio, NULL);
    
    	if (have_fd >= 0) {
    		/* The SSL is already configured with an fd. */
    		if (fd < 0) {
    			/* We should learn the fd from the SSL. */
    			fd = (evutil_socket_t) have_fd;
    		} else if (have_fd == (long)fd) {
    			/* We already know the fd from the SSL; do nothing */
    		} else {
    			/* We specified an fd different from that of the SSL.
    			   This is probably an error on our part.  Fail. */
    			goto err;
    		}
    		(void)BIO_set_close(bio, 0);
    	} else {
    		/* The SSL isn't configured with a BIO with an fd. */
    		if (fd >= 0) {
    			/* ... and we have an fd we want to use. */
    			bio = BIO_new_socket((int)fd, 0);
    			SSL_set_bio(ssl, bio, bio);
    		} else {
    			/* Leave the fd unset. */
    		}
    	}
    
    	return bufferevent_ssl_new_impl(
    		base, NULL, fd, ssl, state, options, &le_openssl_ops);
    
    err:
    	if (options & BEV_OPT_CLOSE_ON_FREE)
    		SSL_free(ssl);
    	return NULL;
    }
    
    int
    bufferevent_ssl_renegotiate(struct bufferevent *bev)
    {
    	return bufferevent_ssl_renegotiate_impl(bev);
    }
    
    SSL *
    bufferevent_openssl_get_ssl(struct bufferevent *bufev)
    {
    	struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev);
    	if (!bev_ssl)
    		return NULL;
    	return bev_ssl->ssl;
    }
    
    int
    bufferevent_openssl_get_allow_dirty_shutdown(struct bufferevent *bev)
    {
    	return bufferevent_ssl_get_allow_dirty_shutdown(bev);
    }
    
    void
    bufferevent_openssl_set_allow_dirty_shutdown(
    	struct bufferevent *bev, int allow_dirty_shutdown)
    {
    	bufferevent_ssl_set_allow_dirty_shutdown(bev, allow_dirty_shutdown);
    }
    
    unsigned long
    bufferevent_get_openssl_error(struct bufferevent *bufev)
    {
    	struct bufferevent_ssl *bev_ssl = bufferevent_ssl_upcast(bufev);
    	if (!bev_ssl)
    		return 0;
    	return bufferevent_get_ssl_error(bufev);
    }