You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and dots ('.'), can be up to 35 characters long. Letters must be lowercase.
526 lines
14 KiB
526 lines
14 KiB
// |
|
// ssl/old/detail/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_OLD_DETAIL_OPENSSL_OPERATION_HPP |
|
#define BOOST_ASIO_SSL_OLD_DETAIL_OPENSSL_OPERATION_HPP |
|
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1200) |
|
# pragma once |
|
#endif // defined(_MSC_VER) && (_MSC_VER >= 1200) |
|
|
|
#include <boost/asio/detail/config.hpp> |
|
#include <boost/function.hpp> |
|
#include <boost/assert.hpp> |
|
#include <boost/bind.hpp> |
|
#include <boost/asio/buffer.hpp> |
|
#include <boost/asio/detail/socket_ops.hpp> |
|
#include <boost/asio/placeholders.hpp> |
|
#include <boost/asio/ssl/detail/openssl_types.hpp> |
|
#include <boost/asio/ssl/error.hpp> |
|
#include <boost/asio/strand.hpp> |
|
#include <boost/system/system_error.hpp> |
|
#include <boost/asio/write.hpp> |
|
|
|
#include <boost/asio/detail/push_options.hpp> |
|
|
|
namespace boost { |
|
namespace asio { |
|
namespace ssl { |
|
namespace old { |
|
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( |
|
sys_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( |
|
sys_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 old |
|
} // namespace ssl |
|
} // namespace asio |
|
} // namespace boost |
|
|
|
#include <boost/asio/detail/pop_options.hpp> |
|
|
|
#endif // BOOST_ASIO_SSL_OLD_DETAIL_OPENSSL_OPERATION_HPP
|
|
|