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

     1  // +build !appengine
     2  
     3  /*
     4  Copyright 2011 Google Inc.
     5  
     6  Licensed under the Apache License, Version 2.0 (the "License");
     7  you may not use this file except in compliance with the License.
     8  You may obtain a copy of the License at
     9  
    10       http://www.apache.org/licenses/LICENSE-2.0
    11  
    12  Unless required by applicable law or agreed to in writing, software
    13  distributed under the License is distributed on an "AS IS" BASIS,
    14  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15  See the License for the specific language governing permissions and
    16  limitations under the License.
    17  */
    18  
    19  // Package gpgagent interacts with the local GPG Agent.
    20  package gpgagent
    21  
    22  import (
    23  	"bufio"
    24  	"encoding/hex"
    25  	"errors"
    26  	"fmt"
    27  
    28  	"io"
    29  	"net"
    30  	"net/url"
    31  	"os"
    32  	"strings"
    33  )
    34  
    35  // Conn is a connection to the GPG agent.
    36  type Conn struct {
    37  	c  io.ReadWriteCloser
    38  	br *bufio.Reader
    39  }
    40  
    41  var (
    42  	ErrNoAgent = errors.New("GPG_AGENT_INFO not set in environment")
    43  	ErrNoData  = errors.New("GPG_ERR_NO_DATA cache miss")
    44  	ErrCancel  = errors.New("gpgagent: Cancel")
    45  )
    46  
    47  // NewConn connects to the GPG Agent as described in the
    48  // GPG_AGENT_INFO environment variable.
    49  func NewConn() (*Conn, error) {
    50  	sp := strings.SplitN(os.Getenv("GPG_AGENT_INFO"), ":", 3)
    51  	if len(sp) == 0 || len(sp[0]) == 0 {
    52  		return nil, ErrNoAgent
    53  	}
    54  	addr := &net.UnixAddr{Net: "unix", Name: sp[0]}
    55  	uc, err := net.DialUnix("unix", nil, addr)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  	br := bufio.NewReader(uc)
    60  	lineb, err := br.ReadSlice('\n')
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	line := string(lineb)
    65  	if !strings.HasPrefix(line, "OK") {
    66  		return nil, fmt.Errorf("gpgagent: didn't get OK; got %q", line)
    67  	}
    68  	return &Conn{uc, br}, nil
    69  }
    70  
    71  func (c *Conn) Close() error {
    72  	c.br = nil
    73  	return c.c.Close()
    74  }
    75  
    76  // PassphraseRequest is a request to get a passphrase from the GPG
    77  // Agent.
    78  type PassphraseRequest struct {
    79  	CacheKey, Error, Prompt, Desc string
    80  
    81  	// If the option --no-ask is used and the passphrase is not in
    82  	// the cache the user will not be asked to enter a passphrase
    83  	// but the error code GPG_ERR_NO_DATA is returned.  (ErrNoData)
    84  	NoAsk bool
    85  }
    86  
    87  func (c *Conn) RemoveFromCache(cacheKey string) error {
    88  	_, err := fmt.Fprintf(c.c, "CLEAR_PASSPHRASE %s\n", url.QueryEscape(cacheKey))
    89  	if err != nil {
    90  		return err
    91  	}
    92  	lineb, err := c.br.ReadSlice('\n')
    93  	if err != nil {
    94  		return err
    95  	}
    96  	line := string(lineb)
    97  	if !strings.HasPrefix(line, "OK") {
    98  		return fmt.Errorf("gpgagent: CLEAR_PASSPHRASE returned %q", line)
    99  	}
   100  	return nil
   101  }
   102  
   103  func (c *Conn) GetPassphrase(pr *PassphraseRequest) (passphrase string, outerr error) {
   104  	defer func() {
   105  		if e, ok := recover().(string); ok {
   106  			passphrase = ""
   107  			outerr = errors.New(e)
   108  		}
   109  	}()
   110  	set := func(cmd string, val string) {
   111  		if val == "" {
   112  			return
   113  		}
   114  		_, err := fmt.Fprintf(c.c, "%s %s\n", cmd, val)
   115  		if err != nil {
   116  			panic("gpgagent: failed to send " + cmd)
   117  		}
   118  		line, _, err := c.br.ReadLine()
   119  		if err != nil {
   120  			panic("gpgagent: failed to read " + cmd)
   121  		}
   122  		if !strings.HasPrefix(string(line), "OK") {
   123  			panic("gpgagent: response to " + cmd + " was " + string(line))
   124  		}
   125  	}
   126  	if d := os.Getenv("DISPLAY"); d != "" {
   127  		set("OPTION", "display="+d)
   128  	}
   129  	tty, err := os.Readlink("/proc/self/fd/0")
   130  	if err == nil {
   131  		set("OPTION", "ttyname="+tty)
   132  	}
   133  	set("OPTION", "ttytype="+os.Getenv("TERM"))
   134  	opts := ""
   135  	if pr.NoAsk {
   136  		opts += "--no-ask "
   137  	}
   138  
   139  	encOrX := func(s string) string {
   140  		if s == "" {
   141  			return "X"
   142  		}
   143  		return url.QueryEscape(s)
   144  	}
   145  
   146  	_, err = fmt.Fprintf(c.c, "GET_PASSPHRASE %s%s %s %s %s\n",
   147  		opts,
   148  		url.QueryEscape(pr.CacheKey),
   149  		encOrX(pr.Error),
   150  		encOrX(pr.Prompt),
   151  		encOrX(pr.Desc))
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	lineb, err := c.br.ReadSlice('\n')
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  	line := string(lineb)
   160  	if strings.HasPrefix(line, "OK ") {
   161  		decb, err := hex.DecodeString(line[3 : len(line)-1])
   162  		if err != nil {
   163  			return "", err
   164  		}
   165  		return string(decb), nil
   166  	}
   167  	fields := strings.Split(line, " ")
   168  	if len(fields) >= 2 && fields[0] == "ERR" {
   169  		switch fields[1] {
   170  		case "67108922":
   171  			return "", ErrNoData
   172  		case "83886179":
   173  			return "", ErrCancel
   174  		}
   175  	}
   176  	return "", errors.New(line)
   177  }