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