| // |
| // openssl_operation.hpp |
| // ~~~~~~~~~~~~~~~~~~~~~ |
| // |
| // Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com |
| // |
| // Distributed under the Boost Software License, Version 1.0. (See accompanying |
| // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) |
| // |
| |
| #ifndef BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP |
| #define BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP |
| |
| #if defined(_MSC_VER) && (_MSC_VER >= 1200) |
| # pragma once |
| #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) |
| |
| #include <boost/asio/detail/push_options.hpp> |
| |
| #include <boost/asio/detail/push_options.hpp> |
| #include <boost/function.hpp> |
| #include <boost/assert.hpp> |
| #include <boost/bind.hpp> |
| #include <boost/asio/detail/pop_options.hpp> |
| |
| #include <boost/asio/buffer.hpp> |
| #include <boost/asio/placeholders.hpp> |
| #include <boost/asio/write.hpp> |
| #include <boost/asio/detail/socket_ops.hpp> |
| #include <boost/asio/ssl/detail/openssl_types.hpp> |
| |
| namespace boost { |
| namespace asio { |
| namespace ssl { |
| namespace detail { |
| |
| typedef boost::function<int (::SSL*)> ssl_primitive_func; |
| typedef boost::function<void (const boost::system::error_code&, int)> |
| user_handler_func; |
| |
| // Network send_/recv buffer implementation |
| // |
| // |
| class net_buffer |
| { |
| static const int NET_BUF_SIZE = 16*1024 + 256; // SSL record size + spare |
| |
| unsigned char buf_[NET_BUF_SIZE]; |
| unsigned char* data_start_; |
| unsigned char* data_end_; |
| |
| public: |
| net_buffer() |
| { |
| data_start_ = data_end_ = buf_; |
| } |
| unsigned char* get_unused_start() { return data_end_; } |
| unsigned char* get_data_start() { return data_start_; } |
| size_t get_unused_len() { return (NET_BUF_SIZE - (data_end_ - buf_)); } |
| size_t get_data_len() { return (data_end_ - data_start_); } |
| void data_added(size_t count) |
| { |
| data_end_ += count; |
| data_end_ = data_end_ > (buf_ + NET_BUF_SIZE)? |
| (buf_ + NET_BUF_SIZE): |
| data_end_; |
| } |
| void data_removed(size_t count) |
| { |
| data_start_ += count; |
| if (data_start_ >= data_end_) reset(); |
| } |
| void reset() { data_start_ = buf_; data_end_ = buf_; } |
| bool has_data() { return (data_start_ < data_end_); } |
| }; // class net_buffer |
| |
| // |
| // Operation class |
| // |
| // |
| template <typename Stream> |
| class openssl_operation |
| { |
| public: |
| |
| // Constructor for asynchronous operations |
| openssl_operation(ssl_primitive_func primitive, |
| Stream& socket, |
| net_buffer& recv_buf, |
| SSL* session, |
| BIO* ssl_bio, |
| user_handler_func handler, |
| boost::asio::io_service::strand& strand |
| ) |
| : primitive_(primitive) |
| , user_handler_(handler) |
| , strand_(&strand) |
| , recv_buf_(recv_buf) |
| , socket_(socket) |
| , ssl_bio_(ssl_bio) |
| , session_(session) |
| { |
| write_ = boost::bind( |
| &openssl_operation::do_async_write, |
| this, boost::arg<1>(), boost::arg<2>() |
| ); |
| read_ = boost::bind( |
| &openssl_operation::do_async_read, |
| this |
| ); |
| handler_= boost::bind( |
| &openssl_operation::async_user_handler, |
| this, boost::arg<1>(), boost::arg<2>() |
| ); |
| } |
| |
| // Constructor for synchronous operations |
| openssl_operation(ssl_primitive_func primitive, |
| Stream& socket, |
| net_buffer& recv_buf, |
| SSL* session, |
| BIO* ssl_bio) |
| : primitive_(primitive) |
| , strand_(0) |
| , recv_buf_(recv_buf) |
| , socket_(socket) |
| , ssl_bio_(ssl_bio) |
| , session_(session) |
| { |
| write_ = boost::bind( |
| &openssl_operation::do_sync_write, |
| this, boost::arg<1>(), boost::arg<2>() |
| ); |
| read_ = boost::bind( |
| &openssl_operation::do_sync_read, |
| this |
| ); |
| handler_ = boost::bind( |
| &openssl_operation::sync_user_handler, |
| this, boost::arg<1>(), boost::arg<2>() |
| ); |
| } |
| |
| // Start operation |
| // In case of asynchronous it returns 0, in sync mode returns success code |
| // or throws an error... |
| int start() |
| { |
| int rc = primitive_( session_ ); |
| |
| bool is_operation_done = (rc > 0); |
| // For connect/accept/shutdown, the operation |
| // is done, when return code is 1 |
| // for write, it is done, when is retcode > 0 |
| // for read, is is done when retcode > 0 |
| |
| int error_code = !is_operation_done ? |
| ::SSL_get_error( session_, rc ) : |
| 0; |
| int sys_error_code = ERR_get_error(); |
| |
| if (error_code == SSL_ERROR_SSL) |
| return handler_(boost::system::error_code( |
| error_code, boost::asio::error::get_ssl_category()), rc); |
| |
| bool is_read_needed = (error_code == SSL_ERROR_WANT_READ); |
| bool is_write_needed = (error_code == SSL_ERROR_WANT_WRITE || |
| ::BIO_ctrl_pending( ssl_bio_ )); |
| bool is_shut_down_received = |
| ((::SSL_get_shutdown( session_ ) & SSL_RECEIVED_SHUTDOWN) == |
| SSL_RECEIVED_SHUTDOWN); |
| bool is_shut_down_sent = |
| ((::SSL_get_shutdown( session_ ) & SSL_SENT_SHUTDOWN) == |
| SSL_SENT_SHUTDOWN); |
| |
| if (is_shut_down_sent && is_shut_down_received && is_operation_done && !is_write_needed) |
| // SSL connection is shut down cleanly |
| return handler_(boost::system::error_code(), 1); |
| |
| if (is_shut_down_received && !is_operation_done) |
| // Shutdown has been requested, while we were reading or writing... |
| // abort our action... |
| return handler_(boost::asio::error::shut_down, 0); |
| |
| if (!is_operation_done && !is_read_needed && !is_write_needed |
| && !is_shut_down_sent) |
| { |
| // The operation has failed... It is not completed and does |
| // not want network communication nor does want to send shutdown out... |
| if (error_code == SSL_ERROR_SYSCALL) |
| { |
| return handler_(boost::system::error_code( |
| sys_error_code, boost::asio::error::system_category), rc); |
| } |
| else |
| { |
| return handler_(boost::system::error_code( |
| error_code, boost::asio::error::get_ssl_category()), rc); |
| } |
| } |
| |
| if (!is_operation_done && !is_write_needed) |
| { |
| // We may have left over data that we can pass to SSL immediately |
| if (recv_buf_.get_data_len() > 0) |
| { |
| // Pass the buffered data to SSL |
| int written = ::BIO_write |
| ( |
| ssl_bio_, |
| recv_buf_.get_data_start(), |
| recv_buf_.get_data_len() |
| ); |
| |
| if (written > 0) |
| { |
| recv_buf_.data_removed(written); |
| } |
| else if (written < 0) |
| { |
| if (!BIO_should_retry(ssl_bio_)) |
| { |
| // Some serios error with BIO.... |
| return handler_(boost::asio::error::no_recovery, 0); |
| } |
| } |
| |
| return start(); |
| } |
| else if (is_read_needed || (is_shut_down_sent && !is_shut_down_received)) |
| { |
| return read_(); |
| } |
| } |
| |
| // Continue with operation, flush any SSL data out to network... |
| return write_(is_operation_done, rc); |
| } |
| |
| // Private implementation |
| private: |
| typedef boost::function<int (const boost::system::error_code&, int)> |
| int_handler_func; |
| typedef boost::function<int (bool, int)> write_func; |
| typedef boost::function<int ()> read_func; |
| |
| ssl_primitive_func primitive_; |
| user_handler_func user_handler_; |
| boost::asio::io_service::strand* strand_; |
| write_func write_; |
| read_func read_; |
| int_handler_func handler_; |
| |
| net_buffer send_buf_; // buffers for network IO |
| |
| // The recv buffer is owned by the stream, not the operation, since there can |
| // be left over bytes after passing the data up to the application, and these |
| // bytes need to be kept around for the next read operation issued by the |
| // application. |
| net_buffer& recv_buf_; |
| |
| Stream& socket_; |
| BIO* ssl_bio_; |
| SSL* session_; |
| |
| // |
| int sync_user_handler(const boost::system::error_code& error, int rc) |
| { |
| if (!error) |
| return rc; |
| |
| throw boost::system::system_error(error); |
| } |
| |
| int async_user_handler(boost::system::error_code error, int rc) |
| { |
| if (rc < 0) |
| { |
| if (!error) |
| error = boost::asio::error::no_recovery; |
| rc = 0; |
| } |
| |
| user_handler_(error, rc); |
| return 0; |
| } |
| |
| // Writes bytes asynchronously from SSL to NET |
| int do_async_write(bool is_operation_done, int rc) |
| { |
| int len = ::BIO_ctrl_pending( ssl_bio_ ); |
| if ( len ) |
| { |
| // There is something to write into net, do it... |
| len = (int)send_buf_.get_unused_len() > len? |
| len: |
| send_buf_.get_unused_len(); |
| |
| if (len == 0) |
| { |
| // In case our send buffer is full, we have just to wait until |
| // previous send to complete... |
| return 0; |
| } |
| |
| // Read outgoing data from bio |
| len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); |
| |
| if (len > 0) |
| { |
| unsigned char *data_start = send_buf_.get_unused_start(); |
| send_buf_.data_added(len); |
| |
| BOOST_ASSERT(strand_); |
| boost::asio::async_write |
| ( |
| socket_, |
| boost::asio::buffer(data_start, len), |
| strand_->wrap |
| ( |
| boost::bind |
| ( |
| &openssl_operation::async_write_handler, |
| this, |
| is_operation_done, |
| rc, |
| boost::asio::placeholders::error, |
| boost::asio::placeholders::bytes_transferred |
| ) |
| ) |
| ); |
| |
| return 0; |
| } |
| else if (!BIO_should_retry(ssl_bio_)) |
| { |
| // Seems like fatal error |
| // reading from SSL BIO has failed... |
| handler_(boost::asio::error::no_recovery, 0); |
| return 0; |
| } |
| } |
| |
| if (is_operation_done) |
| { |
| // Finish the operation, with success |
| handler_(boost::system::error_code(), rc); |
| return 0; |
| } |
| |
| // OPeration is not done and writing to net has been made... |
| // start operation again |
| start(); |
| |
| return 0; |
| } |
| |
| void async_write_handler(bool is_operation_done, int rc, |
| const boost::system::error_code& error, size_t bytes_sent) |
| { |
| if (!error) |
| { |
| // Remove data from send buffer |
| send_buf_.data_removed(bytes_sent); |
| |
| if (is_operation_done) |
| handler_(boost::system::error_code(), rc); |
| else |
| // Since the operation was not completed, try it again... |
| start(); |
| } |
| else |
| handler_(error, rc); |
| } |
| |
| int do_async_read() |
| { |
| // Wait for new data |
| BOOST_ASSERT(strand_); |
| socket_.async_read_some |
| ( |
| boost::asio::buffer(recv_buf_.get_unused_start(), |
| recv_buf_.get_unused_len()), |
| strand_->wrap |
| ( |
| boost::bind |
| ( |
| &openssl_operation::async_read_handler, |
| this, |
| boost::asio::placeholders::error, |
| boost::asio::placeholders::bytes_transferred |
| ) |
| ) |
| ); |
| return 0; |
| } |
| |
| void async_read_handler(const boost::system::error_code& error, |
| size_t bytes_recvd) |
| { |
| if (!error) |
| { |
| recv_buf_.data_added(bytes_recvd); |
| |
| // Pass the received data to SSL |
| int written = ::BIO_write |
| ( |
| ssl_bio_, |
| recv_buf_.get_data_start(), |
| recv_buf_.get_data_len() |
| ); |
| |
| if (written > 0) |
| { |
| recv_buf_.data_removed(written); |
| } |
| else if (written < 0) |
| { |
| if (!BIO_should_retry(ssl_bio_)) |
| { |
| // Some serios error with BIO.... |
| handler_(boost::asio::error::no_recovery, 0); |
| return; |
| } |
| } |
| |
| // and try the SSL primitive again |
| start(); |
| } |
| else |
| { |
| // Error in network level... |
| // SSL can't continue either... |
| handler_(error, 0); |
| } |
| } |
| |
| // Syncronous functions... |
| int do_sync_write(bool is_operation_done, int rc) |
| { |
| int len = ::BIO_ctrl_pending( ssl_bio_ ); |
| if ( len ) |
| { |
| // There is something to write into net, do it... |
| len = (int)send_buf_.get_unused_len() > len? |
| len: |
| send_buf_.get_unused_len(); |
| |
| // Read outgoing data from bio |
| len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); |
| |
| if (len > 0) |
| { |
| size_t sent_len = boost::asio::write( |
| socket_, |
| boost::asio::buffer(send_buf_.get_unused_start(), len) |
| ); |
| |
| send_buf_.data_added(len); |
| send_buf_.data_removed(sent_len); |
| } |
| else if (!BIO_should_retry(ssl_bio_)) |
| { |
| // Seems like fatal error |
| // reading from SSL BIO has failed... |
| throw boost::system::system_error(boost::asio::error::no_recovery); |
| } |
| } |
| |
| if (is_operation_done) |
| // Finish the operation, with success |
| return rc; |
| |
| // Operation is not finished, start again. |
| return start(); |
| } |
| |
| int do_sync_read() |
| { |
| size_t len = socket_.read_some |
| ( |
| boost::asio::buffer(recv_buf_.get_unused_start(), |
| recv_buf_.get_unused_len()) |
| ); |
| |
| // Write data to ssl |
| recv_buf_.data_added(len); |
| |
| // Pass the received data to SSL |
| int written = ::BIO_write |
| ( |
| ssl_bio_, |
| recv_buf_.get_data_start(), |
| recv_buf_.get_data_len() |
| ); |
| |
| if (written > 0) |
| { |
| recv_buf_.data_removed(written); |
| } |
| else if (written < 0) |
| { |
| if (!BIO_should_retry(ssl_bio_)) |
| { |
| // Some serios error with BIO.... |
| throw boost::system::system_error(boost::asio::error::no_recovery); |
| } |
| } |
| |
| // Try the operation again |
| return start(); |
| } |
| }; // class openssl_operation |
| |
| } // namespace detail |
| } // namespace ssl |
| } // namespace asio |
| } // namespace boost |
| |
| #include <boost/asio/detail/pop_options.hpp> |
| |
| #endif // BOOST_ASIO_SSL_DETAIL_OPENSSL_OPERATION_HPP |