Java tutorial
/******************************************************************************* * Copyright (c) 2007, 2014 Massimiliano Ziccardi * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package it.jnrpe; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.ssl.SslHandler; import io.netty.handler.timeout.IdleStateHandler; import it.jnrpe.commands.CommandInvoker; import it.jnrpe.commands.CommandRepository; import it.jnrpe.events.JNRPEStatusEvent; import it.jnrpe.events.JNRPEStatusEvent.STATUS; import it.jnrpe.net.JNRPEIdleStateHandler; import it.jnrpe.net.JNRPERequestDecoder; import it.jnrpe.net.JNRPEResponseEncoder; import it.jnrpe.net.JNRPEServerHandler; import it.jnrpe.plugins.IPluginRepository; import it.jnrpe.utils.StreamManager; import java.io.IOException; import java.io.InputStream; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collection; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; /** * This class is the real JNRPE worker. It must be used to start listening for * NRPE requests * * @author Massimiliano Ziccardi * @version $Revision: 1.0 $ */ public final class JNRPE { /** * The JNRPE logger. */ private final JNRPELogger LOG = new JNRPELogger(this); /** * The current execution context. */ private final IJNRPEExecutionContext context; /** * Default number of accepted connections. */ static final int DEFAULT_MAX_ACCEPTED_CONNECTIONS = 128; /** * The boss group (see netty documentation). */ private final EventLoopGroup bossGroup = new NioEventLoopGroup(); /** * The worker group (see netty documentation). */ private final EventLoopGroup workerGroup = new NioEventLoopGroup(); /** * The default keystore name (used to perform SSL). */ private static final String KEYSTORE_NAME = "keys.jks"; /** * The default keystore password. */ private static final String KEYSTORE_PWD = "p@55w0rd"; /** * The plugin repository to be used to find the requested plugin. */ private final IPluginRepository pluginRepository; /** * The command repository to be used to find the requested command. */ private final CommandRepository commandRepository; /** * The list of accepted clients. */ private Collection<String> acceptedHostsList = new ArrayList<String>(); /** * Charset that will be used by JNRPE. */ private final Charset charset; /** * The maximum number of concurrent connection allowed by JNRPE. */ private final int maxAcceptedConnections; /** * The maximum number of seconds that JNRPE will wait for a client to send a * command. */ private final int readTimeout; /** * The maximum number of seconds that JNRPE will wait for a plugin to * produce a result. */ private final int writeTimeout; /** * <code>true</code> if $ARGxx$ macros must be expanded. */ private final boolean acceptParams; /** * Instantiates the JNRPE engine. * * @param pluginRepo * The plugin repository object * @param commandRepo * The command repository object * * @deprecated This constructor will be removed as of version 2.0.5. Use * {@link JNRPEBuilder} instead */ @Deprecated public JNRPE(final IPluginRepository pluginRepo, final CommandRepository commandRepo) { if (pluginRepo == null) { throw new IllegalArgumentException("Plugin repository cannot be null"); } if (commandRepo == null) { throw new IllegalArgumentException("Command repository cannot be null"); } pluginRepository = pluginRepo; commandRepository = commandRepo; charset = Charset.forName("UTF-8"); acceptParams = true; maxAcceptedConnections = DEFAULT_MAX_ACCEPTED_CONNECTIONS; readTimeout = 10; writeTimeout = 60; context = new JNRPEExecutionContext(new JNRPEEventBus(), charset); } /** * Initializes the JNRPE worker. * * @param pluginRepo * The repository containing all the installed plugins * @param commandRepo * The repository containing all the configured commands. * @param newCharset * The charset that will be used by JNRPE * @param acceptParameters * Sets if $ARGxx$ macros should be expanded * @deprecated This constructor will be removed as of version 2.0.5. Use * {@link JNRPEBuilder} instead */ @Deprecated public JNRPE(final IPluginRepository pluginRepo, final CommandRepository commandRepo, final Charset newCharset, final boolean acceptParameters) { if (pluginRepo == null) { throw new IllegalArgumentException("Plugin repository cannot be null"); } if (commandRepo == null) { throw new IllegalArgumentException("Command repository cannot be null"); } pluginRepository = pluginRepo; commandRepository = commandRepo; charset = newCharset; acceptParams = acceptParameters; maxAcceptedConnections = DEFAULT_MAX_ACCEPTED_CONNECTIONS; readTimeout = 10; writeTimeout = 60; context = new JNRPEExecutionContext(new JNRPEEventBus(), newCharset); } /** * Constructor used by the {@link JNRPEBuilder} to build an immutable * instance of {@link JNRPE}. * * @param pluginRepo * The plugin repository object * @param commandRepo * The command repository object * @param newCharset * The charset that JNRPE will use * @param acceptParameters * Sets if $ARGxx$ macros should be expanded * @param acceptedHostsCollection * The list of accepted client hosts * @param maxConnections * The maximum number of concurrent connections * @param readTimeoutSeconds * The maximum number of seconds to wait for the client to send * the command * @param writeTimeoutSeconds * The maximum number of seconds to wait for a plugin to return a * result */ JNRPE(final IPluginRepository pluginRepo, final CommandRepository commandRepo, final Charset newCharset, final boolean acceptParameters, final Collection<String> acceptedHostsCollection, final int maxConnections, final int readTimeoutSeconds, final int writeTimeoutSeconds) { if (pluginRepo == null) { throw new IllegalArgumentException("Plugin repository cannot be null"); } if (commandRepo == null) { throw new IllegalArgumentException("Command repository cannot be null"); } pluginRepository = pluginRepo; commandRepository = commandRepo; charset = newCharset; acceptParams = acceptParameters; acceptedHostsList = acceptedHostsCollection; maxAcceptedConnections = maxConnections; readTimeout = readTimeoutSeconds; writeTimeout = writeTimeoutSeconds; context = new JNRPEExecutionContext(new JNRPEEventBus(), newCharset); } /** * Instructs the server to listen to the given IP/port. * * @param address * The address to bind to * @param port * The port to bind to * @throws UnknownHostException * - */ public void listen(final String address, final int port) throws UnknownHostException { listen(address, port, true); } /** * Creates, configures and returns the SSL engine. * * @return the SSL Engine * @throws KeyStoreException * on keystore errorss * @throws CertificateException * on certificate errors * @throws IOException * on I/O errors * @throws UnrecoverableKeyException * if key is unrecoverable * @throws KeyManagementException * key management error */ private SSLEngine getSSLEngine() throws KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException { // Open the KeyStore Stream final StreamManager streamManager = new StreamManager(); SSLContext ctx; KeyManagerFactory kmf; try { final InputStream ksStream = getClass().getClassLoader().getResourceAsStream(KEYSTORE_NAME); streamManager.handle(ksStream); ctx = SSLContext.getInstance("SSLv3"); kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); final KeyStore ks = KeyStore.getInstance("JKS"); char[] passphrase = KEYSTORE_PWD.toCharArray(); ks.load(ksStream, passphrase); kmf.init(ks, passphrase); ctx.init(kmf.getKeyManagers(), null, new java.security.SecureRandom()); } catch (NoSuchAlgorithmException e) { throw new SSLException("Unable to initialize SSLSocketFactory" + e.getMessage(), e); } finally { streamManager.closeAll(); } return ctx.createSSLEngine(); } /** * Creates and returns a configured NETTY ServerBootstrap object. * * @param useSSL * <code>true</code> if SSL must be used. * @return the server bootstrap object */ private ServerBootstrap getServerBootstrap(final boolean useSSL) { final CommandInvoker invoker = new CommandInvoker(pluginRepository, commandRepository, acceptParams, getExecutionContext()); final ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(final SocketChannel ch) throws Exception { if (useSSL) { final SSLEngine engine = getSSLEngine(); engine.setEnabledCipherSuites(engine.getSupportedCipherSuites()); engine.setUseClientMode(false); engine.setNeedClientAuth(false); ch.pipeline().addLast("ssl", new SslHandler(engine)); } ch.pipeline() .addLast(new JNRPERequestDecoder(), new JNRPEResponseEncoder(), new JNRPEServerHandler(invoker, context)) .addLast("idleStateHandler", new IdleStateHandler(readTimeout, writeTimeout, 0)) .addLast("jnrpeIdleStateHandler", new JNRPEIdleStateHandler(context)); } }).option(ChannelOption.SO_BACKLOG, maxAcceptedConnections) .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE); return serverBootstrap; } /** * Starts a new thread that listen for requests. The method is <b>not * blocking</b> * * @param address * The address to bind to * @param port * The listening port * @param useSSL * <code>true</code> if an SSL socket must be created. * @throws UnknownHostException * - */ public void listen(final String address, final int port, final boolean useSSL) throws UnknownHostException { // Bind and start to accept incoming connections. ChannelFuture cf = getServerBootstrap(useSSL).bind(address, port); cf.addListener(new ChannelFutureListener() { public void operationComplete(final ChannelFuture future) throws Exception { if (future.isSuccess()) { context.getEventBus().post(new JNRPEStatusEvent(STATUS.STARTED, this, "JNRPE Server started")); LOG.info(context, "Listening on " + (useSSL ? "SSL/" : "") + address + ":" + port); } else { getExecutionContext().getEventBus() .post(new JNRPEStatusEvent(STATUS.FAILED, this, "JNRPE Server start failed")); LOG.error(context, "Unable to listen on " + (useSSL ? "SSL/" : "") + address + ":" + port, future.cause()); } } }); } /** * Adds an address to the list of accepted hosts. * * @param address * The address to accept * @deprecated The JNRPE object will become immutable as of version 2.0.5. * Use {@link JNRPEBuilder} instead */ @Deprecated public void addAcceptedHost(final String address) { acceptedHostsList.add(address); } /** * Returns the current JNRPE Execution context. An execution context * contains useful informations such the encoding to be used ad the list of * listeners configured to receive the events. * * @return the current JNRPE Execution context. */ public IJNRPEExecutionContext getExecutionContext() { //return new JNRPEExecutionContext(eventBus, charset, pluginRepository, commandRepository); return context; } /** * Shuts down the server. */ public void shutdown() { workerGroup.shutdownGracefully().syncUninterruptibly(); bossGroup.shutdownGracefully().syncUninterruptibly(); getExecutionContext().getEventBus() .post(new JNRPEStatusEvent(STATUS.STOPPED, this, "JNRPE Server stopped")); } /** * Method toString. * @return String */ @Override public String toString() { return "JNRPE [LOG=" + LOG + ", context=" + context + ", bossGroup=" + bossGroup + ", workerGroup=" + workerGroup + ", pluginRepository=" + pluginRepository + ", commandRepository=" + commandRepository + ", acceptedHostsList=" + acceptedHostsList + ", charset=" + charset + ", maxAcceptedConnections=" + maxAcceptedConnections + ", readTimeout=" + readTimeout + ", writeTimeout=" + writeTimeout + ", acceptParams=" + acceptParams + "]"; } }