-
Notifications
You must be signed in to change notification settings - Fork 35
/
pass.rs
181 lines (173 loc) · 8.79 KB
/
pass.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! The RFC 959 Password (`PASS`) command
//
// The argument field is a Telnet string specifying the user's
// password. This command must be immediately preceded by the
// user name command, and, for some sites, completes the user's
// identification for access control. Since password
// information is quite sensitive, it is desirable in general
// to "mask" it or suppress typeout. It appears that the
// server has no foolproof way to achieve this. It is
// therefore the responsibility of the user-FTP process to hide
// the sensitive password information.
use crate::server::failed_logins::LockState;
use crate::{
auth::UserDetail,
server::{
chancomms::ControlChanMsg,
controlchan::{
error::ControlChanError,
handler::{CommandContext, CommandHandler},
Reply, ReplyCode,
},
password,
session::SessionState,
},
storage::{Metadata, StorageBackend},
};
use async_trait::async_trait;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::Sender;
use tokio::time::sleep;
#[derive(Debug)]
pub struct Pass {
password: password::Password,
}
impl Pass {
pub fn new(password: password::Password) -> Self {
Pass { password }
}
}
#[async_trait]
impl<Storage, User> CommandHandler<Storage, User> for Pass
where
User: UserDetail + 'static,
Storage: StorageBackend<User> + 'static,
Storage::Metadata: Metadata,
{
#[tracing_attributes::instrument]
async fn handle(&self, args: CommandContext<Storage, User>) -> Result<Reply, ControlChanError> {
let session = args.session.lock().await;
let logger = args.logger;
match &session.state {
SessionState::WaitPass => {
let pass: &str = std::str::from_utf8(self.password.as_ref())?;
let pass: String = pass.to_string();
let username: String = match session.username.clone() {
Some(v) => v,
None => {
slog::error!(logger, "NoneError for username. This shouldn't happen.");
return Ok(Reply::new(ReplyCode::NotLoggedIn, "Please open a new connection to re-authenticate"));
}
};
let tx: Sender<ControlChanMsg> = args.tx_control_chan.clone();
let auther = args.authenticator.clone();
// without this, the REST authenticator hangs when
// performing a http call through Hyper
let session2clone = args.session.clone();
let creds = crate::auth::Credentials {
password: Some(pass),
source_ip: session.source.ip(),
certificate_chain: session.cert_chain.clone(),
};
let failed_logins = session.failed_logins.clone();
let source_ip = session.source.ip();
tokio::spawn(async move {
let msg = match auther.authenticate(&username, &creds).await {
Ok(user) => {
let is_locked = match failed_logins {
Some(failed_logins) => {
let result = failed_logins.success(source_ip, username.clone()).await;
if let Some(state) = result {
slog::warn!(
logger,
"PASS: User authenticated but currently locked out due to previous failed login attempts according to the policy! (Username={}. Note: the account automatically unlocks after the configured period if no further failed login attempts occur. state={:?})",
username,
state
);
true
} else {
false
}
}
None => false,
};
if is_locked {
sleep(Duration::from_millis(1500)).await;
ControlChanMsg::AuthFailed
} else if user.account_enabled() {
let mut session = session2clone.lock().await;
// Using Arc::get_mut means that this won't work if the Session is
// currently servicing multiple commands concurrently. But it
// shouldn't ever be servicing PASS at the same time as another
// command.
match Arc::get_mut(&mut session.storage).map(|s| s.enter(&user)) {
Some(Err(e)) => {
slog::error!(logger, "{}", e);
ControlChanMsg::AuthFailed
}
None => {
slog::error!(logger, "Failed to lock Session::storage during PASS.");
ControlChanMsg::AuthFailed
}
Some(Ok(())) => {
slog::info!(logger, "PASS: User {} logged in", user);
session.user = Arc::new(Some(user));
ControlChanMsg::AuthSuccess {
username,
trace_id: session.trace_id,
}
}
}
} else {
slog::warn!(logger, "PASS: User {} authenticated but account is disabled", user);
ControlChanMsg::AuthFailed
}
}
Err(crate::auth::AuthenticationError::BadUser) => {
slog::warn!(logger, "PASS: Login attempt for unknown user {}", username);
ControlChanMsg::AuthFailed
}
Err(err) => {
slog::warn!(logger, "PASS: Failed login attempt for user {}, reason={}", username, err);
if let Some(failed_logins) = failed_logins {
let result = failed_logins.failed(source_ip, username.clone()).await;
if let Some(state) = result {
match state {
LockState::MaxFailuresReached => {
slog::warn!(
logger,
"PASS: Maximum number bad login attempts reached according to the policy so the locking policy is now active (Username={}, IP={}, LockState={:?})",
username,
source_ip,
state
);
}
LockState::AlreadyLocked => {
slog::info!(
logger,
"PASS: Another bad login attempt but the locking policy is already active (Username={}, IP={}, LockState={:?})",
username,
source_ip,
state
);
}
}
}
}
ControlChanMsg::AuthFailed
}
};
tokio::spawn(async move {
if let Err(err) = tx.send(msg).await {
slog::warn!(logger, "PASS: Could not send internal message: {}", err);
}
});
});
Ok(Reply::none())
}
SessionState::New => Ok(Reply::new(ReplyCode::BadCommandSequence, "Please supply a username first")),
_ => Ok(Reply::new(ReplyCode::NotLoggedIn, "Please open a new connection to re-authenticate")),
}
}
}