Changeset: b58c1b245ede for monetdb-java URL: https://dev.monetdb.org/hg/monetdb-java?cmd=changeset;node=b58c1b245ede Modified Files: src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java Branch: default Log Message:
Correct and improve implementation of getChallengeResponse() for protocol 9 Add method getLogWriter() so when debug is enabled it can be used from other places (e.g. MonetConnection) Disabled unused method: debug(PrintStream out) Improved comments and documentation diffs (truncated from 413 to 300 lines): diff --git a/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java b/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java --- a/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java +++ b/src/main/java/nl/cwi/monetdb/mcl/net/MapiSocket.java @@ -10,15 +10,12 @@ package nl.cwi.monetdb.mcl.net; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.EOFException; import java.io.FileWriter; import java.io.FilterInputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintStream; -import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.Socket; @@ -32,7 +29,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.Set; import nl.cwi.monetdb.mcl.MCLException; import nl.cwi.monetdb.mcl.io.BufferedMCLReader; @@ -111,16 +107,18 @@ public final class MapiSocket { private String language = "sql"; /** The hash methods to use (null = default) */ private String hash = null; + /** Whether we should follow redirects */ private boolean followRedirects = true; /** How many redirections do we follow until we're fed up with it? */ private int ttl = 10; + /** Whether we are debugging or not */ private boolean debug = false; /** The Writer for the debug log-file */ private Writer log; - /** The blocksize (hardcoded in compliance with stream.mx) */ + /** The blocksize (hardcoded in compliance with MonetDB common/stream/stream.h) */ public final static int BLOCK = 8 * 1024 - 2; /** A short in two bytes for holding the block size in bytes */ @@ -201,7 +199,7 @@ public final class MapiSocket { * operation to have effect. * * @param s The specified timeout, in milliseconds. A timeout - * of zero is interpreted as an infinite timeout. + * of zero will disable timeout (i.e., timeout of infinity). * @throws SocketException Issue with the socket */ public void setSoTimeout(int s) throws SocketException { @@ -209,7 +207,7 @@ public final class MapiSocket { throw new IllegalArgumentException("timeout can't be negative"); } this.soTimeout = s; - // limit time to wait on blocking operations (0 = indefinite) + // limit time to wait on blocking operations if (con != null) { con.setSoTimeout(s); } @@ -229,7 +227,7 @@ public final class MapiSocket { } /** - * Enables/disables debug + * Enables/disables debug mode with logging to file * * @param debug Value to set */ @@ -266,7 +264,7 @@ public final class MapiSocket { throws IOException, UnknownHostException, SocketException, MCLParseException, 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) { con = new Socket(host, port); @@ -288,28 +286,20 @@ public final class MapiSocket { String c = reader.readLine(); reader.waitForPrompt(); - writer.writeLine( - getChallengeResponse( - c, - user, - pass, - language, - database, - hash - ) - ); - - // read monet response till prompt + writer.writeLine(getChallengeResponse(c, user, pass, language, database, hash)); + // read monetdb mserver response till prompt List<String> redirects = new ArrayList<String>(); List<String> warns = new ArrayList<String>(); String err = "", tmp; int lineType; do { - if ((tmp = reader.readLine()) == null) + tmp = reader.readLine(); + if (tmp == null) throw new IOException("Read from " + con.getInetAddress().getHostName() + ":" + con.getPort() + ": End of stream reached"); - if ((lineType = reader.getLineType()) == BufferedMCLReader.ERROR) { + lineType = reader.getLineType(); + if (lineType == BufferedMCLReader.ERROR) { err += "\n" + tmp.substring(7); } else if (lineType == BufferedMCLReader.INFO) { warns.add(tmp.substring(1)); @@ -317,10 +307,12 @@ public final class MapiSocket { redirects.add(tmp.substring(1)); } } while (lineType != BufferedMCLReader.PROMPT); + if (err.length() > 0) { close(); - throw new MCLException(err.trim()); + throw new MCLException(err); } + if (!redirects.isEmpty()) { if (followRedirects) { // Ok, server wants us to go somewhere else. The list @@ -353,8 +345,7 @@ public final class MapiSocket { if (tmp.equals("database")) { tmp = args[i].substring(pos + 1); if (!tmp.equals(database)) { - warns.add("redirect points to different " + - "database: " + tmp); + warns.add("redirect points to different database: " + tmp); setDatabase(tmp); } } else if (tmp.equals("language")) { @@ -393,7 +384,7 @@ public final class MapiSocket { if (tmp != null && tmp.length() > 0) { tmp = tmp.substring(1).trim(); if (!tmp.isEmpty() && !tmp.equals(database)) { - warns.add("redirect points to different " + "database: " + tmp); + warns.add("redirect points to different database: " + tmp); setDatabase(tmp); } } @@ -424,6 +415,7 @@ public final class MapiSocket { * string is null, a challengeless response is returned. * * @param chalstr the challenge string + * for example: H8sRMhtevGd:mserver:9:PROT10,RIPEMD160,SHA256,SHA1,MD5,COMPRESSION_SNAPPY,COMPRESSION_LZ4:LIT:SHA512: * @param username the username to use * @param password the password to use * @param language the language to use @@ -438,80 +430,76 @@ public final class MapiSocket { String database, String hash ) throws MCLParseException, MCLException, IOException { - String response; - String algo; - // parse the challenge string, split it on ':' String[] chaltok = chalstr.split(":"); - if (chaltok.length <= 4) - throw new MCLParseException("Server challenge string unusable! Challenge contains too few tokens: " + chalstr); + if (chaltok.length <= 5) + throw new MCLParseException("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 { - version = Integer.parseInt(chaltok[2].trim()); // protocol version + version = Integer.parseInt(chaltok[2]); // protocol version } catch (NumberFormatException e) { - throw new MCLParseException("Protocol version unparseable: " + chaltok[3]); + throw new MCLParseException("Protocol version (" + chaltok[2] + ") unparseable as integer."); } // handle the challenge according to the version it is switch (version) { - default: - throw new MCLException("Unsupported protocol version: " + 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 :( */ - if (chaltok[5].equals("SHA512")) { + if (pwhash.equals("SHA512")) { algo = "SHA-512"; - } else if (chaltok[5].equals("SHA384")) { + } else if (pwhash.equals("SHA384")) { algo = "SHA-384"; - } else if (chaltok[5].equals("SHA256")) { + } else if (pwhash.equals("SHA256")) { algo = "SHA-256"; /* NOTE: Java doesn't support SHA-224 */ - } else if (chaltok[5].equals("SHA1")) { + } else if (pwhash.equals("SHA1")) { algo = "SHA-1"; - } else if (chaltok[5].equals("MD5")) { + } else if (pwhash.equals("MD5")) { algo = "MD5"; } else { - throw new MCLException("Unsupported password hash: " + chaltok[5]); + throw new MCLException("Unsupported password hash: " + pwhash); } - try { MessageDigest md = MessageDigest.getInstance(algo); md.update(password.getBytes("UTF-8")); byte[] digest = md.digest(); password = toHex(digest); } catch (NoSuchAlgorithmException e) { - throw new AssertionError("internal error: " + e.toString()); + throw new MCLException("This JVM does not support password hash: " + pwhash + "\n" + e.toString()); } catch (UnsupportedEncodingException e) { - throw new AssertionError("internal error: " + e.toString()); + throw new MCLException("This JVM does not support UTF-8 encoding\n" + e.toString()); } // 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, + // 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 + // 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<String>(Arrays.asList(hashes.toUpperCase().split("[, ]"))); + 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}"; @@ -528,46 +516,41 @@ public final class MapiSocket { algo = "MD5"; pwhash = "{MD5}"; } else { - throw new MCLException("no supported password hashes in " + hashes); + throw new MCLException("no supported hash algorithms found in " + hashes); } - if (algo != null) { - try { - MessageDigest md = MessageDigest.getInstance(algo); - md.update(password.getBytes("UTF-8")); - md.update(challenge.getBytes("UTF-8")); - byte[] digest = md.digest(); - pwhash += toHex(digest); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError("internal error: " + e.toString()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError("internal error: " + e.toString()); - } + try { + MessageDigest md = MessageDigest.getInstance(algo); + md.update(password.getBytes("UTF-8")); + md.update(challenge.getBytes("UTF-8")); + byte[] digest = md.digest(); + pwhash += toHex(digest); + } 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 + _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list