nickserv: log out the loser of a registration conflict
When a peer's registration wins a name a local session was logged into, that session authenticated against a credential that no longer exists. The node now clears its accountname (the ircd emits RPL_LOGGEDOUT) and tells the user why. A new outbound channel lets the engine push these services-initiated actions to the uplink from the gossip path, drained by the link loop alongside incoming traffic.
This commit is contained in:
parent
f46662db90
commit
aab64baf86
7 changed files with 164 additions and 33 deletions
69
src/link.rs
69
src/link.rs
|
|
@ -3,7 +3,7 @@ use std::sync::Arc;
|
|||
use anyhow::Result;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
|
||||
use crate::engine::db::Db;
|
||||
use crate::engine::Engine;
|
||||
|
|
@ -12,7 +12,7 @@ use crate::proto::{NetAction, Protocol};
|
|||
// One uplink session: connect, handshake + burst, then translate lines forever.
|
||||
// The engine is shared with the gossip layer, so it is locked per operation and
|
||||
// never held across the registration key-stretching await.
|
||||
pub async fn run(mut proto: Box<dyn Protocol>, engine: Arc<Mutex<Engine>>, addr: &str) -> Result<()> {
|
||||
pub async fn run(mut proto: Box<dyn Protocol>, engine: Arc<Mutex<Engine>>, addr: &str, mut irc_rx: mpsc::UnboundedReceiver<NetAction>) -> Result<()> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (read, mut write) = stream.into_split();
|
||||
let mut lines = BufReader::new(read).lines();
|
||||
|
|
@ -27,35 +27,48 @@ pub async fn run(mut proto: Box<dyn Protocol>, engine: Arc<Mutex<Engine>>, addr:
|
|||
}
|
||||
}
|
||||
|
||||
while let Some(line) = lines.next_line().await? {
|
||||
tracing::debug!(dir = "<<", %line);
|
||||
for event in proto.parse(&line) {
|
||||
let actions = engine.lock().await.handle(event);
|
||||
for action in actions {
|
||||
let outs = match action {
|
||||
// Registration: derive the password off the reactor so the
|
||||
// ~1s of key stretching can't stall the link, after a cheap
|
||||
// gate that rejects taken names and rate-limits floods.
|
||||
NetAction::DeferRegister { account, password, email, reply } => {
|
||||
let pre = engine.lock().await.pre_register_check(&account, &reply);
|
||||
match pre {
|
||||
Some(rejection) => rejection,
|
||||
None => {
|
||||
let iterations = engine.lock().await.scram_iterations();
|
||||
let creds = tokio::task::spawn_blocking(move || {
|
||||
Db::derive_credentials(&password, iterations)
|
||||
})
|
||||
.await?;
|
||||
engine.lock().await.complete_register(&account, creds, email, reply)
|
||||
loop {
|
||||
tokio::select! {
|
||||
// A line from the uplink: translate it and answer.
|
||||
line = lines.next_line() => {
|
||||
let Some(line) = line? else { break };
|
||||
tracing::debug!(dir = "<<", %line);
|
||||
for event in proto.parse(&line) {
|
||||
let actions = engine.lock().await.handle(event);
|
||||
for action in actions {
|
||||
let outs = match action {
|
||||
// Registration: derive the password off the reactor so the
|
||||
// ~1s of key stretching can't stall the link, after a cheap
|
||||
// gate that rejects taken names and rate-limits floods.
|
||||
NetAction::DeferRegister { account, password, email, reply } => {
|
||||
let pre = engine.lock().await.pre_register_check(&account, &reply);
|
||||
match pre {
|
||||
Some(rejection) => rejection,
|
||||
None => {
|
||||
let iterations = engine.lock().await.scram_iterations();
|
||||
let creds = tokio::task::spawn_blocking(move || {
|
||||
Db::derive_credentials(&password, iterations)
|
||||
})
|
||||
.await?;
|
||||
engine.lock().await.complete_register(&account, creds, email, reply)
|
||||
}
|
||||
}
|
||||
}
|
||||
action => vec![action],
|
||||
};
|
||||
for act in outs {
|
||||
for out in proto.serialize(&act) {
|
||||
send(&mut write, &out).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
action => vec![action],
|
||||
};
|
||||
for act in outs {
|
||||
for out in proto.serialize(&act) {
|
||||
send(&mut write, &out).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
// A services-initiated action (e.g. a forced logout after a lost
|
||||
// registration conflict), pushed by the engine from the gossip path.
|
||||
Some(action) = irc_rx.recv() => {
|
||||
for out in proto.serialize(&action) {
|
||||
send(&mut write, &out).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue