1use std::
20{
21 str::FromStr,
22 fmt::Debug,
23 path::Path,
24 io::{ self, Cursor },
25 fs::{ self, File },
26};
27
28use toml_edit::{ DocumentMut, Value };
29
30use crate::{ consts, misc };
31
32#[cfg(feature = "client")]
33use std::fmt::Write;
34
35#[cfg(feature = "client")]
36use crate::crypto;
37
38#[cfg(feature = "client")]
40pub enum TofuCode {
42 Valid, Unknown(String, String), Mismatch, }
46
47fn config_path(filename: &str) -> String {
50 misc::get_why2_dir() + filename
51}
52
53fn get_config() -> &'static str {
55 #[cfg(feature = "client")]
57 {
58 include_str!("./client.toml")
59 }
60
61 #[cfg(feature = "server")]
62 {
63 include_str!("./server.toml")
64 }
65}
66
67fn get_data(path: &str) -> DocumentMut {
69 let content = fs::read_to_string(path).expect("Failed to read config"); content.parse::<DocumentMut>().expect("Failed to parse config") }
72
73fn config_read<T: FromStr>(filename: &str, key: &str) -> T where
75 T::Err: Debug,
76{
77 let data = get_data(&config_path(filename));
78
79 if let Some(value) = data.get(key) {
82 let string_value = match value.as_value().expect("Invalid config")
84 {
85 Value::String(s) => s.value().to_string(),
86 Value::Integer(i) => i.value().to_string(),
87 Value::Boolean(b) => b.value().to_string(),
88
89 _ => panic!("Unsupported config datatype")
90 };
91
92 return string_value.parse::<T>().expect("Parsing config value failed");
93 }
94
95 let mut new_config: DocumentMut = get_config().parse().expect("Failed to parse config");
97
98 for (key, old_value) in data.as_table()
100 {
101 if let Some(item) = new_config.get_mut(key)
103 {
104 *item.as_value_mut().expect("Updating config failed") = old_value.as_value().expect("Invalid config").clone();
106 }
107 }
108
109 fs::write(&config_path(&filename), new_config.to_string()).expect("Updating config file failed");
111
112 config_read(filename, key)
114}
115
116fn config_write(filename: &str, key: &str, value: &str) {
118 let path = config_path(filename); let mut data = get_data(&path);
122
123 let table = data.as_table_mut();
125 if let Some(item) = table.get_mut(key)
126 {
127 *item.as_value_mut().expect("Updating config failed") = value.into();
128 } else
129 {
130 table.insert(key, value.into());
131 }
132
133 fs::write(&path, data.to_string()).expect("Saving config failed");
135}
136
137pub fn init_config() {
140 misc::check_directory(); {
143 let filename =
144 {
145 #[cfg(feature = "client")]
146 {
147 consts::CLIENT_CONFIG
148 }
149
150 #[cfg(feature = "server")]
151 {
152 consts::SERVER_CONFIG
153 }
154 };
155
156 let config_path = config_path(filename);
157 if !Path::new(&config_path).is_file()
158 {
159 let mut config_file = File::create(config_path).expect("Failed to create WHY2 config"); let mut config = Cursor::new(get_config());
162 io::copy(&mut config, &mut config_file).expect("Failed writing to config file");
163 }
164 }
165
166 let runtime_path =
167 {
168 #[cfg(feature = "client")]
169 {
170 config_path(consts::SERVER_KEYS_CONFIG)
171 }
172
173 #[cfg(feature = "server")]
174 {
175 config_path(consts::SERVER_USERS_CONFIG)
176 }
177 };
178
179 if !Path::new(&runtime_path).is_file()
181 {
182 fs::write(&runtime_path, "#*#**#*###**#***###*#").expect("Writing to config failed");
183 }
184}
185
186pub fn read_config<T: FromStr>(key: &str) -> T where
188 T::Err: Debug,
189{
190 #[cfg(feature = "client")]
191 {
192 config_read(consts::CLIENT_CONFIG, key)
193 }
194
195 #[cfg(feature = "server")]
196 {
197 config_read(consts::SERVER_CONFIG, key)
198 }
199}
200
201#[cfg(feature = "server")]
202pub fn server_users_config(key: &str) -> String {
204 config_read(consts::SERVER_USERS_CONFIG, key)
205}
206
207#[cfg(feature = "client")]
208pub fn client_write(key: &str, value: &str) {
210 config_write(consts::CLIENT_CONFIG, key, value);
211}
212
213#[cfg(feature = "server")]
214pub fn server_users_write(key: &str, value: &str) {
216 config_write(consts::SERVER_USERS_CONFIG, key, value);
217}
218
219#[cfg(feature = "server")]
220pub fn server_users_contains(key: &str) -> bool {
222 get_data(&config_path(consts::SERVER_USERS_CONFIG)).get(key).is_some()
223}
224
225#[cfg(feature = "client")]
226pub fn server_keys_check(host: &str, pubkey: &str) -> TofuCode {
228 let pubkey_hash = crypto::sha256(pubkey);
230 let mut pubkey_string = String::with_capacity(64);
231
232 for byte in pubkey_hash
234 {
235 write!(pubkey_string, "{:02x}", byte).unwrap();
236 }
237
238 if get_data(&config_path(consts::SERVER_KEYS_CONFIG)).get(host).is_some()
240 {
241 return if config_read::<String>(consts::SERVER_KEYS_CONFIG, host) == pubkey_string
243 {
244 TofuCode::Valid
245 } else
246 {
247 TofuCode::Mismatch
248 }
249 }
250
251 TofuCode::Unknown(pubkey_string, host.to_string())
252}
253
254#[cfg(feature = "client")]
255pub fn server_keys_save(host: &str, pubkey_hash: &str) {
257 config_write(consts::SERVER_KEYS_CONFIG, host, pubkey_hash);
259}