123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- //
- // 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
|