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 {
|
||||
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 {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
return nil, 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