mirror of
https://gogs.blitter.com/RLabs/xs
synced 2024-08-14 10:26:42 +00:00
MSYS+mintty support; pkg renaming to hkexsh
This commit is contained in:
parent
dd746cf343
commit
5da70447b0
15 changed files with 281 additions and 377 deletions
|
@ -1,20 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
conn, err := net.Dial("tcp", "localhost:2000")
|
||||
if err != nil {
|
||||
// handle error
|
||||
fmt.Println("Err!")
|
||||
}
|
||||
fmt.Fprintf(conn, "\x01\x02\x03\x04")
|
||||
//fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
|
||||
//status, err := bufio.NewReader(conn).ReadString('\n')
|
||||
//_, err = bufio.NewReader(conn).ReadString('\n')
|
||||
// ...
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
// Util to generate/store passwords for users in a file akin to /etc/passwd
|
||||
// suitable for the demo hkexsh server, using bcrypt.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/csv"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
|
||||
hkex "github.com/Russtopia/herradurakex"
|
||||
"github.com/jameskeane/bcrypt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var pfName string
|
||||
var newpw string
|
||||
var confirmpw string
|
||||
var userName string
|
||||
|
||||
flag.StringVar(&userName, "u", "", "username")
|
||||
flag.StringVar(&pfName, "f", "/etc/hkexsh.passwd", "passwd file")
|
||||
flag.Parse()
|
||||
|
||||
var uname string
|
||||
if len(userName) == 0 {
|
||||
log.Println("specify username with -u")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
u, err := user.Lookup(userName)
|
||||
if err != nil {
|
||||
log.Printf("Invalid user %s\n", userName)
|
||||
log.Fatal(err)
|
||||
}
|
||||
uname = u.Username
|
||||
|
||||
fmt.Printf("New Password:")
|
||||
ab, err := hkex.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
newpw = string(ab)
|
||||
|
||||
fmt.Printf("Confirm:")
|
||||
ab, err = hkex.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
confirmpw = string(ab)
|
||||
|
||||
if confirmpw != newpw {
|
||||
log.Println("New passwords do not match.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// generate a random salt with specific rounds of complexity
|
||||
// (default in jameskeane/bcrypt is 12 but we'll be explicit here)
|
||||
salt, err := bcrypt.Salt(12)
|
||||
if err != nil {
|
||||
fmt.Println("ERROR: bcrypt.Salt() failed.")
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// hash and verify a password with explicit (random) salt
|
||||
hash, err := bcrypt.Hash(newpw, salt)
|
||||
if err != nil || !bcrypt.Match(newpw, hash) {
|
||||
fmt.Println("ERROR: bcrypt.Match() failed.")
|
||||
log.Fatal(err)
|
||||
}
|
||||
//fmt.Println("Salt:", salt, "Hash:", hash)
|
||||
|
||||
b, err := ioutil.ReadFile(pfName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
r := csv.NewReader(bytes.NewReader(b))
|
||||
|
||||
r.Comma = ':'
|
||||
r.Comment = '#'
|
||||
r.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...)
|
||||
|
||||
records, err := r.ReadAll()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for i, _ := range records {
|
||||
//fmt.Println(records[i])
|
||||
if records[i][0] == uname {
|
||||
records[i][1] = salt
|
||||
records[i][2] = hash
|
||||
}
|
||||
//// csv lib doesn't preserve comment in record, so put it back
|
||||
//if records[i][0][0] == '!' {
|
||||
// records[i][0] = "#" + records[i][0]
|
||||
//}
|
||||
}
|
||||
|
||||
outFile, err := ioutil.TempFile("", "hkexsh-passwd")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
w := csv.NewWriter(outFile)
|
||||
w.Comma = ':'
|
||||
//w.FieldsPerRecord = 4 // username:salt:authCookie:disallowedCmdList (a,b,...)
|
||||
w.Write([]string{"#username", "salt", "authCookie", "disallowedCmdList"})
|
||||
w.WriteAll(records)
|
||||
if err = w.Error(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = os.Remove(pfName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.Rename(outFile.Name(), pfName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"net"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Listen on TCP port 2000 on all available unicast and
|
||||
// anycast IP addresses of the local system.
|
||||
l, err := net.Listen("tcp", ":2000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
fmt.Println("Serving on port 2000")
|
||||
for {
|
||||
// Wait for a connection.
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println("Accepted client")
|
||||
|
||||
// Handle the connection in a new goroutine.
|
||||
// The loop then returns to accepting, so that
|
||||
// multiple connections may be served concurrently.
|
||||
go func(c net.Conn) (e error) {
|
||||
ch := make(chan []byte)
|
||||
chN := 0
|
||||
eCh := make(chan error)
|
||||
|
||||
// Start a goroutine to read from our net connection
|
||||
go func(ch chan []byte, eCh chan error) {
|
||||
for {
|
||||
// try to read the data
|
||||
data := make([]byte, 512)
|
||||
chN, err = c.Read(data)
|
||||
if err != nil {
|
||||
// send an error if it's encountered
|
||||
eCh <- err
|
||||
return
|
||||
}
|
||||
// send data if we read some.
|
||||
ch <- data[0:chN]
|
||||
}
|
||||
}(ch, eCh)
|
||||
|
||||
ticker := time.Tick(time.Second)
|
||||
Term:
|
||||
// continuously read from the connection
|
||||
for {
|
||||
select {
|
||||
// This case means we recieved data on the connection
|
||||
case data := <-ch:
|
||||
// Do something with the data
|
||||
fmt.Printf("Client sent %+v\n", data[0:chN])
|
||||
//fmt.Printf("Client sent %s\n", string(data))
|
||||
// This case means we got an error and the goroutine has finished
|
||||
case err := <-eCh:
|
||||
// handle our error then exit for loop
|
||||
if err.Error() == "EOF" {
|
||||
fmt.Printf("[Client disconnected]\n")
|
||||
} else {
|
||||
fmt.Printf("Error reading client data! (%+v)\n", err)
|
||||
}
|
||||
break Term
|
||||
// This will timeout on the read.
|
||||
case <-ticker:
|
||||
// do nothing? this is just so we can time out if we need to.
|
||||
// you probably don't even need to have this here unless you want
|
||||
// do something specifically on the timeout.
|
||||
}
|
||||
}
|
||||
// Shut down the connection.
|
||||
c.Close()
|
||||
return
|
||||
}(conn)
|
||||
}
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
// Package herradurakex - socket lib conforming to
|
||||
// Package hkexsh - socket lib conforming to
|
||||
// golang.org/pkg/net Conn interface, with
|
||||
// experimental key exchange algorithm by
|
||||
// Omar Alejandro Herrera Reyna
|
||||
// Omar Alejandro Herrera Reyna.
|
||||
//
|
||||
// (https://github.com/Caume/HerraduraKEx)
|
||||
//
|
||||
// Demonstration server (hkexshd) and
|
||||
// client (hkexsh)
|
||||
|
||||
//
|
||||
// See README.md for full license info.
|
||||
package herradurakex
|
||||
package hkexsh
|
||||
|
||||
/* Herradura - a Key exchange scheme in the style of Diffie-Hellman Key Exchange.
|
||||
Copyright (C) 2017 Omar Alejandro Herrera Reyna
|
||||
|
|
124
hkexauth.go
124
hkexauth.go
|
@ -1,6 +1,6 @@
|
|||
// Authentication routines for the HKExSh
|
||||
|
||||
package herradurakex
|
||||
package hkexsh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -11,7 +11,6 @@ import (
|
|||
"runtime"
|
||||
|
||||
"github.com/jameskeane/bcrypt"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func AuthUser(username string, auth string, fname string) (valid bool, allowedCmds string) {
|
||||
|
@ -48,124 +47,3 @@ func AuthUser(username string, auth string, fname string) (valid bool, allowedCm
|
|||
}
|
||||
return
|
||||
}
|
||||
|
||||
/* ------------- minimal terminal APIs brought in from ssh/terminal
|
||||
* (they have no real business being there as they aren't specific to
|
||||
* ssh, but as of v1.10, early 2018, core go stdlib hasn't yet done
|
||||
* the planned terminal lib reorgs.)
|
||||
* -------------
|
||||
*/
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util.go
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
termios unix.Termios
|
||||
}
|
||||
|
||||
// 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 int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldState := State{termios: *termios}
|
||||
|
||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||
// the termios(3) manpage.
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{termios: *termios}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||
}
|
||||
|
||||
// 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 int) ([]byte, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := *termios
|
||||
newState.Lflag &^= unix.ECHO
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||
newState.Iflag |= unix.ICRNL
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return unix.Read(int(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 == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package herradurakex
|
||||
package hkexsh
|
||||
|
||||
/* Support functions to set up encryption once an HKEx Conn has been
|
||||
established with FA exchange and support channel operations
|
||||
|
@ -37,7 +37,6 @@ const (
|
|||
HmacNoneDisallowed
|
||||
)
|
||||
|
||||
/*TODO: HMAC derived from HKEx FA.*/
|
||||
/* Support functionality to set up encryption after a channel has
|
||||
been negotiated via hkexnet.go
|
||||
*/
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
golang implementation by Russ Magee (rmagee_at_gmail.com) */
|
||||
|
||||
package herradurakex
|
||||
package hkexsh
|
||||
|
||||
// Implementation of HKEx-wrapped versions of the golang standard
|
||||
// net package interfaces, allowing clients and servers to simply replace
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"os"
|
||||
"os/user"
|
||||
|
||||
hkex "github.com/Russtopia/hkexsh"
|
||||
"github.com/jameskeane/bcrypt"
|
||||
hkexsh "blitter.com/hkexsh"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -40,7 +40,7 @@ func main() {
|
|||
uname = u.Username
|
||||
|
||||
fmt.Printf("New Password:")
|
||||
ab, err := hkex.ReadPassword(int(os.Stdin.Fd()))
|
||||
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
@ -49,7 +49,7 @@ func main() {
|
|||
newpw = string(ab)
|
||||
|
||||
fmt.Printf("Confirm:")
|
||||
ab, err = hkex.ReadPassword(int(os.Stdin.Fd()))
|
||||
ab, err = hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
|
@ -11,7 +11,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
hkex "blitter.com/hkexsh"
|
||||
hkexsh "blitter.com/hkexsh"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
|
@ -62,7 +62,7 @@ func main() {
|
|||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
|
||||
conn, err := hkex.Dial("tcp", server, cAlg, hAlg)
|
||||
conn, err := hkexsh.Dial("tcp", server, cAlg, hAlg)
|
||||
if err != nil {
|
||||
fmt.Println("Err!")
|
||||
panic(err)
|
||||
|
@ -74,11 +74,11 @@ func main() {
|
|||
// TODO: send flag to server side indicating this
|
||||
// affects shell command used
|
||||
if isatty.IsTerminal(os.Stdin.Fd()) {
|
||||
oldState, err := hkex.MakeRaw(int(os.Stdin.Fd()))
|
||||
oldState, err := hkexsh.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer func() { _ = hkex.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
||||
defer func() { _ = hkexsh.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
|
||||
} else {
|
||||
log.Println("NOT A TTY")
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ func main() {
|
|||
|
||||
if len(authCookie) == 0 {
|
||||
fmt.Printf("Gimme cookie:")
|
||||
ab, err := hkex.ReadPassword(int(os.Stdin.Fd()))
|
||||
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Printf("\r\n")
|
||||
if err != nil {
|
||||
panic(err)
|
32
hkexsh/mintty_wrapper.sh
Normal file
32
hkexsh/mintty_wrapper.sh
Normal file
|
@ -0,0 +1,32 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
## This wrapper may be used within the MSYS/mintty Windows
|
||||
## shell environment to have a functioning hkexsh 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
|
||||
}
|
||||
|
||||
stty -echo raw icrnl
|
||||
./hkexsh $@
|
|
@ -11,8 +11,8 @@ import (
|
|||
"os/user"
|
||||
"syscall"
|
||||
|
||||
hkex "blitter.com/hkexsh"
|
||||
"blitter.com/hkexsh/demo/spinsult"
|
||||
hkexsh "blitter.com/hkexsh"
|
||||
"blitter.com/hkexsh/spinsult"
|
||||
"github.com/kr/pty"
|
||||
)
|
||||
|
||||
|
@ -71,7 +71,7 @@ func runCmdAs(who string, cmd string, conn hkex.Conn) (err error) {
|
|||
// Run a command (via default shell) as a specific user
|
||||
//
|
||||
// Uses ptys to support commands which expect a terminal.
|
||||
func runShellAs(who string, cmd string, interactive bool, conn hkex.Conn) (err error) {
|
||||
func runShellAs(who string, cmd string, interactive bool, conn hkexsh.Conn) (err error) {
|
||||
u, _ := user.Lookup(who)
|
||||
var uid, gid uint32
|
||||
fmt.Sscanf(u.Uid, "%d", &uid)
|
||||
|
@ -153,7 +153,7 @@ func main() {
|
|||
|
||||
// Listen on TCP port 2000 on all available unicast and
|
||||
// anycast IP addresses of the local system.
|
||||
l, err := hkex.Listen("tcp", laddr)
|
||||
l, err := hkexsh.Listen("tcp", laddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ func main() {
|
|||
// Handle the connection in a new goroutine.
|
||||
// The loop then returns to accepting, so that
|
||||
// multiple connections may be served concurrently.
|
||||
go func(c hkex.Conn) (e error) {
|
||||
go func(c hkexsh.Conn) (e error) {
|
||||
defer c.Close()
|
||||
|
||||
//We use io.ReadFull() here to guarantee we consume
|
||||
|
@ -220,7 +220,7 @@ func main() {
|
|||
log.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:****]\n",
|
||||
rec.op[0], string(rec.who), string(rec.cmd))
|
||||
|
||||
valid, allowedCmds := hkex.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd")
|
||||
valid, allowedCmds := hkexsh.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd")
|
||||
if !valid {
|
||||
log.Println("Invalid user", string(rec.who))
|
||||
c.Write([]byte(rejectUserMsg()))
|
130
termmode_unix.go
Normal file
130
termmode_unix.go
Normal file
|
@ -0,0 +1,130 @@
|
|||
// +build linux
|
||||
|
||||
package hkexsh
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
unix "golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
/* -------------
|
||||
* minimal terminal APIs brought in from ssh/terminal
|
||||
* (they have no real business being there as they aren't specific to
|
||||
* ssh, but as of Go v1.10, early 2018, core go stdlib hasn't yet done
|
||||
* the planned terminal lib reorgs.)
|
||||
* ------------- */
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
const ioctlWriteTermios = unix.TCSETS
|
||||
|
||||
// From github.com/golang/crypto/blob/master/ssh/terminal/util.go
|
||||
// State contains the state of a terminal.
|
||||
type State struct {
|
||||
termios unix.Termios
|
||||
}
|
||||
|
||||
// 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 int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldState := State{termios: *termios}
|
||||
|
||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
||||
// the termios(3) manpage.
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
|
||||
termios.Oflag &^= unix.OPOST
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB
|
||||
termios.Cflag |= unix.CS8
|
||||
termios.Cc[unix.VMIN] = 1
|
||||
termios.Cc[unix.VTIME] = 0
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &oldState, nil
|
||||
}
|
||||
|
||||
// GetState returns the current state of a terminal which may be useful to
|
||||
// restore the terminal after a signal.
|
||||
func GetState(fd int) (*State, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &State{termios: *termios}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios)
|
||||
}
|
||||
|
||||
// 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 int) ([]byte, error) {
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newState := *termios
|
||||
newState.Lflag &^= unix.ECHO
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG
|
||||
newState.Iflag |= unix.ICRNL
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
|
||||
}()
|
||||
|
||||
return readPasswordLine(passwordReader(fd))
|
||||
}
|
||||
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
||||
type passwordReader int
|
||||
|
||||
func (r passwordReader) Read(buf []byte) (int, error) {
|
||||
return unix.Read(int(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 == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
93
termmode_windows.go
Normal file
93
termmode_windows.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
// +build windows
|
||||
//
|
||||
// Note the terminal manipulation functions herein are mostly stubs. They
|
||||
// don't really do anything and the hkexsh demo client depends on a wrapper
|
||||
// script using the 'stty' tool to actually set the proper mode for
|
||||
// password login and raw mode required, then restoring it upon logout/exit.
|
||||
//
|
||||
// mintty uses named pipes and ptys rather than Windows 'console'
|
||||
// mode, and Go's x/crypto/ssh/terminal libs only work for the latter, so
|
||||
// until some truly cross-platform terminal mode handling makes it into the
|
||||
// go std lib I'm not going to jump through hoops trying to be cross-platform
|
||||
// here; the wrapper does the bare minimum to make the client workable
|
||||
// under MSYS+mintty which is what I use.
|
||||
|
||||
package hkexsh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
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 int) (*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()
|
||||
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 int) (*State, error) {
|
||||
return &State{}, nil
|
||||
}
|
||||
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
||||
// previous state.
|
||||
func Restore(fd int, state *State) error {
|
||||
cmd := exec.Command("stty", "echo cooked")
|
||||
cmd.Run()
|
||||
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 int) ([]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
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF && len(ret) > 0 {
|
||||
return ret, nil
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue