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:
echo "WIN_MSYS:" $(WIN_MSYS)
ifdef WIN_MSYS
cp xs/mintty_wrapper.sh $(INSTPREFIX)/bin/xs
cp xs/mintty_wrapper.sh $(INSTPREFIX)/bin/xc
cp xs/xs $(INSTPREFIX)/bin/_xs
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"
else
cp xs/xs $(INSTPREFIX)/bin
@ -120,8 +118,7 @@ else
endif
uninstall:
rm -f $(INSTPREFIX)/bin/xs $(INSTPREFIX)/bin/xc \
$(INSTPREFIX)/bin/_xs $(INSTPREFIX)/bin/_xc
rm -f $(INSTPREFIX)/bin/xs $(INSTPREFIX)/bin/xc
ifndef $(WIN_MSYS)
rm -f $(INSTPREFIX)/sbin/xsd
endif

0
auth.go Executable file → Normal file
View File

View File

@ -5,6 +5,7 @@ package xs
import (
"errors"
"io"
"os"
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
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
func MakeRaw(f *os.File) (*State, error) {
fd := f.Fd()
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil {
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
// restore the terminal after a signal.
func GetState(fd uintptr) (*State, error) {
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
func GetState(f *os.File) (*State, error) {
termios, err := unix.IoctlGetTermios(int(f.Fd()), ioctlReadTermios)
if err != nil {
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
// previous state.
func Restore(fd uintptr, state *State) error {
func Restore(f *os.File, state *State) error {
if state != nil {
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.termios)
return unix.IoctlSetTermios(int(f.Fd()), ioctlWriteTermios, &state.termios)
} else {
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
// is commonly used for inputting passwords and other sensitive data. The slice
// 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)
if err != nil {
return nil, err

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows
// Note the terminal manipulation functions herein are mostly stubs. They
@ -15,10 +16,12 @@
package xs
import (
"io"
"bufio"
"fmt"
"log"
"os"
"os/exec"
"golang.org/x/sys/windows"
"os/signal"
)
type State struct {
@ -27,67 +30,84 @@ type State struct {
// 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
// restored.
func MakeRaw(fd uintptr) (*State, error) {
// This doesn't really work. The exec.Command() runs a sub-shell
// so the stty mods don't affect the client process.
cmd := exec.Command("stty", "-echo raw")
cmd.Run()
func MakeRaw(f *os.File) (*State, error) {
cmd := exec.Command("stty", "-echo", "raw")
cmd.Stdin = f
err := 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
}
// GetState returns the current state of a terminal which may be useful to
// restore the terminal after a signal.
func GetState(fd uintptr) (*State, error) {
func GetState(f *os.File) (*State, error) {
return &State{}, nil
}
// Restore restores the terminal connected to the given file descriptor to a
// previous state.
func Restore(fd uintptr, state *State) error {
cmd := exec.Command("stty", "echo cooked")
cmd.Run()
func Restore(f *os.File, state *State) error {
cmd := exec.Command("stty", "sane")
cmd.Stdin = f
err := cmd.Run()
if err != nil {
log.Fatal(err)
return nil
}
return nil
}
// 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
// returned does not include the \n.
func ReadPassword(fd uintptr) ([]byte, error) {
return readPasswordLine(passwordReader(fd))
}
// passwordReader is an io.Reader that reads from a specific file descriptor.
type passwordReader windows.Handle
func (r passwordReader) Read(buf []byte) (int, error) {
return windows.Read(windows.Handle(r), buf)
}
// 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
}
func ReadPassword(f *os.File) (pw []byte, err error) {
sttycmd, err := exec.LookPath("stty")
if err != nil {
if err == io.EOF && len(ret) > 0 {
return ret, nil
return nil, err
} else {
//fmt.Printf("stty found at: %v\n", sttycmd)
cmdOff := exec.Command(sttycmd, "-echo")
cmdOff.Stdin = f //os.Stdin
cmdOff.Stdout = nil //os.Stdout
cmdOff.Stderr = nil //os.Stderr
err = cmdOff.Run()
if err != nil {
return nil, err
}
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/spinsult"
"blitter.com/go/xs/xsnet"
isatty "github.com/mattn/go-isatty"
//isatty "github.com/mattn/go-isatty"
)
var (
@ -990,29 +990,13 @@ func main() { //nolint: funlen, gocyclo
// === 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()
// === From this point on, conn is a secure encrypted channel
if shellMode {
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")
}
}
// === BEGIN Login phase
// === Login phase
var oldState *xs.State
// Start login timeout here and disconnect if user/pass phase stalls
// iloginImpatience := time.AfterFunc(20*time.Second, func() {
@ -1029,7 +1013,7 @@ func main() { //nolint: funlen, gocyclo
// No auth token, prompt for password
fmt.Printf("Gimme cookie:")
}
ab, e := xs.ReadPassword(os.Stdin.Fd())
ab, e := xs.ReadPassword(os.Stdin)
if !gopt {
fmt.Printf("\r\n")
}
@ -1044,6 +1028,25 @@ func main() { //nolint: funlen, gocyclo
// Security scrub
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
// Set up session params and send over to server
@ -1147,7 +1150,7 @@ func localUserName(u *user.User) string {
}
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

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"
"strings"
"sync"
"syscall"
"time"
hkex "blitter.com/go/herradurakex"
@ -1762,7 +1761,9 @@ func (hc *Conn) keepaliveHelper() {
hc.ShutdownKeepAlive()
if hc.Pproc != 0 {
//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
}

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