nut_client/blocking/
mod.rs

1use std::io::{BufRead, BufReader, Write};
2use std::net::{SocketAddr, TcpStream};
3
4use crate::blocking::stream::ConnectionStream;
5use crate::cmd::{Command, Response};
6use crate::{ClientError, Config, Host, NutError};
7
8mod stream;
9
10/// A blocking NUT client connection.
11pub enum Connection {
12    /// A TCP connection.
13    Tcp(TcpConnection),
14}
15
16impl Connection {
17    /// Initializes a connection to a NUT server (upsd).
18    pub fn new(config: &Config) -> crate::Result<Self> {
19        let mut conn = match &config.host {
20            Host::Tcp(host) => Self::Tcp(TcpConnection::new(config.clone(), &host.addr)?),
21        };
22
23        conn.get_network_version()?;
24        conn.login(config)?;
25
26        Ok(conn)
27    }
28
29    /// Gracefully closes the connection.
30    pub fn close(mut self) -> crate::Result<()> {
31        self.logout()?;
32        Ok(())
33    }
34
35    /// Sends username and password, as applicable.
36    fn login(&mut self, config: &Config) -> crate::Result<()> {
37        if let Some(auth) = config.auth.clone() {
38            // Pass username and check for 'OK'
39            self.set_username(&auth.username)?;
40
41            // Pass password and check for 'OK'
42            if let Some(password) = &auth.password {
43                self.set_password(password)?;
44            }
45        }
46        Ok(())
47    }
48}
49
50/// A blocking TCP NUT client connection.
51pub struct TcpConnection {
52    config: Config,
53    stream: ConnectionStream,
54}
55
56impl TcpConnection {
57    fn new(config: Config, socket_addr: &SocketAddr) -> crate::Result<Self> {
58        // Create the TCP connection
59        let tcp_stream = TcpStream::connect_timeout(socket_addr, config.timeout)?;
60        let mut connection = Self {
61            config,
62            stream: ConnectionStream::Plain(tcp_stream),
63        };
64        connection = connection.enable_ssl()?;
65        Ok(connection)
66    }
67
68    #[cfg(feature = "ssl")]
69    fn enable_ssl(mut self) -> crate::Result<Self> {
70        if self.config.ssl {
71            self.write_cmd(Command::StartTLS)?;
72            self.read_response()
73                .map_err(|e| {
74                    if let crate::ClientError::Nut(NutError::FeatureNotConfigured) = e {
75                        crate::ClientError::Nut(NutError::SslNotSupported)
76                    } else {
77                        e
78                    }
79                })?
80                .expect_ok()?;
81
82            let mut ssl_config = rustls::ClientConfig::new();
83            let sess = if self.config.ssl_insecure {
84                ssl_config
85                    .dangerous()
86                    .set_certificate_verifier(std::sync::Arc::new(
87                        crate::ssl::InsecureCertificateValidator::new(&self.config),
88                    ));
89
90                let dns_name = webpki::DNSNameRef::try_from_ascii_str("www.google.com").unwrap();
91
92                rustls::ClientSession::new(&std::sync::Arc::new(ssl_config), dns_name)
93            } else {
94                // Try to get hostname as given (e.g. localhost can be used for strict SSL, but not 127.0.0.1)
95                let hostname = self
96                    .config
97                    .host
98                    .hostname()
99                    .ok_or(ClientError::Nut(NutError::SslInvalidHostname))?;
100
101                let dns_name = webpki::DNSNameRef::try_from_ascii_str(&hostname)
102                    .map_err(|_| ClientError::Nut(NutError::SslInvalidHostname))?;
103
104                ssl_config
105                    .root_store
106                    .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
107
108                rustls::ClientSession::new(&std::sync::Arc::new(ssl_config), dns_name)
109            };
110
111            // Wrap and override the TCP stream
112            self.stream = self.stream.upgrade_ssl(sess)?;
113        }
114        Ok(self)
115    }
116
117    #[cfg(not(feature = "ssl"))]
118    fn enable_ssl(self) -> crate::Result<Self> {
119        Ok(self)
120    }
121
122    pub(crate) fn write_cmd(&mut self, line: Command) -> crate::Result<()> {
123        let line = format!("{}\n", line);
124        if self.config.debug {
125            eprint!("DEBUG -> {}", line);
126        }
127        self.stream.write_all(line.as_bytes())?;
128        self.stream.flush()?;
129        Ok(())
130    }
131
132    fn parse_line(
133        reader: &mut BufReader<&mut ConnectionStream>,
134        debug: bool,
135    ) -> crate::Result<Vec<String>> {
136        let mut raw = String::new();
137        reader.read_line(&mut raw)?;
138        if debug {
139            eprint!("DEBUG <- {}", raw);
140        }
141        raw = raw[..raw.len() - 1].to_string(); // Strip off \n
142
143        // Parse args by splitting whitespace, minding quotes for args with multiple words
144        let args = shell_words::split(&raw)
145            .map_err(|e| NutError::Generic(format!("Parsing server response failed: {}", e)))?;
146
147        Ok(args)
148    }
149
150    pub(crate) fn read_response(&mut self) -> crate::Result<Response> {
151        let mut reader = BufReader::new(&mut self.stream);
152        let args = Self::parse_line(&mut reader, self.config.debug)?;
153        Response::from_args(args)
154    }
155
156    pub(crate) fn read_plain_response(&mut self) -> crate::Result<String> {
157        let mut reader = BufReader::new(&mut self.stream);
158        let args = Self::parse_line(&mut reader, self.config.debug)?;
159        Ok(args.join(" "))
160    }
161
162    pub(crate) fn read_list(&mut self, query: &[&str]) -> crate::Result<Vec<Response>> {
163        let mut reader = BufReader::new(&mut self.stream);
164        let args = Self::parse_line(&mut reader, self.config.debug)?;
165
166        Response::from_args(args)?.expect_begin_list(query)?;
167        let mut lines: Vec<Response> = Vec::new();
168
169        loop {
170            let args = Self::parse_line(&mut reader, self.config.debug)?;
171            let resp = Response::from_args(args)?;
172
173            match resp {
174                Response::EndList(_) => {
175                    break;
176                }
177                _ => lines.push(resp),
178            }
179        }
180
181        Ok(lines)
182    }
183}