changeset 359:68401c1f10fa embedded

Backported more features from default branch.
author Pedro Ferreira <pedro.ferreira@monetdbsolutions.com>
date Fri, 26 Jul 2019 12:00:49 +0200 (2019-07-26)
parents 2ad7f42f141d
children 5e6218d21951
files src/main/java/nl/cwi/monetdb/jdbc/MonetDataSource.java src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java
diffstat 4 files changed, 104 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/nl/cwi/monetdb/jdbc/MonetDataSource.java
+++ b/src/main/java/nl/cwi/monetdb/jdbc/MonetDataSource.java
@@ -49,7 +49,8 @@ public class MonetDataSource extends Mon
 	public MonetDataSource() {}
 
 	/**
-	 * Attempts to establish a connection with the data source that this DataSource object represents.
+	 * Attempts to establish a connection with the data source that this
+	 * DataSource object represents.
 	 *
 	 * @return a MonetConnection
 	 * @throws SQLException if connecting to the database fails
@@ -60,7 +61,8 @@ public class MonetDataSource extends Mon
 	}
 
 	/**
-	 * Attempts to establish a connection with the data source that this DataSource object represents.
+	 * Attempts to establish a connection with the data source that this
+	 * DataSource object represents.
 	 *
 	 * @param username the username to use
 	 * @param password the password to use
@@ -68,7 +70,9 @@ public class MonetDataSource extends Mon
 	 * @throws SQLException if connecting to the database fails
 	 */
 	@Override
-	public Connection getConnection(String username, String password) throws SQLException {
+	public Connection getConnection(String username, String password)
+		throws SQLException
+	{
 		Properties props = new Properties();
 		props.put("user", username);
 		props.put("password", password);
@@ -82,8 +86,10 @@ public class MonetDataSource extends Mon
 		return driver.connect(url, props);
 	}
 
+
 	/**
-	 * Gets the maximum time in seconds that this data source can wait while attempting to connect to a database.
+	 * Gets the maximum time in seconds that this data source can wait while
+	 * attempting to connect to a database.
 	 *
 	 * @return login timeout default is 0 (infinite)
 	 */
@@ -93,7 +99,8 @@ public class MonetDataSource extends Mon
 	}
 
 	/**
-	 * Sets the maximum time in seconds that this data source will wait while attempting to connect to a database.
+	 * Sets the maximum time in seconds that this data source will wait while
+	 * attempting to connect to a database.
 	 *
 	 * @param seconds the number of seconds to wait before aborting the connect
 	 */
@@ -113,12 +120,14 @@ public class MonetDataSource extends Mon
 	}
 
 	/**
-	 * Sets the log writer for this DataSource object to the given java.io.PrintWriter object.
+	 * Sets the log writer for this DataSource object to the given
+	 * java.io.PrintWriter object.
 	 *
 	 * @param out a PrintWriter - ignored
 	 */
 	@Override
-	public void setLogWriter(PrintWriter out) {}
+	public void setLogWriter(PrintWriter out) {
+	}
 
 	/**
 	 * Sets the password to use when connecting.  There is no getter
@@ -220,7 +229,8 @@ public class MonetDataSource extends Mon
 	 * may be the root Logger.
 	 *
 	 * @return the parent Logger for this data source
-	 * @throws SQLFeatureNotSupportedException if the data source does not use java.util.logging
+	 * @throws SQLFeatureNotSupportedException if the data source does
+	 *         not use java.util.logging
 	 */
 	@Override
 	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/helpers/ChannelSecurity.java
@@ -8,6 +8,9 @@
 
 package nl.cwi.monetdb.mcl.connection.helpers;
 
+import nl.cwi.monetdb.mcl.protocol.ProtocolException;
+
+import java.io.UnsupportedEncodingException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
@@ -43,15 +46,11 @@ public final class ChannelSecurity {
 	 * @param toDigests The Strings to digest
 	 * @return The Strings digest as a hexadecimal string
 	 */
-	public static String digestStrings(String algorithm, byte[]... toDigests) {
-		try {
-			MessageDigest md = MessageDigest.getInstance(algorithm);
-			for (byte[] str : toDigests) {
-				md.update(str);
-			}
-			return toHex(md.digest());
-		} catch (NoSuchAlgorithmException e) {
-			throw new AssertionError("internal error: " + e.toString());
+	public static String digestStrings(String algorithm, byte[]... toDigests) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance(algorithm);
+		for (byte[] str : toDigests) {
+			md.update(str);
 		}
+		return toHex(md.digest());
 	}
 }
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/AbstractSocket.java
@@ -92,6 +92,16 @@ public abstract class AbstractSocket imp
 	}
 
 	/**
+	 * Sets the TCP keep alive feature in the underlying socket.
+	 *
+	 * @param on A true or false value
+	 * @throws SocketException If an error in the underlying connection happened
+	 */
+	void setKeepAlive(boolean on) throws SocketException {
+		socket.setKeepAlive(on);
+	}
+
+	/**
 	 * Sets the underlying socket Endianness.
 	 *
 	 * @param bo A ByteOrder order value either Little-endian or Big-endian
--- a/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java
+++ b/src/main/java/nl/cwi/monetdb/mcl/connection/mapi/MapiConnection.java
@@ -18,11 +18,13 @@ import nl.cwi.monetdb.mcl.protocol.Serve
 import nl.cwi.monetdb.mcl.protocol.oldmapi.OldMapiProtocol;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.SocketException;
 import java.net.SocketTimeoutException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.nio.ByteOrder;
+import java.security.NoSuchAlgorithmException;
 import java.sql.BatchUpdateException;
 import java.sql.Connection;
 import java.sql.SQLException;
@@ -326,12 +328,13 @@ public class MapiConnection extends Mone
 	public List<String> connect(String host, int port, String user, String pass, boolean makeConnection)
 			throws IOException, ProtocolException, MCLException {
 		if (ttl-- <= 0)
-			throw new MCLException("Maximum number of redirects reached, aborting connection attempt. Sorry.");
+			throw new MCLException("Maximum number of redirects reached, aborting connection attempt.");
 
 		if (makeConnection) {
 			this.protocol = new OldMapiProtocol(new OldMapiSocket(this.hostname, this.port, this));
 			//set nodelay, as it greatly speeds up small messages (like we often do)
 			((OldMapiProtocol)this.protocol).getSocket().setTcpNoDelay(true);
+			((OldMapiProtocol)this.protocol).getSocket().setKeepAlive(true);
 			((OldMapiProtocol)this.protocol).getSocket().setSoTimeout(this.soTimeout);
 		}
 
@@ -469,21 +472,17 @@ public class MapiConnection extends Mone
 										String database, String hash) throws ProtocolException, MCLException,
 			IOException {
 		String response;
-		String algo;
-
 		// parse the challenge string, split it on ':'
 		String[] chaltok = chalstr.split(":");
-		if (chaltok.length <= 4)
-			throw new ProtocolException("Server challenge string unusable! Challenge contains too few tokens: "
-					+ chalstr);
+		if (chaltok.length <= 5)
+			throw new MCLException("Server challenge string unusable! It contains too few (" + chaltok.length + ") tokens: " + chalstr);
 
 		// challenge string to use as salt/key
 		String challenge = chaltok[0];
-		String servert = chaltok[1];
 		try {
-			this.version = Integer.parseInt(chaltok[2].trim()); // protocol version
+			version = Integer.parseInt(chaltok[2]);	// protocol version
 		} catch (NumberFormatException e) {
-			throw new ProtocolException("Protocol version unparseable: " + chaltok[2]);
+			throw new MCLException("Protocol version (" + chaltok[2] + ") unparseable as integer.");
 		}
 
 		switch (chaltok[4]) {
@@ -501,47 +500,57 @@ public class MapiConnection extends Mone
 		// handle the challenge according to the version it is
 		switch (this.version) {
 			case 9:
-				// proto 9 is like 8, but uses a hash instead of the plain password, the server tells us which hash in
-				// the challenge after the byte-order
+				// proto 9 is like 8, but uses a hash instead of the plain password
+				// the server tells us (in 6th token) which hash in the
+				// challenge after the byte-order token
+
+				String algo;
+				String pwhash = chaltok[5];
 				/* NOTE: Java doesn't support RIPEMD160 :( */
-				switch (chaltok[5]) {
-					case "SHA512":
-						algo = "SHA-512";
-						break;
-					case "SHA384":
-						algo = "SHA-384";
-						break;
-					case "SHA256":
-						algo = "SHA-256";
-				/* NOTE: Java supports SHA-224 only on 8 */
-						break;
-					case "SHA1":
-						algo = "SHA-1";
-						break;
-					case "MD5":
-						algo = "MD5";
-						break;
-					default:
-						throw new MCLException("Unsupported password hash: " + chaltok[5]);
+				if (pwhash.equals("SHA512")) {
+					algo = "SHA-512";
+				} else if (pwhash.equals("SHA384")) {
+					algo = "SHA-384";
+				} else if (pwhash.equals("SHA256")) {
+					algo = "SHA-256";
+					/* NOTE: Java doesn't support SHA-224 */
+				} else if (pwhash.equals("SHA1")) {
+					algo = "SHA-1";
+				} else if (pwhash.equals("MD5")) {
+					algo = "MD5";
+				} else {
+					throw new MCLException("Unsupported password hash: " + pwhash);
+				}
+				try {
+					password = ChannelSecurity.digestStrings(algo, password.getBytes("UTF-8"));
+				} catch (NoSuchAlgorithmException e) {
+					throw new MCLException("This JVM does not support password hash: " + pwhash + "\n" + e.toString());
+				} catch (UnsupportedEncodingException e) {
+					throw new MCLException("This JVM does not support UTF-8 encoding\n" + e.toString());
 				}
 
-				password = ChannelSecurity.digestStrings(algo, password.getBytes("UTF-8"));
-
-				// proto 7 (finally) used the challenge and works with a password hash. The supported implementations
-				// come from the server challenge. We chose the best hash we can find, in the order SHA1, MD5, plain.
-				// Also, the byte-order is reported in the challenge string. proto 8 made this obsolete, but retained
-				// the byte-order report for future "binary" transports. In proto 8, the byte-order of the blocks is
-				// always little endian because most machines today are.
-				String hashes = (hash == null ? chaltok[3] : hash);
-				Set<String> hashesSet = new HashSet<>(Arrays.asList(hashes.toUpperCase().split("[, ]")));
+				// proto 7 (finally) used the challenge and works with a
+				// password hash.  The supported implementations come
+				// from the server challenge.  We chose the best hash
+				// we can find, in the order SHA512, SHA1, MD5, plain.
+				// Also the byte-order is reported in the challenge string,
+				// which makes sense, since only blockmode is supported.
+				// proto 8 made this obsolete, but retained the
+				// byte-order report for future "binary" transports.
+				// In proto 8, the byte-order of the blocks is always little
+				// endian because most machines today are.
+				String hashes = (hash == null || hash.isEmpty()) ? chaltok[3] : hash;
+				HashSet<String> hashesSet = new HashSet<String>(Arrays.asList(hashes.toUpperCase().split("[, ]")));	// split on comma or space
 
 				// if we deal with merovingian, mask our credentials
-				if (servert.equals("merovingian") && !language.equals("control")) {
+				if (chaltok[1].equals("merovingian") && !language.equals("control")) {
 					username = "merovingian";
 					password = "merovingian";
 				}
-				String pwhash;
 
+				// reuse variables algo and pwhash
+				algo = null;
+				pwhash = null;
 				if (hashesSet.contains("SHA512")) {
 					algo = "SHA-512";
 					pwhash = "{SHA512}";
@@ -558,11 +567,26 @@ public class MapiConnection extends Mone
 					algo = "MD5";
 					pwhash = "{MD5}";
 				} else {
-					throw new MCLException("No supported password hashes in " + hashes);
+					throw new MCLException("no supported hash algorithms found in " + hashes);
 				}
 
-				pwhash += ChannelSecurity.digestStrings(algo, password.getBytes("UTF-8"),
-						challenge.getBytes("UTF-8"));
+				try {
+					pwhash += ChannelSecurity.digestStrings(algo, password.getBytes("UTF-8"),
+							challenge.getBytes("UTF-8"));
+				} catch (NoSuchAlgorithmException e) {
+					throw new MCLException("This JVM does not support password hash: " + pwhash + "\n" + e.toString());
+				} catch (UnsupportedEncodingException e) {
+					throw new MCLException("This JVM does not support UTF-8 encoding\n" + e.toString());
+				}
+
+				// TODO: some day when we need this, we should store this
+				if (chaltok[4].equals("BIG")) {
+					// byte-order of server is big-endian
+				} else if (chaltok[4].equals("LIT")) {
+					// byte-order of server is little-endian
+				} else {
+					throw new ProtocolException("Invalid byte-order: " + chaltok[4]);
+				}
 
 				// generate response
 				response = "BIG:";	// JVM byte-order is big-endian