2026-05-28 18:42:58 +08:00
//go:build with_gvisor && windows
package tailssh
import (
2026-07-15 10:14:53 +08:00
"crypto/rand"
"fmt"
"net"
2026-05-28 18:42:58 +08:00
"os"
"os/user"
"strings"
gliderssh "github.com/sagernet/gliderssh"
"github.com/sagernet/sing-box/adapter"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/tailscale/util/winutil"
2026-07-15 10:14:53 +08:00
winio "github.com/tailscale/go-winio"
2026-05-28 18:42:58 +08:00
"golang.org/x/sys/windows"
)
func isPrivilegedUser () bool {
return winutil . IsCurrentProcessElevated ()
}
func requestedUserMatchesProcess ( localUser * adapter . PlatformUser ) ( bool , error ) {
tokenUser , err := windows . GetCurrentProcessToken (). GetTokenUser ()
if err != nil {
return false , E . Cause ( err , "query process token user" )
}
requested , err := user . Lookup ( localUser . Username )
if err != nil {
return false , E . Cause ( err , "lookup requested user" )
}
// On Windows os/user reports SIDs in the Uid field.
return strings . EqualFold ( tokenUser . User . Sid . String (), requested . Uid ), nil
}
2026-07-15 10:14:53 +08:00
func verifyShellIdentity ( platformInterface adapter . PlatformInterface , localUser * adapter . PlatformUser ) error {
if platformInterface != nil && platformInterface . UsePlatformShell () {
_ , loaded := platformInterface .( windowsUserTokenProvider )
if loaded {
return nil
}
}
2026-05-28 18:42:58 +08:00
match , err := requestedUserMatchesProcess ( localUser )
if err != nil {
return err
}
if ! match {
return E . New ( "Windows SSH sessions run as the sing-box process identity; mapping to a different local user (" , localUser . Username , ") requires impersonation, which is not implemented" )
}
return nil
}
func systemHostKeyPath () string {
return ""
}
2026-07-15 10:14:53 +08:00
func defaultPathEnv ( platformInterface adapter . PlatformInterface ) string {
if platformInterface != nil && platformInterface . UsePlatformShell () {
return ""
}
2026-05-28 18:42:58 +08:00
systemRoot := os . Getenv ( "SystemRoot" )
return systemRoot + `\system32;` + systemRoot + `;` + systemRoot + `\System32\Wbem`
}
func userSocketDirectories ( localUser * adapter . PlatformUser ) [] string {
return [] string { localUser . HomeDir , os . TempDir ()}
}
2026-07-15 10:14:53 +08:00
func newAgentListener ( localUser * adapter . PlatformUser ) ( net . Listener , error ) {
requestedUser , err := user . Lookup ( localUser . Username )
if err != nil {
return nil , E . Cause ( err , "lookup requested user" )
}
pipePath := `\\.\pipe\sing-box-tailssh-agent-` + rand . Text ()
securityDescriptor := fmt . Sprintf ( `D:P(A;;GA;;;SY)(A;;GRGW;;;%s)` , requestedUser . Uid )
listener , err := winio . ListenPipe ( pipePath , & winio . PipeConfig {
SecurityDescriptor : securityDescriptor ,
InputBufferSize : 64 * 1024 ,
OutputBufferSize : 64 * 1024 ,
})
if err != nil {
return nil , E . Cause ( err , "listen on agent pipe" )
}
return listener , nil
2026-05-28 18:42:58 +08:00
}
func platformEnvironment ( localUser * adapter . PlatformUser ) [] string {
var env [] string
env = append ( env , "USERPROFILE=" + localUser . HomeDir )
drive , path , found := strings . Cut ( localUser . HomeDir , `\` )
if found && len ( drive ) == 2 && drive [ 1 ] == ':' {
env = append ( env , "HOMEDRIVE=" + drive )
env = append ( env , `HOMEPATH=\` + path )
}
env = append ( env , "SYSTEMROOT=" + os . Getenv ( "SystemRoot" ))
return env
}
2026-07-15 10:14:53 +08:00
func sftpCommand ( sftpPath , shell string ) string {
if isPowerShell ( shell ) {
return `& "` + sftpPath + `"`
}
return `"` + sftpPath + `"`
2026-05-28 18:42:58 +08:00
}
func sshSignalToSyscall ( sig gliderssh . Signal ) int {
switch sig {
case gliderssh . SIGINT :
return 2
case gliderssh . SIGTERM :
return 15
case gliderssh . SIGKILL :
return 9
default :
return 0
}
}