/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.process;
import static org.jboss.as.process.protocol.StreamUtils.readBoolean;
import static org.jboss.as.process.protocol.StreamUtils.readFully;
import static org.jboss.as.process.protocol.StreamUtils.readInt;
import static org.jboss.as.process.protocol.StreamUtils.readUTFZBytes;
import static org.jboss.as.process.protocol.StreamUtils.readUnsignedByte;
import static org.jboss.as.process.protocol.StreamUtils.safeClose;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.jboss.as.process.logging.ProcessLogger;
import org.jboss.as.process.protocol.Connection;
import org.jboss.as.process.protocol.ConnectionHandler;
import org.jboss.as.process.protocol.MessageHandler;
import org.jboss.as.process.protocol.StreamUtils;
/**
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public final class ProcessControllerServerHandler implements ConnectionHandler {
private final ProcessController processController;
public ProcessControllerServerHandler(final ProcessController controller) {
processController = controller;
}
public MessageHandler handleConnected(final Connection connection) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef("Received connection from %s", connection.getPeerAddress());
return new InitMessageHandler(processController);
}
private static class InitMessageHandler implements MessageHandler {
private final ProcessController processController;
public InitMessageHandler(final ProcessController processController) {
this.processController = processController;
}
public void handleMessage(final Connection connection, final InputStream dataStream) throws IOException {
final int cmd = readUnsignedByte(dataStream);
if (cmd != Protocol.AUTH) {
ProcessLogger.SERVER_LOGGER.receivedUnknownGreetingCode(Integer.valueOf(cmd), connection.getPeerAddress());
connection.close();
return;
}
final int version = StreamUtils.readUnsignedByte(dataStream);
if (version < 1) {
ProcessLogger.SERVER_LOGGER.receivedInvalidVersion(connection.getPeerAddress());
connection.close();
return;
}
final byte[] authCode = new byte[ProcessController.AUTH_BYTES_ENCODED_LENGTH];
StreamUtils.readFully(dataStream, authCode);
final ManagedProcess process = processController.getServerByAuthCode(authCode);
if (process == null) {
ProcessLogger.SERVER_LOGGER.receivedUnknownCredentials(connection.getPeerAddress());
StreamUtils.safeClose(connection);
return;
}
ProcessLogger.SERVER_LOGGER.tracef("Received authentic connection from %s", connection.getPeerAddress());
connection.setMessageHandler(new ConnectedMessageHandler(processController, process.isPrivileged()));
processController.addManagedConnection(connection);
dataStream.close();
// Reset the respawn count for the connecting process
process.resetRespawnCount();
}
public void handleShutdown(final Connection connection) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef("Received end-of-stream for connection");
processController.removeManagedConnection(connection);
connection.shutdownWrites();
}
public void handleFailure(final Connection connection, final IOException e) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef(e, "Received failure of connection");
processController.removeManagedConnection(connection);
connection.close();
}
public void handleFinished(final Connection connection) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef("Connection finished");
processController.removeManagedConnection(connection);
// nothing
}
private static class ConnectedMessageHandler implements MessageHandler {
private final boolean isPrivileged;
private final ProcessController processController;
public ConnectedMessageHandler(final ProcessController processController, final boolean isHostController) {
this.processController = processController;
this.isPrivileged = isHostController;
}
public void handleMessage(final Connection connection, final InputStream dataStream) throws IOException {
ProcessMessageHandler.OperationType operationType = null;
String processName = null;
try {
final int cmd = StreamUtils.readUnsignedByte(dataStream);
switch (cmd) {
case Protocol.SEND_STDIN: {
// HostController only
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.SEND_STDIN;
processName = readUTFZBytes(dataStream);
ProcessLogger.SERVER_LOGGER.tracef("Received send_stdin for process %s", processName);
processController.sendStdin(processName, dataStream);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring send_stdin message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.ADD_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.ADD;
processName = readUTFZBytes(dataStream);
final byte[] authBytes = new byte[ProcessController.AUTH_BYTES_ENCODED_LENGTH];
readFully(dataStream, authBytes);
final int commandCount = readInt(dataStream);
final String[] command = new String[commandCount];
for (int i = 0; i < commandCount; i ++) {
command[i] = readUTFZBytes(dataStream);
}
final int envCount = readInt(dataStream);
final Map<String, String> env = new HashMap<String, String>();
for (int i = 0; i < envCount; i ++) {
env.put(readUTFZBytes(dataStream), readUTFZBytes(dataStream));
}
final String workingDirectory = readUTFZBytes(dataStream);
ProcessLogger.SERVER_LOGGER.tracef("Received add_process for process %s", processName);
final String authKey = new String(authBytes, Charset.forName("US-ASCII"));
processController.addProcess(processName, authKey, Arrays.asList(command), env, workingDirectory, false, false);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring add_process message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.START_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.START;
processName = readUTFZBytes(dataStream);
processController.startProcess(processName);
ProcessLogger.SERVER_LOGGER.tracef("Received start_process for process %s", processName);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring start_process message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.STOP_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.STOP;
processName = readUTFZBytes(dataStream);
// HostController only
processController.stopProcess(processName);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring stop_process message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.REMOVE_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.REMOVE;
processName = readUTFZBytes(dataStream);
processController.removeProcess(processName);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring remove_process message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.REQUEST_PROCESS_INVENTORY: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.INVENTORY;
processController.sendInventory();
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring request_process_inventory message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.RECONNECT_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.REMOVE;
processName = readUTFZBytes(dataStream);
final String scheme = StreamUtils.readUTFZBytes(dataStream);
final String hostName = readUTFZBytes(dataStream);
final int port = readInt(dataStream);
final boolean managementSubsystemEndpoint = readBoolean(dataStream);
final byte[] authBytes = new byte[ProcessController.AUTH_BYTES_ENCODED_LENGTH];
readFully(dataStream, authBytes);
final String authKey = new String(authBytes, Charset.forName("US-ASCII"));
processController.sendReconnectProcess(processName, scheme, hostName, port, managementSubsystemEndpoint, authKey);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring reconnect_process message from untrusted source");
}
dataStream.close();
break;
} case Protocol.SHUTDOWN: {
if (isPrivileged) {
final int exitCode = readInt(dataStream);
new Thread(new Runnable() {
public void run() {
processController.shutdown();
System.exit(exitCode);
}
}).start();
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring shutdown message from untrusted source");
}
break;
}
case Protocol.DESTROY_PROECESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.STOP;
processName = readUTFZBytes(dataStream);
processController.destroyProcess(processName);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring destroy_process message from untrusted source");
}
dataStream.close();
break;
}
case Protocol.KILL_PROCESS: {
if (isPrivileged) {
operationType = ProcessMessageHandler.OperationType.STOP;
processName = readUTFZBytes(dataStream);
processController.killProcess(processName);
} else {
ProcessLogger.SERVER_LOGGER.tracef("Ignoring kill_process message from untrusted source");
}
dataStream.close();
break;
}
default: {
ProcessLogger.SERVER_LOGGER.receivedUnknownMessageCode(Integer.valueOf(cmd));
// unknown
dataStream.close();
}
}
} catch(IOException e) {
if(operationType != null && processName != null) {
safeClose(dataStream);
try {
final OutputStream os = connection.writeMessage();
try {
os.write(Protocol.OPERATION_FAILED);
os.write(operationType.getCode());
StreamUtils.writeUTFZBytes(os, processName);
os.close();
} finally {
safeClose(os);
}
} catch (IOException ignore) {
ProcessLogger.ROOT_LOGGER.debugf(ignore, "failed to write operation failed message");
}
}
throw e;
} finally {
safeClose(dataStream);
}
}
public void handleShutdown(final Connection connection) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef("Received end-of-stream for connection");
processController.removeManagedConnection(connection);
connection.shutdownWrites();
}
public void handleFailure(final Connection connection, final IOException e) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef(e, "Received failure of connection");
processController.removeManagedConnection(connection);
connection.close();
}
public void handleFinished(final Connection connection) throws IOException {
ProcessLogger.SERVER_LOGGER.tracef("Connection finished");
processController.removeManagedConnection(connection);
connection.close();
}
}
}
}