conn_waitpong: optionally require a PONG cookie before registration (anti-bot)
This commit is contained in:
parent
e0d849b4c9
commit
61954f6c5c
7 changed files with 70 additions and 2 deletions
|
|
@ -555,8 +555,10 @@ impl Command for Pong {
|
|||
fn before_reg(&self) -> bool {
|
||||
true
|
||||
}
|
||||
fn handle(&self, _s: &mut Server, _uid: Uid, _params: &[String]) -> CmdResult {
|
||||
CmdResult::Ok // keepalive; nothing to do yet
|
||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||
// conn_waitpong: a pre-registration PONG may be answering our cookie
|
||||
crate::modules::conn_waitpong::on_pong(s, uid, params);
|
||||
CmdResult::Ok // otherwise just a keepalive
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -384,6 +384,7 @@ impl Ircd {
|
|||
&& !u.ident.is_empty()
|
||||
&& !u.cap
|
||||
&& !u.dns_pending
|
||||
&& u.waitpong.is_none()
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if ready {
|
||||
|
|
|
|||
54
src/modules/conn_waitpong.rs
Normal file
54
src/modules/conn_waitpong.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! conn_waitpong — optionally hold registration until the client answers a server
|
||||
//! PING with the exact cookie we sent, filtering bots that never PONG. Config:
|
||||
//!
|
||||
//! ```text
|
||||
//! conn_waitpong = yes # require the pong before registering (default off)
|
||||
//! conn_waitpong_killonbadreply = yes # disconnect on a wrong pong (default: keep waiting)
|
||||
//! ```
|
||||
//!
|
||||
//! The gate itself is the core `User.waitpong` field (checked in `try_register`,
|
||||
//! like `dns_pending`); this module just arms it at connect and clears it on the
|
||||
//! matching PONG. Behaviour reference: InspIRCd's `m_conn_waitpong`. Native Rust.
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::Uid;
|
||||
|
||||
/// A short random cookie the client must echo back in its PONG.
|
||||
fn cookie() -> String {
|
||||
let mut b = [0u8; 8];
|
||||
let _ = openssl::rand::rand_bytes(&mut b);
|
||||
b.iter().map(|x| format!("{x:02x}")).collect()
|
||||
}
|
||||
|
||||
/// At connect: if enabled, stash a cookie on the user and PING it. `try_register`
|
||||
/// will not complete while `User.waitpong` is set.
|
||||
pub fn arm(s: &mut Server, uid: Uid) {
|
||||
if !s.conf_bool("conn_waitpong", false) {
|
||||
return;
|
||||
}
|
||||
let c = cookie();
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.waitpong = Some(c.clone());
|
||||
}
|
||||
s.send(uid, format!(":{} PING :{c}", s.name));
|
||||
}
|
||||
|
||||
/// On PONG: clear the gate if the cookie matches. On a wrong reply, optionally drop
|
||||
/// the client (else keep waiting — a real client will retry on the next PING).
|
||||
pub fn on_pong(s: &mut Server, uid: Uid, params: &[String]) {
|
||||
let Some(want) = s.users.get(&uid).and_then(|u| u.waitpong.clone()) else {
|
||||
return; // not waiting (already satisfied, or feature off)
|
||||
};
|
||||
let got = params.last().map(String::as_str).unwrap_or("");
|
||||
if got == want {
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.waitpong = None;
|
||||
}
|
||||
} else if s.conf_bool("conn_waitpong_killonbadreply", false) {
|
||||
s.send(
|
||||
uid,
|
||||
"ERROR :Closing link (incorrect ping reply)".to_string(),
|
||||
);
|
||||
s.remove_user(uid, "Incorrect ping reply");
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ pub mod channelban;
|
|||
pub mod chathistory;
|
||||
pub mod cloak;
|
||||
pub mod cloudflare_challenge;
|
||||
pub mod conn_waitpong;
|
||||
pub mod connectban;
|
||||
pub mod connflood;
|
||||
pub mod customtitle;
|
||||
|
|
|
|||
|
|
@ -318,6 +318,7 @@ impl Server {
|
|||
addr,
|
||||
registered: false,
|
||||
dns_pending: false,
|
||||
waitpong: None,
|
||||
deferred: Vec::new(),
|
||||
cap: false,
|
||||
cap_302: false,
|
||||
|
|
@ -393,6 +394,8 @@ impl Server {
|
|||
"Couldn't look up your hostname; using your IP address instead",
|
||||
);
|
||||
}
|
||||
// conn_waitpong: optionally hold registration until the client PONGs a cookie
|
||||
crate::modules::conn_waitpong::arm(self, uid);
|
||||
}
|
||||
|
||||
/// Fire an HTTP POST on a worker thread and deliver `(status, body)` back to
|
||||
|
|
@ -1039,6 +1042,7 @@ mod tests {
|
|||
addr: "127.0.0.1:1".parse().unwrap(),
|
||||
registered: true,
|
||||
dns_pending: false,
|
||||
waitpong: None,
|
||||
deferred: Vec::new(),
|
||||
cap: false,
|
||||
cap_302: false,
|
||||
|
|
|
|||
|
|
@ -286,6 +286,7 @@ pub struct User {
|
|||
pub addr: SocketAddr,
|
||||
pub registered: bool,
|
||||
pub dns_pending: bool, // holding registration for a reverse-DNS lookup
|
||||
pub waitpong: Option<String>, // conn_waitpong: cookie the client must PONG before registering
|
||||
pub deferred: Vec<String>, // handshake lines held while dns_pending (replayed after)
|
||||
pub cap: bool, // CAP negotiation in progress (holds registration)
|
||||
pub cap_302: bool, // client sent CAP LS 302 (cap-notify aware)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue