Merge remote-tracking branch 'pgconn/master' into v5-dev
This commit is contained in:
@@ -102,10 +102,14 @@ func (c *LRU) StatementErrored(sql string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isInvalidCachedPlanError := pgErr.Severity == "ERROR" &&
|
// https://github.com/jackc/pgx/issues/1162
|
||||||
pgErr.Code == "0A000" &&
|
//
|
||||||
pgErr.Message == "cached plan must not change result type"
|
// We used to look for the message "cached plan must not change result type". However, that message can be localized.
|
||||||
if isInvalidCachedPlanError {
|
// Unfortunately, error code "0A000" - "FEATURE NOT SUPPORTED" is used for many different errors and the only way to
|
||||||
|
// tell the difference is by the message. But all that happens is we clear a statement that we otherwise wouldn't
|
||||||
|
// have so it should be safe.
|
||||||
|
possibleInvalidCachedPlanError := pgErr.Code == "0A000"
|
||||||
|
if possibleInvalidCachedPlanError {
|
||||||
c.stmtsToClear = append(c.stmtsToClear, sql)
|
c.stmtsToClear = append(c.stmtsToClear, sql)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -175,8 +175,6 @@ func NetworkAddress(host string, port uint16) (network, address string) {
|
|||||||
//
|
//
|
||||||
// Other known differences with libpq:
|
// Other known differences with libpq:
|
||||||
//
|
//
|
||||||
// If a host name resolves into multiple addresses, libpq will try all addresses. pgconn will only try the first.
|
|
||||||
//
|
|
||||||
// When multiple hosts are specified, libpq allows them to have different passwords set via the .pgpass file. pgconn
|
// When multiple hosts are specified, libpq allows them to have different passwords set via the .pgpass file. pgconn
|
||||||
// does not.
|
// does not.
|
||||||
//
|
//
|
||||||
@@ -253,6 +251,8 @@ func ParseConfig(connString string) (*Config, error) {
|
|||||||
"sslkey": {},
|
"sslkey": {},
|
||||||
"sslcert": {},
|
"sslcert": {},
|
||||||
"sslrootcert": {},
|
"sslrootcert": {},
|
||||||
|
"krbspn": {},
|
||||||
|
"krbsrvname": {},
|
||||||
"target_session_attrs": {},
|
"target_session_attrs": {},
|
||||||
"service": {},
|
"service": {},
|
||||||
"servicefile": {},
|
"servicefile": {},
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package pgconn
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgproto3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGSSFunc creates a GSS authentication provider, for use with
|
||||||
|
// RegisterGSSProvider.
|
||||||
|
type NewGSSFunc func() (GSS, error)
|
||||||
|
|
||||||
|
var newGSS NewGSSFunc
|
||||||
|
|
||||||
|
// RegisterGSSProvider registers a GSS authentication provider. For example, if
|
||||||
|
// you need to use Kerberos to authenticate with your server, add this to your
|
||||||
|
// main package:
|
||||||
|
//
|
||||||
|
// import "github.com/otan/gopgkrb5"
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// pgconn.RegisterGSSProvider(func() (pgconn.GSS, error) { return gopgkrb5.NewGSS() })
|
||||||
|
// }
|
||||||
|
func RegisterGSSProvider(newGSSArg NewGSSFunc) {
|
||||||
|
newGSS = newGSSArg
|
||||||
|
}
|
||||||
|
|
||||||
|
// GSS provides GSSAPI authentication (e.g., Kerberos).
|
||||||
|
type GSS interface {
|
||||||
|
GetInitToken(host string, service string) ([]byte, error)
|
||||||
|
GetInitTokenFromSPN(spn string) ([]byte, error)
|
||||||
|
Continue(inToken []byte) (done bool, outToken []byte, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PgConn) gssAuth() error {
|
||||||
|
if newGSS == nil {
|
||||||
|
return errors.New("kerberos error: no GSSAPI provider registered, see https://github.com/otan/gopgkrb5")
|
||||||
|
}
|
||||||
|
cli, err := newGSS()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextData []byte
|
||||||
|
if spn, ok := c.config.RuntimeParams["krbspn"]; ok {
|
||||||
|
// Use the supplied SPN if provided.
|
||||||
|
nextData, err = cli.GetInitTokenFromSPN(spn)
|
||||||
|
} else {
|
||||||
|
// Allow the kerberos service name to be overridden
|
||||||
|
service := "postgres"
|
||||||
|
if val, ok := c.config.RuntimeParams["krbsrvname"]; ok {
|
||||||
|
service = val
|
||||||
|
}
|
||||||
|
nextData, err = cli.GetInitToken(c.config.Host, service)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
gssResponse := &pgproto3.GSSResponse{
|
||||||
|
Data: nextData,
|
||||||
|
}
|
||||||
|
_, err = c.conn.Write(gssResponse.Encode(nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := c.rxGSSContinue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var done bool
|
||||||
|
done, nextData, err = cli.Continue(resp.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if done {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PgConn) rxGSSContinue() (*pgproto3.AuthenticationGSSContinue, error) {
|
||||||
|
msg, err := c.receiveMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gssContinue, ok := msg.(*pgproto3.AuthenticationGSSContinue)
|
||||||
|
if ok {
|
||||||
|
return gssContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("expected AuthenticationGSSContinue message but received unexpected message")
|
||||||
|
}
|
||||||
+15
-5
@@ -99,7 +99,7 @@ type PgConn struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format)
|
// Connect establishes a connection to a PostgreSQL server using the environment and connString (in URL or DSN format)
|
||||||
// to provide configuration. See documention for ParseConfig for details. ctx can be used to cancel a connect attempt.
|
// to provide configuration. See documentation for ParseConfig for details. ctx can be used to cancel a connect attempt.
|
||||||
func Connect(ctx context.Context, connString string) (*PgConn, error) {
|
func Connect(ctx context.Context, connString string) (*PgConn, error) {
|
||||||
config, err := ParseConfig(connString)
|
config, err := ParseConfig(connString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -154,9 +154,14 @@ func ConnectConfig(ctx context.Context, config *Config) (pgConn *PgConn, err err
|
|||||||
break
|
break
|
||||||
} else if pgerr, ok := err.(*PgError); ok {
|
} else if pgerr, ok := err.(*PgError); ok {
|
||||||
err = &connectError{config: config, msg: "server error", err: pgerr}
|
err = &connectError{config: config, msg: "server error", err: pgerr}
|
||||||
ERRCODE_INVALID_PASSWORD := "28P01" // worng password
|
const ERRCODE_INVALID_PASSWORD = "28P01" // wrong password
|
||||||
ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION := "28000" // db does not exist
|
const ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION = "28000" // wrong password or bad pg_hba.conf settings
|
||||||
if pgerr.Code == ERRCODE_INVALID_PASSWORD || pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION {
|
const ERRCODE_INVALID_CATALOG_NAME = "3D000" // db does not exist
|
||||||
|
const ERRCODE_INSUFFICIENT_PRIVILEGE = "42501" // missing connect privilege
|
||||||
|
if pgerr.Code == ERRCODE_INVALID_PASSWORD ||
|
||||||
|
pgerr.Code == ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION ||
|
||||||
|
pgerr.Code == ERRCODE_INVALID_CATALOG_NAME ||
|
||||||
|
pgerr.Code == ERRCODE_INSUFFICIENT_PRIVILEGE {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,7 +322,12 @@ func connect(ctx context.Context, config *Config, fallbackConfig *FallbackConfig
|
|||||||
pgConn.conn.Close()
|
pgConn.conn.Close()
|
||||||
return nil, &connectError{config: config, msg: "failed SASL auth", err: err}
|
return nil, &connectError{config: config, msg: "failed SASL auth", err: err}
|
||||||
}
|
}
|
||||||
|
case *pgproto3.AuthenticationGSS:
|
||||||
|
err = pgConn.gssAuth()
|
||||||
|
if err != nil {
|
||||||
|
pgConn.conn.Close()
|
||||||
|
return nil, &connectError{config: config, msg: "failed GSS auth", err: err}
|
||||||
|
}
|
||||||
case *pgproto3.ReadyForQuery:
|
case *pgproto3.ReadyForQuery:
|
||||||
pgConn.status = connStatusIdle
|
pgConn.status = connStatusIdle
|
||||||
if config.ValidateConnect != nil {
|
if config.ValidateConnect != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user