/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2005 - 2025 ScalAgent Distributed Technologies
 *
 * This library 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 any later version.
 * 
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 *
 * Initial developer(s): ScalAgent Distributed Technologies
 * Contributor(s): Alex Porras (MediaOcean)
 */
package org.objectweb.joram.mom.proxies.tcp;

import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.StringTokenizer;

import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;

import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

import fr.dyade.aaa.agent.AgentServer;
import fr.dyade.aaa.common.Debug;

/**
 * Starts a SSL entry point for JMS clients.
 */
public class SSLTcpProxyService extends TcpProxyService implements SSLTcpProxyServiceMBean {
  /** logger */
  private static final Logger logger = Debug.getLogger(SSLTcpProxyService.class.getName());
  private static final boolean DEBUG = logger.isLoggable(BasicLevel.DEBUG);

  private final static String CIPHER = "org.objectweb.joram.cipherList";
  private final static String KS = "org.objectweb.joram.keystore";
  private final static String KS_PASS = "org.objectweb.joram.keystorepass";
  private final static String KS_TYPE = "org.objectweb.joram.keystoretype";
  private final static String SSLCONTEXT = "org.objectweb.joram.sslCtx";
  private final static String CLIENT_AUTH = "org.objectweb.joram.clientAuth";

  private static final String MBEAN_NAME = "type=Connection,mode=ssl";

  private static final String CLIENT_AUTH_NONE = "NONE";
  private static final String CLIENT_AUTH_WANT = "WANT";
  private static final String CLIENT_AUTH_NEED = "NEED";
  private static final String CLIENT_AUTH_DFLT = CLIENT_AUTH_NEED;
  
  private static String clientAuth = CLIENT_AUTH_DFLT;
  
  /**
   * Returns the actual configuration for client authentication:<ul>
   * <li>WANT:client authentication required</li>
   * <li>NEED:client authentication requested</li>
   * <li>NONNEno client authentication desired</li>
   * </ul>
   * 
   * @return the actual configuration for client authentication.
   */
  public String getClientAuth() {
    return clientAuth;
  }
  
  /**
   * Controls whether accepted server-mode SSLSockets will be initially configured to require or not client authentication.
   * A socket's client authentication setting is one of the following:<ul>
   * <li>WANT: client authentication required.</li>
   * <li>NEED: client authentication requested.</li>
   * <li>NONE: no client authentication desired.</li>
   * </ul>
   * 
   * The initial value of this parameter depends of the org.objectweb.joram.clientAuth configuration property.
   * It can be overloaded for new connections  using the corresponding MBean.
   * 
   * @param clientAuth  "WANT", "NEED" or "NONE".
   */
  public void setClientAuth(String clientAuth) {
    this.clientAuth = clientAuth;
  }
  
  /**
   * The proxy service reference (used to stop it).
   */
  private static SSLTcpProxyService proxyService;

  /**
   * Initializes the SSLTCP entry point by creating a
   * ssl server socket listening to the specified port.
   * 
   * @param args stringified listening port
   * @param firstTime <code>true</code> 
   * when the agent server starts.   
   */
  public static void init(String args, boolean firstTime) throws Exception {
    if (DEBUG)
      logger.log(BasicLevel.DEBUG,
                 "SSLTcpProxyService.init(" + args + ',' + firstTime + ')');

    int port = DEFAULT_PORT;
    String address = DEFAULT_BINDADDRESS;
    if (args != null) {
      StringTokenizer st = new StringTokenizer(args);      
      port = Integer.parseInt(st.nextToken());
      if (st.hasMoreTokens()) {
        address = st.nextToken();
      }
    }

    int backlog = AgentServer.getInteger(BACKLOG_PROP, DEFAULT_BACKLOG).intValue();
    clientAuth = AgentServer.getProperty(CLIENT_AUTH, clientAuth);

    if (DEBUG)
      logger.log(BasicLevel.DEBUG,
                 "SSLTcpProxyService.init() - binding to address " + address + ", port " + port + ", client authentication=" + clientAuth);
    // Create the socket here in order to throw an exception
    // if the socket can't be created (even if firstTime is false).

    proxyService = new SSLTcpProxyService(port, backlog, address);
    proxyService.start();
  }

  public String getMBeanName() {
    return MBEAN_NAME;
  }

  public SSLTcpProxyService(int port, int backlog, String address) throws Exception {
    super(port, backlog, address);
  }

  private static ServerSocketFactory createServerSocketFactory() throws Exception {
    char[] keyStorePass =  AgentServer.getProperty(KS_PASS, "jorampass").toCharArray();
    String keystoreFile = AgentServer.getProperty(KS, "./joram_ks");
    String sslContext = AgentServer.getProperty(SSLCONTEXT, "SSL");
    String ksType = AgentServer.getProperty(KS_TYPE, "JKS");

    if (DEBUG)
      logger.log(BasicLevel.DEBUG,
                 "SSLTcpProxyService.createServerSocketFactory: keystore=" + keystoreFile);

    KeyStore keystore = KeyStore.getInstance(ksType);
    keystore.load(new FileInputStream(keystoreFile), keyStorePass);

    KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
    kmf.init(keystore,keyStorePass);

    TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
    tmf.init(keystore);       
    TrustManager[] trustManagers = tmf.getTrustManagers();

    SSLContext ctx = SSLContext.getInstance(sslContext);
    SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
    //    SecureRandom securerandom = null;
    ctx.init(kmf.getKeyManagers(),trustManagers,securerandom);

    return ctx.getServerSocketFactory();
  }

  protected ServerSocket createServerSocket(int port, int backlog, String address) throws Exception {
    ServerSocketFactory serverSocketFactory = createServerSocketFactory();

    SSLServerSocket serverSocket = null;
    try {
      if (address.equals("0.0.0.0")) {
        serverSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(port, backlog);
      } else {
        serverSocket = (SSLServerSocket) serverSocketFactory.createServerSocket(port, backlog, InetAddress.getByName(address));
      }

      logger.log(BasicLevel.DEBUG,
                 "SSLTcpProxyService.createServerSocket(" + port + ',' + backlog + ',' + address + ") use " + clientAuth);
      if (CLIENT_AUTH_NEED.equalsIgnoreCase(clientAuth)) {
        // require mutual authentication
        serverSocket.setNeedClientAuth(true);
      } else if (CLIENT_AUTH_WANT.equalsIgnoreCase(clientAuth)) {
        // request mutual authentication
        serverSocket.setWantClientAuth(true);
      } else {
        // could set need with the same effect
        serverSocket.setWantClientAuth(false);
      }
      String[] cipherTable = getCipherList();
      if (cipherTable != null && cipherTable.length > 0)
        serverSocket.setEnabledCipherSuites(cipherTable);
    } catch (Exception exc) {
      if (DEBUG)
        logger.log(BasicLevel.ERROR,
                   "TcpProxyService.createServerSocket(" + port + ',' + backlog + ',' + address + ')', exc);
      else
        logger.log(BasicLevel.ERROR,
                   "TcpProxyService.createServerSocket(" + port + ',' + backlog + ',' + address + "): " + exc.getMessage());
      throw exc;
    }

    return serverSocket;
  }

  private static String [] getCipherList() throws Exception {
    String cipherList = AgentServer.getProperty(CIPHER,null);
    String[] cipherTable = null;
    if ( cipherList != null ) {
      StringTokenizer tokenizer = new StringTokenizer( cipherList,",");
      int tokens = tokenizer.countTokens();
      if (tokens > 0) {
        cipherTable = new String[tokens];
        while(tokenizer.hasMoreElements())
          cipherTable[--tokens] = tokenizer.nextToken();
      }
    }
    return cipherTable;
  }
}
