Compare commits

...

14 Commits

Author SHA1 Message Date
VenusGirl❤
9e4b7fca4d Update Korean (#14644) 2026-03-31 21:34:35 +08:00
XLion
d135c58ead Update tw.rs (#14643) 2026-03-31 21:26:00 +08:00
Mr-Update
de194417d4 Update de.rs (#14640) 2026-03-31 21:25:05 +08:00
solokot
d01ce3173f Update ru.rs (#14636) 2026-03-30 22:37:35 +08:00
bilimiyorum
010a54d1c9 Update tr.rs (#14628)
New string entries
2026-03-29 23:02:53 +08:00
bovirus
f557fc94fa Italian language update (#14626) 2026-03-28 13:02:09 +08:00
21pages
f02cd9c0f6 Fix Windows session-based logon and lock-screen detection (#14620)
* Fix Windows session-based logon and lock-screen detection

  - scope LogonUI and locked-state checks to the current Windows session
  - allow permanent password fallback for logon and lock-screen access

Signed-off-by: 21pages <sunboeasy@gmail.com>

* Log permanent-password fallback on logon screen

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
2026-03-27 13:22:16 +08:00
fufesou
170516572e refact(password): Store permanent password as hashed verifier (#14619)
* refact(password): Store permanent password as hashed verifier

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(password): remove unused code

Signed-off-by: fufesou <linlong1266@gmail.com>

* fix(password): mobile, password dialog, width 500

Signed-off-by: fufesou <linlong1266@gmail.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-26 14:49:54 +08:00
fufesou
285e29d2dc fix(shell): check kv in update_install_option (#14564)
Signed-off-by: fufesou <linlong1266@gmail.com>
2026-03-26 12:08:29 +08:00
rustdesk
aab34b2338 remove winget 2026-03-25 16:36:35 +08:00
rustdesk
ad1e5330e9 update hbb_common 2026-03-24 20:39:44 +08:00
bovirus
ca4647ddd6 Italian language update (#14598) 2026-03-23 13:48:34 +08:00
Mr-Update
7004acae46 Update de.rs (#14572) 2026-03-21 16:18:56 +08:00
solokot
899dd46f5b Update ru.rs (#14570) 2026-03-21 16:18:39 +08:00
67 changed files with 601 additions and 232 deletions

View File

@@ -1,15 +0,0 @@
name: Publish to WinGet
on:
release:
types: [released]
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: vedantmgoyal9/winget-releaser@main
with:
identifier: RustDesk.RustDesk
version: "1.4.6"
release-tag: "1.4.6"
token: ${{ secrets.WINGET_TOKEN }}

View File

@@ -2377,8 +2377,9 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
final password = uri.path.substring("/".length);
if (password.isNotEmpty) {
Timer(Duration(seconds: 1), () async {
await bind.mainSetPermanentPassword(password: password);
showToast(translate('Successful'));
final ok =
await bind.mainSetPermanentPasswordWithResult(password: password);
showToast(translate(ok ? 'Successful' : 'Failed'));
});
}
}

View File

@@ -908,12 +908,17 @@ class _DesktopHomePageState extends State<DesktopHomePage>
}
void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw);
final p0 = TextEditingController(text: "");
final p1 = TextEditingController(text: "");
var errMsg0 = "";
var errMsg1 = "";
final RxString rxPass = pw.trim().obs;
final localPasswordSet =
(await bind.mainGetCommon(key: "local-permanent-password-set")) == "true";
final permanentPasswordSet =
(await bind.mainGetCommon(key: "permanent-password-set")) == "true";
final presetPassword = permanentPasswordSet && !localPasswordSet;
var canSubmit = false;
final RxString rxPass = "".obs;
final rules = [
DigitValidationRule(),
UppercaseValidationRule(),
@@ -922,9 +927,21 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
MinCharactersValidationRule(8),
];
final maxLength = bind.mainMaxEncryptLen();
final statusTip = localPasswordSet
? translate('password-hidden-tip')
: (presetPassword ? translate('preset-password-in-use-tip') : '');
final showStatusTipOnMobile =
statusTip.isNotEmpty && !isDesktop && !isWebDesktop;
gFFI.dialogManager.show((setState, close, context) {
submit() {
updateCanSubmit() {
canSubmit = p0.text.trim().isNotEmpty || p1.text.trim().isNotEmpty;
}
submit() async {
if (!canSubmit) {
return;
}
setState(() {
errMsg0 = "";
errMsg1 = "";
@@ -947,7 +964,13 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
});
return;
}
bind.mainSetPermanentPassword(password: pass);
final ok = await bind.mainSetPermanentPasswordWithResult(password: pass);
if (!ok) {
setState(() {
errMsg0 = '${translate('Prompt')}: ${translate("Failed")}';
});
return;
}
if (pass.isNotEmpty) {
notEmptyCallback?.call();
}
@@ -955,14 +978,20 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
}
return CustomAlertDialog(
title: Text(translate("Set Password")),
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.key, color: MyTheme.accent),
Text(translate("Set Password")).paddingOnly(left: 10),
],
),
content: ConstrainedBox(
constraints: const BoxConstraints(minWidth: 500),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 8.0,
SizedBox(
height: showStatusTipOnMobile ? 0.0 : 6.0,
),
Row(
children: [
@@ -978,6 +1007,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
rxPass.value = value.trim();
setState(() {
errMsg0 = '';
updateCanSubmit();
});
},
maxLength: maxLength,
@@ -989,9 +1019,9 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
children: [
Expanded(child: PasswordStrengthIndicator(password: rxPass)),
],
).marginSymmetric(vertical: 8),
const SizedBox(
height: 8.0,
).marginOnly(top: 2, bottom: showStatusTipOnMobile ? 2 : 8),
SizedBox(
height: showStatusTipOnMobile ? 0.0 : 8.0,
),
Row(
children: [
@@ -1005,6 +1035,7 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
onChanged: (value) {
setState(() {
errMsg1 = '';
updateCanSubmit();
});
},
maxLength: maxLength,
@@ -1012,11 +1043,23 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
),
],
),
const SizedBox(
height: 8.0,
if (statusTip.isNotEmpty)
Row(
children: [
Icon(Icons.info, color: Colors.amber, size: 18)
.marginOnly(right: 6),
Expanded(
child: Text(
statusTip,
style: const TextStyle(fontSize: 13, height: 1.1),
))
],
).marginOnly(top: 6, bottom: 2),
SizedBox(
height: showStatusTipOnMobile ? 0.0 : 8.0,
),
Obx(() => Wrap(
runSpacing: 8,
runSpacing: showStatusTipOnMobile ? 2.0 : 8.0,
spacing: 4,
children: rules.map((e) {
var checked = e.validate(rxPass.value.trim());
@@ -1036,11 +1079,67 @@ void setPasswordDialog({VoidCallback? notEmptyCallback}) async {
],
),
),
actions: [
dialogButton("Cancel", onPressed: close, isOutline: true),
dialogButton("OK", onPressed: submit),
],
onSubmit: submit,
actions: (() {
final cancelButton = dialogButton(
"Cancel",
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
);
final removeButton = dialogButton(
"Remove",
icon: Icon(Icons.delete_outline_rounded),
onPressed: () async {
setState(() {
errMsg0 = "";
errMsg1 = "";
});
final ok =
await bind.mainSetPermanentPasswordWithResult(password: "");
if (!ok) {
setState(() {
errMsg0 = '${translate('Prompt')}: ${translate("Failed")}';
});
return;
}
close();
},
buttonStyle: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Colors.red)),
);
final okButton = dialogButton(
"OK",
icon: Icon(Icons.done_rounded),
onPressed: canSubmit ? submit : null,
);
if (!isDesktop && !isWebDesktop && localPasswordSet) {
return [
Align(
alignment: Alignment.centerRight,
child: FittedBox(
fit: BoxFit.scaleDown,
alignment: Alignment.centerRight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
cancelButton,
const SizedBox(width: 4),
removeButton,
const SizedBox(width: 4),
okButton,
],
),
),
),
];
}
return [
cancelButton,
if (localPasswordSet) removeButton,
okButton,
];
})(),
onSubmit: canSubmit ? submit : null,
onCancel: close,
);
});

View File

@@ -1109,8 +1109,9 @@ class _SafetyState extends State<_Safety> with AutomaticKeepAliveClientMixin {
if (value ==
passwordValues[passwordKeys
.indexOf(kUsePermanentPassword)] &&
(await bind.mainGetPermanentPassword())
.isEmpty) {
(await bind.mainGetCommon(
key: "permanent-password-set")) !=
"true") {
if (isChangePermanentPasswordDisabled()) {
await callback();
return;

View File

@@ -150,7 +150,8 @@ class _DropDownAction extends StatelessWidget {
}
if (value == kUsePermanentPassword &&
(await bind.mainGetPermanentPassword()).isEmpty) {
(await bind.mainGetCommon(key: "permanent-password-set")) !=
"true") {
if (isChangePermanentPasswordDisabled()) {
callback();
return;

View File

@@ -12,100 +12,6 @@ void _showSuccess() {
showToast(translate("Successful"));
}
void _showError() {
showToast(translate("Error"));
}
void setPermanentPasswordDialog(OverlayDialogManager dialogManager) async {
final pw = await bind.mainGetPermanentPassword();
final p0 = TextEditingController(text: pw);
final p1 = TextEditingController(text: pw);
var validateLength = false;
var validateSame = false;
dialogManager.show((setState, close, context) {
submit() async {
close();
dialogManager.showLoading(translate("Waiting"));
if (await gFFI.serverModel.setPermanentPassword(p0.text)) {
dialogManager.dismissAll();
_showSuccess();
} else {
dialogManager.dismissAll();
_showError();
}
}
return CustomAlertDialog(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.password_rounded, color: MyTheme.accent),
Text(translate('Set your own password')).paddingOnly(left: 10),
],
),
content: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
child: Column(mainAxisSize: MainAxisSize.min, children: [
TextFormField(
autofocus: true,
obscureText: true,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
labelText: translate('Password'),
),
controller: p0,
validator: (v) {
if (v == null) return null;
final val = v.trim().length > 5;
if (validateLength != val) {
// use delay to make setState success
Future.delayed(Duration(microseconds: 1),
() => setState(() => validateLength = val));
}
return val
? null
: translate('Too short, at least 6 characters.');
},
).workaroundFreezeLinuxMint(),
TextFormField(
obscureText: true,
keyboardType: TextInputType.visiblePassword,
decoration: InputDecoration(
labelText: translate('Confirmation'),
),
controller: p1,
validator: (v) {
if (v == null) return null;
final val = p0.text == v;
if (validateSame != val) {
Future.delayed(Duration(microseconds: 1),
() => setState(() => validateSame = val));
}
return val
? null
: translate('The confirmation is not identical.');
},
).workaroundFreezeLinuxMint(),
])),
onCancel: close,
onSubmit: (validateLength && validateSame) ? submit : null,
actions: [
dialogButton(
'Cancel',
icon: Icon(Icons.close_rounded),
onPressed: close,
isOutline: true,
),
dialogButton(
'OK',
icon: Icon(Icons.done_rounded),
onPressed: (validateLength && validateSame) ? submit : null,
),
],
);
});
}
void setTemporaryPasswordLengthDialog(
OverlayDialogManager dialogManager) async {
List<String> lengths = ['6', '8', '10'];

View File

@@ -471,17 +471,6 @@ class ServerModel with ChangeNotifier {
WakelockManager.disable(_wakelockKey);
}
Future<bool> setPermanentPassword(String newPW) async {
await bind.mainSetPermanentPassword(password: newPW);
await Future.delayed(Duration(milliseconds: 500));
final pw = await bind.mainGetPermanentPassword();
if (newPW == pw) {
return true;
} else {
return false;
}
}
fetchID() async {
final id = await bind.mainGetMyId();
if (id != _serverId.id) {

View File

@@ -1159,10 +1159,6 @@ class RustdeskImpl {
return Future.value('');
}
Future<String> mainGetPermanentPassword({dynamic hint}) {
return Future.value('');
}
Future<String> mainGetFingerprint({dynamic hint}) {
return Future.value('');
}
@@ -1346,9 +1342,9 @@ class RustdeskImpl {
throw UnimplementedError("mainUpdateTemporaryPassword");
}
Future<void> mainSetPermanentPassword(
Future<bool> mainSetPermanentPasswordWithResult(
{required String password, dynamic hint}) {
throw UnimplementedError("mainSetPermanentPassword");
throw UnimplementedError("mainSetPermanentPasswordWithResult");
}
Future<bool> mainCheckSuperUserPermission({dynamic hint}) {

View File

@@ -1693,8 +1693,8 @@ pub fn main_get_temporary_password() -> String {
ui_interface::temporary_password()
}
pub fn main_get_permanent_password() -> String {
ui_interface::permanent_password()
pub fn main_set_permanent_password_with_result(password: String) -> bool {
ui_interface::set_permanent_password_with_result(password)
}
pub fn main_get_fingerprint() -> String {
@@ -2072,10 +2072,6 @@ pub fn main_update_temporary_password() {
update_temporary_password();
}
pub fn main_set_permanent_password(password: String) {
set_permanent_password(password);
}
pub fn main_check_super_user_permission() -> bool {
check_super_user_permission()
}
@@ -2423,16 +2419,23 @@ pub fn is_disable_installation() -> SyncReturn<bool> {
}
pub fn is_preset_password() -> bool {
config::HARD_SETTINGS
let hard = config::HARD_SETTINGS
.read()
.unwrap()
.get("password")
.map_or(false, |p| {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return p == &crate::ipc::get_permanent_password();
#[cfg(any(target_os = "android", target_os = "ios"))]
return p == &config::Config::get_permanent_password();
})
.cloned()
.unwrap_or_default();
if hard.is_empty() {
return false;
}
// On desktop, service owns the authoritative config; query it via IPC and return only a boolean.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return crate::ipc::is_permanent_password_preset();
// On mobile, we have no service IPC; verify against local storage.
#[cfg(any(target_os = "android", target_os = "ios"))]
return config::Config::matches_permanent_password_plain(&hard);
}
// Don't call this function for desktop version.
@@ -2768,6 +2771,10 @@ pub fn main_get_common(key: String) -> String {
return crate::platform::linux::has_gnome_shortcuts_inhibitor_permission().to_string();
#[cfg(not(target_os = "linux"))]
return false.to_string();
} else if key == "permanent-password-set" {
return ui_interface::is_permanent_password_set().to_string();
} else if key == "local-permanent-password-set" {
return ui_interface::is_local_permanent_password_set().to_string();
} else {
if key.starts_with("download-data-") {
let id = key.replace("download-data-", "");

View File

@@ -632,8 +632,29 @@ async fn handle(data: Data, stream: &mut Connection) {
value = Some(Config::get_id());
} else if name == "temporary-password" {
value = Some(password::temporary_password());
} else if name == "permanent-password" {
value = Some(Config::get_permanent_password());
} else if name == "permanent-password-storage-and-salt" {
let (storage, salt) = Config::get_local_permanent_password_storage_and_salt();
value = Some(storage + "\n" + &salt);
} else if name == "permanent-password-set" {
value = Some(if Config::has_permanent_password() {
"Y".to_owned()
} else {
"N".to_owned()
});
} else if name == "permanent-password-is-preset" {
let hard = config::HARD_SETTINGS
.read()
.unwrap()
.get("password")
.cloned()
.unwrap_or_default();
let is_preset =
!hard.is_empty() && Config::matches_permanent_password_plain(&hard);
value = Some(if is_preset {
"Y".to_owned()
} else {
"N".to_owned()
});
} else if name == "salt" {
value = Some(Config::get_salt());
} else if name == "rendezvous_server" {
@@ -669,13 +690,24 @@ async fn handle(data: Data, stream: &mut Connection) {
allow_err!(stream.send(&Data::Config((name, value))).await);
}
Some(value) => {
let mut updated = true;
if name == "id" {
Config::set_key_confirmed(false);
Config::set_id(&value);
} else if name == "temporary-password" {
password::update_temporary_password();
} else if name == "permanent-password" {
Config::set_permanent_password(&value);
if Config::is_disable_change_permanent_password() {
log::warn!("Changing permanent password is disabled");
updated = false;
} else {
Config::set_permanent_password(&value);
}
// Explicitly ACK/NACK permanent-password writes. This allows UIs/FFI to
// distinguish "accepted by daemon" vs "IPC send succeeded" without
// reading back any secret.
let ack = if updated { "Y" } else { "N" }.to_owned();
allow_err!(stream.send(&Data::Config((name.clone(), Some(ack)))).await);
} else if name == "salt" {
Config::set_salt(&value);
} else if name == "voice-call-input" {
@@ -685,7 +717,9 @@ async fn handle(data: Data, stream: &mut Connection) {
} else {
return;
}
log::info!("{} updated", name);
if updated {
log::info!("{} updated", name);
}
}
},
Data::Options(value) => match value {
@@ -1143,13 +1177,57 @@ pub fn update_temporary_password() -> ResultType<()> {
set_config("temporary-password", "".to_owned())
}
pub fn get_permanent_password() -> String {
if let Ok(Some(v)) = get_config("permanent-password") {
Config::set_permanent_password(&v);
v
} else {
Config::get_permanent_password()
fn apply_permanent_password_storage_and_salt_payload(payload: Option<&str>) -> ResultType<()> {
let Some(payload) = payload else {
return Ok(());
};
let Some((storage, salt)) = payload.split_once('\n') else {
bail!("Invalid permanent-password-storage-and-salt payload");
};
if storage.is_empty() {
Config::set_permanent_password_storage_for_sync("", "")?;
return Ok(());
}
Config::set_permanent_password_storage_for_sync(storage, salt)?;
Ok(())
}
pub fn sync_permanent_password_storage_from_daemon() -> ResultType<()> {
let v = get_config("permanent-password-storage-and-salt")?;
apply_permanent_password_storage_and_salt_payload(v.as_deref())
}
async fn sync_permanent_password_storage_from_daemon_async() -> ResultType<()> {
let ms_timeout = 1_000;
let v = get_config_async("permanent-password-storage-and-salt", ms_timeout).await?;
apply_permanent_password_storage_and_salt_payload(v.as_deref())
}
pub fn is_permanent_password_set() -> bool {
match get_config("permanent-password-set") {
Ok(Some(v)) => {
let v = v.trim();
return v == "Y";
}
Ok(None) => {
// No response/value (timeout).
}
Err(_) => {
// Connection error.
}
}
log::warn!("Failed to query permanent password state from daemon");
false
}
pub fn is_permanent_password_preset() -> bool {
if let Ok(Some(v)) = get_config("permanent-password-is-preset") {
let v = v.trim();
return v == "Y";
}
false
}
pub fn get_fingerprint() -> String {
@@ -1159,8 +1237,41 @@ pub fn get_fingerprint() -> String {
}
pub fn set_permanent_password(v: String) -> ResultType<()> {
Config::set_permanent_password(&v);
set_config("permanent-password", v)
if Config::is_disable_change_permanent_password() {
bail!("Changing permanent password is disabled");
}
if set_permanent_password_with_ack(v)? {
Ok(())
} else {
bail!("Changing permanent password was rejected by daemon");
}
}
#[tokio::main(flavor = "current_thread")]
pub async fn set_permanent_password_with_ack(v: String) -> ResultType<bool> {
set_permanent_password_with_ack_async(v).await
}
async fn set_permanent_password_with_ack_async(v: String) -> ResultType<bool> {
// The daemon ACK/NACK is expected quickly since it applies the config in-process.
let ms_timeout = 1_000;
let mut c = connect(ms_timeout, "").await?;
c.send_config("permanent-password", v).await?;
if let Some(Data::Config((name2, Some(v)))) = c.next_timeout(ms_timeout).await? {
if name2 == "permanent-password" {
let v = v.trim();
let ok = v == "Y";
if ok {
// Ensure the hashed permanent password storage is written to the user config file.
// This sync must not affect the daemon ACK outcome.
if let Err(err) = sync_permanent_password_storage_from_daemon_async().await {
log::warn!("Failed to sync permanent password storage from daemon: {err}");
}
}
return Ok(ok);
}
}
Ok(false)
}
#[cfg(feature = "flutter")]

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "متابعة مع {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Працягнуць з {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Продължи с {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Continua amb {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "传入会话期间保持屏幕常亮"),
("Continue with {}", "使用 {} 登录"),
("Display Name", "显示名称"),
("password-hidden-tip", "永久密码已设置(已隐藏)"),
("preset-password-in-use-tip", "当前使用预设密码"),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Pokračovat s {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Fortsæt med {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -379,8 +379,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Share", "Bildschirmfreigabe"),
("ubuntu-21-04-required", "Wayland erfordert Ubuntu 21.04 oder eine höhere Version."),
("wayland-requires-higher-linux-version", "Wayland erfordert eine höhere Version der Linux-Distribution. Bitte versuchen Sie den X11-Desktop oder ändern Sie Ihr Betriebssystem."),
("xdp-portal-unavailable", ""),
("JumpLink", "View"),
("xdp-portal-unavailable", "Die Bildschirmaufnahme mit Wayland ist fehlgeschlagen. Das XDG-Desktop-Portal ist möglicherweise abgestürzt oder nicht verfügbar. Versuchen Sie, es mit `systemctl --user restart xdg-desktop-portal` neu zu starten."),
("JumpLink", "Anzeigen"),
("Please Select the screen to be shared(Operate on the peer side).", "Bitte wählen Sie den freizugebenden Bildschirm aus (Bedienung auf der Gegenseite)."),
("Show RustDesk", "RustDesk anzeigen"),
("This PC", "Dieser PC"),
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Bildschirm während eingehender Sitzungen aktiv halten"),
("Continue with {}", "Fortfahren mit {}"),
("Display Name", "Anzeigename"),
("password-hidden-tip", "Ein permanentes Passwort wurde festgelegt (ausgeblendet)."),
("preset-password-in-use-tip", "Das voreingestellte Passwort wird derzeit verwendet."),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Διατήρηση ενεργής οθόνης κατά τη διάρκεια των εισερχόμενων συνεδριών"),
("Continue with {}", "Συνέχεια με {}"),
("Display Name", "Εμφανιζόμενο όνομα"),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -272,5 +272,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("rel-mouse-permission-lost-tip", "Keyboard permission was revoked. Relative Mouse Mode has been disabled."),
("keep-awake-during-outgoing-sessions-label", "Keep screen awake during outgoing sessions"),
("keep-awake-during-incoming-sessions-label", "Keep screen awake during incoming sessions"),
("password-hidden-tip", "Permanent password is set (hidden)."),
("preset-password-in-use-tip", "Preset password is currently in use."),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", ""),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Continuar con {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Jätka koos {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "{} honekin jarraitu"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "ادامه با {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Jatka käyttäen {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Maintenir lécran allumé lors des sessions entrantes"),
("Continue with {}", "Continuer avec {}"),
("Display Name", "Nom daffichage"),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "{}-ით გაგრძელება"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "המשך עם {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Nastavi sa {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Képernyő aktív állapotban tartása a bejövő munkamenetek során"),
("Continue with {}", "Folytatás ezzel: {}"),
("Display Name", "Kijelző név"),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Lanjutkan dengan {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -379,7 +379,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Share", "Condivisione schermo"),
("ubuntu-21-04-required", "Wayland richiede Ubuntu 21.04 o versione successiva."),
("wayland-requires-higher-linux-version", "Wayland richiede una versione superiore della distribuzione Linux.\nProva X11 desktop o cambia il sistema operativo."),
("xdp-portal-unavailable", ""),
("xdp-portal-unavailable", "Acquisizione dello schermo di Wayland non riuscita. Il portale desktop XDG potrebbe essersi bloccato o non essere disponibile. Prova a riavviarlo con `systemctl --user restart xdg-desktop-portal`."),
("JumpLink", "Vai a"),
("Please Select the screen to be shared(Operate on the peer side).", "Seleziona lo schermo da condividere (opera sul lato dispositivo remoto)."),
("Show RustDesk", "Visualizza RustDesk"),
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Mantieni lo schermo attivo durante le sessioni in ingresso"),
("Continue with {}", "Continua con {}"),
("Display Name", "Visualizza nome"),
("password-hidden-tip", "È impostata una password permanente (nascosta)."),
("preset-password-in-use-tip", "È attualmente in uso la password preimpostata."),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "{} で続行"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "수신 세션 중 화면 켜짐 유지"),
("Continue with {}", "{}(으)로 계속"),
("Display Name", "표시 이름"),
("password-hidden-tip", "영구 비밀번호가 설정되었습니다 (숨김)."),
("preset-password-in-use-tip", "현재 사전 설정된 비밀번호가 사용 중입니다."),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", ""),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Tęsti su {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Turpināt ar {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Fortsett med {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Houd het scherm open tijdens de inkomende sessies."),
("Continue with {}", "Ga verder met {}"),
("Display Name", "Naam Weergeven"),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Utrzymuj urządzenie w stanie aktywnym podczas sesji przychodzących"),
("Continue with {}", "Kontynuuj z {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", ""),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Manter tela ativa durante sessões de entrada"),
("Continue with {}", "Continuar com {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Continuă cu {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -379,7 +379,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Screen Share", "Демонстрация экрана"),
("ubuntu-21-04-required", "Wayland требуется Ubuntu версии 21.04 или новее."),
("wayland-requires-higher-linux-version", "Для Wayland требуется более поздняя версия дистрибутива Linux. Используйте рабочий стол X11 или смените ОС."),
("xdp-portal-unavailable", ""),
("xdp-portal-unavailable", "Невозможно сделать снимок экрана Wayland. Возможно, в XDG Desktop Portal сбой или он недоступен. Попробуйте перезапустить его с помощью `systemctl --user restart xdg-desktop-portal`."),
("JumpLink", "Просмотр"),
("Please Select the screen to be shared(Operate on the peer side).", "Выберите экран для демонстрации (работайте на одноранговой стороне)."),
("Show RustDesk", "Показать RustDesk"),
@@ -666,7 +666,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Incoming Print Job", "Входящее задание печати"),
("use-the-default-printer-tip", "Использовать принтер по умолчанию"),
("use-the-selected-printer-tip", "Использовать выбранный принтер"),
("auto-print-tip", "Автоматически выполнять печать на выбранном принтере."),
("auto-print-tip", "Автоматически выполнять печать на выбранном принтере"),
("print-incoming-job-confirm-tip", "Получено задание на печать с удалённого устройства. Выполнить его локально?"),
("remote-printing-disallowed-tile-tip", "Удалённая печать запрещена"),
("remote-printing-disallowed-text-tip", "Настройки разрешений на управляемой стороне запрещают удалённую печать."),
@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Не отключать экран во время входящих сеансов"),
("Continue with {}", "Продолжить с {}"),
("Display Name", "Отображаемое имя"),
("password-hidden-tip", "Установлен постоянный пароль (скрытый)."),
("preset-password-in-use-tip", "Установленный пароль сейчас используется."),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Sighi cun {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Pokračovať s {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Nadaljuj z {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Vazhdo me {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Nastavi sa {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Fortsätt med {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "{} உடன் தொடர்"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", ""),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "ทำต่อด้วย {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", "Gelen oturumlar süresince ekranıık tutun"),
("Continue with {}", "{} ile devam et"),
("Display Name", "Görünen Ad"),
("password-hidden-tip", "Şifre gizli"),
("preset-password-in-use-tip", "Önceden ayarlanmış şifre kullanılıyor"),
].iter().cloned().collect();
}

View File

@@ -740,6 +740,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-outgoing-sessions-label", "在連出工作階段期間保持螢幕喚醒"),
("keep-awake-during-incoming-sessions-label", "在連入工作階段期間保持螢幕喚醒"),
("Continue with {}", "使用 {} 登入"),
("Display Name", ""),
("Display Name", "顯示名稱"),
("password-hidden-tip", "固定密碼已設定(已隱藏)"),
("preset-password-in-use-tip", "目前正在使用預設密碼"),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Продовжити з {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -741,5 +741,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("keep-awake-during-incoming-sessions-label", ""),
("Continue with {}", "Tiếp tục với {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

View File

@@ -580,9 +580,8 @@ extern "C"
return rdp_or_console;
}
BOOL is_session_locked(BOOL include_rdp)
BOOL is_session_locked(DWORD session_id)
{
DWORD session_id = get_current_session(include_rdp);
if (session_id == 0xFFFFFFFF) {
return FALSE;
}

View File

@@ -523,7 +523,7 @@ const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS;
extern "C" {
fn get_current_session(rdp: BOOL) -> DWORD;
fn is_session_locked(include_rdp: BOOL) -> BOOL;
fn is_session_locked(session_id: DWORD) -> BOOL;
fn LaunchProcessWin(
cmd: *const u16,
session_id: DWORD,
@@ -1149,20 +1149,21 @@ pub fn is_prelogin() -> bool {
}
pub fn is_locked() -> bool {
unsafe { is_session_locked(share_rdp()) == TRUE }
let Some(session_id) = get_current_process_session_id() else {
return false;
};
unsafe { is_session_locked(session_id) == TRUE }
}
// `is_logon_ui()` is regardless of multiple sessions now.
// It only check if "LogonUI.exe" exists.
//
// If there're mulitple sessions (logged in users),
// some are in the login screen, while the others are not.
// Then this function may not work fine if the session we want to handle(connect) is not in the login screen.
// But it's a rare case and cannot be simply handled, so it will not be dealt with for the time being.
#[inline]
pub fn is_logon_ui() -> ResultType<bool> {
let Some(current_sid) = get_current_process_session_id() else {
return Ok(false);
};
let pids = get_pids("LogonUI.exe")?;
Ok(!pids.is_empty())
Ok(pids
.into_iter()
.any(|pid| get_session_id_of_process(pid) == Some(current_sid)))
}
pub fn is_root() -> bool {
@@ -2029,6 +2030,9 @@ pub fn update_install_option(k: &str, v: &str) -> ResultType<()> {
if !is_installed() || !crate::is_server() {
return Ok(());
}
if ![REG_NAME_INSTALL_PRINTER].contains(&k) || !["0", "1"].contains(&v) {
return Ok(());
}
let app_name = crate::get_app_name();
let ext = app_name.to_lowercase();
let cmds =

View File

@@ -27,6 +27,7 @@ use hbb_common::platform::linux::run_cmds;
#[cfg(target_os = "android")]
use hbb_common::protobuf::EnumOrUnknown;
use hbb_common::{
config::decode_permanent_password_h1_from_storage,
config::{self, keys, Config, TrustedDevice},
fs::{self, can_enable_overwrite_detection, JobType},
futures::{SinkExt, StreamExt},
@@ -77,6 +78,18 @@ lazy_static::lazy_static! {
static ref WAKELOCK_KEEP_AWAKE_OPTION: Arc::<Mutex<Option<bool>>> = Default::default();
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
// Avoid data-dependent early exits.
let mut x: u8 = 0;
for i in 0..a.len() {
x |= a[i] ^ b[i];
}
x == 0
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
lazy_static::lazy_static! {
static ref WALLPAPER_REMOVER: Arc<Mutex<Option<WallPaperRemover>>> = Default::default();
@@ -1969,23 +1982,53 @@ impl Connection {
self.tx_input.send(MessageInput::Key((msg, press))).ok();
}
fn validate_one_password(&self, password: String) -> bool {
if password.len() == 0 {
return false;
}
let mut hasher = Sha256::new();
hasher.update(password);
hasher.update(&self.hash.salt);
fn verify_h1(&self, h1: &[u8]) -> bool {
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
hasher2.finalize()[..] == self.lr.password[..]
hasher2.update(h1);
hasher2.update(self.hash.challenge.as_bytes());
// A normal `==` on slices may short-circuit on the first mismatch, which can leak how many leading
// bytes matched via timing. In typical remote scenarios this is difficult to exploit due to network
// jitter, changing challenges, and login attempt throttling, but a constant-time comparison here is
// low-cost defensive programming.
constant_time_eq(&hasher2.finalize()[..], &self.lr.password[..])
}
fn validate_password(&mut self) -> bool {
#[inline]
fn validate_one_password(&self, password: &str) -> bool {
self.validate_password_plain(password)
}
fn validate_password_plain(&self, password: &str) -> bool {
if password.is_empty() {
return false;
}
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
hasher.update(self.hash.salt.as_bytes());
let h1_plain = hasher.finalize();
self.verify_h1(&h1_plain[..])
}
fn validate_password_storage(&self, storage: &str) -> bool {
if storage.is_empty() {
return false;
}
// Use strict decode success to detect hashed storage.
// If decode fails, treat as legacy plaintext storage for compatibility.
if let Some(h1) = decode_permanent_password_h1_from_storage(storage) {
return self.verify_h1(&h1[..]);
}
// Legacy plaintext storage path.
self.validate_password_plain(storage)
}
fn validate_password(&mut self, allow_permanent_password: bool) -> bool {
if password::temporary_enabled() {
let password = password::temporary_password();
if self.validate_one_password(password.clone()) {
if self.validate_one_password(&password) {
raii::AuthedConnID::update_or_insert_session(
self.session_key(),
Some(password),
@@ -1994,9 +2037,32 @@ impl Connection {
return true;
}
}
if password::permanent_enabled() {
if self.validate_one_password(Config::get_permanent_password()) {
return true;
if password::permanent_enabled() || allow_permanent_password {
let print_fallback = || {
if allow_permanent_password && !password::permanent_enabled() {
log::info!("Permanent password accepted via logon-screen fallback");
}
};
// Since hashed storage uses a prefix-based encoding, a hard plaintext that
// happens to look like hashed storage could be mis-detected. Validate local storage
// and hard/preset plaintext via separate paths to avoid that ambiguity.
let (local_storage, _) = Config::get_local_permanent_password_storage_and_salt();
if !local_storage.is_empty() {
if self.validate_password_storage(&local_storage) {
print_fallback();
return true;
}
} else {
let hard = config::HARD_SETTINGS
.read()
.unwrap()
.get("password")
.cloned()
.unwrap_or_default();
if !hard.is_empty() && self.validate_password_plain(&hard) {
print_fallback();
return true;
}
}
}
false
@@ -2016,7 +2082,7 @@ impl Connection {
if let Some(session) = session {
if !self.lr.password.is_empty()
&& (tfa && session.tfa
|| !tfa && self.validate_one_password(session.random_password.clone()))
|| !tfa && self.validate_password_plain(&session.random_password))
{
log::info!("is recent session");
return true;
@@ -2290,6 +2356,10 @@ impl Connection {
#[cfg(any(target_os = "android", target_os = "ios"))]
let is_logon = || crate::platform::is_prelogin();
let allow_logon_screen_password =
crate::get_builtin_option(keys::OPTION_ALLOW_LOGON_SCREEN_PASSWORD) == "Y"
&& is_logon();
if !hbb_common::is_ip_str(&lr.username)
&& !hbb_common::is_domain_port_str(&lr.username)
&& lr.username != Config::get_id()
@@ -2298,8 +2368,7 @@ impl Connection {
.await;
return false;
} else if (password::approve_mode() == ApproveMode::Click
&& !(crate::get_builtin_option(keys::OPTION_ALLOW_LOGON_SCREEN_PASSWORD) == "Y"
&& is_logon()))
&& !allow_logon_screen_password)
|| password::approve_mode() == ApproveMode::Both && !password::has_valid_password()
{
self.try_start_cm(lr.my_id, lr.my_name, false);
@@ -2335,7 +2404,7 @@ impl Connection {
if !res {
return true;
}
if !self.validate_password() {
if !self.validate_password(allow_logon_screen_password) {
self.update_failure(failure, false, 0);
if err_msg.is_empty() {
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)

View File

@@ -212,12 +212,16 @@ impl UI {
update_temporary_password()
}
fn permanent_password(&self) -> String {
permanent_password()
fn set_permanent_password(&self, password: String) {
let _ = set_permanent_password_with_result(password);
}
fn set_permanent_password(&self, password: String) {
set_permanent_password(password);
fn is_local_permanent_password_set(&self) -> bool {
is_local_permanent_password_set()
}
fn is_permanent_password_set(&self) -> bool {
is_permanent_password_set()
}
fn get_remote_id(&mut self) -> String {
@@ -726,8 +730,9 @@ impl sciter::EventHandler for UI {
fn get_id();
fn temporary_password();
fn update_temporary_password();
fn permanent_password();
fn set_permanent_password(String);
fn is_local_permanent_password_set();
fn is_permanent_password_set();
fn get_remote_id();
fn set_remote_id(String);
fn closing(i32, i32, i32, i32);

View File

@@ -72,6 +72,11 @@ button.button:hover, button.outline:hover {
border-color: color(hover-border);
}
button:disabled,
button:disabled:hover {
opacity: 0.3;
}
button.link {
background: none !important;
border: none;
@@ -484,4 +489,4 @@ div.user-session select {
background: color(bg);
color: color(text);
padding-left: 0.5em;
}
}

View File

@@ -1072,6 +1072,7 @@ class PasswordArea: Reactor.Component {
var method = handler.get_option('verification-method');
var approve_mode= handler.get_option('approve-mode');
var show_password = approve_mode != 'click';
var has_local_password = handler.is_local_permanent_password_set();
return <popup><menu.context #edit-password-context>
<li #approve-mode-password><span>{svg_checkmark}</span>{translate('Accept sessions via password')}</li>
<li #approve-mode-click><span>{svg_checkmark}</span>{translate('Accept sessions via click')}</li>
@@ -1082,6 +1083,7 @@ class PasswordArea: Reactor.Component {
{ !show_password ? '' : <li #use-both-passwords><span>{svg_checkmark}</span>{translate('Use both passwords')}</li> }
{ !show_password ? '' : <div .separator /> }
{ !show_password || disable_change_permanent_password ? '' : <li #set-password disabled={ method == 'use-temporary-password' ? "true" : "false" }>{translate('Set permanent password')}</li> }
{ !show_password || disable_change_permanent_password ? '' : <li #clear-password disabled={ has_local_password ? "false" : "true" }>{translate('Clear permanent password')}</li> }
{ !show_password ? '' : <TemporaryPasswordLengthMenu /> }
<div .separator />
<li #tfa><span>{svg_checkmark}</span>{translate('enable-2fa-title')}</li>
@@ -1114,6 +1116,10 @@ class PasswordArea: Reactor.Component {
el.state.disabled = true;
}
}
if (el.id == "clear-password") {
var has_local_password = handler.is_local_permanent_password_set();
el.state.disabled = !has_local_password;
}
if (el.id == "tfa")
el.attributes.toggleClass("selected", has_valid_2fa);
}
@@ -1129,16 +1135,28 @@ class PasswordArea: Reactor.Component {
event click $(li#set-password) {
var me = this;
var password = handler.permanent_password();
var value_field = password.length == 0 ? "" : "value=" + password;
var has_local_password = handler.is_local_permanent_password_set();
var permanent_password_set = handler.is_permanent_password_set();
var password_hidden_tip = translate('password-hidden-tip');
var preset_password_tip = translate('preset-password-in-use-tip');
var password_tip = "";
if (has_local_password) {
password_tip = "<div style='margin-top:0.5em; color:#e5a43a;'><span style='font-weight:bold;'>[!]</span> " + password_hidden_tip + "</div>";
} else if (permanent_password_set) {
password_tip = "<div style='margin-top:0.5em; color:#e5a43a;'><span style='font-weight:bold;'>[!]</span> " + preset_password_tip + "</div>";
}
msgbox("custom-password", translate("Set Password"), "<div .form .set-password> \
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus " + value_field + " /></div> \
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) " + value_field + " /></div> \
<div><span>" + translate('Password') + ":</span><input|password(password) .outline-focus /></div> \
<div><span>" + translate('Confirmation') + ":</span><input|password(confirmation) /></div> \
" + password_tip + " \
</div> \
", "", function(res=null) {
if (!res) return;
var p0 = (res.password || "").trim();
var p1 = (res.confirmation || "").trim();
if (p0.length == 0 && p1.length == 0) {
return " ";
}
if (p0.length < 6 && p0.length != 0) {
return translate("Too short, at least 6 characters.");
}
@@ -1148,6 +1166,15 @@ class PasswordArea: Reactor.Component {
handler.set_permanent_password(p0);
me.update();
}, msgbox_default_height, get_msgbox_width());
self.timer(30ms, function() {
updateSetPasswordSubmitState();
});
}
event click $(li#clear-password) {
if (this.$(li#clear-password).state.disabled) return;
handler.set_permanent_password("");
this.update();
}
event click $(menu#edit-password-context>li) (_, me) {
@@ -1227,6 +1254,18 @@ function updatePasswordArea() {
}
if (!outgoing_only) updatePasswordArea();
function updateSetPasswordSubmitState() {
var dialog = $(#msgbox);
if (!dialog) return;
var password = dialog.$(input[name='password']);
var confirmation = dialog.$(input[name='confirmation']);
var submit = dialog.$(button#submit);
if (!password || !confirmation || !submit) return;
var can_submit = (password.value || "").trim().length > 0 ||
(confirmation.value || "").trim().length > 0;
submit.state.disabled = !can_submit;
}
class ID: Reactor.Component {
function render() {
return <input type="text" #remote_id .outline-focus novalue={translate("Enter Remote ID")}
@@ -1284,6 +1323,22 @@ event keydown (evt) {
}
}
event keyup $(#msgbox input[name='password']) {
updateSetPasswordSubmitState();
}
event keyup $(#msgbox input[name='confirmation']) {
updateSetPasswordSubmitState();
}
event change $(#msgbox input[name='password']) {
updateSetPasswordSubmitState();
}
event change $(#msgbox input[name='confirmation']) {
updateSetPasswordSubmitState();
}
$(body).content(<div style="size:*"><App /><div #msgbox /></div>);
event click $(#powered-by) {

View File

@@ -193,8 +193,10 @@ class MsgboxComponent: Reactor.Component {
}
function submit() {
if (this.$(button#submit)) {
this.$(button#submit).sendEvent("click");
var submit_btn = this.$(button#submit);
if (submit_btn) {
if (submit_btn.state.disabled) return;
submit_btn.sendEvent("click");
}
}

View File

@@ -609,19 +609,57 @@ pub fn update_temporary_password() {
}
#[inline]
pub fn permanent_password() -> String {
pub fn is_permanent_password_set() -> bool {
#[cfg(any(target_os = "android", target_os = "ios"))]
return Config::get_permanent_password();
return Config::has_permanent_password();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
return ipc::get_permanent_password();
{
let daemon_is_set = ipc::is_permanent_password_set();
// `daemon_is_set` is authoritative for the return value. Local storage is only used to
// decide whether we should attempt a sync to clear stale user-side state.
let local_storage_is_empty = if daemon_is_set {
true
} else {
let (storage, _) = Config::get_local_permanent_password_storage_and_salt();
storage.is_empty()
};
if daemon_is_set || !local_storage_is_empty {
allow_err!(ipc::sync_permanent_password_storage_from_daemon());
}
daemon_is_set
}
}
#[inline]
pub fn set_permanent_password(password: String) {
pub fn is_local_permanent_password_set() -> bool {
#[cfg(any(target_os = "android", target_os = "ios"))]
Config::set_permanent_password(&password);
return Config::has_local_permanent_password();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
allow_err!(ipc::set_permanent_password(password));
{
allow_err!(ipc::sync_permanent_password_storage_from_daemon());
Config::has_local_permanent_password()
}
}
pub fn set_permanent_password_with_result(password: String) -> bool {
if config::Config::is_disable_change_permanent_password() {
return false;
}
#[cfg(any(target_os = "android", target_os = "ios"))]
{
config::Config::set_permanent_password(&password);
return true;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
match crate::ipc::set_permanent_password_with_ack(password) {
Ok(ok) => ok,
Err(err) => {
log::warn!("Failed to set permanent password via IPC: {err}");
false
}
}
}
}
#[inline]