pgpass.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. // Package pgpassfile is a parser PostgreSQL .pgpass files.
  2. package pgpassfile
  3. import (
  4. "bufio"
  5. "io"
  6. "os"
  7. "regexp"
  8. "strings"
  9. )
  10. // Entry represents a line in a PG passfile.
  11. type Entry struct {
  12. Hostname string
  13. Port string
  14. Database string
  15. Username string
  16. Password string
  17. }
  18. // Passfile is the in memory data structure representing a PG passfile.
  19. type Passfile struct {
  20. Entries []*Entry
  21. }
  22. // ReadPassfile reads the file at path and parses it into a Passfile.
  23. func ReadPassfile(path string) (*Passfile, error) {
  24. f, err := os.Open(path)
  25. if err != nil {
  26. return nil, err
  27. }
  28. defer f.Close()
  29. return ParsePassfile(f)
  30. }
  31. // ParsePassfile reads r and parses it into a Passfile.
  32. func ParsePassfile(r io.Reader) (*Passfile, error) {
  33. passfile := &Passfile{}
  34. scanner := bufio.NewScanner(r)
  35. for scanner.Scan() {
  36. entry := parseLine(scanner.Text())
  37. if entry != nil {
  38. passfile.Entries = append(passfile.Entries, entry)
  39. }
  40. }
  41. return passfile, scanner.Err()
  42. }
  43. // Match (not colons or escaped colon or escaped backslash)+. Essentially gives a split on unescaped
  44. // colon.
  45. var colonSplitterRegexp = regexp.MustCompile("(([^:]|(\\:)))+")
  46. // var colonSplitterRegexp = regexp.MustCompile("((?:[^:]|(?:\\:)|(?:\\\\))+)")
  47. // parseLine parses a line into an *Entry. It returns nil on comment lines or any other unparsable
  48. // line.
  49. func parseLine(line string) *Entry {
  50. const (
  51. tmpBackslash = "\r"
  52. tmpColon = "\n"
  53. )
  54. line = strings.TrimSpace(line)
  55. if strings.HasPrefix(line, "#") {
  56. return nil
  57. }
  58. line = strings.Replace(line, `\\`, tmpBackslash, -1)
  59. line = strings.Replace(line, `\:`, tmpColon, -1)
  60. parts := strings.Split(line, ":")
  61. if len(parts) != 5 {
  62. return nil
  63. }
  64. // Unescape escaped colons and backslashes
  65. for i := range parts {
  66. parts[i] = strings.Replace(parts[i], tmpBackslash, `\`, -1)
  67. parts[i] = strings.Replace(parts[i], tmpColon, `:`, -1)
  68. }
  69. return &Entry{
  70. Hostname: parts[0],
  71. Port: parts[1],
  72. Database: parts[2],
  73. Username: parts[3],
  74. Password: parts[4],
  75. }
  76. }
  77. // FindPassword finds the password for the provided hostname, port, database, and username. For a
  78. // Unix domain socket hostname must be set to "localhost". An empty string will be returned if no
  79. // match is found.
  80. //
  81. // See https://www.postgresql.org/docs/current/libpq-pgpass.html for more password file information.
  82. func (pf *Passfile) FindPassword(hostname, port, database, username string) (password string) {
  83. for _, e := range pf.Entries {
  84. if (e.Hostname == "*" || e.Hostname == hostname) &&
  85. (e.Port == "*" || e.Port == port) &&
  86. (e.Database == "*" || e.Database == database) &&
  87. (e.Username == "*" || e.Username == username) {
  88. return e.Password
  89. }
  90. }
  91. return ""
  92. }