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");
|
s.numeric(uid, ERR_SASLABORTED, ":SASL authentication aborted");
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
} else if arg.eq_ignore_ascii_case("PLAIN") {
|
} 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) {
|
if let Some(u) = s.users.get_mut(&uid) {
|
||||||
u.sasl_mech = Some("PLAIN".to_string());
|
u.sasl_mech = Some("PLAIN".to_string());
|
||||||
}
|
}
|
||||||
if have_services {
|
// start the exchange at services; its `C` challenge is relayed
|
||||||
s.sasl_relay(uid, "S PLAIN"); // start the exchange at services
|
// back to the client as the `AUTHENTICATE +` prompt
|
||||||
}
|
s.sasl_relay(uid, "S PLAIN");
|
||||||
s.send(uid, "AUTHENTICATE +".to_string());
|
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
} else if arg.eq_ignore_ascii_case("EXTERNAL") {
|
} else if arg.eq_ignore_ascii_case("EXTERNAL") {
|
||||||
// CertFP: only works on TLS with a client cert; the fingerprint
|
// 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) {
|
if let Some(u) = s.users.get_mut(&uid) {
|
||||||
u.sasl_mech = Some("EXTERNAL".to_string());
|
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.sasl_relay(uid, &format!("S EXTERNAL {fp}"));
|
||||||
s.send(uid, "AUTHENTICATE +".to_string());
|
|
||||||
CmdResult::Ok
|
CmdResult::Ok
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|
|
||||||
43
src/link.rs
43
src/link.rs
|
|
@ -758,8 +758,11 @@ impl Server {
|
||||||
.map(|sv| sv.via)
|
.map(|sv| sv.via)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Relay one SASL step for local client `uid` to the services server:
|
/// Relay one SASL step for local client `uid` to the services server, wrapped
|
||||||
/// `:<our-sid> SASL <client-uuid> <rest>`. No-op if SASL services aren't linked.
|
/// 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) {
|
pub fn sasl_relay(&self, uid: Uid, rest: &str) {
|
||||||
let (Some(via), Some(uuid)) = (
|
let (Some(via), Some(uuid)) = (
|
||||||
self.sasl_link(),
|
self.sasl_link(),
|
||||||
|
|
@ -767,33 +770,43 @@ impl Server {
|
||||||
) else {
|
) else {
|
||||||
return;
|
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`;
|
/// `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) {
|
fn link_sasl(&mut self, from: Uid, msg: &Message) {
|
||||||
if msg.params.len() < 2 {
|
if msg.params.len() < 3 {
|
||||||
return;
|
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
|
// 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;
|
return;
|
||||||
};
|
};
|
||||||
match msg.params[1].as_str() {
|
match msg.params[2].as_str() {
|
||||||
"C" => {
|
"C" => {
|
||||||
if let Some(data) = msg.params.get(2) {
|
if let Some(data) = msg.params.get(3) {
|
||||||
self.send(uid, format!("AUTHENTICATE {data}"));
|
self.send(uid, format!("AUTHENTICATE {data}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"D" => {
|
"D" => {
|
||||||
let ok = msg.params.get(2).map(|t| t == "S").unwrap_or(false);
|
let ok = msg.params.get(3).map(|t| t == "S").unwrap_or(false);
|
||||||
let account = msg.params.get(3).cloned().unwrap_or_default();
|
let account = self
|
||||||
if ok && !account.is_empty() {
|
.users
|
||||||
self.set_login(uid, &account);
|
.get(&uid)
|
||||||
}
|
.and_then(|u| u.account.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
self.sasl_done(uid, ok, &account);
|
self.sasl_done(uid, ok, &account);
|
||||||
if let Some(u) = self.users.get_mut(&uid) {
|
if let Some(u) = self.users.get_mut(&uid) {
|
||||||
u.sasl_mech = None;
|
u.sasl_mech = None;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue