Module: Pgpass

Defined in:
lib/pgpass.rb,
lib/pgpass/version.rb

Overview

The file .pgpass in a user’s home directory or the file referenced by PGPASSFILE can contain passwords to be used if the connection requires a password (and no password has been specified otherwise). On Microsoft Windows the file is named %APPDATA%postgresqlpgpass.conf (where %APPDATA% refers to the Application Data subdirectory in the user’s profile).

This file should contain lines of the following format:

hostname:port:database:username:password

(You can add a reminder comment to the file by copying the line above and preceding it with ‘#`.) Each of the first four fields can be a literal value, or `*`, which matches anything.

The password field from the first line that matches the current connection parameters will be used.

(Therefore, put more-specific entries first when you are using wildcards.) If an entry needs to contain : or , escape this character with .

A host name of localhost matches both TCP (host name localhost) and Unix domain socket (pghost empty or the default socket directory) connections coming from the local machine.

In a standby server, a database name of replication matches streaming replication connections made to the master server.

The database field is of limited usefulness because users have the same password for all databases in the same cluster.

On Unix systems, the permissions on .pgpass must disallow any access to world or group; achieve this by the command chmod 0600 ~/.pgpass. If the permissions are less strict than this, the file will be ignored.

On Microsoft Windows, it is assumed that the file is stored in a directory that is secure, so no special permissions check is made.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

LOCATIONS =
[ENV['PGPASSFILE'], './.pgpass', '~/.pgpass']
VERSION =
"2022.07.27"

Class Method Summary collapse

Class Method Details

.guess(paths = PATH) ⇒ Object



136
137
138
139
140
141
142
143
144
# File 'lib/pgpass.rb', line 136

def guess(paths = PATH)
  paths.each do |path|
    begin
      load_file(File.join(path, '.pgpass'))
    rescue Errno::ENOENT => e
      warn(e)
    end
  end
end

.load(io) ⇒ Object



150
151
152
# File 'lib/pgpass.rb', line 150

def load(io)
  io.each_line.map { |line| parse_line(line) }
end

.load_file(path) ⇒ Object



146
147
148
# File 'lib/pgpass.rb', line 146

def load_file(path)
  File.open(File.expand_path(path), 'r') { |io| load(io) }
end

.match(given_options = {}) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/pgpass.rb', line 108

def match(given_options = {})
  search = Entry.create(
    user: (ENV['PGUSER'] || '*'),
    password: ENV['PGPASSWORD'],
    host: (ENV['PGHOST'] || '*'),
    port: (ENV['PGPORT'] || '*'),
    database: (ENV['PGDATABASE'] || '*')
  ).merge(given_options)

  LOCATIONS.compact.each do |path|
    path = File.expand_path(path)
    # consider only files
    next unless File.file?(path)

    # that aren't world/group accessible
    unless (File.stat(path).mode & 0o077).zero?
      warn %(WARNING: password file "#{path}" has group or world access; permissions should be u=rw (0600) or less)
      next
    end

    load_file(path).each do |entry|
      return entry.complement(search) if entry == search
    end
  end

  nil
end

.parse_line(line) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/pgpass.rb', line 154

def parse_line(line)
  sc = StringScanner.new(line)
  entry = Entry.new
  key_index = 0
  value = []

  loop do
    pos = sc.pos

    return if sc.bol? && (sc.scan(/\s*#/) || sc.scan(/\s*$/))

    if sc.eos?
      entry[Entry.members[key_index]] = value.join
      return entry # end of string
    end

    if sc.scan(/\\:/)
      value << ':'
    elsif sc.scan(/\\\\/)
      value << '\\'
    elsif sc.scan(/:/)
      entry[Entry.members[key_index]] = value.join
      key_index += 1
      value = []
    elsif sc.scan(/\r\n|\r|\n/)
      entry[Entry.members[key_index]] = value.join
      return entry
    elsif sc.scan(/./)
      value << sc[0]
    end

    raise "position didn't advance, stuck in parsing" if sc.pos == pos
  end

  entry
end