github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/cf/terminal/ui_unix.go (about)

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