mirror of https://github.com/go-gitea/gitea.git
upgrade version of lib/pq to v1.1.0 (#6640)
Adds SCRAM-SHA-256 authentication
This commit is contained in:
parent
83d6e5e3f8
commit
3fb038c53a
2
go.mod
2
go.mod
|
@ -74,7 +74,7 @@ require (
|
||||||
github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc // indirect
|
github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc // indirect
|
||||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect
|
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6 // indirect
|
||||||
github.com/lafriks/xormstore v1.0.0
|
github.com/lafriks/xormstore v1.0.0
|
||||||
github.com/lib/pq v1.0.0
|
github.com/lib/pq v1.1.0
|
||||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e
|
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e
|
||||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de // indirect
|
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -198,6 +198,8 @@ github.com/lafriks/xormstore v1.0.0 h1:P/IJzNSIpjXl/Up3o2Td5ZU/x4v6DEKLMaPQJGtmJ
|
||||||
github.com/lafriks/xormstore v1.0.0/go.mod h1:dD8vHNRfEp3Uy+JvX9cMi2SXcRKJ0x4pYKsZuy843Ic=
|
github.com/lafriks/xormstore v1.0.0/go.mod h1:dD8vHNRfEp3Uy+JvX9cMi2SXcRKJ0x4pYKsZuy843Ic=
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.0 h1:/5u4a+KGJptBRqGzPvYQL9p0d/tPR4S31+Tnzj9lEO4=
|
||||||
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
|
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96 h1:uNwtsDp7ci48vBTTxDuwcoTXz4lwtDTe7TjCQ0noaWY=
|
||||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
|
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
|
||||||
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e h1:GSprKUrG9wNgwQgROvjPGXmcZrg4OLslOuZGB0uJjx8=
|
github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e h1:GSprKUrG9wNgwQgROvjPGXmcZrg4OLslOuZGB0uJjx8=
|
||||||
|
|
|
@ -80,7 +80,7 @@ megacheck_install() {
|
||||||
}
|
}
|
||||||
|
|
||||||
golint_install() {
|
golint_install() {
|
||||||
go get github.com/golang/lint/golint
|
go get golang.org/x/lint/golint
|
||||||
}
|
}
|
||||||
|
|
||||||
$1
|
$1
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
- 1.10.x
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
- master
|
- master
|
||||||
|
|
||||||
sudo: true
|
sudo: true
|
||||||
|
@ -44,7 +44,7 @@ script:
|
||||||
- >
|
- >
|
||||||
goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
|
goimports -d -e $(find -name '*.go') | awk '{ print } END { exit NR == 0 ? 0 : 1 }'
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
- megacheck -go 1.8 ./...
|
- megacheck -go 1.9 ./...
|
||||||
- golint ./...
|
- golint ./...
|
||||||
- PQTEST_BINARY_PARAMETERS=no go test -race -v ./...
|
- PQTEST_BINARY_PARAMETERS=no go test -race -v ./...
|
||||||
- PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...
|
- PQTEST_BINARY_PARAMETERS=yes go test -race -v ./...
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
For detailed documentation and basic usage examples, please see the package
|
For detailed documentation and basic usage examples, please see the package
|
||||||
documentation at <http://godoc.org/github.com/lib/pq>.
|
documentation at <https://godoc.org/github.com/lib/pq>.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha256"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
@ -20,6 +22,7 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/lib/pq/oid"
|
"github.com/lib/pq/oid"
|
||||||
|
"github.com/lib/pq/scram"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Common error types
|
// Common error types
|
||||||
|
@ -89,13 +92,24 @@ type Dialer interface {
|
||||||
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
|
DialTimeout(network, address string, timeout time.Duration) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type defaultDialer struct{}
|
type DialerContext interface {
|
||||||
|
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
func (d defaultDialer) Dial(ntw, addr string) (net.Conn, error) {
|
|
||||||
return net.Dial(ntw, addr)
|
|
||||||
}
|
}
|
||||||
func (d defaultDialer) DialTimeout(ntw, addr string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
return net.DialTimeout(ntw, addr, timeout)
|
type defaultDialer struct {
|
||||||
|
d net.Dialer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d defaultDialer) Dial(network, address string) (net.Conn, error) {
|
||||||
|
return d.d.Dial(network, address)
|
||||||
|
}
|
||||||
|
func (d defaultDialer) DialTimeout(network, address string, timeout time.Duration) (net.Conn, error) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
return d.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
func (d defaultDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
return d.d.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
type conn struct {
|
type conn struct {
|
||||||
|
@ -244,90 +258,35 @@ func (cn *conn) writeBuf(b byte) *writeBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open opens a new connection to the database. name is a connection string.
|
// Open opens a new connection to the database. dsn is a connection string.
|
||||||
// Most users should only use it through database/sql package from the standard
|
// Most users should only use it through database/sql package from the standard
|
||||||
// library.
|
// library.
|
||||||
func Open(name string) (_ driver.Conn, err error) {
|
func Open(dsn string) (_ driver.Conn, err error) {
|
||||||
return DialOpen(defaultDialer{}, name)
|
return DialOpen(defaultDialer{}, dsn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialOpen opens a new connection to the database using a dialer.
|
// DialOpen opens a new connection to the database using a dialer.
|
||||||
func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
func DialOpen(d Dialer, dsn string) (_ driver.Conn, err error) {
|
||||||
|
c, err := NewConnector(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.dialer = d
|
||||||
|
return c.open(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connector) open(ctx context.Context) (cn *conn, err error) {
|
||||||
// Handle any panics during connection initialization. Note that we
|
// Handle any panics during connection initialization. Note that we
|
||||||
// specifically do *not* want to use errRecover(), as that would turn any
|
// specifically do *not* want to use errRecover(), as that would turn any
|
||||||
// connection errors into ErrBadConns, hiding the real error message from
|
// connection errors into ErrBadConns, hiding the real error message from
|
||||||
// the user.
|
// the user.
|
||||||
defer errRecoverNoErrBadConn(&err)
|
defer errRecoverNoErrBadConn(&err)
|
||||||
|
|
||||||
o := make(values)
|
o := c.opts
|
||||||
|
|
||||||
// A number of defaults are applied here, in this order:
|
cn = &conn{
|
||||||
//
|
|
||||||
// * Very low precedence defaults applied in every situation
|
|
||||||
// * Environment variables
|
|
||||||
// * Explicitly passed connection information
|
|
||||||
o["host"] = "localhost"
|
|
||||||
o["port"] = "5432"
|
|
||||||
// N.B.: Extra float digits should be set to 3, but that breaks
|
|
||||||
// Postgres 8.4 and older, where the max is 2.
|
|
||||||
o["extra_float_digits"] = "2"
|
|
||||||
for k, v := range parseEnviron(os.Environ()) {
|
|
||||||
o[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(name, "postgres://") || strings.HasPrefix(name, "postgresql://") {
|
|
||||||
name, err = ParseURL(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parseOpts(name, o); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the "fallback" application name if necessary
|
|
||||||
if fallback, ok := o["fallback_application_name"]; ok {
|
|
||||||
if _, ok := o["application_name"]; !ok {
|
|
||||||
o["application_name"] = fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't work with any client_encoding other than UTF-8 currently.
|
|
||||||
// However, we have historically allowed the user to set it to UTF-8
|
|
||||||
// explicitly, and there's no reason to break such programs, so allow that.
|
|
||||||
// Note that the "options" setting could also set client_encoding, but
|
|
||||||
// parsing its value is not worth it. Instead, we always explicitly send
|
|
||||||
// client_encoding as a separate run-time parameter, which should override
|
|
||||||
// anything set in options.
|
|
||||||
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
|
||||||
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
|
||||||
}
|
|
||||||
o["client_encoding"] = "UTF8"
|
|
||||||
// DateStyle needs a similar treatment.
|
|
||||||
if datestyle, ok := o["datestyle"]; ok {
|
|
||||||
if datestyle != "ISO, MDY" {
|
|
||||||
panic(fmt.Sprintf("setting datestyle must be absent or %v; got %v",
|
|
||||||
"ISO, MDY", datestyle))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o["datestyle"] = "ISO, MDY"
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a user is not provided by any other means, the last
|
|
||||||
// resort is to use the current operating system provided user
|
|
||||||
// name.
|
|
||||||
if _, ok := o["user"]; !ok {
|
|
||||||
u, err := userCurrent()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
o["user"] = u
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := &conn{
|
|
||||||
opts: o,
|
opts: o,
|
||||||
dialer: d,
|
dialer: c.dialer,
|
||||||
}
|
}
|
||||||
err = cn.handleDriverSettings(o)
|
err = cn.handleDriverSettings(o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -335,7 +294,7 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||||
}
|
}
|
||||||
cn.handlePgpass(o)
|
cn.handlePgpass(o)
|
||||||
|
|
||||||
cn.c, err = dial(d, o)
|
cn.c, err = dial(ctx, c.dialer, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -364,10 +323,10 @@ func DialOpen(d Dialer, name string) (_ driver.Conn, err error) {
|
||||||
return cn, err
|
return cn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func dial(d Dialer, o values) (net.Conn, error) {
|
func dial(ctx context.Context, d Dialer, o values) (net.Conn, error) {
|
||||||
ntw, addr := network(o)
|
network, address := network(o)
|
||||||
// SSL is not necessary or supported over UNIX domain sockets
|
// SSL is not necessary or supported over UNIX domain sockets
|
||||||
if ntw == "unix" {
|
if network == "unix" {
|
||||||
o["sslmode"] = "disable"
|
o["sslmode"] = "disable"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,19 +337,30 @@ func dial(d Dialer, o values) (net.Conn, error) {
|
||||||
return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
|
return nil, fmt.Errorf("invalid value for parameter connect_timeout: %s", err)
|
||||||
}
|
}
|
||||||
duration := time.Duration(seconds) * time.Second
|
duration := time.Duration(seconds) * time.Second
|
||||||
|
|
||||||
// connect_timeout should apply to the entire connection establishment
|
// connect_timeout should apply to the entire connection establishment
|
||||||
// procedure, so we both use a timeout for the TCP connection
|
// procedure, so we both use a timeout for the TCP connection
|
||||||
// establishment and set a deadline for doing the initial handshake.
|
// establishment and set a deadline for doing the initial handshake.
|
||||||
// The deadline is then reset after startup() is done.
|
// The deadline is then reset after startup() is done.
|
||||||
deadline := time.Now().Add(duration)
|
deadline := time.Now().Add(duration)
|
||||||
conn, err := d.DialTimeout(ntw, addr, duration)
|
var conn net.Conn
|
||||||
|
if dctx, ok := d.(DialerContext); ok {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, duration)
|
||||||
|
defer cancel()
|
||||||
|
conn, err = dctx.DialContext(ctx, network, address)
|
||||||
|
} else {
|
||||||
|
conn, err = d.DialTimeout(network, address, duration)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = conn.SetDeadline(deadline)
|
err = conn.SetDeadline(deadline)
|
||||||
return conn, err
|
return conn, err
|
||||||
}
|
}
|
||||||
return d.Dial(ntw, addr)
|
if dctx, ok := d.(DialerContext); ok {
|
||||||
|
return dctx.DialContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
return d.Dial(network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func network(o values) (string, string) {
|
func network(o values) (string, string) {
|
||||||
|
@ -704,7 +674,7 @@ func (cn *conn) simpleQuery(q string) (res *rows, err error) {
|
||||||
// res might be non-nil here if we received a previous
|
// res might be non-nil here if we received a previous
|
||||||
// CommandComplete, but that's fine; just overwrite it
|
// CommandComplete, but that's fine; just overwrite it
|
||||||
res = &rows{cn: cn}
|
res = &rows{cn: cn}
|
||||||
res.colNames, res.colFmts, res.colTyps = parsePortalRowDescribe(r)
|
res.rowsHeader = parsePortalRowDescribe(r)
|
||||||
|
|
||||||
// To work around a bug in QueryRow in Go 1.2 and earlier, wait
|
// To work around a bug in QueryRow in Go 1.2 and earlier, wait
|
||||||
// until the first DataRow has been received.
|
// until the first DataRow has been received.
|
||||||
|
@ -861,17 +831,15 @@ func (cn *conn) query(query string, args []driver.Value) (_ *rows, err error) {
|
||||||
cn.readParseResponse()
|
cn.readParseResponse()
|
||||||
cn.readBindResponse()
|
cn.readBindResponse()
|
||||||
rows := &rows{cn: cn}
|
rows := &rows{cn: cn}
|
||||||
rows.colNames, rows.colFmts, rows.colTyps = cn.readPortalDescribeResponse()
|
rows.rowsHeader = cn.readPortalDescribeResponse()
|
||||||
cn.postExecuteWorkaround()
|
cn.postExecuteWorkaround()
|
||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
st := cn.prepareTo(query, "")
|
st := cn.prepareTo(query, "")
|
||||||
st.exec(args)
|
st.exec(args)
|
||||||
return &rows{
|
return &rows{
|
||||||
cn: cn,
|
cn: cn,
|
||||||
colNames: st.colNames,
|
rowsHeader: st.rowsHeader,
|
||||||
colTyps: st.colTyps,
|
|
||||||
colFmts: st.colFmts,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -992,7 +960,6 @@ func (cn *conn) recv() (t byte, r *readBuf) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
case 'E':
|
case 'E':
|
||||||
panic(parseError(r))
|
panic(parseError(r))
|
||||||
|
@ -1163,6 +1130,55 @@ func (cn *conn) auth(r *readBuf, o values) {
|
||||||
if r.int32() != 0 {
|
if r.int32() != 0 {
|
||||||
errorf("unexpected authentication response: %q", t)
|
errorf("unexpected authentication response: %q", t)
|
||||||
}
|
}
|
||||||
|
case 10:
|
||||||
|
sc := scram.NewClient(sha256.New, o["user"], o["password"])
|
||||||
|
sc.Step(nil)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
scOut := sc.Out()
|
||||||
|
|
||||||
|
w := cn.writeBuf('p')
|
||||||
|
w.string("SCRAM-SHA-256")
|
||||||
|
w.int32(len(scOut))
|
||||||
|
w.bytes(scOut)
|
||||||
|
cn.send(w)
|
||||||
|
|
||||||
|
t, r := cn.recv()
|
||||||
|
if t != 'R' {
|
||||||
|
errorf("unexpected password response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.int32() != 11 {
|
||||||
|
errorf("unexpected authentication response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStep := r.next(len(*r))
|
||||||
|
sc.Step(nextStep)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
scOut = sc.Out()
|
||||||
|
w = cn.writeBuf('p')
|
||||||
|
w.bytes(scOut)
|
||||||
|
cn.send(w)
|
||||||
|
|
||||||
|
t, r = cn.recv()
|
||||||
|
if t != 'R' {
|
||||||
|
errorf("unexpected password response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.int32() != 12 {
|
||||||
|
errorf("unexpected authentication response: %q", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextStep = r.next(len(*r))
|
||||||
|
sc.Step(nextStep)
|
||||||
|
if sc.Err() != nil {
|
||||||
|
errorf("SCRAM-SHA-256 error: %s", sc.Err().Error())
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
errorf("unknown authentication response: %d", code)
|
errorf("unknown authentication response: %d", code)
|
||||||
}
|
}
|
||||||
|
@ -1180,12 +1196,10 @@ var colFmtDataAllBinary = []byte{0, 1, 0, 1}
|
||||||
var colFmtDataAllText = []byte{0, 0}
|
var colFmtDataAllText = []byte{0, 0}
|
||||||
|
|
||||||
type stmt struct {
|
type stmt struct {
|
||||||
cn *conn
|
cn *conn
|
||||||
name string
|
name string
|
||||||
colNames []string
|
rowsHeader
|
||||||
colFmts []format
|
|
||||||
colFmtData []byte
|
colFmtData []byte
|
||||||
colTyps []fieldDesc
|
|
||||||
paramTyps []oid.Oid
|
paramTyps []oid.Oid
|
||||||
closed bool
|
closed bool
|
||||||
}
|
}
|
||||||
|
@ -1231,10 +1245,8 @@ func (st *stmt) Query(v []driver.Value) (r driver.Rows, err error) {
|
||||||
|
|
||||||
st.exec(v)
|
st.exec(v)
|
||||||
return &rows{
|
return &rows{
|
||||||
cn: st.cn,
|
cn: st.cn,
|
||||||
colNames: st.colNames,
|
rowsHeader: st.rowsHeader,
|
||||||
colTyps: st.colTyps,
|
|
||||||
colFmts: st.colFmts,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,16 +1356,22 @@ func (cn *conn) parseComplete(commandTag string) (driver.Result, string) {
|
||||||
return driver.RowsAffected(n), commandTag
|
return driver.RowsAffected(n), commandTag
|
||||||
}
|
}
|
||||||
|
|
||||||
type rows struct {
|
type rowsHeader struct {
|
||||||
cn *conn
|
|
||||||
finish func()
|
|
||||||
colNames []string
|
colNames []string
|
||||||
colTyps []fieldDesc
|
colTyps []fieldDesc
|
||||||
colFmts []format
|
colFmts []format
|
||||||
done bool
|
}
|
||||||
rb readBuf
|
|
||||||
result driver.Result
|
type rows struct {
|
||||||
tag string
|
cn *conn
|
||||||
|
finish func()
|
||||||
|
rowsHeader
|
||||||
|
done bool
|
||||||
|
rb readBuf
|
||||||
|
result driver.Result
|
||||||
|
tag string
|
||||||
|
|
||||||
|
next *rowsHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) Close() error {
|
func (rs *rows) Close() error {
|
||||||
|
@ -1440,7 +1458,8 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case 'T':
|
case 'T':
|
||||||
rs.colNames, rs.colFmts, rs.colTyps = parsePortalRowDescribe(&rs.rb)
|
next := parsePortalRowDescribe(&rs.rb)
|
||||||
|
rs.next = &next
|
||||||
return io.EOF
|
return io.EOF
|
||||||
default:
|
default:
|
||||||
errorf("unexpected message after execute: %q", t)
|
errorf("unexpected message after execute: %q", t)
|
||||||
|
@ -1449,10 +1468,16 @@ func (rs *rows) Next(dest []driver.Value) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) HasNextResultSet() bool {
|
func (rs *rows) HasNextResultSet() bool {
|
||||||
return !rs.done
|
hasNext := rs.next != nil && !rs.done
|
||||||
|
return hasNext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *rows) NextResultSet() error {
|
func (rs *rows) NextResultSet() error {
|
||||||
|
if rs.next == nil {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
rs.rowsHeader = *rs.next
|
||||||
|
rs.next = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1630,13 +1655,13 @@ func (cn *conn) readStatementDescribeResponse() (paramTyps []oid.Oid, colNames [
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *conn) readPortalDescribeResponse() (colNames []string, colFmts []format, colTyps []fieldDesc) {
|
func (cn *conn) readPortalDescribeResponse() rowsHeader {
|
||||||
t, r := cn.recv1()
|
t, r := cn.recv1()
|
||||||
switch t {
|
switch t {
|
||||||
case 'T':
|
case 'T':
|
||||||
return parsePortalRowDescribe(r)
|
return parsePortalRowDescribe(r)
|
||||||
case 'n':
|
case 'n':
|
||||||
return nil, nil, nil
|
return rowsHeader{}
|
||||||
case 'E':
|
case 'E':
|
||||||
err := parseError(r)
|
err := parseError(r)
|
||||||
cn.readReadyForQuery()
|
cn.readReadyForQuery()
|
||||||
|
@ -1742,11 +1767,11 @@ func parseStatementRowDescribe(r *readBuf) (colNames []string, colTyps []fieldDe
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, colTyps []fieldDesc) {
|
func parsePortalRowDescribe(r *readBuf) rowsHeader {
|
||||||
n := r.int16()
|
n := r.int16()
|
||||||
colNames = make([]string, n)
|
colNames := make([]string, n)
|
||||||
colFmts = make([]format, n)
|
colFmts := make([]format, n)
|
||||||
colTyps = make([]fieldDesc, n)
|
colTyps := make([]fieldDesc, n)
|
||||||
for i := range colNames {
|
for i := range colNames {
|
||||||
colNames[i] = r.string()
|
colNames[i] = r.string()
|
||||||
r.next(6)
|
r.next(6)
|
||||||
|
@ -1755,7 +1780,11 @@ func parsePortalRowDescribe(r *readBuf) (colNames []string, colFmts []format, co
|
||||||
colTyps[i].Mod = r.int32()
|
colTyps[i].Mod = r.int32()
|
||||||
colFmts[i] = format(r.int16())
|
colFmts[i] = format(r.int16())
|
||||||
}
|
}
|
||||||
return
|
return rowsHeader{
|
||||||
|
colNames: colNames,
|
||||||
|
colFmts: colFmts,
|
||||||
|
colTyps: colTyps,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseEnviron tries to mimic some of libpq's environment handling
|
// parseEnviron tries to mimic some of libpq's environment handling
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package pq
|
package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -9,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Implement the "QueryerContext" interface
|
// Implement the "QueryerContext" interface
|
||||||
|
@ -76,13 +75,32 @@ func (cn *conn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx,
|
||||||
return tx, nil
|
return tx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cn *conn) Ping(ctx context.Context) error {
|
||||||
|
if finish := cn.watchCancel(ctx); finish != nil {
|
||||||
|
defer finish()
|
||||||
|
}
|
||||||
|
rows, err := cn.simpleQuery("SELECT 'lib/pq ping test';")
|
||||||
|
if err != nil {
|
||||||
|
return driver.ErrBadConn // https://golang.org/pkg/database/sql/driver/#Pinger
|
||||||
|
}
|
||||||
|
rows.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (cn *conn) watchCancel(ctx context.Context) func() {
|
func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
if done := ctx.Done(); done != nil {
|
if done := ctx.Done(); done != nil {
|
||||||
finished := make(chan struct{})
|
finished := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-done:
|
case <-done:
|
||||||
_ = cn.cancel()
|
// At this point the function level context is canceled,
|
||||||
|
// so it must not be used for the additional network
|
||||||
|
// request to cancel the query.
|
||||||
|
// Create a new context to pass into the dial.
|
||||||
|
ctxCancel, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_ = cn.cancel(ctxCancel)
|
||||||
finished <- struct{}{}
|
finished <- struct{}{}
|
||||||
case <-finished:
|
case <-finished:
|
||||||
}
|
}
|
||||||
|
@ -97,8 +115,8 @@ func (cn *conn) watchCancel(ctx context.Context) func() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cn *conn) cancel() error {
|
func (cn *conn) cancel(ctx context.Context) error {
|
||||||
c, err := dial(cn.dialer, cn.opts)
|
c, err := dial(ctx, cn.dialer, cn.opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// +build go1.10
|
|
||||||
|
|
||||||
package pq
|
package pq
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Connector represents a fixed configuration for the pq driver with a given
|
// Connector represents a fixed configuration for the pq driver with a given
|
||||||
|
@ -14,30 +16,95 @@ import (
|
||||||
//
|
//
|
||||||
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
// See https://golang.org/pkg/database/sql/#OpenDB.
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
type connector struct {
|
type Connector struct {
|
||||||
name string
|
opts values
|
||||||
|
dialer Dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect returns a connection to the database using the fixed configuration
|
// Connect returns a connection to the database using the fixed configuration
|
||||||
// of this Connector. Context is not used.
|
// of this Connector. Context is not used.
|
||||||
func (c *connector) Connect(_ context.Context) (driver.Conn, error) {
|
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
|
||||||
return (&Driver{}).Open(c.name)
|
return c.open(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Driver returnst the underlying driver of this Connector.
|
// Driver returnst the underlying driver of this Connector.
|
||||||
func (c *connector) Driver() driver.Driver {
|
func (c *Connector) Driver() driver.Driver {
|
||||||
return &Driver{}
|
return &Driver{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ driver.Connector = &connector{}
|
|
||||||
|
|
||||||
// NewConnector returns a connector for the pq driver in a fixed configuration
|
// NewConnector returns a connector for the pq driver in a fixed configuration
|
||||||
// with the given name. The returned connector can be used to create any number
|
// with the given dsn. The returned connector can be used to create any number
|
||||||
// of equivalent Conn's. The returned connector is intended to be used with
|
// of equivalent Conn's. The returned connector is intended to be used with
|
||||||
// database/sql.OpenDB.
|
// database/sql.OpenDB.
|
||||||
//
|
//
|
||||||
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
// See https://golang.org/pkg/database/sql/driver/#Connector.
|
||||||
// See https://golang.org/pkg/database/sql/#OpenDB.
|
// See https://golang.org/pkg/database/sql/#OpenDB.
|
||||||
func NewConnector(name string) (driver.Connector, error) {
|
func NewConnector(dsn string) (*Connector, error) {
|
||||||
return &connector{name: name}, nil
|
var err error
|
||||||
|
o := make(values)
|
||||||
|
|
||||||
|
// A number of defaults are applied here, in this order:
|
||||||
|
//
|
||||||
|
// * Very low precedence defaults applied in every situation
|
||||||
|
// * Environment variables
|
||||||
|
// * Explicitly passed connection information
|
||||||
|
o["host"] = "localhost"
|
||||||
|
o["port"] = "5432"
|
||||||
|
// N.B.: Extra float digits should be set to 3, but that breaks
|
||||||
|
// Postgres 8.4 and older, where the max is 2.
|
||||||
|
o["extra_float_digits"] = "2"
|
||||||
|
for k, v := range parseEnviron(os.Environ()) {
|
||||||
|
o[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
|
||||||
|
dsn, err = ParseURL(dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseOpts(dsn, o); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the "fallback" application name if necessary
|
||||||
|
if fallback, ok := o["fallback_application_name"]; ok {
|
||||||
|
if _, ok := o["application_name"]; !ok {
|
||||||
|
o["application_name"] = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't work with any client_encoding other than UTF-8 currently.
|
||||||
|
// However, we have historically allowed the user to set it to UTF-8
|
||||||
|
// explicitly, and there's no reason to break such programs, so allow that.
|
||||||
|
// Note that the "options" setting could also set client_encoding, but
|
||||||
|
// parsing its value is not worth it. Instead, we always explicitly send
|
||||||
|
// client_encoding as a separate run-time parameter, which should override
|
||||||
|
// anything set in options.
|
||||||
|
if enc, ok := o["client_encoding"]; ok && !isUTF8(enc) {
|
||||||
|
return nil, errors.New("client_encoding must be absent or 'UTF8'")
|
||||||
|
}
|
||||||
|
o["client_encoding"] = "UTF8"
|
||||||
|
// DateStyle needs a similar treatment.
|
||||||
|
if datestyle, ok := o["datestyle"]; ok {
|
||||||
|
if datestyle != "ISO, MDY" {
|
||||||
|
return nil, fmt.Errorf("setting datestyle must be absent or %v; got %v", "ISO, MDY", datestyle)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
o["datestyle"] = "ISO, MDY"
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a user is not provided by any other means, the last
|
||||||
|
// resort is to use the current operating system provided user
|
||||||
|
// name.
|
||||||
|
if _, ok := o["user"]; !ok {
|
||||||
|
u, err := userCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o["user"] = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Connector{opts: o, dialer: defaultDialer{}}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -239,7 +239,7 @@ for more information). Note that the channel name will be truncated to 63
|
||||||
bytes by the PostgreSQL server.
|
bytes by the PostgreSQL server.
|
||||||
|
|
||||||
You can find a complete, working example of Listener usage at
|
You can find a complete, working example of Listener usage at
|
||||||
http://godoc.org/github.com/lib/pq/example/listen.
|
https://godoc.org/github.com/lib/pq/example/listen.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
package pq
|
package pq
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
// Copyright (c) 2014 - Gustavo Niemeyer <gustavo@niemeyer.net>
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
// Pacakage scram implements a SCRAM-{SHA-1,etc} client per RFC5802.
|
||||||
|
//
|
||||||
|
// http://tools.ietf.org/html/rfc5802
|
||||||
|
//
|
||||||
|
package scram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client implements a SCRAM-* client (SCRAM-SHA-1, SCRAM-SHA-256, etc).
|
||||||
|
//
|
||||||
|
// A Client may be used within a SASL conversation with logic resembling:
|
||||||
|
//
|
||||||
|
// var in []byte
|
||||||
|
// var client = scram.NewClient(sha1.New, user, pass)
|
||||||
|
// for client.Step(in) {
|
||||||
|
// out := client.Out()
|
||||||
|
// // send out to server
|
||||||
|
// in := serverOut
|
||||||
|
// }
|
||||||
|
// if client.Err() != nil {
|
||||||
|
// // auth failed
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
type Client struct {
|
||||||
|
newHash func() hash.Hash
|
||||||
|
|
||||||
|
user string
|
||||||
|
pass string
|
||||||
|
step int
|
||||||
|
out bytes.Buffer
|
||||||
|
err error
|
||||||
|
|
||||||
|
clientNonce []byte
|
||||||
|
serverNonce []byte
|
||||||
|
saltedPass []byte
|
||||||
|
authMsg bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new SCRAM-* client with the provided hash algorithm.
|
||||||
|
//
|
||||||
|
// For SCRAM-SHA-256, for example, use:
|
||||||
|
//
|
||||||
|
// client := scram.NewClient(sha256.New, user, pass)
|
||||||
|
//
|
||||||
|
func NewClient(newHash func() hash.Hash, user, pass string) *Client {
|
||||||
|
c := &Client{
|
||||||
|
newHash: newHash,
|
||||||
|
user: user,
|
||||||
|
pass: pass,
|
||||||
|
}
|
||||||
|
c.out.Grow(256)
|
||||||
|
c.authMsg.Grow(256)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Out returns the data to be sent to the server in the current step.
|
||||||
|
func (c *Client) Out() []byte {
|
||||||
|
if c.out.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.out.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error that ocurred, or nil if there were no errors.
|
||||||
|
func (c *Client) Err() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNonce sets the client nonce to the provided value.
|
||||||
|
// If not set, the nonce is generated automatically out of crypto/rand on the first step.
|
||||||
|
func (c *Client) SetNonce(nonce []byte) {
|
||||||
|
c.clientNonce = nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
var escaper = strings.NewReplacer("=", "=3D", ",", "=2C")
|
||||||
|
|
||||||
|
// Step processes the incoming data from the server and makes the
|
||||||
|
// next round of data for the server available via Client.Out.
|
||||||
|
// Step returns false if there are no errors and more data is
|
||||||
|
// still expected.
|
||||||
|
func (c *Client) Step(in []byte) bool {
|
||||||
|
c.out.Reset()
|
||||||
|
if c.step > 2 || c.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c.step++
|
||||||
|
switch c.step {
|
||||||
|
case 1:
|
||||||
|
c.err = c.step1(in)
|
||||||
|
case 2:
|
||||||
|
c.err = c.step2(in)
|
||||||
|
case 3:
|
||||||
|
c.err = c.step3(in)
|
||||||
|
}
|
||||||
|
return c.step > 2 || c.err != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step1(in []byte) error {
|
||||||
|
if len(c.clientNonce) == 0 {
|
||||||
|
const nonceLen = 16
|
||||||
|
buf := make([]byte, nonceLen+b64.EncodedLen(nonceLen))
|
||||||
|
if _, err := rand.Read(buf[:nonceLen]); err != nil {
|
||||||
|
return fmt.Errorf("cannot read random SCRAM-SHA-256 nonce from operating system: %v", err)
|
||||||
|
}
|
||||||
|
c.clientNonce = buf[nonceLen:]
|
||||||
|
b64.Encode(c.clientNonce, buf[:nonceLen])
|
||||||
|
}
|
||||||
|
c.authMsg.WriteString("n=")
|
||||||
|
escaper.WriteString(&c.authMsg, c.user)
|
||||||
|
c.authMsg.WriteString(",r=")
|
||||||
|
c.authMsg.Write(c.clientNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("n,,")
|
||||||
|
c.out.Write(c.authMsg.Bytes())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var b64 = base64.StdEncoding
|
||||||
|
|
||||||
|
func (c *Client) step2(in []byte) error {
|
||||||
|
c.authMsg.WriteByte(',')
|
||||||
|
c.authMsg.Write(in)
|
||||||
|
|
||||||
|
fields := bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) != 3 {
|
||||||
|
return fmt.Errorf("expected 3 fields in first SCRAM-SHA-256 server message, got %d: %q", len(fields), in)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[0], []byte("r=")) || len(fields[0]) < 2 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 nonce: %q", fields[0])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[1], []byte("s=")) || len(fields[1]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 salt: %q", fields[1])
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(fields[2], []byte("i=")) || len(fields[2]) < 6 {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
c.serverNonce = fields[0][2:]
|
||||||
|
if !bytes.HasPrefix(c.serverNonce, c.clientNonce) {
|
||||||
|
return fmt.Errorf("server SCRAM-SHA-256 nonce is not prefixed by client nonce: got %q, want %q+\"...\"", c.serverNonce, c.clientNonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, b64.DecodedLen(len(fields[1][2:])))
|
||||||
|
n, err := b64.Decode(salt, fields[1][2:])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot decode SCRAM-SHA-256 salt sent by server: %q", fields[1])
|
||||||
|
}
|
||||||
|
salt = salt[:n]
|
||||||
|
iterCount, err := strconv.Atoi(string(fields[2][2:]))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("server sent an invalid SCRAM-SHA-256 iteration count: %q", fields[2])
|
||||||
|
}
|
||||||
|
c.saltPassword(salt, iterCount)
|
||||||
|
|
||||||
|
c.authMsg.WriteString(",c=biws,r=")
|
||||||
|
c.authMsg.Write(c.serverNonce)
|
||||||
|
|
||||||
|
c.out.WriteString("c=biws,r=")
|
||||||
|
c.out.Write(c.serverNonce)
|
||||||
|
c.out.WriteString(",p=")
|
||||||
|
c.out.Write(c.clientProof())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) step3(in []byte) error {
|
||||||
|
var isv, ise bool
|
||||||
|
var fields = bytes.Split(in, []byte(","))
|
||||||
|
if len(fields) == 1 {
|
||||||
|
isv = bytes.HasPrefix(fields[0], []byte("v="))
|
||||||
|
ise = bytes.HasPrefix(fields[0], []byte("e="))
|
||||||
|
}
|
||||||
|
if ise {
|
||||||
|
return fmt.Errorf("SCRAM-SHA-256 authentication error: %s", fields[0][2:])
|
||||||
|
} else if !isv {
|
||||||
|
return fmt.Errorf("unsupported SCRAM-SHA-256 final message from server: %q", in)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c.serverSignature(), fields[0][2:]) {
|
||||||
|
return fmt.Errorf("cannot authenticate SCRAM-SHA-256 server signature: %q", fields[0][2:])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) saltPassword(salt []byte, iterCount int) {
|
||||||
|
mac := hmac.New(c.newHash, []byte(c.pass))
|
||||||
|
mac.Write(salt)
|
||||||
|
mac.Write([]byte{0, 0, 0, 1})
|
||||||
|
ui := mac.Sum(nil)
|
||||||
|
hi := make([]byte, len(ui))
|
||||||
|
copy(hi, ui)
|
||||||
|
for i := 1; i < iterCount; i++ {
|
||||||
|
mac.Reset()
|
||||||
|
mac.Write(ui)
|
||||||
|
mac.Sum(ui[:0])
|
||||||
|
for j, b := range ui {
|
||||||
|
hi[j] ^= b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.saltedPass = hi
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) clientProof() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Client Key"))
|
||||||
|
clientKey := mac.Sum(nil)
|
||||||
|
hash := c.newHash()
|
||||||
|
hash.Write(clientKey)
|
||||||
|
storedKey := hash.Sum(nil)
|
||||||
|
mac = hmac.New(c.newHash, storedKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
clientProof := mac.Sum(nil)
|
||||||
|
for i, b := range clientKey {
|
||||||
|
clientProof[i] ^= b
|
||||||
|
}
|
||||||
|
clientProof64 := make([]byte, b64.EncodedLen(len(clientProof)))
|
||||||
|
b64.Encode(clientProof64, clientProof)
|
||||||
|
return clientProof64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) serverSignature() []byte {
|
||||||
|
mac := hmac.New(c.newHash, c.saltedPass)
|
||||||
|
mac.Write([]byte("Server Key"))
|
||||||
|
serverKey := mac.Sum(nil)
|
||||||
|
|
||||||
|
mac = hmac.New(c.newHash, serverKey)
|
||||||
|
mac.Write(c.authMsg.Bytes())
|
||||||
|
serverSignature := mac.Sum(nil)
|
||||||
|
|
||||||
|
encoded := make([]byte, b64.EncodedLen(len(serverSignature)))
|
||||||
|
b64.Encode(encoded, serverSignature)
|
||||||
|
return encoded
|
||||||
|
}
|
|
@ -58,7 +58,13 @@ func ssl(o values) (func(net.Conn) (net.Conn, error), error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sslRenegotiation(&tlsConf)
|
|
||||||
|
// Accept renegotiation requests initiated by the backend.
|
||||||
|
//
|
||||||
|
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
||||||
|
// the default configuration of older versions has it enabled. Redshift
|
||||||
|
// also initiates renegotiations and cannot be reconfigured.
|
||||||
|
tlsConf.Renegotiation = tls.RenegotiateFreelyAsClient
|
||||||
|
|
||||||
return func(conn net.Conn) (net.Conn, error) {
|
return func(conn net.Conn) (net.Conn, error) {
|
||||||
client := tls.Client(conn, &tlsConf)
|
client := tls.Client(conn, &tlsConf)
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package pq
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Accept renegotiation requests initiated by the backend.
|
|
||||||
//
|
|
||||||
// Renegotiation was deprecated then removed from PostgreSQL 9.5, but
|
|
||||||
// the default configuration of older versions has it enabled. Redshift
|
|
||||||
// also initiates renegotiations and cannot be reconfigured.
|
|
||||||
func sslRenegotiation(conf *tls.Config) {
|
|
||||||
conf.Renegotiation = tls.RenegotiateFreelyAsClient
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package pq
|
|
||||||
|
|
||||||
import "crypto/tls"
|
|
||||||
|
|
||||||
// Renegotiation is not supported by crypto/tls until Go 1.7.
|
|
||||||
func sslRenegotiation(*tls.Config) {}
|
|
|
@ -209,9 +209,10 @@ github.com/klauspost/crc32
|
||||||
# github.com/lafriks/xormstore v1.0.0
|
# github.com/lafriks/xormstore v1.0.0
|
||||||
github.com/lafriks/xormstore
|
github.com/lafriks/xormstore
|
||||||
github.com/lafriks/xormstore/util
|
github.com/lafriks/xormstore/util
|
||||||
# github.com/lib/pq v1.0.0
|
# github.com/lib/pq v1.1.0
|
||||||
github.com/lib/pq
|
github.com/lib/pq
|
||||||
github.com/lib/pq/oid
|
github.com/lib/pq/oid
|
||||||
|
github.com/lib/pq/scram
|
||||||
# github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
# github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96
|
||||||
github.com/lunny/dingtalk_webhook
|
github.com/lunny/dingtalk_webhook
|
||||||
# github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e
|
# github.com/lunny/levelqueue v0.0.0-20190217115915-02b525a4418e
|
||||||
|
|
Loading…
Reference in New Issue