/*
* TeleStax, Open Source Cloud Communications Copyright 2012.
* and individual contributors
* by the @authors tag. See the copyright.txt 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.mobicents.ss7.management.console;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.security.Principal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javolution.util.FastList;
import javolution.util.FastSet;
import org.apache.log4j.Logger;
import org.jboss.security.SecurityContext;
import org.jboss.security.SecurityContextFactory;
import org.jboss.security.audit.AuditEvent;
import org.jboss.security.audit.AuditLevel;
import org.mobicents.protocols.ss7.scheduler.Scheduler;
import org.mobicents.protocols.ss7.scheduler.Task;
import org.mobicents.ss7.management.transceiver.ChannelProvider;
import org.mobicents.ss7.management.transceiver.ChannelSelectionKey;
import org.mobicents.ss7.management.transceiver.ChannelSelector;
import org.mobicents.ss7.management.transceiver.Message;
import org.mobicents.ss7.management.transceiver.MessageFactory;
import org.mobicents.ss7.management.transceiver.ShellChannel;
import org.mobicents.ss7.management.transceiver.ShellServerChannel;
/**
* @author amit bhayani
*
*/
public abstract class ShellServer extends Task implements ShellServerMBean {
Logger logger = Logger.getLogger(ShellServer.class);
public static final String CONNECTED_MESSAGE = "Connected to %s %s %s";
public static final String CONNECTED_AUTHENTICATING_MESSAGE = "Authenticating against configured security realm";
public static final String CONNECTED_AUTHENTICATION_FAILED = "Authentication failed";
public static final String AUDIT_MESSAGE = "message";
public static final String AUDIT_COMMAND = "command";
public static final String AUDIT_COMMAND_RESPONSE = "response";
Version version = Version.instance;
private ChannelProvider provider;
private ShellServerChannel serverChannel;
private ShellChannel channel;
private ChannelSelector selector;
private ChannelSelectionKey skey;
private MessageFactory messageFactory = null;
private String rxMessage = "";
private String txMessage = "";
private volatile boolean started = false;
private String address;
private int port;
private String securityDomain = null;
private String userName = null;
private String password = null;
private SecurityContext securityContext = null;
private SimplePrincipal principal = null;
private final FastList<ShellExecutor> shellExecutors = new FastList<ShellExecutor>();
public ShellServer(Scheduler scheduler, List<ShellExecutor> shellExecutors) throws IOException {
super(scheduler);
this.shellExecutors.addAll(shellExecutors);
}
@Override
public String getAddress() {
return address;
}
@Override
public void setAddress(String address) {
this.address = address;
}
@Override
public int getPort() {
return port;
}
@Override
public void setPort(int port) {
this.port = port;
}
/**
* @return the securityDomain
*/
@Override
public String getSecurityDomain() {
return securityDomain;
}
/**
* @param securityDomain the securityDomain to set
*/
@Override
public void setSecurityDomain(String securityDomain) {
this.securityDomain = securityDomain;
}
public void start() throws IOException, NamingException {
logger.info("Starting SS7 management shell environment");
provider = ChannelProvider.provider();
serverChannel = provider.openServerChannel();
InetSocketAddress inetSocketAddress = new InetSocketAddress(address, port);
serverChannel.bind(inetSocketAddress);
selector = provider.openSelector();
skey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
messageFactory = ChannelProvider.provider().getMessageFactory();
this.logger.info(String.format("ShellExecutor listening at %s", inetSocketAddress));
this.started = true;
this.activate(false);
scheduler.submit(this, scheduler.MANAGEMENT_QUEUE);
if (this.securityDomain != null) {
InitialContext initialContext = new InitialContext();
startSecurityManager(initialContext, securityDomain);
}
}
protected abstract void startSecurityManager(InitialContext initialContext, String securityDomain) throws NamingException;
protected abstract void putPrincipal(Map map, Principal principal);
protected abstract boolean isAuthManagementLoaded();
protected abstract boolean isValid(Principal principal, Object credential);
protected abstract String getLocalSecurityDomain();
public void stop() {
this.started = false;
this.resetSecurity();
try {
skey.cancel();
if (this.channel != null) {
this.channel.close();
this.channel = null;
}
serverChannel.close();
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
this.logger.info("Stopped ShellExecutor service");
}
@Override
public int getQueueNumber() {
return scheduler.MANAGEMENT_QUEUE;
}
public long perform() {
if (!this.started)
return 0;
try {
FastSet<ChannelSelectionKey> keys = selector.selectNow();
for (FastSet.Record record = keys.head(), end = keys.tail(); (record = record.getNext()) != end;) {
ChannelSelectionKey key = (ChannelSelectionKey) keys.valueOf(record);
if (key.isAcceptable()) {
accept();
} else if (key.isReadable()) {
ShellChannel chan = (ShellChannel) key.channel();
Message msg = (Message) chan.receive();
if (msg != null) {
rxMessage = msg.toString();
logger.info("received command : " + rxMessage);
if (rxMessage.compareTo("disconnect") == 0) {
this.txMessage = "Bye";
if (this.securityDomain != null) {
Map map = new HashMap();
map.put(AUDIT_MESSAGE, "logout success");
putPrincipal(map, principal);
this.securityContext.getAuditManager().audit(new AuditEvent(AuditLevel.SUCCESS, map));
}
chan.send(messageFactory.createMessage(txMessage));
} else if (this.securityDomain != null && this.userName == null) {
// The first incoming message should be username
this.userName = rxMessage;
this.txMessage = " ";
chan.send(messageFactory.createMessage(txMessage));
// TODO Authentication
} else if (this.securityDomain != null && this.password == null) {
// The second incoming message should be password
this.password = rxMessage;
this.txMessage = "";
if (!isAuthManagementLoaded()) {
logger.error("Cant authenticate because AuthenticationManagement is null!");
} else {
this.principal = new SimplePrincipal(this.userName);
boolean isValid = this.isValid(principal, this.password);
if (!isValid) {
chan.send(messageFactory.createMessage(CONNECTED_AUTHENTICATION_FAILED));
logger.warn(String.format("Authentication to CLI failed for username=%s", this.userName));
this.txMessage = "Bye";
} else {
// Audit Stuff
this.securityContext = SecurityContextFactory.createSecurityContext(getLocalSecurityDomain());
Map map = new HashMap();
map.put(AUDIT_MESSAGE, "login success");
putPrincipal(map, principal);
this.securityContext.getAuditManager().audit(new AuditEvent(AuditLevel.SUCCESS, map));
this.txMessage = " ";
chan.send(messageFactory.createMessage(txMessage));
}
}
} else {
String[] options = rxMessage.split(" ");
ShellExecutor shellExecutor = null;
for (FastList.Node<ShellExecutor> n = this.shellExecutors.head(), end1 = this.shellExecutors.tail(); (n = n
.getNext()) != end1;) {
ShellExecutor value = n.getValue();
if (value.handles(options[0])) {
shellExecutor = value;
break;
}
}
if (shellExecutor == null) {
logger.warn(String.format("Received command=\"%s\" for which no ShellExecutor is configured ",
rxMessage));
if (this.securityDomain != null) {
Map map = new HashMap();
map.put(AUDIT_COMMAND, rxMessage);
map.put(AUDIT_COMMAND_RESPONSE, "Invalid command");
putPrincipal(map, principal);
this.securityContext.getAuditManager().audit(new AuditEvent(AuditLevel.INFO, map));
}
chan.send(messageFactory.createMessage("Invalid command"));
} else {
this.txMessage = shellExecutor.execute(options);
if (this.securityDomain != null) {
Map map = new HashMap();
map.put(AUDIT_COMMAND, rxMessage);
map.put(AUDIT_COMMAND_RESPONSE, this.txMessage);
putPrincipal(map, principal);
this.securityContext.getAuditManager().audit(new AuditEvent(AuditLevel.INFO, map));
}
chan.send(messageFactory.createMessage(this.txMessage));
}
} // if (rxMessage.compareTo("disconnect")
} // if (msg != null)
// TODO Handle message
rxMessage = "";
} else if (key.isWritable() && txMessage.length() > 0) {
if (this.txMessage.compareTo("Bye") == 0) {
this.closeChannel();
}
this.txMessage = "";
}
}
} catch (IOException e) {
logger.error("IO Exception while operating on ChannelSelectionKey. Client CLI connection will be closed now", e);
try {
this.closeChannel();
} catch (IOException e1) {
logger.error("IO Exception while closing Channel", e);
}
} catch (Exception e) {
logger.error("Exception while operating on ChannelSelectionKey. Client CLI connection will be closed now", e);
try {
this.closeChannel();
} catch (IOException e1) {
logger.error("IO Exception while closing Channel", e);
}
}
if (this.started)
scheduler.submit(this, scheduler.MANAGEMENT_QUEUE);
return 0;
}
private void accept() throws IOException {
ShellChannel channelTmp = serverChannel.accept();
if (logger.isDebugEnabled()) {
logger.debug("Accepting client connection. Remote Address= " + channelTmp.getRemoteAddress());
}
if (this.channel != null) {
String exitmessage = "Already client from " + this.channel.getRemoteAddress()
+ " is connected. Closing this connection";
logger.warn(exitmessage);
channelTmp.sendImmediate(messageFactory.createMessage(exitmessage));
channelTmp.close();
return;
}
this.channel = channelTmp;
skey = channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
if (this.securityDomain == null) {
channel.send(messageFactory.createMessage(String.format(CONNECTED_MESSAGE, this.version.getProperty("name"),
this.version.getProperty("version"), this.version.getProperty("vendor"))));
} else {
String tmp = String.format(CONNECTED_MESSAGE, this.version.getProperty("name"),
this.version.getProperty("version"), this.version.getProperty("vendor"));
tmp = tmp + " " + CONNECTED_AUTHENTICATING_MESSAGE;
channel.send(messageFactory.createMessage(tmp));
}
}
private void closeChannel() throws IOException {
this.resetSecurity();
if (channel != null) {
try {
this.channel.close();
} catch (IOException e) {
logger.error("Error closing channel", e);
}
if (logger.isDebugEnabled()) {
logger.debug("Closed client connection. Remote Address= " + this.channel.getRemoteAddress());
}
this.channel = null;
}
// skey.cancel();
// skey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
private void resetSecurity() {
this.userName = null;
this.password = null;
this.principal = null;
}
}