1use std::fmt;
11use std::io::{self, Read, Write};
12use std::net::{Shutdown, SocketAddr, TcpStream, ToSocketAddrs};
13use std::os::unix::net::UnixStream;
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, mpsc};
16use std::time::Duration;
17
18use rustls::pki_types::{CertificateDer, ServerName, pem::PemObject};
19use rustls::{ClientConfig, ClientConnection, RootCertStore, StreamOwned};
20
21pub const CERT_FILE: &str = "SSL_CERT_FILE";
24
25#[derive(Clone, Debug)]
28pub enum Address {
29 Socket(PathBuf),
30 Network(Remote),
31}
32
33impl Address {
34 #[must_use]
37 pub fn named(&self) -> String {
38 match self {
39 Self::Socket(path) => path.display().to_string(),
40 Self::Network(remote) => remote.endpoint.clone(),
41 }
42 }
43}
44
45#[derive(Clone)]
48pub struct Remote {
49 endpoint: String,
50 server_name: ServerName<'static>,
51 trust: Trust,
52}
53
54impl fmt::Debug for Remote {
55 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
56 formatter
57 .debug_struct("Remote")
58 .field("endpoint", &self.endpoint)
59 .field("server_name", &self.server_name)
60 .finish_non_exhaustive()
61 }
62}
63
64impl Remote {
65 pub fn new(endpoint: &str, server_name: &str, trust: &Trust) -> Result<Self, String> {
72 if !endpoint_parses(endpoint) {
73 return Err(format!(
74 "endpoint {endpoint:?} is not <host>:<port> with a port from 1 to 65535"
75 ));
76 }
77 Ok(Self {
78 endpoint: endpoint.to_owned(),
79 server_name: name(server_name)?,
80 trust: trust.clone(),
81 })
82 }
83
84 #[must_use]
86 pub fn endpoint(&self) -> &str {
87 &self.endpoint
88 }
89}
90
91pub fn name(text: &str) -> Result<ServerName<'static>, String> {
97 ServerName::try_from(text.to_owned())
98 .map_err(|_| format!("server_name {text:?} is not a DNS name or an IP address"))
99}
100
101#[must_use]
104pub fn endpoint_parses(text: &str) -> bool {
105 let Some((host, port)) = text.rsplit_once(':') else {
106 return false;
107 };
108 let port_ok = port.parse::<u16>().is_ok_and(|port| port != 0);
109 let host_ok = host.strip_prefix('[').map_or_else(
110 || {
111 !host.is_empty()
112 && host.len() <= 253
113 && host
114 .bytes()
115 .all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'.')
116 },
117 |inner| {
118 inner
119 .strip_suffix(']')
120 .is_some_and(|inner| inner.parse::<std::net::Ipv6Addr>().is_ok())
121 },
122 );
123 port_ok && host_ok
124}
125
126#[derive(Clone)]
128pub struct Trust(Arc<ClientConfig>);
129
130impl Trust {
131 pub fn system() -> Result<Self, String> {
141 let mut roots = mozilla();
142 if let Some(file) = std::env::var_os(CERT_FILE) {
143 add_file(&mut roots, Path::new(&file))?;
144 }
145 Self::from_roots(roots)
146 }
147
148 pub fn mozilla() -> Result<Self, String> {
156 Self::from_roots(mozilla())
157 }
158
159 #[cfg(test)]
165 pub(crate) fn only(file: &Path) -> Result<Self, String> {
166 let mut roots = RootCertStore::empty();
167 add_file(&mut roots, file)?;
168 Self::from_roots(roots)
169 }
170
171 #[must_use]
174 pub(crate) fn client_config(&self) -> Arc<ClientConfig> {
175 Arc::clone(&self.0)
176 }
177
178 fn from_roots(roots: RootCertStore) -> Result<Self, String> {
179 ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider()))
180 .with_safe_default_protocol_versions()
181 .map(|builder| {
182 Self(Arc::new(
183 builder.with_root_certificates(roots).with_no_client_auth(),
184 ))
185 })
186 .map_err(|error| format!("door_trust_roots_invalid: protocol versions: {error}"))
187 }
188}
189
190fn mozilla() -> RootCertStore {
191 let mut roots = RootCertStore::empty();
192 roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
193 roots
194}
195
196fn add_file(roots: &mut RootCertStore, file: &Path) -> Result<(), String> {
197 let invalid =
198 |why: &dyn fmt::Display| format!("door_trust_roots_invalid: {}: {why}", file.display());
199 let mut added = 0_usize;
200 for certificate in CertificateDer::pem_file_iter(file).map_err(|error| invalid(&error))? {
201 roots
202 .add(certificate.map_err(|error| invalid(&error))?)
203 .map_err(|error| invalid(&error))?;
204 added += 1;
205 }
206 if added == 0 {
207 return Err(invalid(&"holds no certificate"));
208 }
209 Ok(())
210}
211
212#[derive(Debug)]
214pub enum Refused {
215 Unreached(String),
217 NotTls(String),
220 Untrusted(String),
222 TlsRefused(String),
224}
225
226impl Refused {
227 #[must_use]
229 pub const fn code(&self) -> &'static str {
230 match self {
231 Self::Unreached(_) => "door_store_unreached",
232 Self::NotTls(_) => "door_store_not_tls",
233 Self::Untrusted(_) => "door_store_tls_untrusted",
234 Self::TlsRefused(_) => "door_store_tls_refused",
235 }
236 }
237
238 #[must_use]
240 pub fn why(&self) -> &str {
241 match self {
242 Self::Unreached(why)
243 | Self::NotTls(why)
244 | Self::Untrusted(why)
245 | Self::TlsRefused(why) => why,
246 }
247 }
248}
249
250impl fmt::Display for Refused {
251 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
252 write!(formatter, "{}: {}", self.code(), self.why())
253 }
254}
255
256pub enum Stream {
258 Unix(UnixStream),
259 Tls(Box<StreamOwned<ClientConnection, TcpStream>>),
260}
261
262impl Stream {
263 pub fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
269 match self {
270 Self::Unix(stream) => stream.set_read_timeout(timeout),
271 Self::Tls(stream) => stream.sock.set_read_timeout(timeout),
272 }
273 }
274
275 pub fn set_write_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
281 match self {
282 Self::Unix(stream) => stream.set_write_timeout(timeout),
283 Self::Tls(stream) => stream.sock.set_write_timeout(timeout),
284 }
285 }
286
287 pub fn hangup(&self) -> io::Result<Hangup> {
293 Ok(match self {
294 Self::Unix(stream) => Hangup::Unix(stream.try_clone()?),
295 Self::Tls(stream) => Hangup::Tcp(stream.sock.try_clone()?),
296 })
297 }
298}
299
300impl Read for Stream {
301 fn read(&mut self, bytes: &mut [u8]) -> io::Result<usize> {
302 match self {
303 Self::Unix(stream) => stream.read(bytes),
304 Self::Tls(stream) => match stream.read(bytes) {
308 Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => Ok(0),
309 read => read,
310 },
311 }
312 }
313}
314
315impl Write for Stream {
316 fn write(&mut self, bytes: &[u8]) -> io::Result<usize> {
317 match self {
318 Self::Unix(stream) => stream.write(bytes),
319 Self::Tls(stream) => stream.write(bytes),
320 }
321 }
322
323 fn flush(&mut self) -> io::Result<()> {
324 match self {
325 Self::Unix(stream) => stream.flush(),
326 Self::Tls(stream) => stream.flush(),
327 }
328 }
329}
330
331pub enum Hangup {
333 Unix(UnixStream),
334 Tcp(TcpStream),
335}
336
337impl Hangup {
338 pub fn shutdown(&self, how: Shutdown) -> io::Result<()> {
344 match self {
345 Self::Unix(stream) => stream.shutdown(how),
346 Self::Tcp(stream) => stream.shutdown(how),
347 }
348 }
349}
350
351pub fn open(remote: &Remote, timeout: Duration) -> Result<Stream, Refused> {
361 let endpoint = remote.endpoint.clone();
362 let (sender, receiver) = mpsc::channel();
363 let thread_endpoint = endpoint.clone();
364 std::thread::Builder::new()
365 .name("haem-network-connect".to_owned())
366 .spawn(move || {
367 let _unwaited = sender.send(connect(&thread_endpoint, timeout));
369 })
370 .map_err(|error| Refused::Unreached(format!("the connect could not start: {error}")))?;
371 let mut tcp = match receiver.recv_timeout(timeout) {
372 Ok(Ok(tcp)) => tcp,
373 Ok(Err(error)) => {
374 return Err(Refused::Unreached(format!(
375 "could not connect to {endpoint}: {error}"
376 )));
377 }
378 Err(_) => {
379 return Err(Refused::Unreached(format!(
380 "could not connect to {endpoint} within {} ms",
381 timeout.as_millis()
382 )));
383 }
384 };
385 let unbounded = |error: io::Error| {
386 Refused::Unreached(format!(
387 "could not bound the connection to {endpoint}: {error}"
388 ))
389 };
390 tcp.set_read_timeout(Some(timeout)).map_err(unbounded)?;
391 tcp.set_write_timeout(Some(timeout)).map_err(unbounded)?;
392 let mut connection =
393 ClientConnection::new(Arc::clone(&remote.trust.0), remote.server_name.clone()).map_err(
394 |error| Refused::TlsRefused(format!("{endpoint}: TLS could not start: {error}")),
395 )?;
396 while connection.is_handshaking() {
397 if let Err(error) = connection.complete_io(&mut tcp) {
398 return Err(handshake_failed(&endpoint, &error));
399 }
400 }
401 Ok(Stream::Tls(Box::new(StreamOwned::new(connection, tcp))))
402}
403
404fn connect(endpoint: &str, timeout: Duration) -> io::Result<TcpStream> {
405 let addresses: Vec<SocketAddr> = endpoint.to_socket_addrs()?.collect();
406 let mut last = io::Error::new(
407 io::ErrorKind::NotFound,
408 "the endpoint's name resolved to no address",
409 );
410 for address in addresses {
411 match TcpStream::connect_timeout(&address, timeout) {
412 Ok(tcp) => return Ok(tcp),
413 Err(error) => last = error,
414 }
415 }
416 Err(last)
417}
418
419fn handshake_failed(endpoint: &str, error: &io::Error) -> Refused {
422 let tls = error
423 .get_ref()
424 .and_then(|inner| inner.downcast_ref::<rustls::Error>());
425 match tls {
426 Some(rustls::Error::InvalidCertificate(why)) => Refused::Untrusted(format!(
427 "{endpoint}: the store's certificate is not trusted for its server_name: {why:?}"
428 )),
429 Some(rustls::Error::AlertReceived(alert)) => Refused::TlsRefused(format!(
430 "{endpoint}: the store refused the TLS handshake: {alert:?}"
431 )),
432 _ if matches!(
433 error.kind(),
434 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
435 ) =>
436 {
437 Refused::Unreached(format!(
438 "{endpoint}: no TLS handshake answer within the bound: {error}"
439 ))
440 }
441 _ => Refused::NotTls(format!(
442 "{endpoint}: the endpoint did not complete a TLS handshake; a network store is reached only over TLS, never plain TCP: {error}"
443 )),
444 }
445}