github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/cf/terminal/ui_unix.go (about)

     1  // Copied from https://code.google.com/p/gopass/
     2  
     3  // +build !windows
     4  
     5  package terminal
     6  
     7  import (
     8  	"bufio"
     9  	"fmt"
    10  	"os"
    11  	"os/signal"
    12  	"strings"
    13  	"syscall"
    14  
    15  	. "github.com/cloudfoundry/cli/cf/i18n"
    16  )
    17  
    18  const (
    19  	sttyArg0  = "/bin/stty"
    20  	execCWDir = ""
    21  )
    22  
    23  // Tells the terminal to turn echo off.
    24  var sttyArgvEOff = []string{"stty", "-echo"}
    25  
    26  // Tells the terminal to turn echo on.
    27  var sttyArgvEOn = []string{"stty", "echo"}
    28  
    29  var ws syscall.WaitStatus
    30  
    31  func (ui terminalUI) AskForPassword(prompt string) (passwd string) {
    32  	sig := make(chan os.Signal, 10)
    33  
    34  	// Display the prompt.
    35  	fmt.Printf("\n%s%s ", prompt, PromptColor(">"))
    36  
    37  	// File descriptors for stdin, stdout, and stderr.
    38  	fd := []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}
    39  
    40  	// Setup notifications of termination signals to channel sig, create a process to
    41  	// watch for these signals so we can turn back on echo if need be.
    42  	signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGKILL, syscall.SIGQUIT,
    43  		syscall.SIGTERM)
    44  	defer signal.Stop(sig)
    45  
    46  	go catchSignal(fd, sig)
    47  
    48  	pid, err := echoOff(fd)
    49  	defer echoOn(fd)
    50  	if err != nil {
    51  		return
    52  	}
    53  
    54  	passwd = readPassword(pid)
    55  
    56  	// Carriage return after the user input.
    57  	fmt.Println("")
    58  
    59  	return
    60  }
    61  
    62  func readPassword(pid int) string {
    63  	rd := bufio.NewReader(os.Stdin)
    64  	_, _ = syscall.Wait4(pid, &ws, 0, nil)
    65  
    66  	line, err := rd.ReadString('\n')
    67  	if err == nil {
    68  		return strings.TrimSpace(line)
    69  	}
    70  	return ""
    71  }
    72  
    73  func echoOff(fd []uintptr) (int, error) {
    74  	pid, err := syscall.ForkExec(sttyArg0, sttyArgvEOff, &syscall.ProcAttr{Dir: execCWDir, Files: fd})
    75  
    76  	if err != nil {
    77  		return 0, fmt.Errorf(T("failed turning off console echo for password entry:\n{{.ErrorDescription}}", map[string]interface{}{"ErrorDescription": err}))
    78  	}
    79  
    80  	return pid, nil
    81  }
    82  
    83  // echoOn turns back on the terminal echo.
    84  func echoOn(fd []uintptr) {
    85  	// Turn on the terminal echo.
    86  	pid, e := syscall.ForkExec(sttyArg0, sttyArgvEOn, &syscall.ProcAttr{Dir: execCWDir, Files: fd})
    87  
    88  	if e == nil {
    89  		_, _ = syscall.Wait4(pid, &ws, 0, nil)
    90  	}
    91  }
    92  
    93  // catchSignal tries to catch SIGKILL, SIGQUIT and SIGINT so that we can turn terminal
    94  // echo back on before the program ends.  Otherwise the user is left with echo off on
    95  // their terminal.
    96  func catchSignal(fd []uintptr, sig chan os.Signal) {
    97  	select {
    98  	case <-sig:
    99  		echoOn(fd)
   100  		os.Exit(2)
   101  	}
   102  }