package net.i2p.sam;
/*
* free (adj.): unencumbered; not under the control of others
* Written by human in 2004 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.IOException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.util.Log;
import net.i2p.util.PasswordManager;
import net.i2p.util.VersionComparator;
/**
* SAM handler factory class.
*/
class SAMHandlerFactory {
private static final String VERSION = "3.3";
private static final int HELLO_TIMEOUT = 60*1000;
/**
* Return the right SAM handler depending on the protocol version
* required by the client.
*
* @param s Socket attached to SAM client
* @param i2cpProps config options for our i2cp connection
* @throws SAMException if the connection handshake (HELLO message) was malformed
* @return A SAM protocol handler, or null if the client closed before the handshake
*/
public static SAMHandler createSAMHandler(SocketChannel s, Properties i2cpProps,
SAMBridge parent) throws SAMException {
String line;
Log log = I2PAppContext.getGlobalContext().logManager().getLog(SAMHandlerFactory.class);
try {
Socket sock = s.socket();
sock.setKeepAlive(true);
StringBuilder buf = new StringBuilder(128);
ReadLine.readLine(sock, buf, HELLO_TIMEOUT);
sock.setSoTimeout(0);
line = buf.toString();
} catch (SocketTimeoutException e) {
throw new SAMException("Timeout waiting for HELLO VERSION", e);
} catch (IOException e) {
throw new SAMException("Error reading from socket", e);
} catch (RuntimeException e) {
throw new SAMException("Unexpected error", e);
}
if (log.shouldDebug())
log.debug("New message received: [" + line + ']');
// Message format: HELLO VERSION [MIN=v1] [MAX=v2]
Properties props = SAMUtils.parseParams(line);
if (!"HELLO".equals(props.remove(SAMUtils.COMMAND)) ||
!"VERSION".equals(props.remove(SAMUtils.OPCODE))) {
throw new SAMException("Must start with HELLO VERSION");
}
String minVer = props.getProperty("MIN");
if (minVer == null) {
//throw new SAMException("Missing MIN parameter in HELLO VERSION message");
// MIN optional as of 0.9.14
minVer = "1";
}
String maxVer = props.getProperty("MAX");
if (maxVer == null) {
//throw new SAMException("Missing MAX parameter in HELLO VERSION message");
// MAX optional as of 0.9.14
maxVer = "99.99";
}
String ver = chooseBestVersion(minVer, maxVer);
if (ver == null) {
SAMHandler.writeString("HELLO REPLY RESULT=NOVERSION\n", s);
return null;
}
if (Boolean.parseBoolean(i2cpProps.getProperty(SAMBridge.PROP_AUTH))) {
String user = props.getProperty("USER");
String pw = props.getProperty("PASSWORD");
if (user == null || pw == null) {
if (user == null)
log.logAlways(Log.WARN, "SAM authentication failed");
else
log.logAlways(Log.WARN, "SAM authentication failed, user: " + user);
throw new SAMException("USER and PASSWORD required");
}
String savedPW = i2cpProps.getProperty(SAMBridge.PROP_PW_PREFIX + user + SAMBridge.PROP_PW_SUFFIX);
if (savedPW == null) {
log.logAlways(Log.WARN, "SAM authentication failed, user: " + user);
throw new SAMException("Authorization failed");
}
PasswordManager pm = new PasswordManager(I2PAppContext.getGlobalContext());
if (!pm.checkHash(savedPW, pw)) {
log.logAlways(Log.WARN, "SAM authentication failed, user: " + user);
throw new SAMException("Authorization failed");
}
}
// Let's answer positively
if (!SAMHandler.writeString("HELLO REPLY RESULT=OK VERSION=" + ver + "\n", s))
throw new SAMException("Error writing to socket");
// ...and instantiate the right SAM handler
int verMajor = getMajor(ver);
int verMinor = getMinor(ver);
SAMHandler handler;
try {
switch (verMajor) {
case 1:
handler = new SAMv1Handler(s, verMajor, verMinor, i2cpProps, parent);
break;
case 2:
handler = new SAMv2Handler(s, verMajor, verMinor, i2cpProps, parent);
break;
case 3:
handler = new SAMv3Handler(s, verMajor, verMinor, i2cpProps, parent);
break;
default:
log.error("BUG! Trying to initialize the wrong SAM version!");
throw new SAMException("BUG! (in handler instantiation)");
}
} catch (IOException e) {
log.error("Error creating the handler for version "+verMajor, e);
throw new SAMException("IOException caught during SAM handler instantiation");
}
return handler;
}
/*
* @return "x.y" the best version we can use, or null on failure
*/
private static String chooseBestVersion(String minVer, String maxVer) {
if (VersionComparator.comp(VERSION, minVer) >= 0 &&
VersionComparator.comp(VERSION, maxVer) <= 0)
return VERSION;
if (VersionComparator.comp("3.2", minVer) >= 0 &&
VersionComparator.comp("3.2", maxVer) <= 0)
return "3.2";
if (VersionComparator.comp("3.1", minVer) >= 0 &&
VersionComparator.comp("3.1", maxVer) <= 0)
return "3.1";
// in VersionComparator, "3" < "3.0" so
// use comparisons carefully
if (VersionComparator.comp("3.0", minVer) >= 0 &&
VersionComparator.comp("3", maxVer) <= 0)
return "3.0";
if (VersionComparator.comp("2.0", minVer) >= 0 &&
VersionComparator.comp("2", maxVer) <= 0)
return "2.0";
if (VersionComparator.comp("1.0", minVer) >= 0 &&
VersionComparator.comp("1", maxVer) <= 0)
return "1.0";
return null;
}
/* Get the major protocol version from a string, or -1 */
private static int getMajor(String ver) {
if (ver == null)
return -1;
int dot = ver.indexOf('.');
if (dot == 0)
return -1;
if (dot > 0)
ver = ver.substring(0, dot);
try {
return Integer.parseInt(ver);
} catch (NumberFormatException e) {
return -1;
}
}
/* Get the minor protocol version from a string, or -1 */
private static int getMinor(String ver) {
if ( (ver == null) || (ver.indexOf('.') < 0) )
return -1;
try {
String major = ver.substring(ver.indexOf('.') + 1);
return Integer.parseInt(major);
} catch (NumberFormatException e) {
return -1;
} catch (ArrayIndexOutOfBoundsException e) {
return -1;
}
}
}