gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

pgpass.go (2709B)


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