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