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
|
// golang.org/pkg/net Conn interface, with
|
||||||
// experimental key exchange algorithm by
|
// experimental key exchange algorithm by
|
||||||
// Omar Alejandro Herrera Reyna
|
// Omar Alejandro Herrera Reyna.
|
||||||
|
//
|
||||||
// (https://github.com/Caume/HerraduraKEx)
|
// (https://github.com/Caume/HerraduraKEx)
|
||||||
|
//
|
||||||
|
// Demonstration server (hkexshd) and
|
||||||
|
// client (hkexsh)
|
||||||
|
|
||||||
//
|
//
|
||||||
// See README.md for full license info.
|
// See README.md for full license info.
|
||||||
package herradurakex
|
package hkexsh
|
||||||
|
|
||||||
/* Herradura - a Key exchange scheme in the style of Diffie-Hellman Key Exchange.
|
/* Herradura - a Key exchange scheme in the style of Diffie-Hellman Key Exchange.
|
||||||
Copyright (C) 2017 Omar Alejandro Herrera Reyna
|
Copyright (C) 2017 Omar Alejandro Herrera Reyna
|
||||||
|
@ -22,7 +27,7 @@ package herradurakex
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
golang implementation by Russ Magee (rmagee_at_gmail.com) */
|
golang implementation by Russ Magee (rmagee_at_gmail.com) */
|
||||||
|
|
||||||
/* This is the core KEx algorithm. For client/server net support code,
|
/* This is the core KEx algorithm. For client/server net support code,
|
||||||
|
|
124
hkexauth.go
124
hkexauth.go
|
@ -1,6 +1,6 @@
|
||||||
// Authentication routines for the HKExSh
|
// Authentication routines for the HKExSh
|
||||||
|
|
||||||
package herradurakex
|
package hkexsh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -11,7 +11,6 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/jameskeane/bcrypt"
|
"github.com/jameskeane/bcrypt"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func AuthUser(username string, auth string, fname string) (valid bool, allowedCmds string) {
|
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
|
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
|
/* Support functions to set up encryption once an HKEx Conn has been
|
||||||
established with FA exchange and support channel operations
|
established with FA exchange and support channel operations
|
||||||
|
@ -37,7 +37,6 @@ const (
|
||||||
HmacNoneDisallowed
|
HmacNoneDisallowed
|
||||||
)
|
)
|
||||||
|
|
||||||
/*TODO: HMAC derived from HKEx FA.*/
|
|
||||||
/* Support functionality to set up encryption after a channel has
|
/* Support functionality to set up encryption after a channel has
|
||||||
been negotiated via hkexnet.go
|
been negotiated via hkexnet.go
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
golang implementation by Russ Magee (rmagee_at_gmail.com) */
|
golang implementation by Russ Magee (rmagee_at_gmail.com) */
|
||||||
|
|
||||||
package herradurakex
|
package hkexsh
|
||||||
|
|
||||||
// Implementation of HKEx-wrapped versions of the golang standard
|
// Implementation of HKEx-wrapped versions of the golang standard
|
||||||
// net package interfaces, allowing clients and servers to simply replace
|
// net package interfaces, allowing clients and servers to simply replace
|
||||||
|
|
|
@ -12,8 +12,8 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
|
||||||
hkex "github.com/Russtopia/hkexsh"
|
|
||||||
"github.com/jameskeane/bcrypt"
|
"github.com/jameskeane/bcrypt"
|
||||||
|
hkexsh "blitter.com/hkexsh"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -40,7 +40,7 @@ func main() {
|
||||||
uname = u.Username
|
uname = u.Username
|
||||||
|
|
||||||
fmt.Printf("New Password:")
|
fmt.Printf("New Password:")
|
||||||
ab, err := hkex.ReadPassword(int(os.Stdin.Fd()))
|
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||||
fmt.Printf("\r\n")
|
fmt.Printf("\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -49,7 +49,7 @@ func main() {
|
||||||
newpw = string(ab)
|
newpw = string(ab)
|
||||||
|
|
||||||
fmt.Printf("Confirm:")
|
fmt.Printf("Confirm:")
|
||||||
ab, err = hkex.ReadPassword(int(os.Stdin.Fd()))
|
ab, err = hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||||
fmt.Printf("\r\n")
|
fmt.Printf("\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
hkex "blitter.com/hkexsh"
|
hkexsh "blitter.com/hkexsh"
|
||||||
isatty "github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ func main() {
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := hkex.Dial("tcp", server, cAlg, hAlg)
|
conn, err := hkexsh.Dial("tcp", server, cAlg, hAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Err!")
|
fmt.Println("Err!")
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -74,11 +74,11 @@ func main() {
|
||||||
// TODO: send flag to server side indicating this
|
// TODO: send flag to server side indicating this
|
||||||
// affects shell command used
|
// affects shell command used
|
||||||
if isatty.IsTerminal(os.Stdin.Fd()) {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
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 {
|
} else {
|
||||||
log.Println("NOT A TTY")
|
log.Println("NOT A TTY")
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ func main() {
|
||||||
|
|
||||||
if len(authCookie) == 0 {
|
if len(authCookie) == 0 {
|
||||||
fmt.Printf("Gimme cookie:")
|
fmt.Printf("Gimme cookie:")
|
||||||
ab, err := hkex.ReadPassword(int(os.Stdin.Fd()))
|
ab, err := hkexsh.ReadPassword(int(os.Stdin.Fd()))
|
||||||
fmt.Printf("\r\n")
|
fmt.Printf("\r\n")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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"
|
"os/user"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
hkex "blitter.com/hkexsh"
|
hkexsh "blitter.com/hkexsh"
|
||||||
"blitter.com/hkexsh/demo/spinsult"
|
"blitter.com/hkexsh/spinsult"
|
||||||
"github.com/kr/pty"
|
"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
|
// Run a command (via default shell) as a specific user
|
||||||
//
|
//
|
||||||
// Uses ptys to support commands which expect a terminal.
|
// 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)
|
u, _ := user.Lookup(who)
|
||||||
var uid, gid uint32
|
var uid, gid uint32
|
||||||
fmt.Sscanf(u.Uid, "%d", &uid)
|
fmt.Sscanf(u.Uid, "%d", &uid)
|
||||||
|
@ -153,7 +153,7 @@ func main() {
|
||||||
|
|
||||||
// Listen on TCP port 2000 on all available unicast and
|
// Listen on TCP port 2000 on all available unicast and
|
||||||
// anycast IP addresses of the local system.
|
// anycast IP addresses of the local system.
|
||||||
l, err := hkex.Listen("tcp", laddr)
|
l, err := hkexsh.Listen("tcp", laddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ func main() {
|
||||||
// Handle the connection in a new goroutine.
|
// Handle the connection in a new goroutine.
|
||||||
// The loop then returns to accepting, so that
|
// The loop then returns to accepting, so that
|
||||||
// multiple connections may be served concurrently.
|
// multiple connections may be served concurrently.
|
||||||
go func(c hkex.Conn) (e error) {
|
go func(c hkexsh.Conn) (e error) {
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
//We use io.ReadFull() here to guarantee we consume
|
//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",
|
log.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:****]\n",
|
||||||
rec.op[0], string(rec.who), string(rec.cmd))
|
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 {
|
if !valid {
|
||||||
log.Println("Invalid user", string(rec.who))
|
log.Println("Invalid user", string(rec.who))
|
||||||
c.Write([]byte(rejectUserMsg()))
|
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