diff --git a/go.mod b/go.mod index 6397887..8e64671 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/trzsz/tsshd go 1.21 require ( + github.com/UserExistsError/conpty v0.1.4 github.com/creack/pty v1.1.21 github.com/quic-go/quic-go v0.45.1 github.com/trzsz/go-arg v1.5.3 @@ -15,17 +16,18 @@ require ( require ( github.com/alexflint/go-scalar v1.2.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 // indirect + github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect - github.com/klauspost/reedsolomon v1.12.2 // indirect + github.com/klauspost/reedsolomon v1.12.3 // indirect github.com/onsi/ginkgo/v2 v2.19.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/templexxx/cpu v0.1.1-0.20240303154708-598a14b050c5 // indirect github.com/templexxx/xorsimd v0.4.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect go.uber.org/mock v0.4.0 // indirect - golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.27.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/tools v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 32ba523..7e2b8fd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to= +github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -33,12 +35,12 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0 h1:e+8XbKB6IMn8A4OAyZccO4pYfB3s7bt6azNIPE7AnPg= -github.com/google/pprof v0.0.0-20240625030939-27f56978b8b0/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g= +github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/reedsolomon v1.12.2 h1:TC0hlL/tTRxiMNnqHCzKsY11E0fIIKGCoZ2vQoPKIEM= -github.com/klauspost/reedsolomon v1.12.2/go.mod h1:nEi5Kjb6QqtbofI6s+cbG/j1da11c96IBYBSnVGtuBs= +github.com/klauspost/reedsolomon v1.12.3 h1:tzUznbfc3OFwJaTebv/QdhnFf2Xvb7gZ24XaHLBPmdc= +github.com/klauspost/reedsolomon v1.12.3/go.mod h1:3K5rXwABAvzGeR01r6pWZieUALXO/Tq7bFKGIb4m4WI= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= @@ -76,8 +78,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY= -golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -102,6 +104,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -115,8 +118,8 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/internal/conpty/conpty.go b/internal/conpty/conpty.go deleted file mode 100644 index 2c55ea3..0000000 --- a/internal/conpty/conpty.go +++ /dev/null @@ -1,363 +0,0 @@ -//go:build windows -// +build windows - -// Forked From: https://github.com/UserExistsError/conpty - -package conpty - -import ( - "context" - "errors" - "fmt" - "unicode/utf16" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - modKernel32 = windows.NewLazySystemDLL("kernel32.dll") - fCreatePseudoConsole = modKernel32.NewProc("CreatePseudoConsole") - fResizePseudoConsole = modKernel32.NewProc("ResizePseudoConsole") - fClosePseudoConsole = modKernel32.NewProc("ClosePseudoConsole") - fInitializeProcThreadAttributeList = modKernel32.NewProc("InitializeProcThreadAttributeList") - fUpdateProcThreadAttribute = modKernel32.NewProc("UpdateProcThreadAttribute") - ErrConPtyUnsupported = errors.New("ConPty is not available on this version of Windows") -) - -func IsConPtyAvailable() bool { - return fCreatePseudoConsole.Find() == nil && - fResizePseudoConsole.Find() == nil && - fClosePseudoConsole.Find() == nil && - fInitializeProcThreadAttributeList.Find() == nil && - fUpdateProcThreadAttribute.Find() == nil -} - -const ( - _STILL_ACTIVE uint32 = 259 - _S_OK uintptr = 0 - _PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE uintptr = 0x20016 - defaultConsoleWidth = 80 // in characters - defaultConsoleHeight = 40 // in characters -) - -type _COORD struct { - X, Y int16 -} - -func (c *_COORD) Pack() uintptr { - return uintptr((int32(c.Y) << 16) | int32(c.X)) -} - -type _HPCON windows.Handle - -type handleIO struct { - handle windows.Handle -} - -func (h *handleIO) Read(p []byte) (int, error) { - var numRead uint32 = 0 - err := windows.ReadFile(h.handle, p, &numRead, nil) - return int(numRead), err -} - -func (h *handleIO) Write(p []byte) (int, error) { - var numWritten uint32 = 0 - err := windows.WriteFile(h.handle, p, &numWritten, nil) - return int(numWritten), err -} - -func (h *handleIO) Close() error { - return windows.CloseHandle(h.handle) -} - -type ConPty struct { - hpc _HPCON - pi *windows.ProcessInformation - ptyIn, ptyOut, cmdIn, cmdOut *handleIO -} - -func win32ClosePseudoConsole(hPc _HPCON) { - if fClosePseudoConsole.Find() != nil { - return - } - // this kills the attached process. there is no return value. - fClosePseudoConsole.Call(uintptr(hPc)) -} - -func win32ResizePseudoConsole(hPc _HPCON, coord *_COORD) error { - if fResizePseudoConsole.Find() != nil { - return fmt.Errorf("ResizePseudoConsole not found") - } - ret, _, _ := fResizePseudoConsole.Call(uintptr(hPc), coord.Pack()) - if ret != _S_OK { - return fmt.Errorf("ResizePseudoConsole failed with status 0x%x", ret) - } - return nil -} - -func win32CreatePseudoConsole(c *_COORD, hIn, hOut windows.Handle) (_HPCON, error) { - if fCreatePseudoConsole.Find() != nil { - return 0, fmt.Errorf("CreatePseudoConsole not found") - } - var hPc _HPCON - ret, _, _ := fCreatePseudoConsole.Call( - c.Pack(), - uintptr(hIn), - uintptr(hOut), - 0, - uintptr(unsafe.Pointer(&hPc))) - if ret != _S_OK { - return 0, fmt.Errorf("CreatePseudoConsole() failed with status 0x%x", ret) - } - return hPc, nil -} - -type _StartupInfoEx struct { - startupInfo windows.StartupInfo - attributeList []byte -} - -func getStartupInfoExForPTY(hpc _HPCON) (*_StartupInfoEx, error) { - if fInitializeProcThreadAttributeList.Find() != nil { - return nil, fmt.Errorf("InitializeProcThreadAttributeList not found") - } - if fUpdateProcThreadAttribute.Find() != nil { - return nil, fmt.Errorf("UpdateProcThreadAttribute not found") - } - var siEx _StartupInfoEx - siEx.startupInfo.Cb = uint32(unsafe.Sizeof(windows.StartupInfo{}) + unsafe.Sizeof(&siEx.attributeList[0])) - siEx.startupInfo.Flags |= windows.STARTF_USESTDHANDLES - var size uintptr - - // first call is to get required size. this should return false. - ret, _, _ := fInitializeProcThreadAttributeList.Call(0, 1, 0, uintptr(unsafe.Pointer(&size))) - siEx.attributeList = make([]byte, size, size) - ret, _, err := fInitializeProcThreadAttributeList.Call( - uintptr(unsafe.Pointer(&siEx.attributeList[0])), - 1, - 0, - uintptr(unsafe.Pointer(&size))) - if ret != 1 { - return nil, fmt.Errorf("InitializeProcThreadAttributeList: %v", err) - } - - ret, _, err = fUpdateProcThreadAttribute.Call( - uintptr(unsafe.Pointer(&siEx.attributeList[0])), - 0, - _PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, - uintptr(hpc), - unsafe.Sizeof(hpc), - 0, - 0) - if ret != 1 { - return nil, fmt.Errorf("InitializeProcThreadAttributeList: %v", err) - } - return &siEx, nil -} - -func createConsoleProcessAttachedToPTY(hpc _HPCON, commandLine, workDir string, env []string) (*windows.ProcessInformation, error) { - cmdLine, err := windows.UTF16PtrFromString(commandLine) - if err != nil { - return nil, err - } - var currentDirectory *uint16 - if workDir != "" { - currentDirectory, err = windows.UTF16PtrFromString(workDir) - if err != nil { - return nil, err - } - } - var envBlock *uint16 - flags := uint32(windows.EXTENDED_STARTUPINFO_PRESENT) - if env != nil { - flags |= uint32(windows.CREATE_UNICODE_ENVIRONMENT) - envBlock = createEnvBlock(env) - } - siEx, err := getStartupInfoExForPTY(hpc) - if err != nil { - return nil, err - } - var pi windows.ProcessInformation - err = windows.CreateProcess( - nil, // use this if no args - cmdLine, - nil, - nil, - false, // inheritHandle - flags, - envBlock, - currentDirectory, - &siEx.startupInfo, - &pi) - if err != nil { - return nil, err - } - return &pi, nil -} - -// createEnvBlock refers to syscall.createEnvBlock in go/src/syscall/exec_windows.go -// Sourced From: https://github.com/creack/pty/pull/155 -func createEnvBlock(envv []string) *uint16 { - if len(envv) == 0 { - return &utf16.Encode([]rune("\x00\x00"))[0] - } - length := 0 - for _, s := range envv { - length += len(s) + 1 - } - length += 1 - - b := make([]byte, length) - i := 0 - for _, s := range envv { - l := len(s) - copy(b[i:i+l], []byte(s)) - copy(b[i+l:i+l+1], []byte{0}) - i = i + l + 1 - } - copy(b[i:i+1], []byte{0}) - - return &utf16.Encode([]rune(string(b)))[0] -} - -// This will only return the first error. -func closeHandles(handles ...windows.Handle) error { - var err error - for _, h := range handles { - if h != windows.InvalidHandle { - if err == nil { - err = windows.CloseHandle(h) - } else { - windows.CloseHandle(h) - } - } - } - return err -} - -// Close all open handles and terminate the process. -func (cpty *ConPty) Close() error { - // there is no return code - win32ClosePseudoConsole(cpty.hpc) - return closeHandles( - cpty.pi.Process, - cpty.pi.Thread, - cpty.ptyIn.handle, - cpty.ptyOut.handle, - cpty.cmdIn.handle, - cpty.cmdOut.handle) -} - -// Wait for the process to exit and return the exit code. If context is canceled, -// Wait() will return STILL_ACTIVE and an error indicating the context was canceled. -func (cpty *ConPty) Wait(ctx context.Context) (uint32, error) { - var exitCode uint32 = _STILL_ACTIVE - for { - if err := ctx.Err(); err != nil { - return _STILL_ACTIVE, fmt.Errorf("wait canceled: %v", err) - } - ret, _ := windows.WaitForSingleObject(cpty.pi.Process, 1000) - if ret != uint32(windows.WAIT_TIMEOUT) { - err := windows.GetExitCodeProcess(cpty.pi.Process, &exitCode) - return exitCode, err - } - } -} - -func (cpty *ConPty) Resize(width, height int) error { - coords := _COORD{ - int16(width), - int16(height), - } - - return win32ResizePseudoConsole(cpty.hpc, &coords) -} - -func (cpty *ConPty) Read(p []byte) (int, error) { - return cpty.cmdOut.Read(p) -} - -func (cpty *ConPty) Write(p []byte) (int, error) { - return cpty.cmdIn.Write(p) -} - -func (cpty *ConPty) Pid() int { - return int(cpty.pi.ProcessId) -} - -type conPtyArgs struct { - coords _COORD - workDir string - env []string -} - -type ConPtyOption func(args *conPtyArgs) - -func ConPtyDimensions(width, height int) ConPtyOption { - return func(args *conPtyArgs) { - args.coords.X = int16(width) - args.coords.Y = int16(height) - } -} - -func ConPtyWorkDir(workDir string) ConPtyOption { - return func(args *conPtyArgs) { - args.workDir = workDir - } -} - -func ConPtyEnv(env []string) ConPtyOption { - return func(args *conPtyArgs) { - args.env = env - } -} - -// Start a new process specified in `commandLine` and attach a pseudo console using the Windows -// ConPty API. If ConPty is not available, ErrConPtyUnsupported will be returned. -// -// On successful return, an instance of ConPty is returned. You must call Close() on this to release -// any resources associated with the process. To get the exit code of the process, you can call Wait(). -func Start(commandLine string, options ...ConPtyOption) (*ConPty, error) { - if !IsConPtyAvailable() { - return nil, ErrConPtyUnsupported - } - args := &conPtyArgs{ - coords: _COORD{defaultConsoleWidth, defaultConsoleHeight}, - } - for _, opt := range options { - opt(args) - } - - var cmdIn, cmdOut, ptyIn, ptyOut windows.Handle - if err := windows.CreatePipe(&ptyIn, &cmdIn, nil, 0); err != nil { - return nil, fmt.Errorf("CreatePipe: %v", err) - } - if err := windows.CreatePipe(&cmdOut, &ptyOut, nil, 0); err != nil { - closeHandles(ptyIn, cmdIn) - return nil, fmt.Errorf("CreatePipe: %v", err) - } - - hPc, err := win32CreatePseudoConsole(&args.coords, ptyIn, ptyOut) - if err != nil { - closeHandles(ptyIn, ptyOut, cmdIn, cmdOut) - return nil, err - } - - pi, err := createConsoleProcessAttachedToPTY(hPc, commandLine, args.workDir, args.env) - if err != nil { - closeHandles(ptyIn, ptyOut, cmdIn, cmdOut) - win32ClosePseudoConsole(hPc) - return nil, fmt.Errorf("Failed to create console process: %v", err) - } - - cpty := &ConPty{ - hpc: hPc, - pi: pi, - ptyIn: &handleIO{ptyIn}, - ptyOut: &handleIO{ptyOut}, - cmdIn: &handleIO{cmdIn}, - cmdOut: &handleIO{cmdOut}, - } - return cpty, nil -} diff --git a/tsshd/utils_windows.go b/tsshd/utils_windows.go index 6430343..cc5cd70 100644 --- a/tsshd/utils_windows.go +++ b/tsshd/utils_windows.go @@ -31,7 +31,7 @@ import ( "strings" "syscall" - "github.com/trzsz/tsshd/internal/conpty" + "github.com/UserExistsError/conpty" "golang.org/x/sys/windows" )