1package pgconn
2
3import (
4	"context"
5	"crypto/tls"
6	"crypto/x509"
7	"errors"
8	"fmt"
9	"io"
10	"io/ioutil"
11	"math"
12	"net"
13	"net/url"
14	"os"
15	"path/filepath"
16	"strconv"
17	"strings"
18	"time"
19
20	"github.com/jackc/chunkreader/v2"
21	"github.com/jackc/pgpassfile"
22	"github.com/jackc/pgproto3/v2"
23	"github.com/jackc/pgservicefile"
24)
25
26type AfterConnectFunc func(ctx context.Context, pgconn *PgConn) error
27type ValidateConnectFunc func(ctx context.Context, pgconn *PgConn) error
28
29// Config is the settings used to establish a connection to a PostgreSQL server. It must be created by ParseConfig. A
30// manually initialized Config will cause ConnectConfig to panic.
31type Config struct {
32	Host           string // host (e.g. localhost) or absolute path to unix domain socket directory (e.g. /private/tmp)
33	Port           uint16
34	Database       string
35	User           string
36	Password       string
37	TLSConfig      *tls.Config // nil disables TLS
38	ConnectTimeout time.Duration
39	DialFunc       DialFunc   // e.g. net.Dialer.DialContext
40	LookupFunc     LookupFunc // e.g. net.Resolver.LookupHost
41	BuildFrontend  BuildFrontendFunc
42	RuntimeParams  map[string]string // Run-time parameters to set on connection as session default values (e.g. search_path or application_name)
43
44	Fallbacks []*FallbackConfig
45
46	// ValidateConnect is called during a connection attempt after a successful authentication with the PostgreSQL server.
47	// It can be used to validate that the server is acceptable. If this returns an error the connection is closed and the next
48	// fallback config is tried. This allows implementing high availability behavior such as libpq does with target_session_attrs.
49	ValidateConnect ValidateConnectFunc
50
51	// AfterConnect is called after ValidateConnect. It can be used to set up the connection (e.g. Set session variables
52	// or prepare statements). If this returns an error the connection attempt fails.
53	AfterConnect AfterConnectFunc
54
55	// OnNotice is a callback function called when a notice response is received.
56	OnNotice NoticeHandler
57
58	// OnNotification is a callback function called when a notification from the LISTEN/NOTIFY system is received.
59	OnNotification NotificationHandler
60
61	createdByParseConfig bool // Used to enforce created by ParseConfig rule.
62}
63
64// Copy returns a deep copy of the config that is safe to use and modify.
65// The only exception is the TLSConfig field:
66// according to the tls.Config docs it must not be modified after creation.
67func (c *Config) Copy() *Config {
68	newConf := new(Config)
69	*newConf = *c
70	if newConf.TLSConfig != nil {
71		newConf.TLSConfig = c.TLSConfig.Clone()
72	}
73	if newConf.RuntimeParams != nil {
74		newConf.RuntimeParams = make(map[string]string, len(c.RuntimeParams))
75		for k, v := range c.RuntimeParams {
76			newConf.RuntimeParams[k] = v
77		}
78	}
79	if newConf.Fallbacks != nil {
80		newConf.Fallbacks = make([]*FallbackConfig, len(c.Fallbacks))
81		for i, fallback := range c.Fallbacks {
82			newFallback := new(FallbackConfig)
83			*newFallback = *fallback
84			if newFallback.TLSConfig != nil {
85				newFallback.TLSConfig = fallback.TLSConfig.Clone()
86			}
87			newConf.Fallbacks[i] = newFallback
88		}
89	}
90	return newConf
91}
92
93// FallbackConfig is additional settings to attempt a connection with when the primary Config fails to establish a
94// network connection. It is used for TLS fallback such as sslmode=prefer and high availability (HA) connections.
95type FallbackConfig struct {
96	Host      string // host (e.g. localhost) or path to unix domain socket directory (e.g. /private/tmp)
97	Port      uint16
98	TLSConfig *tls.Config // nil disables TLS
99}
100
101// NetworkAddress converts a PostgreSQL host and port into network and address suitable for use with
102// net.Dial.
103func NetworkAddress(host string, port uint16) (network, address string) {
104	if strings.HasPrefix(host, "/") {
105		network = "unix"
106		address = filepath.Join(host, ".s.PGSQL.") + strconv.FormatInt(int64(port), 10)
107	} else {
108		network = "tcp"
109		address = net.JoinHostPort(host, strconv.Itoa(int(port)))
110	}
111	return network, address
112}
113
114// ParseConfig builds a *Config with similar behavior to the PostgreSQL standard C library libpq. It uses the same
115// defaults as libpq (e.g. port=5432) and understands most PG* environment variables. ParseConfig closely matches
116// the parsing behavior of libpq. connString may either be in URL format or keyword = value format (DSN style). See
117// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING for details. connString also may be
118// empty to only read from the environment. If a password is not supplied it will attempt to read the .pgpass file.
119//
120//   # Example DSN
121//   user=jack password=secret host=pg.example.com port=5432 dbname=mydb sslmode=verify-ca
122//
123//   # Example URL
124//   postgres://jack:secret@pg.example.com:5432/mydb?sslmode=verify-ca
125//
126// The returned *Config may be modified. However, it is strongly recommended that any configuration that can be done
127// through the connection string be done there. In particular the fields Host, Port, TLSConfig, and Fallbacks can be
128// interdependent (e.g. TLSConfig needs knowledge of the host to validate the server certificate). These fields should
129// not be modified individually. They should all be modified or all left unchanged.
130//
131// ParseConfig supports specifying multiple hosts in similar manner to libpq. Host and port may include comma separated
132// values that will be tried in order. This can be used as part of a high availability system. See
133// https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS for more information.
134//
135//   # Example URL
136//   postgres://jack:secret@foo.example.com:5432,bar.example.com:5432/mydb
137//
138// ParseConfig currently recognizes the following environment variable and their parameter key word equivalents passed
139// via database URL or DSN:
140//
141// 	 PGHOST
142// 	 PGPORT
143// 	 PGDATABASE
144// 	 PGUSER
145// 	 PGPASSWORD
146// 	 PGPASSFILE
147// 	 PGSERVICE
148// 	 PGSERVICEFILE
149// 	 PGSSLMODE
150// 	 PGSSLCERT
151// 	 PGSSLKEY
152// 	 PGSSLROOTCERT
153// 	 PGAPPNAME
154// 	 PGCONNECT_TIMEOUT
155// 	 PGTARGETSESSIONATTRS
156//
157// See http://www.postgresql.org/docs/11/static/libpq-envars.html for details on the meaning of environment variables.
158//
159// See https://www.postgresql.org/docs/11/libpq-connect.html#LIBPQ-PARAMKEYWORDS for parameter key word names. They are
160// usually but not always the environment variable name downcased and without the "PG" prefix.
161//
162// Important Security Notes:
163//
164// ParseConfig tries to match libpq behavior with regard to PGSSLMODE. This includes defaulting to "prefer" behavior if
165// not set.
166//
167// See http://www.postgresql.org/docs/11/static/libpq-ssl.html#LIBPQ-SSL-PROTECTION for details on what level of
168// security each sslmode provides.
169//
170// The sslmode "prefer" (the default), sslmode "allow", and multiple hosts are implemented via the Fallbacks field of
171// the Config struct. If TLSConfig is manually changed it will not affect the fallbacks. For example, in the case of
172// sslmode "prefer" this means it will first try the main Config settings which use TLS, then it will try the fallback
173// which does not use TLS. This can lead to an unexpected unencrypted connection if the main TLS config is manually
174// changed later but the unencrypted fallback is present. Ensure there are no stale fallbacks when manually setting
175// TLCConfig.
176//
177// Other known differences with libpq:
178//
179// If a host name resolves into multiple addresses, libpq will try all addresses. pgconn will only try the first.
180//
181// When multiple hosts are specified, libpq allows them to have different passwords set via the .pgpass file. pgconn
182// does not.
183//
184// In addition, ParseConfig accepts the following options:
185//
186// 	min_read_buffer_size
187// 		The minimum size of the internal read buffer. Default 8192.
188// 	servicefile
189// 		libpq only reads servicefile from the PGSERVICEFILE environment variable. ParseConfig accepts servicefile as a
190// 		part of the connection string.
191func ParseConfig(connString string) (*Config, error) {
192	defaultSettings := defaultSettings()
193	envSettings := parseEnvSettings()
194
195	connStringSettings := make(map[string]string)
196	if connString != "" {
197		var err error
198		// connString may be a database URL or a DSN
199		if strings.HasPrefix(connString, "postgres://") || strings.HasPrefix(connString, "postgresql://") {
200			connStringSettings, err = parseURLSettings(connString)
201			if err != nil {
202				return nil, &parseConfigError{connString: connString, msg: "failed to parse as URL", err: err}
203			}
204		} else {
205			connStringSettings, err = parseDSNSettings(connString)
206			if err != nil {
207				return nil, &parseConfigError{connString: connString, msg: "failed to parse as DSN", err: err}
208			}
209		}
210	}
211
212	settings := mergeSettings(defaultSettings, envSettings, connStringSettings)
213	if service, present := settings["service"]; present {
214		serviceSettings, err := parseServiceSettings(settings["servicefile"], service)
215		if err != nil {
216			return nil, &parseConfigError{connString: connString, msg: "failed to read service", err: err}
217		}
218
219		settings = mergeSettings(defaultSettings, envSettings, serviceSettings, connStringSettings)
220	}
221
222	minReadBufferSize, err := strconv.ParseInt(settings["min_read_buffer_size"], 10, 32)
223	if err != nil {
224		return nil, &parseConfigError{connString: connString, msg: "cannot parse min_read_buffer_size", err: err}
225	}
226
227	config := &Config{
228		createdByParseConfig: true,
229		Database:             settings["database"],
230		User:                 settings["user"],
231		Password:             settings["password"],
232		RuntimeParams:        make(map[string]string),
233		BuildFrontend:        makeDefaultBuildFrontendFunc(int(minReadBufferSize)),
234	}
235
236	if connectTimeoutSetting, present := settings["connect_timeout"]; present {
237		connectTimeout, err := parseConnectTimeoutSetting(connectTimeoutSetting)
238		if err != nil {
239			return nil, &parseConfigError{connString: connString, msg: "invalid connect_timeout", err: err}
240		}
241		config.ConnectTimeout = connectTimeout
242		config.DialFunc = makeConnectTimeoutDialFunc(connectTimeout)
243	} else {
244		defaultDialer := makeDefaultDialer()
245		config.DialFunc = defaultDialer.DialContext
246	}
247
248	config.LookupFunc = makeDefaultResolver().LookupHost
249
250	notRuntimeParams := map[string]struct{}{
251		"host":                 struct{}{},
252		"port":                 struct{}{},
253		"database":             struct{}{},
254		"user":                 struct{}{},
255		"password":             struct{}{},
256		"passfile":             struct{}{},
257		"connect_timeout":      struct{}{},
258		"sslmode":              struct{}{},
259		"sslkey":               struct{}{},
260		"sslcert":              struct{}{},
261		"sslrootcert":          struct{}{},
262		"target_session_attrs": struct{}{},
263		"min_read_buffer_size": struct{}{},
264		"service":              struct{}{},
265		"servicefile":          struct{}{},
266	}
267
268	for k, v := range settings {
269		if _, present := notRuntimeParams[k]; present {
270			continue
271		}
272		config.RuntimeParams[k] = v
273	}
274
275	fallbacks := []*FallbackConfig{}
276
277	hosts := strings.Split(settings["host"], ",")
278	ports := strings.Split(settings["port"], ",")
279
280	for i, host := range hosts {
281		var portStr string
282		if i < len(ports) {
283			portStr = ports[i]
284		} else {
285			portStr = ports[0]
286		}
287
288		port, err := parsePort(portStr)
289		if err != nil {
290			return nil, &parseConfigError{connString: connString, msg: "invalid port", err: err}
291		}
292
293		var tlsConfigs []*tls.Config
294
295		// Ignore TLS settings if Unix domain socket like libpq
296		if network, _ := NetworkAddress(host, port); network == "unix" {
297			tlsConfigs = append(tlsConfigs, nil)
298		} else {
299			var err error
300			tlsConfigs, err = configTLS(settings)
301			if err != nil {
302				return nil, &parseConfigError{connString: connString, msg: "failed to configure TLS", err: err}
303			}
304		}
305
306		for _, tlsConfig := range tlsConfigs {
307			fallbacks = append(fallbacks, &FallbackConfig{
308				Host:      host,
309				Port:      port,
310				TLSConfig: tlsConfig,
311			})
312		}
313	}
314
315	config.Host = fallbacks[0].Host
316	config.Port = fallbacks[0].Port
317	config.TLSConfig = fallbacks[0].TLSConfig
318	config.Fallbacks = fallbacks[1:]
319
320	passfile, err := pgpassfile.ReadPassfile(settings["passfile"])
321	if err == nil {
322		if config.Password == "" {
323			host := config.Host
324			if network, _ := NetworkAddress(config.Host, config.Port); network == "unix" {
325				host = "localhost"
326			}
327
328			config.Password = passfile.FindPassword(host, strconv.Itoa(int(config.Port)), config.Database, config.User)
329		}
330	}
331
332	if settings["target_session_attrs"] == "read-write" {
333		config.ValidateConnect = ValidateConnectTargetSessionAttrsReadWrite
334	} else if settings["target_session_attrs"] != "any" {
335		return nil, &parseConfigError{connString: connString, msg: fmt.Sprintf("unknown target_session_attrs value: %v", settings["target_session_attrs"])}
336	}
337
338	return config, nil
339}
340
341func mergeSettings(settingSets ...map[string]string) map[string]string {
342	settings := make(map[string]string)
343
344	for _, s2 := range settingSets {
345		for k, v := range s2 {
346			settings[k] = v
347		}
348	}
349
350	return settings
351}
352
353func parseEnvSettings() map[string]string {
354	settings := make(map[string]string)
355
356	nameMap := map[string]string{
357		"PGHOST":               "host",
358		"PGPORT":               "port",
359		"PGDATABASE":           "database",
360		"PGUSER":               "user",
361		"PGPASSWORD":           "password",
362		"PGPASSFILE":           "passfile",
363		"PGAPPNAME":            "application_name",
364		"PGCONNECT_TIMEOUT":    "connect_timeout",
365		"PGSSLMODE":            "sslmode",
366		"PGSSLKEY":             "sslkey",
367		"PGSSLCERT":            "sslcert",
368		"PGSSLROOTCERT":        "sslrootcert",
369		"PGTARGETSESSIONATTRS": "target_session_attrs",
370		"PGSERVICE":            "service",
371		"PGSERVICEFILE":        "servicefile",
372	}
373
374	for envname, realname := range nameMap {
375		value := os.Getenv(envname)
376		if value != "" {
377			settings[realname] = value
378		}
379	}
380
381	return settings
382}
383
384func parseURLSettings(connString string) (map[string]string, error) {
385	settings := make(map[string]string)
386
387	url, err := url.Parse(connString)
388	if err != nil {
389		return nil, err
390	}
391
392	if url.User != nil {
393		settings["user"] = url.User.Username()
394		if password, present := url.User.Password(); present {
395			settings["password"] = password
396		}
397	}
398
399	// Handle multiple host:port's in url.Host by splitting them into host,host,host and port,port,port.
400	var hosts []string
401	var ports []string
402	for _, host := range strings.Split(url.Host, ",") {
403		if host == "" {
404			continue
405		}
406		if isIPOnly(host) {
407			hosts = append(hosts, strings.Trim(host, "[]"))
408			continue
409		}
410		h, p, err := net.SplitHostPort(host)
411		if err != nil {
412			return nil, fmt.Errorf("failed to split host:port in '%s', err: %w", host, err)
413		}
414		hosts = append(hosts, h)
415		ports = append(ports, p)
416	}
417	if len(hosts) > 0 {
418		settings["host"] = strings.Join(hosts, ",")
419	}
420	if len(ports) > 0 {
421		settings["port"] = strings.Join(ports, ",")
422	}
423
424	database := strings.TrimLeft(url.Path, "/")
425	if database != "" {
426		settings["database"] = database
427	}
428
429	for k, v := range url.Query() {
430		settings[k] = v[0]
431	}
432
433	return settings, nil
434}
435
436func isIPOnly(host string) bool {
437	return net.ParseIP(strings.Trim(host, "[]")) != nil || !strings.Contains(host, ":")
438}
439
440var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
441
442func parseDSNSettings(s string) (map[string]string, error) {
443	settings := make(map[string]string)
444
445	nameMap := map[string]string{
446		"dbname": "database",
447	}
448
449	for len(s) > 0 {
450		var key, val string
451		eqIdx := strings.IndexRune(s, '=')
452		if eqIdx < 0 {
453			return nil, errors.New("invalid dsn")
454		}
455
456		key = strings.Trim(s[:eqIdx], " \t\n\r\v\f")
457		s = strings.TrimLeft(s[eqIdx+1:], " \t\n\r\v\f")
458		if len(s) == 0 {
459		} else if s[0] != '\'' {
460			end := 0
461			for ; end < len(s); end++ {
462				if asciiSpace[s[end]] == 1 {
463					break
464				}
465				if s[end] == '\\' {
466					end++
467					if end == len(s) {
468						return nil, errors.New("invalid backslash")
469					}
470				}
471			}
472			val = strings.Replace(strings.Replace(s[:end], "\\\\", "\\", -1), "\\'", "'", -1)
473			if end == len(s) {
474				s = ""
475			} else {
476				s = s[end+1:]
477			}
478		} else { // quoted string
479			s = s[1:]
480			end := 0
481			for ; end < len(s); end++ {
482				if s[end] == '\'' {
483					break
484				}
485				if s[end] == '\\' {
486					end++
487				}
488			}
489			if end == len(s) {
490				return nil, errors.New("unterminated quoted string in connection info string")
491			}
492			val = strings.Replace(strings.Replace(s[:end], "\\\\", "\\", -1), "\\'", "'", -1)
493			if end == len(s) {
494				s = ""
495			} else {
496				s = s[end+1:]
497			}
498		}
499
500		if k, ok := nameMap[key]; ok {
501			key = k
502		}
503
504		if key == "" {
505			return nil, errors.New("invalid dsn")
506		}
507
508		settings[key] = val
509	}
510
511	return settings, nil
512}
513
514func parseServiceSettings(servicefilePath, serviceName string) (map[string]string, error) {
515	servicefile, err := pgservicefile.ReadServicefile(servicefilePath)
516	if err != nil {
517		return nil, fmt.Errorf("failed to read service file: %v", servicefilePath)
518	}
519
520	service, err := servicefile.GetService(serviceName)
521	if err != nil {
522		return nil, fmt.Errorf("unable to find service: %v", serviceName)
523	}
524
525	nameMap := map[string]string{
526		"dbname": "database",
527	}
528
529	settings := make(map[string]string, len(service.Settings))
530	for k, v := range service.Settings {
531		if k2, present := nameMap[k]; present {
532			k = k2
533		}
534		settings[k] = v
535	}
536
537	return settings, nil
538}
539
540// configTLS uses libpq's TLS parameters to construct  []*tls.Config. It is
541// necessary to allow returning multiple TLS configs as sslmode "allow" and
542// "prefer" allow fallback.
543func configTLS(settings map[string]string) ([]*tls.Config, error) {
544	host := settings["host"]
545	sslmode := settings["sslmode"]
546	sslrootcert := settings["sslrootcert"]
547	sslcert := settings["sslcert"]
548	sslkey := settings["sslkey"]
549
550	// Match libpq default behavior
551	if sslmode == "" {
552		sslmode = "prefer"
553	}
554
555	tlsConfig := &tls.Config{}
556
557	switch sslmode {
558	case "disable":
559		return []*tls.Config{nil}, nil
560	case "allow", "prefer":
561		tlsConfig.InsecureSkipVerify = true
562	case "require":
563		// According to PostgreSQL documentation, if a root CA file exists,
564		// the behavior of sslmode=require should be the same as that of verify-ca
565		//
566		// See https://www.postgresql.org/docs/12/libpq-ssl.html
567		if sslrootcert != "" {
568			goto nextCase
569		}
570		tlsConfig.InsecureSkipVerify = true
571		break
572	nextCase:
573		fallthrough
574	case "verify-ca":
575		// Don't perform the default certificate verification because it
576		// will verify the hostname. Instead, verify the server's
577		// certificate chain ourselves in VerifyPeerCertificate and
578		// ignore the server name. This emulates libpq's verify-ca
579		// behavior.
580		//
581		// See https://github.com/golang/go/issues/21971#issuecomment-332693931
582		// and https://pkg.go.dev/crypto/tls?tab=doc#example-Config-VerifyPeerCertificate
583		// for more info.
584		tlsConfig.InsecureSkipVerify = true
585		tlsConfig.VerifyPeerCertificate = func(certificates [][]byte, _ [][]*x509.Certificate) error {
586			certs := make([]*x509.Certificate, len(certificates))
587			for i, asn1Data := range certificates {
588				cert, err := x509.ParseCertificate(asn1Data)
589				if err != nil {
590					return errors.New("failed to parse certificate from server: " + err.Error())
591				}
592				certs[i] = cert
593			}
594
595			// Leave DNSName empty to skip hostname verification.
596			opts := x509.VerifyOptions{
597				Roots:         tlsConfig.RootCAs,
598				Intermediates: x509.NewCertPool(),
599			}
600			// Skip the first cert because it's the leaf. All others
601			// are intermediates.
602			for _, cert := range certs[1:] {
603				opts.Intermediates.AddCert(cert)
604			}
605			_, err := certs[0].Verify(opts)
606			return err
607		}
608	case "verify-full":
609		tlsConfig.ServerName = host
610	default:
611		return nil, errors.New("sslmode is invalid")
612	}
613
614	if sslrootcert != "" {
615		caCertPool := x509.NewCertPool()
616
617		caPath := sslrootcert
618		caCert, err := ioutil.ReadFile(caPath)
619		if err != nil {
620			return nil, fmt.Errorf("unable to read CA file: %w", err)
621		}
622
623		if !caCertPool.AppendCertsFromPEM(caCert) {
624			return nil, errors.New("unable to add CA to cert pool")
625		}
626
627		tlsConfig.RootCAs = caCertPool
628		tlsConfig.ClientCAs = caCertPool
629	}
630
631	if (sslcert != "" && sslkey == "") || (sslcert == "" && sslkey != "") {
632		return nil, errors.New(`both "sslcert" and "sslkey" are required`)
633	}
634
635	if sslcert != "" && sslkey != "" {
636		cert, err := tls.LoadX509KeyPair(sslcert, sslkey)
637		if err != nil {
638			return nil, fmt.Errorf("unable to read cert: %w", err)
639		}
640
641		tlsConfig.Certificates = []tls.Certificate{cert}
642	}
643
644	switch sslmode {
645	case "allow":
646		return []*tls.Config{nil, tlsConfig}, nil
647	case "prefer":
648		return []*tls.Config{tlsConfig, nil}, nil
649	case "require", "verify-ca", "verify-full":
650		return []*tls.Config{tlsConfig}, nil
651	default:
652		panic("BUG: bad sslmode should already have been caught")
653	}
654}
655
656func parsePort(s string) (uint16, error) {
657	port, err := strconv.ParseUint(s, 10, 16)
658	if err != nil {
659		return 0, err
660	}
661	if port < 1 || port > math.MaxUint16 {
662		return 0, errors.New("outside range")
663	}
664	return uint16(port), nil
665}
666
667func makeDefaultDialer() *net.Dialer {
668	return &net.Dialer{KeepAlive: 5 * time.Minute}
669}
670
671func makeDefaultResolver() *net.Resolver {
672	return net.DefaultResolver
673}
674
675func makeDefaultBuildFrontendFunc(minBufferLen int) BuildFrontendFunc {
676	return func(r io.Reader, w io.Writer) Frontend {
677		cr, err := chunkreader.NewConfig(r, chunkreader.Config{MinBufLen: minBufferLen})
678		if err != nil {
679			panic(fmt.Sprintf("BUG: chunkreader.NewConfig failed: %v", err))
680		}
681		frontend := pgproto3.NewFrontend(cr, w)
682
683		return frontend
684	}
685}
686
687func parseConnectTimeoutSetting(s string) (time.Duration, error) {
688	timeout, err := strconv.ParseInt(s, 10, 64)
689	if err != nil {
690		return 0, err
691	}
692	if timeout < 0 {
693		return 0, errors.New("negative timeout")
694	}
695	return time.Duration(timeout) * time.Second, nil
696}
697
698func makeConnectTimeoutDialFunc(timeout time.Duration) DialFunc {
699	d := makeDefaultDialer()
700	d.Timeout = timeout
701	return d.DialContext
702}
703
704// ValidateConnectTargetSessionAttrsReadWrite is an ValidateConnectFunc that implements libpq compatible
705// target_session_attrs=read-write.
706func ValidateConnectTargetSessionAttrsReadWrite(ctx context.Context, pgConn *PgConn) error {
707	result := pgConn.ExecParams(ctx, "show transaction_read_only", nil, nil, nil, nil).Read()
708	if result.Err != nil {
709		return result.Err
710	}
711
712	if string(result.Rows[0][0]) == "on" {
713		return errors.New("read only connection")
714	}
715
716	return nil
717}
718