Brought in ReadPassword from ssh/terminal, enabling entry of authCookie w/o term

echo.
TODO: consider methods of securing authCookie in auth file (salt+hash etc.)
This commit is contained in:
Russ Magee 2018-01-21 22:02:08 -08:00
parent 59337db7e3
commit 4d9ea3cbe1
2 changed files with 86 additions and 2 deletions

View file

@ -45,6 +45,7 @@ func main() {
var server string var server string
var cmdStr string var cmdStr string
var altUser string var altUser string
var authCookie string
isInteractive := false isInteractive := false
flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]") flag.StringVar(&cAlg, "c", "C_AES_256", "cipher [\"C_AES_256\" | \"C_TWOFISH_128\" | \"C_BLOWFISH_64\"]")
@ -52,6 +53,7 @@ func main() {
flag.StringVar(&server, "s", "localhost:2000", "server hostname/address[:port]") flag.StringVar(&server, "s", "localhost:2000", "server hostname/address[:port]")
flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)") flag.StringVar(&cmdStr, "x", "", "command to run (default empty - interactive shell)")
flag.StringVar(&altUser, "u", "", "specify alternate user") flag.StringVar(&altUser, "u", "", "specify alternate user")
flag.StringVar(&authCookie, "a", "", "auth cookie (MultiCheese3999(tm) 2FA cookie")
flag.BoolVar(&dbg, "d", false, "debug logging") flag.BoolVar(&dbg, "d", false, "debug logging")
flag.Parse() flag.Parse()
@ -78,7 +80,7 @@ func main() {
} }
defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort. defer func() { _ = Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
} else { } else {
fmt.Println("NOT A TTY") log.Println("NOT A TTY")
} }
var uname string var uname string
@ -104,11 +106,20 @@ func main() {
op = []byte{'c'} op = []byte{'c'}
} }
if len(authCookie) == 0 {
fmt.Printf("Gimme cookie:")
ab, err := ReadPassword(int(os.Stdin.Fd()))
if err != nil {
panic(err)
}
authCookie = string(ab)
}
rec := &cmdSpec{ rec := &cmdSpec{
op: op, op: op,
who: []byte(uname), who: []byte(uname),
cmd: []byte(cmdStr), cmd: []byte(cmdStr),
authCookie: []byte("99"), authCookie: []byte(authCookie),
status: 0} status: 0}
_, err = fmt.Fprintf(conn, "%d %d %d %d\n", len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie)) _, err = fmt.Fprintf(conn, "%d %d %d %d\n", len(rec.op), len(rec.who), len(rec.cmd), len(rec.authCookie))
@ -229,3 +240,63 @@ func GetState(fd int) (*State, error) {
func Restore(fd int, state *State) error { func Restore(fd int, state *State) error {
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) 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
}
}
}

View file

@ -108,6 +108,11 @@ func runShellAs(who string, cmd string, interactive bool, conn hkex.Conn) (err e
return return
} }
func rejectUserMsg() string {
// TODO: Use Shakespeare insult generator. :p
return "Invalid user\r\n"
}
// Demo of a simple server that listens and spawns goroutines for each // Demo of a simple server that listens and spawns goroutines for each
// connecting client. Note this code is identical to standard tcp // connecting client. Note this code is identical to standard tcp
// server code, save for declaring 'hkex' rather than 'net' // server code, save for declaring 'hkex' rather than 'net'
@ -194,6 +199,14 @@ func main() {
fmt.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:%s]\n", fmt.Printf("[cmdSpec: op:%c who:%s cmd:%s auth:%s]\n",
rec.op[0], string(rec.who), string(rec.cmd), string(rec.authCookie)) rec.op[0], string(rec.who), string(rec.cmd), string(rec.authCookie))
valid, allowedCmds := hkex.AuthUser(string(rec.who), string(rec.authCookie), "/etc/hkexsh.passwd")
if !valid {
log.Println("Invalid user", string(rec.who))
c.Write([]byte(rejectUserMsg()))
return
}
log.Printf("[allowedCmds:%s]\n", allowedCmds)
if rec.op[0] == 'c' { if rec.op[0] == 'c' {
// Non-interactive command // Non-interactive command
fmt.Println("[Running command]") fmt.Println("[Running command]")