why2_chat/network/
client.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    thread,
22    net::TcpStream,
23    sync::mpsc::Sender,
24};
25
26use zeroize::Zeroizing;
27
28use serde_json::Value;
29
30use crate::
31{
32    options,
33    misc,
34    crypto::kex,
35    config::{ self, TofuCode },
36    network::
37    {
38        self,
39        MessageCode,
40        MessagePacket,
41        voice::
42        {
43            client as voice_client,
44            options as voice_options,
45        }
46    },
47};
48
49//CONSTS
50const GRID_W: usize = options::GRID_DIMENSIONS.0;
51const GRID_H: usize = options::GRID_DIMENSIONS.1;
52
53//STRUCTS
54pub struct VoiceUser
55{
56    pub id: usize,          //ID OF USER
57    pub username: String,   //USERNAME TO DISPLAY
58    pub is_speaking: bool,  //TAKE A WILD GUESS
59    pub latency: u128,      //USER'S PING
60    pub is_local: bool,     //AM I THE USER?
61}
62
63//ENUMS
64pub enum ClientEvent
65{
66    Connected(String),             //SUCCESSFUL CONNECTION MESSAGE
67    Message(MessagePacket),        //RECEIVED MESSAGE
68    Info(String, bool, usize),     //INFO/STATUS LOG, WITH NEWLINE BOOLEAN AND LINES TO CLEAR
69    Prompt(String, String),        //">>>" PROMPT, WITH CHANNEL AND WRITTEN MESSAGE
70    TofuError(TofuCode),           //TOFU VERIFICATION FAILED
71    VoiceActivity(Vec<VoiceUser>), //VOICE OVERLAY
72    Clear(usize),                  //CLEAR n LINES
73    ExtraSpace,                    //JUST RANDOM NEWLINE
74    Quit,                          //SERVER QUIT COMMUNICATION
75}
76
77//FUNCTIONS
78//PRIVATE
79fn key_exchange(stream: &mut TcpStream, keys: &mut options::SharedKeys, tx: &Sender<ClientEvent>) -> bool //KEY EXCHANGE FOR CLIENT-SIDE
80{
81    //WAIT FOR KeyExchange
82    let message = loop
83    {
84        //READ MESSAGE
85        let received = network::receive(stream, None).unwrap();
86
87        if received.code == Some(MessageCode::KeyExchange) { break received; }
88    };
89
90    let message_text = message.text.as_ref().unwrap();
91
92    //PARSE SERVER KEYS JSON
93    let server_keys: Value = serde_json::from_str(message_text).expect("Failed to parse server keys JSON");
94    let server_ecc_pk = server_keys["ecc"].as_str().expect("Parsing server ECC key failed");
95    let server_pq_pk = server_keys["pq"].as_str().expect("Parsing server PQ key failed");
96
97    //VERIFY PUBKEY VALIDITY (TOFU)
98    match config::server_keys_check(&stream.peer_addr().unwrap().ip().to_string(), message_text)
99    {
100        TofuCode::Valid => {},
101
102        status @ (TofuCode::Mismatch | TofuCode::Unknown(_, _)) =>
103        {
104            //GRACEFULLY DISCONNECT FROM SERVER
105            network::send(stream, MessagePacket
106            {
107                code: Some(MessageCode::Disconnect),
108                ..Default::default()
109            }, None);
110
111            //PRINT SECURITY MESSAGE
112            tx.send(ClientEvent::TofuError(status)).unwrap();
113
114            //EXIT
115            return false;
116        },
117    }
118
119    //GENERATE EPHEMERAL ECC KEYS
120    let (sk, pk) = kex::generate_ephemeral_keys();
121
122    //ENCAPSULATE PQ
123    let (pq_ciphertext, pq_secret) = kex::encapsulate_pq(server_pq_pk);
124
125    //PREPARE RESPONSE JSON
126    let response_text = serde_json::json!
127    ({
128        "ecc": pk,
129        "pq": pq_ciphertext,
130    }).to_string();
131
132    //SEND ECC PUBKEY TO SERVER
133    network::send(stream, MessagePacket
134    {
135        text: Some(response_text),
136        code: Some(MessageCode::KeyExchange),
137        ..Default::default()
138    }, None);
139
140    //CALCULATE SHARED SECRET (HYBRID)
141    *keys = kex::derive_shared_secret::<GRID_W, GRID_H>(sk, server_ecc_pk.to_string(), pq_secret).expect("Shared secret derivation failed");
142
143    //SET GLOBAL KEYS VARIABLE
144    options::set_keys(keys.clone());
145
146    true
147}
148
149//PUBLIC
150pub fn listen_server(stream: &mut TcpStream, tx: Sender<ClientEvent>) //SERVER -> CLIENT COMMUNICATION
151{
152    //SET GLOBAL CLIENT ENCRYPTION & MAC KEY
153    let mut keys = (Zeroizing::new(vec![]), Zeroizing::new(vec![]));
154    if !key_exchange(stream, &mut keys, &tx) { return; }
155
156    //SERVER INFO VARIABLES
157    let mut min_pass: Option<u64> = None;
158    let mut max_uname: Option<u64> = None;
159    let mut min_uname: Option<u64> = None;
160    let mut server_name: &str;
161
162    let mut invalid_username = false; //PRINT "Invalid Username!"
163    let mut invalid_password = false;
164
165    let mut disabled_registration = false; //PRINT "Registration disabled!"
166
167    //FORMATTING SHIT
168    let mut first_message = true;
169    let mut extra_space: bool;
170
171    let mut channel = String::new();
172
173    //CONNECTION PROPERTIES
174    let mut id = 0usize; //ID SET BY SERVER
175    let mut username: Option<String> = None;
176
177    //LOOP READING
178    loop
179    {
180        let read = match network::receive(stream, Some(&keys))
181        {
182            Some(packet) => packet,
183            None => continue
184        };
185
186        extra_space = false; //RESET EXTRA SPACE
187
188        //EXTRA SPACE
189        if options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
190
191        //CODES
192        if let Some(code) = read.code
193        {
194            match code
195            {
196                //VERSION CHECK
197                MessageCode::Version =>
198                {
199                    let version = misc::get_version().to_string();
200                    let server_version = read.text.unwrap();
201
202                    //NON MATCHING VERSION (WILL GET DISCONNECTED)
203                    if server_version != version
204                    {
205                        tx.send(ClientEvent::Info(String::from("Incompatible version! ({version}/{server_version})"), true, 1)).unwrap();
206                    }
207
208                    //RESPOND
209                    network::send(stream, MessagePacket
210                    {
211                        text: Some(version),
212                        code: Some(MessageCode::Version),
213                        ..Default::default()
214                    }, Some(&keys));
215
216                    continue;
217                }
218
219                //WELCOME CODE - SERVER INFORMATIONS
220                MessageCode::Welcome =>
221                {
222                    //PARSE JSON
223                    let welcome_json: Value = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed"); //PARSE WELCOME JSON
224
225                    //GET INFO FROM JSON
226                    min_pass = Some(welcome_json["min_pass"].as_u64().expect("Invalid welcome json"));
227                    max_uname = Some(welcome_json["max_uname"].as_u64().expect("Invalid welcome json"));
228                    min_uname = Some(welcome_json["min_uname"].as_u64().expect("Invalid welcome json"));
229                    server_name = welcome_json["server_name"].as_str().expect("Invalid welcome json");
230
231                    tx.send(ClientEvent::Connected(server_name.to_string())).unwrap();
232                },
233
234                //REKEY - CHANGE KEYS
235                MessageCode::Rekey =>
236                {
237                    //WAIT FOR SERVER TO INIT KEY EXCHANGE
238                    key_exchange(stream, &mut keys, &tx);
239                }
240
241                //PICK_USERNAME CODE - guess what
242                MessageCode::Username =>
243                {
244                    tx.send(ClientEvent::Clear(2)).unwrap();
245
246                    //INVALID UNAME
247                    if invalid_username
248                    {
249                        tx.send(ClientEvent::Clear(2)).unwrap();
250                        tx.send(ClientEvent::Info(String::from("Username rejected!"), false, 0)).unwrap();
251                    } else //VALID
252                    {
253                        //SET INVALID USERNAME FOR POSSIBLE NEXT CODE
254                        invalid_username = true;
255                    }
256
257                    tx.send(ClientEvent::Info(format!
258                    (
259                        "\n\rEnter username ({}):",
260
261                        if disabled_registration
262                        {
263                            String::from("Registration disabled!")
264                        } else
265                        {
266                            format!("a-Z, 0-9; {}-{} characters", min_uname.unwrap(), max_uname.unwrap())
267                        }
268                    ), true, 0)).unwrap();
269                },
270
271                //REGISTER
272                MessageCode::PasswordR =>
273                {
274                    tx.send(ClientEvent::Clear(3)).unwrap();
275                    options::set_asking_password(true);
276
277                    //INVALID PASS
278                    if invalid_password
279                    {
280                        tx.send(ClientEvent::Info(format!("Password rejected! Enter at least {} characters.", min_pass.unwrap()), false, 3)).unwrap();
281                    } else
282                    {
283                        invalid_password = true;
284                    }
285
286                    tx.send(ClientEvent::Info(String::from("\n\rEnter password: (REGISTER)"), true, 0)).unwrap();
287                },
288
289                //LOGIN
290                MessageCode::PasswordL =>
291                {
292                    options::set_asking_password(true);
293                    tx.send(ClientEvent::Info(String::from("\nEnter password: (LOGIN)"), true, 3)).unwrap();
294                },
295
296                //START CHATTING
297                MessageCode::Accept =>
298                {
299                    tx.send(ClientEvent::Info(String::from("Login successful. Press Ctrl+H for help.\n"), true, 3)).unwrap();
300
301                    //SET SERVER-SIDE ID
302                    id = read.text.unwrap_or("0".to_string()).parse().unwrap();
303
304                    //ALLOW MESSAGE HISTORY & COMMANDS
305                    options::set_sending_messages(true);
306                },
307
308                //JOIN MESSAGE (CLIENT CONNECTED)
309                MessageCode::Join =>
310                {
311                    tx.send(ClientEvent::Clear(2)).unwrap();
312
313                    let user = read.text.unwrap();
314
315                    if first_message
316                    {
317                        tx.send(ClientEvent::ExtraSpace).unwrap();
318                        username = Some(user.clone());
319                        first_message = false;
320                    }
321
322                    tx.send(ClientEvent::Info(format!("[{}]: {} connected.\n", read.username.unwrap(), user), true, 0)).unwrap();
323                }
324
325                //LEAVE MESSAGE (CLIENT DISCONNECTED)
326                MessageCode::Leave =>
327                {
328                    tx.send(ClientEvent::Info(format!("[{}]: {} disconnected.\n", read.username.unwrap(), read.text.unwrap()), true, 2)).unwrap();
329                    voice_client::remove_consumer(&read.id.unwrap());
330                },
331
332                //CHANNEL CHANGE
333                MessageCode::Channel =>
334                {
335                    //REMOVE ALL STORED VOICE CLIENTS
336                    voice_client::remove_all_consumers();
337
338                    channel = if let Some(c) = read.text
339                    {
340                        format!("#{c} | ")
341                    } else
342                    {
343                        String::new()
344                    };
345
346                    tx.send(ClientEvent::Clear(1)).unwrap();
347                },
348
349                //SERVER ALLOWED VOICE
350                MessageCode::Voice =>
351                {
352                    if options::socks5_enabled()
353                    {
354                        tx.send(ClientEvent::Info(String::from("Voice chat cannot be enabled while using SOCKS5.\n"), true, 2)).unwrap();
355                        continue;
356                    }
357
358                    //TOGGLE VOICE
359                    let status = if voice_options::swap_use_voice()
360                    {
361                        let username = username.clone();
362                        let voice_tx = tx.clone();
363                        thread::spawn(move || voice_client::listen_server_voice(id, username.unwrap(), voice_tx));
364                        "en"
365                    } else
366                    {
367                        "dis"
368                    };
369
370                    //PRINT STATUS
371                    tx.send(ClientEvent::Info(format!("Voice {}abled.\n", status), true, 2)).unwrap();
372                },
373
374                //VOICE CLIENTS
375                MessageCode::VoiceClients =>
376                {
377                    //PARSE JSON
378                    let clients: Vec<(usize, String)> = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed");
379
380                    //ADD CLIENTS
381                    for (id, username) in clients
382                    {
383                        voice_client::add_consumer(id, username);
384                    }
385                }
386
387                //CLIENT JOINED VOICE CHANNEL
388                MessageCode::ChannelJoin =>
389                {
390                    let joined_id = read.id.unwrap();
391                    if voice_options::get_use_voice() && id != joined_id
392                    {
393                        voice_client::add_consumer(read.id.unwrap(), read.username.unwrap());
394                    }
395                },
396
397                //CLIENT LEFT VOICE CHANNEL
398                MessageCode::ChannelLeave =>
399                {
400                    voice_client::remove_consumer(&read.id.unwrap());
401                },
402
403                //LIST OF ONLINE USERS
404                MessageCode::List =>
405                {
406                    if !options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
407                    tx.send(ClientEvent::Info(String::from("Online clients:"), true, 2)).unwrap();
408
409                    //PARSE JSON
410                    let users_json: Value = serde_json::from_str(&read.text.unwrap()).unwrap();
411
412                    //PRINT USERS
413                    for user in users_json.as_array().unwrap()
414                    {
415                        //GET CHANNEL
416                        let c = if let Some(c) = user["channel"].as_str().map(String::from)
417                        {
418                            format!(" | #{c}")
419                        } else
420                        {
421                            String::new()
422                        };
423
424                        tx.send(ClientEvent::Info(format!("\r{} ({}){}", user["username"].as_str().unwrap(), user["id"], c), true, 0)).unwrap();
425                    }
426
427                    tx.send(ClientEvent::ExtraSpace).unwrap();
428
429                    extra_space = true;
430                    options::set_extra_space(true);
431                },
432
433                //PRIVATE MESSAGE INCOMING
434                MessageCode::PrivateMessage =>
435                {
436                    tx.send(ClientEvent::Info(format!("[PM FROM] {} ({}): {}\n", read.username.unwrap(), read.id.unwrap(), read.text.unwrap()), true, 2)).unwrap();
437                },
438
439                //PRIVATE MESSAGE INCOMING
440                MessageCode::PrivateMessageBack =>
441                {
442                    tx.send(ClientEvent::Info(format!("[PM TO] {} ({}): {}\n", read.username.unwrap(), read.id.unwrap(), read.text.unwrap()), true, 2)).unwrap();
443                },
444
445                //SPAM WARNING
446                MessageCode::SpamWarning =>
447                {
448                    tx.send(ClientEvent::Info(String::from("Slow down! You're sending messages too quickly.\n"), true, 2)).unwrap();
449                },
450
451                //REGISTRATION DISABLED
452                MessageCode::RegisterDisabled =>
453                {
454                    disabled_registration = true;
455                },
456
457                //CLIENT MESSED SOME COMMAND UP
458                MessageCode::InvalidUsage =>
459                {
460                    tx.send(ClientEvent::Info(String::from("Invalid usage! Press Ctrl+H for help.\n"), true, 2)).unwrap();
461                },
462
463                //CLIENTED REQUESTED DISABLED FEATURE
464                MessageCode::InvalidFeature =>
465                {
466                    tx.send(ClientEvent::Info(String::from("Server has disabled the feature you requested.\n"), true, 2)).unwrap();
467                },
468
469                //SERVER DOESN'T LIKE YA ANYMORE - EXIT
470                MessageCode::Disconnect =>
471                {
472                    tx.send(ClientEvent::Quit).unwrap();
473                    return;
474                },
475
476                _ => continue //EITHER INVALID CODE OR A KEY EXCHANGE CODE
477            }
478        } else //NO CODE, PRINT MESSAGE
479        {
480            tx.send(ClientEvent::Message(read)).unwrap();
481        }
482
483        //PRINT INPUT PROMPT
484        tx.send(ClientEvent::Prompt(channel.clone(), options::INPUT_READ.lock().unwrap().iter().collect::<String>())).unwrap();
485        if !extra_space { options::set_extra_space(false); } //DISABLE EXTRA SPACE
486    }
487}