#![cfg(feature = "server")]
use std::
{
process,
sync::Arc,
time::Duration,
};
use tokio::
{
io::Result,
time,
signal,
sync::Mutex,
io::AsyncReadExt,
net::{ TcpListener, UdpSocket },
};
use log::LevelFilter;
use simple_logger::SimpleLogger;
use why2_chat::
{
misc,
config,
options,
crypto::kex,
network::
{
file::server as file,
screen::{ self, server as screen_server },
voice::server as voice_server,
server::
{
self,
connection::ConnectionType,
handshake::HandshakeSlot,
},
},
};
const BIND_ATTEMPTS: usize = 15; const BIND_RETRY_DELAY: u64 = 200;
async fn bind<T>(what: &str, address: &str, bind: impl AsyncFn() -> Result<T>) -> T
{
let mut last = None;
for attempt in 0..BIND_ATTEMPTS
{
match bind().await
{
Ok(bound) => return bound,
Err(error) =>
{
last = Some(error);
if attempt + 1 < BIND_ATTEMPTS { time::sleep(Duration::from_millis(BIND_RETRY_DELAY)).await; }
}
}
}
log::error!("Binding {what} on {address} failed: {}", last.unwrap());
process::exit(1);
}
async fn quit() {
log::info!("Exiting...");
server::disconnect_all().await; }
#[tokio::main]
async fn main()
{
SimpleLogger::new()
.with_level(LevelFilter::Info)
.with_module_level("ureq", LevelFilter::Warn) .with_module_level("rustls", LevelFilter::Warn) .init()
.unwrap();
misc::check_version().await; config::init_config(); kex::generate_server_keys();
if config::read_config("enable_voice_chat")
{
options::enable_voice_chat();
}
options::set_server_username(&config::read_config::<String>("server_username"));
let address = format!("{}:{}", config::read_config::<String>("server_ip"), config::read_config::<u16>("server_port"));
let listener = bind("TCP", &address, async || TcpListener::bind(&address).await).await;
let udp_socket = match options::voice_chat_enabled()
{
true => Some(bind("UDP", &address, async || UdpSocket::bind(&address).await).await), false => None, };
log::info!("Listening on {address}");
tokio::spawn(async move
{
let mut n = 0;
loop
{
time::sleep(Duration::from_secs(5)).await;
server::disconnect_inactive().await;
server::PENDING_TOKENS.retain(|_, (_, _, created)| created.elapsed().as_secs() < 5);
n = (n + 1) % 6;
if n == 0
{
server::send_keepalive().await;
}
}
});
tokio::spawn(async
{
signal::ctrl_c().await.expect("Setting Ctrl+C handler failed");
quit().await;
process::exit(0);
});
if options::voice_chat_enabled()
{
tokio::spawn(voice_server::listen_client_voice(udp_socket.unwrap()));
}
loop
{
match listener.accept().await
{
Ok((mut stream, peer_addr)) =>
{
if config::bans::banned_ip(&peer_addr.ip())
{
log::error!("Connection rejected (ip banned): {peer_addr}");
continue;
}
let slot = match HandshakeSlot::reserve(peer_addr.ip())
{
Some(s) => s,
None =>
{
log::error!("Connection rejected (handshake limit): {peer_addr}");
continue;
}
};
tokio::spawn(async move
{
let _slot = slot;
let mut token = [0u8; 32];
if let Ok(Ok(_)) = time::timeout(Duration::from_millis(2000), stream.read_exact(&mut token)).await
{
match stream.set_nodelay(true)
{
Ok(_) => {},
Err(_) => return
}
if let Some((_, (id, conn_type, _))) = server::PENDING_TOKENS.remove(&token)
{
match conn_type
{
ConnectionType::FileUpload { uid } =>
{
server::spawn_with_abort(move |task| async move
{
let (mut read_stream, write_stream) = stream.into_split();
file::download(token, id, &mut (&mut read_stream, Arc::new(Mutex::new(write_stream))), uid, task).await;
});
return;
},
ConnectionType::FileDownload { uid, file: file_data } =>
{
server::spawn_with_abort(move |task| async move
{
let (_read_stream, write_stream) = stream.into_split();
file::upload(token, id, write_stream, file_data, uid, task).await;
});
return;
},
ConnectionType::Screen =>
{
screen::cap_socket_buffers(&stream);
server::spawn_with_abort(move |task| async move
{
let (mut read_stream, write_stream) = stream.into_split();
screen_server::screen(token, id, &mut (&mut read_stream, Arc::new(Mutex::new(write_stream))), task).await;
});
return;
},
ConnectionType::Attach { id: sharer_id } =>
{
screen::cap_socket_buffers(&stream);
let (_read_stream, write_stream) = stream.into_split();
if let Some(mut conn) = server::CONNECTIONS.iter_mut().find(|c| c.id() == Some(&id))
{
conn.attach_screen(sharer_id, Arc::new(Mutex::new(write_stream)), token);
}
return;
},
}
} else
{
let auth_clients = server::CONNECTIONS.iter().filter(|c| c.is_authenticated()).count();
let unauth_clients = server::CONNECTIONS.len() - auth_clients;
let ip_clients = server::CONNECTIONS.iter().filter(|c| c.peer_addr().ip() == peer_addr.ip()).count();
if auth_clients >= config::read_config::<usize>("max_clients") ||
unauth_clients >= config::read_config::<usize>("max_unauth_clients") ||
ip_clients >= config::read_config::<usize>("max_ip_clients")
{
log::error!("Connection rejected (limit): {peer_addr}");
return;
}
server::spawn_with_abort(move |task| async move
{
let (mut read_stream, write_stream) = stream.into_split();
server::listen_client(&mut (&mut read_stream, Arc::new(Mutex::new(write_stream))), peer_addr, token, task).await;
});
return;
}
}
log::error!("Connection rejected (header): {peer_addr}");
});
},
Err(e) =>
{
log::error!("Connection failed: {}", e);
}
}
}
}