Skip to content

Commit b14354e

Browse files
authored
Merge commit from fork
1 parent 72b250a commit b14354e

3 files changed

Lines changed: 144 additions & 6 deletions

File tree

russh/src/auth.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,23 @@ pub enum Method {
226226
#[doc(hidden)]
227227
#[derive(Debug)]
228228
pub struct AuthRequest {
229+
initial_methods: MethodSet,
229230
pub methods: MethodSet,
230231
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
231232
pub partial_success: bool,
232233
pub current: Option<CurrentRequest>,
234+
pub(crate) principal: Option<AuthPrincipal>,
233235
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
234236
pub rejection_count: usize,
235237
}
236238

239+
#[doc(hidden)]
240+
#[derive(Debug)]
241+
pub(crate) struct AuthPrincipal {
242+
user: String,
243+
service: String,
244+
}
245+
237246
#[doc(hidden)]
238247
#[derive(Debug)]
239248
pub enum CurrentRequest {
@@ -252,22 +261,53 @@ pub enum CurrentRequest {
252261
}
253262

254263
impl AuthRequest {
264+
pub(crate) fn server(methods: MethodSet) -> Self {
265+
Self {
266+
initial_methods: methods.clone(),
267+
methods,
268+
partial_success: false,
269+
current: None,
270+
principal: None,
271+
rejection_count: 0,
272+
}
273+
}
274+
255275
pub(crate) fn new(method: &Method) -> Self {
256276
match method {
257277
Method::KeyboardInteractive { submethods } => Self {
278+
initial_methods: MethodSet::all(),
258279
methods: MethodSet::all(),
259280
partial_success: false,
260281
current: Some(CurrentRequest::KeyboardInteractive {
261282
submethods: submethods.to_string(),
262283
}),
284+
principal: None,
263285
rejection_count: 0,
264286
},
265287
_ => Self {
288+
initial_methods: MethodSet::all(),
266289
methods: MethodSet::all(),
267290
partial_success: false,
268291
current: None,
292+
principal: None,
269293
rejection_count: 0,
270294
},
271295
}
272296
}
297+
298+
pub(crate) fn bind_or_reset_principal(&mut self, user: &str, service: &str) -> bool {
299+
match &self.principal {
300+
Some(bound) if bound.user == user && bound.service == service => false,
301+
_ => {
302+
self.principal = Some(AuthPrincipal {
303+
user: user.to_owned(),
304+
service: service.to_owned(),
305+
});
306+
self.methods = self.initial_methods.clone();
307+
self.partial_success = false;
308+
self.current = None;
309+
true
310+
}
311+
}
312+
}
273313
}

russh/src/server/encrypted.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -333,12 +333,7 @@ fn server_accept_service(
333333
})
334334
}
335335

336-
Ok(AuthRequest {
337-
methods,
338-
partial_success: false, // not used immediately anway.
339-
current: None,
340-
rejection_count: 0,
341-
})
336+
Ok(AuthRequest::server(methods))
342337
}
343338

344339
impl Encrypted {
@@ -359,6 +354,18 @@ impl Encrypted {
359354
debug!("name: {user:?} {service_name:?} {method:?}",);
360355

361356
if service_name == "ssh-connection" {
357+
{
358+
let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state
359+
{
360+
a
361+
} else {
362+
unreachable!()
363+
};
364+
if auth_request.bind_or_reset_principal(&user, &service_name) {
365+
auth_user.clear();
366+
}
367+
}
368+
362369
if method == "password" {
363370
let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state
364371
{

russh/tests/auth_state_reset.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use std::sync::Arc;
2+
use std::time::Duration;
3+
4+
use russh::client;
5+
use russh::keys::PrivateKey;
6+
use russh::{MethodKind, MethodSet, server};
7+
8+
struct AcceptTestServerKey;
9+
10+
impl client::Handler for AcceptTestServerKey {
11+
type Error = russh::Error;
12+
13+
async fn check_server_key(
14+
&mut self,
15+
_server_public_key: &russh::keys::ssh_key::PublicKey,
16+
) -> Result<bool, Self::Error> {
17+
Ok(true)
18+
}
19+
}
20+
21+
struct RemainingMethodsUserSwitchServer;
22+
23+
impl server::Handler for RemainingMethodsUserSwitchServer {
24+
type Error = russh::Error;
25+
26+
async fn auth_none(&mut self, user: &str) -> Result<server::Auth, Self::Error> {
27+
if user == "alice" {
28+
Ok(server::Auth::Reject {
29+
proceed_with_methods: Some(MethodSet::from(&[MethodKind::Password][..])),
30+
partial_success: true,
31+
})
32+
} else {
33+
Ok(server::Auth::reject())
34+
}
35+
}
36+
}
37+
38+
#[tokio::test]
39+
async fn auth_does_not_carry_remaining_methods_across_username_change() {
40+
let mut server_config = server::Config::default();
41+
server_config.inactivity_timeout = None;
42+
server_config.auth_rejection_time = Duration::from_millis(1);
43+
server_config.auth_rejection_time_initial = Some(Duration::from_millis(1));
44+
server_config.keys.push(
45+
PrivateKey::random(&mut rand::rng(), russh::keys::ssh_key::Algorithm::Ed25519).unwrap(),
46+
);
47+
let server_config = Arc::new(server_config);
48+
49+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
50+
let addr = listener.local_addr().unwrap();
51+
let server = tokio::spawn(async move {
52+
let (socket, _) = listener.accept().await.unwrap();
53+
let running = server::run_stream(server_config, socket, RemainingMethodsUserSwitchServer)
54+
.await
55+
.unwrap();
56+
running.await
57+
});
58+
59+
let mut session = client::connect(
60+
Arc::new(client::Config::default()),
61+
addr,
62+
AcceptTestServerKey,
63+
)
64+
.await
65+
.unwrap();
66+
67+
let alice = session.authenticate_none("alice").await.unwrap();
68+
assert!(
69+
matches!(
70+
alice,
71+
client::AuthResult::Failure {
72+
ref remaining_methods,
73+
..
74+
} if *remaining_methods == MethodSet::from(&[MethodKind::Password][..])
75+
),
76+
"unexpected Alice auth result: {alice:?}"
77+
);
78+
79+
let bob = session.authenticate_none("bob").await.unwrap();
80+
if let client::AuthResult::Failure {
81+
remaining_methods, ..
82+
} = bob
83+
{
84+
assert!(
85+
remaining_methods.contains(&MethodKind::PublicKey),
86+
"server reused Alice's narrowed remaining methods for Bob: {remaining_methods:?}"
87+
);
88+
}
89+
90+
server.abort();
91+
}

0 commit comments

Comments
 (0)