Skip to content

Commit

Permalink
Support pgpass
Browse files Browse the repository at this point in the history
  • Loading branch information
j7b authored and jackc committed Mar 17, 2017
1 parent 94749e5 commit 94d56e8
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 3 deletions.
12 changes: 9 additions & 3 deletions conn.go
Expand Up @@ -542,7 +542,9 @@ func ParseURI(uri string) (ConnConfig, error) {

cp.RuntimeParams[k] = v[0]
}

if cp.Password == "" {
pgpass(&cp)
}
return cp, nil
}

Expand Down Expand Up @@ -595,7 +597,9 @@ func ParseDSN(s string) (ConnConfig, error) {
if err != nil {
return cp, err
}

if cp.Password == "" {
pgpass(&cp)
}
return cp, nil
}

Expand Down Expand Up @@ -658,7 +662,9 @@ func ParseEnvLibpq() (ConnConfig, error) {
if appname := os.Getenv("PGAPPNAME"); appname != "" {
cc.RuntimeParams["application_name"] = appname
}

if cc.Password == "" {
pgpass(&cc)
}
return cc, nil
}

Expand Down
85 changes: 85 additions & 0 deletions pgpass.go
@@ -0,0 +1,85 @@
package pgx

import (
"bufio"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
)

func parsepgpass(cfg *ConnConfig, line string) *string {
const (
backslash = "\r"
colon = "\n"
)
const (
host int = iota
port
database
username
pw
)
line = strings.Replace(line, `\:`, colon, -1)
line = strings.Replace(line, `\\`, backslash, -1)
parts := strings.Split(line, `:`)
if len(parts) != 5 {
return nil
}
for i := range parts {
if parts[i] == `*` {
continue
}
parts[i] = strings.Replace(strings.Replace(parts[i], backslash, `\`, -1), colon, `:`, -1)
switch i {
case host:
if parts[i] != cfg.Host {
return nil
}
case port:
portstr := fmt.Sprintf(`%v`, cfg.Port)
if portstr == "0" {
portstr = "5432"
}
if parts[i] != portstr {
return nil
}
case database:
if parts[i] != cfg.Database {
return nil
}
case username:
if parts[i] != cfg.User {
return nil
}
}
}
return &parts[4]
}

func pgpass(cfg *ConnConfig) (found bool) {
passfile := os.Getenv("PGPASSFILE")
if passfile == "" {
u, err := user.Current()
if err != nil {
return
}
passfile = filepath.Join(u.HomeDir, ".pgpass")
}
f, err := os.Open(passfile)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
var pw *string
for scanner.Scan() {
pw = parsepgpass(cfg, scanner.Text())
if pw != nil {
cfg.Password = *pw
return true
}
}
return false
}
57 changes: 57 additions & 0 deletions pgpass_test.go
@@ -0,0 +1,57 @@
package pgx

import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
)

func unescape(s string) string {
s = strings.Replace(s, `\:`, `:`, -1)
s = strings.Replace(s, `\\`, `\`, -1)
return s
}

var passfile = [][]string{
[]string{"test1", "5432", "larrydb", "larry", "whatstheidea"},
[]string{"test1", "5432", "moedb", "moe", "imbecile"},
[]string{"test1", "5432", "curlydb", "curly", "nyuknyuknyuk"},
[]string{"test2", "5432", "*", "shemp", "heymoe"},
[]string{"test2", "5432", "*", "*", `test\\ing\:`},
}

func TestPGPass(t *testing.T) {
tf, err := ioutil.TempFile("", "")
if err != nil {
t.Fatal(err)
}
defer tf.Close()
defer os.Remove(tf.Name())
os.Setenv("PGPASSFILE", tf.Name())
for _, l := range passfile {
_, err := fmt.Fprintln(tf, strings.Join(l, `:`))
if err != nil {
t.Fatal(err)
}
}
if err = tf.Close(); err != nil {
t.Fatal(err)
}
for i, l := range passfile {
cfg := ConnConfig{Host: l[0], Database: l[2], User: l[3]}
found := pgpass(&cfg)
if !found {
t.Fatalf("Entry %v not found", i)
}
if cfg.Password != unescape(l[4]) {
t.Fatalf(`Password mismatch entry %v want %s got %s`, i, unescape(l[4]), cfg.Password)
}
}
cfg := ConnConfig{Host: "derp", Database: "herp", User: "joe"}
found := pgpass(&cfg)
if found {
t.Fatal("bad found")
}
}

0 comments on commit 94d56e8

Please sign in to comment.