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>
This commit is contained in:
fufesou
2026-03-26 14:49:54 +08:00
committed by GitHub
parent 285e29d2dc
commit 170516572e
64 changed files with 563 additions and 192 deletions

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

@@ -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", ""),
("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

@@ -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

@@ -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", ""),
("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", ""),
("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

@@ -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", ""),
("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 {}", "Tiếp tục với {}"),
("Display Name", ""),
("password-hidden-tip", ""),
("preset-password-in-use-tip", ""),
].iter().cloned().collect();
}

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 {
fn verify_h1(&self, h1: &[u8]) -> bool {
let mut hasher2 = Sha256::new();
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[..])
}
#[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);
hasher.update(&self.hash.salt);
let mut hasher2 = Sha256::new();
hasher2.update(&hasher.finalize()[..]);
hasher2.update(&self.hash.challenge);
hasher2.finalize()[..] == self.lr.password[..]
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) -> 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),
@@ -1995,8 +2038,24 @@ impl Connection {
}
}
if password::permanent_enabled() {
if self.validate_one_password(Config::get_permanent_password()) {
return true;
// 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) {
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) {
return true;
}
}
}
false
@@ -2016,7 +2075,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;

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]