sing-box/protocol/tailscale/tailssh/server_helper_windows.go
2026-06-25 17:38:51 +08:00

98 lines
2.8 KiB
Go

//go:build with_gvisor && windows
package tailssh
import (
"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"
"golang.org/x/sys/windows"
)
func isPrivilegedUser() bool {
return winutil.IsCurrentProcessElevated()
}
// requestedUserMatchesProcess reports whether the ACL-mapped user is the same Windows
// account the sing-box process runs as. Windows has no impersonation wired up, so a
// session always runs with the process identity; this is the only case where the
// identity it runs as equals the requested one.
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
}
// verifyShellIdentity refuses a spawned shell/SFTP session whose ACL-mapped user differs
// from the process identity it would actually run as, since Windows has no impersonation.
func verifyShellIdentity(localUser *adapter.PlatformUser) error {
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 ""
}
func defaultPathEnv() string {
systemRoot := os.Getenv("SystemRoot")
return systemRoot + `\system32;` + systemRoot + `;` + systemRoot + `\System32\Wbem`
}
func userSocketDirectories(localUser *adapter.PlatformUser) []string {
return []string{localUser.HomeDir, os.TempDir()}
}
// prepareAgentSocket is a no-op on Windows: shells run as the server identity, so
// the agent socket needs no ownership change.
func prepareAgentSocket(_ string, _, _ int) error {
return nil
}
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
}
func sftpCommand(sftpPath string) string {
return sftpPath
}
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
}
}