diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 10e3e8956eef71dca7a1f8fb1484613ca2ad4c89..fd5aa4695a5b7b133f97b664a0a51261d53c7665 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -1556,6 +1556,7 @@ extern { #[cfg(not(ossl101))] pub fn SSL_get0_param(ssl: *mut SSL) -> *mut X509_VERIFY_PARAM; pub fn SSL_get_verify_result(ssl: *const SSL) -> c_long; + pub fn SSL_shutdown(ssl: *mut SSL) -> c_int; #[cfg(not(osslconf = "OPENSSL_NO_COMP"))] pub fn SSL_COMP_get_name(comp: *const COMP_METHOD) -> *const c_char; diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index bf4c03f71e1f4c21b6b95967c8b7804f466e8771..2577600f7cc47df639f2bcbe9e8334fe2385e35e 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -1278,6 +1278,26 @@ impl SslStream { Err(self.make_error(ret)) } } + + /// Shuts down the session. + /// + /// The shutdown process consists of two steps. The first step sends a + /// close notify message to the peer, after which `ShutdownResult::Sent` + /// is returned. The second step awaits the receipt of a close notify + /// message from the peer, after which `ShutdownResult::Received` is + /// returned. + /// + /// While the connection may be closed after the first step, it is + /// recommended to fully shut the session down. In particular, it must + /// be fully shut down if the connection is to be used for further + /// communication in the future. + pub fn shutdown(&mut self) -> Result { + match unsafe { ffi::SSL_shutdown(self.ssl.as_ptr()) } { + 0 => Ok(ShutdownResult::Sent), + 1 => Ok(ShutdownResult::Received), + n => Err(self.make_error(n)), + } + } } impl SslStream { @@ -1383,6 +1403,16 @@ impl Write for SslStream { } } +/// The result of a shutdown request. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ShutdownResult { + /// A close notify message has been sent to the peer. + Sent, + + /// A close notify response message has been received from the peer. + Received, +} + #[cfg(ossl110)] mod compat { use std::ptr; diff --git a/openssl/src/ssl/tests/mod.rs b/openssl/src/ssl/tests/mod.rs index d11073f219bf4c8686aa5c82dd5f28c1c22e40b8..4bc6f216c3b2890a17fd4633e6b8099a02a02dbc 100644 --- a/openssl/src/ssl/tests/mod.rs +++ b/openssl/src/ssl/tests/mod.rs @@ -19,7 +19,7 @@ use ssl; use ssl::SSL_VERIFY_PEER; use ssl::{SslMethod, HandshakeError}; use ssl::error::Error; -use ssl::{SslContext, SslStream, Ssl}; +use ssl::{SslContext, SslStream, Ssl, ShutdownResult}; use x509::X509StoreContextRef; use x509::X509FileType; use x509::X509; @@ -1084,6 +1084,38 @@ fn invalid_hostname() { assert!(ssl.connect(s).is_err()); } +#[test] +fn shutdown() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + + thread::spawn(move || { + let stream = listener.accept().unwrap().0; + let mut ctx = SslContext::new(SslMethod::tls()).unwrap(); + ctx.set_certificate_file(&Path::new("test/cert.pem"), X509FileType::PEM).unwrap(); + ctx.set_private_key_file(&Path::new("test/key.pem"), X509FileType::PEM).unwrap(); + let ssl = Ssl::new(&ctx).unwrap(); + let mut stream = ssl.accept(stream).unwrap(); + + stream.write_all(b"hello").unwrap(); + let mut buf = [0; 1]; + assert_eq!(stream.read(&mut buf).unwrap(), 0); + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Received); + }); + + let stream = TcpStream::connect(("127.0.0.1", port)).unwrap(); + let ctx = SslContext::new(SslMethod::tls()).unwrap(); + let ssl = Ssl::new(&ctx).unwrap(); + let mut stream = ssl.connect(stream).unwrap(); + + let mut buf = [0; 5]; + stream.read_exact(&mut buf).unwrap(); + assert_eq!(b"hello", &buf); + + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Sent); + assert_eq!(stream.shutdown().unwrap(), ShutdownResult::Received); +} + fn _check_kinds() { fn is_send() {} fn is_sync() {}