Skip to main content

why2_chat/network/client/
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
19//MODULES
20pub mod file;
21
22use std::
23{
24    thread,
25    net::TcpStream,
26    io::{ Error, Write },
27    collections::HashMap,
28    path::PathBuf,
29    sync::
30    {
31        Arc,
32        Mutex,
33        LazyLock,
34        mpsc::Sender,
35    },
36};
37
38use rand::
39{
40    TryRng,
41    rngs::SysRng,
42};
43
44use socks::Socks5Stream;
45
46use zeroize::Zeroizing;
47
48use serde_json::Value;
49
50use semver::Version;
51
52use crate::
53{
54    options,
55    misc,
56    crypto::kex,
57    consts::{ self, Streams },
58    config::{ self, TofuCode },
59    network::
60    {
61        self,
62        MessageCode,
63        MessagePacket,
64        voice::
65        {
66            client as voice_client,
67            options as voice_options,
68        }
69    },
70};
71
72//STRUCTS
73pub struct VoiceUser
74{
75    pub id: usize,          //ID OF USER
76    pub username: String,   //USERNAME TO DISPLAY
77    pub is_speaking: bool,  //TAKE A WILD GUESS
78    pub latency: u128,      //USER'S PING
79    pub is_local: bool,     //AM I THE USER?
80}
81
82//ENUMS
83pub enum ClientEvent
84{
85    Register,                                  //REGISTER PROMPT
86    Login,                                     //LOGIN PROMPT
87    Authenticated,                             //LOGIN SUCCESSFUL
88    Connected(String),                         //SUCCESSFUL CONNECTION MESSAGE
89    Message(MessagePacket),                    //RECEIVED MESSAGE
90    Prompt(String),                            //">>>" PROMPT, WITH WRITTEN MESSAGE
91    PrivateMessageSent(String, usize, String), //SENT PM
92    PrivateMessageRecv(String, usize, String), //RECEIVED PM
93    TofuError(TofuCode),                       //TOFU VERIFICATION FAILED
94    VoiceActivity(Vec<VoiceUser>),             //VOICE OVERLAY
95    Join(String),                              //CLIENT CONNECTED
96    Leave(String),                             //CLIENT DISCONNECTED
97    Clear(usize),                              //CLEAR n LINES
98    InvalidUsage,                              //INVALID COMMAND USAGE
99    VersionFailed,                             //FETCHING VERSIONS FAILED
100    VersionMismatch(String, String),           //MISMATCH GIT HASH
101    UnsafeVersion(usize, Version, String),     //OLD VERSION
102    Username(bool, u64, u64),                  //USERNAME PROMPT
103    VoiceEnabled,                              //VOICE CHAT ENABLED
104    VoiceDisabled,                             //VOICE CHAT DISABLED
105    List(Value),                               //LIST OF USERS
106    Upload(String),                            //UPLOADING FILE
107    Uploaded(String, String),                  //USER UPLOADED FILE
108    Download(String),                          //DOWNLOADING FILE
109    Downloaded(String),                        //DOWNLOADED FILE
110    DownloadFailed(String),                    //DOWNLOADING FAILED
111    Files(Vec<Value>),                         //FILE LIST
112    UploadLimit,                               //MAX CONCURRENT UPLOADS REACHED
113    ExtraSpace,                                //JUST RANDOM NEWLINE
114    IncompatibleVersion(String, String),       //INCOMPATIBLE SERVER VERSION
115    UsernameRejected,                          //USERNAME REJECTED BY SERVER
116    PasswordRejected(u64),                     //PASSWORD REJECTED BY SERVER
117    SpamWarning,                               //SPAM WARNING
118    Socks5Voice,                               //DISABLED VOICE ON SOCKS5
119    DisabledFeature,                           //DISABLED FEATURE
120    Quit,                                      //SERVER QUIT COMMUNICATION
121}
122
123//LISTS
124pub static ACTIVE_UPLOADS: LazyLock<Arc<Mutex<HashMap<[u8; 32], PathBuf>>>> = LazyLock::new(|| //ACTIVE UPLOADS
125{
126    Arc::new(Mutex::new(HashMap::new()))
127});
128
129//FUNCTIONS
130//PRIVATE
131fn key_exchange
132(
133    streams: &mut Streams,
134    keys: &mut consts::SharedKeys,
135    tx: &Sender<ClientEvent>,
136    exchange_keys: Option<&consts::SharedKeys>,
137) -> bool //KEY EXCHANGE FOR CLIENT-SIDE
138{
139    //WAIT FOR KeyExchange
140    let message = loop
141    {
142        //READ MESSAGE
143        let received = network::receive(streams, exchange_keys, None).unwrap();
144
145        if received.code == Some(MessageCode::KeyExchange) { break received; }
146    };
147
148    let message_text = message.text.as_ref().unwrap();
149
150    //PARSE SERVER KEYS JSON
151    let server_keys: Value = serde_json::from_str(message_text).expect("Failed to parse server keys JSON");
152    let server_ecc_pk = server_keys["ecc"].as_str().expect("Parsing server ECC key failed");
153    let server_pq_pk = server_keys["pq"].as_str().expect("Parsing server PQ key failed");
154
155    //VERIFY PUBKEY VALIDITY (TOFU)
156    match config::server_keys_check(&streams.0.peer_addr().unwrap().ip().to_string(), message_text)
157    {
158        TofuCode::Valid => {},
159
160        status @ (TofuCode::Mismatch | TofuCode::Unknown(_, _)) =>
161        {
162            //GRACEFULLY DISCONNECT FROM SERVER
163            network::send(&mut streams.1.lock().unwrap(), MessagePacket
164            {
165                code: Some(MessageCode::Disconnect),
166                ..Default::default()
167            }, None);
168
169            //PRINT SECURITY MESSAGE
170            tx.send(ClientEvent::TofuError(status)).unwrap();
171
172            //EXIT
173            return false;
174        },
175    }
176
177    //GENERATE EPHEMERAL ECC KEYS
178    let (sk, pk) = kex::generate_ephemeral_keys();
179
180    //ENCAPSULATE PQ
181    let (pq_ciphertext, pq_secret) = kex::encapsulate_pq(server_pq_pk);
182
183    //PREPARE RESPONSE JSON
184    let response_text = serde_json::json!
185    ({
186        "ecc": pk,
187        "pq": pq_ciphertext,
188    }).to_string();
189
190    //SEND ECC PUBKEY TO SERVER
191    network::send(&mut streams.1.lock().unwrap(), MessagePacket
192    {
193        text: Some(response_text),
194        code: Some(MessageCode::KeyExchange),
195        ..Default::default()
196    }, exchange_keys);
197
198    //CALCULATE SHARED SECRET (HYBRID)
199    *keys = kex::derive_shared_secret(sk, server_ecc_pk.to_string(), pq_secret).expect("Shared secret derivation failed");
200
201    //SET GLOBAL KEYS VARIABLE
202    options::set_keys(keys.clone());
203
204    true
205}
206
207//PUBLIC
208pub fn connect(connecting_addr: String) -> Result<TcpStream, Error> //CONNECT TO SERVER
209{
210    if !options::socks5_enabled() //NO SOCKS5
211    {
212        TcpStream::connect(connecting_addr)
213    } else //USE PROXY
214    {
215        Socks5Stream::connect(config::read_config::<String>("socks5_addr"), connecting_addr.as_str())
216            .map(|s| s.into_inner())
217    }.and_then(|s|
218    {
219        //SET TCP_NODELAY
220        s.set_nodelay(true)?;
221        Ok(s)
222    })
223}
224
225pub fn listen_server(streams: &mut Streams, tx: Sender<ClientEvent>) //SERVER -> CLIENT COMMUNICATION
226{
227    //SEND HEADER
228    let mut header = [0u8; 32];
229    SysRng.try_fill_bytes(&mut header).unwrap(); //GENERATE RANDOM HEADER
230    options::set_obfuscation_key(&header);
231    streams.1.lock().unwrap().write_all(&header).unwrap();
232
233    //SET GLOBAL CLIENT ENCRYPTION & MAC KEY
234    let mut keys = (Zeroizing::new(vec![]), Zeroizing::new(vec![]));
235    if !key_exchange(streams, &mut keys, &tx, None) { return; }
236
237    //SERVER INFO VARIABLES
238    let mut min_pass: Option<u64> = None;
239    let mut max_uname: Option<u64> = None;
240    let mut min_uname: Option<u64> = None;
241
242    let mut invalid_username = false; //PRINT "Invalid Username!"
243    let mut invalid_password = false;
244
245    let mut disabled_registration = false; //PRINT "Registration disabled!"
246
247    //FORMATTING SHIT
248    let mut first_message = true;
249    let mut extra_space: bool;
250
251    //CONNECTION PROPERTIES
252    let mut id = 0usize; //ID SET BY SERVER
253    let mut username: Option<String> = None;
254
255    //LOOP READING
256    loop
257    {
258        let read = match network::receive(streams, Some(&keys), None)
259        {
260            Some(packet) => packet,
261            None => MessagePacket
262            {
263                code: Some(MessageCode::Disconnect),
264                ..Default::default()
265            }
266        };
267
268        //CHECK FOR MUTED CLIENT
269        if read.id.is_some() && options::is_muted(read.id) { continue; }
270
271        //KEEPALIVE
272        if read.code == Some(MessageCode::KeepAlive)
273        {
274            //ECHO
275            network::send(&mut streams.1.lock().unwrap(), MessagePacket
276            {
277                code: Some(MessageCode::KeepAlive),
278                ..Default::default()
279            }, options::get_keys().as_ref());
280            continue;
281        }
282
283        extra_space = false; //RESET EXTRA SPACE
284
285        //EXTRA SPACE
286        if options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
287
288        //CODES
289        if let Some(code) = read.code
290        {
291            match code
292            {
293                //VERSION CHECK
294                MessageCode::Version =>
295                {
296                    let version = misc::get_version().to_string();
297                    let server_version = read.text.unwrap();
298
299                    //NON MATCHING VERSION (WILL GET DISCONNECTED)
300                    if server_version != version
301                    {
302                        tx.send(ClientEvent::IncompatibleVersion(version.clone(), server_version)).unwrap();
303                    }
304
305                    //RESPOND
306                    network::send(&mut streams.1.lock().unwrap(), MessagePacket
307                    {
308                        text: Some(version),
309                        code: Some(MessageCode::Version),
310                        ..Default::default()
311                    }, Some(&keys));
312
313                    continue;
314                }
315
316                //WELCOME CODE - SERVER INFORMATIONS
317                MessageCode::Welcome =>
318                {
319                    //PARSE JSON
320                    let welcome_json: Value = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed"); //PARSE WELCOME JSON
321
322                    //GET INFO FROM JSON
323                    min_pass = Some(welcome_json["min_pass"].as_u64().expect("Invalid welcome json"));
324                    max_uname = Some(welcome_json["max_uname"].as_u64().expect("Invalid welcome json"));
325                    min_uname = Some(welcome_json["min_uname"].as_u64().expect("Invalid welcome json"));
326                    options::set_server_username(welcome_json["server_uname"].as_str().expect("Invalid welcome json"));
327
328                    //COMPARSE HASHES
329                    let client_hash = env!("WHY2_GIT_HASH");
330                    let server_hash = welcome_json["git_hash"].as_str().expect("Invalid welcome json");
331                    if !client_hash.is_empty() && !server_hash.is_empty() && client_hash != server_hash
332                    {
333                        //DISPLAY VERSION MISMATCH
334                        tx.send(ClientEvent::VersionMismatch(client_hash.to_string(), server_hash.to_string())).unwrap();
335                    }
336
337                    tx.send(ClientEvent::Connected(welcome_json["server_name"].as_str().expect("Invalid welcome json").to_string())).unwrap();
338                },
339
340                //REKEY - CHANGE KEYS
341                MessageCode::Rekey =>
342                {
343                    //WAIT FOR SERVER TO INIT KEY EXCHANGE
344                    let current_keys = keys.clone();
345                    key_exchange(streams, &mut keys, &tx, Some(&current_keys));
346                }
347
348                //PICK_USERNAME CODE - guess what
349                MessageCode::Username =>
350                {
351                    tx.send(ClientEvent::Clear(2)).unwrap();
352
353                    //INVALID UNAME
354                    if invalid_username
355                    {
356                        tx.send(ClientEvent::UsernameRejected).unwrap();
357                    } else //VALID
358                    {
359                        //SET INVALID USERNAME FOR POSSIBLE NEXT CODE
360                        invalid_username = true;
361                    }
362
363                    tx.send(ClientEvent::Username(disabled_registration, min_uname.unwrap(), max_uname.unwrap())).unwrap();
364                },
365
366                //REGISTER
367                MessageCode::PasswordR =>
368                {
369                    tx.send(ClientEvent::Clear(3)).unwrap();
370                    options::set_asking_password(true);
371
372                    //INVALID PASS
373                    if invalid_password
374                    {
375                        tx.send(ClientEvent::PasswordRejected(min_pass.unwrap())).unwrap();
376                    } else
377                    {
378                        invalid_password = true;
379                    }
380
381                    tx.send(ClientEvent::Register).unwrap();
382                },
383
384                //LOGIN
385                MessageCode::PasswordL =>
386                {
387                    options::set_asking_password(true);
388                    tx.send(ClientEvent::Login).unwrap();
389                },
390
391                //START CHATTING
392                MessageCode::Accept =>
393                {
394                    tx.send(ClientEvent::Authenticated).unwrap();
395
396                    //SET SERVER-SIDE ID
397                    id = read.text.unwrap_or_else(|| "0".to_string()).parse().unwrap();
398
399                    //ALLOW MESSAGE HISTORY & COMMANDS
400                    options::set_sending_messages(true);
401                },
402
403                //JOIN MESSAGE (CLIENT CONNECTED)
404                MessageCode::Join =>
405                {
406                    tx.send(ClientEvent::Clear(2)).unwrap();
407
408                    let user = read.text.unwrap();
409
410                    if first_message
411                    {
412                        tx.send(ClientEvent::ExtraSpace).unwrap();
413                        username = Some(user.clone());
414                        first_message = false;
415                    }
416
417                    tx.send(ClientEvent::Join(user)).unwrap();
418                }
419
420                //LEAVE MESSAGE (CLIENT DISCONNECTED)
421                MessageCode::Leave =>
422                {
423                    tx.send(ClientEvent::Leave(read.text.unwrap())).unwrap();
424                    voice_client::remove_consumer(&read.id.unwrap());
425                },
426
427                //CHANNEL CHANGE
428                MessageCode::Channel =>
429                {
430                    //REMOVE ALL STORED VOICE CLIENTS
431                    voice_client::remove_all_consumers();
432
433                    options::set_channel(read.text.unwrap_or_else(|| String::new()));
434
435                    tx.send(ClientEvent::Clear(1)).unwrap();
436                },
437
438                //SERVER ALLOWED VOICE
439                MessageCode::Voice =>
440                {
441                    if options::socks5_enabled()
442                    {
443                        tx.send(ClientEvent::Socks5Voice).unwrap();
444                        continue;
445                    }
446
447                    //TOGGLE VOICE (& PRINT STATUS)
448                    tx.send(if voice_options::swap_use_voice()
449                    {
450                        let username = username.clone();
451                        let voice_tx = tx.clone();
452                        let stream = streams.1.clone();
453                        thread::spawn(move || voice_client::listen_server_voice(id, username.unwrap(), voice_tx, stream));
454                        ClientEvent::VoiceEnabled
455                    } else
456                    {
457                        ClientEvent::VoiceDisabled
458                    }).unwrap();
459                },
460
461                //VOICE CLIENTS
462                MessageCode::VoiceClients =>
463                {
464                    //PARSE JSON
465                    let clients: Vec<(usize, String)> = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed");
466
467                    //ADD CLIENTS
468                    for (id, username) in clients
469                    {
470                        voice_client::add_consumer(id, username);
471                    }
472                }
473
474                //CLIENT JOINED VOICE CHANNEL
475                MessageCode::ChannelJoin =>
476                {
477                    let joined_id = read.id.unwrap();
478                    if voice_options::get_use_voice() && id != joined_id
479                    {
480                        voice_client::add_consumer(read.id.unwrap(), read.username.unwrap());
481                    }
482                },
483
484                //CLIENT LEFT VOICE CHANNEL
485                MessageCode::ChannelLeave =>
486                {
487                    voice_client::remove_consumer(&read.id.unwrap());
488                },
489
490                //LIST OF ONLINE USERS
491                MessageCode::List =>
492                {
493                    if !options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
494                    tx.send(ClientEvent::List(serde_json::from_str(&read.text.unwrap()).unwrap())).unwrap();
495                    extra_space = true;
496                    options::set_extra_space(true);
497                },
498
499                //UPLOAD APPROVAL
500                MessageCode::Upload =>
501                {
502                    //SPAWN UPLOAD THREAD
503                    let file_tx = tx.clone(); //CLONE TX
504                    thread::spawn(move || file::upload(read.file.unwrap(), file_tx));
505                    continue;
506                },
507
508                //DOWNLOAD
509                MessageCode::Download =>
510                {
511                    //SPAWN DOWNLOAD THREAD
512                    let file_tx = tx.clone(); //CLONE TX
513                    thread::spawn(move || file::download(read.file.unwrap(), file_tx));
514                    continue;
515                },
516
517                //UPLOADED ANNOUNCEMENT
518                MessageCode::Uploaded =>
519                {
520                    tx.send(ClientEvent::Uploaded(read.username.unwrap(), read.text.unwrap())).unwrap();
521                },
522
523                //FILE LIST
524                MessageCode::Files =>
525                {
526                    //PARSE JSON
527                    let uploads_json: Vec<Value> = serde_json::from_str(&read.text.unwrap()).unwrap();
528
529                    if !uploads_json.is_empty()
530                    {
531                        if !options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
532                        extra_space = true;
533                        options::set_extra_space(true);
534                    }
535
536                    tx.send(ClientEvent::Files(uploads_json)).unwrap();
537                },
538
539                //MAX PARALLEL UPLOADS
540                MessageCode::UploadLimit =>
541                {
542                    tx.send(ClientEvent::UploadLimit).unwrap();
543                },
544
545                //PRIVATE MESSAGE INCOMING
546                MessageCode::PrivateMessage =>
547                {
548                    tx.send(ClientEvent::PrivateMessageRecv(read.username.unwrap(), read.id.unwrap(), read.text.unwrap())).unwrap();
549                },
550
551                //PRIVATE MESSAGE INCOMING
552                MessageCode::PrivateMessageBack =>
553                {
554                    tx.send(ClientEvent::PrivateMessageSent(read.username.unwrap(), read.id.unwrap(), read.text.unwrap())).unwrap();
555                },
556
557                //SPAM WARNING
558                MessageCode::SpamWarning =>
559                {
560                    tx.send(ClientEvent::SpamWarning).unwrap();
561                },
562
563                //REGISTRATION DISABLED
564                MessageCode::RegisterDisabled =>
565                {
566                    disabled_registration = true;
567                },
568
569                //CLIENT MESSED SOME COMMAND UP
570                MessageCode::InvalidUsage =>
571                {
572                    tx.send(ClientEvent::InvalidUsage).unwrap();
573                },
574
575                //CLIENTED REQUESTED DISABLED FEATURE
576                MessageCode::InvalidFeature =>
577                {
578                    tx.send(ClientEvent::DisabledFeature).unwrap();
579                },
580
581                //SERVER DOESN'T LIKE YA ANYMORE - EXIT
582                MessageCode::Disconnect =>
583                {
584                    tx.send(ClientEvent::Quit).unwrap();
585                    return;
586                },
587
588                _ => continue //EITHER INVALID CODE OR A KEY EXCHANGE CODE
589            }
590        } else //NO CODE, PRINT MESSAGE
591        {
592            tx.send(ClientEvent::Message(read)).unwrap();
593        }
594
595        //PRINT INPUT PROMPT
596        tx.send(ClientEvent::Prompt(options::INPUT_READ.lock().unwrap().iter().collect::<String>())).unwrap();
597        if !extra_space { options::set_extra_space(false); } //DISABLE EXTRA SPACE
598    }
599}