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::{ options, 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 fetch_config(filename: &str) -> String //FETCH CONFIG FROM GIT
54{
55    misc::fetch_data(&(options::CONFIG_URL.to_owned() + filename)).expect("Fetching config failed")
56}
57
58fn init_config(filename: &str) //CREATE CONFIG IF MISSING
59{
60    misc::check_directory(); //CREATE USER CONFIG DIRECTORY IF MISSING
61
62    let config_path = config_path(filename);
63    if !Path::new(&config_path).is_file()
64    {
65        let mut config_file = File::create(config_path).expect("Failed to create WHY2 config"); //CREATE CONFIG
66
67        let mut config = Cursor::new(fetch_config(filename));
68        io::copy(&mut config, &mut config_file).expect("Failed writing to config file");
69    }
70}
71
72fn get_data(path: &str) -> DocumentMut //GET DocumentMut FROM path
73{
74    let content = fs::read_to_string(path).expect("Failed to read config"); //READ CONFIG FILE
75    content.parse::<DocumentMut>().expect("Failed to parse config") //PARSE CONFIG & RETURN
76}
77
78fn config_read<T: FromStr>(filename: &str, key: &str) -> T //READ CONFIG
79where
80    T::Err: Debug,
81{
82    let data = get_data(&config_path(filename));
83
84    //READ
85    if let Some(value) = data.get(key) //FOUND IN CONFIG
86    {
87        //USE APPROPRIATE DATATYPE
88        let string_value = match value.as_value().expect("Invalid config")
89        {
90            Value::String(s) => s.value().to_string(),
91            Value::Integer(i) => i.value().to_string(),
92            Value::Boolean(b) => b.value().to_string(),
93
94            _ => panic!("Unsupported config datatype")
95        };
96
97        return string_value.parse::<T>().expect("Parsing config value failed");
98    }
99
100    //key NOT FOUND IN CONFIG, FETCH CONFIG AND INSERT NEW KEY
101    let mut new_config: DocumentMut = fetch_config(filename).parse().expect("Failed to parse config");
102
103    //LOAD OLD CONFIG
104    for (key, old_value) in data.as_table()
105    {
106        //NEW CONFIG CONTAINS SAME KEY AS THE OLD ONE, USE OLD VALUE
107        if let Some(item) = new_config.get_mut(key)
108        {
109            //COPY OLD VALUE
110            *item.as_value_mut().expect("Updating config failed") = old_value.as_value().expect("Invalid config").clone();
111        }
112    }
113
114    //UPDATE
115    fs::write(&config_path(&filename), new_config.to_string()).expect("Updating config file failed");
116
117    //REPEAT
118    config_read(filename, key)
119}
120
121fn config_write(filename: &str, key: &str, value: &str) //WRITE TO CONFIG
122{
123    let path = config_path(filename); //PATH TO CONFIG
124
125    //GET data
126    let mut data = get_data(&path);
127
128    //WRITE
129    let table = data.as_table_mut();
130    if let Some(item) = table.get_mut(key)
131    {
132        *item.as_value_mut().expect("Updating config failed") = value.into();
133    } else
134    {
135        table.insert(key, value.into());
136    }
137
138    //SAVE
139    fs::write(&path, data.to_string()).expect("Saving config failed");
140}
141
142//PUBLIC
143#[cfg(feature = "server")]
144pub fn init_server_config() //INITIALIZE SERVER CONFIG FILES
145{
146    init_config(options::SERVER_CONFIG); //DOWNLOAD server.toml
147
148    let users_dir_path = config_path(options::SERVER_USERS_CONFIG);
149    if !Path::new(&users_dir_path).is_file()
150    {
151        fs::write(&users_dir_path, "#*#**#*###**#***###*#").expect("Writing to config failed");
152    }
153}
154
155#[cfg(feature = "client")]
156pub fn init_client_config()
157{
158    init_config(options::CLIENT_CONFIG); //DOWNLOAD client.toml
159
160    //CRETE CONFIG FOR TOFU
161    let server_keys_path = config_path(options::SERVER_KEYS_CONFIG);
162    if !Path::new(&server_keys_path).is_file()
163    {
164        File::create(server_keys_path).expect("Failed to create server-keys config"); //CREATE CONFIG
165    }
166}
167
168#[cfg(feature = "server")]
169pub fn server_config<T: FromStr>(key: &str) -> T //RETURN key FROM server.toml
170where
171    T::Err: Debug,
172{
173    config_read(options::SERVER_CONFIG, key)
174}
175
176#[cfg(feature = "client")]
177pub fn client_config<T: FromStr>(key: &str) -> T //RETURN key FROM client.toml
178where
179    T::Err: Debug,
180{
181    config_read(options::CLIENT_CONFIG, key)
182}
183
184#[cfg(feature = "server")]
185pub fn server_users_config(key: &str) -> String //RETURN key FROM server_users.toml
186{
187    config_read(options::SERVER_USERS_CONFIG, key)
188}
189
190#[cfg(feature = "client")]
191pub fn client_write(key: &str, value: &str) //WRITE TO client.toml
192{
193    config_write(options::CLIENT_CONFIG, key, value);
194}
195
196#[cfg(feature = "server")]
197pub fn server_users_write(key: &str, value: &str) //WRITE TO server_users.toml
198{
199    config_write(options::SERVER_USERS_CONFIG, key, value);
200}
201
202#[cfg(feature = "server")]
203pub fn server_users_contains(key: &str) -> bool //CHECK IF server_users.toml contains
204{
205    get_data(&config_path(options::SERVER_USERS_CONFIG)).get(key).is_some()
206}
207
208#[cfg(feature = "client")]
209pub fn server_keys_check(host: &str, pubkey: &str) -> TofuCode //CHECK PUBKEY VALIDITY (TOFU)
210{
211    //HASH PUBKEY
212    let pubkey_hash = crypto::sha256(pubkey);
213    let mut pubkey_string = String::with_capacity(64);
214
215    //SERIALIZE
216    for byte in pubkey_hash
217    {
218        write!(pubkey_string, "{:02x}", byte).unwrap();
219    }
220
221    //PEER PUBKEY STORED, CHECK VALIDITY
222    if get_data(&config_path(options::SERVER_KEYS_CONFIG)).get(host).is_some()
223    {
224        //COMPARE
225        return if config_read::<String>(options::SERVER_KEYS_CONFIG, host) == pubkey_string
226        {
227            TofuCode::Valid
228        } else
229        {
230            TofuCode::Mismatch
231        }
232    }
233
234    TofuCode::Unknown(pubkey_string, host.to_string())
235}
236
237#[cfg(feature = "client")]
238pub fn server_keys_save(host: &str, pubkey_hash: &str) //SAVE KEY
239{
240    //WRITE
241    config_write(options::SERVER_KEYS_CONFIG, host, pubkey_hash);
242}