solvemsg: arithmetic challenge before an un-vouched user's PMs deliver (anti-spam)
This commit is contained in:
parent
0bf6992d8a
commit
349200d695
3 changed files with 116 additions and 0 deletions
|
|
@ -51,6 +51,7 @@ pub mod securelist;
|
|||
pub mod securitygroups;
|
||||
pub mod serverban;
|
||||
pub mod showfile;
|
||||
pub mod solvemsg;
|
||||
pub mod snoop;
|
||||
pub mod tline;
|
||||
pub mod whoisport;
|
||||
|
|
@ -86,6 +87,7 @@ pub fn default_modules() -> Vec<Box<dyn Module>> {
|
|||
Box::new(disable::Disable),
|
||||
Box::new(maphide::MapHide),
|
||||
Box::new(dccallow::DccAllow),
|
||||
Box::new(solvemsg::SolveMsg),
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
110
src/modules/solvemsg.rs
Normal file
110
src/modules/solvemsg.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//! solvemsg — a lightweight anti-spam gate: before an un-vouched user's *private*
|
||||
//! messages are delivered, they must answer one small arithmetic question. Opers
|
||||
//! and users logged into an account are exempt. Off unless `solvemsg = yes`.
|
||||
//!
|
||||
//! Flow: the first PM is held and a question is posed; the user replies with the
|
||||
//! number (that reply is consumed), and once correct every later message passes.
|
||||
//! Reference: InspIRCd's `m_solvemsg`. Original native Rust.
|
||||
|
||||
use crate::module::{ModResult, Module};
|
||||
use crate::server::Server;
|
||||
use crate::Uid;
|
||||
|
||||
/// Per-user challenge state, on `User.ext`.
|
||||
#[derive(Default)]
|
||||
struct SolveState {
|
||||
solved: bool,
|
||||
answer: Option<i64>,
|
||||
}
|
||||
|
||||
/// A uniform-ish random byte in `0..max` via openssl (no `rand` crate).
|
||||
fn rnd(max: u8) -> u8 {
|
||||
let mut b = [0u8; 1];
|
||||
let _ = openssl::rand::rand_bytes(&mut b);
|
||||
b[0] % max
|
||||
}
|
||||
|
||||
pub struct SolveMsg;
|
||||
|
||||
impl Module for SolveMsg {
|
||||
fn name(&self) -> &'static str {
|
||||
"solvemsg"
|
||||
}
|
||||
|
||||
fn on_pre_message(
|
||||
&mut self,
|
||||
s: &mut Server,
|
||||
uid: Uid,
|
||||
target: &str,
|
||||
text: &str,
|
||||
) -> ModResult {
|
||||
if !s.conf_bool("solvemsg", false) {
|
||||
return ModResult::Passthru;
|
||||
}
|
||||
// trusted: opers and logged-in accounts never see a challenge
|
||||
let exempt = s
|
||||
.users
|
||||
.get(&uid)
|
||||
.map(|u| u.flags.oper || u.account.is_some())
|
||||
.unwrap_or(true);
|
||||
if exempt {
|
||||
return ModResult::Passthru;
|
||||
}
|
||||
// only gate PMs to another user (channels have their own controls)
|
||||
let Some(tuid) = s.find_nick(target) else {
|
||||
return ModResult::Passthru;
|
||||
};
|
||||
if tuid == uid {
|
||||
return ModResult::Passthru;
|
||||
}
|
||||
let st = s.users.get(&uid).and_then(|u| u.ext.get::<SolveState>());
|
||||
if st.map(|st| st.solved).unwrap_or(false) {
|
||||
return ModResult::Passthru; // already solved
|
||||
}
|
||||
let pending = st.and_then(|st| st.answer);
|
||||
let nick = s.users.get(&uid).map(|u| u.nick.clone()).unwrap_or_default();
|
||||
|
||||
// is this message the answer to an outstanding challenge?
|
||||
if let Some(ans) = pending {
|
||||
if text.trim().parse::<i64>().ok() == Some(ans) {
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.ext.set(SolveState {
|
||||
solved: true,
|
||||
answer: None,
|
||||
});
|
||||
}
|
||||
s.send(
|
||||
uid,
|
||||
format!(
|
||||
":{} NOTICE {nick} :*** Correct — you may now message freely; please resend your message.",
|
||||
s.name
|
||||
),
|
||||
);
|
||||
return ModResult::Deny; // consume the answer itself
|
||||
}
|
||||
}
|
||||
|
||||
// pose a fresh question
|
||||
let a = (rnd(9) + 1) as i64;
|
||||
let b = (rnd(9) + 1) as i64;
|
||||
let (sym, ans) = match rnd(3) {
|
||||
0 => ("+", a + b),
|
||||
1 => ("-", a - b),
|
||||
_ => ("*", a * b),
|
||||
};
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.ext.set(SolveState {
|
||||
solved: false,
|
||||
answer: Some(ans),
|
||||
});
|
||||
}
|
||||
s.send(
|
||||
uid,
|
||||
format!(
|
||||
":{} NOTICE {nick} :*** To cut spam, answer this to send your message — what is {a} {sym} {b} ?",
|
||||
s.name
|
||||
),
|
||||
);
|
||||
ModResult::Deny
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue