Skip to content

Commit de5a0de

Browse files
committed
Merge pull request #2 from go-sql-driver/master
Pull recent changes from the main fork
2 parents 5e93316 + d8a5f6c commit de5a0de

File tree

6 files changed

+136
-45
lines changed

6 files changed

+136
-45
lines changed

AUTHORS

+3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ Aaron Hopkins <go-sql-driver at die.net>
1515
Arne Hormann <arnehormann at gmail.com>
1616
Carlos Nieto <jose.carlos at menteslibres.net>
1717
Chris Moos <chris at tech9computers.com>
18+
Daniel Nichter <nil at codenode.com>
1819
DisposaBoy <disposaboy at dby.me>
1920
Frederick Mayle <frederickmayle at gmail.com>
2021
Gustavo Kristic <gkristic at gmail.com>
2122
Hanno Braun <mail at hannobraun.com>
2223
Henri Yandell <flamefew at gmail.com>
24+
Hirotaka Yamamoto <ymmt2005 at gmail.com>
2325
INADA Naoki <songofacandy at gmail.com>
2426
James Harr <james.harr at gmail.com>
2527
Jian Zhen <zhenjl at gmail.com>
2628
Joshua Prunier <joshua.prunier at gmail.com>
29+
Julien Lefevre <julien.lefevr at gmail.com>
2730
Julien Schmidt <go-sql-driver at julienschmidt.com>
2831
Kamil Dziedzic <kamil at klecza.pl>
2932
Kevin Malachowski <kevin at chowski.com>

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ import "github.com/go-sql-driver/mysql"
331331

332332
Files must be whitelisted by registering them with `mysql.RegisterLocalFile(filepath)` (recommended) or the Whitelist 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)).
333333

334-
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.
334+
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.
335335

336336
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
337337

connection.go

+17-8
Original file line numberDiff line numberDiff line change
@@ -120,18 +120,27 @@ func (mc *mysqlConn) Close() (err error) {
120120
// Makes Close idempotent
121121
if mc.netConn != nil {
122122
err = mc.writeCommandPacket(comQuit)
123-
if err == nil {
124-
err = mc.netConn.Close()
125-
} else {
126-
mc.netConn.Close()
123+
}
124+
125+
mc.cleanup()
126+
127+
return
128+
}
129+
130+
// Closes the network connection and unsets internal variables. Do not call this
131+
// function after successfully authentication, call Close instead. This function
132+
// is called before auth or on auth failure because MySQL will have already
133+
// closed the network connection.
134+
func (mc *mysqlConn) cleanup() {
135+
// Makes cleanup idempotent
136+
if mc.netConn != nil {
137+
if err := mc.netConn.Close(); err != nil {
138+
errLog.Print(err)
127139
}
128140
mc.netConn = nil
129141
}
130-
131142
mc.cfg = nil
132143
mc.buf.rd = nil
133-
134-
return
135144
}
136145

137146
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
@@ -253,7 +262,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
253262
if v == nil {
254263
buf = append(buf, "NULL"...)
255264
} else {
256-
buf = append(buf, '\'')
265+
buf = append(buf, "_binary'"...)
257266
if mc.status&statusNoBackslashEscapes == 0 {
258267
buf = escapeBytesBackslash(buf, v)
259268
} else {

driver.go

+41-29
Original file line numberDiff line numberDiff line change
@@ -84,43 +84,23 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
8484
// Reading Handshake Initialization Packet
8585
cipher, err := mc.readInitPacket()
8686
if err != nil {
87-
mc.Close()
87+
mc.cleanup()
8888
return nil, err
8989
}
9090

9191
// Send Client Authentication Packet
9292
if err = mc.writeAuthPacket(cipher); err != nil {
93-
mc.Close()
93+
mc.cleanup()
9494
return nil, err
9595
}
9696

97-
// Read Result Packet
98-
err = mc.readResultOK()
99-
if err != nil {
100-
// Retry with old authentication method, if allowed
101-
if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword {
102-
if err = mc.writeOldAuthPacket(cipher); err != nil {
103-
mc.Close()
104-
return nil, err
105-
}
106-
if err = mc.readResultOK(); err != nil {
107-
mc.Close()
108-
return nil, err
109-
}
110-
} else if mc.cfg != nil && mc.cfg.allowCleartextPasswords && err == ErrCleartextPassword {
111-
if err = mc.writeClearAuthPacket(); err != nil {
112-
mc.Close()
113-
return nil, err
114-
}
115-
if err = mc.readResultOK(); err != nil {
116-
mc.Close()
117-
return nil, err
118-
}
119-
} else {
120-
mc.Close()
121-
return nil, err
122-
}
123-
97+
// Handle response to auth packet, switch methods if possible
98+
if err = handleAuthResult(mc, cipher); err != nil {
99+
// Authentication failed and MySQL has already closed the connection
100+
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
101+
// Do not send COM_QUIT, just cleanup and return the error.
102+
mc.cleanup()
103+
return nil, err
124104
}
125105

126106
// Get max allowed packet size
@@ -144,6 +124,38 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
144124
return mc, nil
145125
}
146126

127+
func handleAuthResult(mc *mysqlConn, cipher []byte) error {
128+
// Read Result Packet
129+
err := mc.readResultOK()
130+
if err == nil {
131+
return nil // auth successful
132+
}
133+
134+
if mc.cfg == nil {
135+
return err // auth failed and retry not possible
136+
}
137+
138+
// Retry auth if configured to do so.
139+
if mc.cfg.allowOldPasswords && err == ErrOldPassword {
140+
// Retry with old authentication method. Note: there are edge cases
141+
// where this should work but doesn't; this is currently "wontfix":
142+
// https://github.com/go-sql-driver/mysql/issues/184
143+
if err = mc.writeOldAuthPacket(cipher); err != nil {
144+
return err
145+
}
146+
err = mc.readResultOK()
147+
} else if mc.cfg.allowCleartextPasswords && err == ErrCleartextPassword {
148+
// Retry with clear text password for
149+
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
150+
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
151+
if err = mc.writeClearAuthPacket(); err != nil {
152+
return err
153+
}
154+
err = mc.readResultOK()
155+
}
156+
return err
157+
}
158+
147159
func init() {
148160
sql.Register("mysql", &MySQLDriver{})
149161
}

driver_test.go

+52-3
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@
99
package mysql
1010

1111
import (
12+
"bytes"
1213
"crypto/tls"
1314
"database/sql"
1415
"database/sql/driver"
1516
"fmt"
1617
"io"
1718
"io/ioutil"
19+
"log"
1820
"net"
1921
"net/url"
2022
"os"
@@ -1018,7 +1020,7 @@ func TestFoundRows(t *testing.T) {
10181020

10191021
func TestStrict(t *testing.T) {
10201022
// ALLOW_INVALID_DATES to get rid of stricter modes - we want to test for warnings, not errors
1021-
relaxedDsn := dsn + "&sql_mode=ALLOW_INVALID_DATES"
1023+
relaxedDsn := dsn + "&sql_mode='ALLOW_INVALID_DATES,NO_AUTO_CREATE_USER'"
10221024
// make sure the MySQL version is recent enough with a separate connection
10231025
// before running the test
10241026
conn, err := MySQLDriver{}.Open(relaxedDsn)
@@ -1643,7 +1645,7 @@ func TestSqlInjection(t *testing.T) {
16431645

16441646
dsns := []string{
16451647
dsn,
1646-
dsn + "&sql_mode=NO_BACKSLASH_ESCAPES",
1648+
dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
16471649
}
16481650
for _, testdsn := range dsns {
16491651
runTests(t, testdsn, createTest("1 OR 1=1"))
@@ -1673,9 +1675,56 @@ func TestInsertRetrieveEscapedData(t *testing.T) {
16731675

16741676
dsns := []string{
16751677
dsn,
1676-
dsn + "&sql_mode=NO_BACKSLASH_ESCAPES",
1678+
dsn + "&sql_mode='NO_BACKSLASH_ESCAPES,NO_AUTO_CREATE_USER'",
16771679
}
16781680
for _, testdsn := range dsns {
16791681
runTests(t, testdsn, testData)
16801682
}
16811683
}
1684+
1685+
func TestUnixSocketAuthFail(t *testing.T) {
1686+
runTests(t, dsn, func(dbt *DBTest) {
1687+
// Save the current logger so we can restore it.
1688+
oldLogger := errLog
1689+
1690+
// Set a new logger so we can capture its output.
1691+
buffer := bytes.NewBuffer(make([]byte, 0, 64))
1692+
newLogger := log.New(buffer, "prefix: ", 0)
1693+
SetLogger(newLogger)
1694+
1695+
// Restore the logger.
1696+
defer SetLogger(oldLogger)
1697+
1698+
// Make a new DSN that uses the MySQL socket file and a bad password, which
1699+
// we can make by simply appending any character to the real password.
1700+
badPass := pass + "x"
1701+
socket := ""
1702+
if prot == "unix" {
1703+
socket = addr
1704+
} else {
1705+
// Get socket file from MySQL.
1706+
err := dbt.db.QueryRow("SELECT @@socket").Scan(&socket)
1707+
if err != nil {
1708+
t.Fatalf("Error on SELECT @@socket: %s", err.Error())
1709+
}
1710+
}
1711+
t.Logf("socket: %s", socket)
1712+
badDSN := fmt.Sprintf("%s:%s@unix(%s)/%s?timeout=30s&strict=true", user, badPass, socket, dbname)
1713+
db, err := sql.Open("mysql", badDSN)
1714+
if err != nil {
1715+
t.Fatalf("Error connecting: %s", err.Error())
1716+
}
1717+
defer db.Close()
1718+
1719+
// Connect to MySQL for real. This will cause an auth failure.
1720+
err = db.Ping()
1721+
if err == nil {
1722+
t.Error("expected Ping() to return an error")
1723+
}
1724+
1725+
// The driver should not log anything.
1726+
if actual := buffer.String(); actual != "" {
1727+
t.Errorf("expected no output, got %q", actual)
1728+
}
1729+
})
1730+
}

infile.go

+22-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import (
1313
"io"
1414
"os"
1515
"strings"
16+
"sync"
1617
)
1718

1819
var (
19-
fileRegister map[string]bool
20-
readerRegister map[string]func() io.Reader
20+
fileRegister map[string]bool
21+
fileRegisterLock sync.RWMutex
22+
readerRegister map[string]func() io.Reader
23+
readerRegisterLock sync.RWMutex
2124
)
2225

2326
// RegisterLocalFile adds the given file to the file whitelist,
@@ -32,17 +35,21 @@ var (
3235
// ...
3336
//
3437
func RegisterLocalFile(filePath string) {
38+
fileRegisterLock.Lock()
3539
// lazy map init
3640
if fileRegister == nil {
3741
fileRegister = make(map[string]bool)
3842
}
3943

4044
fileRegister[strings.Trim(filePath, `"`)] = true
45+
fileRegisterLock.Unlock()
4146
}
4247

4348
// DeregisterLocalFile removes the given filepath from the whitelist.
4449
func DeregisterLocalFile(filePath string) {
50+
fileRegisterLock.Lock()
4551
delete(fileRegister, strings.Trim(filePath, `"`))
52+
fileRegisterLock.Unlock()
4653
}
4754

4855
// RegisterReaderHandler registers a handler function which is used
@@ -61,18 +68,22 @@ func DeregisterLocalFile(filePath string) {
6168
// ...
6269
//
6370
func RegisterReaderHandler(name string, handler func() io.Reader) {
71+
readerRegisterLock.Lock()
6472
// lazy map init
6573
if readerRegister == nil {
6674
readerRegister = make(map[string]func() io.Reader)
6775
}
6876

6977
readerRegister[name] = handler
78+
readerRegisterLock.Unlock()
7079
}
7180

7281
// DeregisterReaderHandler removes the ReaderHandler function with
7382
// the given name from the registry.
7483
func DeregisterReaderHandler(name string) {
84+
readerRegisterLock.Lock()
7585
delete(readerRegister, name)
86+
readerRegisterLock.Unlock()
7687
}
7788

7889
func deferredClose(err *error, closer io.Closer) {
@@ -90,7 +101,11 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
90101
// The server might return an an absolute path. See issue #355.
91102
name = name[idx+8:]
92103

93-
if handler, inMap := readerRegister[name]; inMap {
104+
readerRegisterLock.RLock()
105+
handler, inMap := readerRegister[name]
106+
readerRegisterLock.RUnlock()
107+
108+
if inMap {
94109
rdr = handler()
95110
if rdr != nil {
96111
data = make([]byte, 4+mc.maxWriteSize)
@@ -106,7 +121,10 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
106121
}
107122
} else { // File
108123
name = strings.Trim(name, `"`)
109-
if mc.cfg.allowAllFiles || fileRegister[name] {
124+
fileRegisterLock.RLock()
125+
fr := fileRegister[name]
126+
fileRegisterLock.RUnlock()
127+
if mc.cfg.allowAllFiles || fr {
110128
var file *os.File
111129
var fi os.FileInfo
112130

0 commit comments

Comments
 (0)