Fixes for MSYS2 and CYGWIN term mode; removed mintty_wrapper.sh

This commit is contained in:
Russ Magee 2024-10-27 11:58:22 -07:00
parent 12409319e7
commit e5b6422d70
13 changed files with 132 additions and 121 deletions

View File

@ -108,10 +108,8 @@ reinstall: uninstall install
install: install:
echo "WIN_MSYS:" $(WIN_MSYS) echo "WIN_MSYS:" $(WIN_MSYS)
ifdef WIN_MSYS ifdef WIN_MSYS
cp xs/mintty_wrapper.sh $(INSTPREFIX)/bin/xs cp xs/xs $(INSTPREFIX)/bin/xs
cp xs/mintty_wrapper.sh $(INSTPREFIX)/bin/xc cp xs/xs $(INSTPREFIX)/bin/xc
cp xs/xs $(INSTPREFIX)/bin/_xs
cp xs/xs $(INSTPREFIX)/bin/_xc
echo "Install of xsd server for Windows not yet supported" echo "Install of xsd server for Windows not yet supported"
else else
cp xs/xs $(INSTPREFIX)/bin cp xs/xs $(INSTPREFIX)/bin
@ -120,8 +118,7 @@ else
endif endif
uninstall: uninstall:
rm -f $(INSTPREFIX)/bin/xs $(INSTPREFIX)/bin/xc \ rm -f $(INSTPREFIX)/bin/xs $(INSTPREFIX)/bin/xc
$(INSTPREFIX)/bin/_xs $(INSTPREFIX)/bin/_xc
ifndef $(WIN_MSYS) ifndef $(WIN_MSYS)
rm -f $(INSTPREFIX)/sbin/xsd rm -f $(INSTPREFIX)/sbin/xsd
endif endif

0
auth.go Executable file → Normal file
View File

View File

@ -5,6 +5,7 @@ package xs
import ( import (
"errors" "errors"
"io" "io"
"os"
unix "golang.org/x/sys/unix" unix "golang.org/x/sys/unix"
) )
@ -30,7 +31,8 @@ type State struct {
// MakeRaw put the terminal connected to the given file descriptor into raw // MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be // mode and returns the previous state of the terminal so that it can be
// restored. // restored.
func MakeRaw(fd uintptr) (*State, error) { func MakeRaw(f *os.File) (*State, error) {
fd := f.Fd()
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil { if err != nil {
return nil, err return nil, err
@ -56,8 +58,8 @@ func MakeRaw(fd uintptr) (*State, error) {
// GetState returns the current state of a terminal which may be useful to // GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal. // restore the terminal after a signal.
func GetState(fd uintptr) (*State, error) { func GetState(f *os.File) (*State, error) {
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) termios, err := unix.IoctlGetTermios(int(f.Fd()), ioctlReadTermios)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,9 +69,9 @@ func GetState(fd uintptr) (*State, error) {
// Restore restores the terminal connected to the given file descriptor to a // Restore restores the terminal connected to the given file descriptor to a
// previous state. // previous state.
func Restore(fd uintptr, state *State) error { func Restore(f *os.File, state *State) error {
if state != nil { if state != nil {
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.termios) return unix.IoctlSetTermios(int(f.Fd()), ioctlWriteTermios, &state.termios)
} else { } else {
return errors.New("nil State") return errors.New("nil State")
} }
@ -78,7 +80,8 @@ func Restore(fd uintptr, state *State) error {
// ReadPassword reads a line of input from a terminal without local echo. This // ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice // is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n. // returned does not include the \n.
func ReadPassword(fd uintptr) ([]byte, error) { func ReadPassword(f *os.File) ([]byte, error) {
fd := f.Fd()
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios) termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
// Note the terminal manipulation functions herein are mostly stubs. They // Note the terminal manipulation functions herein are mostly stubs. They
@ -15,10 +16,12 @@
package xs package xs
import ( import (
"io" "bufio"
"fmt"
"log"
"os"
"os/exec" "os/exec"
"os/signal"
"golang.org/x/sys/windows"
) )
type State struct { type State struct {
@ -27,67 +30,84 @@ type State struct {
// MakeRaw put the terminal connected to the given file descriptor into raw // MakeRaw put the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be // mode and returns the previous state of the terminal so that it can be
// restored. // restored.
func MakeRaw(fd uintptr) (*State, error) { func MakeRaw(f *os.File) (*State, error) {
// This doesn't really work. The exec.Command() runs a sub-shell cmd := exec.Command("stty", "-echo", "raw")
// so the stty mods don't affect the client process. cmd.Stdin = f
cmd := exec.Command("stty", "-echo raw") err := cmd.Run()
cmd.Run() if err != nil {
log.Fatal(err)
return &State{}, err
}
// MSYS2/CYGWIN: wintty needs CTRL-C caught
// ----------------------------------------
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
for sig := range c {
_ = sig
//fmt.Println(sig)
}
}()
// ----------------------------------------
return &State{}, nil return &State{}, nil
} }
// GetState returns the current state of a terminal which may be useful to // GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal. // restore the terminal after a signal.
func GetState(fd uintptr) (*State, error) { func GetState(f *os.File) (*State, error) {
return &State{}, nil return &State{}, nil
} }
// Restore restores the terminal connected to the given file descriptor to a // Restore restores the terminal connected to the given file descriptor to a
// previous state. // previous state.
func Restore(fd uintptr, state *State) error { func Restore(f *os.File, state *State) error {
cmd := exec.Command("stty", "echo cooked") cmd := exec.Command("stty", "sane")
cmd.Run() cmd.Stdin = f
err := cmd.Run()
if err != nil {
log.Fatal(err)
return nil
}
return nil return nil
} }
// ReadPassword reads a line of input from a terminal without local echo. This // ReadPassword reads a line of input from a terminal without local echo. This
// is commonly used for inputting passwords and other sensitive data. The slice // is commonly used for inputting passwords and other sensitive data. The slice
// returned does not include the \n. // returned does not include the \n.
func ReadPassword(fd uintptr) ([]byte, error) { func ReadPassword(f *os.File) (pw []byte, err error) {
return readPasswordLine(passwordReader(fd)) sttycmd, err := exec.LookPath("stty")
} if err != nil {
return nil, err
// passwordReader is an io.Reader that reads from a specific file descriptor. } else {
type passwordReader windows.Handle //fmt.Printf("stty found at: %v\n", sttycmd)
cmdOff := exec.Command(sttycmd, "-echo")
func (r passwordReader) Read(buf []byte) (int, error) { cmdOff.Stdin = f //os.Stdin
return windows.Read(windows.Handle(r), buf) cmdOff.Stdout = nil //os.Stdout
} cmdOff.Stderr = nil //os.Stderr
err = cmdOff.Run()
// readPasswordLine reads from reader until it finds \n or io.EOF.
// The slice returned does not include the \n.
// readPasswordLine also ignores any \r it finds.
func readPasswordLine(reader io.Reader) ([]byte, error) {
var buf [1]byte
var ret []byte
for {
n, err := reader.Read(buf[:])
if n > 0 {
switch buf[0] {
case '\n':
return ret, nil
case '\r':
// remove \r from passwords on Windows
default:
ret = append(ret, buf[0])
}
continue
}
if err != nil { if err != nil {
if err == io.EOF && len(ret) > 0 { return nil, err
return ret, nil }
}
return ret, err //fmt.Printf("Enter password:")
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
err = scanner.Err()
if err != nil {
return nil, err
}
pw = scanner.Bytes()
fmt.Println()
cmdOn := exec.Command(sttycmd, "echo")
cmdOn.Stdin = f //os.Stdin
cmdOn.Stdout = nil //os.Stdout
cmdOn.Stderr = nil //os.Stderr
err = cmdOn.Run()
if err != nil {
return nil, err
} }
} }
return
} }

View File

@ -1,39 +0,0 @@
#!/bin/bash
#
## This wrapper may be used within the MSYS/mintty Windows
## shell environment to have a functioning xs client with
## working 'raw' mode and hidden password entry.
##
## mintty uses named pipes and ptys to get a more POSIX-like
## terminal (incl. VT/ANSI codes) rather than the dumb Windows
## console interface; however Go on Windows does not have functioning
## MSYS/mintty code to set raw, echo etc. modes.
##
## Someday it would be preferable to put native Windows term mode
## code into the client build, but this is 'good enough' for now
## (with the exception of tty rows/cols not being set based on
## info from the server).
##
## INSTALLATION
## --
## Build the client, put it somewhere in your $PATH with this
## wrapper and edit the name of the client binary
## eg.,
## $ cp hkexsh.exe /usr/bin/.hkexsh.exe
## $ cp mintty_wrapper.sh /usr/bin/hkexsh
####
trap cleanup EXIT ERR
cleanup() {
stty sane
}
me="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")"
if [ ${1}x == "-hx" ]; then
_${me} -h
else
stty -echo raw icrnl
_${me} $@
fi

View File

@ -35,7 +35,7 @@ import (
"blitter.com/go/xs/logger" "blitter.com/go/xs/logger"
"blitter.com/go/xs/spinsult" "blitter.com/go/xs/spinsult"
"blitter.com/go/xs/xsnet" "blitter.com/go/xs/xsnet"
isatty "github.com/mattn/go-isatty" //isatty "github.com/mattn/go-isatty"
) )
var ( var (
@ -990,29 +990,13 @@ func main() { //nolint: funlen, gocyclo
// === Shell terminal mode (Shell vs. Copy) setup // === Shell terminal mode (Shell vs. Copy) setup
// Set stdin in raw mode if it's an interactive session
// TODO: send flag to server side indicating this
// affects shell command used
var oldState *xs.State
defer conn.Close() defer conn.Close()
// === From this point on, conn is a secure encrypted channel // === From this point on, conn is a secure encrypted channel
if shellMode { // === BEGIN Login phase
if isatty.IsTerminal(os.Stdin.Fd()) {
oldState, err = xs.MakeRaw(os.Stdin.Fd())
if err != nil {
panic(err)
}
// #gv:s/label=\"main\$1\"/label=\"deferRestore\"/
// TODO:.gv:main:1:deferRestore
defer restoreTermState(oldState)
} else {
log.Println("NOT A TTY")
}
}
// === Login phase var oldState *xs.State
// Start login timeout here and disconnect if user/pass phase stalls // Start login timeout here and disconnect if user/pass phase stalls
// iloginImpatience := time.AfterFunc(20*time.Second, func() { // iloginImpatience := time.AfterFunc(20*time.Second, func() {
@ -1029,7 +1013,7 @@ func main() { //nolint: funlen, gocyclo
// No auth token, prompt for password // No auth token, prompt for password
fmt.Printf("Gimme cookie:") fmt.Printf("Gimme cookie:")
} }
ab, e := xs.ReadPassword(os.Stdin.Fd()) ab, e := xs.ReadPassword(os.Stdin)
if !gopt { if !gopt {
fmt.Printf("\r\n") fmt.Printf("\r\n")
} }
@ -1044,6 +1028,25 @@ func main() { //nolint: funlen, gocyclo
// Security scrub // Security scrub
runtime.GC() runtime.GC()
// === END Login phase
// === Terminal mode adjustment for session
if shellMode {
if true { //isatty.IsTerminal(os.Stdin.Fd()) {
oldState, err = xs.MakeRaw(os.Stdin)
if err != nil {
panic(err)
}
// #gv:s/label=\"main\$1\"/label=\"deferRestore\"/
// TODO:.gv:main:1:deferRestore
defer restoreTermState(oldState)
} else {
log.Println("NOT A TTY")
}
}
// === Session param and TERM setup // === Session param and TERM setup
// Set up session params and send over to server // Set up session params and send over to server
@ -1147,7 +1150,7 @@ func localUserName(u *user.User) string {
} }
func restoreTermState(oldState *xs.State) { func restoreTermState(oldState *xs.State) {
_ = xs.Restore(os.Stdin.Fd(), oldState) _ = xs.Restore(os.Stdin, oldState)
} }
// exitWithStatus wraps os.Exit() plus does any required pprof housekeeping // exitWithStatus wraps os.Exit() plus does any required pprof housekeeping

0
xsnet/chan.go Normal file → Executable file
View File

0
xsnet/consts.go Normal file → Executable file
View File

0
xsnet/kcp.go Normal file → Executable file
View File

5
xsnet/net.go Normal file → Executable file
View File

@ -39,7 +39,6 @@ import (
"net" "net"
"strings" "strings"
"sync" "sync"
"syscall"
"time" "time"
hkex "blitter.com/go/herradurakex" hkex "blitter.com/go/herradurakex"
@ -1762,7 +1761,9 @@ func (hc *Conn) keepaliveHelper() {
hc.ShutdownKeepAlive() hc.ShutdownKeepAlive()
if hc.Pproc != 0 { if hc.Pproc != 0 {
//fmt.Printf("[pid %d needs to be killed]\n", hc.Pproc) //fmt.Printf("[pid %d needs to be killed]\n", hc.Pproc)
syscall.Kill(hc.Pproc, syscall.SIGABRT) //nolint:errcheck //syscall.Kill(hc.Pproc, syscall.SIGABRT) //nolint:errcheck
//exec.Command("taskkill", "/f", "/pid", strconv.Itoa(hc.Pproc)).Run()
hc.kill()
} }
break break
} }

13
xsnet/net_linux.go Executable file
View File

@ -0,0 +1,13 @@
//go:build linux
// +build linux
package xsnet
import (
"syscall"
)
func (hc *Conn) kill() {
syscall.Kill(hc.Pproc, syscall.SIGABRT) //nolint:errcheck
}

13
xsnet/net_windows.go Executable file
View File

@ -0,0 +1,13 @@
//go:build windows
// +build windows
package xsnet
import (
"os/exec"
"strconv"
)
func (hc *Conn) kill() {
exec.Command("taskkill", "/f", "/pid", strconv.Itoa(hc.Pproc)).Run()
}

0
xsnet/tun.go Normal file → Executable file
View File