blob: 59cb8c9a4d5ce40a9c17495c40884d6568127086 [file] [log] [blame]
Abhay Kumara61c5222025-11-10 07:32:50 +00001// based on https://code.google.com/p/gopass
2// Author: johnsiilver@gmail.com (John Doak)
3//
4// Original code is based on code by RogerV in the golang-nuts thread:
5// https://groups.google.com/group/golang-nuts/browse_thread/thread/40cc41e9d9fc9247
6
7// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
8
9package speakeasy
10
11import (
12 "fmt"
13 "os"
14 "os/exec"
15 "os/signal"
16 "strings"
17 "syscall"
18)
19
20const sttyBin = "stty"
21
22var (
23 sttyArgvEOff = []string{"stty", "-echo"}
24 sttyArgvEOn = []string{"stty", "echo"}
25)
26
27// getPassword gets input hidden from the terminal from a user. This is
28// accomplished by turning off terminal echo, reading input from the user and
29// finally turning on terminal echo.
30func getPassword() (password string, err error) {
31 sig := make(chan os.Signal, 10)
32 brk := make(chan bool)
33
34 // File descriptors for stdin, stdout, and stderr.
35 fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
36
37 // Setup notifications of termination signals to channel sig, create a process to
38 // watch for these signals so we can turn back on echo if need be.
39 signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
40 syscall.SIGTERM)
41 go catchSignal(fd, sig, brk)
42
43 // Turn off the terminal echo.
44 pid, err := echoOff(fd)
45 if err != nil {
46 return "", err
47 }
48
49 // Turn on the terminal echo and stop listening for signals.
50 defer signal.Stop(sig)
51 defer close(brk)
52 defer echoOn(fd)
53
54 syscall.Wait4(pid, nil, 0, nil)
55
56 line, err := readline()
57 if err == nil {
58 password = strings.TrimSpace(line)
59 } else {
60 err = fmt.Errorf("failed during password entry: %s", err)
61 }
62
63 return password, err
64}
65
66// echoOff turns off the terminal echo.
67func echoOff(fd []uintptr) (int, error) {
68 path, err := exec.LookPath(sttyBin)
69 if err != nil {
70 return 0, fmt.Errorf("%s binary not found:\n\t%s", sttyBin, err)
71 }
72 pid, err := syscall.ForkExec(path, sttyArgvEOff, &syscall.ProcAttr{Dir: "", Files: fd})
73 if err != nil {
74 return 0, fmt.Errorf("failed turning off console echo for password entry:\n\t%s", err)
75 }
76 return pid, nil
77}
78
79// echoOn turns back on the terminal echo.
80func echoOn(fd []uintptr) {
81 // Turn on the terminal echo.
82 path, err := exec.LookPath(sttyBin)
83 if err != nil {
84 return
85 }
86 pid, e := syscall.ForkExec(path, sttyArgvEOn, &syscall.ProcAttr{Dir: "", Files: fd})
87 if e == nil {
88 syscall.Wait4(pid, nil, 0, nil)
89 }
90}
91
92// catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn
93// terminal echo back on before the program ends. Otherwise the user is left
94// with echo off on their terminal.
95func catchSignal(fd []uintptr, sig chan os.Signal, brk chan bool) {
96 select {
97 case <-sig:
98 echoOn(fd)
99 os.Exit(-1)
100 case <-brk:
101 }
102}