Skip to main content

why2_chat/config/
mod.rs

1/*
2This is part of WHY2
3Copyright (C) 2022-2026 Václav Šmejkal
4
5This program is free software: you can redistribute it and/or modify
6it under the terms of the GNU General Public License as published by
7the Free Software Foundation, either version 3 of the License, or
8(at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program.  If not, see <https://www.gnu.org/licenses/>.
17*/
18
19use 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//ENUMS
39#[cfg(feature = "client")]
40pub enum TofuCode //POSSIBLE KEY VERIFICATION RESULTS
41{
42    Valid, //KEY MATCHES LOCAL CONFIG
43    Unknown(String, String), //KEY NOT FOUND IN CONFIG
44    Mismatch, //KEY DIFFERS
45}
46
47//PRIVATE
48fn config_path(filename: &str) -> String //GET CONFIGURATION PATH
49{
50    misc::get_why2_dir() + filename
51}
52
53fn get_config() -> &'static str //GET CONFIG FROM BINARY
54{
55    //TODO: FIGURE OUT A BETTER WAY TO USE CONSTANTS
56    #[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 //GET DocumentMut FROM path
68{
69    let content = fs::read_to_string(path).expect("Failed to read config"); //READ CONFIG FILE
70    content.parse::<DocumentMut>().expect("Failed to parse config") //PARSE CONFIG & RETURN
71}
72
73fn config_read<T: FromStr>(filename: &str, key: &str) -> T //READ CONFIG
74where
75    T::Err: Debug,
76{
77    let data = get_data(&config_path(filename));
78
79    //READ
80    if let Some(value) = data.get(key) //FOUND IN CONFIG
81    {
82        //USE APPROPRIATE DATATYPE
83        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    //key NOT FOUND IN CONFIG, FETCH CONFIG AND INSERT NEW KEY
96    let mut new_config: DocumentMut = get_config().parse().expect("Failed to parse config");
97
98    //LOAD OLD CONFIG
99    for (key, old_value) in data.as_table()
100    {
101        //NEW CONFIG CONTAINS SAME KEY AS THE OLD ONE, USE OLD VALUE
102        if let Some(item) = new_config.get_mut(key)
103        {
104            //COPY OLD VALUE
105            *item.as_value_mut().expect("Updating config failed") = old_value.as_value().expect("Invalid config").clone();
106        }
107    }
108
109    //UPDATE
110    fs::write(&config_path(&filename), new_config.to_string()).expect("Updating config file failed");
111
112    //REPEAT
113    config_read(filename, key)
114}
115
116fn config_write(filename: &str, key: &str, value: &str) //WRITE TO CONFIG
117{
118    let path = config_path(filename); //PATH TO CONFIG
119
120    //GET data
121    let mut data = get_data(&path);
122
123    //WRITE
124    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    //SAVE
134    fs::write(&path, data.to_string()).expect("Saving config failed");
135}
136
137//PUBLIC
138pub fn init_config() //INITIALIZE CONFIG FILES
139{
140    misc::check_directory(); //CREATE USER CONFIG DIRECTORY IF MISSING
141
142    {
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"); //CREATE CONFIG
160
161            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    //CREATE RUNTIME CONFIG
180    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 //RETURN key FROM TOML CONFIG
187where
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 //RETURN key FROM server_users.toml
203{
204    config_read(consts::SERVER_USERS_CONFIG, key)
205}
206
207#[cfg(feature = "client")]
208pub fn client_write(key: &str, value: &str) //WRITE TO client.toml
209{
210    config_write(consts::CLIENT_CONFIG, key, value);
211}
212
213#[cfg(feature = "server")]
214pub fn server_users_write(key: &str, value: &str) //WRITE TO server_users.toml
215{
216    config_write(consts::SERVER_USERS_CONFIG, key, value);
217}
218
219#[cfg(feature = "server")]
220pub fn server_users_contains(key: &str) -> bool //CHECK IF server_users.toml contains
221{
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 //CHECK PUBKEY VALIDITY (TOFU)
227{
228    //HASH PUBKEY
229    let pubkey_hash = crypto::sha256(pubkey);
230    let mut pubkey_string = String::with_capacity(64);
231
232    //SERIALIZE
233    for byte in pubkey_hash
234    {
235        write!(pubkey_string, "{:02x}", byte).unwrap();
236    }
237
238    //PEER PUBKEY STORED, CHECK VALIDITY
239    if get_data(&config_path(consts::SERVER_KEYS_CONFIG)).get(host).is_some()
240    {
241        //COMPARE
242        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) //SAVE KEY
256{
257    //WRITE
258    config_write(consts::SERVER_KEYS_CONFIG, host, pubkey_hash);
259}