mirror of https://gogs.blitter.com/RLabs/xs
				
				
				
			Fixes for MSYS2 and CYGWIN term mode; removed mintty_wrapper.sh
This commit is contained in:
		
							parent
							
								
									12409319e7
								
							
						
					
					
						commit
						e5b6422d70
					
				
							
								
								
									
										9
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										9
									
								
								Makefile
								
								
								
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										45
									
								
								xs/xs.go
								
								
								
								
							
							
						
						
									
										45
									
								
								xs/xs.go
								
								
								
								
							| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue