mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
2fa for unattended access
This commit is contained in:
@@ -67,7 +67,7 @@ use std::collections::HashSet;
|
||||
pub type Sender = mpsc::UnboundedSender<(Instant, Arc<Message>)>;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref LOGIN_FAILURES: Arc::<Mutex<HashMap<String, (i32, i32, i32)>>> = Default::default();
|
||||
static ref LOGIN_FAILURES: [Arc::<Mutex<HashMap<String, (i32, i32, i32)>>>; 2] = Default::default();
|
||||
static ref SESSIONS: Arc::<Mutex<HashMap<String, Session>>> = Default::default();
|
||||
static ref ALIVE_CONNS: Arc::<Mutex<Vec<i32>>> = Default::default();
|
||||
static ref AUTHED_CONNS: Arc::<Mutex<Vec<(i32, AuthConnType)>>> = Default::default();
|
||||
@@ -150,6 +150,7 @@ struct Session {
|
||||
session_id: u64,
|
||||
last_recv_time: Arc<Mutex<Instant>>,
|
||||
random_password: String,
|
||||
tfa: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
@@ -181,6 +182,7 @@ pub struct Connection {
|
||||
port_forward_address: String,
|
||||
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
|
||||
authorized: bool,
|
||||
require_2fa: Option<totp_rs::TOTP>,
|
||||
keyboard: bool,
|
||||
clipboard: bool,
|
||||
audio: bool,
|
||||
@@ -317,6 +319,7 @@ impl Connection {
|
||||
tx: Some(tx),
|
||||
tx_video: Some(tx_video),
|
||||
},
|
||||
require_2fa: crate::auth_2fa::get_2fa(None),
|
||||
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
|
||||
stream,
|
||||
server,
|
||||
@@ -434,6 +437,7 @@ impl Connection {
|
||||
Some(data) = rx_from_cm.recv() => {
|
||||
match data {
|
||||
ipc::Data::Authorize => {
|
||||
conn.require_2fa.take();
|
||||
conn.send_logon_response().await;
|
||||
if conn.port_forward_socket.is_some() {
|
||||
break;
|
||||
@@ -1029,6 +1033,10 @@ impl Connection {
|
||||
if self.authorized {
|
||||
return;
|
||||
}
|
||||
if self.require_2fa.is_some() && !self.is_recent_session(true) {
|
||||
self.send_login_error(crate::client::REQUIRE_2FA).await;
|
||||
return;
|
||||
}
|
||||
let (conn_type, auth_conn_type) = if self.file_transfer.is_some() {
|
||||
(1, AuthConnType::FileTransfer)
|
||||
} else if self.port_forward_socket.is_some() {
|
||||
@@ -1402,6 +1410,7 @@ impl Connection {
|
||||
session_id: self.lr.session_id,
|
||||
last_recv_time: self.last_recv_time.clone(),
|
||||
random_password: password,
|
||||
tfa: false,
|
||||
},
|
||||
);
|
||||
return true;
|
||||
@@ -1415,7 +1424,7 @@ impl Connection {
|
||||
false
|
||||
}
|
||||
|
||||
fn is_recent_session(&mut self) -> bool {
|
||||
fn is_recent_session(&mut self, tfa: bool) -> bool {
|
||||
SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
@@ -1426,21 +1435,18 @@ impl Connection {
|
||||
.get(&self.lr.my_id)
|
||||
.map(|s| s.to_owned());
|
||||
// last_recv_time is a mutex variable shared with connection, can be updated lively.
|
||||
if let Some(session) = session {
|
||||
if let Some(mut session) = session {
|
||||
if session.name == self.lr.my_name
|
||||
&& session.session_id == self.lr.session_id
|
||||
&& !self.lr.password.is_empty()
|
||||
&& self.validate_one_password(session.random_password.clone())
|
||||
&& (tfa && session.tfa
|
||||
|| !tfa && self.validate_one_password(session.random_password.clone()))
|
||||
{
|
||||
SESSIONS.lock().unwrap().insert(
|
||||
self.lr.my_id.clone(),
|
||||
Session {
|
||||
name: self.lr.my_name.clone(),
|
||||
session_id: self.lr.session_id,
|
||||
last_recv_time: self.last_recv_time.clone(),
|
||||
random_password: session.random_password,
|
||||
},
|
||||
);
|
||||
session.last_recv_time = self.last_recv_time.clone();
|
||||
SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.lr.my_id.clone(), session);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1614,7 +1620,7 @@ impl Connection {
|
||||
{
|
||||
self.send_login_error("Connection not allowed").await;
|
||||
return false;
|
||||
} else if self.is_recent_session() {
|
||||
} else if self.is_recent_session(false) {
|
||||
if err_msg.is_empty() {
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
@@ -1637,47 +1643,12 @@ impl Connection {
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
let mut failure = LOGIN_FAILURES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.ip)
|
||||
.map(|x| x.clone())
|
||||
.unwrap_or((0, 0, 0));
|
||||
let time = (get_time() / 60_000) as i32;
|
||||
if failure.2 > 30 {
|
||||
self.send_login_error("Too many wrong password attempts")
|
||||
.await;
|
||||
Self::post_alarm_audit(
|
||||
AlarmAuditType::ExceedThirtyAttempts,
|
||||
json!({
|
||||
"ip":self.ip,
|
||||
"id":lr.my_id.clone(),
|
||||
"name": lr.my_name.clone(),
|
||||
}),
|
||||
);
|
||||
} else if time == failure.0 && failure.1 > 6 {
|
||||
self.send_login_error("Please try 1 minute later").await;
|
||||
Self::post_alarm_audit(
|
||||
AlarmAuditType::SixAttemptsWithinOneMinute,
|
||||
json!({
|
||||
"ip":self.ip,
|
||||
"id":lr.my_id.clone(),
|
||||
"name": lr.my_name.clone(),
|
||||
}),
|
||||
);
|
||||
} else if !self.validate_password() {
|
||||
if failure.0 == time {
|
||||
failure.1 += 1;
|
||||
failure.2 += 1;
|
||||
} else {
|
||||
failure.0 = time;
|
||||
failure.1 = 1;
|
||||
failure.2 += 1;
|
||||
}
|
||||
LOGIN_FAILURES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.ip.clone(), failure);
|
||||
let (failure, res) = self.check_failure(0).await;
|
||||
if !res {
|
||||
return true;
|
||||
}
|
||||
if !self.validate_password() {
|
||||
self.update_failure(failure, false, 0);
|
||||
if err_msg.is_empty() {
|
||||
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
|
||||
.await;
|
||||
@@ -1689,9 +1660,7 @@ impl Connection {
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
if failure.0 != 0 {
|
||||
LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
|
||||
}
|
||||
self.update_failure(failure, true, 0);
|
||||
if err_msg.is_empty() {
|
||||
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
|
||||
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
|
||||
@@ -1706,6 +1675,47 @@ impl Connection {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(message::Union::Auth2fa(tfa)) = msg.union {
|
||||
let (failure, res) = self.check_failure(1).await;
|
||||
if !res {
|
||||
return true;
|
||||
}
|
||||
if let Some(totp) = self.require_2fa.as_ref() {
|
||||
if let Ok(code) = totp.generate_current() {
|
||||
if tfa.code == code {
|
||||
self.update_failure(failure, true, 1);
|
||||
self.require_2fa.take();
|
||||
self.send_logon_response().await;
|
||||
let session = SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.lr.my_id)
|
||||
.map(|s| s.to_owned());
|
||||
if let Some(mut session) = session {
|
||||
session.tfa = true;
|
||||
SESSIONS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.lr.my_id.clone(), session);
|
||||
} else {
|
||||
SESSIONS.lock().unwrap().insert(
|
||||
self.lr.my_id.clone(),
|
||||
Session {
|
||||
name: self.lr.my_name.clone(),
|
||||
session_id: self.lr.session_id,
|
||||
last_recv_time: self.last_recv_time.clone(),
|
||||
random_password: "".to_owned(),
|
||||
tfa: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
self.update_failure(failure, false, 1);
|
||||
self.send_login_error(crate::client::LOGIN_MSG_2FA_WRONG)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(message::Union::TestDelay(t)) = msg.union {
|
||||
if t.from_client {
|
||||
let mut msg_out = Message::new();
|
||||
@@ -2245,6 +2255,63 @@ impl Connection {
|
||||
true
|
||||
}
|
||||
|
||||
fn update_failure(&self, (mut failure, time): ((i32, i32, i32), i32), remove: bool, i: usize) {
|
||||
if remove {
|
||||
if failure.0 != 0 {
|
||||
LOGIN_FAILURES[i].lock().unwrap().remove(&self.ip);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if failure.0 == time {
|
||||
failure.1 += 1;
|
||||
failure.2 += 1;
|
||||
} else {
|
||||
failure.0 = time;
|
||||
failure.1 = 1;
|
||||
failure.2 += 1;
|
||||
}
|
||||
LOGIN_FAILURES[i]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.insert(self.ip.clone(), failure);
|
||||
}
|
||||
|
||||
async fn check_failure(&mut self, i: usize) -> (((i32, i32, i32), i32), bool) {
|
||||
let failure = LOGIN_FAILURES[i]
|
||||
.lock()
|
||||
.unwrap()
|
||||
.get(&self.ip)
|
||||
.map(|x| x.clone())
|
||||
.unwrap_or((0, 0, 0));
|
||||
let time = (get_time() / 60_000) as i32;
|
||||
let res = if failure.2 > 30 {
|
||||
self.send_login_error("Too many wrong attempts").await;
|
||||
Self::post_alarm_audit(
|
||||
AlarmAuditType::ExceedThirtyAttempts,
|
||||
json!({
|
||||
"ip": self.ip,
|
||||
"id": self.lr.my_id.clone(),
|
||||
"name": self.lr.my_name.clone(),
|
||||
}),
|
||||
);
|
||||
false
|
||||
} else if time == failure.0 && failure.1 > 6 {
|
||||
self.send_login_error("Please try 1 minute later").await;
|
||||
Self::post_alarm_audit(
|
||||
AlarmAuditType::SixAttemptsWithinOneMinute,
|
||||
json!({
|
||||
"ip": self.ip,
|
||||
"id": self.lr.my_id.clone(),
|
||||
"name": self.lr.my_name.clone(),
|
||||
}),
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
((failure, time), res)
|
||||
}
|
||||
|
||||
fn refresh_video_display(&self, display: Option<usize>) {
|
||||
video_service::refresh();
|
||||
self.server.upgrade().map(|s| {
|
||||
|
||||
Reference in New Issue
Block a user