blob: 5a12559419f4643b8a5184d5e0472e2bbbd1e001 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2002, 2012 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Initial Contributors:
* The following IBM employees contributed to the Remote System Explorer
* component that contains this file: David McKnight, Kushal Munir,
* Michael Berger, David Dykstal, Phil Coulthard, Don Yantzi, Eric Simpson,
* Emily Bruner, Mazen Faraj, Adrian Storisteanu, Li Ding, and Kent Hawley.
*
* Contributors:
* David McKnight (IBM) - [193426] don't display exceptions
* David McKnight (IBM) - [226561] [apidoc] Add API markup to RSE Javadocs where extend / implement is allowed
* David McKnight (IBM) - [245714] [dstore] Multiple user ID/password prompts and connect fails
* David McKnight (IBM) - [283613] [dstore] Create a Constants File for all System Properties we support
* David McKnight (IBM) - [378878] [dstore] Need ability to log handshake messages from the authentication/server process to ServerLauncher
* David McKnight (IBM) - [388472] [dstore] need alternative option for getting at server hostname
*******************************************************************************/
package org.eclipse.dstore.core.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Pattern;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.eclipse.dstore.core.model.DE;
import org.eclipse.dstore.core.model.IDataStoreConstants;
import org.eclipse.dstore.core.model.ISSLProperties;
import org.eclipse.dstore.internal.core.model.IDataStoreSystemProperties;
import org.eclipse.dstore.internal.core.server.ServerAttributes;
import org.eclipse.dstore.internal.core.server.ServerReturnCodes;
import org.eclipse.dstore.internal.core.server.ServerSSLProperties;
import org.eclipse.dstore.internal.core.util.ssl.DStoreSSLContext;
/**
* This class is the DataStore daemon. It is used for authenticating users,
* launching DataStore servers under particular user IDs, and providing a client
* with enough information to connect to a launched server.
*
*/
public class ServerLauncher extends Thread {
/**
* An instances of this class get loaded whenever a client requests access
* to a DataStore server. The ConnectionListener attempts to launch a server
* under the client user's ID, communicating back information to the client
* so that if may connect to the launched server. If the authentification
* and connection to the server are successful, ConnectionListener continues
* to monitor the server connection until it is terminated.
*/
public class ConnectionListener extends Thread implements
HandshakeCompletedListener {
private Socket _socket;
private PrintWriter _writer;
private BufferedReader _reader;
private Process _serverProcess;
private String _port;
private boolean _done;
private BufferedReader _outReader;
private BufferedReader _errReader;
/**
* Constructor
*
* @param socket
* a socket to the daemon
*/
public ConnectionListener(Socket socket) {
_socket = socket;
try {
_writer = new PrintWriter(new OutputStreamWriter(_socket
.getOutputStream(), DE.ENCODING_UTF_8));
_reader = new BufferedReader(new InputStreamReader(_socket
.getInputStream(), DE.ENCODING_UTF_8));
} catch (java.io.IOException e) {
String msg = "ServerLauncher:" + e; //$NON-NLS-1$
System.out.println(msg);
logError(msg, e);
}
}
/**
* Called when shutdown
*/
public void finalize() throws Throwable {
if (_serverProcess != null) {
_serverProcess.destroy();
}
super.finalize();
}
/**
* Listens to the connection and prints any output while the connection
* is active
*/
public void run() {
_done = true;
if (listen()) {
if (_serverProcess != null) {
_done = false;
try {
String line = null;
while ((_outReader != null)
&& ((line = _outReader.readLine()) != null)) {
if (line.equals(ServerReturnCodes.RC_FINISHED)) {
break;
} else {
System.out.println(line);
}
}
if (_outReader != null) {
_outReader.close();
}
if (_errReader != null) {
_errReader.close();
}
_serverProcess.waitFor();
} catch (Exception e) {
String msg = "ServerLauncher:" + e; //$NON-NLS-1$
System.out.println(msg);
logError(msg, e);
}
}
String msg = "finished on port " + _port; //$NON-NLS-1$
System.out.println(msg);
_outReader = null;
_errReader = null;
_serverProcess = null;
_done = true;
} else {
_done = true;
}
}
/**
* Indicates whether the connection has terminated or not
*
* @return true if the connection has terminated
*/
public boolean isDone() {
return _done;
}
/**
* Returns the DataStore server port used
*
* @return the server port
*/
public String getServerPort() {
return _port;
}
private boolean isPortInRange(String portStr, String portRange) {
if (portRange != null) {
String[] range = portRange.split("-"); //$NON-NLS-1$
if (range.length == 2) {
int lPort = 0;
int hPort = 0;
int port = 0;
try {
lPort = Integer.parseInt(range[0]);
hPort = Integer.parseInt(range[1]);
port = Integer.parseInt(portStr);
} catch (Exception e) {
}
return (port >= lPort && port <= hPort);
}
}
return true;
}
/**
* Attempt to start a new DataStore server. The port and the ticket for
* a newly started DataStore are captured and sent back to the client so
* that it may connect to the server.
*
* @return whether the server started successfully
*/
public boolean listen()
{
boolean connected = false;
String user = null;
String password = null;
_port = null;
boolean problemReadingSocket = false;
try
{
String userTmp = _reader.readLine();
if (Pattern.matches("\\w+", userTmp)) {
user = userTmp;
}
password = _reader.readLine();
String portTmp = _reader.readLine();
if (Pattern.matches("\\d{5}", portTmp)) {
_port = portTmp;
}
}
catch (IOException e)
{
// don't display exceptions 193426
//e.printStackTrace();
_port = "0"; //$NON-NLS-1$
// this is probably an SSL exception
_writer.println(IDataStoreConstants.SERVER_FAILURE + e);
problemReadingSocket = true;
}
if (_serverPortRange != null && (_port == null || _port.equals("0"))) //$NON-NLS-1$
{
_port = _serverPortRange;
}
if (!problemReadingSocket)
{
boolean isError = false;
if (_serverPortRange != null && _port != _serverPortRange)
{
if (!isPortInRange(_port, _serverPortRange))
{
String message = IDataStoreConstants.PORT_OUT_RANGE;
message += _serverPortRange;
_writer.println(message);
isError = true;
}
}
if (!isError)
{
// start new server
try
{
String launchStatus = null;
String ticket = new String("" + System.currentTimeMillis()); //$NON-NLS-1$
String theOS = System.getProperty("os.name"); //$NON-NLS-1$
String timeout = "120000"; //$NON-NLS-1$
if (!theOS.toLowerCase().startsWith("win")) //$NON-NLS-1$
{
// assuming unix compatable
//
// Get the property which
// contains the authorization
// script path
//
String authPath = System.getProperty(IDataStoreSystemProperties.RSE_AUTH);
File authFile = null;
if (authPath != null && authPath.length() > 0)
{
authFile = new File(authPath);
}
if (authFile == null || !authFile.exists())
{
authPath = "perl " + _path + File.separator + "auth.pl"; //$NON-NLS-1$ //$NON-NLS-2$
}
String authString =
authPath
+ " " //$NON-NLS-1$
+ user
+ " " //$NON-NLS-1$
+ _path
+ " " //$NON-NLS-1$
+ _port
+ " " //$NON-NLS-1$
+ timeout
+ " " //$NON-NLS-1$
+ ticket
+ " " //$NON-NLS-1$
+ System.getProperty("java.home") //$NON-NLS-1$
;
String[] authArray = { "sh", "-c", authString }; //$NON-NLS-1$ //$NON-NLS-2$
// test password
_serverProcess = Runtime.getRuntime().exec(authArray);
_outReader = new BufferedReader(new InputStreamReader(_serverProcess.getInputStream()));
_errReader = new BufferedReader(new InputStreamReader(_serverProcess.getErrorStream()));
BufferedWriter inWriter = new BufferedWriter(new OutputStreamWriter(_serverProcess.getOutputStream()));
// write password
if (password != null)
{
inWriter.write(password);
inWriter.newLine();
inWriter.flush();
launchStatus = _outReader.readLine();
}
}
else
{
// launch new server
String[] cmdArray =
{
"java", //$NON-NLS-1$
"-DA_PLUGIN_PATH=" + _path, //$NON-NLS-1$
"-DDSTORE_SPIRIT_ON=true", //$NON-NLS-1$
"org.eclipse.dstore.core.server.Server", //$NON-NLS-1$
_port,
timeout,
ticket};
_serverProcess = Runtime.getRuntime().exec(cmdArray);
_outReader = new BufferedReader(new InputStreamReader(_serverProcess.getInputStream()));
_errReader = new BufferedReader(new InputStreamReader(_serverProcess.getErrorStream()));
launchStatus = "success"; //$NON-NLS-1$
}
logMessage("launch status = "+launchStatus); //$NON-NLS-1$
if ((launchStatus == null) || !launchStatus.equals("success")) //$NON-NLS-1$
{
_writer.println(IDataStoreConstants.AUTHENTICATION_FAILED);
}
else
{
// look for the server startup string, it needs to occur
// somewhere in the line.
String status = _errReader.readLine();
logMessage("status = "+status); //$NON-NLS-1$
while (status!=null && (status.indexOf(ServerReturnCodes.RC_DSTORE_SERVER_MAGIC) < 0))
{
status = _errReader.readLine();
}
// now read the real server status
if (status != null)
{
status = _errReader.readLine();
}
if ((status != null) && status.equals(ServerReturnCodes.RC_SUCCESS))
{
_port = _errReader.readLine();
_errReader.readLine();
_writer.println(IDataStoreConstants.CONNECTED);
_writer.println(_port);
_writer.println(ticket);
String msg = "launched new server on " + _port; //$NON-NLS-1$
System.out.println(msg);
logMessage(msg);
connected = true;
}
else
{
if (status == null)
{
status = new String(IDataStoreConstants.UNKNOWN_PROBLEM);
}
// TODO Make sure that the client doesnt try
// connecting forever
_writer.println(status);
_serverProcess.destroy();
_serverProcess = null;
_outReader.close();
_outReader = null;
_errReader.close();
_errReader = null;
}
}
}
catch (IOException e)
{
_writer.println(IDataStoreConstants.SERVER_FAILURE + e);
}
}
}
_writer.flush();
// close socket
try
{
_socket.close();
}
catch (IOException e)
{
String msg = "ServerLauncher:" + e; //$NON-NLS-1$
System.out.println(msg);
logError(msg, e);
}
return connected;
}
public void handshakeCompleted(HandshakeCompletedEvent event) {
String msg = "handshake completed"; //$NON-NLS-1$
System.out.println(msg);
System.out.println(event);
logMessage(msg);
}
}
private ServerSocket _serverSocket;
private String _path;
private ArrayList _connections;
private String _serverPortRange;
private ISSLProperties _sslProperties;
private boolean _logDaemon = false;
private RandomAccessFile _logFile = null;
public static int DEFAULT_DAEMON_PORT = 4075;
/**
* Constructor
*/
public ServerLauncher() {
String pluginPath = System.getProperty(IDataStoreSystemProperties.A_PLUGIN_PATH);
if (pluginPath == null) {
System.out.println("A_PLUGIN_PATH is not defined"); //$NON-NLS-1$
System.exit(-1);
}
else
{
_path = pluginPath.trim();
_connections = new ArrayList();
init(DEFAULT_DAEMON_PORT + ""); //$NON-NLS-1$
}
}
/**
* Constructor
*
* @param portStr
* the port for the daemon socket to run on
*/
public ServerLauncher(String portStr) {
String pluginPath = System.getProperty(IDataStoreSystemProperties.A_PLUGIN_PATH);
if (pluginPath == null) {
System.out.println("A_PLUGIN_PATH is not defined"); //$NON-NLS-1$
System.exit(-1);
}
else
{
_path = pluginPath.trim();
_connections = new ArrayList();
init(portStr);
}
}
/**
* Constructor
*
* @param portStr
* the port for the daemon socket to run on
* @param serverPortRange
* the port range for launched servers
*/
public ServerLauncher(String portStr, String serverPortRange) {
String pluginPath = System.getProperty(IDataStoreSystemProperties.A_PLUGIN_PATH);
if (pluginPath == null) {
System.out.println("A_PLUGIN_PATH is not defined"); //$NON-NLS-1$
System.exit(-1);
}
else
{
_path = pluginPath.trim();
_serverPortRange = serverPortRange;
_connections = new ArrayList();
init(portStr);
}
}
private String getKeyStoreLocation() {
return _sslProperties.getDaemonKeyStorePath();
}
private String getKeyStorePassword() {
return _sslProperties.getDaemonKeyStorePassword();
}
/**
* initializes the DataStore daemon
*
* @param portStr the daemon port
*/
public void init(String portStr) {
String logdaemonLocation = System.getProperty("logdaemonpath"); //$NON-NLS-1$
if (logdaemonLocation != null && logdaemonLocation.length() > 0){
// create temp file for logging
File traceFileHandle = new File(logdaemonLocation);
if (!traceFileHandle.exists()){
try { // try to create it
traceFileHandle.createNewFile();
} catch (IOException e) {
}
}
if (traceFileHandle.canWrite()){
try {
_logFile = new RandomAccessFile(traceFileHandle, "rw"); //$NON-NLS-1$
_logFile.seek(traceFileHandle.length());
logMessage("-----------------------------------------"); //$NON-NLS-1$
logMessage("Start Tracing at " + System.currentTimeMillis()); //$NON-NLS-1$
_logDaemon = true;
}
catch (Exception e){
}
}
}
// create server socket from port
_sslProperties = new ServerSSLProperties();
// determine if portStr is a port range or just a port
String[] range = portStr.split("-"); //$NON-NLS-1$
if (range.length == 2) {
int lPort = 0;
int hPort = 0;
try {
lPort = Integer.parseInt(range[0]);
hPort = Integer.parseInt(range[1]);
} catch (Exception e) {
}
boolean socketBound = false;
for (int i = lPort; i < hPort && !socketBound; i++) {
// create server socket from port
try {
if (_sslProperties.usingSSL()) {
String keyStoreFileName = getKeyStoreLocation();
String keyStorePassword = getKeyStorePassword();
try {
SSLContext sslContext = DStoreSSLContext
.getServerSSLContext(keyStoreFileName,
keyStorePassword);
_serverSocket = sslContext.getServerSocketFactory()
.createServerSocket(i);
} catch (Exception e) {
// don't display exceptions 193426
//e.printStackTrace();
}
} else {
_serverSocket = new ServerSocket(i);
}
if (_serverSocket != null
&& _serverSocket.getLocalPort() > 0) {
socketBound = true;
String msg = "Daemon running on: " + ServerAttributes.getHostName() + ", port: " + i; //$NON-NLS-1$ //$NON-NLS-2$
System.out.println(msg);
logMessage(msg);
}
} catch (UnknownHostException e) {
String msg = "Networking problem, can't resolve local host"; //$NON-NLS-1$
System.err.println(msg);
// don't display exceptions 193426
logError(msg, e);
System.exit(-1);
} catch (BindException e) {
String msg = "socket taken on " + i; //$NON-NLS-1$
System.err.println(msg);
logError(msg, e);
// keep going
} catch (IOException e) {
String msg = "Failure to create ServerSocket"; //$NON-NLS-1$
System.err.println(msg);
// don't display exceptions 193426
//e.printStackTrace();
logError(msg, e);
System.exit(-1);
}
}
} else {
int port = Integer.parseInt(portStr);
try {
if (_sslProperties.usingSSL()) {
String keyStoreFileName = getKeyStoreLocation();
String keyStorePassword = getKeyStorePassword();
try {
SSLContext sslContext = DStoreSSLContext
.getServerSSLContext(keyStoreFileName,
keyStorePassword);
_serverSocket = sslContext.getServerSocketFactory()
.createServerSocket(port);
} catch (Exception e) {
// don't display exceptions 193426
//e.printStackTrace();
System.err.println(e.getMessage());
logError(e.getMessage(), e);
System.exit(-1);
}
} else {
_serverSocket = new ServerSocket(port);
}
String msg = "Daemon running on: " //$NON-NLS-1$
+ ServerAttributes.getHostName() + ", port: " //$NON-NLS-1$
+ port;
System.out.println(msg);
logMessage(msg);
} catch (UnknownHostException e) {
String msg = "Networking problem, can't resolve local host"; //$NON-NLS-1$
System.err.println(msg);
// don't display exceptions 193426
//e.printStackTrace();
logError(msg, e);
System.exit(-1);
} catch (IOException e) {
String msg = "Failure to create ServerSocket"; //$NON-NLS-1$
System.err.println(msg);
// don't display exceptions 193426
//e.printStackTrace();
logError(msg, e);
System.exit(-1);
}
}
}
private void logMessage(String msg){
if (_logDaemon && _logFile != null){
try {
_logFile.writeBytes((new Date()).toString() + ": "); //$NON-NLS-1$
_logFile.writeBytes(msg);
_logFile.writeBytes(System.getProperty("line.separator")); //$NON-NLS-1$
}
catch (IOException e)
{
}
}
}
private void logError(String msg, Throwable e){
if (_logDaemon && _logFile != null){
try {
_logFile.writeBytes((new Date()).toString() + ": "); //$NON-NLS-1$
_logFile.writeBytes(msg);
_logFile.writeBytes(System.getProperty("line.separator")); //$NON-NLS-1$
StackTraceElement[] stack = e.getStackTrace();
for (int i = 0;i<stack.length;i++){
_logFile.writeBytes(stack[i].toString());
_logFile.writeBytes(System.getProperty("line.separator")); //$NON-NLS-1$
}
_logFile.writeBytes(System.getProperty("line.separator")); //$NON-NLS-1$
}
catch (IOException ex)
{
}
}
}
/**
* Return the connection listener for the specified port if there is one
*
* @param port
* the port
* @return the listener associated with the port
*/
protected ConnectionListener getListenerForPort(String port) {
for (int i = 0; i < _connections.size(); i++) {
ConnectionListener listener = (ConnectionListener) _connections
.get(i);
if (listener.getServerPort().equals(port)) {
return listener;
}
}
return null;
}
/**
* Run the daemon
*/
public void run() {
while (true) {
try {
boolean connectionOkay = true;
Socket newSocket = _serverSocket.accept();
if (_sslProperties.usingSSL()) {
SSLSocket sslSocket = (SSLSocket) newSocket;
sslSocket
.addHandshakeCompletedListener(new HandshakeCompletedListener() {
public void handshakeCompleted(
HandshakeCompletedEvent event) {
String msg = "handshake completed"; //$NON-NLS-1$
System.out.println(msg);
logMessage(msg);
}
});
SSLSession session = sslSocket.getSession();
if (session == null) {
String msg = "handshake failed"; //$NON-NLS-1$
System.out.println(msg);
logMessage(msg);
sslSocket.close();
connectionOkay = false;
}
}
if (connectionOkay) {
ConnectionListener listener = new ConnectionListener(
newSocket);
listener.start();
_connections.add(listener);
}
} catch (IOException ioe) {
String msg = "Server: error initializing socket: " + ioe; //$NON-NLS-1$
System.err.println(msg);
logMessage(msg);
System.exit(-1);
}
}
}
/**
* Entry point into the DataStore daemon
*
* @param args
* the port for the daemon to run on (default is 4075).
* Optionally, the second arg specifies whether to use SSL or
* not. an optional second arg can be used to specify the port
* range of servers that get launched
*/
public static void main(String args[]) {
if (args.length == 2) {
ServerLauncher theServer = new ServerLauncher(args[0], args[1]);
theServer.start();
} else if (args.length == 1) {
ServerLauncher theServer = new ServerLauncher(args[0]);
theServer.start();
} else {
ServerLauncher theServer = new ServerLauncher();
theServer.start();
}
}
}