Skip to content

Commit 4c6c0cf

Browse files
committed
Merge branch 'master' of https://github.com/go-sql-driver/mysql into add-beforeconnect
2 parents 4b86241 + fc589cb commit 4c6c0cf

21 files changed

+362
-202
lines changed

.github/workflows/test.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ jobs:
3131
import os
3232
go = [
3333
# Keep the most recent production release at the top
34-
'1.20',
34+
'1.21',
3535
# Older production releases
36+
'1.20',
3637
'1.19',
3738
'1.18',
3839
]
3940
mysql = [
41+
'8.1',
4042
'8.0',
4143
'5.7',
4244
'5.6',
@@ -75,7 +77,7 @@ jobs:
7577
- uses: actions/setup-go@v4
7678
with:
7779
go-version: ${{ matrix.go }}
78-
- uses: shogo82148/actions-setup-mysql@v1.16.0
80+
- uses: shogo82148/actions-setup-mysql@v1.21.0
7981
with:
8082
mysql-version: ${{ matrix.mysql }}
8183
user: ${{ env.MYSQL_TEST_USER }}
@@ -94,7 +96,7 @@ jobs:
9496
9597
- name: test
9698
run: |
97-
go test -v '-covermode=count' '-coverprofile=coverage.out'
99+
go test -v '-race' '-covermode=atomic' '-coverprofile=coverage.out'
98100
99101
- name: Send coverage
100102
uses: shogo82148/actions-goveralls@v1

AUTHORS

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
Aaron Hopkins <go-sql-driver at die.net>
1515
Achille Roussel <achille.roussel at gmail.com>
16+
Aidan <aidan.liu at pingcap.com>
1617
Alex Snast <alexsn at fb.com>
1718
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
1819
Andrew Reid <andrew.reid at tixtrack.com>
@@ -38,6 +39,7 @@ Evan Elias <evan at skeema.net>
3839
Evan Shaw <evan at vendhq.com>
3940
Frederick Mayle <frederickmayle at gmail.com>
4041
Gustavo Kristic <gkristic at gmail.com>
42+
Gusted <postmaster at gusted.xyz>
4143
Hajime Nakagami <nakagami at gmail.com>
4244
Hanno Braun <mail at hannobraun.com>
4345
Henri Yandell <flamefew at gmail.com>
@@ -49,6 +51,7 @@ INADA Naoki <songofacandy at gmail.com>
4951
Jacek Szwec <szwec.jacek at gmail.com>
5052
James Harr <james.harr at gmail.com>
5153
Janek Vedock <janekvedock at comcast.net>
54+
Jason Ng <oblitorum at gmail.com>
5255
Jean-Yves Pellé <jy at pelle.link>
5356
Jeff Hodges <jeff at somethingsimilar.com>
5457
Jeffrey Charles <jeffreycharles at gmail.com>
@@ -77,6 +80,7 @@ Maciej Zimnoch <maciej.zimnoch at codilime.com>
7780
Michael Woolnough <michael.woolnough at gmail.com>
7881
Nathanial Murphy <nathanial.murphy at gmail.com>
7982
Nicola Peduzzi <thenikso at gmail.com>
83+
Oliver Bone <owbone at github.com>
8084
Olivier Mengué <dolmen at cpan.org>
8185
oscarzhao <oscarzhaosl at gmail.com>
8286
Paul Bonser <misterpib at gmail.com>
@@ -108,6 +112,7 @@ Xiangyu Hu <xiangyu.hu at outlook.com>
108112
Xiaobing Jiang <s7v7nislands at gmail.com>
109113
Xiuming Chen <cc at cxm.cc>
110114
Xuehong Chan <chanxuehong at gmail.com>
115+
Zhang Xiang <angwerzx at 126.com>
111116
Zhenye Xie <xiezhenye at gmail.com>
112117
Zhixin Wen <john.wenzhixin at gmail.com>
113118
Ziheng Lyu <zihenglv at gmail.com>
@@ -127,7 +132,9 @@ Keybase Inc.
127132
Microsoft Corp.
128133
Multiplay Ltd.
129134
Percona LLC
135+
PingCAP Inc.
130136
Pivotal Inc.
137+
Shattered Silicon Ltd.
131138
Stripe Inc.
132139
Zendesk Inc.
133140
Dolthub Inc.

CHANGELOG.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ New Features:
162162

163163
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
164164
- Support for returning table alias on Columns() (#289, #359, #382)
165-
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
165+
- Placeholder interpolation, can be activated with the DSN parameter `interpolateParams=true` (#309, #318, #490)
166166
- Support for uint64 parameters with high bit set (#332, #345)
167167
- Cleartext authentication plugin support (#327)
168168
- Exported ParseDSN function and the Config struct (#403, #419, #429)
@@ -206,7 +206,7 @@ Changes:
206206
- Also exported the MySQLWarning type
207207
- mysqlConn.Close returns the first error encountered instead of ignoring all errors
208208
- writePacket() automatically writes the packet size to the header
209-
- readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets
209+
- readPacket() uses an iterative approach instead of the recursive approach to merge split packets
210210

211211
New Features:
212212

@@ -254,7 +254,7 @@ Bugfixes:
254254

255255
- Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification
256256
- Convert to DB timezone when inserting `time.Time`
257-
- Splitted packets (more than 16MB) are now merged correctly
257+
- Split packets (more than 16MB) are now merged correctly
258258
- Fixed false positive `io.EOF` errors when the data was fully read
259259
- Avoid panics on reuse of closed connections
260260
- Fixed empty string producing false nil values

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
4848
## Installation
4949
Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
5050
```bash
51-
$ go get -u github.com/go-sql-driver/mysql
51+
go get -u github.com/go-sql-driver/mysql
5252
```
5353
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
5454

@@ -114,7 +114,7 @@ This has the same effect as an empty DSN string:
114114
115115
```
116116

117-
`dbname` is escaped by [PathEscape()]()https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes:
117+
`dbname` is escaped by [PathEscape()](https://pkg.go.dev/net/url#PathEscape) since v1.8.0. If your database name is `dbname/withslash`, it becomes:
118118

119119
```
120120
/dbname%2Fwithslash
@@ -127,7 +127,7 @@ Passwords can consist of any character. Escaping is **not** necessary.
127127

128128
#### Protocol
129129
See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
130-
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
130+
In general you should use a Unix domain socket if available and TCP otherwise for best performance.
131131

132132
#### Address
133133
For TCP and UDP networks, addresses have the form `host[:port]`.
@@ -151,7 +151,7 @@ Default: false
151151
```
152152

153153
`allowAllFiles=true` disables the file allowlist for `LOAD DATA LOCAL INFILE` and allows *all* files.
154-
[*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)
154+
[*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local)
155155

156156
##### `allowCleartextPasswords`
157157

@@ -200,7 +200,7 @@ Valid Values: <name>
200200
Default: none
201201
```
202202

203-
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset failes. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
203+
Sets the charset used for client-server interaction (`"SET NAMES <value>"`). If multiple charsets are set (separated by a comma), the following charset is used if setting the charset fails. This enables for example support for `utf8mb4` ([introduced in MySQL 5.5.3](http://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html)) with fallback to `utf8` for older servers (`charset=utf8mb4,utf8`).
204204

205205
See also [Unicode Support](#unicode-support).
206206

@@ -509,7 +509,7 @@ For this feature you need direct access to the package. Therefore you must chang
509509
import "github.com/go-sql-driver/mysql"
510510
```
511511

512-
Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](http://dev.mysql.com/doc/refman/5.7/en/load-data-local.html)).
512+
Files must be explicitly allowed by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the allowlist check must be deactivated by using the DSN parameter `allowAllFiles=true` ([*Might be insecure!*](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-local)).
513513

514514
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
515515

auth.go

+49-2
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ import (
1313
"crypto/rsa"
1414
"crypto/sha1"
1515
"crypto/sha256"
16+
"crypto/sha512"
1617
"crypto/x509"
1718
"encoding/pem"
1819
"fmt"
1920
"sync"
21+
22+
"filippo.io/edwards25519"
2023
)
2124

2225
// server pub keys registry
@@ -225,6 +228,44 @@ func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte,
225228
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
226229
}
227230

231+
// authEd25519 does ed25519 authentication used by MariaDB.
232+
func authEd25519(scramble []byte, password string) ([]byte, error) {
233+
// Derived from https://github.com/MariaDB/server/blob/d8e6bb00888b1f82c031938f4c8ac5d97f6874c3/plugin/auth_ed25519/ref10/sign.c
234+
// Code style is from https://cs.opensource.google/go/go/+/refs/tags/go1.21.5:src/crypto/ed25519/ed25519.go;l=207
235+
h := sha512.Sum512([]byte(password))
236+
237+
s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32])
238+
if err != nil {
239+
return nil, err
240+
}
241+
A := (&edwards25519.Point{}).ScalarBaseMult(s)
242+
243+
mh := sha512.New()
244+
mh.Write(h[32:])
245+
mh.Write(scramble)
246+
messageDigest := mh.Sum(nil)
247+
r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest)
248+
if err != nil {
249+
return nil, err
250+
}
251+
252+
R := (&edwards25519.Point{}).ScalarBaseMult(r)
253+
254+
kh := sha512.New()
255+
kh.Write(R.Bytes())
256+
kh.Write(A.Bytes())
257+
kh.Write(scramble)
258+
hramDigest := kh.Sum(nil)
259+
k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest)
260+
if err != nil {
261+
return nil, err
262+
}
263+
264+
S := k.MultiplyAdd(k, s, r)
265+
266+
return append(R.Bytes(), S.Bytes()...), nil
267+
}
268+
228269
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
229270
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
230271
if err != nil {
@@ -290,6 +331,12 @@ func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
290331
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
291332
return enc, err
292333

334+
case "client_ed25519":
335+
if len(authData) != 32 {
336+
return nil, ErrMalformPkt
337+
}
338+
return authEd25519(authData, mc.cfg.Passwd)
339+
293340
default:
294341
mc.cfg.Logger.Print("unknown auth plugin:", plugin)
295342
return nil, ErrUnknownPlugin
@@ -338,7 +385,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
338385

339386
switch plugin {
340387

341-
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
388+
// https://dev.mysql.com/blog-archive/preparing-your-community-connector-for-mysql-8-part-2-sha256/
342389
case "caching_sha2_password":
343390
switch len(authData) {
344391
case 0:
@@ -376,7 +423,7 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
376423
}
377424

378425
if data[0] != iAuthMoreData {
379-
return fmt.Errorf("unexpect resp from server for caching_sha2_password perform full authentication")
426+
return fmt.Errorf("unexpected resp from server for caching_sha2_password, perform full authentication")
380427
}
381428

382429
// parse public key

auth_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -1328,3 +1328,54 @@ func TestAuthSwitchSHA256PasswordSecure(t *testing.T) {
13281328
t.Errorf("got unexpected data: %v", conn.written)
13291329
}
13301330
}
1331+
1332+
// Derived from https://github.com/MariaDB/server/blob/6b2287fff23fbdc362499501c562f01d0d2db52e/plugin/auth_ed25519/ed25519-t.c
1333+
func TestEd25519Auth(t *testing.T) {
1334+
conn, mc := newRWMockConn(1)
1335+
mc.cfg.User = "root"
1336+
mc.cfg.Passwd = "foobar"
1337+
1338+
authData := []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
1339+
plugin := "client_ed25519"
1340+
1341+
// Send Client Authentication Packet
1342+
authResp, err := mc.auth(authData, plugin)
1343+
if err != nil {
1344+
t.Fatal(err)
1345+
}
1346+
err = mc.writeHandshakeResponsePacket(authResp, plugin)
1347+
if err != nil {
1348+
t.Fatal(err)
1349+
}
1350+
1351+
// check written auth response
1352+
authRespStart := 4 + 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1
1353+
authRespEnd := authRespStart + 1 + len(authResp)
1354+
writtenAuthRespLen := conn.written[authRespStart]
1355+
writtenAuthResp := conn.written[authRespStart+1 : authRespEnd]
1356+
expectedAuthResp := []byte{
1357+
232, 61, 201, 63, 67, 63, 51, 53, 86, 73, 238, 35, 170, 117, 146,
1358+
214, 26, 17, 35, 9, 8, 132, 245, 141, 48, 99, 66, 58, 36, 228, 48,
1359+
84, 115, 254, 187, 168, 88, 162, 249, 57, 35, 85, 79, 238, 167, 106,
1360+
68, 117, 56, 135, 171, 47, 20, 14, 133, 79, 15, 229, 124, 160, 176,
1361+
100, 138, 14,
1362+
}
1363+
if writtenAuthRespLen != 64 {
1364+
t.Fatalf("expected 64 bytes from client, got %d", writtenAuthRespLen)
1365+
}
1366+
if !bytes.Equal(writtenAuthResp, expectedAuthResp) {
1367+
t.Fatalf("auth response did not match expected value:\n%v\n%v", writtenAuthResp, expectedAuthResp)
1368+
}
1369+
conn.written = nil
1370+
1371+
// auth response
1372+
conn.data = []byte{
1373+
7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, // OK
1374+
}
1375+
conn.maxReads = 1
1376+
1377+
// Handle response to auth packet
1378+
if err := mc.handleAuthResult(authData, plugin); err != nil {
1379+
t.Errorf("got error: %v", err)
1380+
}
1381+
}

benchmark_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
4848

4949
func initDB(b *testing.B, queries ...string) *sql.DB {
5050
tb := (*TB)(b)
51-
db := tb.checkDB(sql.Open("mysql", dsn))
51+
db := tb.checkDB(sql.Open(driverNameTest, dsn))
5252
for _, query := range queries {
5353
if _, err := db.Exec(query); err != nil {
5454
b.Fatalf("error on %q: %v", query, err)
@@ -105,7 +105,7 @@ func BenchmarkExec(b *testing.B) {
105105
tb := (*TB)(b)
106106
b.StopTimer()
107107
b.ReportAllocs()
108-
db := tb.checkDB(sql.Open("mysql", dsn))
108+
db := tb.checkDB(sql.Open(driverNameTest, dsn))
109109
db.SetMaxIdleConns(concurrencyLevel)
110110
defer db.Close()
111111

@@ -151,7 +151,7 @@ func BenchmarkRoundtripTxt(b *testing.B) {
151151
sampleString := string(sample)
152152
b.ReportAllocs()
153153
tb := (*TB)(b)
154-
db := tb.checkDB(sql.Open("mysql", dsn))
154+
db := tb.checkDB(sql.Open(driverNameTest, dsn))
155155
defer db.Close()
156156
b.StartTimer()
157157
var result string
@@ -184,7 +184,7 @@ func BenchmarkRoundtripBin(b *testing.B) {
184184
sample, min, max := initRoundtripBenchmarks()
185185
b.ReportAllocs()
186186
tb := (*TB)(b)
187-
db := tb.checkDB(sql.Open("mysql", dsn))
187+
db := tb.checkDB(sql.Open(driverNameTest, dsn))
188188
defer db.Close()
189189
stmt := tb.checkStmt(db.Prepare("SELECT ?"))
190190
defer stmt.Close()

connection.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ type mysqlConn struct {
3434
status statusFlag
3535
sequence uint8
3636
parseTime bool
37-
reset bool // set when the Go SQL package calls ResetSession
3837

3938
// for context support (Go 1.8+)
4039
watching bool
@@ -646,7 +645,31 @@ func (mc *mysqlConn) ResetSession(ctx context.Context) error {
646645
if mc.closed.Load() {
647646
return driver.ErrBadConn
648647
}
649-
mc.reset = true
648+
649+
// Perform a stale connection check. We only perform this check for
650+
// the first query on a connection that has been checked out of the
651+
// connection pool: a fresh connection from the pool is more likely
652+
// to be stale, and it has not performed any previous writes that
653+
// could cause data corruption, so it's safe to return ErrBadConn
654+
// if the check fails.
655+
if mc.cfg.CheckConnLiveness {
656+
conn := mc.netConn
657+
if mc.rawConn != nil {
658+
conn = mc.rawConn
659+
}
660+
var err error
661+
if mc.cfg.ReadTimeout != 0 {
662+
err = conn.SetReadDeadline(time.Now().Add(mc.cfg.ReadTimeout))
663+
}
664+
if err == nil {
665+
err = connCheck(conn)
666+
}
667+
if err != nil {
668+
mc.cfg.Logger.Print("closing bad idle connection: ", err)
669+
return driver.ErrBadConn
670+
}
671+
}
672+
650673
return nil
651674
}
652675

0 commit comments

Comments
 (0)