-
-
Save pmatseykanets/691884a9d72e724c404f6140edbda16e to your computer and use it in GitHub Desktop.
Go code to prompt for password using only standard packages by utilizing syscall.ForkExec() and syscall.Wait4(), recovers from ^C gracefully.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License: MIT Open Source | |
// Copyright (c) Joe Linoff 2016 | |
// Go code to prompt for password using only standard packages by utilizing syscall.ForkExec() and syscall.Wait4(). | |
// Correctly resets terminal echo after ^C interrupts. | |
package main | |
import ( | |
"bufio" | |
"fmt" | |
"os" | |
"os/signal" | |
"strings" | |
"syscall" | |
) | |
func main() { | |
// Get password. | |
p := getPassword("password: ") | |
fmt.Println("value:", p) | |
} | |
// techEcho() - turns terminal echo on or off. | |
func termEcho(on bool) { | |
// Common settings and variables for both stty calls. | |
attrs := syscall.ProcAttr{ | |
Dir: "", | |
Env: []string{}, | |
Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, | |
Sys: nil} | |
var ws syscall.WaitStatus | |
cmd := "echo" | |
if on == false { | |
cmd = "-echo" | |
} | |
// Enable/disable echoing. | |
pid, err := syscall.ForkExec( | |
"/bin/stty", | |
[]string{"stty", cmd}, | |
&attrs) | |
if err != nil { | |
panic(err) | |
} | |
// Wait for the stty process to complete. | |
_, err = syscall.Wait4(pid, &ws, 0, nil) | |
if err != nil { | |
panic(err) | |
} | |
} | |
// getPassword - Prompt for password. | |
func getPassword(prompt string) string { | |
fmt.Print(prompt) | |
// Catch a ^C interrupt. | |
// Make sure that we reset term echo before exiting. | |
signalChannel := make(chan os.Signal, 1) | |
signal.Notify(signalChannel, os.Interrupt) | |
go func() { | |
for _ = range signalChannel { | |
fmt.Println("\n^C interrupt.") | |
termEcho(true) | |
os.Exit(1) | |
} | |
}() | |
// Echo is disabled, now grab the data. | |
termEcho(false) // disable terminal echo | |
reader := bufio.NewReader(os.Stdin) | |
text, err := reader.ReadString('\n') | |
termEcho(true) // always re-enable terminal echo | |
fmt.Println("") | |
if err != nil { | |
// The terminal has been reset, go ahead and exit. | |
fmt.Println("ERROR:", err.Error()) | |
os.Exit(1) | |
} | |
return strings.TrimSpace(text) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License: MIT Open Source | |
// Copyright (c) Joe Linoff 2016 | |
// Go code to prompt for password using only standard packages by utilizing syscall.Syscall6(). | |
// Could be used to modify golang.org/x/crypto/ssh/terminal. | |
// Correctly resets terminal echo after ^C interrupts. | |
package main | |
import ( | |
"fmt" | |
"io" | |
"os" | |
"os/signal" | |
"runtime" | |
"syscall" | |
"unsafe" | |
) | |
func main() { | |
// Get password. | |
p, e := getPassword("password: ") | |
if e != nil { | |
panic(e) | |
} | |
fmt.Println("value:", string(p)) | |
} | |
// getPassword() gets the password. | |
// Similar to terminal.ReadPassword(). | |
func getPassword(prompt string) ([]byte, error) { | |
fmt.Print(prompt) | |
// CITATION: https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go | |
// CITATION: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go | |
var ioctlReadTermios uintptr | |
var ioctlWriteTermios uintptr | |
switch runtime.GOOS { | |
case "linux": | |
ioctlReadTermios = 0x5401 // syscall.TCGETS | |
ioctlWriteTermios = 0x5402 // syscall.TCSETS | |
case "darwin": | |
ioctlReadTermios = syscall.TIOCGETA | |
ioctlWriteTermios = syscall.TIOCSETA | |
default: | |
return nil, fmt.Errorf("unsupported OS: " + runtime.GOOS) | |
} | |
// CITATION: https://github.com/golang/crypto/blob/master/ssh/terminal/util.go | |
fd := 1 // stdin | |
var oldState syscall.Termios | |
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { | |
return nil, err | |
} | |
// Disable echo. | |
newState := oldState | |
newState.Lflag &^= syscall.ECHO | |
newState.Lflag |= syscall.ICANON | syscall.ISIG | |
newState.Iflag |= syscall.ICRNL | |
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { | |
return nil, err | |
} | |
// Re-enable echo. | |
// Doesn't work for ^C interrupts. | |
defer func() { | |
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) | |
fmt.Println("") | |
}() | |
// Added this modification to handle a ^C interrupt. | |
// Make sure that we reset term echo before exiting. | |
signalChannel := make(chan os.Signal, 1) | |
signal.Notify(signalChannel, os.Interrupt) | |
go func() { | |
for _ = range signalChannel { | |
fmt.Println("\n^C interrupt.") | |
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) | |
os.Exit(1) | |
} | |
}() | |
// Read value byte by byte. | |
var buf [64]byte | |
var ret []byte | |
for { | |
n, err := syscall.Read(fd, buf[:]) | |
if err != nil { | |
return nil, io.EOF | |
} | |
if n == 0 { | |
if len(ret) == 0 { | |
return nil, io.EOF | |
} | |
break | |
} | |
if buf[n-1] == '\n' { | |
n-- | |
} | |
ret = append(ret, buf[:n]...) | |
if n < len(buf) { | |
break | |
} | |
} | |
return ret, nil | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// License: MIT Open Source | |
// Copyright (c) Joe Linoff 2016 | |
// Wrap around golang.org/x/crypto/ssh/terminal to handle ^C interrupts based on a suggestion by Konstantin Shaposhnikov in | |
// this thread: https://groups.google.com/forum/#!topic/golang-nuts/kTVAbtee9UA. | |
// Correctly resets terminal echo after ^C interrupts. | |
package main | |
import ( | |
"fmt" | |
"os" | |
"os/signal" | |
"syscall" | |
"golang.org/x/crypto/ssh/terminal" | |
) | |
func main() { | |
// Get password. | |
p := getPassword("password: ") | |
fmt.Println("value:", p) | |
} | |
func getPassword(prompt string) string { | |
// Get the initial state of the terminal. | |
initialTermState, e1 := terminal.GetState(syscall.Stdin) | |
if e1 != nil { | |
panic(e1) | |
} | |
// Restore it in the event of an interrupt. | |
// CITATION: Konstantin Shaposhnikov - https://groups.google.com/forum/#!topic/golang-nuts/kTVAbtee9UA | |
c := make(chan os.Signal) | |
signal.Notify(c, os.Interrupt, os.Kill) | |
go func() { | |
<-c | |
_ = terminal.Restore(syscall.Stdin, initialTermState) | |
os.Exit(1) | |
}() | |
// Now get the password. | |
fmt.Print(prompt) | |
p, err := terminal.ReadPassword(syscall.Stdin) | |
fmt.Println("") | |
if err != nil { | |
panic(err) | |
} | |
// Stop looking for ^C on the channel. | |
signal.Stop(c) | |
// Return the password as a string. | |
return string(p) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment