1use std::io;
4use std::net::SocketAddr;
5use std::time::Duration;
6
7use anyhow::{bail, ensure, Context};
8use base64::prelude::*;
9use hmac::digest::Update;
10use hmac::{Hmac, Mac};
11use num_bigint::BigInt;
12use reqwest::StatusCode;
13use rsa::Pkcs1v15Encrypt;
14use serde::Deserialize;
15use serde_json::{json, Value};
16use sha1::Sha1;
17use sha2::{Digest, Sha256};
18use tokio::net::{TcpListener, TcpStream};
19use tracing::{error, info, trace, warn};
20use uuid::Uuid;
21use valence_lang::keys;
22use valence_protocol::profile::Property;
23use valence_protocol::Decode;
24use valence_server::client::Properties;
25use valence_server::protocol::packets::handshaking::handshake_c2s::HandshakeNextState;
26use valence_server::protocol::packets::handshaking::HandshakeC2s;
27use valence_server::protocol::packets::login::{
28 LoginCompressionS2c, LoginDisconnectS2c, LoginHelloC2s, LoginHelloS2c, LoginKeyC2s,
29 LoginQueryRequestS2c, LoginQueryResponseC2s, LoginSuccessS2c,
30};
31use valence_server::protocol::packets::status::{
32 QueryPingC2s, QueryPongS2c, QueryRequestC2s, QueryResponseS2c,
33};
34use valence_server::protocol::{PacketDecoder, PacketEncoder, RawBytes, VarInt};
35use valence_server::text::{Color, IntoText};
36use valence_server::{ident, Text, MINECRAFT_VERSION, PROTOCOL_VERSION};
37
38use crate::legacy_ping::try_handle_legacy_ping;
39use crate::packet_io::PacketIo;
40use crate::{CleanupOnDrop, ConnectionMode, NewClientInfo, ServerListPing, SharedNetworkState};
41
42pub(super) async fn do_accept_loop(shared: SharedNetworkState) {
44 let listener = match TcpListener::bind(shared.0.address).await {
45 Ok(listener) => listener,
46 Err(e) => {
47 error!("failed to start TCP listener: {e}");
48 return;
49 }
50 };
51
52 let timeout = Duration::from_secs(5);
53
54 loop {
55 match shared.0.connection_sema.clone().acquire_owned().await {
56 Ok(permit) => match listener.accept().await {
57 Ok((stream, remote_addr)) => {
58 let shared = shared.clone();
59
60 tokio::spawn(async move {
61 if let Err(e) = tokio::time::timeout(
62 timeout,
63 handle_connection(shared, stream, remote_addr),
64 )
65 .await
66 {
67 warn!("initial connection timed out: {e}");
68 }
69
70 drop(permit);
71 });
72 }
73 Err(e) => {
74 error!("failed to accept incoming connection: {e}");
75 }
76 },
77 Err(_) => return,
79 }
80 }
81}
82
83async fn handle_connection(
84 shared: SharedNetworkState,
85 mut stream: TcpStream,
86 remote_addr: SocketAddr,
87) {
88 trace!("handling connection");
89
90 if let Err(e) = stream.set_nodelay(true) {
91 error!("failed to set TCP_NODELAY: {e}");
92 }
93
94 match try_handle_legacy_ping(&shared, &mut stream, remote_addr).await {
95 Ok(true) => return, Ok(false) => {} Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {}
98 Err(e) => {
99 warn!("legacy ping ended with error: {e:#}");
100 }
101 }
102
103 let io = PacketIo::new(stream, PacketEncoder::new(), PacketDecoder::new());
104
105 if let Err(e) = handle_handshake(shared, io, remote_addr).await {
106 if let Some(e) = e.downcast_ref::<io::Error>() {
109 if e.kind() == io::ErrorKind::UnexpectedEof {
110 return;
111 }
112 }
113 warn!("connection ended with error: {e:#}");
114 }
115}
116
117#[derive(Default, Debug)]
120pub struct HandshakeData {
121 pub protocol_version: i32,
123 pub server_address: String,
125 pub server_port: u16,
127}
128
129async fn handle_handshake(
130 shared: SharedNetworkState,
131 mut io: PacketIo,
132 remote_addr: SocketAddr,
133) -> anyhow::Result<()> {
134 let handshake = io.recv_packet::<HandshakeC2s>().await?;
135
136 let next_state = handshake.next_state;
137
138 let handshake = HandshakeData {
139 protocol_version: handshake.protocol_version.0,
140 server_address: handshake.server_address.0.to_owned(),
141 server_port: handshake.server_port,
142 };
143
144 ensure!(
146 shared.0.connection_mode == ConnectionMode::BungeeCord
147 || handshake.server_address.encode_utf16().count() <= 255,
148 "handshake server address is too long"
149 );
150
151 match next_state {
152 HandshakeNextState::Status => handle_status(shared, io, remote_addr, handshake)
153 .await
154 .context("handling status"),
155 HandshakeNextState::Login => {
156 match handle_login(&shared, &mut io, remote_addr, handshake)
157 .await
158 .context("handling login")?
159 {
160 Some((info, cleanup)) => {
161 let client = io.into_client_args(
162 info,
163 shared.0.incoming_byte_limit,
164 shared.0.outgoing_byte_limit,
165 cleanup,
166 );
167
168 let _ = shared.0.new_clients_send.send_async(client).await;
169
170 Ok(())
171 }
172 None => Ok(()),
173 }
174 }
175 }
176}
177
178async fn handle_status(
179 shared: SharedNetworkState,
180 mut io: PacketIo,
181 remote_addr: SocketAddr,
182 handshake: HandshakeData,
183) -> anyhow::Result<()> {
184 io.recv_packet::<QueryRequestC2s>().await?;
185
186 match shared
187 .0
188 .callbacks
189 .inner
190 .server_list_ping(&shared, remote_addr, &handshake)
191 .await
192 {
193 ServerListPing::Respond {
194 online_players,
195 max_players,
196 player_sample,
197 mut description,
198 favicon_png,
199 version_name,
200 protocol,
201 } => {
202 if handshake.protocol_version < 735 {
206 fn fallback_webcolors(txt: &mut Text) {
207 if let Some(Color::Rgb(color)) = txt.color {
208 txt.color = Some(Color::Named(color.to_named_lossy()));
209 }
210 for child in &mut txt.extra {
211 fallback_webcolors(child);
212 }
213 }
214
215 fallback_webcolors(&mut description);
216 }
217
218 let mut json = json!({
219 "version": {
220 "name": version_name,
221 "protocol": protocol,
222 },
223 "players": {
224 "online": online_players,
225 "max": max_players,
226 "sample": player_sample,
227 },
228 "description": description,
229 });
230
231 if !favicon_png.is_empty() {
232 let mut buf = "data:image/png;base64,".to_owned();
233 BASE64_STANDARD.encode_string(favicon_png, &mut buf);
234 json["favicon"] = Value::String(buf);
235 }
236
237 io.send_packet(&QueryResponseS2c {
238 json: &json.to_string(),
239 })
240 .await?;
241 }
242 ServerListPing::Ignore => return Ok(()),
243 }
244
245 let QueryPingC2s { payload } = io.recv_packet().await?;
246
247 io.send_packet(&QueryPongS2c { payload }).await?;
248
249 Ok(())
250}
251
252async fn handle_login(
254 shared: &SharedNetworkState,
255 io: &mut PacketIo,
256 remote_addr: SocketAddr,
257 handshake: HandshakeData,
258) -> anyhow::Result<Option<(NewClientInfo, CleanupOnDrop)>> {
259 if handshake.protocol_version != PROTOCOL_VERSION {
260 io.send_packet(&LoginDisconnectS2c {
261 reason: format!("Mismatched Minecraft version (server is on {MINECRAFT_VERSION})")
263 .color(Color::RED)
264 .into(),
265 })
266 .await?;
267
268 return Ok(None);
269 }
270
271 let LoginHelloC2s {
272 username,
273 .. } = io.recv_packet().await?;
275
276 let username = username.0.to_owned();
277
278 let info = match shared.connection_mode() {
279 ConnectionMode::Online { .. } => login_online(shared, io, remote_addr, username).await?,
280 ConnectionMode::Offline => login_offline(remote_addr, username)?,
281 ConnectionMode::BungeeCord => {
282 login_bungeecord(remote_addr, &handshake.server_address, username)?
283 }
284 ConnectionMode::Velocity { secret } => login_velocity(io, username, secret).await?,
285 };
286
287 if shared.0.threshold.0 > 0 {
288 io.send_packet(&LoginCompressionS2c {
289 threshold: shared.0.threshold.0.into(),
290 })
291 .await?;
292
293 io.set_compression(shared.0.threshold);
294 }
295
296 let cleanup = match shared.0.callbacks.inner.login(shared, &info).await {
297 Ok(f) => CleanupOnDrop(Some(f)),
298 Err(reason) => {
299 info!("disconnect at login: \"{reason}\"");
300 io.send_packet(&LoginDisconnectS2c {
301 reason: reason.into(),
302 })
303 .await?;
304 return Ok(None);
305 }
306 };
307
308 io.send_packet(&LoginSuccessS2c {
309 uuid: info.uuid,
310 username: info.username.as_str().into(),
311 properties: Default::default(),
312 })
313 .await?;
314
315 Ok(Some((info, cleanup)))
316}
317
318async fn login_online(
320 shared: &SharedNetworkState,
321 io: &mut PacketIo,
322 remote_addr: SocketAddr,
323 username: String,
324) -> anyhow::Result<NewClientInfo> {
325 let my_verify_token: [u8; 16] = rand::random();
326
327 io.send_packet(&LoginHelloS2c {
328 server_id: "".into(), public_key: &shared.0.public_key_der,
330 verify_token: &my_verify_token,
331 })
332 .await?;
333
334 let LoginKeyC2s {
335 shared_secret,
336 verify_token: encrypted_verify_token,
337 } = io.recv_packet().await?;
338
339 let shared_secret = shared
340 .0
341 .rsa_key
342 .decrypt(Pkcs1v15Encrypt, shared_secret)
343 .context("failed to decrypt shared secret")?;
344
345 let verify_token = shared
346 .0
347 .rsa_key
348 .decrypt(Pkcs1v15Encrypt, encrypted_verify_token)
349 .context("failed to decrypt verify token")?;
350
351 ensure!(
352 my_verify_token.as_slice() == verify_token,
353 "verify tokens do not match"
354 );
355
356 let crypt_key: [u8; 16] = shared_secret
357 .as_slice()
358 .try_into()
359 .context("shared secret has the wrong length")?;
360
361 io.enable_encryption(&crypt_key);
362
363 let hash = Sha1::new()
364 .chain(&shared_secret)
365 .chain(&shared.0.public_key_der)
366 .finalize();
367
368 let url = shared
369 .0
370 .callbacks
371 .inner
372 .session_server(
373 shared,
374 username.as_str(),
375 &auth_digest(&hash),
376 &remote_addr.ip(),
377 )
378 .await;
379
380 let resp = shared.0.http_client.get(url).send().await?;
381
382 match resp.status() {
383 StatusCode::OK => {}
384 StatusCode::NO_CONTENT => {
385 let reason = Text::translate(keys::MULTIPLAYER_DISCONNECT_UNVERIFIED_USERNAME, []);
386 io.send_packet(&LoginDisconnectS2c {
387 reason: reason.into(),
388 })
389 .await?;
390 bail!("session server could not verify username");
391 }
392 status => {
393 bail!("session server GET request failed (status code {status})");
394 }
395 }
396
397 #[derive(Deserialize)]
398 struct GameProfile {
399 id: Uuid,
400 name: String,
401 properties: Vec<Property>,
402 }
403
404 let profile: GameProfile = resp.json().await.context("parsing game profile")?;
405
406 ensure!(profile.name == username, "usernames do not match");
407
408 Ok(NewClientInfo {
409 uuid: profile.id,
410 username,
411 ip: remote_addr.ip(),
412 properties: Properties(profile.properties),
413 })
414}
415
416fn auth_digest(bytes: &[u8]) -> String {
417 BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
418}
419
420fn offline_uuid(username: &str) -> anyhow::Result<Uuid> {
421 Uuid::from_slice(&Sha256::digest(username)[..16]).map_err(Into::into)
422}
423
424fn login_offline(remote_addr: SocketAddr, username: String) -> anyhow::Result<NewClientInfo> {
426 Ok(NewClientInfo {
427 uuid: offline_uuid(username.as_str())?,
429 username,
430 properties: Default::default(),
431 ip: remote_addr.ip(),
432 })
433}
434
435fn login_bungeecord(
437 remote_addr: SocketAddr,
438 server_address: &str,
439 username: String,
440) -> anyhow::Result<NewClientInfo> {
441 let data = server_address.split('\0').take(4).collect::<Vec<_>>();
443
444 let ip = match data.get(1) {
446 Some(ip) => ip.parse()?,
447 None => remote_addr.ip(),
448 };
449
450 let uuid = match data.get(2) {
452 Some(uuid) => uuid.parse()?,
453 None => offline_uuid(username.as_str())?,
454 };
455
456 let properties: Vec<Property> = match data.get(3) {
460 Some(properties) => serde_json::from_str(properties)
461 .context("failed to parse BungeeCord player properties")?,
462 None => vec![],
463 };
464
465 Ok(NewClientInfo {
466 uuid,
467 username,
468 properties: Properties(properties),
469 ip,
470 })
471}
472
473async fn login_velocity(
475 io: &mut PacketIo,
476 username: String,
477 velocity_secret: &str,
478) -> anyhow::Result<NewClientInfo> {
479 const VELOCITY_MIN_SUPPORTED_VERSION: u8 = 1;
480 const VELOCITY_MODERN_FORWARDING_WITH_KEY_V2: i32 = 3;
481
482 let message_id: i32 = 0; io.send_packet(&LoginQueryRequestS2c {
486 message_id: VarInt(message_id),
487 channel: ident!("velocity:player_info").into(),
488 data: RawBytes(&[VELOCITY_MIN_SUPPORTED_VERSION]).into(),
489 })
490 .await?;
491
492 let plugin_response: LoginQueryResponseC2s = io.recv_packet().await?;
494
495 ensure!(
496 plugin_response.message_id.0 == message_id,
497 "mismatched plugin response ID (got {}, expected {message_id})",
498 plugin_response.message_id.0,
499 );
500
501 let data = plugin_response
502 .data
503 .context("missing plugin response data")?
504 .0;
505
506 ensure!(data.len() >= 32, "invalid plugin response data length");
507 let (signature, mut data_without_signature) = data.split_at(32);
508
509 let mut mac = Hmac::<Sha256>::new_from_slice(velocity_secret.as_bytes())?;
511 Mac::update(&mut mac, data_without_signature);
512 mac.verify_slice(signature)?;
513
514 let version = VarInt::decode(&mut data_without_signature)
516 .context("failed to decode velocity version")?
517 .0;
518
519 let remote_addr = String::decode(&mut data_without_signature)?.parse()?;
521
522 let uuid = Uuid::decode(&mut data_without_signature)?;
524
525 ensure!(
527 username == <&str>::decode(&mut data_without_signature)?,
528 "mismatched usernames"
529 );
530
531 let properties = Vec::<Property>::decode(&mut data_without_signature)
533 .context("decoding velocity game profile properties")?;
534
535 if version >= VELOCITY_MODERN_FORWARDING_WITH_KEY_V2 {
536 }
538
539 Ok(NewClientInfo {
540 uuid,
541 username,
542 properties: Properties(properties),
543 ip: remote_addr,
544 })
545}
546
547#[cfg(test)]
548mod tests {
549 use sha1::Digest;
550
551 use super::*;
552
553 #[test]
554 fn auth_digest_usernames() {
555 assert_eq!(
556 auth_digest(&Sha1::digest("Notch")),
557 "4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48"
558 );
559 assert_eq!(
560 auth_digest(&Sha1::digest("jeb_")),
561 "-7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1"
562 );
563 assert_eq!(
564 auth_digest(&Sha1::digest("simon")),
565 "88e16a1019277b15d58faf0541e11910eb756f6"
566 );
567 }
568}