pub mod file;
use std::
{
thread,
net::TcpStream,
io::{ Error, Write },
collections::HashMap,
path::PathBuf,
sync::
{
Arc,
Mutex,
LazyLock,
mpsc::Sender,
},
};
use rand::
{
TryRng,
rngs::SysRng,
};
use socks::Socks5Stream;
use zeroize::Zeroizing;
use serde_json::Value;
use semver::Version;
use crate::
{
options,
misc,
crypto::kex,
consts::{ self, Streams },
config::{ self, TofuCode },
network::
{
self,
MessageCode,
MessagePacket,
voice::
{
client as voice_client,
options as voice_options,
}
},
};
pub struct VoiceUser
{
pub id: usize, pub username: String, pub is_speaking: bool, pub latency: u128, pub is_local: bool, }
pub enum ClientEvent
{
Register, Login, Authenticated, Connected(String), Message(MessagePacket), Prompt(String), PrivateMessageSent(String, usize, String), PrivateMessageRecv(String, usize, String), TofuError(TofuCode), VoiceActivity(Vec<VoiceUser>), Join(String), Leave(String), Clear(usize), InvalidUsage, VersionFailed, VersionMismatch(String, String), UnsafeVersion(usize, Version, String), Username(bool, u64, u64), VoiceEnabled, VoiceDisabled, List(Value), Upload(String), Uploaded(String, String), Download(String), Downloaded(String), DownloadFailed(String), Files(Vec<Value>), UploadLimit, ExtraSpace, IncompatibleVersion(String, String), UsernameRejected, PasswordRejected(u64), SpamWarning, Socks5Voice, DisabledFeature, Quit, }
pub static ACTIVE_UPLOADS: LazyLock<Arc<Mutex<HashMap<[u8; 32], PathBuf>>>> = LazyLock::new(|| {
Arc::new(Mutex::new(HashMap::new()))
});
fn key_exchange
(
streams: &mut Streams,
keys: &mut consts::SharedKeys,
tx: &Sender<ClientEvent>,
exchange_keys: Option<&consts::SharedKeys>,
) -> bool {
let message = loop
{
let received = network::receive(streams, exchange_keys, None).unwrap();
if received.code == Some(MessageCode::KeyExchange) { break received; }
};
let message_text = message.text.as_ref().unwrap();
let server_keys: Value = serde_json::from_str(message_text).expect("Failed to parse server keys JSON");
let server_ecc_pk = server_keys["ecc"].as_str().expect("Parsing server ECC key failed");
let server_pq_pk = server_keys["pq"].as_str().expect("Parsing server PQ key failed");
match config::server_keys_check(&streams.0.peer_addr().unwrap().ip().to_string(), message_text)
{
TofuCode::Valid => {},
status @ (TofuCode::Mismatch | TofuCode::Unknown(_, _)) =>
{
network::send(&mut streams.1.lock().unwrap(), MessagePacket
{
code: Some(MessageCode::Disconnect),
..Default::default()
}, None);
tx.send(ClientEvent::TofuError(status)).unwrap();
return false;
},
}
let (sk, pk) = kex::generate_ephemeral_keys();
let (pq_ciphertext, pq_secret) = kex::encapsulate_pq(server_pq_pk);
let response_text = serde_json::json!
({
"ecc": pk,
"pq": pq_ciphertext,
}).to_string();
network::send(&mut streams.1.lock().unwrap(), MessagePacket
{
text: Some(response_text),
code: Some(MessageCode::KeyExchange),
..Default::default()
}, exchange_keys);
*keys = kex::derive_shared_secret(sk, server_ecc_pk.to_string(), pq_secret).expect("Shared secret derivation failed");
options::set_keys(keys.clone());
true
}
pub fn connect(connecting_addr: String) -> Result<TcpStream, Error> {
if !options::socks5_enabled() {
TcpStream::connect(connecting_addr)
} else {
Socks5Stream::connect(config::read_config::<String>("socks5_addr"), connecting_addr.as_str())
.map(|s| s.into_inner())
}.and_then(|s|
{
s.set_nodelay(true)?;
Ok(s)
})
}
pub fn listen_server(streams: &mut Streams, tx: Sender<ClientEvent>) {
let mut header = [0u8; 32];
SysRng.try_fill_bytes(&mut header).unwrap(); options::set_obfuscation_key(&header);
streams.1.lock().unwrap().write_all(&header).unwrap();
let mut keys = (Zeroizing::new(vec![]), Zeroizing::new(vec![]));
if !key_exchange(streams, &mut keys, &tx, None) { return; }
let mut min_pass: Option<u64> = None;
let mut max_uname: Option<u64> = None;
let mut min_uname: Option<u64> = None;
let mut invalid_username = false; let mut invalid_password = false;
let mut disabled_registration = false;
let mut first_message = true;
let mut extra_space: bool;
let mut id = 0usize; let mut username: Option<String> = None;
loop
{
let read = match network::receive(streams, Some(&keys), None)
{
Some(packet) => packet,
None => MessagePacket
{
code: Some(MessageCode::Disconnect),
..Default::default()
}
};
if read.id.is_some() && options::is_muted(read.id) { continue; }
if read.code == Some(MessageCode::KeepAlive)
{
network::send(&mut streams.1.lock().unwrap(), MessagePacket
{
code: Some(MessageCode::KeepAlive),
..Default::default()
}, options::get_keys().as_ref());
continue;
}
extra_space = false;
if options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
if let Some(code) = read.code
{
match code
{
MessageCode::Version =>
{
let version = misc::get_version().to_string();
let server_version = read.text.unwrap();
if server_version != version
{
tx.send(ClientEvent::IncompatibleVersion(version.clone(), server_version)).unwrap();
}
network::send(&mut streams.1.lock().unwrap(), MessagePacket
{
text: Some(version),
code: Some(MessageCode::Version),
..Default::default()
}, Some(&keys));
continue;
}
MessageCode::Welcome =>
{
let welcome_json: Value = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed");
min_pass = Some(welcome_json["min_pass"].as_u64().expect("Invalid welcome json"));
max_uname = Some(welcome_json["max_uname"].as_u64().expect("Invalid welcome json"));
min_uname = Some(welcome_json["min_uname"].as_u64().expect("Invalid welcome json"));
options::set_server_username(welcome_json["server_uname"].as_str().expect("Invalid welcome json"));
let client_hash = env!("WHY2_GIT_HASH");
let server_hash = welcome_json["git_hash"].as_str().expect("Invalid welcome json");
if !client_hash.is_empty() && !server_hash.is_empty() && client_hash != server_hash
{
tx.send(ClientEvent::VersionMismatch(client_hash.to_string(), server_hash.to_string())).unwrap();
}
tx.send(ClientEvent::Connected(welcome_json["server_name"].as_str().expect("Invalid welcome json").to_string())).unwrap();
},
MessageCode::Rekey =>
{
let current_keys = keys.clone();
key_exchange(streams, &mut keys, &tx, Some(¤t_keys));
}
MessageCode::Username =>
{
tx.send(ClientEvent::Clear(2)).unwrap();
if invalid_username
{
tx.send(ClientEvent::UsernameRejected).unwrap();
} else {
invalid_username = true;
}
tx.send(ClientEvent::Username(disabled_registration, min_uname.unwrap(), max_uname.unwrap())).unwrap();
},
MessageCode::PasswordR =>
{
tx.send(ClientEvent::Clear(3)).unwrap();
options::set_asking_password(true);
if invalid_password
{
tx.send(ClientEvent::PasswordRejected(min_pass.unwrap())).unwrap();
} else
{
invalid_password = true;
}
tx.send(ClientEvent::Register).unwrap();
},
MessageCode::PasswordL =>
{
options::set_asking_password(true);
tx.send(ClientEvent::Login).unwrap();
},
MessageCode::Accept =>
{
tx.send(ClientEvent::Authenticated).unwrap();
id = read.text.unwrap_or_else(|| "0".to_string()).parse().unwrap();
options::set_sending_messages(true);
},
MessageCode::Join =>
{
tx.send(ClientEvent::Clear(2)).unwrap();
let user = read.text.unwrap();
if first_message
{
tx.send(ClientEvent::ExtraSpace).unwrap();
username = Some(user.clone());
first_message = false;
}
tx.send(ClientEvent::Join(user)).unwrap();
}
MessageCode::Leave =>
{
tx.send(ClientEvent::Leave(read.text.unwrap())).unwrap();
voice_client::remove_consumer(&read.id.unwrap());
},
MessageCode::Channel =>
{
voice_client::remove_all_consumers();
options::set_channel(read.text.unwrap_or_else(|| String::new()));
tx.send(ClientEvent::Clear(1)).unwrap();
},
MessageCode::Voice =>
{
if options::socks5_enabled()
{
tx.send(ClientEvent::Socks5Voice).unwrap();
continue;
}
tx.send(if voice_options::swap_use_voice()
{
let username = username.clone();
let voice_tx = tx.clone();
let stream = streams.1.clone();
thread::spawn(move || voice_client::listen_server_voice(id, username.unwrap(), voice_tx, stream));
ClientEvent::VoiceEnabled
} else
{
ClientEvent::VoiceDisabled
}).unwrap();
},
MessageCode::VoiceClients =>
{
let clients: Vec<(usize, String)> = serde_json::from_str(&read.text.unwrap()).expect("Parsing welcome json failed");
for (id, username) in clients
{
voice_client::add_consumer(id, username);
}
}
MessageCode::ChannelJoin =>
{
let joined_id = read.id.unwrap();
if voice_options::get_use_voice() && id != joined_id
{
voice_client::add_consumer(read.id.unwrap(), read.username.unwrap());
}
},
MessageCode::ChannelLeave =>
{
voice_client::remove_consumer(&read.id.unwrap());
},
MessageCode::List =>
{
if !options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
tx.send(ClientEvent::List(serde_json::from_str(&read.text.unwrap()).unwrap())).unwrap();
extra_space = true;
options::set_extra_space(true);
},
MessageCode::Upload =>
{
let file_tx = tx.clone(); thread::spawn(move || file::upload(read.file.unwrap(), file_tx));
continue;
},
MessageCode::Download =>
{
let file_tx = tx.clone(); thread::spawn(move || file::download(read.file.unwrap(), file_tx));
continue;
},
MessageCode::Uploaded =>
{
tx.send(ClientEvent::Uploaded(read.username.unwrap(), read.text.unwrap())).unwrap();
},
MessageCode::Files =>
{
let uploads_json: Vec<Value> = serde_json::from_str(&read.text.unwrap()).unwrap();
if !uploads_json.is_empty()
{
if !options::get_extra_space() { tx.send(ClientEvent::ExtraSpace).unwrap(); }
extra_space = true;
options::set_extra_space(true);
}
tx.send(ClientEvent::Files(uploads_json)).unwrap();
},
MessageCode::UploadLimit =>
{
tx.send(ClientEvent::UploadLimit).unwrap();
},
MessageCode::PrivateMessage =>
{
tx.send(ClientEvent::PrivateMessageRecv(read.username.unwrap(), read.id.unwrap(), read.text.unwrap())).unwrap();
},
MessageCode::PrivateMessageBack =>
{
tx.send(ClientEvent::PrivateMessageSent(read.username.unwrap(), read.id.unwrap(), read.text.unwrap())).unwrap();
},
MessageCode::SpamWarning =>
{
tx.send(ClientEvent::SpamWarning).unwrap();
},
MessageCode::RegisterDisabled =>
{
disabled_registration = true;
},
MessageCode::InvalidUsage =>
{
tx.send(ClientEvent::InvalidUsage).unwrap();
},
MessageCode::InvalidFeature =>
{
tx.send(ClientEvent::DisabledFeature).unwrap();
},
MessageCode::Disconnect =>
{
tx.send(ClientEvent::Quit).unwrap();
return;
},
_ => continue }
} else {
tx.send(ClientEvent::Message(read)).unwrap();
}
tx.send(ClientEvent::Prompt(options::INPUT_READ.lock().unwrap().iter().collect::<String>())).unwrap();
if !extra_space { options::set_extra_space(false); } }
}