1// 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 darwin dragonfly freebsd linux netbsd openbsd solaris
8
9package speakeasy
10
11import (
12	"fmt"
13	"os"
14	"os/signal"
15	"strings"
16	"syscall"
17)
18
19const sttyArg0 = "/bin/stty"
20
21var (
22	sttyArgvEOff = []string{"stty", "-echo"}
23	sttyArgvEOn  = []string{"stty", "echo"}
24)
25
26// getPassword gets input hidden from the terminal from a user. This is
27// accomplished by turning off terminal echo, reading input from the user and
28// finally turning on terminal echo.
29func getPassword() (password string, err error) {
30	sig := make(chan os.Signal, 10)
31	brk := make(chan bool)
32
33	// File descriptors for stdin, stdout, and stderr.
34	fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
35
36	// Setup notifications of termination signals to channel sig, create a process to
37	// watch for these signals so we can turn back on echo if need be.
38	signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
39		syscall.SIGTERM)
40	go catchSignal(fd, sig, brk)
41
42	// Turn off the terminal echo.
43	pid, err := echoOff(fd)
44	if err != nil {
45		return "", err
46	}
47
48	// Turn on the terminal echo and stop listening for signals.
49	defer signal.Stop(sig)
50	defer close(brk)
51	defer echoOn(fd)
52
53	syscall.Wait4(pid, nil, 0, nil)
54
55	line, err := readline()
56	if err == nil {
57		password = strings.TrimSpace(line)
58	} else {
59		err = fmt.Errorf("failed during password entry: %s", err)
60	}
61
62	return password, err
63}
64
65// echoOff turns off the terminal echo.
66func echoOff(fd []uintptr) (int, error) {
67	pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: "", Files: fd})
68	if err != nil {
69		return 0, fmt.Errorf("failed turning off console echo for password entry:\n\t%s", err)
70	}
71	return pid, nil
72}
73
74// echoOn turns back on the terminal echo.
75func echoOn(fd []uintptr) {
76	// Turn on the terminal echo.
77	pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: "", Files: fd})
78	if e == nil {
79		syscall.Wait4(pid, nil, 0, nil)
80	}
81}
82
83// catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn
84// terminal echo back on before the program ends. Otherwise the user is left
85// with echo off on their terminal.
86func catchSignal(fd []uintptr, sig chan os.Signal, brk chan bool) {
87	select {
88	case <-sig:
89		echoOn(fd)
90		os.Exit(-1)
91	case <-brk:
92	}
93}
94