rndc/
lib.rs

1mod internal;
2
3use base64::Engine;
4use base64::engine::general_purpose;
5use indexmap::IndexMap;
6use internal::constants::RndcAlg;
7use internal::{decoder, decoder::RNDCPayload, encoder, encoder::RNDCValue, utils};
8use std::io::{Read, Write};
9use std::net::TcpStream;
10
11#[derive(Debug, Clone)]
12pub struct RndcClient {
13    server_url: String,
14    algorithm: RndcAlg,
15    secret_key: Vec<u8>,
16}
17
18#[derive(Debug, Clone)]
19pub struct RndcResult {
20    pub result: bool,
21    pub text: Option<String>,
22    pub err: Option<String>,
23}
24
25impl RndcClient {
26    pub fn new(server_url: &str, algorithm: &str, secret_key_b64: &str) -> Self {
27        let secret_key = general_purpose::STANDARD
28            .decode(secret_key_b64.as_bytes())
29            .expect("Invalid base64 RNDC_SECRET_KEY");
30
31        RndcClient {
32            server_url: server_url.to_string(),
33            algorithm: RndcAlg::from_string(algorithm).expect("Invalid RNDC algorithm"),
34            secret_key,
35        }
36    }
37
38    fn get_stream(&self) -> TcpStream {
39        TcpStream::connect(&self.server_url).expect("Failed to connect to RNDC server")
40    }
41
42    fn close_stream(&self, stream: &TcpStream) -> Result<(), String> {
43        stream
44            .shutdown(std::net::Shutdown::Both)
45            .map_err(|e| format!("Failed to shutdown stream: {}", e))?;
46
47        Ok(())
48    }
49
50    fn rndc_handshake(&self) -> Result<(TcpStream, String), String> {
51        let msg = Self::build_message(
52            "null",
53            &self.algorithm,
54            &self.secret_key,
55            None,
56            rand::random(),
57        )?;
58
59        let mut stream = self.get_stream();
60        stream
61            .write_all(&msg)
62            .map_err(|e| format!("Failed to write to stream: {}", e))?;
63
64        let res = RndcClient::read_packet(&mut stream)
65            .map_err(|e| format!("Failed to read packet: {}", e))?;
66
67        let nonce = self.get_nonce(&res)?;
68
69        Ok((stream, nonce))
70    }
71
72    pub fn rndc_command(&self, command: &str) -> Result<RndcResult, String> {
73        let (mut stream, nonce) = self.rndc_handshake()?;
74
75        let msg = RndcClient::build_message(
76            command,
77            &self.algorithm,
78            &self.secret_key,
79            Some(&nonce),
80            rand::random(),
81        )?;
82
83        stream
84            .write_all(&msg)
85            .map_err(|e| format!("Failed to write to stream: {}", e))?;
86
87        let res = RndcClient::read_packet(&mut stream)
88            .map_err(|e| format!("Failed to read packet: {}", e))?;
89
90        self.close_stream(&stream)?;
91
92        let resp = decoder::decode(&res)?;
93
94        if let Some(RNDCPayload::Table(data)) = resp.get("_data") {
95            // dbg!("Received data: {:?}", data);
96
97            let result = data.get("result").and_then(|v| {
98                if let RNDCPayload::String(s) = v {
99                    Some(s == "0")
100                } else {
101                    None
102                }
103            });
104            let text = data.get("text").and_then(|v| {
105                if let RNDCPayload::String(s) = v {
106                    Some(s.clone())
107                } else {
108                    None
109                }
110            });
111            let err = data.get("err").and_then(|v| {
112                if let RNDCPayload::String(s) = v {
113                    Some(s.clone())
114                } else {
115                    None
116                }
117            });
118
119            return Ok(RndcResult {
120                result: result.unwrap_or(false),
121                text,
122                err,
123            });
124        }
125        Err("Failed to parse status response".to_string())
126    }
127
128    fn build_message(
129        command: &str,
130        algorithm: &RndcAlg,
131        secret: &[u8],
132        nonce: Option<&str>,
133        ser: u32,
134    ) -> Result<Vec<u8>, String> {
135        let now = utils::get_timestamp();
136        let exp = now + 60;
137
138        // _ctrl = {type: rndc, _ser, _tim, _exp}
139        let mut ctrl_map = IndexMap::new();
140        ctrl_map.insert(
141            "_ser".to_string(),
142            RNDCValue::Binary(ser.to_string().into_bytes()),
143        );
144        ctrl_map.insert(
145            "_tim".to_string(),
146            RNDCValue::Binary(now.to_string().into_bytes()),
147        );
148        ctrl_map.insert(
149            "_exp".to_string(),
150            RNDCValue::Binary(exp.to_string().into_bytes()),
151        );
152        if let Some(nonce) = nonce {
153            ctrl_map.insert(
154                "_nonce".to_string(),
155                RNDCValue::Binary(nonce.as_bytes().to_vec()),
156            );
157        }
158
159        // _data = {type: command}
160        let mut data_map = IndexMap::new();
161        data_map.insert(
162            "type".to_string(),
163            RNDCValue::Binary(command.as_bytes().to_vec()),
164        );
165
166        // message_body = {_ctrl, _data}
167        let mut message_body = IndexMap::new();
168        message_body.insert("_ctrl".to_string(), RNDCValue::Table(ctrl_map));
169        message_body.insert("_data".to_string(), RNDCValue::Table(data_map));
170
171        match encoder::encode(&mut message_body, algorithm, secret) {
172            Ok(buf) => Ok(buf),
173            Err(e) => Err(format!("Failed to encode message: {}", e)),
174        }
175    }
176
177    fn get_nonce(&self, packet: &[u8]) -> Result<String, String> {
178        let resp = decoder::decode(packet)?;
179        if let Some(RNDCPayload::Table(ctrl_map)) = resp.get("_ctrl").and_then(|ctrl| {
180            if let RNDCPayload::Table(map) = ctrl {
181                Some(RNDCPayload::Table(map.clone()))
182            } else {
183                None
184            }
185        }) {
186            if let Some(RNDCPayload::String(new_nonce)) = ctrl_map.get("_nonce") {
187                // println!("Received nonce: {:?}", new_nonce);
188                return Ok(new_nonce.to_string());
189            }
190        }
191        Err("RNDC nonce not received".to_string())
192    }
193
194    fn read_packet(stream: &mut TcpStream) -> Result<Vec<u8>, String> {
195        let mut header = [0u8; 8];
196        stream.read_exact(&mut header).map_err(|e| {
197            format!(
198                "Failed to read header: {} (expected length: {})",
199                e,
200                header.len()
201            )
202        })?;
203
204        let length_field = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) - 4;
205        // let version = u32::from_be_bytes([header[4], header[5], header[6], header[7]]);
206
207        let mut payload = vec![0u8; length_field as usize];
208        stream.read_exact(&mut payload).map_err(|e| {
209            format!(
210                "Failed to read payload: {} (expected length: {})",
211                e, length_field
212            )
213        })?;
214
215        let mut full_packet = Vec::with_capacity(8 + payload.len());
216        full_packet.extend_from_slice(&header);
217        full_packet.extend_from_slice(&payload);
218
219        Ok(full_packet)
220    }
221}