diff --git a/.gitignore b/.gitignore index e8223c0e..c07dcb97 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ /manager/.settings/* /driver/.settings/* /server/.settings/* +/manager/classes/ +/driver/classes/ +/server/classes/ .project .classpath .DS_Store diff --git a/README.md b/README.md index 6d976ed7..35b22e74 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![](https://raw.githubusercontent.com/alibaba/cobar/master/doc/Cobar_logo.png) [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) -![Project Status](https://img.shields.io/badge/status-rc-yellow.svg) +![Project Status](https://img.shields.io/badge/status-release-green.svg) ## What is Cobar? diff --git a/manager/pom.xml b/manager/pom.xml index 63e7a481..82873ad9 100644 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -28,6 +28,7 @@ 1.0.5 GBK + 2.5.6.SEC03 @@ -45,52 +46,52 @@ org.springframework spring - 2.5.6 + ${spring.version} org.springframework spring-aop - 2.5.6 + ${spring.version} org.springframework spring-beans - 2.5.6 + ${spring.version} org.springframework spring-jdbc - 2.5.6 + ${spring.version} org.springframework spring-context-support - 2.5.6 + ${spring.version} org.springframework spring-context - 2.5.6 + ${spring.version} org.springframework spring-core - 2.5.6 + ${spring.version} org.springframework spring-web - 2.5.6 + ${spring.version} org.springframework spring-webmvc - 2.5.6 + ${spring.version} org.springframework spring-test - 2.5.6 + ${spring.version} test diff --git a/server/assembly/bin/startup.bat b/server/assembly/bin/startup.bat index ff260aa3..194e8166 100644 --- a/server/assembly/bin/startup.bat +++ b/server/assembly/bin/startup.bat @@ -55,7 +55,7 @@ echo --------------------------------------------------- goto end :okHome -set "APP_VERSION=1.2.7" +set "APP_VERSION=1.2.8-SNAPSHOT" REM set COBAR_CLASSPATH set "COBAR_CLASSPATH=%COBAR_HOME%\conf;%COBAR_HOME%\lib\classes" diff --git a/server/assembly/logs/.gitkeep b/server/assembly/logs/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/server/pom.xml b/server/pom.xml index 1d3dd3a8..5faa8530 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -21,13 +21,13 @@ cobar-server ${app.version} jar - ${artifactId} + ${project.artifactId} The project of cobar-server http://code.alibabatech.com/wiki/display/Cobar UTF-8 - 1.2.7 + 1.2.8-SNAPSHOT @@ -160,4 +160,4 @@ - \ No newline at end of file + diff --git a/server/src/main/net/com/alibaba/cobar/net/handler/FrontendAuthenticator.java b/server/src/main/net/com/alibaba/cobar/net/handler/FrontendAuthenticator.java index d844204b..a71ef5be 100644 --- a/server/src/main/net/com/alibaba/cobar/net/handler/FrontendAuthenticator.java +++ b/server/src/main/net/com/alibaba/cobar/net/handler/FrontendAuthenticator.java @@ -1,21 +1,20 @@ /* * Copyright 1999-2012 Alibaba Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.alibaba.cobar.net.handler; import java.nio.ByteBuffer; +import java.security.DigestException; import java.security.NoSuchAlgorithmException; import java.util.Set; @@ -35,131 +34,148 @@ * @author xianmao.hexm */ public class FrontendAuthenticator implements NIOHandler { - private static final Logger LOGGER = Logger.getLogger(FrontendAuthenticator.class); - private static final byte[] AUTH_OK = new byte[] { 7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0 }; - - protected final FrontendConnection source; + private static final Logger LOGGER = Logger.getLogger(FrontendAuthenticator.class); + private static final byte[] AUTH_OK = new byte[] {7, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0}; - public FrontendAuthenticator(FrontendConnection source) { - this.source = source; - } - - @Override - public void handle(byte[] data) { - // check quit packet - if (data.length == QuitPacket.QUIT.length && data[4] == MySQLPacket.COM_QUIT) { - source.close(); - return; - } + protected final FrontendConnection source; - AuthPacket auth = new AuthPacket(); - auth.read(data); + public FrontendAuthenticator(FrontendConnection source) { + this.source = source; + } - // check user - if (!checkUser(auth.user, source.getHost())) { - failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "'"); - return; - } + @Override + public void handle(byte[] data) { + // check quit packet + if (data.length == QuitPacket.QUIT.length && data[4] == MySQLPacket.COM_QUIT) { + source.close(); + return; + } - // check password - if (!checkPassword(auth.password, auth.user)) { - failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "'"); - return; - } + AuthPacket auth = new AuthPacket(); + auth.read(data); - // check schema - switch (checkSchema(auth.database, auth.user)) { - case ErrorCode.ER_BAD_DB_ERROR: - failure(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + auth.database + "'"); - break; - case ErrorCode.ER_DBACCESS_DENIED_ERROR: - String s = "Access denied for user '" + auth.user + "' to database '" + auth.database + "'"; - failure(ErrorCode.ER_DBACCESS_DENIED_ERROR, s); - break; - default: - success(auth); - } + // check user + if (!checkUser(auth.user, source.getHost())) { + failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "'"); + return; } - protected boolean checkUser(String user, String host) { - return source.getPrivileges().userExists(user, host); + // check password + if (!checkPassword(auth.password, auth.user)) { + failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "'"); + return; } - protected boolean checkPassword(byte[] password, String user) { - String pass = source.getPrivileges().getPassword(user); + // check schema + switch (checkSchema(auth.database, auth.user)) { + case ErrorCode.ER_BAD_DB_ERROR: + failure(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + auth.database + "'"); + break; + case ErrorCode.ER_DBACCESS_DENIED_ERROR: + String s = "Access denied for user '" + auth.user + "' to database '" + auth.database + "'"; + failure(ErrorCode.ER_DBACCESS_DENIED_ERROR, s); + break; + default: + success(auth); + } + } - // check null - if (pass == null || pass.length() == 0) { - if (password == null || password.length == 0) { - return true; - } else { - return false; - } - } - if (password == null || password.length == 0) { - return false; - } + protected boolean checkUser(String user, String host) { + return source.getPrivileges().userExists(user, host); + } - // encrypt - byte[] encryptPass = null; - try { - encryptPass = SecurityUtil.scramble411(pass.getBytes(), source.getSeed()); - } catch (NoSuchAlgorithmException e) { - LOGGER.warn(source.toString(), e); - return false; - } - if (encryptPass != null && (encryptPass.length == password.length)) { - int i = encryptPass.length; - while (i-- != 0) { - if (encryptPass[i] != password[i]) { - return false; - } - } - } else { - return false; - } + protected boolean checkPassword(byte[] password, String user) { + String pass = source.getPrivileges().getPassword(user); + // check null + if (pass == null || pass.length() == 0) { + if (password == null || password.length == 0) { return true; + } else { + return false; + } + } + if (password == null || password.length == 0) { + return false; } - protected int checkSchema(String schema, String user) { - if (schema == null) { - return 0; - } - FrontendPrivileges privileges = source.getPrivileges(); - if (!privileges.schemaExists(schema)) { - return ErrorCode.ER_BAD_DB_ERROR; - } - Set schemas = privileges.getUserSchemas(user); - if (schemas == null || schemas.size() == 0 || schemas.contains(schema)) { - return 0; - } else { - return ErrorCode.ER_DBACCESS_DENIED_ERROR; - } + byte[] passBytes = pass.getBytes(); + byte[] encryptPass = null; + + // encrypt 1 + try { + encryptPass = SecurityUtil.scramble411(passBytes, source.getSeed()); + } catch (NoSuchAlgorithmException e) { + LOGGER.warn(source.toString(), e); + return false; + } + boolean auth = checkBytes(encryptPass, password); + if (auth) { + return true; } - protected void success(AuthPacket auth) { - source.setAuthenticated(true); - source.setUser(auth.user); - source.setSchema(auth.database); - source.setCharsetIndex(auth.charsetIndex); - source.setHandler(new FrontendCommandHandler(source)); - if (LOGGER.isInfoEnabled()) { - StringBuilder s = new StringBuilder(); - s.append(source).append('\'').append(auth.user).append("' login success"); - byte[] extra = auth.extra; - if (extra != null && extra.length > 0) { - s.append(",extra:").append(new String(extra)); - } - LOGGER.info(s.toString()); + // encrypt 2 + try { + encryptPass = SecurityUtil.scrambleCachingSha2(passBytes, source.getSeed()); + } catch (DigestException e) { + LOGGER.warn(source.toString(), e); + return false; + } + return checkBytes(encryptPass, password); + } + + private boolean checkBytes(byte[] encryptPass, byte[] password) { + if (encryptPass != null && (encryptPass.length == password.length)) { + int i = encryptPass.length; + while (i-- != 0) { + if (encryptPass[i] != password[i]) { + return false; } - ByteBuffer buffer = source.allocate(); - source.write(source.writeToBuffer(AUTH_OK, buffer)); + } + return true; + } else { + return false; } + } - protected void failure(int errno, String info) { - LOGGER.error(source.toString() + info); - source.writeErrMessage((byte) 2, errno, info); + protected int checkSchema(String schema, String user) { + if (schema == null) { + return 0; + } + FrontendPrivileges privileges = source.getPrivileges(); + if (!privileges.schemaExists(schema)) { + return ErrorCode.ER_BAD_DB_ERROR; + } + Set schemas = privileges.getUserSchemas(user); + if (schemas == null || schemas.size() == 0 || schemas.contains(schema)) { + return 0; + } else { + return ErrorCode.ER_DBACCESS_DENIED_ERROR; + } + } + + protected void success(AuthPacket auth) { + source.setAuthenticated(true); + source.setUser(auth.user); + source.setSchema(auth.database); + source.setCharsetIndex(auth.charsetIndex); + source.setHandler(new FrontendCommandHandler(source)); + if (LOGGER.isInfoEnabled()) { + StringBuilder s = new StringBuilder(); + s.append(source).append('\'').append(auth.user).append("' login success"); + byte[] extra = auth.extra; + if (extra != null && extra.length > 0) { + s.append(",extra:").append(new String(extra)); + } + LOGGER.info(s.toString()); } + ByteBuffer buffer = source.allocate(); + source.write(source.writeToBuffer(AUTH_OK, buffer)); + } + + protected void failure(int errno, String info) { + LOGGER.error(source.toString() + info); + source.writeErrMessage((byte) 2, errno, info); + } } diff --git a/server/src/main/server/com/alibaba/cobar/mysql/SecurityUtil.java b/server/src/main/server/com/alibaba/cobar/mysql/SecurityUtil.java index baf0f02a..68f493cf 100644 --- a/server/src/main/server/com/alibaba/cobar/mysql/SecurityUtil.java +++ b/server/src/main/server/com/alibaba/cobar/mysql/SecurityUtil.java @@ -1,20 +1,19 @@ /* * Copyright 1999-2012 Alibaba Group. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. */ package com.alibaba.cobar.mysql; +import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -25,70 +24,115 @@ */ public class SecurityUtil { - public static final byte[] scramble411(byte[] pass, byte[] seed) throws NoSuchAlgorithmException { - MessageDigest md = MessageDigest.getInstance("SHA-1"); - byte[] pass1 = md.digest(pass); - md.reset(); - byte[] pass2 = md.digest(pass1); - md.reset(); - md.update(seed); - byte[] pass3 = md.digest(pass2); - for (int i = 0; i < pass3.length; i++) { - pass3[i] = (byte) (pass3[i] ^ pass1[i]); - } - return pass3; + private static final int CACHING_SHA2_DIGEST_LENGTH = 32; + + public static byte[] scrambleCachingSha2(byte[] password, byte[] seed) throws DigestException { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException ex) { + throw new DigestException(ex); + } + + byte[] dig1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + byte[] dig2 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + byte[] scramble1 = new byte[CACHING_SHA2_DIGEST_LENGTH]; + + // SHA2(src) => digest_stage1 + md.update(password, 0, password.length); + md.digest(dig1, 0, CACHING_SHA2_DIGEST_LENGTH); + md.reset(); + + // SHA2(digest_stage1) => digest_stage2 + md.update(dig1, 0, dig1.length); + md.digest(dig2, 0, CACHING_SHA2_DIGEST_LENGTH); + md.reset(); + + // SHA2(digest_stage2, m_rnd) => scramble_stage1 + md.update(dig2, 0, dig1.length); + md.update(seed, 0, seed.length); + md.digest(scramble1, 0, CACHING_SHA2_DIGEST_LENGTH); + + // XOR(digest_stage1, scramble_stage1) => scramble + byte[] mysqlScrambleBuff = new byte[CACHING_SHA2_DIGEST_LENGTH]; + xorString(dig1, mysqlScrambleBuff, scramble1, CACHING_SHA2_DIGEST_LENGTH); + + return mysqlScrambleBuff; + } + + public static final byte[] scramble411(byte[] pass, byte[] seed) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] pass1 = md.digest(pass); + md.reset(); + byte[] pass2 = md.digest(pass1); + md.reset(); + md.update(seed); + byte[] pass3 = md.digest(pass2); + for (int i = 0; i < pass3.length; i++) { + pass3[i] = (byte) (pass3[i] ^ pass1[i]); + } + return pass3; + } + + public static final String scramble323(String pass, String seed) { + if ((pass == null) || (pass.length() == 0)) { + return pass; + } + byte b; + double d; + long[] pw = hash(seed); + long[] msg = hash(pass); + long max = 0x3fffffffL; + long seed1 = (pw[0] ^ msg[0]) % max; + long seed2 = (pw[1] ^ msg[1]) % max; + char[] chars = new char[seed.length()]; + for (int i = 0; i < seed.length(); i++) { + seed1 = ((seed1 * 3) + seed2) % max; + seed2 = (seed1 + seed2 + 33) % max; + d = (double) seed1 / (double) max; + b = (byte) java.lang.Math.floor((d * 31) + 64); + chars[i] = (char) b; + } + seed1 = ((seed1 * 3) + seed2) % max; + seed2 = (seed1 + seed2 + 33) % max; + d = (double) seed1 / (double) max; + b = (byte) java.lang.Math.floor(d * 31); + for (int i = 0; i < seed.length(); i++) { + chars[i] ^= (char) b; } + return new String(chars); + } - public static final String scramble323(String pass, String seed) { - if ((pass == null) || (pass.length() == 0)) { - return pass; - } - byte b; - double d; - long[] pw = hash(seed); - long[] msg = hash(pass); - long max = 0x3fffffffL; - long seed1 = (pw[0] ^ msg[0]) % max; - long seed2 = (pw[1] ^ msg[1]) % max; - char[] chars = new char[seed.length()]; - for (int i = 0; i < seed.length(); i++) { - seed1 = ((seed1 * 3) + seed2) % max; - seed2 = (seed1 + seed2 + 33) % max; - d = (double) seed1 / (double) max; - b = (byte) java.lang.Math.floor((d * 31) + 64); - chars[i] = (char) b; - } - seed1 = ((seed1 * 3) + seed2) % max; - seed2 = (seed1 + seed2 + 33) % max; - d = (double) seed1 / (double) max; - b = (byte) java.lang.Math.floor(d * 31); - for (int i = 0; i < seed.length(); i++) { - chars[i] ^= (char) b; - } - return new String(chars); + private static long[] hash(String src) { + long nr = 1345345333L; + long add = 7; + long nr2 = 0x12345671L; + long tmp; + for (int i = 0; i < src.length(); ++i) { + switch (src.charAt(i)) { + case ' ': + case '\t': + continue; + default: + tmp = (0xff & src.charAt(i)); + nr ^= ((((nr & 63) + add) * tmp) + (nr << 8)); + nr2 += ((nr2 << 8) ^ nr); + add += tmp; + } } + long[] result = new long[2]; + result[0] = nr & 0x7fffffffL; + result[1] = nr2 & 0x7fffffffL; + return result; + } - private static long[] hash(String src) { - long nr = 1345345333L; - long add = 7; - long nr2 = 0x12345671L; - long tmp; - for (int i = 0; i < src.length(); ++i) { - switch (src.charAt(i)) { - case ' ': - case '\t': - continue; - default: - tmp = (0xff & src.charAt(i)); - nr ^= ((((nr & 63) + add) * tmp) + (nr << 8)); - nr2 += ((nr2 << 8) ^ nr); - add += tmp; - } - } - long[] result = new long[2]; - result[0] = nr & 0x7fffffffL; - result[1] = nr2 & 0x7fffffffL; - return result; + private static void xorString(byte[] from, byte[] to, byte[] scramble, int length) { + int pos = 0; + int scrambleLength = scramble.length; + while (pos < length) { + to[pos] = (byte) (from[pos] ^ scramble[pos % scrambleLength]); + pos++; } + } } diff --git a/server/src/main/server/com/alibaba/cobar/mysql/bio/MySQLChannel.java b/server/src/main/server/com/alibaba/cobar/mysql/bio/MySQLChannel.java index 15093ddb..484ab487 100644 --- a/server/src/main/server/com/alibaba/cobar/mysql/bio/MySQLChannel.java +++ b/server/src/main/server/com/alibaba/cobar/mysql/bio/MySQLChannel.java @@ -210,8 +210,9 @@ public BinaryPacket execute(RouteResultsetNode rrn, ServerConnection sc, boolean 详细见:http://dev.mysql.com/doc/connector-j/en/connector-j-usagenotes-troubleshooting.html#qandaitem-15-1-15 https://dev.mysql.com/doc/relnotes/connector-j/en/news-5-1-13.html */ - if(this.charsetIndex != 45 && sc.getCharsetIndex() != 33) + if (!(this.charsetIndex == 45 && sc.getCharsetIndex() == 33)) { sendCharset(sc.getCharsetIndex()); + } } if (this.txIsolation != sc.getTxIsolation()) { sendTxIsolation(sc.getTxIsolation()); @@ -224,8 +225,6 @@ public BinaryPacket execute(RouteResultsetNode rrn, ServerConnection sc, boolean CommandPacket packet = new CommandPacket(); packet.packetId = 0; packet.command = MySQLPacket.COM_QUERY; - - packet.arg = rrn.getStatement().getBytes(charset); // 记录执行开始时间 @@ -465,15 +464,12 @@ private void sendCharset(int ci) throws IOException { cmd.write(out); out.flush(); - BinaryPacket bin = receive(); switch (bin.data[0]) { case OkPacket.FIELD_COUNT: - this.charsetIndex = ci; this.charset = CharsetUtil.getCharset(ci); this.dbCharset = CharsetUtil.getCharset(ci); - break; case ErrorPacket.FIELD_COUNT: ErrorPacket err = new ErrorPacket();