1 use cfg_if::cfg_if;
2 use ffi::{
3     self, BIO_clear_retry_flags, BIO_new, BIO_set_retry_read, BIO_set_retry_write, BIO,
4     BIO_CTRL_DGRAM_QUERY_MTU, BIO_CTRL_FLUSH,
5 };
6 use libc::{c_char, c_int, c_long, c_void, strlen};
7 use std::any::Any;
8 use std::io;
9 use std::io::prelude::*;
10 use std::panic::{catch_unwind, AssertUnwindSafe};
11 use std::ptr;
12 use std::slice;
13 
14 use crate::cvt_p;
15 use crate::error::ErrorStack;
16 
17 pub struct StreamState<S> {
18     pub stream: S,
19     pub error: Option<io::Error>,
20     pub panic: Option<Box<dyn Any + Send>>,
21     pub dtls_mtu_size: c_long,
22 }
23 
24 /// Safe wrapper for BIO_METHOD
25 pub struct BioMethod(BIO_METHOD);
26 
27 impl BioMethod {
new<S: Read + Write>() -> Result<BioMethod, ErrorStack>28     fn new<S: Read + Write>() -> Result<BioMethod, ErrorStack> {
29         BIO_METHOD::new::<S>().map(BioMethod)
30     }
31 }
32 
33 unsafe impl Sync for BioMethod {}
34 unsafe impl Send for BioMethod {}
35 
new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack>36 pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack> {
37     let method = BioMethod::new::<S>()?;
38 
39     let state = Box::new(StreamState {
40         stream,
41         error: None,
42         panic: None,
43         dtls_mtu_size: 0,
44     });
45 
46     unsafe {
47         let bio = cvt_p(BIO_new(method.0.get()))?;
48         BIO_set_data(bio, Box::into_raw(state) as *mut _);
49         BIO_set_init(bio, 1);
50 
51         Ok((bio, method))
52     }
53 }
54 
take_error<S>(bio: *mut BIO) -> Option<io::Error>55 pub unsafe fn take_error<S>(bio: *mut BIO) -> Option<io::Error> {
56     let state = state::<S>(bio);
57     state.error.take()
58 }
59 
take_panic<S>(bio: *mut BIO) -> Option<Box<dyn Any + Send>>60 pub unsafe fn take_panic<S>(bio: *mut BIO) -> Option<Box<dyn Any + Send>> {
61     let state = state::<S>(bio);
62     state.panic.take()
63 }
64 
get_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S65 pub unsafe fn get_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S {
66     let state = &*(BIO_get_data(bio) as *const StreamState<S>);
67     &state.stream
68 }
69 
get_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S70 pub unsafe fn get_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S {
71     &mut state(bio).stream
72 }
73 
set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize)74 pub unsafe fn set_dtls_mtu_size<S>(bio: *mut BIO, mtu_size: usize) {
75     if mtu_size as u64 > c_long::max_value() as u64 {
76         panic!(
77             "Given MTU size {} can't be represented in a positive `c_long` range",
78             mtu_size
79         )
80     }
81     state::<S>(bio).dtls_mtu_size = mtu_size as c_long;
82 }
83 
state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S>84 unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
85     &mut *(BIO_get_data(bio) as *mut _)
86 }
87 
bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int88 unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int {
89     BIO_clear_retry_flags(bio);
90 
91     let state = state::<S>(bio);
92     let buf = slice::from_raw_parts(buf as *const _, len as usize);
93 
94     match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) {
95         Ok(Ok(len)) => len as c_int,
96         Ok(Err(err)) => {
97             if retriable_error(&err) {
98                 BIO_set_retry_write(bio);
99             }
100             state.error = Some(err);
101             -1
102         }
103         Err(err) => {
104             state.panic = Some(err);
105             -1
106         }
107     }
108 }
109 
bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int110 unsafe extern "C" fn bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int {
111     BIO_clear_retry_flags(bio);
112 
113     let state = state::<S>(bio);
114     let buf = slice::from_raw_parts_mut(buf as *mut _, len as usize);
115 
116     match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) {
117         Ok(Ok(len)) => len as c_int,
118         Ok(Err(err)) => {
119             if retriable_error(&err) {
120                 BIO_set_retry_read(bio);
121             }
122             state.error = Some(err);
123             -1
124         }
125         Err(err) => {
126             state.panic = Some(err);
127             -1
128         }
129     }
130 }
131 
132 #[allow(clippy::match_like_matches_macro)] // matches macro requires rust 1.42.0
retriable_error(err: &io::Error) -> bool133 fn retriable_error(err: &io::Error) -> bool {
134     match err.kind() {
135         io::ErrorKind::WouldBlock | io::ErrorKind::NotConnected => true,
136         _ => false,
137     }
138 }
139 
bputs<S: Write>(bio: *mut BIO, s: *const c_char) -> c_int140 unsafe extern "C" fn bputs<S: Write>(bio: *mut BIO, s: *const c_char) -> c_int {
141     bwrite::<S>(bio, s, strlen(s) as c_int)
142 }
143 
ctrl<S: Write>( bio: *mut BIO, cmd: c_int, _num: c_long, _ptr: *mut c_void, ) -> c_long144 unsafe extern "C" fn ctrl<S: Write>(
145     bio: *mut BIO,
146     cmd: c_int,
147     _num: c_long,
148     _ptr: *mut c_void,
149 ) -> c_long {
150     let state = state::<S>(bio);
151 
152     if cmd == BIO_CTRL_FLUSH {
153         match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) {
154             Ok(Ok(())) => 1,
155             Ok(Err(err)) => {
156                 state.error = Some(err);
157                 0
158             }
159             Err(err) => {
160                 state.panic = Some(err);
161                 0
162             }
163         }
164     } else if cmd == BIO_CTRL_DGRAM_QUERY_MTU {
165         state.dtls_mtu_size
166     } else {
167         0
168     }
169 }
170 
create(bio: *mut BIO) -> c_int171 unsafe extern "C" fn create(bio: *mut BIO) -> c_int {
172     BIO_set_init(bio, 0);
173     BIO_set_num(bio, 0);
174     BIO_set_data(bio, ptr::null_mut());
175     BIO_set_flags(bio, 0);
176     1
177 }
178 
destroy<S>(bio: *mut BIO) -> c_int179 unsafe extern "C" fn destroy<S>(bio: *mut BIO) -> c_int {
180     if bio.is_null() {
181         return 0;
182     }
183 
184     let data = BIO_get_data(bio);
185     assert!(!data.is_null());
186     Box::<StreamState<S>>::from_raw(data as *mut _);
187     BIO_set_data(bio, ptr::null_mut());
188     BIO_set_init(bio, 0);
189     1
190 }
191 
192 cfg_if! {
193     if #[cfg(any(ossl110, libressl273))] {
194         use ffi::{BIO_get_data, BIO_set_data, BIO_set_flags, BIO_set_init};
195         use crate::cvt;
196 
197         #[allow(bad_style)]
198         unsafe fn BIO_set_num(_bio: *mut ffi::BIO, _num: c_int) {}
199 
200         #[allow(bad_style, clippy::upper_case_acronyms)]
201         struct BIO_METHOD(*mut ffi::BIO_METHOD);
202 
203         impl BIO_METHOD {
204             fn new<S: Read + Write>() -> Result<BIO_METHOD, ErrorStack> {
205                 unsafe {
206                     let ptr = cvt_p(ffi::BIO_meth_new(ffi::BIO_TYPE_NONE, b"rust\0".as_ptr() as *const _))?;
207                     let method = BIO_METHOD(ptr);
208                     cvt(ffi::BIO_meth_set_write(method.0, bwrite::<S>))?;
209                     cvt(ffi::BIO_meth_set_read(method.0, bread::<S>))?;
210                     cvt(ffi::BIO_meth_set_puts(method.0, bputs::<S>))?;
211                     cvt(ffi::BIO_meth_set_ctrl(method.0, ctrl::<S>))?;
212                     cvt(ffi::BIO_meth_set_create(method.0, create))?;
213                     cvt(ffi::BIO_meth_set_destroy(method.0, destroy::<S>))?;
214                     Ok(method)
215                 }
216             }
217 
218             fn get(&self) -> *mut ffi::BIO_METHOD {
219                 self.0
220             }
221         }
222 
223         impl Drop for BIO_METHOD {
224             fn drop(&mut self) {
225                 unsafe {
226                     ffi::BIO_meth_free(self.0);
227                 }
228             }
229         }
230     } else {
231         #[allow(bad_style, clippy::upper_case_acronyms)]
232         struct BIO_METHOD(*mut ffi::BIO_METHOD);
233 
234         impl BIO_METHOD {
235             fn new<S: Read + Write>() -> Result<BIO_METHOD, ErrorStack> {
236                 let ptr = Box::new(ffi::BIO_METHOD {
237                     type_: ffi::BIO_TYPE_NONE,
238                     name: b"rust\0".as_ptr() as *const _,
239                     bwrite: Some(bwrite::<S>),
240                     bread: Some(bread::<S>),
241                     bputs: Some(bputs::<S>),
242                     bgets: None,
243                     ctrl: Some(ctrl::<S>),
244                     create: Some(create),
245                     destroy: Some(destroy::<S>),
246                     callback_ctrl: None,
247                 });
248 
249                 Ok(BIO_METHOD(Box::into_raw(ptr)))
250             }
251 
252             fn get(&self) -> *mut ffi::BIO_METHOD {
253                 self.0
254             }
255         }
256 
257         impl Drop for BIO_METHOD {
258             fn drop(&mut self) {
259                 unsafe {
260                     Box::<ffi::BIO_METHOD>::from_raw(self.0);
261                 }
262             }
263         }
264 
265         #[allow(bad_style)]
266         unsafe fn BIO_set_init(bio: *mut ffi::BIO, init: c_int) {
267             (*bio).init = init;
268         }
269 
270         #[allow(bad_style)]
271         unsafe fn BIO_set_flags(bio: *mut ffi::BIO, flags: c_int) {
272             (*bio).flags = flags;
273         }
274 
275         #[allow(bad_style)]
276         unsafe fn BIO_get_data(bio: *mut ffi::BIO) -> *mut c_void {
277             (*bio).ptr
278         }
279 
280         #[allow(bad_style)]
281         unsafe fn BIO_set_data(bio: *mut ffi::BIO, data: *mut c_void) {
282             (*bio).ptr = data;
283         }
284 
285         #[allow(bad_style)]
286         unsafe fn BIO_set_num(bio: *mut ffi::BIO, num: c_int) {
287             (*bio).num = num;
288         }
289     }
290 }
291