]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // |
2 | // ssl/old/detail/openssl_operation.hpp | |
3 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
4 | // | |
5 | // Copyright (c) 2005 Voipster / Indrek dot Juhani at voipster dot com | |
6 | // | |
7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |
8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
9 | // | |
10 | ||
11 | #ifndef BOOST_ASIO_SSL_OLD_DETAIL_OPENSSL_OPERATION_HPP | |
12 | #define BOOST_ASIO_SSL_OLD_DETAIL_OPENSSL_OPERATION_HPP | |
13 | ||
14 | #if defined(_MSC_VER) && (_MSC_VER >= 1200) | |
15 | # pragma once | |
16 | #endif // defined(_MSC_VER) && (_MSC_VER >= 1200) | |
17 | ||
18 | #include <boost/asio/detail/config.hpp> | |
19 | #include <boost/function.hpp> | |
20 | #include <boost/bind.hpp> | |
21 | #include <boost/asio/buffer.hpp> | |
22 | #include <boost/asio/detail/assert.hpp> | |
23 | #include <boost/asio/detail/socket_ops.hpp> | |
24 | #include <boost/asio/placeholders.hpp> | |
25 | #include <boost/asio/ssl/detail/openssl_types.hpp> | |
26 | #include <boost/asio/ssl/error.hpp> | |
27 | #include <boost/asio/strand.hpp> | |
28 | #include <boost/system/system_error.hpp> | |
29 | #include <boost/asio/write.hpp> | |
30 | ||
31 | #include <boost/asio/detail/push_options.hpp> | |
32 | ||
33 | namespace boost { | |
34 | namespace asio { | |
35 | namespace ssl { | |
36 | namespace old { | |
37 | namespace detail { | |
38 | ||
39 | typedef boost::function<int (::SSL*)> ssl_primitive_func; | |
40 | typedef boost::function<void (const boost::system::error_code&, int)> | |
41 | user_handler_func; | |
42 | ||
43 | // Network send_/recv buffer implementation | |
44 | // | |
45 | // | |
46 | class net_buffer | |
47 | { | |
48 | static const int NET_BUF_SIZE = 16*1024 + 256; // SSL record size + spare | |
49 | ||
50 | unsigned char buf_[NET_BUF_SIZE]; | |
51 | unsigned char* data_start_; | |
52 | unsigned char* data_end_; | |
53 | ||
54 | public: | |
55 | net_buffer() | |
56 | { | |
57 | data_start_ = data_end_ = buf_; | |
58 | } | |
59 | unsigned char* get_unused_start() { return data_end_; } | |
60 | unsigned char* get_data_start() { return data_start_; } | |
61 | size_t get_unused_len() { return (NET_BUF_SIZE - (data_end_ - buf_)); } | |
62 | size_t get_data_len() { return (data_end_ - data_start_); } | |
63 | void data_added(size_t count) | |
64 | { | |
65 | data_end_ += count; | |
66 | data_end_ = data_end_ > (buf_ + NET_BUF_SIZE)? | |
67 | (buf_ + NET_BUF_SIZE): | |
68 | data_end_; | |
69 | } | |
70 | void data_removed(size_t count) | |
71 | { | |
72 | data_start_ += count; | |
73 | if (data_start_ >= data_end_) reset(); | |
74 | } | |
75 | void reset() { data_start_ = buf_; data_end_ = buf_; } | |
76 | bool has_data() { return (data_start_ < data_end_); } | |
77 | }; // class net_buffer | |
78 | ||
79 | // | |
80 | // Operation class | |
81 | // | |
82 | // | |
83 | template <typename Stream> | |
84 | class openssl_operation | |
85 | { | |
86 | public: | |
87 | ||
88 | // Constructor for asynchronous operations | |
89 | openssl_operation(ssl_primitive_func primitive, | |
90 | Stream& socket, | |
91 | net_buffer& recv_buf, | |
92 | SSL* session, | |
93 | BIO* ssl_bio, | |
94 | user_handler_func handler, | |
95 | boost::asio::io_service::strand& strand | |
96 | ) | |
97 | : primitive_(primitive) | |
98 | , user_handler_(handler) | |
99 | , strand_(&strand) | |
100 | , recv_buf_(recv_buf) | |
101 | , socket_(socket) | |
102 | , ssl_bio_(ssl_bio) | |
103 | , session_(session) | |
104 | { | |
105 | write_ = boost::bind( | |
106 | &openssl_operation::do_async_write, | |
107 | this, boost::arg<1>(), boost::arg<2>() | |
108 | ); | |
109 | read_ = boost::bind( | |
110 | &openssl_operation::do_async_read, | |
111 | this | |
112 | ); | |
113 | handler_= boost::bind( | |
114 | &openssl_operation::async_user_handler, | |
115 | this, boost::arg<1>(), boost::arg<2>() | |
116 | ); | |
117 | } | |
118 | ||
119 | // Constructor for synchronous operations | |
120 | openssl_operation(ssl_primitive_func primitive, | |
121 | Stream& socket, | |
122 | net_buffer& recv_buf, | |
123 | SSL* session, | |
124 | BIO* ssl_bio) | |
125 | : primitive_(primitive) | |
126 | , strand_(0) | |
127 | , recv_buf_(recv_buf) | |
128 | , socket_(socket) | |
129 | , ssl_bio_(ssl_bio) | |
130 | , session_(session) | |
131 | { | |
132 | write_ = boost::bind( | |
133 | &openssl_operation::do_sync_write, | |
134 | this, boost::arg<1>(), boost::arg<2>() | |
135 | ); | |
136 | read_ = boost::bind( | |
137 | &openssl_operation::do_sync_read, | |
138 | this | |
139 | ); | |
140 | handler_ = boost::bind( | |
141 | &openssl_operation::sync_user_handler, | |
142 | this, boost::arg<1>(), boost::arg<2>() | |
143 | ); | |
144 | } | |
145 | ||
146 | // Start operation | |
147 | // In case of asynchronous it returns 0, in sync mode returns success code | |
148 | // or throws an error... | |
149 | int start() | |
150 | { | |
151 | int rc = primitive_( session_ ); | |
152 | ||
153 | bool is_operation_done = (rc > 0); | |
154 | // For connect/accept/shutdown, the operation | |
155 | // is done, when return code is 1 | |
156 | // for write, it is done, when is retcode > 0 | |
157 | // for read, it is done when retcode > 0 | |
158 | ||
159 | int error_code = !is_operation_done ? | |
160 | ::SSL_get_error( session_, rc ) : | |
161 | 0; | |
162 | int sys_error_code = ERR_get_error(); | |
163 | ||
164 | if (error_code == SSL_ERROR_SSL) | |
165 | return handler_(boost::system::error_code( | |
166 | sys_error_code, boost::asio::error::get_ssl_category()), rc); | |
167 | ||
168 | bool is_read_needed = (error_code == SSL_ERROR_WANT_READ); | |
169 | bool is_write_needed = (error_code == SSL_ERROR_WANT_WRITE || | |
170 | ::BIO_ctrl_pending( ssl_bio_ )); | |
171 | bool is_shut_down_received = | |
172 | ((::SSL_get_shutdown( session_ ) & SSL_RECEIVED_SHUTDOWN) == | |
173 | SSL_RECEIVED_SHUTDOWN); | |
174 | bool is_shut_down_sent = | |
175 | ((::SSL_get_shutdown( session_ ) & SSL_SENT_SHUTDOWN) == | |
176 | SSL_SENT_SHUTDOWN); | |
177 | ||
178 | if (is_shut_down_sent && is_shut_down_received | |
179 | && is_operation_done && !is_write_needed) | |
180 | // SSL connection is shut down cleanly | |
181 | return handler_(boost::system::error_code(), 1); | |
182 | ||
183 | if (is_shut_down_received && !is_operation_done) | |
184 | // Shutdown has been requested, while we were reading or writing... | |
185 | // abort our action... | |
186 | return handler_(boost::asio::error::shut_down, 0); | |
187 | ||
188 | if (!is_operation_done && !is_read_needed && !is_write_needed | |
189 | && !is_shut_down_sent) | |
190 | { | |
191 | // The operation has failed... It is not completed and does | |
192 | // not want network communication nor does want to send shutdown out... | |
193 | if (error_code == SSL_ERROR_SYSCALL) | |
194 | { | |
195 | return handler_(boost::system::error_code( | |
196 | sys_error_code, boost::asio::error::system_category), rc); | |
197 | } | |
198 | else | |
199 | { | |
200 | return handler_(boost::system::error_code( | |
201 | sys_error_code, boost::asio::error::get_ssl_category()), rc); | |
202 | } | |
203 | } | |
204 | ||
205 | if (!is_operation_done && !is_write_needed) | |
206 | { | |
207 | // We may have left over data that we can pass to SSL immediately | |
208 | if (recv_buf_.get_data_len() > 0) | |
209 | { | |
210 | // Pass the buffered data to SSL | |
211 | int written = ::BIO_write | |
212 | ( | |
213 | ssl_bio_, | |
214 | recv_buf_.get_data_start(), | |
215 | recv_buf_.get_data_len() | |
216 | ); | |
217 | ||
218 | if (written > 0) | |
219 | { | |
220 | recv_buf_.data_removed(written); | |
221 | } | |
222 | else if (written < 0) | |
223 | { | |
224 | if (!BIO_should_retry(ssl_bio_)) | |
225 | { | |
226 | // Some serios error with BIO.... | |
227 | return handler_(boost::asio::error::no_recovery, 0); | |
228 | } | |
229 | } | |
230 | ||
231 | return start(); | |
232 | } | |
233 | else if (is_read_needed || (is_shut_down_sent && !is_shut_down_received)) | |
234 | { | |
235 | return read_(); | |
236 | } | |
237 | } | |
238 | ||
239 | // Continue with operation, flush any SSL data out to network... | |
240 | return write_(is_operation_done, rc); | |
241 | } | |
242 | ||
243 | // Private implementation | |
244 | private: | |
245 | typedef boost::function<int (const boost::system::error_code&, int)> | |
246 | int_handler_func; | |
247 | typedef boost::function<int (bool, int)> write_func; | |
248 | typedef boost::function<int ()> read_func; | |
249 | ||
250 | ssl_primitive_func primitive_; | |
251 | user_handler_func user_handler_; | |
252 | boost::asio::io_service::strand* strand_; | |
253 | write_func write_; | |
254 | read_func read_; | |
255 | int_handler_func handler_; | |
256 | ||
257 | net_buffer send_buf_; // buffers for network IO | |
258 | ||
259 | // The recv buffer is owned by the stream, not the operation, since there can | |
260 | // be left over bytes after passing the data up to the application, and these | |
261 | // bytes need to be kept around for the next read operation issued by the | |
262 | // application. | |
263 | net_buffer& recv_buf_; | |
264 | ||
265 | Stream& socket_; | |
266 | BIO* ssl_bio_; | |
267 | SSL* session_; | |
268 | ||
269 | // | |
270 | int sync_user_handler(const boost::system::error_code& error, int rc) | |
271 | { | |
272 | if (!error) | |
273 | return rc; | |
274 | ||
275 | throw boost::system::system_error(error); | |
276 | } | |
277 | ||
278 | int async_user_handler(boost::system::error_code error, int rc) | |
279 | { | |
280 | if (rc < 0) | |
281 | { | |
282 | if (!error) | |
283 | error = boost::asio::error::no_recovery; | |
284 | rc = 0; | |
285 | } | |
286 | ||
287 | user_handler_(error, rc); | |
288 | return 0; | |
289 | } | |
290 | ||
291 | // Writes bytes asynchronously from SSL to NET | |
292 | int do_async_write(bool is_operation_done, int rc) | |
293 | { | |
294 | int len = ::BIO_ctrl_pending( ssl_bio_ ); | |
295 | if ( len ) | |
296 | { | |
297 | // There is something to write into net, do it... | |
298 | len = (int)send_buf_.get_unused_len() > len? | |
299 | len: | |
300 | send_buf_.get_unused_len(); | |
301 | ||
302 | if (len == 0) | |
303 | { | |
304 | // In case our send buffer is full, we have just to wait until | |
305 | // previous send to complete... | |
306 | return 0; | |
307 | } | |
308 | ||
309 | // Read outgoing data from bio | |
310 | len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); | |
311 | ||
312 | if (len > 0) | |
313 | { | |
314 | unsigned char *data_start = send_buf_.get_unused_start(); | |
315 | send_buf_.data_added(len); | |
316 | ||
317 | BOOST_ASIO_ASSERT(strand_); | |
318 | boost::asio::async_write | |
319 | ( | |
320 | socket_, | |
321 | boost::asio::buffer(data_start, len), | |
322 | strand_->wrap | |
323 | ( | |
324 | boost::bind | |
325 | ( | |
326 | &openssl_operation::async_write_handler, | |
327 | this, | |
328 | is_operation_done, | |
329 | rc, | |
330 | boost::asio::placeholders::error, | |
331 | boost::asio::placeholders::bytes_transferred | |
332 | ) | |
333 | ) | |
334 | ); | |
335 | ||
336 | return 0; | |
337 | } | |
338 | else if (!BIO_should_retry(ssl_bio_)) | |
339 | { | |
340 | // Seems like fatal error | |
341 | // reading from SSL BIO has failed... | |
342 | handler_(boost::asio::error::no_recovery, 0); | |
343 | return 0; | |
344 | } | |
345 | } | |
346 | ||
347 | if (is_operation_done) | |
348 | { | |
349 | // Finish the operation, with success | |
350 | handler_(boost::system::error_code(), rc); | |
351 | return 0; | |
352 | } | |
353 | ||
354 | // OPeration is not done and writing to net has been made... | |
355 | // start operation again | |
356 | start(); | |
357 | ||
358 | return 0; | |
359 | } | |
360 | ||
361 | void async_write_handler(bool is_operation_done, int rc, | |
362 | const boost::system::error_code& error, size_t bytes_sent) | |
363 | { | |
364 | if (!error) | |
365 | { | |
366 | // Remove data from send buffer | |
367 | send_buf_.data_removed(bytes_sent); | |
368 | ||
369 | if (is_operation_done) | |
370 | handler_(boost::system::error_code(), rc); | |
371 | else | |
372 | // Since the operation was not completed, try it again... | |
373 | start(); | |
374 | } | |
375 | else | |
376 | handler_(error, rc); | |
377 | } | |
378 | ||
379 | int do_async_read() | |
380 | { | |
381 | // Wait for new data | |
382 | BOOST_ASIO_ASSERT(strand_); | |
383 | socket_.async_read_some | |
384 | ( | |
385 | boost::asio::buffer(recv_buf_.get_unused_start(), | |
386 | recv_buf_.get_unused_len()), | |
387 | strand_->wrap | |
388 | ( | |
389 | boost::bind | |
390 | ( | |
391 | &openssl_operation::async_read_handler, | |
392 | this, | |
393 | boost::asio::placeholders::error, | |
394 | boost::asio::placeholders::bytes_transferred | |
395 | ) | |
396 | ) | |
397 | ); | |
398 | return 0; | |
399 | } | |
400 | ||
401 | void async_read_handler(const boost::system::error_code& error, | |
402 | size_t bytes_recvd) | |
403 | { | |
404 | if (!error) | |
405 | { | |
406 | recv_buf_.data_added(bytes_recvd); | |
407 | ||
408 | // Pass the received data to SSL | |
409 | int written = ::BIO_write | |
410 | ( | |
411 | ssl_bio_, | |
412 | recv_buf_.get_data_start(), | |
413 | recv_buf_.get_data_len() | |
414 | ); | |
415 | ||
416 | if (written > 0) | |
417 | { | |
418 | recv_buf_.data_removed(written); | |
419 | } | |
420 | else if (written < 0) | |
421 | { | |
422 | if (!BIO_should_retry(ssl_bio_)) | |
423 | { | |
424 | // Some serios error with BIO.... | |
425 | handler_(boost::asio::error::no_recovery, 0); | |
426 | return; | |
427 | } | |
428 | } | |
429 | ||
430 | // and try the SSL primitive again | |
431 | start(); | |
432 | } | |
433 | else | |
434 | { | |
435 | // Error in network level... | |
436 | // SSL can't continue either... | |
437 | handler_(error, 0); | |
438 | } | |
439 | } | |
440 | ||
441 | // Syncronous functions... | |
442 | int do_sync_write(bool is_operation_done, int rc) | |
443 | { | |
444 | int len = ::BIO_ctrl_pending( ssl_bio_ ); | |
445 | if ( len ) | |
446 | { | |
447 | // There is something to write into net, do it... | |
448 | len = (int)send_buf_.get_unused_len() > len? | |
449 | len: | |
450 | send_buf_.get_unused_len(); | |
451 | ||
452 | // Read outgoing data from bio | |
453 | len = ::BIO_read( ssl_bio_, send_buf_.get_unused_start(), len); | |
454 | ||
455 | if (len > 0) | |
456 | { | |
457 | size_t sent_len = boost::asio::write( | |
458 | socket_, | |
459 | boost::asio::buffer(send_buf_.get_unused_start(), len) | |
460 | ); | |
461 | ||
462 | send_buf_.data_added(len); | |
463 | send_buf_.data_removed(sent_len); | |
464 | } | |
465 | else if (!BIO_should_retry(ssl_bio_)) | |
466 | { | |
467 | // Seems like fatal error | |
468 | // reading from SSL BIO has failed... | |
469 | throw boost::system::system_error(boost::asio::error::no_recovery); | |
470 | } | |
471 | } | |
472 | ||
473 | if (is_operation_done) | |
474 | // Finish the operation, with success | |
475 | return rc; | |
476 | ||
477 | // Operation is not finished, start again. | |
478 | return start(); | |
479 | } | |
480 | ||
481 | int do_sync_read() | |
482 | { | |
483 | size_t len = socket_.read_some | |
484 | ( | |
485 | boost::asio::buffer(recv_buf_.get_unused_start(), | |
486 | recv_buf_.get_unused_len()) | |
487 | ); | |
488 | ||
489 | // Write data to ssl | |
490 | recv_buf_.data_added(len); | |
491 | ||
492 | // Pass the received data to SSL | |
493 | int written = ::BIO_write | |
494 | ( | |
495 | ssl_bio_, | |
496 | recv_buf_.get_data_start(), | |
497 | recv_buf_.get_data_len() | |
498 | ); | |
499 | ||
500 | if (written > 0) | |
501 | { | |
502 | recv_buf_.data_removed(written); | |
503 | } | |
504 | else if (written < 0) | |
505 | { | |
506 | if (!BIO_should_retry(ssl_bio_)) | |
507 | { | |
508 | // Some serios error with BIO.... | |
509 | throw boost::system::system_error(boost::asio::error::no_recovery); | |
510 | } | |
511 | } | |
512 | ||
513 | // Try the operation again | |
514 | return start(); | |
515 | } | |
516 | }; // class openssl_operation | |
517 | ||
518 | } // namespace detail | |
519 | } // namespace old | |
520 | } // namespace ssl | |
521 | } // namespace asio | |
522 | } // namespace boost | |
523 | ||
524 | #include <boost/asio/detail/pop_options.hpp> | |
525 | ||
526 | #endif // BOOST_ASIO_SSL_OLD_DETAIL_OPENSSL_OPERATION_HPP |