Proof of concept mechanical port of ircnet/ircd to Rust as part of a bit about C being insecure for network services
0

Configure Feed

Select the types of activity you want to include in your feed.

ircd.rs / leveva / tests / golden_okick.rs
3.1 kB 77 lines
1//! Boot-level golden: `OKICK` end-to-end through the real `leveva` binary (slice 294). 2//! 3//! `OKICK <channel> <user> [:comment]` lets an operator force-kick a member without being a 4//! chanop — or even a member of the channel. One booted server (`dline.kdl`, whose `root` 5//! operator suffices — OKICK needs only the oper bit), three clients: 6//! 7//! 1. `alice`, `bob`, `carol` register; `alice` opers up but never joins `#rust`. 8//! 2. `bob` JOINs `#rust` (→ `@op`), `carol` JOINs (a plain member). 9//! 3. `bob` (not an operator) `OKICK #rust carol` → `481 ERR_NOPRIVILEGES`. 10//! 4. `alice` (an oper, **not** a member) `OKICK #rust carol :spam` → `:leveva.test KICK #rust 11//! carol :spam`, echoed to `alice` and seen by `bob` (and `carol`, the victim). 12//! 5. `alice` `OKICK #ghost carol` (channel does not exist) → `403`. 13//! 14//! Lines are server- or numeric-prefixed and deterministic, so only the shared masks apply. 15 16mod harness; 17use harness::{boot_fixture, canonicalize, Client, PORT}; 18 19fn register(name: &str) -> Client { 20 let mut c = Client::connect(); 21 c.send(&format!("NICK {name}")); 22 c.send(&format!("USER {name} 0 * :{name} Tester")); 23 c.read_until(" 422 "); // drain the welcome burst (MOTD-missing 422) 24 c 25} 26 27#[test] 28fn okick_end_to_end() { 29 let _srv = boot_fixture("dline.kdl", PORT); 30 31 let mut alice = register("alice"); 32 let mut bob = register("bob"); 33 let mut carol = register("carol"); 34 35 // alice opers up (any operator suffices for OKICK) but never joins #rust. 36 alice.send("OPER root hunter2"); 37 alice.read_until(" 381 "); 38 39 // bob creates #rust (→ @op); carol joins as a plain member. 40 bob.send("JOIN #rust"); 41 bob.read_until(" 366 "); 42 carol.send("JOIN #rust"); 43 carol.read_until(" 366 "); 44 bob.read_until("JOIN #rust"); // bob sees carol's JOIN 45 46 // 3. non-oper bob → 481 (and the channel is untouched). 47 bob.send("OKICK #rust carol"); 48 let bob_481 = bob.read_until(" 481 "); 49 50 // 4. alice (oper, not a member) force-kicks carol → server-sourced KICK, echoed + seen. 51 alice.send("OKICK #rust carol :spam"); 52 let alice_kick = alice.read_until("KICK #rust"); 53 let bob_kick = bob.read_until("KICK #rust"); 54 let carol_kick = carol.read_until("KICK #rust"); 55 56 // 5. nonexistent channel → 403. 57 alice.send("OKICK #ghost carol"); 58 let alice_403 = alice.read_until(" 403 "); 59 60 let mut transcript = String::new(); 61 let mut keep = |src: &str, needle: &str, label: &str| { 62 for line in canonicalize(src).lines() { 63 if line.contains(needle) { 64 transcript.push_str(label); 65 transcript.push_str(line); 66 transcript.push('\n'); 67 } 68 } 69 }; 70 keep(&bob_481, " 481 ", "bob 481: "); 71 keep(&alice_kick, "KICK #rust carol", "alice sees KICK: "); 72 keep(&bob_kick, "KICK #rust carol", "bob sees KICK: "); 73 keep(&carol_kick, "KICK #rust carol", "carol sees KICK: "); 74 keep(&alice_403, " 403 ", "alice 403: "); 75 76 insta::assert_snapshot!(transcript); 77}