strat.mining.stratum.proxy.Launcher.java Source code

Java tutorial

Introduction

Here is the source code for strat.mining.stratum.proxy.Launcher.java

Source

/**
 * stratum-proxy is a proxy supporting the crypto-currency stratum pool mining
 * protocol.
 * Copyright (C) 2014-2015  Stratehm (stratehm@hotmail.com)
 *
 * 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 3 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 multipool-stats-backend. If not, see <http://www.gnu.org/licenses/>.
 */
package strat.mining.stratum.proxy;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Date;
import java.util.List;

import javax.ws.rs.core.UriBuilder;

import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
import org.glassfish.grizzly.http.CompressionConfig.CompressionMode;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.grizzly.http.server.StaticHttpHandlerBase;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.kohsuke.args4j.CmdLineException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import strat.mining.stratum.proxy.configuration.ConfigurationManager;
import strat.mining.stratum.proxy.constant.Constants;
import strat.mining.stratum.proxy.database.DatabaseManager;
import strat.mining.stratum.proxy.grizzly.CLStaticHttpHandlerWithIndexSupport;
import strat.mining.stratum.proxy.grizzly.StaticHttpHandlerWithCharset;
import strat.mining.stratum.proxy.manager.HashrateRecorder;
import strat.mining.stratum.proxy.manager.ProxyManager;
import strat.mining.stratum.proxy.pool.Pool;
import strat.mining.stratum.proxy.rest.ProxyResources;
import strat.mining.stratum.proxy.rest.authentication.AuthenticationAddOn;
import strat.mining.stratum.proxy.rest.ssl.SSLRedirectAddOn;
import strat.mining.stratum.proxy.utils.Timer;
import strat.mining.stratum.proxy.worker.GetworkRequestHandler;

import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;

public class Launcher {

    private static final String KEYSTORE_KEY_ENTRY_ALIAS = "stratum-proxy";

    private static final String KEYSTORE_PASSWORD = "stratum-proxy";

    private static final String KEYSTORE_FILE_NAME = "stratum-proxy-keystore.jks";

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    public static Logger LOGGER = null;

    public static final String THREAD_MONITOR = "";

    private static HttpServer apiHttpServer;

    private static HttpServer getWorkHttpServer;

    public static void main(String[] args) {

        try {
            ConfigurationManager configurationManager = ConfigurationManager.getInstance();
            configurationManager.loadConfiguration(args);
            LOGGER = LoggerFactory.getLogger(Launcher.class);

            // Initialize the shutdown hook
            initShutdownHook();

            // Start initialization of the database manager
            initDatabaseManager();

            // Initialize the IP protocol version to use
            initIpVersion(configurationManager);

            // Initialize the proxy manager
            initProxyManager(configurationManager);

            // Initialize the Getwork system
            initGetwork(configurationManager);

            // Initialize the rest services
            initHttpServices(configurationManager);

            // Initialize the hashrate recorder
            initHashrateRecorder();

            // Wait the end of the program
            waitInfinite();

        } catch (Exception e) {
            if (LOGGER != null) {
                LOGGER.error("Failed to start the proxy.", e);
            } else {
                System.out.println("Failed to start the proxy: ");
                e.printStackTrace();
            }
        }
    }

    /**
     * Initialize the shutdown hook to close gracefully all connections.
     */
    private static void initShutdownHook() {
        Thread hookThread = new Thread() {
            public void run() {

                // Shutdown the database
                DatabaseManager.close();

                // Start a timer task that will exit the program after 1 second
                // if the cleanup is not over.
                Timer.getInstance().schedule(new Timer.Task() {
                    public void run() {
                        if (LOGGER != null) {
                            LOGGER.error("Force killing of the proxy...");
                        } else {
                            System.err.println("Force killing of the proxy...");
                        }
                        Runtime.getRuntime().halt(0);
                    }
                }, 1000);

                if (ProxyManager.getInstance() != null) {
                    if (LOGGER != null) {
                        LOGGER.info("User requested shutdown... Gracefuly kill all connections...");
                    } else {
                        System.out.println("User requested shutdown... Gracefuly kill all connections...");
                    }
                    ProxyManager.getInstance().stopListeningIncomingConnections();
                    ProxyManager.getInstance().closeAllWorkerConnections();
                    ProxyManager.getInstance().stopPools();
                }
                if (apiHttpServer != null) {
                    apiHttpServer.shutdownNow();
                }
                if (getWorkHttpServer != null) {
                    getWorkHttpServer.shutdownNow();
                }
                if (LOGGER != null) {
                    LOGGER.info("Shutdown !");
                } else {
                    System.out.println("Shutdown !");
                }
            }
        };
        hookThread.setDaemon(true);

        Runtime.getRuntime().addShutdownHook(hookThread);
    }

    /**
     * Initialize the hashrate recoder.
     */
    private static void initHashrateRecorder() {
        HashrateRecorder.getInstance().startCapture();
    }

    /**
     * Initialize the database manager.
     */
    private static void initDatabaseManager() {
        // Just get the instance to create it.
        DatabaseManager.getInstance();
    }

    /**
     * Initialize the IP protocol version to use
     * 
     * @param configurationManager
     */
    private static void initIpVersion(ConfigurationManager configurationManager) {
        if (Constants.IP_VERSION_V4.equals(configurationManager.getIpVersion())) {
            System.setProperty("java.net.preferIPv4Stack", "true");
            System.setProperty("java.net.preferIPv6Addresses", "false");
        } else if (Constants.IP_VERSION_V6.equals(configurationManager.getIpVersion())) {
            System.setProperty("java.net.preferIPv4Stack", "false");
            System.setProperty("java.net.preferIPv6Addresses", "true");
        } else {
            System.setProperty("java.net.preferIPv4Stack", "false");
            System.setProperty("java.net.preferIPv6Addresses", "false");
        }
    }

    /**
     * Initialize the HTTP services.
     * 
     * @param configurationManager
     * @throws IOException
     * @throws NoSuchAlgorithmException
     */
    private static void initHttpServices(ConfigurationManager configurationManager)
            throws IOException, NoSuchAlgorithmException {
        if (!ConfigurationManager.getInstance().isDisableApi()) {
            URI baseUri = UriBuilder.fromUri("https://" + configurationManager.getRestBindAddress())
                    .port(configurationManager.getRestListenPort()).path("/proxy").build();
            ResourceConfig config = new ResourceConfig(ProxyResources.class);
            config.register(JacksonFeature.class);
            apiHttpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri, config, false);

            initializeHttpCompression();

            initializeStaticContentHandler();

            if (ConfigurationManager.getInstance().getApiEnableSsl()) {
                initializeSslEngine();
            }

            // Initialize the HTTP Basic Authentication
            apiHttpServer.getListener("grizzly").registerAddOn(new AuthenticationAddOn("/proxy"));

            apiHttpServer.start();
        } else {
            LOGGER.info("API port disabled. GUI will not be available.");
        }
    }

    /**
     * Initialize the handler which handles static resources HTTP requests.
     */
    private static void initializeStaticContentHandler() {
        HttpHandler staticHandler = getStaticHandler();
        if (staticHandler != null) {
            ServerConfiguration serverConfiguration = apiHttpServer.getServerConfiguration();
            serverConfiguration.addHttpHandler(staticHandler, "/");
        }
    }

    /**
     * Initialize the compression for HTTP requests.
     */
    private static void initializeHttpCompression() {
        apiHttpServer.getListener("grizzly").getCompressionConfig().setCompressionMode(CompressionMode.ON);
        apiHttpServer.getListener("grizzly").getCompressionConfig().setCompressableMimeTypes("text/javascript",
                "application/json", "text/html", "text/css", "text/plain");
        apiHttpServer.getListener("grizzly").getCompressionConfig().setCompressionMinSize(1024);
    }

    /**
     * Initialize the SSL engine
     */
    private static void initializeSslEngine() {
        try {
            checkCertificate();
            SSLContextConfigurator sslContext = new SSLContextConfigurator();
            sslContext.setKeyStoreFile(
                    new File(ConfigurationManager.getInstance().getDatabaseDirectory(), KEYSTORE_FILE_NAME)
                            .getAbsolutePath());
            sslContext.setKeyStorePass(KEYSTORE_PASSWORD);
            sslContext.setKeyPass(KEYSTORE_PASSWORD);

            apiHttpServer.getListener("grizzly").setSecure(true);
            apiHttpServer.getListener("grizzly").setSSLEngineConfig(
                    new SSLEngineConfigurator(sslContext).setClientMode(false).setNeedClientAuth(false));

            apiHttpServer.getListener("grizzly").registerAddOn(new SSLRedirectAddOn());
        } catch (Exception e) {
            LOGGER.error("Failed to generate the HTTPS certificate. HTTP instead of HTTPS will be used.", e);
        }
    }

    /**
     * Return the handler to serve static content.
     * 
     * @return
     */
    private static HttpHandler getStaticHandler() {
        StaticHttpHandlerBase handler = null;
        // If the application is running form the jar file, use a Class Loader
        // to get the web content.
        if (ConfigurationManager.isRunningFromJar()) {
            try {
                handler = new CLStaticHttpHandlerWithIndexSupport(Launcher.class.getClassLoader(), "/");
            } catch (Exception e) {
                LOGGER.warn("Failed to initialize the Web content loader. GUI will not be available.", e);
            }
        } else {
            // If not running from a jar, it is running from the dev
            // environment. So use a static handler.
            File installPath = new File(ConfigurationManager.getInstallDirectory());
            File docRootPath = new File(installPath.getParentFile(), "src/main/resources/webapp");
            handler = new StaticHttpHandlerWithCharset(docRootPath.getAbsolutePath());
        }
        // Disable the file cache if in development.
        handler.setFileCacheEnabled(!ConfigurationManager.getVersion().equals("Dev"));

        return handler;
    }

    /**
     * Initialize the Getwork system.
     * 
     * @param configurationManager
     */
    private static void initGetwork(ConfigurationManager configurationManager) throws IOException {
        if (!ConfigurationManager.getInstance().isDisableGetwork()) {
            URI baseUri = UriBuilder.fromUri("http://" + configurationManager.getGetworkBindAddress())
                    .port(configurationManager.getGetworkListenPort()).build();
            getWorkHttpServer = GrizzlyHttpServerFactory.createHttpServer(baseUri);
            ServerConfiguration serverConfiguration = getWorkHttpServer.getServerConfiguration();
            serverConfiguration.addHttpHandler(new GetworkRequestHandler(), "/",
                    Constants.DEFAULT_GETWORK_LONG_POLLING_URL);
        } else {
            LOGGER.info("Getwork port disabled.");
        }
    }

    /**
     * Initialize the proxy manager
     * 
     * @param configurationManager
     * @throws IOException
     * @throws CmdLineException
     */
    private static void initProxyManager(ConfigurationManager configurationManager)
            throws IOException, CmdLineException {
        List<Pool> pools = configurationManager.getPools();
        LOGGER.info("Using pools: {}.", pools);

        // Start the pools.
        ProxyManager.getInstance().startPools(pools);

        if (!ConfigurationManager.getInstance().isDisableStratum()) {
            // Start to accept incoming workers connections
            ProxyManager.getInstance().startListeningIncomingConnections(
                    configurationManager.getStratumBindAddress(), configurationManager.getStratumListeningPort());
        } else {
            LOGGER.info("Stratum port disabled.");
        }
    }

    /**
     * Wait and never return
     */
    private static void waitInfinite() {
        try {
            synchronized (THREAD_MONITOR) {
                THREAD_MONITOR.wait();
            }
        } catch (Exception e) {
            LOGGER.info("Closing proxy...");
        }
    }

    /**
     * Check that a valid SSl certificate already exists. If not, create a new
     * one.
     * 
     * @throws Exception
     */
    private static void checkCertificate() throws Exception {
        File storeFile = new File(ConfigurationManager.getInstance().getDatabaseDirectory(), KEYSTORE_FILE_NAME);
        KeyStore keyStore = KeyStore.getInstance("JKS");
        if (!storeFile.exists()) {
            LOGGER.info("KeyStore does not exist. Create {}", storeFile.getAbsolutePath());
            storeFile.getParentFile().mkdirs();
            storeFile.createNewFile();
            keyStore.load(null, null);

            LOGGER.info("Generating new SSL certificate.");
            AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
            AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);

            RSAKeyPairGenerator keyGenerator = new RSAKeyPairGenerator();
            keyGenerator
                    .init(new RSAKeyGenerationParameters(BigInteger.valueOf(101), new SecureRandom(), 2048, 14));
            AsymmetricCipherKeyPair keysPair = keyGenerator.generateKeyPair();

            RSAKeyParameters rsaPrivateKey = (RSAKeyParameters) keysPair.getPrivate();
            RSAPrivateKeySpec rsaPrivSpec = new RSAPrivateKeySpec(rsaPrivateKey.getModulus(),
                    rsaPrivateKey.getExponent());
            RSAKeyParameters rsaPublicKey = (RSAKeyParameters) keysPair.getPublic();
            RSAPublicKeySpec rsaPublicSpec = new RSAPublicKeySpec(rsaPublicKey.getModulus(),
                    rsaPublicKey.getExponent());
            KeyFactory kf = KeyFactory.getInstance("RSA");
            PrivateKey rsaPriv = kf.generatePrivate(rsaPrivSpec);
            PublicKey rsaPub = kf.generatePublic(rsaPublicSpec);

            X500Name issuerDN = new X500Name("CN=localhost, OU=None, O=None, L=None, C=None");
            Integer randomNumber = new SecureRandom().nextInt();
            BigInteger serialNumber = BigInteger.valueOf(randomNumber >= 0 ? randomNumber : randomNumber * -1);
            Date notBefore = new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30);
            Date notAfter = new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 365 * 10));
            X500Name subjectDN = new X500Name("CN=localhost, OU=None, O=None, L=None, C=None");
            byte[] publickeyb = rsaPub.getEncoded();
            ASN1Sequence sequence = (ASN1Sequence) ASN1Primitive.fromByteArray(publickeyb);
            SubjectPublicKeyInfo subPubKeyInfo = new SubjectPublicKeyInfo(sequence);
            X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(issuerDN, serialNumber, notBefore,
                    notAfter, subjectDN, subPubKeyInfo);

            ContentSigner contentSigner = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
                    .build(keysPair.getPrivate());
            X509CertificateHolder certificateHolder = v3CertGen.build(contentSigner);

            Certificate certificate = new CertificateFactory()
                    .engineGenerateCertificate(new ByteBufferBackedInputStream(
                            ByteBuffer.wrap(certificateHolder.toASN1Structure().getEncoded())));

            LOGGER.info("Certificate generated.");

            keyStore.setKeyEntry(KEYSTORE_KEY_ENTRY_ALIAS, rsaPriv, KEYSTORE_PASSWORD.toCharArray(),
                    new java.security.cert.Certificate[] { certificate });

            keyStore.store(new FileOutputStream(storeFile), KEYSTORE_PASSWORD.toCharArray());
        }
    }

}