mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-02-17 14:07:28 +08:00
refact: terminal, win, run as admin (#12300)
Signed-off-by: fufesou <linlong1266@gmail.com>
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -2218,9 +2218,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "filedescriptor"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e40758ed24c9b2eeb76c35fb0aebc66c626084edd827e07e1552279814c6682d"
|
||||
version = "0.8.2"
|
||||
source = "git+https://github.com/rustdesk-org/wezterm?branch=rustdesk/pty_based_0.8.1#80174f8009f41565f0fa8c66dab90d4f9211ae16"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"thiserror 1.0.61",
|
||||
@@ -5233,8 +5232,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "806ee80c2a03dbe1a9fb9534f8d19e4c0546b790cde8fd1fea9d6390644cb0be"
|
||||
source = "git+https://github.com/rustdesk-org/wezterm?branch=rustdesk/pty_based_0.8.1#80174f8009f41565f0fa8c66dab90d4f9211ae16"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 1.3.2",
|
||||
|
||||
@@ -98,7 +98,7 @@ ctrlc = "3.2"
|
||||
# arboard = { version = "3.4", features = ["wayland-data-control"] }
|
||||
arboard = { git = "https://github.com/rustdesk-org/arboard", features = ["wayland-data-control"] }
|
||||
clipboard-master = { git = "https://github.com/rustdesk-org/clipboard-master" }
|
||||
portable-pty = "0.8.1" # higher version not work on rustc 1.75
|
||||
portable-pty = { git = "https://github.com/rustdesk-org/wezterm", branch = "rustdesk/pty_based_0.8.1", package = "portable-pty" }
|
||||
|
||||
system_shutdown = "4.0"
|
||||
qrcode-generator = "4.1"
|
||||
|
||||
@@ -2124,6 +2124,10 @@ enum UriLinkType {
|
||||
terminal,
|
||||
}
|
||||
|
||||
setEnvTerminalAdmin() {
|
||||
bind.mainSetEnv(key: 'IS_TERMINAL_ADMIN', value: 'Y');
|
||||
}
|
||||
|
||||
// uri link handler
|
||||
bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
|
||||
List<String>? args;
|
||||
@@ -2191,6 +2195,12 @@ bool handleUriLink({List<String>? cmdArgs, Uri? uri, String? uriString}) {
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--terminal-admin':
|
||||
setEnvTerminalAdmin();
|
||||
type = UriLinkType.terminal;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--password':
|
||||
password = args[i + 1];
|
||||
i++;
|
||||
@@ -2264,7 +2274,8 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
||||
"view-camera",
|
||||
"port-forward",
|
||||
"rdp",
|
||||
"terminal"
|
||||
"terminal",
|
||||
"terminal-admin",
|
||||
];
|
||||
if (uri.authority.isEmpty &&
|
||||
uri.path.split('').every((char) => char == '/')) {
|
||||
@@ -2334,6 +2345,10 @@ List<String>? urlLinkToCmdArgs(Uri uri) {
|
||||
} else if (command == '--terminal') {
|
||||
connect(Get.context!, id,
|
||||
isTerminal: true, forceRelay: forceRelay, password: password);
|
||||
} else if (command == 'terminal-admin') {
|
||||
setEnvTerminalAdmin();
|
||||
connect(Get.context!, id,
|
||||
isTerminal: true, forceRelay: forceRelay, password: password);
|
||||
} else {
|
||||
// Default to remote desktop for '--connect', '--play', or direct connection
|
||||
connect(Get.context!, id, forceRelay: forceRelay, password: password);
|
||||
|
||||
@@ -819,23 +819,33 @@ void enterPasswordDialog(
|
||||
}
|
||||
|
||||
void enterUserLoginDialog(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
||||
SessionID sessionId,
|
||||
OverlayDialogManager dialogManager,
|
||||
String osAccountDescTip,
|
||||
bool canRememberAccount) async {
|
||||
await _connectDialog(
|
||||
sessionId,
|
||||
dialogManager,
|
||||
osUsernameController: TextEditingController(),
|
||||
osPasswordController: TextEditingController(),
|
||||
osAccountDescTip: osAccountDescTip,
|
||||
canRememberAccount: canRememberAccount,
|
||||
);
|
||||
}
|
||||
|
||||
void enterUserLoginAndPasswordDialog(
|
||||
SessionID sessionId, OverlayDialogManager dialogManager) async {
|
||||
SessionID sessionId,
|
||||
OverlayDialogManager dialogManager,
|
||||
String osAccountDescTip,
|
||||
bool canRememberAccount) async {
|
||||
await _connectDialog(
|
||||
sessionId,
|
||||
dialogManager,
|
||||
osUsernameController: TextEditingController(),
|
||||
osPasswordController: TextEditingController(),
|
||||
passwordController: TextEditingController(),
|
||||
osAccountDescTip: osAccountDescTip,
|
||||
canRememberAccount: canRememberAccount,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -845,17 +855,28 @@ _connectDialog(
|
||||
TextEditingController? osUsernameController,
|
||||
TextEditingController? osPasswordController,
|
||||
TextEditingController? passwordController,
|
||||
String? osAccountDescTip,
|
||||
bool canRememberAccount = true,
|
||||
}) async {
|
||||
final errUsername = ''.obs;
|
||||
var rememberPassword = false;
|
||||
if (passwordController != null) {
|
||||
rememberPassword =
|
||||
await bind.sessionGetRemember(sessionId: sessionId) ?? false;
|
||||
}
|
||||
var rememberAccount = false;
|
||||
if (osUsernameController != null) {
|
||||
if (canRememberAccount && osUsernameController != null) {
|
||||
rememberAccount =
|
||||
await bind.sessionGetRemember(sessionId: sessionId) ?? false;
|
||||
}
|
||||
if (osUsernameController != null) {
|
||||
osUsernameController.addListener(() {
|
||||
if (errUsername.value.isNotEmpty) {
|
||||
errUsername.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dialogManager.dismissAll();
|
||||
dialogManager.show((setState, close, context) {
|
||||
cancel() {
|
||||
@@ -864,6 +885,13 @@ _connectDialog(
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (osUsernameController != null) {
|
||||
if (osUsernameController.text.trim().isEmpty) {
|
||||
errUsername.value = translate('Empty Username');
|
||||
setState(() {});
|
||||
return;
|
||||
}
|
||||
}
|
||||
final osUsername = osUsernameController?.text.trim() ?? '';
|
||||
final osPassword = osPasswordController?.text.trim() ?? '';
|
||||
final password = passwordController?.text.trim() ?? '';
|
||||
@@ -927,26 +955,39 @@ _connectDialog(
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
descWidget(translate('login_linux_tip')),
|
||||
if (osAccountDescTip != null) descWidget(translate(osAccountDescTip)),
|
||||
DialogTextField(
|
||||
title: translate(DialogTextField.kUsernameTitle),
|
||||
controller: osUsernameController,
|
||||
prefixIcon: DialogTextField.kUsernameIcon,
|
||||
errorText: null,
|
||||
),
|
||||
if (errUsername.value.isNotEmpty)
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: SelectableText(
|
||||
errUsername.value,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 12,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
).paddingOnly(left: 12, bottom: 2),
|
||||
),
|
||||
PasswordWidget(
|
||||
controller: osPasswordController,
|
||||
autoFocus: false,
|
||||
),
|
||||
rememberWidget(
|
||||
translate('remember_account_tip'),
|
||||
rememberAccount,
|
||||
(v) {
|
||||
if (v != null) {
|
||||
setState(() => rememberAccount = v);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (canRememberAccount)
|
||||
rememberWidget(
|
||||
translate('remember_account_tip'),
|
||||
rememberAccount,
|
||||
(v) {
|
||||
if (v != null) {
|
||||
setState(() => rememberAccount = v);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -492,6 +492,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
bool isTcpTunneling = false,
|
||||
bool isRDP = false,
|
||||
bool isTerminal = false,
|
||||
bool isTerminalRunAsAdmin = false,
|
||||
}) {
|
||||
return MenuEntryButton<String>(
|
||||
childBuilder: (TextStyle? style) => Text(
|
||||
@@ -499,6 +500,9 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
style: style,
|
||||
),
|
||||
proc: () {
|
||||
if (isTerminalRunAsAdmin) {
|
||||
setEnvTerminalAdmin();
|
||||
}
|
||||
connectInPeerTab(
|
||||
context,
|
||||
peer,
|
||||
@@ -507,7 +511,7 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
isViewCamera: isViewCamera,
|
||||
isTcpTunneling: isTcpTunneling,
|
||||
isRDP: isRDP,
|
||||
isTerminal: isTerminal,
|
||||
isTerminal: isTerminal || isTerminalRunAsAdmin,
|
||||
);
|
||||
},
|
||||
padding: menuPadding,
|
||||
@@ -552,6 +556,15 @@ abstract class BasePeerCard extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _terminalRunAsAdminAction(BuildContext context) {
|
||||
return _connectCommonAction(
|
||||
context,
|
||||
translate('Terminal (Run as administrator)'),
|
||||
isTerminalRunAsAdmin: true,
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
MenuEntryBase<String> _tcpTunnelingAction(BuildContext context) {
|
||||
return _connectCommonAction(
|
||||
@@ -906,6 +919,10 @@ class RecentPeerCard extends BasePeerCard {
|
||||
_terminalAction(context),
|
||||
];
|
||||
|
||||
if (peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_terminalRunAsAdminAction(context));
|
||||
}
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
@@ -966,6 +983,11 @@ class FavoritePeerCard extends BasePeerCard {
|
||||
_viewCameraAction(context),
|
||||
_terminalAction(context),
|
||||
];
|
||||
|
||||
if (peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_terminalRunAsAdminAction(context));
|
||||
}
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
@@ -1022,6 +1044,10 @@ class DiscoveredPeerCard extends BasePeerCard {
|
||||
_terminalAction(context),
|
||||
];
|
||||
|
||||
if (peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_terminalRunAsAdminAction(context));
|
||||
}
|
||||
|
||||
final List favs = (await bind.mainGetFav()).toList();
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
@@ -1076,6 +1102,11 @@ class AddressBookPeerCard extends BasePeerCard {
|
||||
_viewCameraAction(context),
|
||||
_terminalAction(context),
|
||||
];
|
||||
|
||||
if (peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_terminalRunAsAdminAction(context));
|
||||
}
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
@@ -1212,6 +1243,11 @@ class MyGroupPeerCard extends BasePeerCard {
|
||||
_viewCameraAction(context),
|
||||
_terminalAction(context),
|
||||
];
|
||||
|
||||
if (peer.platform == kPeerPlatformWindows) {
|
||||
menuItems.add(_terminalRunAsAdminAction(context));
|
||||
}
|
||||
|
||||
if (isDesktop && peer.platform != kPeerPlatformAndroid) {
|
||||
menuItems.add(_tcpTunnelingAction(context));
|
||||
}
|
||||
|
||||
@@ -230,6 +230,12 @@ class WebHomePage extends StatelessWidget {
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--terminal-admin':
|
||||
setEnvTerminalAdmin();
|
||||
isTerminal = true;
|
||||
id = args[i + 1];
|
||||
i++;
|
||||
break;
|
||||
case '--password':
|
||||
password = args[i + 1];
|
||||
i++;
|
||||
|
||||
@@ -836,10 +836,16 @@ class FfiModel with ChangeNotifier {
|
||||
} else if (type == 'input-password') {
|
||||
enterPasswordDialog(sessionId, dialogManager);
|
||||
} else if (type == 'session-login' || type == 'session-re-login') {
|
||||
enterUserLoginDialog(sessionId, dialogManager);
|
||||
} else if (type == 'session-login-password' ||
|
||||
type == 'session-login-password') {
|
||||
enterUserLoginAndPasswordDialog(sessionId, dialogManager);
|
||||
enterUserLoginDialog(sessionId, dialogManager, 'login_linux_tip', true);
|
||||
} else if (type == 'session-login-password') {
|
||||
enterUserLoginAndPasswordDialog(
|
||||
sessionId, dialogManager, 'login_linux_tip', true);
|
||||
} else if (type == 'terminal-admin-login') {
|
||||
enterUserLoginDialog(
|
||||
sessionId, dialogManager, 'terminal-admin-login-tip', false);
|
||||
} else if (type == 'terminal-admin-login-password') {
|
||||
enterUserLoginAndPasswordDialog(
|
||||
sessionId, dialogManager, 'terminal-admin-login-tip', false);
|
||||
} else if (type == 'restarting') {
|
||||
showMsgBox(sessionId, type, title, text, link, false, dialogManager,
|
||||
hasCancel: false);
|
||||
|
||||
@@ -1611,6 +1611,7 @@ struct ConnToken {
|
||||
pub struct LoginConfigHandler {
|
||||
id: String,
|
||||
pub conn_type: ConnType,
|
||||
pub is_terminal_admin: bool,
|
||||
hash: Hash,
|
||||
password: Vec<u8>, // remember password for reconnect
|
||||
pub remember: bool,
|
||||
@@ -1736,6 +1737,7 @@ impl LoginConfigHandler {
|
||||
self.other_server = Some((real_id.to_owned(), server.to_owned(), other_server_key));
|
||||
}
|
||||
}
|
||||
|
||||
self.direct = None;
|
||||
self.received = false;
|
||||
self.switch_uuid = switch_uuid;
|
||||
@@ -1744,6 +1746,11 @@ impl LoginConfigHandler {
|
||||
self.shared_password = shared_password;
|
||||
self.record_state = false;
|
||||
self.record_permission = true;
|
||||
|
||||
// `std::env::remove_var("IS_TERMINAL_ADMIN");` is called in `session_add_sync()` - `flutter_ffi.rs`.
|
||||
let is_terminal_admin = conn_type == ConnType::TERMINAL
|
||||
&& std::env::var("IS_TERMINAL_ADMIN").map_or(false, |v| v == "Y");
|
||||
self.is_terminal_admin = is_terminal_admin;
|
||||
}
|
||||
|
||||
/// Check if the client should auto login.
|
||||
@@ -1956,7 +1963,7 @@ impl LoginConfigHandler {
|
||||
.into();
|
||||
} else if name == keys::OPTION_TERMINAL_PERSISTENT {
|
||||
config.terminal_persistent.v = !config.terminal_persistent.v;
|
||||
option.terminal_persistent = (if config.terminal_persistent.v {
|
||||
option.terminal_persistent = (if config.terminal_persistent.v {
|
||||
BoolOption::Yes
|
||||
} else {
|
||||
BoolOption::No
|
||||
@@ -3274,6 +3281,19 @@ pub async fn handle_hash(
|
||||
}
|
||||
|
||||
lc.write().unwrap().password = password.clone();
|
||||
|
||||
let is_terminal_admin = lc.read().unwrap().is_terminal_admin;
|
||||
let is_terminal = lc.read().unwrap().conn_type.eq(&ConnType::TERMINAL);
|
||||
if is_terminal && is_terminal_admin {
|
||||
if password.is_empty() {
|
||||
interface.msgbox("terminal-admin-login-password", "", "", "");
|
||||
} else {
|
||||
interface.msgbox("terminal-admin-login", "", "", "");
|
||||
}
|
||||
lc.write().unwrap().hash = hash;
|
||||
return;
|
||||
}
|
||||
|
||||
let password = if password.is_empty() {
|
||||
// login without password, the remote side can click accept
|
||||
interface.msgbox("input-password", "Password Required", "", "");
|
||||
@@ -3285,8 +3305,15 @@ pub async fn handle_hash(
|
||||
hasher.finalize()[..].into()
|
||||
};
|
||||
|
||||
let os_username = lc.read().unwrap().get_option("os-username");
|
||||
let os_password = lc.read().unwrap().get_option("os-password");
|
||||
let is_terminal = lc.read().unwrap().conn_type.eq(&ConnType::TERMINAL);
|
||||
let (os_username, os_password) = if is_terminal {
|
||||
("".to_owned(), "".to_owned())
|
||||
} else {
|
||||
(
|
||||
lc.read().unwrap().get_option("os-username"),
|
||||
lc.read().unwrap().get_option("os-password"),
|
||||
)
|
||||
};
|
||||
|
||||
send_login(lc.clone(), os_username, os_password, password, peer).await;
|
||||
lc.write().unwrap().hash = hash;
|
||||
|
||||
@@ -138,7 +138,7 @@ pub fn session_add_sync(
|
||||
is_shared_password: bool,
|
||||
conn_token: Option<String>,
|
||||
) -> SyncReturn<String> {
|
||||
if let Err(e) = session_add(
|
||||
let add_res = session_add(
|
||||
&session_id,
|
||||
&id,
|
||||
is_file_transfer,
|
||||
@@ -151,7 +151,14 @@ pub fn session_add_sync(
|
||||
password,
|
||||
is_shared_password,
|
||||
conn_token,
|
||||
) {
|
||||
);
|
||||
// We can't put the remove call together with `std::env::var("IS_TERMINAL_ADMIN")`.
|
||||
// Because there are some `bail!` in `session_add()`, we must make sure `IS_TERMINAL_ADMIN` is removed at last.
|
||||
if is_terminal {
|
||||
std::env::remove_var("IS_TERMINAL_ADMIN");
|
||||
}
|
||||
|
||||
if let Err(e) = add_res {
|
||||
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
|
||||
} else {
|
||||
SyncReturn("".to_owned())
|
||||
@@ -1067,6 +1074,35 @@ pub fn main_get_env(key: String) -> SyncReturn<String> {
|
||||
SyncReturn(std::env::var(key).unwrap_or_default())
|
||||
}
|
||||
|
||||
// Dart does not support changing environment variables.
|
||||
// `Platform.environment['MY_VAR'] = 'VAR';` will throw an error
|
||||
// `Unsupported operation: Cannot modify unmodifiable map`.
|
||||
//
|
||||
// And we need to share the environment variables between rust and dart isolates sometimes.
|
||||
pub fn main_set_env(key: String, value: Option<String>) -> SyncReturn<()> {
|
||||
let is_valid_key = !key.is_empty() && !key.contains('=') && !key.contains('\0');
|
||||
debug_assert!(is_valid_key, "Invalid environment variable key: {}", key);
|
||||
if !is_valid_key {
|
||||
log::error!("Invalid environment variable key: {}", key);
|
||||
return SyncReturn(());
|
||||
}
|
||||
|
||||
match value {
|
||||
Some(v) => {
|
||||
let is_valid_value = !v.contains('\0');
|
||||
debug_assert!(is_valid_value, "Invalid environment variable value: {}", v);
|
||||
if !is_valid_value {
|
||||
log::error!("Invalid environment variable value: {}", v);
|
||||
return SyncReturn(());
|
||||
}
|
||||
std::env::set_var(key, v);
|
||||
}
|
||||
None => std::env::remove_var(key),
|
||||
}
|
||||
|
||||
SyncReturn(())
|
||||
}
|
||||
|
||||
pub fn main_set_local_option(key: String, value: String) {
|
||||
let is_texture_render_key = key.eq(config::keys::OPTION_TEXTURE_RENDER);
|
||||
let is_d3d_render_key = key.eq(config::keys::OPTION_ALLOW_D3D_RENDER);
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "تمكين الطرفية"),
|
||||
("New tab", "تبويب جديد"),
|
||||
("Keep terminal sessions on disconnect", "الاحتفاظ بجلسات الطرفية عند قطع الاتصال"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "启用终端"),
|
||||
("New tab", "新建选项卡"),
|
||||
("Keep terminal sessions on disconnect", "断开连接时保持终端会话"),
|
||||
("Terminal (Run as administrator)", "终端(以管理员身份运行)"),
|
||||
("terminal-admin-login-tip", "请输入被控端的管理员账号密码。"),
|
||||
("Failed to get user token.", "获取用户令牌时出错。"),
|
||||
("Incorrect username or password.", "用户名或密码不正确。"),
|
||||
("The user is not an administrator.", "用户不是管理员。"),
|
||||
("Failed to check if the user is an administrator.", "检查用户是否为管理员时出错。"),
|
||||
("Supported only by the installation version.", "仅安装版本支持。"),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Terminal zulassen"),
|
||||
("New tab", "Neuer Tab"),
|
||||
("Keep terminal sessions on disconnect", "Terminalsitzungen beim Trennen der Verbindung beibehalten"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -256,5 +256,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("download-new-version-failed-tip", "Download failed. You can try again or click the \"Download\" button to download from the release page and upgrade manually."),
|
||||
("update-failed-check-msi-tip", "Installation method check failed. Please click the \"Download\" button to download from the release page and upgrade manually."),
|
||||
("websocket_tip", "When using WebSocket, only relay connections are supported."),
|
||||
("terminal-admin-login-tip", "Please input the administrator username and password of the controlled side."),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "فعالسازی ترمینال"),
|
||||
("New tab", "زبانه جدید"),
|
||||
("Keep terminal sessions on disconnect", "حفظ جلسات ترمینال پس از قطع اتصال"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Activer le terminal"),
|
||||
("New tab", "Nouvel onglet"),
|
||||
("Keep terminal sessions on disconnect", "Maintenir les sessions du terminal lors de la déconnexion"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Terminál engedélyezése"),
|
||||
("New tab", "Új lap"),
|
||||
("Keep terminal sessions on disconnect", "Terminál munkamenetek megtartása leválasztáskor"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Abilita terminale"),
|
||||
("New tab", "Nuova scheda"),
|
||||
("Keep terminal sessions on disconnect", "Quando disconetti mantieni attiva sessione terminale"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "터미널 사용함"),
|
||||
("New tab", "새 탭"),
|
||||
("Keep terminal sessions on disconnect", "터미널 세션 연결 해제 상태 유지"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Iespējot termināli"),
|
||||
("New tab", "Jauna cilne"),
|
||||
("Keep terminal sessions on disconnect", "Atvienojoties saglabāt termināļa sesijas"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Terminal inschakelen"),
|
||||
("New tab", "Nieuw tabblad"),
|
||||
("Keep terminal sessions on disconnect", "Terminalsessies bij verbreking van de verbinding behouden"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", "Включить терминал"),
|
||||
("New tab", "Новая вкладка"),
|
||||
("Keep terminal sessions on disconnect", "Сохранять сеансы терминала при отключении"),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -703,5 +703,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
||||
("Enable terminal", ""),
|
||||
("New tab", ""),
|
||||
("Keep terminal sessions on disconnect", ""),
|
||||
("Terminal (Run as administrator)", ""),
|
||||
("terminal-admin-login-tip", ""),
|
||||
("Failed to get user token.", ""),
|
||||
("Incorrect username or password.", ""),
|
||||
("The user is not an administrator.", ""),
|
||||
("Failed to check if the user is an administrator.", ""),
|
||||
("Supported only by the installation version.", ""),
|
||||
].iter().cloned().collect();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ use winapi::{
|
||||
shared::{minwindef::*, ntdef::NULL, windef::*, winerror::*},
|
||||
um::{
|
||||
errhandlingapi::GetLastError,
|
||||
handleapi::CloseHandle,
|
||||
handleapi::{CloseHandle, INVALID_HANDLE_VALUE},
|
||||
libloaderapi::{
|
||||
GetProcAddress, LoadLibraryA, LoadLibraryExA, LOAD_LIBRARY_SEARCH_SYSTEM32,
|
||||
},
|
||||
@@ -49,15 +49,19 @@ use winapi::{
|
||||
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, OpenProcess,
|
||||
OpenProcessToken, ProcessIdToSessionId, PROCESS_INFORMATION, STARTUPINFOW,
|
||||
},
|
||||
securitybaseapi::GetTokenInformation,
|
||||
securitybaseapi::{
|
||||
AllocateAndInitializeSid, DuplicateToken, EqualSid, FreeSid, GetTokenInformation,
|
||||
},
|
||||
shellapi::ShellExecuteW,
|
||||
sysinfoapi::{GetNativeSystemInfo, SYSTEM_INFO},
|
||||
winbase::*,
|
||||
wingdi::*,
|
||||
winnt::{
|
||||
TokenElevation, ES_AWAYMODE_REQUIRED, ES_CONTINUOUS, ES_DISPLAY_REQUIRED,
|
||||
SecurityImpersonation, TokenElevation, TokenGroups, TokenImpersonation, TokenType,
|
||||
DOMAIN_ALIAS_RID_ADMINS, ES_AWAYMODE_REQUIRED, ES_CONTINUOUS, ES_DISPLAY_REQUIRED,
|
||||
ES_SYSTEM_REQUIRED, HANDLE, PROCESS_ALL_ACCESS, PROCESS_QUERY_LIMITED_INFORMATION,
|
||||
TOKEN_ELEVATION, TOKEN_QUERY,
|
||||
PSID, SECURITY_BUILTIN_DOMAIN_RID, SECURITY_NT_AUTHORITY, SID_IDENTIFIER_AUTHORITY,
|
||||
TOKEN_ELEVATION, TOKEN_GROUPS, TOKEN_QUERY, TOKEN_TYPE,
|
||||
},
|
||||
winreg::HKEY_CURRENT_USER,
|
||||
winspool::{
|
||||
@@ -521,6 +525,10 @@ extern "C" {
|
||||
fn is_service_running_w(svc_name: *const u16) -> bool;
|
||||
}
|
||||
|
||||
pub fn get_current_session_id(share_rdp: bool) -> DWORD {
|
||||
unsafe { get_current_session(if share_rdp { TRUE } else { FALSE }) }
|
||||
}
|
||||
|
||||
extern "system" {
|
||||
fn BlockInput(v: BOOL) -> BOOL;
|
||||
}
|
||||
@@ -2158,6 +2166,177 @@ pub fn send_message_to_hnwd(
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn get_logon_user_token(user: &str, pwd: &str) -> ResultType<HANDLE> {
|
||||
let user_split = user.split("\\").collect::<Vec<&str>>();
|
||||
let wuser = wide_string(user_split.get(1).unwrap_or(&user));
|
||||
let wpc = wide_string(user_split.get(0).unwrap_or(&""));
|
||||
let wpwd = wide_string(pwd);
|
||||
let mut ph_token: HANDLE = std::ptr::null_mut();
|
||||
let res = unsafe {
|
||||
LogonUserW(
|
||||
wuser.as_ptr(),
|
||||
wpc.as_ptr(),
|
||||
wpwd.as_ptr(),
|
||||
LOGON32_LOGON_INTERACTIVE,
|
||||
LOGON32_PROVIDER_DEFAULT,
|
||||
&mut ph_token as _,
|
||||
)
|
||||
};
|
||||
if res == FALSE {
|
||||
bail!(
|
||||
"Failed to log on user {}: {}",
|
||||
user,
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
} else {
|
||||
if ph_token.is_null() {
|
||||
bail!(
|
||||
"Failed to log on user {}: {}",
|
||||
user,
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
Ok(ph_token)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the token returned is a primary token.
|
||||
// If the provided token is an impersonation token, it duplicates it to a primary token.
|
||||
// If the provided token is already a primary token, it returns it as is.
|
||||
// The caller is responsible for closing the returned token handle.
|
||||
pub fn ensure_primary_token(user_token: HANDLE) -> ResultType<HANDLE> {
|
||||
if user_token.is_null() || user_token == INVALID_HANDLE_VALUE {
|
||||
bail!("Invalid user token provided");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut token_type: TOKEN_TYPE = 0;
|
||||
let mut return_length: DWORD = 0;
|
||||
|
||||
if GetTokenInformation(
|
||||
user_token,
|
||||
TokenType,
|
||||
&mut token_type as *mut _ as *mut _,
|
||||
std::mem::size_of::<TOKEN_TYPE>() as DWORD,
|
||||
&mut return_length,
|
||||
) == FALSE
|
||||
{
|
||||
bail!(
|
||||
"Failed to get token type, error {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
if token_type == TokenImpersonation {
|
||||
let mut duplicate_token: HANDLE = std::ptr::null_mut();
|
||||
let dup_res = DuplicateToken(user_token, SecurityImpersonation, &mut duplicate_token);
|
||||
CloseHandle(user_token);
|
||||
if dup_res == FALSE {
|
||||
bail!(
|
||||
"Failed to duplicate token, error {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
Ok(duplicate_token)
|
||||
} else {
|
||||
Ok(user_token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_user_token_admin(user_token: HANDLE) -> ResultType<bool> {
|
||||
if user_token.is_null() || user_token == INVALID_HANDLE_VALUE {
|
||||
bail!("Invalid user token provided");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let mut dw_size: DWORD = 0;
|
||||
GetTokenInformation(
|
||||
user_token,
|
||||
TokenGroups,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
&mut dw_size,
|
||||
);
|
||||
|
||||
let last_error = GetLastError();
|
||||
if last_error != ERROR_INSUFFICIENT_BUFFER {
|
||||
bail!(
|
||||
"Failed to get token groups buffer size, error: {}",
|
||||
last_error
|
||||
);
|
||||
}
|
||||
if dw_size == 0 {
|
||||
bail!("Token groups buffer size is zero");
|
||||
}
|
||||
|
||||
let mut buffer = vec![0u8; dw_size as usize];
|
||||
if GetTokenInformation(
|
||||
user_token,
|
||||
TokenGroups,
|
||||
buffer.as_mut_ptr() as *mut _,
|
||||
dw_size,
|
||||
&mut dw_size,
|
||||
) == FALSE
|
||||
{
|
||||
bail!(
|
||||
"Failed to get token groups information, error: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
let p_token_groups = buffer.as_ptr() as *const TOKEN_GROUPS;
|
||||
let group_count = (*p_token_groups).GroupCount;
|
||||
|
||||
if group_count == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut nt_authority: SID_IDENTIFIER_AUTHORITY = SID_IDENTIFIER_AUTHORITY {
|
||||
Value: SECURITY_NT_AUTHORITY,
|
||||
};
|
||||
let mut administrators_group: PSID = std::ptr::null_mut();
|
||||
if AllocateAndInitializeSid(
|
||||
&mut nt_authority,
|
||||
2,
|
||||
SECURITY_BUILTIN_DOMAIN_RID,
|
||||
DOMAIN_ALIAS_RID_ADMINS,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&mut administrators_group,
|
||||
) == FALSE
|
||||
{
|
||||
bail!(
|
||||
"Failed to allocate administrators group SID, error: {}",
|
||||
io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
if administrators_group.is_null() {
|
||||
bail!("Failed to create administrators group SID");
|
||||
}
|
||||
|
||||
let mut is_admin = false;
|
||||
let groups =
|
||||
std::slice::from_raw_parts((*p_token_groups).Groups.as_ptr(), group_count as usize);
|
||||
for group in groups {
|
||||
if EqualSid(administrators_group, group.Sid) == TRUE {
|
||||
is_admin = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !administrators_group.is_null() {
|
||||
FreeSid(administrators_group);
|
||||
}
|
||||
|
||||
Ok(is_admin)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_process_with_logon(user: &str, pwd: &str, exe: &str, arg: &str) -> ResultType<()> {
|
||||
let last_error_table = HashMap::from([
|
||||
(
|
||||
|
||||
@@ -56,6 +56,8 @@ use std::{
|
||||
};
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
use system_shutdown;
|
||||
#[cfg(target_os = "windows")]
|
||||
use windows::Win32::Foundation::{CloseHandle, HANDLE};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::virtual_display_manager;
|
||||
@@ -172,6 +174,22 @@ pub enum AuthConnType {
|
||||
Terminal,
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
#[derive(Clone, Debug)]
|
||||
enum TerminalUserToken {
|
||||
SelfUser,
|
||||
CurrentLogonUser(crate::terminal_service::UserToken),
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
impl TerminalUserToken {
|
||||
fn to_terminal_service_token(&self) -> Option<crate::terminal_service::UserToken> {
|
||||
match self {
|
||||
TerminalUserToken::SelfUser => None,
|
||||
TerminalUserToken::CurrentLogonUser(token) => Some(*token),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct Connection {
|
||||
inner: ConnInner,
|
||||
display_idx: usize,
|
||||
@@ -254,6 +272,11 @@ pub struct Connection {
|
||||
tx_post_seq: mpsc::UnboundedSender<(String, Value)>,
|
||||
terminal_service_id: String,
|
||||
terminal_persistent: bool,
|
||||
// The user token must be set when terminal is enabled.
|
||||
// 0 indicates SYSTEM user
|
||||
// other values indicate current user
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
terminal_user_token: Option<TerminalUserToken>,
|
||||
terminal_generic_service: Option<Box<GenericService>>,
|
||||
}
|
||||
|
||||
@@ -418,6 +441,8 @@ impl Connection {
|
||||
tx_post_seq,
|
||||
terminal_service_id: "".to_owned(),
|
||||
terminal_persistent: false,
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
terminal_user_token: None,
|
||||
terminal_generic_service: None,
|
||||
};
|
||||
let addr = hbb_common::try_into_v4(addr);
|
||||
@@ -1415,12 +1440,19 @@ impl Connection {
|
||||
.unwrap()
|
||||
.insert(self.lr.my_id.clone(), self.tx_input.clone());
|
||||
|
||||
// Terminal feature is supported on desktop only
|
||||
#[allow(unused_mut)]
|
||||
let mut terminal = cfg!(not(any(target_os = "android", target_os = "ios")));
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
terminal = terminal && portable_pty::win::check_support().is_ok();
|
||||
}
|
||||
pi.username = username;
|
||||
pi.sas_enabled = sas_enabled;
|
||||
pi.features = Some(Features {
|
||||
privacy_mode: privacy_mode::is_privacy_mode_supported(),
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
terminal: true, // Terminal feature is supported on desktop only
|
||||
terminal,
|
||||
..Default::default()
|
||||
})
|
||||
.into();
|
||||
@@ -1429,7 +1461,9 @@ impl Connection {
|
||||
#[allow(unused_mut)]
|
||||
let mut wait_session_id_confirm = false;
|
||||
#[cfg(windows)]
|
||||
self.handle_windows_specific_session(&mut pi, &mut wait_session_id_confirm);
|
||||
if !self.terminal {
|
||||
self.handle_windows_specific_session(&mut pi, &mut wait_session_id_confirm);
|
||||
}
|
||||
if self.file_transfer.is_some() || self.terminal {
|
||||
res.set_peer_info(pi);
|
||||
} else if self.view_camera {
|
||||
@@ -1933,12 +1967,28 @@ impl Connection {
|
||||
sleep(1.).await;
|
||||
return false;
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
if !lr.os_login.username.is_empty() && !crate::platform::is_installed() {
|
||||
self.send_login_error("Supported only by the installation version.")
|
||||
.await;
|
||||
sleep(1.).await;
|
||||
return false;
|
||||
}
|
||||
|
||||
self.terminal = true;
|
||||
if let Some(o) = self.options_in_login.as_ref() {
|
||||
self.terminal_persistent =
|
||||
o.terminal_persistent.enum_value() == Ok(BoolOption::Yes);
|
||||
}
|
||||
self.terminal_service_id = terminal.service_id;
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(msg) =
|
||||
self.fill_terminal_user_token(&lr.os_login.username, &lr.os_login.password)
|
||||
{
|
||||
self.send_login_error(msg).await;
|
||||
sleep(1.).await;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Some(login_request::Union::PortForward(mut pf)) => {
|
||||
if !Connection::permission("enable-tunnel") {
|
||||
@@ -2893,6 +2943,94 @@ impl Connection {
|
||||
true
|
||||
}
|
||||
|
||||
// Try to fill user token for terminal connection.
|
||||
// If username is empty, use the user token of the current session.
|
||||
// If username is not empty, try to logon and check if the user is an administrator.
|
||||
// If the user is an administrator, use the user token of current process (SYSTEM).
|
||||
// If the user is not an administrator, return an error message.
|
||||
// Note: Only local and domain users are supported, Microsoft account (online account) not supported for now.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn fill_terminal_user_token(&mut self, username: &str, password: &str) -> Option<&'static str> {
|
||||
// No need to check if the password is empty.
|
||||
if !username.is_empty() {
|
||||
return self.handle_administrator_check(username, password);
|
||||
}
|
||||
|
||||
if crate::platform::is_prelogin() {
|
||||
self.terminal_user_token = None;
|
||||
return Some("No active console user logged on, please connect and logon first.");
|
||||
}
|
||||
|
||||
if crate::platform::is_installed() {
|
||||
return self.handle_installed_user();
|
||||
}
|
||||
|
||||
self.terminal_user_token = Some(TerminalUserToken::SelfUser);
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn handle_administrator_check(
|
||||
&mut self,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Option<&'static str> {
|
||||
let check_admin_res =
|
||||
crate::platform::get_logon_user_token(username, password).map(|token| {
|
||||
let is_token_admin = crate::platform::is_user_token_admin(token);
|
||||
unsafe {
|
||||
hbb_common::allow_err!(CloseHandle(HANDLE(token as _)));
|
||||
};
|
||||
is_token_admin
|
||||
});
|
||||
match check_admin_res {
|
||||
Ok(Ok(b)) => {
|
||||
if b {
|
||||
self.terminal_user_token = Some(TerminalUserToken::SelfUser);
|
||||
None
|
||||
} else {
|
||||
Some("The user is not an administrator.")
|
||||
}
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
log::error!("Failed to check if the user is an administrator: {}", e);
|
||||
Some("Failed to check if the user is an administrator.")
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to get logon user token: {}", e);
|
||||
Some("Incorrect username or password.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn handle_installed_user(&mut self) -> Option<&'static str> {
|
||||
let session_id = crate::platform::get_current_session_id(true);
|
||||
if session_id == 0xFFFFFFFF {
|
||||
return Some("Failed to get current session id.");
|
||||
}
|
||||
let token = crate::platform::get_user_token(session_id, true);
|
||||
if !token.is_null() {
|
||||
match crate::platform::ensure_primary_token(token) {
|
||||
Ok(t) => {
|
||||
self.terminal_user_token = Some(TerminalUserToken::CurrentLogonUser(t as _));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to ensure primary token: {}", e);
|
||||
self.terminal_user_token =
|
||||
Some(TerminalUserToken::CurrentLogonUser(token as _));
|
||||
}
|
||||
}
|
||||
None
|
||||
} else {
|
||||
log::error!(
|
||||
"Failed to get user token for terminal action, {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
Some("Failed to get user token.")
|
||||
}
|
||||
}
|
||||
|
||||
fn update_failure(&self, (mut failure, time): ((i32, i32, i32), i32), remove: bool, i: usize) {
|
||||
if remove {
|
||||
if failure.0 != 0 {
|
||||
@@ -3833,12 +3971,19 @@ impl Connection {
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
async fn init_terminal_service(&mut self) {
|
||||
debug_assert!(self.terminal_user_token.is_some());
|
||||
let Some(user_token) = self.terminal_user_token.clone() else {
|
||||
// unreachable, but keep it for safety
|
||||
log::error!("Terminal user token is not set.");
|
||||
return;
|
||||
};
|
||||
if self.terminal_service_id.is_empty() {
|
||||
self.terminal_service_id = terminal_service::generate_service_id();
|
||||
}
|
||||
let s = Box::new(terminal_service::new(
|
||||
self.terminal_service_id.clone(),
|
||||
self.terminal_persistent,
|
||||
user_token.to_terminal_service_token(),
|
||||
));
|
||||
s.on_subscribe(self.inner.clone());
|
||||
self.terminal_generic_service = Some(s);
|
||||
@@ -3846,9 +3991,15 @@ impl Connection {
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
async fn handle_terminal_action(&mut self, action: TerminalAction) -> ResultType<()> {
|
||||
debug_assert!(self.terminal_user_token.is_some());
|
||||
let Some(user_token) = self.terminal_user_token.clone() else {
|
||||
// unreacheable, but keep it for safety
|
||||
bail!("Terminal user token is not set.");
|
||||
};
|
||||
let mut proxy = terminal_service::TerminalServiceProxy::new(
|
||||
self.terminal_service_id.clone(),
|
||||
Some(self.terminal_persistent),
|
||||
user_token.to_terminal_service_token(),
|
||||
);
|
||||
|
||||
match proxy.handle_action(&action) {
|
||||
@@ -4249,6 +4400,15 @@ impl Drop for Connection {
|
||||
if let Some(s) = self.terminal_generic_service.as_ref() {
|
||||
s.join();
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(TerminalUserToken::CurrentLogonUser(token)) = self.terminal_user_token.take() {
|
||||
if token != 0 {
|
||||
unsafe {
|
||||
hbb_common::allow_err!(CloseHandle(HANDLE(token as _)));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use portable_pty::{Child, CommandBuilder, PtySize};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
io::{Read, Write},
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
mpsc::{self, Receiver, SyncSender},
|
||||
@@ -271,17 +272,51 @@ pub fn get_terminal_session_count(include_zombie_tasks: bool) -> usize {
|
||||
c
|
||||
}
|
||||
|
||||
pub fn new(service_id: String, is_persistent: bool) -> GenericService {
|
||||
pub type UserToken = u64;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TerminalService {
|
||||
sp: GenericService,
|
||||
user_token: Option<UserToken>,
|
||||
}
|
||||
|
||||
impl Deref for TerminalService {
|
||||
type Target = ServiceTmpl<ConnInner>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.sp
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for TerminalService {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.sp
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_service_name(source: VideoSource, idx: usize) -> String {
|
||||
format!("{}{}", source.service_name_prefix(), idx)
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
service_id: String,
|
||||
is_persistent: bool,
|
||||
user_token: Option<UserToken>,
|
||||
) -> GenericService {
|
||||
// Create the service with initial persistence setting
|
||||
allow_err!(get_or_create_service(service_id.clone(), is_persistent));
|
||||
let svc = EmptyExtraFieldService::new(service_id.clone(), false);
|
||||
let svc = TerminalService {
|
||||
sp: GenericService::new(service_id.clone(), false),
|
||||
user_token,
|
||||
};
|
||||
GenericService::run(&svc.clone(), move |sp| run(sp, service_id.clone()));
|
||||
svc.sp
|
||||
}
|
||||
|
||||
fn run(sp: EmptyExtraFieldService, service_id: String) -> ResultType<()> {
|
||||
fn run(sp: TerminalService, service_id: String) -> ResultType<()> {
|
||||
while sp.ok() {
|
||||
let responses = TerminalServiceProxy::new(service_id.clone(), None).read_outputs();
|
||||
let responses = TerminalServiceProxy::new(service_id.clone(), None, sp.user_token.clone())
|
||||
.read_outputs();
|
||||
for response in responses {
|
||||
let mut msg_out = Message::new();
|
||||
msg_out.set_terminal_response(response);
|
||||
@@ -451,6 +486,7 @@ impl TerminalSession {
|
||||
}
|
||||
drop(input_tx);
|
||||
}
|
||||
self.output_rx = None;
|
||||
|
||||
// Wait for threads to finish
|
||||
// The reader thread should join before the writer thread on Windows.
|
||||
@@ -544,6 +580,8 @@ impl PersistentTerminalService {
|
||||
pub struct TerminalServiceProxy {
|
||||
service_id: String,
|
||||
is_persistent: bool,
|
||||
#[cfg(target_os = "windows")]
|
||||
user_token: Option<UserToken>,
|
||||
}
|
||||
|
||||
pub fn set_persistent(service_id: &str, is_persistent: bool) -> Result<()> {
|
||||
@@ -556,7 +594,11 @@ pub fn set_persistent(service_id: &str, is_persistent: bool) -> Result<()> {
|
||||
}
|
||||
|
||||
impl TerminalServiceProxy {
|
||||
pub fn new(service_id: String, is_persistent: Option<bool>) -> Self {
|
||||
pub fn new(
|
||||
service_id: String,
|
||||
is_persistent: Option<bool>,
|
||||
_user_token: Option<UserToken>,
|
||||
) -> Self {
|
||||
// Get persistence from the service if it exists
|
||||
let is_persistent =
|
||||
is_persistent.unwrap_or(if let Some(service) = get_service(&service_id) {
|
||||
@@ -567,6 +609,8 @@ impl TerminalServiceProxy {
|
||||
TerminalServiceProxy {
|
||||
service_id,
|
||||
is_persistent,
|
||||
#[cfg(target_os = "windows")]
|
||||
user_token: _user_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -670,7 +714,14 @@ impl TerminalServiceProxy {
|
||||
// Use default shell for the platform
|
||||
let shell = get_default_shell();
|
||||
log::debug!("Using shell: {}", shell);
|
||||
let cmd = CommandBuilder::new(&shell);
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut cmd = CommandBuilder::new(&shell);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
if let Some(token) = &self.user_token {
|
||||
cmd.set_user_token(*token as _);
|
||||
}
|
||||
|
||||
log::debug!("Spawning shell process...");
|
||||
let child = pty_pair
|
||||
|
||||
Reference in New Issue
Block a user