/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use crate::{conn_test::test_connect, Registry, MAX_SERVERS}; use anyhow::Result; use hurrycurry_protocol::registry::Submission; use log::{debug, info}; use rocket::{http::hyper::Uri, post, serde::json::Json, State}; use std::{ net::{IpAddr, SocketAddr}, str::FromStr, sync::Arc, time::Instant, }; use tokio::{net::lookup_host, sync::RwLock}; #[post("/v1/register", data = "")] pub(super) async fn r_register( client_addr: IpAddr, registry: &State>>, submission: Json, ) -> Result<&'static str, &'static str> { match register_inner(client_addr, registry, &submission).await { Ok(()) => { info!("ok"); Ok("ok") } Err(e) => { info!("err: {e}"); Err(e) } } } async fn register_inner( client_addr: IpAddr, registry: &Arc>, submission: &Submission, ) -> Result<(), &'static str> { debug!("submission {submission:?} from {client_addr}"); let client_addr = client_addr.to_canonical(); let uri = Uri::from_str(&submission.uri).map_err(|_| "invalid uri")?; let scheme = uri.scheme().ok_or("no scheme")?.as_str(); let secure = match scheme { "ws" => false, "wss" => true, _ => return Err("invalid scheme"), }; let host = uri.host().ok_or("no host")?; let port = uri.port_u16().unwrap_or(if secure { 443 } else { 27032 }); let (saddr, uri_q) = match IpAddr::from_str(host) { Ok(mut addr) => { if addr.is_unspecified() { addr = client_addr; } if addr.is_loopback() { return Err("loopback address"); } if addr.is_multicast() { return Err("multicast address"); } info!("{client_addr} != {addr}"); let addr = addr.to_canonical(); if client_addr == addr { let saddr = SocketAddr::new(addr, port); (saddr, format!("{scheme}://{saddr}")) } else { return Err("source address does not match uri"); } } Err(_) => { if let Some(addr) = lookup_host(format!("{host}:0")) .await .map_err(|_| "dns lookup failed")? .find(|a| a.ip().to_canonical() == client_addr) { ( SocketAddr::new(addr.ip().to_canonical(), port), format!("{scheme}://{host}:{port}"), ) } else { return Err("host verification failed"); } } }; test_connect(saddr, &uri_q).await?; let mut g = registry.write().await; if g.servers.len() > MAX_SERVERS { return Err("too many registered servers"); } info!("submission approved for {uri_q:?}"); let entry = g.servers.entry(submission.secret).or_default(); entry.name = submission.name.clone(); entry.players_online = submission.players; entry.last_game = submission.last_game; entry.version = submission.version; entry.address.insert(uri_q, Instant::now()); Ok(()) }