github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/misc/pinentry/pinentry.go (about)

     1  /*
     2  Copyright 2011 Google Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package pinentry interfaces with the pinentry(1) command to securely
    18  // prompt the user for a password using whichever user interface the
    19  // user is currently using.
    20  package pinentry
    21  
    22  import (
    23  	"bufio"
    24  	"errors"
    25  	"fmt"
    26  	"os"
    27  	"os/exec"
    28  	"strings"
    29  )
    30  
    31  // ErrCancel is returned when the user explicitly aborts the password
    32  // request.
    33  var ErrCancel = errors.New("pinentry: Cancel")
    34  
    35  // Request describes what the user should see during the request for
    36  // their password.
    37  type Request struct {
    38  	Desc, Prompt, OK, Cancel, Error string
    39  }
    40  
    41  func catch(err *error) {
    42  	rerr := recover()
    43  	if rerr == nil {
    44  		return
    45  	}
    46  	if e, ok := rerr.(string); ok {
    47  		*err = errors.New(e)
    48  	}
    49  	if e, ok := rerr.(error); ok {
    50  		*err = e
    51  	}
    52  }
    53  
    54  func check(err error) {
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  }
    59  
    60  func (r *Request) GetPIN() (pin string, outerr error) {
    61  	defer catch(&outerr)
    62  	bin, err := exec.LookPath("pinentry")
    63  	if err != nil {
    64  		return r.getPINNaïve()
    65  	}
    66  	cmd := exec.Command(bin)
    67  	stdin, _ := cmd.StdinPipe()
    68  	stdout, _ := cmd.StdoutPipe()
    69  	check(cmd.Start())
    70  	defer cmd.Wait()
    71  	defer stdin.Close()
    72  	br := bufio.NewReader(stdout)
    73  	lineb, _, err := br.ReadLine()
    74  	if err != nil {
    75  		return "", fmt.Errorf("Failed to get getpin greeting")
    76  	}
    77  	line := string(lineb)
    78  	if !strings.HasPrefix(line, "OK") {
    79  		return "", fmt.Errorf("getpin greeting said %q", line)
    80  	}
    81  	set := func(cmd string, val string) {
    82  		if val == "" {
    83  			return
    84  		}
    85  		fmt.Fprintf(stdin, "%s %s\n", cmd, val)
    86  		line, _, err := br.ReadLine()
    87  		if err != nil {
    88  			panic("Failed to " + cmd)
    89  		}
    90  		if string(line) != "OK" {
    91  			panic("Response to " + cmd + " was " + string(line))
    92  		}
    93  	}
    94  	set("SETPROMPT", r.Prompt)
    95  	set("SETDESC", r.Desc)
    96  	set("SETOK", r.OK)
    97  	set("SETCANCEL", r.Cancel)
    98  	set("SETERROR", r.Error)
    99  	set("OPTION", "ttytype="+os.Getenv("TERM"))
   100  	tty, err := os.Readlink("/proc/self/fd/0")
   101  	if err == nil {
   102  		set("OPTION", "ttyname="+tty)
   103  	}
   104  	fmt.Fprintf(stdin, "GETPIN\n")
   105  	lineb, _, err = br.ReadLine()
   106  	if err != nil {
   107  		return "", fmt.Errorf("Failed to read line after GETPIN: %v", err)
   108  	}
   109  	line = string(lineb)
   110  	if strings.HasPrefix(line, "D ") {
   111  		return line[2:], nil
   112  	}
   113  	if strings.HasPrefix(line, "ERR 83886179 ") {
   114  		return "", ErrCancel
   115  	}
   116  	return "", fmt.Errorf("GETPIN response didn't start with D; got %q", line)
   117  }
   118  
   119  func runPass(bin string, args ...string) {
   120  	cmd := exec.Command(bin, args...)
   121  	cmd.Stdout = os.Stdout
   122  	cmd.Run()
   123  }
   124  
   125  func (r *Request) getPINNaïve() (string, error) {
   126  	stty, err := exec.LookPath("stty")
   127  	if err != nil {
   128  		return "", errors.New("no pinentry or stty found")
   129  	}
   130  	runPass(stty, "-echo")
   131  	defer runPass(stty, "echo")
   132  
   133  	if r.Desc != "" {
   134  		fmt.Printf("%s\n\n", r.Desc)
   135  	}
   136  	prompt := r.Prompt
   137  	if prompt == "" {
   138  		prompt = "Password"
   139  	}
   140  	fmt.Printf("%s: ", prompt)
   141  	br := bufio.NewReader(os.Stdin)
   142  	line, _, err := br.ReadLine()
   143  	if err != nil {
   144  		return "", err
   145  	}
   146  	return string(line), nil
   147  }