sasl: wrap the s2s sasl relay in encap (client/agent/mode/data) so services drive authenticate, account via metadata
This commit is contained in:
parent
1c8799d785
commit
62f901bdc8
2 changed files with 41 additions and 20 deletions
|
|
@ -284,13 +284,20 @@ impl Command for Authenticate {
|
|||
s.numeric(uid, ERR_SASLABORTED, ":SASL authentication aborted");
|
||||
CmdResult::Ok
|
||||
} else if arg.eq_ignore_ascii_case("PLAIN") {
|
||||
if !have_services {
|
||||
s.numeric(
|
||||
uid,
|
||||
ERR_SASLFAIL,
|
||||
":SASL authentication failed (services are not available)",
|
||||
);
|
||||
return CmdResult::Fail;
|
||||
}
|
||||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.sasl_mech = Some("PLAIN".to_string());
|
||||
}
|
||||
if have_services {
|
||||
s.sasl_relay(uid, "S PLAIN"); // start the exchange at services
|
||||
}
|
||||
s.send(uid, "AUTHENTICATE +".to_string());
|
||||
// start the exchange at services; its `C` challenge is relayed
|
||||
// back to the client as the `AUTHENTICATE +` prompt
|
||||
s.sasl_relay(uid, "S PLAIN");
|
||||
CmdResult::Ok
|
||||
} else if arg.eq_ignore_ascii_case("EXTERNAL") {
|
||||
// CertFP: only works on TLS with a client cert; the fingerprint
|
||||
|
|
@ -301,8 +308,9 @@ impl Command for Authenticate {
|
|||
if let Some(u) = s.users.get_mut(&uid) {
|
||||
u.sasl_mech = Some("EXTERNAL".to_string());
|
||||
}
|
||||
// services replies with a `C` challenge we relay as the
|
||||
// client's `AUTHENTICATE +` prompt
|
||||
s.sasl_relay(uid, &format!("S EXTERNAL {fp}"));
|
||||
s.send(uid, "AUTHENTICATE +".to_string());
|
||||
CmdResult::Ok
|
||||
}
|
||||
_ => {
|
||||
|
|
|
|||
43
src/link.rs
43
src/link.rs
|
|
@ -758,8 +758,11 @@ impl Server {
|
|||
.map(|sv| sv.via)
|
||||
}
|
||||
|
||||
/// Relay one SASL step for local client `uid` to the services server:
|
||||
/// `:<our-sid> SASL <client-uuid> <rest>`. No-op if SASL services aren't linked.
|
||||
/// Relay one SASL step for local client `uid` to the services server, wrapped
|
||||
/// as `:<our-sid> ENCAP <svc> SASL <client-uuid> * <mode> [data...]`. `rest` is
|
||||
/// the mode letter and its data (e.g. `S PLAIN`, `C <b64>`). The agent field is
|
||||
/// `*` — services accept it, so we needn't track their agent id. No-op with no
|
||||
/// SASL services linked.
|
||||
pub fn sasl_relay(&self, uid: Uid, rest: &str) {
|
||||
let (Some(via), Some(uuid)) = (
|
||||
self.sasl_link(),
|
||||
|
|
@ -767,33 +770,43 @@ impl Server {
|
|||
) else {
|
||||
return;
|
||||
};
|
||||
self.link_out(via, format!(":{} SASL {uuid} {rest}", self.sid));
|
||||
let mask = self
|
||||
.servers
|
||||
.values()
|
||||
.find(|s| s.via == via)
|
||||
.map(|s| s.sid.clone())
|
||||
.unwrap_or_else(|| "*".to_string());
|
||||
self.link_out(via, format!(":{} ENCAP {mask} SASL {uuid} * {rest}", self.sid));
|
||||
}
|
||||
|
||||
/// A SASL message from services: `:<svcsid> SASL <client-uuid> <type> …`.
|
||||
/// A SASL step from services, unwrapped from its ENCAP: params are
|
||||
/// `<agent> <client-uuid> <mode> [data...]`.
|
||||
/// `C <data>` → relay a server challenge to the client as `AUTHENTICATE`;
|
||||
/// `D S [account]` → success (log in + 900/903); `D <other>` → fail (904).
|
||||
/// `D S` → success (the account was set by a preceding `METADATA accountname`,
|
||||
/// so we emit 900/903 for it); `D <other>` → fail (904).
|
||||
fn link_sasl(&mut self, from: Uid, msg: &Message) {
|
||||
if msg.params.len() < 2 {
|
||||
if msg.params.len() < 3 {
|
||||
return;
|
||||
}
|
||||
let Some(&uid) = self.uuid_local.get(&msg.params[0]) else {
|
||||
let client = msg.params[1].clone();
|
||||
let Some(&uid) = self.uuid_local.get(&client) else {
|
||||
// not our client — route toward the server that owns them
|
||||
self.forward_to_target(&msg.params[0], msg, from);
|
||||
self.forward_to_target(&client, msg, from);
|
||||
return;
|
||||
};
|
||||
match msg.params[1].as_str() {
|
||||
match msg.params[2].as_str() {
|
||||
"C" => {
|
||||
if let Some(data) = msg.params.get(2) {
|
||||
if let Some(data) = msg.params.get(3) {
|
||||
self.send(uid, format!("AUTHENTICATE {data}"));
|
||||
}
|
||||
}
|
||||
"D" => {
|
||||
let ok = msg.params.get(2).map(|t| t == "S").unwrap_or(false);
|
||||
let account = msg.params.get(3).cloned().unwrap_or_default();
|
||||
if ok && !account.is_empty() {
|
||||
self.set_login(uid, &account);
|
||||
}
|
||||
let ok = msg.params.get(3).map(|t| t == "S").unwrap_or(false);
|
||||
let account = self
|
||||
.users
|
||||
.get(&uid)
|
||||
.and_then(|u| u.account.clone())
|
||||
.unwrap_or_default();
|
||||
self.sasl_done(uid, ok, &account);
|
||||
if let Some(u) = self.users.get_mut(&uid) {
|
||||
u.sasl_mech = None;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue