1;;; 2;;; PostgreSQL pgpass parser, see 3;;; https://www.postgresql.org/docs/current/static/libpq-pgpass.html 4;;; 5 6(in-package :pgloader.parser) 7 8(defstruct pgpass 9 hostname port database username password) 10 11(defun pgpass-char-p (char) 12 (not (member char '(#\: #\\) :test #'char=))) 13 14(defrule pgpass-escaped-char (and #\\ (or #\\ #\:)) 15 (:lambda (c) (second c))) 16 17(defrule pgpass-ipv6-hostname (and #\[ 18 (+ (or (digit-char-p character) ":")) 19 #\]) 20 (:lambda (ipv6) (text (second ipv6)))) 21 22(defrule pgpass-entry (or "*" 23 (+ (or pgpass-ipv6-hostname 24 pgpass-escaped-char 25 (pgpass-char-p character)))) 26 (:lambda (e) (text e))) 27 28(defrule pgpass-line (and (? pgpass-entry) #\: pgpass-entry #\: 29 pgpass-entry #\: pgpass-entry #\: 30 (? pgpass-entry)) 31 (:lambda (pl) 32 (make-pgpass :hostname (or (first pl) "localhost") 33 :port (third pl) 34 :database (fifth pl) 35 :username (seventh pl) 36 :password (ninth pl)))) 37 38(defun get-pgpass-filename () 39 "Return where to find .pgpass file" 40 (or (uiop:getenv "PGPASSFILE") 41 #-windows (uiop:merge-pathnames* (uiop:make-pathname* :name ".pgpass") 42 (user-homedir-pathname)) 43 #+windows (let ((pgpass-dir (format nil "~a/~a/" 44 (uiop:getenv " %APPDATA%") 45 "postgresql"))) 46 (uiop:make-pathname* :directory pgpass-dir 47 :name "pgpass" 48 :type "conf")))) 49 50(defun parse-pgpass-file (&optional pgpass-filename) 51 (let ((pgpass-filename (or pgpass-filename (get-pgpass-filename)))) 52 (when (and pgpass-filename (probe-file pgpass-filename)) 53 (with-open-file (s pgpass-filename 54 :direction :input 55 :if-does-not-exist nil 56 :element-type 'character) 57 (when s 58 (loop :for line := (read-line s nil nil) 59 :while line 60 :when (and line 61 (< 0 (length line)) 62 (char/= #\# (aref line 0))) 63 :collect (parse 'pgpass-line line))))))) 64 65(defun match-hostname (pgpass hostname) 66 "A host name of localhost matches both TCP (host name localhost) and Unix 67 domain socket (pghost empty or the default socket directory) connections 68 coming from the local machine." 69 (cond ((and (string= "localhost" (pgpass-hostname pgpass)) 70 (or (eq :unix hostname) 71 (and (stringp hostname) 72 (string= "localhost" hostname))))) 73 ((string= "*" (pgpass-hostname pgpass)) 74 t) 75 (t 76 (and (stringp hostname) 77 (string= (pgpass-hostname pgpass) hostname))))) 78 79(defun match-pgpass (pgpass hostname port database username) 80 (flet ((same-p (entry param) 81 (or (string= "*" entry) 82 (string= entry param)))) 83 (when (and (match-hostname pgpass hostname) 84 (same-p (pgpass-port pgpass) port) 85 (same-p (pgpass-database pgpass) database) 86 (same-p (pgpass-username pgpass) username)) 87 (pgpass-password pgpass)))) 88 89(defun match-pgpass-entries (pgpass-lines hostname port database username) 90 "Return matched password from ~/.pgpass or PGPASSFILE, or nil." 91 (loop :for pgpass :in pgpass-lines 92 :thereis (match-pgpass pgpass hostname port database username))) 93 94(defun match-pgpass-file (hostname port database username) 95 "Return matched password from ~/.pgpass or PGPASSFILE, or nil." 96 (handler-case 97 (let ((pgpass-entries (parse-pgpass-file))) 98 (when pgpass-entries 99 (match-pgpass-entries pgpass-entries hostname port database username))) 100 (condition (e) 101 ;; if we had any problem (parsing error in pgpass or otherwise), just 102 ;; return a NIL password 103 (log-message :warning "Error reading pgass file: ~a" e) 104 nil))) 105