You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
110 lines
2.6 KiB
110 lines
2.6 KiB
// Package pgpassfile is a parser PostgreSQL .pgpass files. |
|
package pgpassfile |
|
|
|
import ( |
|
"bufio" |
|
"io" |
|
"os" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
// Entry represents a line in a PG passfile. |
|
type Entry struct { |
|
Hostname string |
|
Port string |
|
Database string |
|
Username string |
|
Password string |
|
} |
|
|
|
// Passfile is the in memory data structure representing a PG passfile. |
|
type Passfile struct { |
|
Entries []*Entry |
|
} |
|
|
|
// ReadPassfile reads the file at path and parses it into a Passfile. |
|
func ReadPassfile(path string) (*Passfile, error) { |
|
f, err := os.Open(path) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer f.Close() |
|
|
|
return ParsePassfile(f) |
|
} |
|
|
|
// ParsePassfile reads r and parses it into a Passfile. |
|
func ParsePassfile(r io.Reader) (*Passfile, error) { |
|
passfile := &Passfile{} |
|
|
|
scanner := bufio.NewScanner(r) |
|
for scanner.Scan() { |
|
entry := parseLine(scanner.Text()) |
|
if entry != nil { |
|
passfile.Entries = append(passfile.Entries, entry) |
|
} |
|
} |
|
|
|
return passfile, scanner.Err() |
|
} |
|
|
|
// Match (not colons or escaped colon or escaped backslash)+. Essentially gives a split on unescaped |
|
// colon. |
|
var colonSplitterRegexp = regexp.MustCompile("(([^:]|(\\:)))+") |
|
|
|
// var colonSplitterRegexp = regexp.MustCompile("((?:[^:]|(?:\\:)|(?:\\\\))+)") |
|
|
|
// parseLine parses a line into an *Entry. It returns nil on comment lines or any other unparsable |
|
// line. |
|
func parseLine(line string) *Entry { |
|
const ( |
|
tmpBackslash = "\r" |
|
tmpColon = "\n" |
|
) |
|
|
|
line = strings.TrimSpace(line) |
|
|
|
if strings.HasPrefix(line, "#") { |
|
return nil |
|
} |
|
|
|
line = strings.Replace(line, `\\`, tmpBackslash, -1) |
|
line = strings.Replace(line, `\:`, tmpColon, -1) |
|
|
|
parts := strings.Split(line, ":") |
|
if len(parts) != 5 { |
|
return nil |
|
} |
|
|
|
// Unescape escaped colons and backslashes |
|
for i := range parts { |
|
parts[i] = strings.Replace(parts[i], tmpBackslash, `\`, -1) |
|
parts[i] = strings.Replace(parts[i], tmpColon, `:`, -1) |
|
} |
|
|
|
return &Entry{ |
|
Hostname: parts[0], |
|
Port: parts[1], |
|
Database: parts[2], |
|
Username: parts[3], |
|
Password: parts[4], |
|
} |
|
} |
|
|
|
// FindPassword finds the password for the provided hostname, port, database, and username. For a |
|
// Unix domain socket hostname must be set to "localhost". An empty string will be returned if no |
|
// match is found. |
|
// |
|
// See https://www.postgresql.org/docs/current/libpq-pgpass.html for more password file information. |
|
func (pf *Passfile) FindPassword(hostname, port, database, username string) (password string) { |
|
for _, e := range pf.Entries { |
|
if (e.Hostname == "*" || e.Hostname == hostname) && |
|
(e.Port == "*" || e.Port == port) && |
|
(e.Database == "*" || e.Database == database) && |
|
(e.Username == "*" || e.Username == username) { |
|
return e.Password |
|
} |
|
} |
|
return "" |
|
}
|
|
|