diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 6fdedd5..4f63522 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,7 +1,7 @@ name: Go on: push: - branches: [ master ] + branches: [ master,dev ] paths: - '**.go' - 'go.mod' @@ -21,10 +21,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 -# Create frontend `dist` folder. -# -# - name: Create frontend dist folder -# run: mkdir frontend/dist/ && touch frontend/dist/1 - name: Run golangci-lint uses: golangci/golangci-lint-action@v2.4.0 with: diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e6723b4 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,32 @@ +linters-settings: + nakedret: + max-func-lines: 0 + govet: + settings: + printf: + funcs: + - (unknwon.dev/clog/v2).Trace + - (unknwon.dev/clog/v2).Info + - (unknwon.dev/clog/v2).Warn + - (unknwon.dev/clog/v2).Error + - (unknwon.dev/clog/v2).ErrorDepth + - (unknwon.dev/clog/v2).Fatal + - (unknwon.dev/clog/v2).FatalDepth + +linters: + enable: + - deadcode + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + - nakedret + - gofmt + - rowserrcheck + - unconvert + - goimports diff --git a/internal/cli/token.go b/internal/cli/token.go deleted file mode 100644 index 3585445..0000000 --- a/internal/cli/token.go +++ /dev/null @@ -1,13 +0,0 @@ -package cli - -import "math/rand" - -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()" - -func genToken() string { - b := make([]byte, 8) - for i := range b { - b[i] = letterBytes[rand.Intn(len(letterBytes))] - } - return string(b) -} diff --git a/internal/database/db.go b/internal/database/db.go index 0302b6f..7b66f2a 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -9,5 +9,5 @@ func InitDB(driver, dsn string) (err error) { case "sqlite": DB, err = NewSqlite3(dsn) } - return + return err } diff --git a/internal/database/json_field.go b/internal/database/json_field.go index 71269a0..b6dc2cd 100644 --- a/internal/database/json_field.go +++ b/internal/database/json_field.go @@ -15,7 +15,6 @@ func (f *MapField) Scan(data interface{}) error { return json.Unmarshal(data.([]byte), f) } - type ListField []string func (f ListField) Value() (driver.Value, error) { @@ -24,4 +23,4 @@ func (f ListField) Value() (driver.Value, error) { func (f *ListField) Scan(data interface{}) error { return json.Unmarshal(data.([]byte), f) -} \ No newline at end of file +} diff --git a/internal/newdns/run.go b/internal/newdns/run.go index 88443ff..5783dc4 100644 --- a/internal/newdns/run.go +++ b/internal/newdns/run.go @@ -11,13 +11,13 @@ func Accept(logger Logger) dns.MsgAcceptFunc { return func(dh dns.Header) dns.MsgAcceptAction { // check if request if dh.Bits&(1<<15) != 0 { - log(logger, Ignored, nil, nil, fmt.Sprintf("not a request")) + log(logger, Ignored, nil, nil, "not a request") return dns.MsgIgnore } // check opcode if int(dh.Bits>>11)&0xF != dns.OpcodeQuery { - log(logger, Ignored, nil, nil, fmt.Sprintf("not a query")) + log(logger, Ignored, nil, nil, "not a query") return dns.MsgIgnore } diff --git a/internal/newdns/server.go b/internal/newdns/server.go index e543304..694a786 100644 --- a/internal/newdns/server.go +++ b/internal/newdns/server.go @@ -272,7 +272,7 @@ func (s *Server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { // Close will close the server. func (s *Server) Close() { - defer func() { recover() }() + defer func() { recover() }() // nolint:errcheck close(s.close) } diff --git a/internal/newdns/zone.go b/internal/newdns/zone.go index 2cb94b4..d42d00b 100644 --- a/internal/newdns/zone.go +++ b/internal/newdns/zone.go @@ -172,7 +172,7 @@ func (z *Zone) Lookup(name, remoteAddr string, needle ...Type) ([]Set, bool, err for i := 0; ; i++ { // get sets - sets, err := z.Handler(TrimZone(z.Name, name),remoteAddr) + sets, err := z.Handler(TrimZone(z.Name, name), remoteAddr) if err != nil { return nil, false, errors.Wrap(err, "zone handler error") } diff --git a/internal/newdns/zone_test.go b/internal/newdns/zone_test.go index 419d077..907c500 100644 --- a/internal/newdns/zone_test.go +++ b/internal/newdns/zone_test.go @@ -116,7 +116,7 @@ func TestZoneLookup(t *testing.T) { "ns1.example.com.", "ns2.example.com.", }, - Handler: func(name,remoteAddr string) ([]Set, error) { + Handler: func(name, remoteAddr string) ([]Set, error) { if name == "error" { return nil, io.EOF } diff --git a/internal/notice/format.go b/internal/notice/format.go index 371d87f..8a29a6c 100644 --- a/internal/notice/format.go +++ b/internal/notice/format.go @@ -8,7 +8,7 @@ import ( "github.com/li4n0/revsuit/internal/record" ) -func formatRecordField(r record.Record,fieldFormat string) (content string) { +func formatRecordField(r record.Record, fieldFormat string) (content string) { structType := reflect.ValueOf(r) for i := 0; i < structType.NumField(); i++ { structField := structType.Type().Field(i) @@ -27,6 +27,6 @@ func formatRecordField(r record.Record,fieldFormat string) (content string) { } content += fmt.Sprintf(fieldFormat+"\n", strings.ToUpper(fieldName), value) } - strings.TrimSuffix(content, "\n") - return + content = strings.TrimSuffix(content, "\n") + return content } diff --git a/internal/notice/lark.go b/internal/notice/lark.go index 3031512..a2816ea 100644 --- a/internal/notice/lark.go +++ b/internal/notice/lark.go @@ -27,7 +27,7 @@ type larkElement struct { } type larkCard struct { - Header larkHeader `json:"header"` + Header larkHeader `json:"header"` Elements []larkElement `json:"elements"` } @@ -44,9 +44,9 @@ func (d *Lark) buildPayload(r record.Record) string { payload := larkPayload{ MsgType: "interactive", Card: larkCard{ - Header:larkHeader{ + Header: larkHeader{ Title: larkText{ - Tag: "plain_text", + Tag: "plain_text", Content: "New Connection", }, }, @@ -55,7 +55,7 @@ func (d *Lark) buildPayload(r record.Record) string { Tag: "div", Text: larkText{ Tag: "lark_md", - Content: formatRecordField(r,"**%s**: %v"), + Content: formatRecordField(r, "**%s**: %v"), }, }, }, diff --git a/internal/notice/slack.go b/internal/notice/slack.go index c76b763..f296dfa 100644 --- a/internal/notice/slack.go +++ b/internal/notice/slack.go @@ -27,7 +27,7 @@ type slackBlock struct { } type slackAttachments struct { - Color string `json:"color"` + Color string `json:"color"` Blocks []slackBlock `json:"blocks"` } diff --git a/internal/qqwry/download.go b/internal/qqwry/download.go index 6ffc339..67a9259 100644 --- a/internal/qqwry/download.go +++ b/internal/qqwry/download.go @@ -35,7 +35,7 @@ func get(url string) (b []byte, err error) { defer resp.Body.Close() b, err = ioutil.ReadAll(resp.Body) - return + return b, err } func getKey(b []byte) (key uint32, err error) { @@ -43,7 +43,7 @@ func getKey(b []byte) (key uint32, err error) { return 0, errors.New("copywrite.rar is corrupt") } key = binary.LittleEndian.Uint32(b[20:]) - return + return key, err } func decrypt(b []byte, key uint32) (_ []byte, err error) { diff --git a/internal/qqwry/wry.go b/internal/qqwry/wry.go index 8e18aff..f77cbb9 100644 --- a/internal/qqwry/wry.go +++ b/internal/qqwry/wry.go @@ -21,7 +21,7 @@ func init() { log.Error("Download qqwry.dat failed, caused by:%v, recommend to download it by yourself otherwise the `IpArea` will be null", err.Error()) } } - } else if info.ModTime().Sub(time.Now()) > 5*24*time.Hour { + } else if time.Until(info.ModTime()) > 5*24*time.Hour { log.Info("Updating qqwry.dat...") err := download() if err != nil { diff --git a/internal/rule/rule.go b/internal/rule/rule.go index f49cad3..7365a10 100644 --- a/internal/rule/rule.go +++ b/internal/rule/rule.go @@ -61,5 +61,5 @@ func (br BaseRule) Match(s string) (flag, flagGroup string) { flagGroup = matched[1] } } - return + return flag, flagGroup } diff --git a/pkg/dns/config.go b/pkg/dns/config.go index b9985de..7e3e855 100644 --- a/pkg/dns/config.go +++ b/pkg/dns/config.go @@ -1,5 +1,5 @@ package dns type Config struct { - Enable bool + Enable bool } diff --git a/pkg/dns/record.go b/pkg/dns/record.go index a14fffe..fd207a5 100644 --- a/pkg/dns/record.go +++ b/pkg/dns/record.go @@ -39,7 +39,7 @@ func newRecord(rule *Rule, flag, domain, remoteIp, ipArea string) (r *Record, er Rule: *rule, } err = database.DB.Create(r).Error - return + return r, err } func List(c *gin.Context) { diff --git a/pkg/dns/rule.go b/pkg/dns/rule.go index 3a85a5f..bf0fd4b 100644 --- a/pkg/dns/rule.go +++ b/pkg/dns/rule.go @@ -59,7 +59,7 @@ func (r *Rule) CreateOrUpdate() (err error) { return } err = GetServer().updateRules() - return + return err } // Delete the dns rule in database and ruleSet @@ -70,7 +70,7 @@ func (r *Rule) Delete() (err error) { return } err = GetServer().updateRules() - return + return err } // List all dns rules those satisfy the filter @@ -194,5 +194,4 @@ func DeleteRules(c *gin.Context) { "error": nil, "data": nil, }) - return } diff --git a/pkg/mysql/config.go b/pkg/mysql/config.go index 405ac23..762b7f0 100644 --- a/pkg/mysql/config.go +++ b/pkg/mysql/config.go @@ -1,7 +1,7 @@ package mysql type Config struct { - Enable bool - Addr string + Enable bool + Addr string VersionString string `yaml:"version_string"` } diff --git a/pkg/mysql/mysql.go b/pkg/mysql/mysql.go index 67b2c19..bde3e31 100644 --- a/pkg/mysql/mysql.go +++ b/pkg/mysql/mysql.go @@ -227,7 +227,7 @@ func (s *Server) ComQuery(c *vmysql.Conn, query string, callback func(*sqltypes. if c.Files[filename] == nil { log.Trace("MySQL now try to read file [%s], ID [%d]", filename, c.ConnectionID) data := c.RequestFile(filename) - if data == nil || len(data) == 0 { + if len(data) == 0 { log.Trace("MySQL file [%s] read failed, file may not exist in client [%d]", filename, c.ConnectionID) c.Files[filename] = []byte{} } else { diff --git a/pkg/mysql/mysql_test.go b/pkg/mysql/mysql_test.go index 53bb74c..9917135 100644 --- a/pkg/mysql/mysql_test.go +++ b/pkg/mysql/mysql_test.go @@ -13,5 +13,6 @@ func TestServer_NewConnection(t *testing.T) { if err != nil { fmt.Println(err) } - db.Exec("SELECT 1;") + + _, _ = db.Exec("SELECT 1;") } diff --git a/pkg/mysql/record.go b/pkg/mysql/record.go index ea9c162..2c18f7d 100644 --- a/pkg/mysql/record.go +++ b/pkg/mysql/record.go @@ -14,13 +14,12 @@ var _ record.Record = (*Record)(nil) type Record struct { record.BaseRecord - Username string `gorm:"index",form:"username" json:"username" notice:"username"` - ClientName string `gorm:"index",form:"client_name" json:"client_name" notice:"client_name"` - ClientOS string `gorm:"index",form:"client_os" json:"client_os" notice:"client_os"` - LoadLocalData bool `gorm:"index",form:"load_local_data" json:"load_local_data" notice:"load_local_data"` - //FileID uint `form:"file_id" json:"file_id" notice:"file_id"` - Files []File `form:"-" json:"files" notice:"-"` - Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"` + Username string `gorm:"index" form:"username" json:"username" notice:"username"` + ClientName string `gorm:"index" form:"client_name" json:"client_name" notice:"client_name"` + ClientOS string `gorm:"index" form:"client_os" json:"client_os" notice:"client_os"` + LoadLocalData bool `gorm:"index" form:"load_local_data" json:"load_local_data" notice:"load_local_data"` + Files []File `form:"-" json:"files" notice:"-"` + Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"` } func (Record) TableName() string { @@ -47,7 +46,7 @@ func newRecord(rule *Rule, flag, username, clientName, clientOS, remoteIp, ipAre Rule: *rule, } err = database.DB.Create(r).Error - return + return r, err } func List(c *gin.Context) { diff --git a/pkg/mysql/rule.go b/pkg/mysql/rule.go index 0afabda..5ca90bf 100644 --- a/pkg/mysql/rule.go +++ b/pkg/mysql/rule.go @@ -14,7 +14,7 @@ type Rule struct { rule.BaseRule Files string `form:"files" json:"files"` ExploitJdbcClient bool `gorm:"exploit_jdbc_client" form:"exploit_jdbc_client" json:"exploit_jdbc_client"` - Payloads database.MapField `json:"payloads" json:"payloads"` + Payloads database.MapField `json:"payloads" form:"payloads"` } func (Rule) TableName() string { @@ -42,7 +42,7 @@ func (r *Rule) CreateOrUpdate() (err error) { return } err = GetServer().updateRules() - return + return err } // Delete the mysql rule in database and ruleSet @@ -53,7 +53,7 @@ func (r *Rule) Delete() (err error) { return } err = GetServer().updateRules() - return + return err } // List all mysql rules those satisfy the filter @@ -176,5 +176,4 @@ func DeleteRules(c *gin.Context) { "error": nil, "data": nil, }) - return } diff --git a/pkg/mysql/vmysql/auth_server.go b/pkg/mysql/vmysql/auth_server.go index 8519833..e9e1ecb 100644 --- a/pkg/mysql/vmysql/auth_server.go +++ b/pkg/mysql/vmysql/auth_server.go @@ -117,18 +117,18 @@ func ScramblePassword(salt, password []byte) []byte { // stage1Hash = SHA1(password) crypt := sha1.New() - crypt.Write(password) + _, _ = crypt.Write(password) stage1 := crypt.Sum(nil) // scrambleHash = SHA1(salt + SHA1(stage1Hash)) // inner Hash crypt.Reset() - crypt.Write(stage1) + _, _ = crypt.Write(stage1) hash := crypt.Sum(nil) // outer Hash crypt.Reset() - crypt.Write(salt) - crypt.Write(hash) + _, _ = crypt.Write(salt) + _, _ = crypt.Write(hash) scramble := crypt.Sum(nil) // token = scrambleHash XOR stage1Hash @@ -164,8 +164,8 @@ func isPassScrambleMysqlNativePassword(reply, salt []byte, mysqlNativePassword s // scramble = SHA1(salt+hash) crypt := sha1.New() - crypt.Write(salt) - crypt.Write(hash) + _, _ = crypt.Write(salt) + _, _ = crypt.Write(hash) scramble := crypt.Sum(nil) // token = scramble XOR stage1Hash @@ -175,7 +175,7 @@ func isPassScrambleMysqlNativePassword(reply, salt []byte, mysqlNativePassword s hashStage1 := scramble crypt.Reset() - crypt.Write(hashStage1) + _, _ = crypt.Write(hashStage1) candidateHash2 := crypt.Sum(nil) return bytes.Equal(candidateHash2, hash) diff --git a/pkg/mysql/vmysql/auth_server_none.go b/pkg/mysql/vmysql/auth_server_none.go index b063063..034f078 100644 --- a/pkg/mysql/vmysql/auth_server_none.go +++ b/pkg/mysql/vmysql/auth_server_none.go @@ -17,9 +17,9 @@ limitations under the License. package vmysql import ( - "net" + "net" - querypb "vitess.io/vitess/go/vt/proto/query" + querypb "vitess.io/vitess/go/vt/proto/query" ) // AuthServerNone takes all comers. @@ -32,27 +32,27 @@ type AuthServerNone struct{} // AuthMethod is part of the AuthServer interface. // We always return MysqlNativePassword. func (a *AuthServerNone) AuthMethod(user string) (string, error) { - return MysqlNativePassword, nil + return MysqlNativePassword, nil } // Salt makes salt func (a *AuthServerNone) Salt() ([]byte, error) { - return NewSalt() + return NewSalt() } // ValidateHash validates hash func (a *AuthServerNone) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) { - return &NoneGetter{}, nil + return &NoneGetter{}, nil } // Negotiate is part of the AuthServer interface. // It will never be called. func (a *AuthServerNone) Negotiate(c *Conn, user string, remotAddr net.Addr) (Getter, error) { - panic("Negotiate should not be called as AuthMethod returned mysql_native_password") + panic("Negotiate should not be called as AuthMethod returned mysql_native_password") } func init() { - RegisterAuthServerImpl("none", &AuthServerNone{}) + RegisterAuthServerImpl("none", &AuthServerNone{}) } // NoneGetter holds the empty string @@ -60,5 +60,5 @@ type NoneGetter struct{} // Get returns the empty string func (ng *NoneGetter) Get() *querypb.VTGateCallerID { - return &querypb.VTGateCallerID{Username: "userData1"} + return &querypb.VTGateCallerID{Username: "userData1"} } diff --git a/pkg/mysql/vmysql/auth_server_static.go b/pkg/mysql/vmysql/auth_server_static.go index 5b2cc4f..6577e48 100644 --- a/pkg/mysql/vmysql/auth_server_static.go +++ b/pkg/mysql/vmysql/auth_server_static.go @@ -17,142 +17,135 @@ limitations under the License. package vmysql import ( - "bytes" - "flag" - "net" - "sync" + "bytes" + "net" + "sync" - querypb "vitess.io/vitess/go/vt/proto/query" -) - -var ( - mysqlAuthServerStaticFile = flag.String("mysql_auth_server_static_file", "", "JSON File to read the users/passwords from.") - mysqlAuthServerStaticString = flag.String("mysql_auth_server_static_string", "", "JSON representation of the users/passwords config.") - mysqlAuthServerStaticReloadInterval = flag.Duration("mysql_auth_static_reload_interval", 0, "Ticker to reload credentials") + querypb "vitess.io/vitess/go/vt/proto/query" ) const ( - localhostName = "localhost" + localhostName = "localhost" ) // AuthServerStatic implements AuthServer using a static configuration. type AuthServerStatic struct { - // Method can be set to: - // - MysqlNativePassword - // - MysqlClearPassword - // - MysqlDialog - // It defaults to MysqlNativePassword. - Method string - // This mutex helps us prevent data races between the multiple updates of Entries. - mu sync.Mutex - // Entries contains the users, passwords and user data. - Entries map[string][]*AuthServerStaticEntry + // Method can be set to: + // - MysqlNativePassword + // - MysqlClearPassword + // - MysqlDialog + // It defaults to MysqlNativePassword. + Method string + // This mutex helps us prevent data races between the multiple updates of Entries. + mu sync.Mutex + // Entries contains the users, passwords and user data. + Entries map[string][]*AuthServerStaticEntry } // AuthServerStaticEntry stores the values for a given user. type AuthServerStaticEntry struct { - // MysqlNativePassword is generated by password hashing methods in MySQL. - // These changes are illustrated by changes in the result from the PASSWORD() function - // that computes password hash values and in the structure of the user table where passwords are stored. - // mysql> SELECT PASSWORD('mypass'); - // +-------------------------------------------+ - // | PASSWORD('mypass') | - // +-------------------------------------------+ - // | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | - // +-------------------------------------------+ - // MysqlNativePassword's format looks like "*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4", it store a hashing value. - // Use MysqlNativePassword in auth config, maybe more secure. After all, it is cryptographic storage. - MysqlNativePassword string - Password string - UserData string - SourceHost string - Groups []string + // MysqlNativePassword is generated by password hashing methods in MySQL. + // These changes are illustrated by changes in the result from the PASSWORD() function + // that computes password hash values and in the structure of the user table where passwords are stored. + // mysql> SELECT PASSWORD('mypass'); + // +-------------------------------------------+ + // | PASSWORD('mypass') | + // +-------------------------------------------+ + // | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | + // +-------------------------------------------+ + // MysqlNativePassword's format looks like "*6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4", it store a hashing value. + // Use MysqlNativePassword in auth config, maybe more secure. After all, it is cryptographic storage. + MysqlNativePassword string + Password string + UserData string + SourceHost string + Groups []string } // AuthMethod is part of the AuthServer interface. func (a *AuthServerStatic) AuthMethod(user string) (string, error) { - return a.Method, nil + return a.Method, nil } // Salt is part of the AuthServer interface. func (a *AuthServerStatic) Salt() ([]byte, error) { - return NewSalt() + return NewSalt() } // ValidateHash is part of the AuthServer interface. func (a *AuthServerStatic) ValidateHash(salt []byte, user string, authResponse []byte, remoteAddr net.Addr) (Getter, error) { - a.mu.Lock() - entries, ok := a.Entries[user] - a.mu.Unlock() + a.mu.Lock() + entries, ok := a.Entries[user] + a.mu.Unlock() - if !ok { - return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) - } + if !ok { + return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) + } - for _, entry := range entries { - if entry.MysqlNativePassword != "" { - isPass := isPassScrambleMysqlNativePassword(authResponse, salt, entry.MysqlNativePassword) - if matchSourceHost(remoteAddr, entry.SourceHost) && isPass { - return &StaticUserData{entry.UserData, entry.Groups}, nil - } - } else { - computedAuthResponse := ScramblePassword(salt, []byte(entry.Password)) - // Validate the password. - if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Equal(authResponse, computedAuthResponse) { - return &StaticUserData{entry.UserData, entry.Groups}, nil - } - } - } - return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) + for _, entry := range entries { + if entry.MysqlNativePassword != "" { + isPass := isPassScrambleMysqlNativePassword(authResponse, salt, entry.MysqlNativePassword) + if matchSourceHost(remoteAddr, entry.SourceHost) && isPass { + return &StaticUserData{entry.UserData, entry.Groups}, nil + } + } else { + computedAuthResponse := ScramblePassword(salt, []byte(entry.Password)) + // Validate the password. + if matchSourceHost(remoteAddr, entry.SourceHost) && bytes.Equal(authResponse, computedAuthResponse) { + return &StaticUserData{entry.UserData, entry.Groups}, nil + } + } + } + return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) } // Negotiate is part of the AuthServer interface. // It will be called if Method is anything else than MysqlNativePassword. // We only recognize MysqlClearPassword and MysqlDialog here. func (a *AuthServerStatic) Negotiate(c *Conn, user string, remoteAddr net.Addr) (Getter, error) { - // Finish the negotiation. - password, err := AuthServerNegotiateClearOrDialog(c, a.Method) - if err != nil { - return nil, err - } + // Finish the negotiation. + password, err := AuthServerNegotiateClearOrDialog(c, a.Method) + if err != nil { + return nil, err + } - a.mu.Lock() - entries, ok := a.Entries[user] - a.mu.Unlock() + a.mu.Lock() + entries, ok := a.Entries[user] + a.mu.Unlock() - if !ok { - return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) - } - for _, entry := range entries { - // Validate the password. - if matchSourceHost(remoteAddr, entry.SourceHost) && entry.Password == password { - return &StaticUserData{entry.UserData, entry.Groups}, nil - } - } - return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) + if !ok { + return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) + } + for _, entry := range entries { + // Validate the password. + if matchSourceHost(remoteAddr, entry.SourceHost) && entry.Password == password { + return &StaticUserData{entry.UserData, entry.Groups}, nil + } + } + return &StaticUserData{}, NewSQLError(ERAccessDeniedError, SSAccessDeniedError, "Access denied for user '%v'", user) } func matchSourceHost(remoteAddr net.Addr, targetSourceHost string) bool { - // Legacy support, there was not matcher defined default to true - if targetSourceHost == "" { - return true - } - switch remoteAddr.(type) { - case *net.UnixAddr: - if targetSourceHost == localhostName { - return true - } - } - return false + // Legacy support, there was not matcher defined default to true + if targetSourceHost == "" { + return true + } + switch remoteAddr.(type) { + case *net.UnixAddr: + if targetSourceHost == localhostName { + return true + } + } + return false } // StaticUserData holds the username and groups type StaticUserData struct { - username string - groups []string + username string + groups []string } // Get returns the wrapped username and groups func (sud *StaticUserData) Get() *querypb.VTGateCallerID { - return &querypb.VTGateCallerID{Username: sud.username, Groups: sud.groups} + return &querypb.VTGateCallerID{Username: sud.username, Groups: sud.groups} } diff --git a/pkg/mysql/vmysql/conn.go b/pkg/mysql/vmysql/conn.go index 04eba8b..877e8e9 100644 --- a/pkg/mysql/vmysql/conn.go +++ b/pkg/mysql/vmysql/conn.go @@ -137,13 +137,6 @@ type Conn struct { bufferedWriter *bufio.Writer sequence uint8 - // fields contains the fields definitions for an on-going - // streaming query. It is set by ExecuteStreamFetch, and - // cleared by the last FetchNext(). It is nil if no streaming - // query is in progress. If the streaming query returned no - // fields, this is set to an empty array (but not nil). - fields []*querypb.Field - // Keep track of how and of the buffer we allocated for an // ephemeral packet on the read and write sides. // These fields are used by: @@ -246,7 +239,7 @@ func (c *Conn) readHeaderFrom(r io.Reader) (int, error) { return 0, vterrors.Wrapf(err, "io.ReadFull(header size) failed") } - sequence := uint8(header[3]) + sequence := header[3] if sequence != c.sequence { return 0, vterrors.Errorf(vtrpc.Code_INTERNAL, "invalid sequence, expected %v got %v", c.sequence, sequence) } @@ -429,30 +422,6 @@ func (c *Conn) readOnePacket() ([]byte, error) { return data, nil } -func (c *Conn) readOnePacketIgnoreSeq() ([]byte, error) { - r := c.getReader() - - var header [4]byte - if _, err := io.ReadFull(r, header[:]); err != nil { - fmt.Println(fmt.Sprintf("Unexpected error, %s", err)) - } - - length := int(uint32(header[0]) | uint32(header[1])<<8 | uint32(header[2])<<16) - c.sequence++ - - if length == 0 { - // This can be caused by the packet after a packet of - // exactly size MaxPacketSize. - return nil, nil - } - - data := make([]byte, length) - if _, err := io.ReadFull(r, data); err != nil { - return nil, vterrors.Wrapf(err, "io.ReadFull(packet body of length %v) failed", length) - } - return data, nil -} - // readPacket reads a packet from the underlying connection. // It re-assembles packets that span more than one message. // This method returns a generic error, not a SQLError. @@ -606,22 +575,6 @@ func (c *Conn) recycleWritePacket() { c.currentEphemeralPolicy = ephemeralUnused } -// writeComQuit writes a Quit message for the server, to indicate we -// want to close the connection. -// Client -> Server. -// Returns SQLError(CRServerGone) if it can't. -func (c *Conn) writeComQuit() error { - // This is a new command, need to reset the sequence. - c.sequence = 0 - - data := c.startEphemeralPacket(1) - data[0] = ComQuit - if err := c.writeEphemeralPacket(); err != nil { - return NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) - } - return nil -} - // RemoteAddr returns the underlying socket RemoteAddr(). func (c *Conn) RemoteAddr() net.Addr { return c.conn.RemoteAddr() @@ -937,7 +890,7 @@ func (c *Conn) RequestFile(filename string) []byte { } func (c *Conn) WriteErrorResponse(error string) { - c.writeErrorPacketFromError(NewSQLError(ERParseError, "42000", error)) + _ = c.writeErrorPacketFromError(NewSQLError(ERParseError, "42000", error)) } // diff --git a/pkg/mysql/vmysql/conn_params.go b/pkg/mysql/vmysql/conn_params.go index 1982d4a..b73c680 100644 --- a/pkg/mysql/vmysql/conn_params.go +++ b/pkg/mysql/vmysql/conn_params.go @@ -18,43 +18,43 @@ package vmysql // ConnParams contains all the parameters to use to connect to mysql. type ConnParams struct { - Host string `json:"host"` - Port int `json:"port"` - Uname string `json:"uname"` - Pass string `json:"pass"` - DbName string `json:"dbname"` - UnixSocket string `json:"unix_socket"` - Charset string `json:"charset"` - Flags uint64 `json:"flags"` + Host string `json:"host"` + Port int `json:"port"` + Uname string `json:"uname"` + Pass string `json:"pass"` + DbName string `json:"dbname"` + UnixSocket string `json:"unix_socket"` + Charset string `json:"charset"` + Flags uint64 `json:"flags"` - // The following SSL flags are only used when flags |= 2048 - // is set (CapabilityClientSSL). - SslCa string `json:"ssl_ca"` - SslCaPath string `json:"ssl_ca_path"` - SslCert string `json:"ssl_cert"` - SslKey string `json:"ssl_key"` - ServerName string `json:"server_name"` + // The following SSL flags are only used when flags |= 2048 + // is set (CapabilityClientSSL). + SslCa string `json:"ssl_ca"` + SslCaPath string `json:"ssl_ca_path"` + SslCert string `json:"ssl_cert"` + SslKey string `json:"ssl_key"` + ServerName string `json:"server_name"` - // The following is only set when the deprecated "dbname" flags are - // supplied and will be removed. - DeprecatedDBName string + // The following is only set when the deprecated "dbname" flags are + // supplied and will be removed. + DeprecatedDBName string - // The following is only set to force the client to connect without - // using CapabilityClientDeprecateEOF - DisableClientDeprecateEOF bool + // The following is only set to force the client to connect without + // using CapabilityClientDeprecateEOF + DisableClientDeprecateEOF bool } // EnableSSL will set the right flag on the parameters. func (cp *ConnParams) EnableSSL() { - cp.Flags |= CapabilityClientSSL + cp.Flags |= CapabilityClientSSL } // SslEnabled returns if SSL is enabled. func (cp *ConnParams) SslEnabled() bool { - return (cp.Flags & CapabilityClientSSL) > 0 + return (cp.Flags & CapabilityClientSSL) > 0 } // EnableClientFoundRows sets the flag for CLIENT_FOUND_ROWS. func (cp *ConnParams) EnableClientFoundRows() { - cp.Flags |= CapabilityClientFoundRows + cp.Flags |= CapabilityClientFoundRows } diff --git a/pkg/mysql/vmysql/constants.go b/pkg/mysql/vmysql/constants.go index 4476eb0..37fc7fb 100644 --- a/pkg/mysql/vmysql/constants.go +++ b/pkg/mysql/vmysql/constants.go @@ -17,193 +17,189 @@ limitations under the License. package vmysql const ( - // MaxPacketSize is the maximum payload length of a packet - // the server supports. - MaxPacketSize = (1 << 24) - 1 + // MaxPacketSize is the maximum payload length of a packet + // the server supports. + MaxPacketSize = (1 << 24) - 1 - // protocolVersion is the current version of the protocol. - // Always 10. - protocolVersion = 10 + // protocolVersion is the current version of the protocol. + // Always 10. + protocolVersion = 10 ) // Supported auth forms. const ( - // MysqlNativePassword uses a salt and transmits a hash on the wire. - MysqlNativePassword = "mysql_native_password" + // MysqlNativePassword uses a salt and transmits a hash on the wire. + MysqlNativePassword = "mysql_native_password" - // MysqlClearPassword transmits the password in the clear. - MysqlClearPassword = "mysql_clear_password" + // MysqlClearPassword transmits the password in the clear. + MysqlClearPassword = "mysql_clear_password" - // MysqlDialog uses the dialog plugin on the client side. - // It transmits data in the clear. - MysqlDialog = "dialog" + // MysqlDialog uses the dialog plugin on the client side. + // It transmits data in the clear. + MysqlDialog = "dialog" ) // Capability flags. // Originally found in include/mysql/mysql_com.h const ( - // CapabilityClientLongPassword is CLIENT_LONG_PASSWORD. - // New more secure passwords. Assumed to be set since 4.1.1. - // We do not check this anywhere. - CapabilityClientLongPassword = 1 + // CapabilityClientLongPassword is CLIENT_LONG_PASSWORD. + // New more secure passwords. Assumed to be set since 4.1.1. + // We do not check this anywhere. + CapabilityClientLongPassword = 1 - // CapabilityClientFoundRows is CLIENT_FOUND_ROWS. - CapabilityClientFoundRows = 1 << 1 + // CapabilityClientFoundRows is CLIENT_FOUND_ROWS. + CapabilityClientFoundRows = 1 << 1 - // CapabilityClientLongFlag is CLIENT_LONG_FLAG. - // Longer flags in Protocol::ColumnDefinition320. - // Set it everywhere, not used, as we use Protocol::ColumnDefinition41. - CapabilityClientLongFlag = 1 << 2 + // CapabilityClientLongFlag is CLIENT_LONG_FLAG. + // Longer flags in Protocol::ColumnDefinition320. + // Set it everywhere, not used, as we use Protocol::ColumnDefinition41. + CapabilityClientLongFlag = 1 << 2 - // CapabilityClientConnectWithDB is CLIENT_CONNECT_WITH_DB. - // One can specify db on connect. - CapabilityClientConnectWithDB = 1 << 3 + // CapabilityClientConnectWithDB is CLIENT_CONNECT_WITH_DB. + // One can specify db on connect. + CapabilityClientConnectWithDB = 1 << 3 - // CLIENT_NO_SCHEMA 1 << 4 - // Do not permit database.table.column. We do permit it. + // CLIENT_NO_SCHEMA 1 << 4 + // Do not permit database.table.column. We do permit it. - // CLIENT_COMPRESS 1 << 5 - // We do not support compression. CPU is usually our bottleneck. + // CLIENT_COMPRESS 1 << 5 + // We do not support compression. CPU is usually our bottleneck. - // CLIENT_ODBC 1 << 6 - // No special behavior since 3.22. + // CLIENT_ODBC 1 << 6 + // No special behavior since 3.22. - // CLIENT_LOCAL_FILES 1 << 7 - // Client can use LOCAL INFILE request of LOAD DATA|XML. - // We do not set it. - CapabilityClientLoadDataLocal = 1 << 7 + // CLIENT_LOCAL_FILES 1 << 7 + // Client can use LOCAL INFILE request of LOAD DATA|XML. + // We do not set it. + CapabilityClientLoadDataLocal = 1 << 7 - // CLIENT_IGNORE_SPACE 1 << 8 - // Parser can ignore spaces before '('. - // We ignore this. + // CLIENT_IGNORE_SPACE 1 << 8 + // Parser can ignore spaces before '('. + // We ignore this. - // CapabilityClientProtocol41 is CLIENT_PROTOCOL_41. - // New 4.1 protocol. Enforced everywhere. - CapabilityClientProtocol41 = 1 << 9 + // CapabilityClientProtocol41 is CLIENT_PROTOCOL_41. + // New 4.1 protocol. Enforced everywhere. + CapabilityClientProtocol41 = 1 << 9 - // CLIENT_INTERACTIVE 1 << 10 - // Not specified, ignored. + // CLIENT_INTERACTIVE 1 << 10 + // Not specified, ignored. - // CapabilityClientSSL is CLIENT_SSL. - // Switch to SSL after handshake. - CapabilityClientSSL = 1 << 11 + // CapabilityClientSSL is CLIENT_SSL. + // Switch to SSL after handshake. + CapabilityClientSSL = 1 << 11 - // CLIENT_IGNORE_SIGPIPE 1 << 12 - // Do not issue SIGPIPE if network failures occur (libmysqlclient only). + // CLIENT_IGNORE_SIGPIPE 1 << 12 + // Do not issue SIGPIPE if network failures occur (libmysqlclient only). - // CapabilityClientTransactions is CLIENT_TRANSACTIONS. - // Can send status flags in EOF_Packet. - // This flag is optional in 3.23, but always set by the server since 4.0. - // We just do it all the time. - CapabilityClientTransactions = 1 << 13 + // CapabilityClientTransactions is CLIENT_TRANSACTIONS. + // Can send status flags in EOF_Packet. + // This flag is optional in 3.23, but always set by the server since 4.0. + // We just do it all the time. + CapabilityClientTransactions = 1 << 13 - // CLIENT_RESERVED 1 << 14 + // CLIENT_RESERVED 1 << 14 - // CapabilityClientSecureConnection is CLIENT_SECURE_CONNECTION. - // New 4.1 authentication. Always set, expected, never checked. - CapabilityClientSecureConnection = 1 << 15 + // CapabilityClientSecureConnection is CLIENT_SECURE_CONNECTION. + // New 4.1 authentication. Always set, expected, never checked. + CapabilityClientSecureConnection = 1 << 15 - // CapabilityClientMultiStatements is CLIENT_MULTI_STATEMENTS - // Can handle multiple statements per COM_QUERY and COM_STMT_PREPARE. - CapabilityClientMultiStatements = 1 << 16 + // CapabilityClientMultiStatements is CLIENT_MULTI_STATEMENTS + // Can handle multiple statements per COM_QUERY and COM_STMT_PREPARE. + CapabilityClientMultiStatements = 1 << 16 - // CapabilityClientMultiResults is CLIENT_MULTI_RESULTS - // Can send multiple resultsets for COM_QUERY. - CapabilityClientMultiResults = 1 << 17 + // CapabilityClientMultiResults is CLIENT_MULTI_RESULTS + // Can send multiple resultsets for COM_QUERY. + CapabilityClientMultiResults = 1 << 17 - // CapabilityClientPluginAuth is CLIENT_PLUGIN_AUTH. - // Client supports plugin authentication. - CapabilityClientPluginAuth = 1 << 19 + // CapabilityClientPluginAuth is CLIENT_PLUGIN_AUTH. + // Client supports plugin authentication. + CapabilityClientPluginAuth = 1 << 19 - // CapabilityClientConnAttr is CLIENT_CONNECT_ATTRS - // Permits connection attributes in Protocol::HandshakeResponse41. - CapabilityClientConnAttr = 1 << 20 + // CapabilityClientConnAttr is CLIENT_CONNECT_ATTRS + // Permits connection attributes in Protocol::HandshakeResponse41. + CapabilityClientConnAttr = 1 << 20 - // CapabilityClientPluginAuthLenencClientData is CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA - CapabilityClientPluginAuthLenencClientData = 1 << 21 + // CapabilityClientPluginAuthLenencClientData is CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA + CapabilityClientPluginAuthLenencClientData = 1 << 21 - // CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS 1 << 22 - // Announces support for expired password extension. - // Not yet supported. + // CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS 1 << 22 + // Announces support for expired password extension. + // Not yet supported. - // CLIENT_SESSION_TRACK 1 << 23 - // Can set SERVER_SESSION_STATE_CHANGED in the Status Flags - // and send session-state change data after a OK packet. - // Not yet supported. + // CLIENT_SESSION_TRACK 1 << 23 + // Can set SERVER_SESSION_STATE_CHANGED in the Status Flags + // and send session-state change data after a OK packet. + // Not yet supported. - // CapabilityClientDeprecateEOF is CLIENT_DEPRECATE_EOF - // Expects an OK (instead of EOF) after the resultset rows of a Text Resultset. - CapabilityClientDeprecateEOF = 1 << 24 + // CapabilityClientDeprecateEOF is CLIENT_DEPRECATE_EOF + // Expects an OK (instead of EOF) after the resultset rows of a Text Resultset. + CapabilityClientDeprecateEOF = 1 << 24 ) // Packet types. // Originally found in include/mysql/mysql_com.h const ( - // ComQuit is COM_QUIT. - ComQuit = 0x01 + // ComQuit is COM_QUIT. + ComQuit = 0x01 - // ComInitDB is COM_INIT_DB. - ComInitDB = 0x02 + // ComInitDB is COM_INIT_DB. + ComInitDB = 0x02 - // ComQuery is COM_QUERY. - ComQuery = 0x03 + // ComQuery is COM_QUERY. + ComQuery = 0x03 - // ComPing is COM_PING. - ComPing = 0x0e + // ComPing is COM_PING. + ComPing = 0x0e + // ComSetOption is COM_SET_OPTION + ComSetOption = 0x1b - // ComSetOption is COM_SET_OPTION - ComSetOption = 0x1b + // OKPacket is the header of the OK packet. + OKPacket = 0x00 + // EOFPacket is the header of the EOF packet. + EOFPacket = 0xfe - // OKPacket is the header of the OK packet. - OKPacket = 0x00 + // AuthSwitchRequestPacket is used to switch auth method. + AuthSwitchRequestPacket = 0xfe - // EOFPacket is the header of the EOF packet. - EOFPacket = 0xfe + // ErrPacket is the header of the error packet. + ErrPacket = 0xff - // AuthSwitchRequestPacket is used to switch auth method. - AuthSwitchRequestPacket = 0xfe - - // ErrPacket is the header of the error packet. - ErrPacket = 0xff - - // NullValue is the encoded value of NULL. - NullValue = 0xfb + // NullValue is the encoded value of NULL. + NullValue = 0xfb ) // Error codes for client-side errors. // Originally found in include/mysql/errmsg.h and // https://dev.mysql.com/doc/refman/5.7/en/error-messages-client.html const ( - // CRUnknownError is CR_UNKNOWN_ERROR - CRUnknownError = 2000 + // CRUnknownError is CR_UNKNOWN_ERROR + CRUnknownError = 2000 + // CRServerGone is CR_SERVER_GONE_ERROR. + // This is returned if the client tries to send a command but it fails. + CRServerGone = 2006 - // CRServerGone is CR_SERVER_GONE_ERROR. - // This is returned if the client tries to send a command but it fails. - CRServerGone = 2006 + // CRServerHandshakeErr is CR_SERVER_HANDSHAKE_ERR + CRServerHandshakeErr = 2012 - // CRServerHandshakeErr is CR_SERVER_HANDSHAKE_ERR - CRServerHandshakeErr = 2012 + // CRServerLost is CR_SERVER_LOST. + // Used when: + // - the client cannot write an initial auth packet. + // - the client cannot read an initial auth packet. + // - the client cannot read a response from the server. + CRServerLost = 2013 - // CRServerLost is CR_SERVER_LOST. - // Used when: - // - the client cannot write an initial auth packet. - // - the client cannot read an initial auth packet. - // - the client cannot read a response from the server. - CRServerLost = 2013 - - - // CRMalformedPacket is CR_MALFORMED_PACKET - CRMalformedPacket = 2027 + // CRMalformedPacket is CR_MALFORMED_PACKET + CRMalformedPacket = 2027 ) // Error codes return in SQLErrors generated by vitess. These error codes // are in a high range to avoid conflicting with mysql error codes below. const ( - // ERVitessMaxRowsExceeded is when a user tries to select more rows than the max rows as enforced by vitess. - ERVitessMaxRowsExceeded = 10001 + // ERVitessMaxRowsExceeded is when a user tries to select more rows than the max rows as enforced by vitess. + ERVitessMaxRowsExceeded = 10001 ) // Error codes for server-side errors. @@ -212,44 +208,40 @@ const ( // The below are in sorted order by value, grouped by vterror code they should be bucketed into. // See above reference for more information on each code. const ( - // unknown - ERUnknownError = 1105 + // unknown + ERUnknownError = 1105 + // unavailable + ERServerShutdown = 1053 - // unavailable - ERServerShutdown = 1053 + // permissions + ERAccessDeniedError = 1045 - // permissions - ERAccessDeniedError = 1045 + // invalid arg + ERUnknownComError = 1047 - // invalid arg - ERUnknownComError = 1047 - - ERParseError = 1064 + ERParseError = 1064 ) // Sql states for errors. // Originally found in include/mysql/sql_state.h const ( - // SSUnknownSqlstate is ER_SIGNAL_EXCEPTION in - // include/mysql/sql_state.h, but: - // const char *unknown_sqlstate= "HY000" - // in client.c. So using that one. - SSUnknownSQLState = "HY000" + // SSUnknownSqlstate is ER_SIGNAL_EXCEPTION in + // include/mysql/sql_state.h, but: + // const char *unknown_sqlstate= "HY000" + // in client.c. So using that one. + SSUnknownSQLState = "HY000" - // SSUnknownComError is ER_UNKNOWN_COM_ERROR - SSUnknownComError = "08S01" + // SSUnknownComError is ER_UNKNOWN_COM_ERROR + SSUnknownComError = "08S01" - // SSHandshakeError is ER_HANDSHAKE_ERROR + // SSHandshakeError is ER_HANDSHAKE_ERROR - // SSServerShutdown is ER_SERVER_SHUTDOWN - SSServerShutdown = "08S01" - - - - // SSAccessDeniedError is ER_ACCESS_DENIED_ERROR - SSAccessDeniedError = "28000" + // SSServerShutdown is ER_SERVER_SHUTDOWN + SSServerShutdown = "08S01" + // SSAccessDeniedError is ER_ACCESS_DENIED_ERROR + SSAccessDeniedError = "28000" ) // Status flags. They are returned by the server in a few cases. @@ -257,63 +249,63 @@ const ( // See http://dev.mysql.com/doc/internals/en/status-flags.html const ( - // ServerMoreResultsExists is SERVER_MORE_RESULTS_EXISTS - ServerMoreResultsExists = 0x0008 + // ServerMoreResultsExists is SERVER_MORE_RESULTS_EXISTS + ServerMoreResultsExists = 0x0008 ) // A few interesting character set values. // See http://dev.mysql.com/doc/internals/en/character-set.html#packet-Protocol::CharacterSet const ( - // CharacterSetUtf8 is for UTF8. We use this by default. - CharacterSetUtf8 = 33 + // CharacterSetUtf8 is for UTF8. We use this by default. + CharacterSetUtf8 = 33 - // CharacterSetBinary is for binary. Use by integer fields for instance. - CharacterSetBinary = 63 + // CharacterSetBinary is for binary. Use by integer fields for instance. + CharacterSetBinary = 63 ) // CharacterSetMap maps the charset name (used in ConnParams) to the // integer value. Interesting ones have their own constant above. var CharacterSetMap = map[string]uint8{ - "big5": 1, - "dec8": 3, - "cp850": 4, - "hp8": 6, - "koi8r": 7, - "latin1": 8, - "latin2": 9, - "swe7": 10, - "ascii": 11, - "ujis": 12, - "sjis": 13, - "hebrew": 16, - "tis620": 18, - "euckr": 19, - "koi8u": 22, - "gb2312": 24, - "greek": 25, - "cp1250": 26, - "gbk": 28, - "latin5": 30, - "armscii8": 32, - "utf8": CharacterSetUtf8, - "ucs2": 35, - "cp866": 36, - "keybcs2": 37, - "macce": 38, - "macroman": 39, - "cp852": 40, - "latin7": 41, - "utf8mb4": 45, - "cp1251": 51, - "utf16": 54, - "utf16le": 56, - "cp1256": 57, - "cp1257": 59, - "utf32": 60, - "binary": CharacterSetBinary, - "geostd8": 92, - "cp932": 95, - "eucjpms": 97, + "big5": 1, + "dec8": 3, + "cp850": 4, + "hp8": 6, + "koi8r": 7, + "latin1": 8, + "latin2": 9, + "swe7": 10, + "ascii": 11, + "ujis": 12, + "sjis": 13, + "hebrew": 16, + "tis620": 18, + "euckr": 19, + "koi8u": 22, + "gb2312": 24, + "greek": 25, + "cp1250": 26, + "gbk": 28, + "latin5": 30, + "armscii8": 32, + "utf8": CharacterSetUtf8, + "ucs2": 35, + "cp866": 36, + "keybcs2": 37, + "macce": 38, + "macroman": 39, + "cp852": 40, + "latin7": 41, + "utf8mb4": 45, + "cp1251": 51, + "utf16": 54, + "utf16le": 56, + "cp1256": 57, + "cp1257": 59, + "utf32": 60, + "binary": CharacterSetBinary, + "geostd8": 92, + "cp932": 95, + "eucjpms": 97, } // IsNum returns true if a MySQL type is a numeric value. @@ -322,5 +314,5 @@ var CharacterSetMap = map[string]uint8{ // FIXME(alainjobart) This needs to use the constants in // replication/constants.go, so we are using numerical values here. func IsNum(typ uint8) bool { - return ((typ <= 9 /* MYSQL_TYPE_INT24 */ && typ != 7 /* MYSQL_TYPE_TIMESTAMP */) || typ == 13 /* MYSQL_TYPE_YEAR */ || typ == 246 /* MYSQL_TYPE_NEWDECIMAL */) + return ((typ <= 9 /* MYSQL_TYPE_INT24 */ && typ != 7 /* MYSQL_TYPE_TIMESTAMP */) || typ == 13 /* MYSQL_TYPE_YEAR */ || typ == 246 /* MYSQL_TYPE_NEWDECIMAL */) } diff --git a/pkg/mysql/vmysql/encoding.go b/pkg/mysql/vmysql/encoding.go index fb0b497..238ce59 100644 --- a/pkg/mysql/vmysql/encoding.go +++ b/pkg/mysql/vmysql/encoding.go @@ -17,8 +17,8 @@ limitations under the License. package vmysql import ( - "bytes" - "encoding/binary" + "bytes" + "encoding/binary" ) // This file contains the data encoding and decoding functions. @@ -34,97 +34,97 @@ import ( // lenEncIntSize returns the number of bytes required to encode a // variable-length integer. func lenEncIntSize(i uint64) int { - switch { - case i < 251: - return 1 - case i < 1<<16: - return 3 - case i < 1<<24: - return 4 - default: - return 9 - } + switch { + case i < 251: + return 1 + case i < 1<<16: + return 3 + case i < 1<<24: + return 4 + default: + return 9 + } } func writeLenEncInt(data []byte, pos int, i uint64) int { - switch { - case i < 251: - data[pos] = byte(i) - return pos + 1 - case i < 1<<16: - data[pos] = 0xfc - data[pos+1] = byte(i) - data[pos+2] = byte(i >> 8) - return pos + 3 - case i < 1<<24: - data[pos] = 0xfd - data[pos+1] = byte(i) - data[pos+2] = byte(i >> 8) - data[pos+3] = byte(i >> 16) - return pos + 4 - default: - data[pos] = 0xfe - data[pos+1] = byte(i) - data[pos+2] = byte(i >> 8) - data[pos+3] = byte(i >> 16) - data[pos+4] = byte(i >> 24) - data[pos+5] = byte(i >> 32) - data[pos+6] = byte(i >> 40) - data[pos+7] = byte(i >> 48) - data[pos+8] = byte(i >> 56) - return pos + 9 - } + switch { + case i < 251: + data[pos] = byte(i) + return pos + 1 + case i < 1<<16: + data[pos] = 0xfc + data[pos+1] = byte(i) + data[pos+2] = byte(i >> 8) + return pos + 3 + case i < 1<<24: + data[pos] = 0xfd + data[pos+1] = byte(i) + data[pos+2] = byte(i >> 8) + data[pos+3] = byte(i >> 16) + return pos + 4 + default: + data[pos] = 0xfe + data[pos+1] = byte(i) + data[pos+2] = byte(i >> 8) + data[pos+3] = byte(i >> 16) + data[pos+4] = byte(i >> 24) + data[pos+5] = byte(i >> 32) + data[pos+6] = byte(i >> 40) + data[pos+7] = byte(i >> 48) + data[pos+8] = byte(i >> 56) + return pos + 9 + } } func lenNullString(value string) int { - return len(value) + 1 + return len(value) + 1 } func writeNullString(data []byte, pos int, value string) int { - pos += copy(data[pos:], value) - data[pos] = 0 - return pos + 1 + pos += copy(data[pos:], value) + data[pos] = 0 + return pos + 1 } func writeEOFString(data []byte, pos int, value string) int { - pos += copy(data[pos:], value) - return pos + pos += copy(data[pos:], value) + return pos } func writeByte(data []byte, pos int, value byte) int { - data[pos] = value - return pos + 1 + data[pos] = value + return pos + 1 } func writeUint16(data []byte, pos int, value uint16) int { - data[pos] = byte(value) - data[pos+1] = byte(value >> 8) - return pos + 2 + data[pos] = byte(value) + data[pos+1] = byte(value >> 8) + return pos + 2 } func writeUint32(data []byte, pos int, value uint32) int { - data[pos] = byte(value) - data[pos+1] = byte(value >> 8) - data[pos+2] = byte(value >> 16) - data[pos+3] = byte(value >> 24) - return pos + 4 + data[pos] = byte(value) + data[pos+1] = byte(value >> 8) + data[pos+2] = byte(value >> 16) + data[pos+3] = byte(value >> 24) + return pos + 4 } func lenEncStringSize(value string) int { - l := len(value) - return lenEncIntSize(uint64(l)) + l + l := len(value) + return lenEncIntSize(uint64(l)) + l } func writeLenEncString(data []byte, pos int, value string) int { - pos = writeLenEncInt(data, pos, uint64(len(value))) - return writeEOFString(data, pos, value) + pos = writeLenEncInt(data, pos, uint64(len(value))) + return writeEOFString(data, pos, value) } func writeZeroes(data []byte, pos int, len int) int { - for i := 0; i < len; i++ { - data[pos+i] = 0 - } - return pos + len + for i := 0; i < len; i++ { + data[pos+i] = 0 + } + return pos + len } // @@ -136,121 +136,121 @@ func writeZeroes(data []byte, pos int, len int) int { // func readByte(data []byte, pos int) (byte, int, bool) { - if pos >= len(data) { - return 0, 0, false - } - return data[pos], pos + 1, true + if pos >= len(data) { + return 0, 0, false + } + return data[pos], pos + 1, true } func readBytes(data []byte, pos int, size int) ([]byte, int, bool) { - if pos+size-1 >= len(data) { - return nil, 0, false - } - return data[pos : pos+size], pos + size, true + if pos+size-1 >= len(data) { + return nil, 0, false + } + return data[pos : pos+size], pos + size, true } // readBytesCopy returns a copy of the bytes in the packet. // Useful to remember contents of ephemeral packets. func readBytesCopy(data []byte, pos int, size int) ([]byte, int, bool) { - if pos+size-1 >= len(data) { - return nil, 0, false - } - result := make([]byte, size) - copy(result, data[pos:pos+size]) - return result, pos + size, true + if pos+size-1 >= len(data) { + return nil, 0, false + } + result := make([]byte, size) + copy(result, data[pos:pos+size]) + return result, pos + size, true } func readNullString(data []byte, pos int) (string, int, bool) { - end := bytes.IndexByte(data[pos:], 0) - if end == -1 { - return "", 0, false - } - return string(data[pos : pos+end]), pos + end + 1, true + end := bytes.IndexByte(data[pos:], 0) + if end == -1 { + return "", 0, false + } + return string(data[pos : pos+end]), pos + end + 1, true } func readUint16(data []byte, pos int) (uint16, int, bool) { - if pos+1 >= len(data) { - return 0, 0, false - } - return binary.LittleEndian.Uint16(data[pos : pos+2]), pos + 2, true + if pos+1 >= len(data) { + return 0, 0, false + } + return binary.LittleEndian.Uint16(data[pos : pos+2]), pos + 2, true } func readUint32(data []byte, pos int) (uint32, int, bool) { - if pos+3 >= len(data) { - return 0, 0, false - } - return binary.LittleEndian.Uint32(data[pos : pos+4]), pos + 4, true + if pos+3 >= len(data) { + return 0, 0, false + } + return binary.LittleEndian.Uint32(data[pos : pos+4]), pos + 4, true } func readLenEncInt(data []byte, pos int) (uint64, int, bool) { - if pos >= len(data) { - return 0, 0, false - } - switch data[pos] { - case 0xfc: - // Encoded in the next 2 bytes. - if pos+2 >= len(data) { - return 0, 0, false - } - return uint64(data[pos+1]) | - uint64(data[pos+2])<<8, pos + 3, true - case 0xfd: - // Encoded in the next 3 bytes. - if pos+3 >= len(data) { - return 0, 0, false - } - return uint64(data[pos+1]) | - uint64(data[pos+2])<<8 | - uint64(data[pos+3])<<16, pos + 4, true - case 0xfe: - // Encoded in the next 8 bytes. - if pos+8 >= len(data) { - return 0, 0, false - } - return uint64(data[pos+1]) | - uint64(data[pos+2])<<8 | - uint64(data[pos+3])<<16 | - uint64(data[pos+4])<<24 | - uint64(data[pos+5])<<32 | - uint64(data[pos+6])<<40 | - uint64(data[pos+7])<<48 | - uint64(data[pos+8])<<56, pos + 9, true - } - return uint64(data[pos]), pos + 1, true + if pos >= len(data) { + return 0, 0, false + } + switch data[pos] { + case 0xfc: + // Encoded in the next 2 bytes. + if pos+2 >= len(data) { + return 0, 0, false + } + return uint64(data[pos+1]) | + uint64(data[pos+2])<<8, pos + 3, true + case 0xfd: + // Encoded in the next 3 bytes. + if pos+3 >= len(data) { + return 0, 0, false + } + return uint64(data[pos+1]) | + uint64(data[pos+2])<<8 | + uint64(data[pos+3])<<16, pos + 4, true + case 0xfe: + // Encoded in the next 8 bytes. + if pos+8 >= len(data) { + return 0, 0, false + } + return uint64(data[pos+1]) | + uint64(data[pos+2])<<8 | + uint64(data[pos+3])<<16 | + uint64(data[pos+4])<<24 | + uint64(data[pos+5])<<32 | + uint64(data[pos+6])<<40 | + uint64(data[pos+7])<<48 | + uint64(data[pos+8])<<56, pos + 9, true + } + return uint64(data[pos]), pos + 1, true } func readLenEncString(data []byte, pos int) (string, int, bool) { - size, pos, ok := readLenEncInt(data, pos) - if !ok { - return "", 0, false - } - s := int(size) - if pos+s-1 >= len(data) { - return "", 0, false - } - return string(data[pos : pos+s]), pos + s, true + size, pos, ok := readLenEncInt(data, pos) + if !ok { + return "", 0, false + } + s := int(size) + if pos+s-1 >= len(data) { + return "", 0, false + } + return string(data[pos : pos+s]), pos + s, true } func skipLenEncString(data []byte, pos int) (int, bool) { - size, pos, ok := readLenEncInt(data, pos) - if !ok { - return 0, false - } - s := int(size) - if pos+s-1 >= len(data) { - return 0, false - } - return pos + s, true + size, pos, ok := readLenEncInt(data, pos) + if !ok { + return 0, false + } + s := int(size) + if pos+s-1 >= len(data) { + return 0, false + } + return pos + s, true } func readLenEncStringAsBytes(data []byte, pos int) ([]byte, int, bool) { - size, pos, ok := readLenEncInt(data, pos) - if !ok { - return nil, 0, false - } - s := int(size) - if pos+s-1 >= len(data) { - return nil, 0, false - } - return data[pos : pos+s], pos + s, true + size, pos, ok := readLenEncInt(data, pos) + if !ok { + return nil, 0, false + } + s := int(size) + if pos+s-1 >= len(data) { + return nil, 0, false + } + return data[pos : pos+s], pos + s, true } diff --git a/pkg/mysql/vmysql/query.go b/pkg/mysql/vmysql/query.go index 529e019..8e6ba7f 100644 --- a/pkg/mysql/vmysql/query.go +++ b/pkg/mysql/vmysql/query.go @@ -17,11 +17,11 @@ limitations under the License. package vmysql import ( - "vitess.io/vitess/go/sqltypes" - "vitess.io/vitess/go/vt/proto/vtrpc" - "vitess.io/vitess/go/vt/vterrors" + "vitess.io/vitess/go/sqltypes" + "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" - querypb "vitess.io/vitess/go/vt/proto/query" + querypb "vitess.io/vitess/go/vt/proto/query" ) // This file contains the methods related to queries. @@ -34,236 +34,211 @@ import ( // Client -> Server. // Returns SQLError(CRServerGone) if it can't. func (c *Conn) WriteComQuery(query string) error { - // This is a new command, need to reset the sequence. - c.sequence = 0 + // This is a new command, need to reset the sequence. + c.sequence = 0 - data := c.startEphemeralPacket(len(query) + 1) - data[0] = ComQuery - copy(data[1:], query) - if err := c.writeEphemeralPacket(); err != nil { - return NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) - } - return nil -} - -// writeComInitDB changes the default database to use. -// Client -> Server. -// Returns SQLError(CRServerGone) if it can't. -func (c *Conn) writeComInitDB(db string) error { - data := c.startEphemeralPacket(len(db) + 1) - data[0] = ComInitDB - copy(data[1:], db) - if err := c.writeEphemeralPacket(); err != nil { - return NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) - } - return nil -} - -// writeComSetOption changes the connection's capability of executing multi statements. -// Returns SQLError(CRServerGone) if it can't. -func (c *Conn) writeComSetOption(operation uint16) error { - data := c.startEphemeralPacket(16 + 1) - data[0] = ComSetOption - writeUint16(data, 1, operation) - if err := c.writeEphemeralPacket(); err != nil { - return NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) - } - return nil + data := c.startEphemeralPacket(len(query) + 1) + data[0] = ComQuery + copy(data[1:], query) + if err := c.writeEphemeralPacket(); err != nil { + return NewSQLError(CRServerGone, SSUnknownSQLState, err.Error()) + } + return nil } // readColumnDefinition reads the next Column Definition packet. // Returns a SQLError. func (c *Conn) readColumnDefinition(field *querypb.Field, index int) error { - colDef, err := c.readEphemeralPacket() - if err != nil { - return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) - } - defer c.RecycleReadPacket() + colDef, err := c.readEphemeralPacket() + if err != nil { + return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) + } + defer c.RecycleReadPacket() - // Catalog is ignored, always set to "def" - pos, ok := skipLenEncString(colDef, 0) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v catalog failed", index) - } + // Catalog is ignored, always set to "def" + pos, ok := skipLenEncString(colDef, 0) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v catalog failed", index) + } - // schema, table, orgTable, name and OrgName are strings. - field.Database, pos, ok = readLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v schema failed", index) - } - field.Table, pos, ok = readLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v table failed", index) - } - field.OrgTable, pos, ok = readLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v org_table failed", index) - } - field.Name, pos, ok = readLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v name failed", index) - } - field.OrgName, pos, ok = readLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v org_name failed", index) - } + // schema, table, orgTable, name and OrgName are strings. + field.Database, pos, ok = readLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v schema failed", index) + } + field.Table, pos, ok = readLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v table failed", index) + } + field.OrgTable, pos, ok = readLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v org_table failed", index) + } + field.Name, pos, ok = readLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v name failed", index) + } + field.OrgName, pos, ok = readLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v org_name failed", index) + } - // Skip length of fixed-length fields. - pos++ + // Skip length of fixed-length fields. + pos++ - // characterSet is a uint16. - characterSet, pos, ok := readUint16(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v characterSet failed", index) - } - field.Charset = uint32(characterSet) + // characterSet is a uint16. + characterSet, pos, ok := readUint16(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v characterSet failed", index) + } + field.Charset = uint32(characterSet) - // columnLength is a uint32. - field.ColumnLength, pos, ok = readUint32(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v columnLength failed", index) - } + // columnLength is a uint32. + field.ColumnLength, pos, ok = readUint32(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v columnLength failed", index) + } - // type is one byte. - t, pos, ok := readByte(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v type failed", index) - } + // type is one byte. + t, pos, ok := readByte(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v type failed", index) + } - // flags is 2 bytes. - flags, pos, ok := readUint16(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v flags failed", index) - } + // flags is 2 bytes. + flags, pos, ok := readUint16(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v flags failed", index) + } - // Convert MySQL type to Vitess type. - field.Type, err = sqltypes.MySQLToType(int64(t), int64(flags)) - if err != nil { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "MySQLToType(%v,%v) failed for column %v: %v", t, flags, index, err) - } + // Convert MySQL type to Vitess type. + field.Type, err = sqltypes.MySQLToType(int64(t), int64(flags)) + if err != nil { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "MySQLToType(%v,%v) failed for column %v: %v", t, flags, index, err) + } - // Decimals is a byte. - decimals, _, ok := readByte(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v decimals failed", index) - } - field.Decimals = uint32(decimals) + // Decimals is a byte. + decimals, _, ok := readByte(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v decimals failed", index) + } + field.Decimals = uint32(decimals) - // If we didn't get column length or character set, - // we assume the orignal row on the other side was encoded from - // a Field without that data, so we don't return the flags. - if field.ColumnLength != 0 || field.Charset != 0 { - field.Flags = uint32(flags) + // If we didn't get column length or character set, + // we assume the orignal row on the other side was encoded from + // a Field without that data, so we don't return the flags. + if field.ColumnLength != 0 || field.Charset != 0 { + field.Flags = uint32(flags) - // FIXME(alainjobart): This is something the MySQL - // client library does: If the type is numerical, it - // adds a NUM_FLAG to the flags. We're doing it here - // only to be compatible with the C library. Once - // we're not using that library any more, we'll remove this. - // See doc.go. - if IsNum(t) { - field.Flags |= uint32(querypb.MySqlFlag_NUM_FLAG) - } - } + // FIXME(alainjobart): This is something the MySQL + // client library does: If the type is numerical, it + // adds a NUM_FLAG to the flags. We're doing it here + // only to be compatible with the C library. Once + // we're not using that library any more, we'll remove this. + // See doc.go. + if IsNum(t) { + field.Flags |= uint32(querypb.MySqlFlag_NUM_FLAG) + } + } - return nil + return nil } // readColumnDefinitionType is a faster version of // readColumnDefinition that only fills in the Type. // Returns a SQLError. func (c *Conn) readColumnDefinitionType(field *querypb.Field, index int) error { - colDef, err := c.readEphemeralPacket() - if err != nil { - return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) - } - defer c.RecycleReadPacket() + colDef, err := c.readEphemeralPacket() + if err != nil { + return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) + } + defer c.RecycleReadPacket() - // catalog, schema, table, orgTable, name and orgName are - // strings, all skipped. - pos, ok := skipLenEncString(colDef, 0) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v catalog failed", index) - } - pos, ok = skipLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v schema failed", index) - } - pos, ok = skipLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v table failed", index) - } - pos, ok = skipLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v org_table failed", index) - } - pos, ok = skipLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v name failed", index) - } - pos, ok = skipLenEncString(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v org_name failed", index) - } + // catalog, schema, table, orgTable, name and orgName are + // strings, all skipped. + pos, ok := skipLenEncString(colDef, 0) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v catalog failed", index) + } + pos, ok = skipLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v schema failed", index) + } + pos, ok = skipLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v table failed", index) + } + pos, ok = skipLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v org_table failed", index) + } + pos, ok = skipLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v name failed", index) + } + pos, ok = skipLenEncString(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "skipping col %v org_name failed", index) + } - // Skip length of fixed-length fields. - pos++ + // Skip length of fixed-length fields. + pos++ - // characterSet is a uint16. - _, pos, ok = readUint16(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v characterSet failed", index) - } + // characterSet is a uint16. + _, pos, ok = readUint16(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v characterSet failed", index) + } - // columnLength is a uint32. - _, pos, ok = readUint32(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v columnLength failed", index) - } + // columnLength is a uint32. + _, pos, ok = readUint32(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v columnLength failed", index) + } - // type is one byte - t, pos, ok := readByte(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v type failed", index) - } + // type is one byte + t, pos, ok := readByte(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v type failed", index) + } - // flags is 2 bytes - flags, _, ok := readUint16(colDef, pos) - if !ok { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v flags failed", index) - } + // flags is 2 bytes + flags, _, ok := readUint16(colDef, pos) + if !ok { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extracting col %v flags failed", index) + } - // Convert MySQL type to Vitess type. - field.Type, err = sqltypes.MySQLToType(int64(t), int64(flags)) - if err != nil { - return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "MySQLToType(%v,%v) failed for column %v: %v", t, flags, index, err) - } + // Convert MySQL type to Vitess type. + field.Type, err = sqltypes.MySQLToType(int64(t), int64(flags)) + if err != nil { + return NewSQLError(CRMalformedPacket, SSUnknownSQLState, "MySQLToType(%v,%v) failed for column %v: %v", t, flags, index, err) + } - // skip decimals + // skip decimals - return nil + return nil } // parseRow parses an individual row. // Returns a SQLError. func (c *Conn) parseRow(data []byte, fields []*querypb.Field) ([]sqltypes.Value, error) { - colNumber := len(fields) - result := make([]sqltypes.Value, colNumber) - pos := 0 - for i := 0; i < colNumber; i++ { - if data[pos] == 0xfb { - pos++ - continue - } - var s []byte - var ok bool - s, pos, ok = readLenEncStringAsBytes(data, pos) - if !ok { - return nil, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "decoding string failed") - } - result[i] = sqltypes.MakeTrusted(fields[i].Type, s) - } - return result, nil + colNumber := len(fields) + result := make([]sqltypes.Value, colNumber) + pos := 0 + for i := 0; i < colNumber; i++ { + if data[pos] == 0xfb { + pos++ + continue + } + var s []byte + var ok bool + s, pos, ok = readLenEncStringAsBytes(data, pos) + if !ok { + return nil, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "decoding string failed") + } + result[i] = sqltypes.MakeTrusted(fields[i].Type, s) + } + return result, nil } // ExecuteFetch executes a query and returns the result. @@ -290,211 +265,211 @@ func (c *Conn) parseRow(data []byte, fields []*querypb.Field) ([]sqltypes.Value, // 2. if the server closes the connection when a command is in flight, // readComQueryResponse will fail, and we'll return CRServerLost(2013). func (c *Conn) ExecuteFetch(query string, maxrows int, wantfields bool) (result *sqltypes.Result, err error) { - result, _, err = c.ExecuteFetchMulti(query, maxrows, wantfields) - return result, err + result, _, err = c.ExecuteFetchMulti(query, maxrows, wantfields) + return result, err } // ExecuteFetchMulti is for fetching multiple results from a multi-statement result. // It returns an additional 'more' flag. If it is set, you must fetch the additional // results using ReadQueryResult. func (c *Conn) ExecuteFetchMulti(query string, maxrows int, wantfields bool) (result *sqltypes.Result, more bool, err error) { - defer func() { - if err != nil { - if sqlerr, ok := err.(*SQLError); ok { - sqlerr.Query = query - } - } - }() + defer func() { + if err != nil { + if sqlerr, ok := err.(*SQLError); ok { + sqlerr.Query = query + } + } + }() - // Send the query as a COM_QUERY packet. - if err = c.WriteComQuery(query); err != nil { - return nil, false, err - } + // Send the query as a COM_QUERY packet. + if err = c.WriteComQuery(query); err != nil { + return nil, false, err + } - res, more, _, err := c.ReadQueryResult(maxrows, wantfields) - return res, more, err + res, more, _, err := c.ReadQueryResult(maxrows, wantfields) + return res, more, err } // ExecuteFetchWithWarningCount is for fetching results and a warning count // Note: In a future iteration this should be abolished and merged into the // ExecuteFetch API. func (c *Conn) ExecuteFetchWithWarningCount(query string, maxrows int, wantfields bool) (result *sqltypes.Result, warnings uint16, err error) { - defer func() { - if err != nil { - if sqlerr, ok := err.(*SQLError); ok { - sqlerr.Query = query - } - } - }() + defer func() { + if err != nil { + if sqlerr, ok := err.(*SQLError); ok { + sqlerr.Query = query + } + } + }() - // Send the query as a COM_QUERY packet. - if err = c.WriteComQuery(query); err != nil { - return nil, 0, err - } + // Send the query as a COM_QUERY packet. + if err = c.WriteComQuery(query); err != nil { + return nil, 0, err + } - res, _, warnings, err := c.ReadQueryResult(maxrows, wantfields) - return res, warnings, err + res, _, warnings, err := c.ReadQueryResult(maxrows, wantfields) + return res, warnings, err } // ReadQueryResult gets the result from the last written query. func (c *Conn) ReadQueryResult(maxrows int, wantfields bool) (result *sqltypes.Result, more bool, warnings uint16, err error) { - // Get the result. - affectedRows, lastInsertID, colNumber, more, warnings, err := c.readComQueryResponse() - if err != nil { - return nil, false, 0, err - } + // Get the result. + affectedRows, lastInsertID, colNumber, more, warnings, err := c.readComQueryResponse() + if err != nil { + return nil, false, 0, err + } - if colNumber == 0 { - // OK packet, means no results. Just use the numbers. - return &sqltypes.Result{ - RowsAffected: affectedRows, - InsertID: lastInsertID, - }, more, warnings, nil - } + if colNumber == 0 { + // OK packet, means no results. Just use the numbers. + return &sqltypes.Result{ + RowsAffected: affectedRows, + InsertID: lastInsertID, + }, more, warnings, nil + } - fields := make([]querypb.Field, colNumber) - result = &sqltypes.Result{ - Fields: make([]*querypb.Field, colNumber), - } + fields := make([]querypb.Field, colNumber) + result = &sqltypes.Result{ + Fields: make([]*querypb.Field, colNumber), + } - // Read column headers. One packet per column. - // Build the fields. - for i := 0; i < colNumber; i++ { - result.Fields[i] = &fields[i] + // Read column headers. One packet per column. + // Build the fields. + for i := 0; i < colNumber; i++ { + result.Fields[i] = &fields[i] - if wantfields { - if err := c.readColumnDefinition(result.Fields[i], i); err != nil { - return nil, false, 0, err - } - } else { - if err := c.readColumnDefinitionType(result.Fields[i], i); err != nil { - return nil, false, 0, err - } - } - } + if wantfields { + if err := c.readColumnDefinition(result.Fields[i], i); err != nil { + return nil, false, 0, err + } + } else { + if err := c.readColumnDefinitionType(result.Fields[i], i); err != nil { + return nil, false, 0, err + } + } + } - if c.Capabilities&CapabilityClientDeprecateEOF == 0 { - // EOF is only present here if it's not deprecated. - data, err := c.readEphemeralPacket() - if err != nil { - return nil, false, 0, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) - } - if isEOFPacket(data) { + if c.Capabilities&CapabilityClientDeprecateEOF == 0 { + // EOF is only present here if it's not deprecated. + data, err := c.readEphemeralPacket() + if err != nil { + return nil, false, 0, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) + } + if isEOFPacket(data) { - // This is what we expect. - // Warnings and status flags are ignored. - c.RecycleReadPacket() - // goto: read row loop + // This is what we expect. + // Warnings and status flags are ignored. + c.RecycleReadPacket() + // goto: read row loop - } else if isErrorPacket(data) { - defer c.RecycleReadPacket() - return nil, false, 0, ParseErrorPacket(data) - } else { - defer c.RecycleReadPacket() - return nil, false, 0, vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected packet after fields: %v", data) - } - } + } else if isErrorPacket(data) { + defer c.RecycleReadPacket() + return nil, false, 0, ParseErrorPacket(data) + } else { + defer c.RecycleReadPacket() + return nil, false, 0, vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected packet after fields: %v", data) + } + } - // read each row until EOF or OK packet. - for { - data, err := c.ReadPacket() - if err != nil { - return nil, false, 0, err - } + // read each row until EOF or OK packet. + for { + data, err := c.ReadPacket() + if err != nil { + return nil, false, 0, err + } - if isEOFPacket(data) { - // Strip the partial Fields before returning. - if !wantfields { - result.Fields = nil - } - result.RowsAffected = uint64(len(result.Rows)) + if isEOFPacket(data) { + // Strip the partial Fields before returning. + if !wantfields { + result.Fields = nil + } + result.RowsAffected = uint64(len(result.Rows)) - // The deprecated EOF packets change means that this is either an - // EOF packet or an OK packet with the EOF type code. - if c.Capabilities&CapabilityClientDeprecateEOF == 0 { - warnings, more, err = parseEOFPacket(data) - if err != nil { - return nil, false, 0, err - } - } else { - var statusFlags uint16 - _, _, statusFlags, warnings, err = parseOKPacket(data) - if err != nil { - return nil, false, 0, err - } - more = (statusFlags & ServerMoreResultsExists) != 0 - } - return result, more, warnings, nil + // The deprecated EOF packets change means that this is either an + // EOF packet or an OK packet with the EOF type code. + if c.Capabilities&CapabilityClientDeprecateEOF == 0 { + warnings, more, err = parseEOFPacket(data) + if err != nil { + return nil, false, 0, err + } + } else { + var statusFlags uint16 + _, _, statusFlags, warnings, err = parseOKPacket(data) + if err != nil { + return nil, false, 0, err + } + more = (statusFlags & ServerMoreResultsExists) != 0 + } + return result, more, warnings, nil - } else if isErrorPacket(data) { - // Error packet. - return nil, false, 0, ParseErrorPacket(data) - } + } else if isErrorPacket(data) { + // Error packet. + return nil, false, 0, ParseErrorPacket(data) + } - // Check we're not over the limit before we add more. - if len(result.Rows) == maxrows { - if err := c.drainResults(); err != nil { - return nil, false, 0, err - } - return nil, false, 0, NewSQLError(ERVitessMaxRowsExceeded, SSUnknownSQLState, "Row count exceeded %d", maxrows) - } + // Check we're not over the limit before we add more. + if len(result.Rows) == maxrows { + if err := c.drainResults(); err != nil { + return nil, false, 0, err + } + return nil, false, 0, NewSQLError(ERVitessMaxRowsExceeded, SSUnknownSQLState, "Row count exceeded %d", maxrows) + } - // Regular row. - row, err := c.parseRow(data, result.Fields) - if err != nil { - return nil, false, 0, err - } - result.Rows = append(result.Rows, row) - } + // Regular row. + row, err := c.parseRow(data, result.Fields) + if err != nil { + return nil, false, 0, err + } + result.Rows = append(result.Rows, row) + } } // drainResults will read all packets for a result set and ignore them. func (c *Conn) drainResults() error { - for { - data, err := c.readEphemeralPacket() - if err != nil { - return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) - } - if isEOFPacket(data) { - c.RecycleReadPacket() - return nil - } else if isErrorPacket(data) { - defer c.RecycleReadPacket() - return ParseErrorPacket(data) - } - c.RecycleReadPacket() - } + for { + data, err := c.readEphemeralPacket() + if err != nil { + return NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) + } + if isEOFPacket(data) { + c.RecycleReadPacket() + return nil + } else if isErrorPacket(data) { + defer c.RecycleReadPacket() + return ParseErrorPacket(data) + } + c.RecycleReadPacket() + } } func (c *Conn) readComQueryResponse() (affectedRows uint64, lastInsertID uint64, status int, more bool, warnings uint16, err error) { - data, err := c.readEphemeralPacket() - if err != nil { - return 0, 0, 0, false, 0, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) - } - defer c.RecycleReadPacket() - if len(data) == 0 { - return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "invalid empty COM_QUERY response packet") - } + data, err := c.readEphemeralPacket() + if err != nil { + return 0, 0, 0, false, 0, NewSQLError(CRServerLost, SSUnknownSQLState, "%v", err) + } + defer c.RecycleReadPacket() + if len(data) == 0 { + return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "invalid empty COM_QUERY response packet") + } - switch data[0] { - case OKPacket: - affectedRows, lastInsertID, status, warnings, err := parseOKPacket(data) - return affectedRows, lastInsertID, 0, (status & ServerMoreResultsExists) != 0, warnings, err - case ErrPacket: - // Error - return 0, 0, 0, false, 0, ParseErrorPacket(data) - case 0xfb: - // Local infile - return 0, 0, 0, false, 0, vterrors.Errorf(vtrpc.Code_UNIMPLEMENTED, "not implemented") - } - n, pos, ok := readLenEncInt(data, 0) - if !ok { - return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "cannot get column number") - } - if pos != len(data) { - return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extra data in COM_QUERY response") - } - return 0, 0, int(n), false, 0, nil + switch data[0] { + case OKPacket: + affectedRows, lastInsertID, status, warnings, err := parseOKPacket(data) + return affectedRows, lastInsertID, 0, (status & ServerMoreResultsExists) != 0, warnings, err + case ErrPacket: + // Error + return 0, 0, 0, false, 0, ParseErrorPacket(data) + case 0xfb: + // Local infile + return 0, 0, 0, false, 0, vterrors.Errorf(vtrpc.Code_UNIMPLEMENTED, "not implemented") + } + n, pos, ok := readLenEncInt(data, 0) + if !ok { + return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "cannot get column number") + } + if pos != len(data) { + return 0, 0, 0, false, 0, NewSQLError(CRMalformedPacket, SSUnknownSQLState, "extra data in COM_QUERY response") + } + return 0, 0, int(n), false, 0, nil } // @@ -502,156 +477,156 @@ func (c *Conn) readComQueryResponse() (affectedRows uint64, lastInsertID uint64, // func (c *Conn) parseComQuery(data []byte) string { - return string(data[1:]) + return string(data[1:]) } func (c *Conn) parseComSetOption(data []byte) (uint16, bool) { - val, _, ok := readUint16(data, 1) - return val, ok + val, _, ok := readUint16(data, 1) + return val, ok } func (c *Conn) parseComInitDB(data []byte) string { - return string(data[1:]) + return string(data[1:]) } func (c *Conn) sendColumnCount(count uint64) error { - length := lenEncIntSize(count) - data := c.startEphemeralPacket(length) - writeLenEncInt(data, 0, count) - return c.writeEphemeralPacket() + length := lenEncIntSize(count) + data := c.startEphemeralPacket(length) + writeLenEncInt(data, 0, count) + return c.writeEphemeralPacket() } func (c *Conn) writeColumnDefinition(field *querypb.Field) error { - length := 4 + // lenEncStringSize("def") - lenEncStringSize(field.Database) + - lenEncStringSize(field.Table) + - lenEncStringSize(field.OrgTable) + - lenEncStringSize(field.Name) + - lenEncStringSize(field.OrgName) + - 1 + // length of fixed length fields - 2 + // character set - 4 + // column length - 1 + // type - 2 + // flags - 1 + // decimals - 2 // filler + length := 4 + // lenEncStringSize("def") + lenEncStringSize(field.Database) + + lenEncStringSize(field.Table) + + lenEncStringSize(field.OrgTable) + + lenEncStringSize(field.Name) + + lenEncStringSize(field.OrgName) + + 1 + // length of fixed length fields + 2 + // character set + 4 + // column length + 1 + // type + 2 + // flags + 1 + // decimals + 2 // filler - // Get the type and the flags back. If the Field contains - // non-zero flags, we use them. Otherwise use the flags we - // derive from the type. - typ, flags := sqltypes.TypeToMySQL(field.Type) - if field.Flags != 0 { - flags = int64(field.Flags) - } + // Get the type and the flags back. If the Field contains + // non-zero flags, we use them. Otherwise use the flags we + // derive from the type. + typ, flags := sqltypes.TypeToMySQL(field.Type) + if field.Flags != 0 { + flags = int64(field.Flags) + } - data := c.startEphemeralPacket(length) - pos := 0 + data := c.startEphemeralPacket(length) + pos := 0 - pos = writeLenEncString(data, pos, "def") // Always the same. - pos = writeLenEncString(data, pos, field.Database) - pos = writeLenEncString(data, pos, field.Table) - pos = writeLenEncString(data, pos, field.OrgTable) - pos = writeLenEncString(data, pos, field.Name) - pos = writeLenEncString(data, pos, field.OrgName) - pos = writeByte(data, pos, 0x0c) - pos = writeUint16(data, pos, uint16(field.Charset)) - pos = writeUint32(data, pos, field.ColumnLength) - pos = writeByte(data, pos, byte(typ)) - pos = writeUint16(data, pos, uint16(flags)) - pos = writeByte(data, pos, byte(field.Decimals)) - pos = writeUint16(data, pos, uint16(0x0000)) + pos = writeLenEncString(data, pos, "def") // Always the same. + pos = writeLenEncString(data, pos, field.Database) + pos = writeLenEncString(data, pos, field.Table) + pos = writeLenEncString(data, pos, field.OrgTable) + pos = writeLenEncString(data, pos, field.Name) + pos = writeLenEncString(data, pos, field.OrgName) + pos = writeByte(data, pos, 0x0c) + pos = writeUint16(data, pos, uint16(field.Charset)) + pos = writeUint32(data, pos, field.ColumnLength) + pos = writeByte(data, pos, byte(typ)) + pos = writeUint16(data, pos, uint16(flags)) + pos = writeByte(data, pos, byte(field.Decimals)) + pos = writeUint16(data, pos, uint16(0x0000)) - if pos != len(data) { - return vterrors.Errorf(vtrpc.Code_INTERNAL, "packing of column definition used %v bytes instead of %v", pos, len(data)) - } + if pos != len(data) { + return vterrors.Errorf(vtrpc.Code_INTERNAL, "packing of column definition used %v bytes instead of %v", pos, len(data)) + } - return c.writeEphemeralPacket() + return c.writeEphemeralPacket() } func (c *Conn) writeRow(row []sqltypes.Value) error { - length := 0 - for _, val := range row { - if val.IsNull() { - length++ - } else { - l := len(val.Raw()) - length += lenEncIntSize(uint64(l)) + l - } - } + length := 0 + for _, val := range row { + if val.IsNull() { + length++ + } else { + l := len(val.Raw()) + length += lenEncIntSize(uint64(l)) + l + } + } - data := c.startEphemeralPacket(length) - pos := 0 - for _, val := range row { - if val.IsNull() { - pos = writeByte(data, pos, NullValue) - } else { - l := len(val.Raw()) - pos = writeLenEncInt(data, pos, uint64(l)) - pos += copy(data[pos:], val.Raw()) - } - } + data := c.startEphemeralPacket(length) + pos := 0 + for _, val := range row { + if val.IsNull() { + pos = writeByte(data, pos, NullValue) + } else { + l := len(val.Raw()) + pos = writeLenEncInt(data, pos, uint64(l)) + pos += copy(data[pos:], val.Raw()) + } + } - if pos != length { - return vterrors.Errorf(vtrpc.Code_INTERNAL, "packet row: got %v bytes but expected %v", pos, length) - } + if pos != length { + return vterrors.Errorf(vtrpc.Code_INTERNAL, "packet row: got %v bytes but expected %v", pos, length) + } - return c.writeEphemeralPacket() + return c.writeEphemeralPacket() } // writeFields writes the fields of a Result. It should be called only // if there are valid columns in the result. func (c *Conn) writeFields(result *sqltypes.Result) error { - // Send the number of fields first. - if err := c.sendColumnCount(uint64(len(result.Fields))); err != nil { - return err - } + // Send the number of fields first. + if err := c.sendColumnCount(uint64(len(result.Fields))); err != nil { + return err + } - // Now send each Field. - for _, field := range result.Fields { - if err := c.writeColumnDefinition(field); err != nil { - return err - } - } + // Now send each Field. + for _, field := range result.Fields { + if err := c.writeColumnDefinition(field); err != nil { + return err + } + } - // Now send an EOF packet. - if c.Capabilities&CapabilityClientDeprecateEOF == 0 { - // With CapabilityClientDeprecateEOF, we do not send this EOF. - if err := c.writeEOFPacket(c.StatusFlags, 0); err != nil { - return err - } - } - return nil + // Now send an EOF packet. + if c.Capabilities&CapabilityClientDeprecateEOF == 0 { + // With CapabilityClientDeprecateEOF, we do not send this EOF. + if err := c.writeEOFPacket(c.StatusFlags, 0); err != nil { + return err + } + } + return nil } // writeRows sends the rows of a Result. func (c *Conn) writeRows(result *sqltypes.Result) error { - for _, row := range result.Rows { - if err := c.writeRow(row); err != nil { - return err - } - } - return nil + for _, row := range result.Rows { + if err := c.writeRow(row); err != nil { + return err + } + } + return nil } // writeEndResult concludes the sending of a Result. // if more is set to true, then it means there are more results afterwords func (c *Conn) writeEndResult(more bool, affectedRows, lastInsertID uint64, warnings uint16) error { - // Send either an EOF, or an OK packet. - // See doc.go. - flags := c.StatusFlags - if more { - flags |= ServerMoreResultsExists - } - if c.Capabilities&CapabilityClientDeprecateEOF == 0 { - if err := c.writeEOFPacket(flags, warnings); err != nil { - return err - } - } else { - // This will flush too. - if err := c.writeOKPacketWithEOFHeader(affectedRows, lastInsertID, flags, warnings); err != nil { - return err - } - } + // Send either an EOF, or an OK packet. + // See doc.go. + flags := c.StatusFlags + if more { + flags |= ServerMoreResultsExists + } + if c.Capabilities&CapabilityClientDeprecateEOF == 0 { + if err := c.writeEOFPacket(flags, warnings); err != nil { + return err + } + } else { + // This will flush too. + if err := c.writeOKPacketWithEOFHeader(affectedRows, lastInsertID, flags, warnings); err != nil { + return err + } + } - return nil + return nil } diff --git a/pkg/mysql/vmysql/server.go b/pkg/mysql/vmysql/server.go index 4a788e8..5095705 100644 --- a/pkg/mysql/vmysql/server.go +++ b/pkg/mysql/vmysql/server.go @@ -37,7 +37,6 @@ const ( // timing metric keys connectTimingKey = "Connect" queryTimingKey = "Query" - versionSSL30 = "SSL30" versionTLS10 = "TLS10" versionTLS11 = "TLS11" versionTLS12 = "TLS12" @@ -322,7 +321,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti } } else { if l.RequireSecureTransport { - c.writeErrorPacketFromError(vterrors.Errorf(vtrpc.Code_UNAVAILABLE, "server does not allow insecure connections, client must use SSL/TLS")) + _ = c.writeErrorPacketFromError(vterrors.Errorf(vtrpc.Code_UNAVAILABLE, "server does not allow insecure connections, client must use SSL/TLS")) } connCountByTLSVer.Add(versionNoTLS, 1) defer connCountByTLSVer.Add(versionNoTLS, -1) @@ -331,7 +330,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti // See what auth method the AuthServer wants to use for that user. authServerMethod, err := l.authServer.AuthMethod(user) if err != nil { - c.writeErrorPacketFromError(err) + _ = c.writeErrorPacketFromError(err) return } @@ -344,7 +343,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti userData, err := l.authServer.ValidateHash(salt, user, authResponse, conn.RemoteAddr()) if err != nil { log.Trace("Error authenticating user using MySQL native password: %v", err) - c.writeErrorPacketFromError(err) + _ = c.writeErrorPacketFromError(err) return } c.User = user @@ -358,8 +357,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti if err != nil { return } - //lint:ignore SA4006 This line is required because the binary protocol requires padding with 0 - data := make([]byte, 21) + data := make([]byte, 21) //nolint:ineffassign,staticcheck // SA4006 This line is required because the binary protocol requires padding with 0 data = append(salt, byte(0x00)) if err := c.writeAuthSwitchRequest(MysqlNativePassword, data); err != nil { log.Error("Error writing auth switch packet for %s: %v", c, err) @@ -376,7 +374,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti userData, err := l.authServer.ValidateHash(salt, user, response, conn.RemoteAddr()) if err != nil { log.Trace("Error authenticating user using MySQL native password: %v", err) - c.writeErrorPacketFromError(err) + _ = c.writeErrorPacketFromError(err) return } c.User = user @@ -387,7 +385,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti // The negotiation happens in clear text. Let's check we can. if !l.AllowClearTextWithoutTLS && c.Capabilities&CapabilityClientSSL == 0 { - c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.") + _ = c.writeErrorPacket(CRServerHandshakeErr, SSUnknownSQLState, "Cannot use clear text authentication over non-SSL connections.") return } @@ -406,7 +404,7 @@ func (l *Listener) handle(conn net.Conn, connectionID uint32, acceptTime time.Ti // auth server. userData, err := l.authServer.Negotiate(c, user, conn.RemoteAddr()) if err != nil { - c.writeErrorPacketFromError(err) + _ = c.writeErrorPacketFromError(err) return } c.User = user @@ -770,8 +768,6 @@ func (c *Conn) writeAuthSwitchRequest(pluginName string, pluginData []byte) erro // Whenever we move to a new version of go, we will need add any new supported TLS versions here func tlsVersionToString(version uint16) string { switch version { - case tls.VersionSSL30: - return versionSSL30 case tls.VersionTLS10: return versionTLS10 case tls.VersionTLS11: diff --git a/pkg/mysql/vmysql/sql_error.go b/pkg/mysql/vmysql/sql_error.go index 78b33a1..fd27a8c 100644 --- a/pkg/mysql/vmysql/sql_error.go +++ b/pkg/mysql/vmysql/sql_error.go @@ -17,58 +17,58 @@ limitations under the License. package vmysql import ( - "bytes" - "fmt" + "bytes" + "fmt" - "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/sqlparser" ) // SQLError is the error structure returned from calling a db library function type SQLError struct { - Num int - State string - Message string - Query string + Num int + State string + Message string + Query string } // NewSQLError creates a new SQLError. // If sqlState is left empty, it will default to "HY000" (general error). // TODO: Should be aligned with vterrors, stack traces and wrapping func NewSQLError(number int, sqlState string, format string, args ...interface{}) *SQLError { - if sqlState == "" { - sqlState = SSUnknownSQLState - } - return &SQLError{ - Num: number, - State: sqlState, - Message: fmt.Sprintf(format, args...), - } + if sqlState == "" { + sqlState = SSUnknownSQLState + } + return &SQLError{ + Num: number, + State: sqlState, + Message: fmt.Sprintf(format, args...), + } } // Error implements the error interface func (se *SQLError) Error() string { - buf := &bytes.Buffer{} - buf.WriteString(se.Message) + buf := &bytes.Buffer{} + buf.WriteString(se.Message) - // Add MySQL errno and SQLSTATE in a format that we can later parse. - // There's no avoiding string parsing because all errors - // are converted to strings anyway at RPC boundaries. - // See NewSQLErrorFromError. - fmt.Fprintf(buf, " (errno %v) (sqlstate %v)", se.Num, se.State) + // Add MySQL errno and SQLSTATE in a format that we can later parse. + // There's no avoiding string parsing because all errors + // are converted to strings anyway at RPC boundaries. + // See NewSQLErrorFromError. + fmt.Fprintf(buf, " (errno %v) (sqlstate %v)", se.Num, se.State) - if se.Query != "" { - fmt.Fprintf(buf, " during query: %s", sqlparser.TruncateForLog(se.Query)) - } + if se.Query != "" { + fmt.Fprintf(buf, " during query: %s", sqlparser.TruncateForLog(se.Query)) + } - return buf.String() + return buf.String() } // Number returns the internal MySQL error code. func (se *SQLError) Number() int { - return se.Num + return se.Num } // SQLState returns the SQLSTATE value. func (se *SQLError) SQLState() string { - return se.State -} \ No newline at end of file + return se.State +} diff --git a/pkg/rhttp/config.go b/pkg/rhttp/config.go index 3c4217b..723f9a5 100644 --- a/pkg/rhttp/config.go +++ b/pkg/rhttp/config.go @@ -1,5 +1,5 @@ package rhttp type Config struct { - IpHeader string + IpHeader string } diff --git a/pkg/rhttp/http.go b/pkg/rhttp/http.go index 09351b5..3ee74c6 100644 --- a/pkg/rhttp/http.go +++ b/pkg/rhttp/http.go @@ -119,7 +119,7 @@ func compileTpl(c *gin.Context, tpl string) (compiled string) { if headerVarMatcher.FindString(tpl) != "" { compiled = headerVarMatcher.ReplaceAllString(compiled, c.GetHeader(headerVarMatcher.FindStringSubmatch(tpl)[1])) } - return + return compiled } func (s *Server) Receive(c *gin.Context) { diff --git a/pkg/rhttp/record.go b/pkg/rhttp/record.go index 36039b7..bc48b03 100644 --- a/pkg/rhttp/record.go +++ b/pkg/rhttp/record.go @@ -17,7 +17,7 @@ type Record struct { Path string `form:"path" json:"path"` record.BaseRecord RawRequest string `json:"raw_request" notice:"-"` - Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"` + Rule Rule `gorm:"foreignKey:RuleName;references:Name;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" form:"-" json:"-" notice:"-"` } func (Record) TableName() string { @@ -42,7 +42,7 @@ func NewRecord(rule *Rule, flag, method, url, ip, area, raw string) (r *Record, Rule: *rule, } err = database.DB.Create(r).Error - return + return r, err } func ListRecords(c *gin.Context) { diff --git a/pkg/rhttp/rule.go b/pkg/rhttp/rule.go index 3bf221d..d57d464 100644 --- a/pkg/rhttp/rule.go +++ b/pkg/rhttp/rule.go @@ -13,7 +13,7 @@ import ( // Http rule struct type Rule struct { rule.BaseRule - ResponseStatusCode string `gorm:"index;default:200;not null" form:"response_status_code" json:"response_status_code"` + ResponseStatusCode string `gorm:"index;default:200;not null" form:"response_status_code" json:"response_status_code"` ResponseHeaders database.MapField `form:"response_headers" json:"response_headers"` ResponseBody string `gorm:"default:Hello RevSuit!" form:"response_body" json:"response_body"` } @@ -23,7 +23,7 @@ func (Rule) TableName() string { } // New http rule struct -func NewRule(name, flagFormat, responseBody string, pushToClient, notice bool, responseStatus string, responseHeaders database.MapField, ) *Rule { +func NewRule(name, flagFormat, responseBody string, pushToClient, notice bool, responseStatus string, responseHeaders database.MapField) *Rule { return &Rule{ BaseRule: rule.BaseRule{ Name: name, @@ -59,7 +59,7 @@ func (r *Rule) CreateOrUpdate() (err error) { } err = GetServer().updateRules() - return + return err } // Delete the http rule in database and ruleSet @@ -71,7 +71,7 @@ func (r *Rule) Delete() (err error) { } err = GetServer().updateRules() - return + return err } // List all http rules those satisfy the filter @@ -195,5 +195,4 @@ func DeleteRules(c *gin.Context) { "error": nil, "data": nil, }) - return } diff --git a/pkg/server/controller.go b/pkg/server/controller.go index 0c468b6..cda8545 100644 --- a/pkg/server/controller.go +++ b/pkg/server/controller.go @@ -14,7 +14,7 @@ func ping(c *gin.Context) { } func events(c *gin.Context) { - log.Info("Receive connection from ", c.Request.RemoteAddr) + log.Info("Receive connection from %v", c.Request.RemoteAddr) c.Stream(func(w io.Writer) bool { c.SSEvent("message", "connect succeed") select { diff --git a/pkg/server/server.go b/pkg/server/server.go index 2d8a907..d2ea5a7 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -24,11 +24,29 @@ func initDatabase(dsn string) { } err = database.DB.AutoMigrate(&http.Record{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&dns.Record{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&mysql.Record{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&http.Rule{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&dns.Rule{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&mysql.Rule{}) + if err != nil { + log.Fatal(err.Error()) + } err = database.DB.AutoMigrate(&mysql.File{}) if err != nil { log.Fatal(err.Error())