diff --git a/build.pl b/build.pl index 6e4c7c995..48f5dc569 100755 --- a/build.pl +++ b/build.pl @@ -433,6 +433,7 @@ TARGET: lib/go/camli/magic TARGET: lib/go/camli/misc TARGET: lib/go/camli/misc/amazon/s3 TARGET: lib/go/camli/misc/httprange +TARGET: lib/go/camli/misc/gpgagent TARGET: lib/go/camli/misc/pinentry TARGET: lib/go/camli/mysqlindexer TARGET: lib/go/camli/netutil diff --git a/lib/go/camli/misc/gpgagent/gpgagent.go b/lib/go/camli/misc/gpgagent/gpgagent.go new file mode 100644 index 000000000..a6b6f828d --- /dev/null +++ b/lib/go/camli/misc/gpgagent/gpgagent.go @@ -0,0 +1,102 @@ +/* +Copyright 2011 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gpgagent + +import ( + "bufio" + "fmt" + "net" + "io" + "strings" + "os" +) + +type PassphraseRequest struct { + CacheKey, ErrorMessage, Prompt, Desc string + + // Open file/socket to use, mostly for testing. + // If nil, os.Getenv("GPG_AGENT_INFO") will be used. + Conn io.ReadWriter +} + +var ErrNoAgent = os.NewError("GPG_AGENT_INFO not set in environment") + +func (pr *PassphraseRequest) GetPassphrase() (passphrase string, outerr os.Error) { + defer func() { + if e, ok := recover().(string); ok { + passphrase = "" + outerr = os.NewError(e) + } + }() + + conn := pr.Conn + var br *bufio.Reader + if conn == nil { + sp := strings.Split(os.Getenv("GPG_AGENT_INFO"), ":", 3) + if len(sp) == 0 || len(sp[0]) == 0 { + return "", ErrNoAgent + } + var err os.Error + addr := &net.UnixAddr{Net: "unix", Name: sp[0]} + uc, err := net.DialUnix("unix", nil, addr) + if err != nil { + return "", err + } + defer uc.Close() + br = bufio.NewReader(uc) + lineb, err := br.ReadSlice('\n') + if err != nil { + return "", err + } + line := string(lineb) + if !strings.HasPrefix(line, "OK") { + return "", fmt.Errorf("didn't get OK; got %q", line) + } + conn = uc + } else { + br = bufio.NewReader(conn) + } + set := func(cmd string, val string) { + if val == "" { + return + } + fmt.Fprintf(conn, "%s %s\n", cmd, val) + line, _, err := br.ReadLine() + if err != nil { + panic("Failed to " + cmd) + } + if !strings.HasPrefix(string(line), "OK") { + panic("Response to " + cmd + " was " + string(line)) + } + } + if d := os.Getenv("DISPLAY"); d != "" { + set("OPTION", "display="+d) + } + tty, err := os.Readlink("/proc/self/fd/0") + if err == nil { + set("OPTION", "ttyname="+tty) + } + set("OPTION", "ttytype="+os.Getenv("TERM")) + fmt.Fprintf(conn, "GET_PASSPHRASE foo err+msg prompt desc\n") + lineb, err := br.ReadSlice('\n') + if err != nil { + return "", err + } + line := string(lineb) + + return line, nil +} diff --git a/lib/go/camli/misc/gpgagent/gpgagent_test.go b/lib/go/camli/misc/gpgagent/gpgagent_test.go new file mode 100644 index 000000000..62b6589c2 --- /dev/null +++ b/lib/go/camli/misc/gpgagent/gpgagent_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2011 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gpgagent + +import ( + "os" + "testing" + "time" +) + +func TestPrompt(t *testing.T) { + if os.Getenv("TEST_GPGAGENT_LIB") != "1" { + t.Logf("skipping TestPrompt without $TEST_GPGAGENT_LIB == 1") + return + } + req := &PassphraseRequest{ + CacheKey: "gpgagent_test-cachekey", + } + s1, err := req.GetPassphrase() + if err != nil { + t.Fatal(err) + } + t1 := time.Nanoseconds() + s2, err := req.GetPassphrase() + if err != nil { + t.Fatal(err) + } + t2 := time.Nanoseconds() + if td := t2 - t1; td > 1e9/5 { + t.Errorf("cached passphrase took more than 1/5 second; took %d ns", td) + } + if s1 != s2 { + t.Errorf("cached passphrase differed; got %q, want %q", s2, s1) + } +} diff --git a/lib/go/camli/misc/pinentry/pinentry.go b/lib/go/camli/misc/pinentry/pinentry.go index 7ae826d54..ef4b1ee62 100644 --- a/lib/go/camli/misc/pinentry/pinentry.go +++ b/lib/go/camli/misc/pinentry/pinentry.go @@ -82,7 +82,7 @@ func (r *Request) GetPIN() (pin string, outerr os.Error) { set("SETCANCEL", r.Cancel) set("SETERROR", r.Error) set("OPTION", "ttytype=" + os.Getenv("TERM")) - tty, err := os.Readlink("/proc/self/fd/1") + tty, err := os.Readlink("/proc/self/fd/0") if err == nil { set("OPTION", "ttyname=" + tty) }