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 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 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 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 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 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 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}