Skip to main content

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