/*
* Copyright (C) 2006-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program 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 General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ("FLOSS") applications as described in Alfresco's
* FLOSS exception. You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing"
*/
package org.alfresco.jlan.oncrpc.portmap;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import org.alfresco.jlan.debug.Debug;
import org.alfresco.jlan.oncrpc.PortMapping;
import org.alfresco.jlan.oncrpc.Rpc;
import org.alfresco.jlan.oncrpc.RpcPacket;
import org.alfresco.jlan.oncrpc.RpcProcessor;
import org.alfresco.jlan.oncrpc.TcpRpcSessionHandler;
import org.alfresco.jlan.oncrpc.UdpRpcDatagramHandler;
import org.alfresco.jlan.oncrpc.nfs.NFSConfigSection;
import org.alfresco.jlan.server.NetworkServer;
import org.alfresco.jlan.server.ServerListener;
import org.alfresco.jlan.server.Version;
import org.alfresco.jlan.server.config.ServerConfiguration;
/**
* Port Mapper Server Class
*
* @author gkspencer
*/
public class PortMapperServer extends NetworkServer implements RpcProcessor {
// Constants
//
// Server version
private static final String ServerVersion = Version.PortMapServerVersion;
// Default port mapper port
public final static int DefaultPort = 111;
// Maximum request size to accept
public final static int MaxRequestSize = 1024;
// Configuration sections
private NFSConfigSection m_nfsConfig;
// Incoming datagram handler for UDP requests
private UdpRpcDatagramHandler m_udpHandler;
// Incoming session handler for TCP requests
private TcpRpcSessionHandler m_tcpHandler;
// Portmapper port
private int m_port;
// Table of active port mappings
private Hashtable<Integer, PortMapping> m_mappings;
private Hashtable<Integer, PortMapping> m_noVerMappings;
/**
* Class constructor
*
* @param config ServerConfiguration
*/
public PortMapperServer(ServerConfiguration config) {
super("Portmap", config);
// Set the server version
setVersion(ServerVersion);
// Get the NFS configuration
m_nfsConfig = (NFSConfigSection) config.getConfigSection( NFSConfigSection.SectionName);
if ( m_nfsConfig != null) {
// Enable/disable debug output
setDebug( getNFSConfiguration().hasPortMapperDebug());
// Set the port to use
if ( getNFSConfiguration().getPortMapperPort() != 0)
setPort( getNFSConfiguration().getPortMapperPort());
else
setPort(DefaultPort);
// Create the mappings tables
m_mappings = new Hashtable<Integer, PortMapping>();
m_noVerMappings = new Hashtable<Integer, PortMapping>();
}
else
setEnabled( false);
}
/**
* Return the server port
*
* @return int
*/
public final int getPort() {
return m_port;
}
/**
* Return the NFS configuration section
*
* @return NFSConfigSection
*/
private final NFSConfigSection getNFSConfiguration() {
return m_nfsConfig;
}
/**
* Start the portmapper server
*/
public void startServer() {
try {
// Create the UDP RPC handler to accept incoming requests
m_udpHandler = new UdpRpcDatagramHandler("PortMap", "Port", this, this, null, getPort(), MaxRequestSize);
m_udpHandler.initializeSessionHandler(this);
// Start the UDP request listener is a seperate thread
Thread udpThread = new Thread(m_udpHandler);
udpThread.setName("PortMap_UDP");
udpThread.start();
// Create the TCP RPC handler to accept incoming requests
m_tcpHandler = new TcpRpcSessionHandler("PortMap", "Port", this, this, null, getPort(), MaxRequestSize);
m_tcpHandler.initializeSessionHandler(this);
// Start the UDP request listener is a seperate thread
Thread tcpThread = new Thread(m_tcpHandler);
tcpThread.setName("PortMap_TCP");
tcpThread.start();
// Add port mapper entries for the portmapper service
PortMapping portMap = new PortMapping(PortMapper.ProgramId, PortMapper.VersionId, Rpc.UDP, getPort());
addPortMapping(portMap);
portMap = new PortMapping(PortMapper.ProgramId, PortMapper.VersionId, Rpc.TCP, getPort());
addPortMapping(portMap);
}
catch (Exception ex) {
Debug.println(ex);
}
}
/**
* Shutdown the server
*
* @param immediate boolean
*/
public void shutdownServer(boolean immediate) {
// Stop the RPC handlers
if ( m_udpHandler != null) {
m_udpHandler.closeSessionHandler(this);
m_udpHandler = null;
}
if ( m_tcpHandler != null) {
m_tcpHandler.closeSessionHandler(this);
m_tcpHandler = null;
}
// Fire a shutdown notification event
fireServerEvent(ServerListener.ServerShutdown);
}
/**
* Set the server port
*
* @param port int
*/
public final void setPort(int port) {
m_port = port;
}
/**
* Process an RPC request
*
* @param rpc RpcPacket
* @return RpcPacket
* @throws IOException
*/
public RpcPacket processRpc(RpcPacket rpc)
throws IOException {
// Validate the request
if ( rpc.getProgramId() != PortMapper.ProgramId) {
// Request is not for us
rpc.buildAcceptErrorResponse(Rpc.StsProgUnavail);
return rpc;
}
else if ( rpc.getProgramVersion() != PortMapper.VersionId) {
// Request is not for this version of portmapper
rpc.buildProgramMismatchResponse(PortMapper.VersionId, PortMapper.VersionId);
return rpc;
}
// Position the RPC buffer pointer at the start of the call parameters
rpc.positionAtParameters();
// Process the RPC request
RpcPacket response = null;
switch ( rpc.getProcedureId()) {
// Null request
case PortMapper.ProcNull:
response = procNull(rpc);
break;
// Set a port
case PortMapper.ProcSet:
response = procSet(rpc);
break;
// Release a port
case PortMapper.ProcUnSet:
response = procUnSet(rpc);
break;
// Get the port for a service
case PortMapper.ProcGetPort:
response = procGetPort(rpc);
break;
// Dump ports request
case PortMapper.ProcDump:
response = procDump(rpc);
break;
}
// Return the RPC response
return response;
}
/**
* Process the null request
*
* @param rpc RpcPacket
* @return RpcPacket
*/
private final RpcPacket procNull(RpcPacket rpc) {
// Build the response
rpc.buildResponseHeader();
return rpc;
}
/**
* Process the set request
*
* @param rpc RpcPacket
* @return RpcPacket
*/
private final RpcPacket procSet(RpcPacket rpc) {
// Get the call parameters
int progId = rpc.unpackInt();
int verId = rpc.unpackInt();
int proto = rpc.unpackInt();
int port = rpc.unpackInt();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[PortMap] Set port program=" + Rpc.getServiceName(progId) + ", version=" + verId +
", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") + ", port=" + port);
// Check if the port is already mapped
PortMapping portMap = findPortMapping(progId, verId, proto);
int portAdded = Rpc.False;
if ( portMap == null) {
// Add a mapping for the new service
portMap = new PortMapping(progId, verId, proto, port);
if ( addPortMapping(portMap) == true)
portAdded = Rpc.True;
}
// Check if the service is on the same port as the current port mapping, and it is not
// an attempt to set the port mapper service port.
else if ( progId != PortMapper.ProgramId && portMap.getPort() == port) {
// Settings are the same as the existing service settings so accept it
portAdded = Rpc.True;
}
// Build the response header
rpc.buildResponseHeader();
// Pack a boolean indicating if the port was added, or not
rpc.packInt(portAdded);
rpc.setLength();
// Return the response
return rpc;
}
/**
* Process the unset request
*
* @param rpc RpcPacket
* @return RpcPacket
*/
private final RpcPacket procUnSet(RpcPacket rpc) {
// Get the call parameters
int progId = rpc.unpackInt();
int verId = rpc.unpackInt();
int proto = rpc.unpackInt();
int port = rpc.unpackInt();
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[PortMap] UnSet port program=" + Rpc.getServiceName(progId) + ", version=" + verId +
", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") + ", port=" + port);
// Check if the port is mapped, and it is not an attempt to remove a portmapper portt
PortMapping portMap = findPortMapping(progId, verId, proto);
int portRemoved = Rpc.False;
if ( portMap != null && progId != PortMapper.ProgramId) {
// Remove the port mapping
if ( removePortMapping(portMap) == true)
portRemoved = Rpc.True;
}
// Build the response header
rpc.buildResponseHeader();
// Pack a boolean indicating if the port was removed, or not
rpc.packInt(portRemoved);
rpc.setLength();
// Return the response
return rpc;
}
/**
* Process the get port request
*
* @param rpc RpcPacket
* @return RpcPacket
*/
private final RpcPacket procGetPort(RpcPacket rpc) {
// Get the call parameters
int progId = rpc.unpackInt();
int verId = rpc.unpackInt();
int proto = rpc.unpackInt();
// Find the required port mapping
PortMapping portMap = findPortMapping(progId, verId, proto);
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[PortMap] Get port program=" + Rpc.getServiceName(progId) + ", version=" + verId +
", protocol=" + (proto == Rpc.TCP ? "TCP" : "UDP") +
", port=" + ( portMap != null ? portMap.getPort() : 0));
// Build the response header
rpc.buildResponseHeader();
// Pack the port number of the requested RPC service, or zero if not found
rpc.packInt(portMap != null ? portMap.getPort() : 0);
rpc.setLength();
// Return the response
return rpc;
}
/**
* Process the dump request
*
* @param rpc RpcPacket
* @return RpcPacket
*/
private final RpcPacket procDump(RpcPacket rpc) {
// DEBUG
if ( Debug.EnableInfo && hasDebug())
Debug.println("[PortMap] Dump ports request from " + rpc.getClientDetails());
// Build the response
rpc.buildResponseHeader();
// Pack the active port mappings structures
Enumeration enm = m_mappings.elements();
while ( enm.hasMoreElements()) {
// Get the current port mapping
PortMapping portMap = (PortMapping) enm.nextElement();
// Pack the port mapping structure
rpc.packInt(Rpc.True);
rpc.packPortMapping(portMap);
}
// Pack the end of list structure, set the response length
rpc.packInt(Rpc.False);
rpc.setLength();
// Return the response
return rpc;
}
/**
* Add a port mapping to the active list
*
* @param portMap PortMapping
* @return boolean
*/
private final boolean addPortMapping(PortMapping portMap) {
// Check if there is an existing port mapping that matches the new port
Integer key = new Integer(portMap.hashCode());
if ( m_mappings.get(key) != null)
return false;
// Add the port mapping
m_mappings.put( key, portMap);
// Add a port mapping with a version id of zero
key = new Integer( PortMapping.generateHashCode(portMap.getProgramId(), 0, portMap.getProtocol()));
m_noVerMappings.put ( key, portMap);
// Indicate that the mapping was added
return true;
}
/**
* Remove a port mapping from the active list
*
* @param portMap PortMapping
* @return boolean
*/
private final boolean removePortMapping(PortMapping portMap) {
// Remove the port mapping from the active lists
Integer key = new Integer(portMap.hashCode());
Object removedObj = m_mappings.remove(key);
key = new Integer( PortMapping.generateHashCode(portMap.getProgramId(), 0, portMap.getProtocol()));
m_noVerMappings.remove(key);
// Return a status indicating if the mapping was removed
return removedObj != null ? true : false;
}
/**
* Search for a port mapping
*
* @param progId int
* @param verId int
* @param proto int
* @return PortMapping
*/
private final PortMapping findPortMapping(int progId, int verId, int proto) {
// Create a key for the RPC service
Integer key = new Integer(PortMapping.generateHashCode(progId, verId, proto));
// Search for the required port mapping, including the version id
PortMapping portMap = m_mappings.get(key);
if ( portMap == null && verId == 0) {
// Search for the port mapping without the version id
portMap = m_noVerMappings.get(key);
}
// Return the port mapping, or null if not found
return portMap;
}
}