From 12cbb10d1185b912e1cbe212af652f424cbd042a Mon Sep 17 00:00:00 2001 From: Mitch Gaffigan Date: Sun, 13 Sep 2026 13:00:30 -0500 Subject: [PATCH] Add AttachToConsoleSession option for Windows Windows sshd runs every session in the service session, which has no access to the interactive desktop. A GUI application launched over SSH starts invisibly and cannot be seen or clicked, so tooling that expects to drive a desktop over SSH does not work. The motivating case is VS Code Remote SSH from a Mac into a Windows VM, such as one hosted by Parallels. Today, pressing F5 on a GUI project runs the app where nobody can see it. With this option enabled the app appears on the desktop already visible in the VM window and the debugger attaches to it, so remote debugging of GUI applications works the way it does locally. Add an opt-in sshd_config keyword that runs the post-auth session process inside the user's existing physical console session. The default is no, preserving current behavior. The process launch and authentication paths are otherwise unchanged. The console session's token is substituted for the authenticated one only when both carry the same user SID, and only for the post-auth spawn; the pre-auth child and AuthorizedKeysCommand share the same helper and continue to run in the service session. --- contrib/win32/openssh/win32iocompat.vcxproj | 1 + .../openssh/win32iocompat.vcxproj.filters | 1 + contrib/win32/win32compat/misc_internal.h | 2 + contrib/win32/win32compat/spawn-ext.c | 18 +- contrib/win32/win32compat/w32api_proxies.c | 67 ++++++ contrib/win32/win32compat/w32api_proxies.h | 5 +- contrib/win32/win32compat/w32fd.c | 12 + contrib/win32/win32compat/win32_session.c | 205 ++++++++++++++++++ servconf.c | 17 ++ servconf.h | 2 + sshd-session.c | 5 + sshd_config.0 | 47 ++-- sshd_config.5 | 10 + 13 files changed, 370 insertions(+), 22 deletions(-) create mode 100644 contrib/win32/win32compat/win32_session.c diff --git a/contrib/win32/openssh/win32iocompat.vcxproj b/contrib/win32/openssh/win32iocompat.vcxproj index 5573140daa29..3cceb5598666 100644 --- a/contrib/win32/openssh/win32iocompat.vcxproj +++ b/contrib/win32/openssh/win32iocompat.vcxproj @@ -315,6 +315,7 @@ + diff --git a/contrib/win32/openssh/win32iocompat.vcxproj.filters b/contrib/win32/openssh/win32iocompat.vcxproj.filters index c727e636bca0..2c1349056b8b 100644 --- a/contrib/win32/openssh/win32iocompat.vcxproj.filters +++ b/contrib/win32/openssh/win32iocompat.vcxproj.filters @@ -22,6 +22,7 @@ + diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index 5a43a992e820..9881e0faa146 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -67,6 +67,8 @@ void to_lower_case(char *s); void to_wlower_case(wchar_t *s); HANDLE get_user_token(const char* user, int impersonation); int load_user_profile(HANDLE user_token, char* user); +extern int attach_to_console_session; +HANDLE get_console_session_token(HANDLE authenticated_token); int create_directory_withsddl(wchar_t *path, wchar_t *sddl, BOOL check_permissions); int is_absolute_path(const char *); int file_in_chroot_jail(HANDLE); diff --git a/contrib/win32/win32compat/spawn-ext.c b/contrib/win32/win32compat/spawn-ext.c index 19b569ff892d..54b268ada59b 100644 --- a/contrib/win32/win32compat/spawn-ext.c +++ b/contrib/win32/win32compat/spawn-ext.c @@ -14,7 +14,8 @@ __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actio int r = -1; /* use token generated from password auth if already present */ HANDLE user_token = NULL; - + int on_console_session = FALSE; + if (password_auth_token) user_token = password_auth_token; else if (sspi_auth_user) @@ -25,7 +26,20 @@ __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actio errno = EOTHER; return -1; } - if (strcmp(user, "sshd")) + + /* if configured, run inside the user's existing console session */ + if (attach_to_console_session) { + HANDLE console_token = get_console_session_token(user_token); + + if (console_token != NULL) { + CloseHandle(user_token); + user_token = console_token; + on_console_session = TRUE; + } + } + + /* a console user's profile is already loaded by their interactive logon */ + if (!on_console_session && strcmp(user, "sshd")) load_user_profile(user_token, user); r = posix_spawn_internal(pidp, path, file_actions, attrp, argv, envp, user_token, TRUE); diff --git a/contrib/win32/win32compat/w32api_proxies.c b/contrib/win32/win32compat/w32api_proxies.c index 704fd8bebaae..7fc61fef8586 100644 --- a/contrib/win32/win32compat/w32api_proxies.c +++ b/contrib/win32/win32compat/w32api_proxies.c @@ -105,6 +105,17 @@ load_secur32() return s_hm_secur32; } +static HMODULE +load_wtsapi32() +{ + static HMODULE s_hm_wtsapi32 = NULL; + + if (!s_hm_wtsapi32) + s_hm_wtsapi32 = load_module(L"wtsapi32.dll"); + + return s_hm_wtsapi32; +} + static HMODULE load_ntdll() { @@ -259,6 +270,62 @@ ULONG pRtlNtStatusToDosError(NTSTATUS status) return pRtlNtStatusToDosError(status); } +BOOL pWTSQuerySessionInformationW(HANDLE server, DWORD session_id, + WTS_INFO_CLASS info_class, + LPWSTR *buffer, + DWORD *bytes_returned) +{ + HMODULE hm = NULL; + typedef BOOL(WINAPI *WTSQuerySessionInformationWType)(HANDLE, DWORD, WTS_INFO_CLASS, LPWSTR *, DWORD *); + static WTSQuerySessionInformationWType s_pWTSQuerySessionInformationW = NULL; + + if (!s_pWTSQuerySessionInformationW) { + if ((hm = load_wtsapi32()) == NULL) + return FALSE; + + if ((s_pWTSQuerySessionInformationW = (WTSQuerySessionInformationWType) + get_proc_address(hm, "WTSQuerySessionInformationW")) == NULL) + return FALSE; + } + + return s_pWTSQuerySessionInformationW(server, session_id, info_class, buffer, bytes_returned); +} + +BOOL pWTSQueryUserToken(ULONG session_id, PHANDLE token) +{ + HMODULE hm = NULL; + typedef BOOL(WINAPI *WTSQueryUserTokenType)(ULONG, PHANDLE); + static WTSQueryUserTokenType s_pWTSQueryUserToken = NULL; + + if (!s_pWTSQueryUserToken) { + if ((hm = load_wtsapi32()) == NULL) + return FALSE; + + if ((s_pWTSQueryUserToken = (WTSQueryUserTokenType) + get_proc_address(hm, "WTSQueryUserToken")) == NULL) + return FALSE; + } + + return s_pWTSQueryUserToken(session_id, token); +} + +void pWTSFreeMemory(PVOID memory) +{ + HMODULE hm = NULL; + typedef void(WINAPI *WTSFreeMemoryType)(PVOID); + static WTSFreeMemoryType s_pWTSFreeMemory = NULL; + + if (!s_pWTSFreeMemory) { + if ((hm = load_wtsapi32()) == NULL) + return; + + if ((s_pWTSFreeMemory = (WTSFreeMemoryType)get_proc_address(hm, "WTSFreeMemory")) == NULL) + return; + } + + s_pWTSFreeMemory(memory); +} + NTSTATUS pLsaClose(LSA_HANDLE lsa_h) { HMODULE hm = NULL; diff --git a/contrib/win32/win32compat/w32api_proxies.h b/contrib/win32/win32compat/w32api_proxies.h index a47d83b9c3e5..1971d580a6db 100644 --- a/contrib/win32/win32compat/w32api_proxies.h +++ b/contrib/win32/win32compat/w32api_proxies.h @@ -11,6 +11,7 @@ #define SECURITY_WIN32 #include #include +#include BOOL pLogonUserExExW(wchar_t *, wchar_t *, wchar_t *, DWORD, DWORD, PTOKEN_GROUPS, PHANDLE, PSID *, PVOID *, LPDWORD, PQUOTA_LIMITS); BOOLEAN pTranslateNameW(LPCWSTR, EXTENDED_NAME_FORMAT, EXTENDED_NAME_FORMAT, LPWSTR, PULONG); @@ -20,5 +21,7 @@ NTSTATUS pLsaAddAccountRights(LSA_HANDLE, PSID, PLSA_UNICODE_STRING, ULONG); ULONG pRtlNtStatusToDosError(NTSTATUS); NTSTATUS pLsaClose(LSA_HANDLE); NTSTATUS pLsaRemoveAccountRights(LSA_HANDLE, PSID, BOOLEAN, PLSA_UNICODE_STRING, ULONG); - +BOOL pWTSQuerySessionInformationW(HANDLE, DWORD, WTS_INFO_CLASS, LPWSTR *, DWORD *); +BOOL pWTSQueryUserToken(ULONG, PHANDLE); +void pWTSFreeMemory(PVOID); diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c index b4c436864dc4..0b2a75f03b0a 100644 --- a/contrib/win32/win32compat/w32fd.c +++ b/contrib/win32/win32compat/w32fd.c @@ -1146,7 +1146,19 @@ spawn_child_internal(const char* cmd, char *const argv[], HANDLE in, HANDLE out, if (as_user) { debug3("spawning %ls as user", t); LPVOID lpEnvironment = NULL; + DWORD token_session = 0, my_session = 0, info_len = 0; + /* lpDesktop is not const, so this cannot be a literal */ + static wchar_t winsta0_default[] = L"WinSta0\\Default"; wchar_t* as_user_name = get_username_from_token(as_user); + + /* a process in another session cannot inherit our window station and desktop */ + if (GetTokenInformation(as_user, TokenSessionId, &token_session, sizeof(token_session), &info_len) && + ProcessIdToSessionId(GetCurrentProcessId(), &my_session) && + token_session != my_session) { + debug3("spawning into session %d (from session %d) on %ls", token_session, my_session, winsta0_default); + si.lpDesktop = winsta0_default; + } + if (as_user_name) { if (wcsncmp(L"sshd", as_user_name, wcslen(L"sshd")) != 0) { /* Ignore any names that begin with the service name `sshd`. */ b = CreateEnvironmentBlock(&lpEnvironment, as_user, TRUE); /* Load a user environment block inheriting the current context, thereby passing session state. */ diff --git a/contrib/win32/win32compat/win32_session.c b/contrib/win32/win32compat/win32_session.c new file mode 100644 index 000000000000..a621eefec5b8 --- /dev/null +++ b/contrib/win32/win32compat/win32_session.c @@ -0,0 +1,205 @@ +/* + * Author: Mitch Gaffigan + * + * Support for running a session inside the user's existing physical console + * session (WTS session) instead of the service session. + * + * Copyright (c) 2026 Mitch Gaffigan + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include "w32api_proxies.h" +#include "misc_internal.h" +#include "Debug.h" + +/* set from sshd_config's AttachToConsoleSession, armed only for the post-auth spawn */ +int attach_to_console_session = 0; + +/* union so the SID stays suitably aligned */ +typedef union { + SID sid; + BYTE buf[SECURITY_MAX_SID_SIZE]; +} sid_buf; + +/* returns 1 on success, 0 on failure */ +static int +copy_token_user_sid(HANDLE token, sid_buf *out) +{ + /* union so the sid that follows the TOKEN_USER stays aligned */ + union { + TOKEN_USER token_user; + BYTE buf[sizeof(TOKEN_USER) + SECURITY_MAX_SID_SIZE]; + } u; + DWORD len = 0; + + if (GetTokenInformation(token, TokenUser, &u, sizeof(u), &len) == FALSE) { + error_f("GetTokenInformation(TokenUser) failed with error:%d", GetLastError()); + return 0; + } + + if (CopySid(sizeof(out->buf), &out->sid, u.token_user.User.Sid) == FALSE) { + error_f("CopySid failed with error:%d", GetLastError()); + return 0; + } + + return 1; +} + +static BOOL +tokens_same_user(HANDLE a, HANDLE b) +{ + sid_buf a_sid, b_sid; + + if (!copy_token_user_sid(a, &a_sid) || !copy_token_user_sid(b, &b_sid)) + return FALSE; + + return EqualSid(&a_sid.sid, &b_sid.sid); +} + +/* needs no privilege, so use it to avoid WTSQueryUserToken at the logon screen */ +static int +console_session_is_active(DWORD session_id) +{ + WTS_CONNECTSTATE_CLASS *state = NULL; + DWORD len = 0; + int ret = 0; + + if (pWTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, + WTSConnectState, (LPWSTR *)&state, &len) == FALSE) { + debug3_f("WTSQuerySessionInformationW failed for session:%u error:%d", + session_id, GetLastError()); + return 0; + } + + if (state == NULL || len < sizeof(*state)) { + debug3_f("unexpected WTSConnectState result for session:%u", session_id); + goto done; + } + + if (*state != WTSActive) { + debug_f("nobody is logged on to console session:%u", session_id); + goto done; + } + + ret = 1; +done: + if (state) + pWTSFreeMemory(state); + + return ret; +} + +/* + * WTSQueryUserToken returns the filtered token for an administrator on a UAC + * enabled system. sshd sessions are elevated today, so follow the linked token + * to keep that behavior. Returns the token to use, closing the original if it + * was replaced. + */ +static HANDLE +elevate_token(HANDLE token) +{ + TOKEN_ELEVATION_TYPE elevation_type; + TOKEN_LINKED_TOKEN linked; + HANDLE primary = NULL; + DWORD len = 0; + + if (GetTokenInformation(token, TokenElevationType, &elevation_type, + sizeof(elevation_type), &len) == FALSE) { + debug3_f("GetTokenInformation(TokenElevationType) failed with error:%d", + GetLastError()); + return token; + } + + /* standard users and UAC disabled systems have no linked token */ + if (elevation_type != TokenElevationTypeLimited) + return token; + + if (GetTokenInformation(token, TokenLinkedToken, &linked, sizeof(linked), &len) == FALSE) { + debug_f("GetTokenInformation(TokenLinkedToken) failed with error:%d, " + "continuing with the filtered token", GetLastError()); + return token; + } + + /* the linked token is an impersonation token, we need a primary one */ + if (DuplicateTokenEx(linked.LinkedToken, TOKEN_ALL_ACCESS, NULL, + SecurityImpersonation, TokenPrimary, &primary) == FALSE) { + debug_f("DuplicateTokenEx failed with error:%d, " + "continuing with the filtered token", GetLastError()); + CloseHandle(linked.LinkedToken); + return token; + } + + debug3_f("using the linked elevated token"); + CloseHandle(linked.LinkedToken); + CloseHandle(token); + + return primary; +} + +/* + * Returns a primary token for the physical console session, or NULL when there + * is no such session or it belongs to a user other than authenticated_token. + * The caller owns the returned handle. + */ +HANDLE +get_console_session_token(HANDLE authenticated_token) +{ + HANDLE token = NULL; + DWORD console_session_id; + + console_session_id = WTSGetActiveConsoleSessionId(); + if (console_session_id == 0xFFFFFFFF || console_session_id == 0) { + debug_f("no physical console session is attached"); + return NULL; + } + + if (!console_session_is_active(console_session_id)) + return NULL; + + if (pWTSQueryUserToken(console_session_id, &token) == FALSE) { + DWORD err = GetLastError(); + + if (err == ERROR_PRIVILEGE_NOT_HELD) + error_f("WTSQueryUserToken needs SeTcbPrivilege, ensure the sshd " + "service has TCB privileges"); + else + debug_f("WTSQueryUserToken failed for session:%u error:%d", + console_session_id, err); + + return NULL; + } + + if (!tokens_same_user(token, authenticated_token)) { + debug_f("console session:%u belongs to a different user, not attaching", + console_session_id); + CloseHandle(token); + return NULL; + } + + token = elevate_token(token); + + verbose("attaching session to console session %u", console_session_id); + + return token; +} diff --git a/servconf.c b/servconf.c index eeffe77ce36e..a9ec07823062 100644 --- a/servconf.c +++ b/servconf.c @@ -221,6 +221,7 @@ initialize_server_options(ServerOptions *options) options->sshd_session_path = NULL; options->sshd_auth_path = NULL; options->refuse_connection = -1; + options->attach_to_console_session = -1; } /* Returns 1 if a string option is unset or set to "none" or 0 otherwise. */ @@ -513,6 +514,8 @@ fill_default_server_options(ServerOptions *options) #endif // WINDOWS if (options->refuse_connection == -1) options->refuse_connection = 0; + if (options->attach_to_console_session == -1) + options->attach_to_console_session = 0; assemble_algorithms(options); @@ -596,6 +599,7 @@ typedef enum { sExposeAuthInfo, sRDomain, sPubkeyAuthOptions, sSecurityKeyProvider, sRequiredRSASize, sChannelTimeout, sUnusedConnectionTimeout, sSshdSessionPath, sSshdAuthPath, sRefuseConnection, + sAttachToConsoleSession, sDeprecated, sIgnore, sUnsupported } ServerOpCodes; @@ -765,6 +769,11 @@ static struct { { "sshdsessionpath", sSshdSessionPath, SSHCFG_GLOBAL }, { "sshdauthpath", sSshdAuthPath, SSHCFG_GLOBAL }, { "refuseconnection", sRefuseConnection, SSHCFG_ALL }, +#ifdef WINDOWS + { "attachtoconsolesession", sAttachToConsoleSession, SSHCFG_ALL }, +#else + { "attachtoconsolesession", sUnsupported, SSHCFG_ALL }, +#endif // WINDOWS { NULL, sBadOption, 0 } }; @@ -2824,6 +2833,10 @@ process_server_config_line_depth(ServerOptions *options, char *line, multistate_ptr = multistate_flag; goto parse_multistate; + case sAttachToConsoleSession: + intptr = &options->attach_to_console_session; + goto parse_flag; + case sDeprecated: case sIgnore: case sUnsupported: @@ -3044,6 +3057,7 @@ copy_set_server_options(ServerOptions *dst, ServerOptions *src, int preauth) M_CP_INTOPT(required_rsa_size); M_CP_INTOPT(unused_connection_timeout); M_CP_INTOPT(refuse_connection); + M_CP_INTOPT(attach_to_console_session); /* * The bind_mask is a mode_t that may be unsigned, so we can't use @@ -3430,6 +3444,9 @@ dump_config(ServerOptions *o) dump_cfg_fmtint(sFingerprintHash, o->fingerprint_hash); dump_cfg_fmtint(sExposeAuthInfo, o->expose_userauth_info); dump_cfg_fmtint(sRefuseConnection, o->refuse_connection); +#ifdef WINDOWS + dump_cfg_fmtint(sAttachToConsoleSession, o->attach_to_console_session); +#endif // WINDOWS /* string arguments */ dump_cfg_string(sPidFile, o->pid_file); diff --git a/servconf.h b/servconf.h index 9beb90fae3da..adf4342301aa 100644 --- a/servconf.h +++ b/servconf.h @@ -252,6 +252,8 @@ typedef struct { char *sshd_auth_path; int refuse_connection; + + int attach_to_console_session; /* Windows only */ } ServerOptions; /* Information about the incoming connection as used by Match */ diff --git a/sshd-session.c b/sshd-session.c index 623c20eba2df..40274af2a844 100644 --- a/sshd-session.c +++ b/sshd-session.c @@ -126,6 +126,7 @@ /* Privilege separation related spawn fds */ #ifdef WINDOWS #define PRIVSEP_AUTH_MIN_FREE_FD (PRIVSEP_LOG_FD + 1) +extern int attach_to_console_session; #endif /* WINDOWS */ extern char *__progname; @@ -842,6 +843,10 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt) fatal("posix_spawn initialization failed"); char** argv = privsep_child_cmdline(); +#ifdef WINDOWS + /* arm for this spawn only, the pre-auth child stays in the service session */ + attach_to_console_session = options.attach_to_console_session; +#endif /* WINDOWS */ if (__posix_spawn_asuser(&pmonitor->m_pid, argv[0], &actions, NULL, argv, NULL, authctxt->pw->pw_name) != 0) fatal("fork of unprivileged child failed"); posix_spawn_file_actions_destroy(&actions); diff --git a/sshd_config.0 b/sshd_config.0 index 2f77b4f4c0b6..1a0934dfdfef 100644 --- a/sshd_config.0 +++ b/sshd_config.0 @@ -87,6 +87,15 @@ DESCRIPTION This keyword may appear multiple times in sshd_config with each instance appending to the list. + AttachToConsoleSession + Windows only. Specifies whether a session should run inside the + authenticated user's existing physical console session rather + than in the service session, so that processes it starts are + visible on that user's desktop. The console session is used only + when the same user that authenticated is logged on to it; + otherwise the session runs in the service session. The default + is no. + AuthenticationMethods Specifies the authentication methods that must be successfully completed for a user to be granted access. This option must be @@ -780,25 +789,25 @@ DESCRIPTION Only a subset of keywords may be used on the lines following a Match keyword. Available keywords are AcceptEnv, AllowAgentForwarding, AllowGroups, AllowStreamLocalForwarding, - AllowTcpForwarding, AllowUsers, AuthenticationMethods, - AuthorizedKeysCommand, AuthorizedKeysCommandUser, - AuthorizedKeysFile, AuthorizedPrincipalsCommand, - AuthorizedPrincipalsCommandUser, AuthorizedPrincipalsFile, - Banner, CASignatureAlgorithms, ChannelTimeout, ChrootDirectory, - ClientAliveCountMax, ClientAliveInterval, DenyGroups, DenyUsers, - DisableForwarding, ExposeAuthInfo, ForceCommand, GatewayPorts, - GSSAPIAuthentication, HostbasedAcceptedAlgorithms, - HostbasedAuthentication, HostbasedUsesNameFromPacketOnly, - IgnoreRhosts, Include, IPQoS, KbdInteractiveAuthentication, - KerberosAuthentication, LogLevel, MaxAuthTries, MaxSessions, - PAMServiceName, PasswordAuthentication, PermitEmptyPasswords, - PermitListen, PermitOpen, PermitRootLogin, PermitTTY, - PermitTunnel, PermitUserRC, PubkeyAcceptedAlgorithms, - PubkeyAuthentication, PubkeyAuthOptions, RefuseConnection, - RekeyLimit, RevokedKeys, RDomain, SetEnv, StreamLocalBindMask, - StreamLocalBindUnlink, TrustedUserCAKeys, - UnusedConnectionTimeout, X11DisplayOffset, X11Forwarding and - X11UseLocalhost. + AllowTcpForwarding, AllowUsers, AttachToConsoleSession, + AuthenticationMethods, AuthorizedKeysCommand, + AuthorizedKeysCommandUser, AuthorizedKeysFile, + AuthorizedPrincipalsCommand, AuthorizedPrincipalsCommandUser, + AuthorizedPrincipalsFile, Banner, CASignatureAlgorithms, + ChannelTimeout, ChrootDirectory, ClientAliveCountMax, + ClientAliveInterval, DenyGroups, DenyUsers, DisableForwarding, + ExposeAuthInfo, ForceCommand, GatewayPorts, GSSAPIAuthentication, + HostbasedAcceptedAlgorithms, HostbasedAuthentication, + HostbasedUsesNameFromPacketOnly, IgnoreRhosts, Include, IPQoS, + KbdInteractiveAuthentication, KerberosAuthentication, LogLevel, + MaxAuthTries, MaxSessions, PAMServiceName, + PasswordAuthentication, PermitEmptyPasswords, PermitListen, + PermitOpen, PermitRootLogin, PermitTTY, PermitTunnel, + PermitUserRC, PubkeyAcceptedAlgorithms, PubkeyAuthentication, + PubkeyAuthOptions, RefuseConnection, RekeyLimit, RevokedKeys, + RDomain, SetEnv, StreamLocalBindMask, StreamLocalBindUnlink, + TrustedUserCAKeys, UnusedConnectionTimeout, X11DisplayOffset, + X11Forwarding and X11UseLocalhost. MaxAuthTries Specifies the maximum number of authentication attempts permitted diff --git a/sshd_config.5 b/sshd_config.5 index c07717375d90..9fc6a69e0fae 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -183,6 +183,15 @@ for more information on patterns. This keyword may appear multiple times in .Nm with each instance appending to the list. +.It Cm AttachToConsoleSession +Windows only. +Specifies whether a session should run inside the authenticated user's +existing physical console session rather than in the service session, +so that processes it starts are visible on that user's desktop. +The console session is used only when the same user that authenticated is +logged on to it; otherwise the session runs in the service session. +The default is +.Cm no . .It Cm AuthenticationMethods Specifies the authentication methods that must be successfully completed for a user to be granted access. @@ -1288,6 +1297,7 @@ Available keywords are .Cm AllowStreamLocalForwarding , .Cm AllowTcpForwarding , .Cm AllowUsers , +.Cm AttachToConsoleSession , .Cm AuthenticationMethods , .Cm AuthorizedKeysCommand , .Cm AuthorizedKeysCommandUser ,