# NativeSecureConnection.java

    /*
    ** Module   : NativeSecureConnection.java
    ** Abstract : Helper for connecting to a remote P2J server from native code, via JNI.
    **
    ** Copyright (c) 2014-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description---------------------------------
    ** 001 CA  20140211 Created initial version.
    ** 002 CA  20140226 Write any errors to STDERR instead of STDOUT, so they end up on the server's
    **                  log.
    ** 003 MAG 20140718 Implements remote batch clients. Substitute placeholders on remote launch.
    **     IAS 20210325 Added NIO configuration via BootstrapConfig
    ** 004 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 005 GBB 20240729 Initialize CentralLogger in try-catch.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero 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 Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.main;

    import java.util.*;

    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.net.*;
    import com.goldencode.p2j.security.SecurityManager;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Helper class for connecting to a remote P2J server from native code, via JNI. Used by the
     * {@code spawn} tool when no password is specified and authentication needs to be done via P2J.
     */
    public class NativeSecureConnection
    {
       /**
        * Establish a secure connection to the target P2J server and retrieve the spawn command
        * registered with this request, via the specified UUID.
        * <p>
        * Authentication will be performed with the temporary credentials set in the environment. If
        * the secure connection is not possible, the received certificate can not be validated or
        * if the authentication is not possible, it returns {@code null}.
        *
        * @param    port
        *           The P2J server's secure port.
        * @param    host
        *           The P2J server's host.
        * @param    alias
        *           The certificate alias.
        * @param    uuid
        *           The UUID identifying this request.
        * @param    misc
        *           Misc. BootstrapConfig overrides
        * @return   The spawn command or {@code null}.
        */
       public static String[] command(int port, String host, String alias, String uuid, String misc)
       {
          // establish a secure connection to the P2J server running on the specified port; use the
          // certificate targeted by the specified alias for authentication.
          Session sess = null;
          try
          {
             CentralLogger.setMode(CentralLogger.Mode.SPAWNER_JVM);
             CentralLogger log = CentralLogger.get(NativeSecureConnection.class);

             BootstrapConfig cfg = new BootstrapConfig();
             cfg.setServer(false);
             cfg.setConfigItem("net",      "connection",     "secure",       "true");
             cfg.setConfigItem("net",      "server",         "secure_port",  Integer.toString(port));
             cfg.setConfigItem("net",      "server",         "host",         host);
             cfg.setConfigItem("net",      "queue",          "conversation", "false");
             cfg.setConfigItem("security", "certificate",    "validate",     "true");
             cfg.setConfigItem("security", "truststore",     "filename",     "srv-certs.store");
             cfg.setConfigItem("security", "truststore",     "alias",        alias);
             cfg.setConfigItem("security", "authentication", "type",         "program");

             // Get user credentials from environment
             String subject = System.getenv(TemporaryAccount.P2J_SUBJECT);
             String password = System.getenv(TemporaryAccount.P2J_PASSWORD);

             cfg.setConfigItem("access", "subject",  "id",   TemporaryAccount.fromHex(subject));
             cfg.setConfigItem("access", "password", "user", TemporaryAccount.fromHex(password));

             String[] overrides = misc.split(" ");
             for (String override : overrides)
             {
                String[] args = override.split(":");
                if (args.length != 3)
                {
                   log.severe("Invalid override: " + override + ", ignored");
                   continue;
                }
                String [] last = args[2].split("=");
                if (last.length != 2)
                {
                   log.severe("Invalid override: " + override + ", ignored");
                   continue;
                }
                cfg.setConfigItem(args[0], args[1], last[0], last[1]);
             }
             @SuppressWarnings("unused")
             SecurityManager secMgr = SecurityManager.createInstance(cfg);
             SessionManager sessMgr = SessionManagerFactory.createLeafNode(cfg);

             // establish a secure connection with the remote P2J server
             sess = sessMgr.connectDirect(cfg, null, null);
             RemoteSpawner remote =
                (RemoteSpawner) RemoteObject.obtainNetworkInstance(RemoteSpawner.class, sess);

             // get the command in use for this UUID
             String[] cmd = remote.getCommand(uuid);

             return remote(cmd);
          }
          catch (Throwable t)
          {
             final String msg = "Problem retrieving the spawn command, using these settings: " +
                                "port [%d] host [%s] alias [%s] UUID [%s]";
             CentralLogger.get(NativeSecureConnection.class)
                          .severe(String.format(msg, port, host, alias, uuid), t);

             // do not allow exceptions to propagate into native code.
             return null;
          }
          finally
          {
             if (sess != null)
             {
                sess.terminate();
             }
          }
       }

       /**
        * Prepare command substituting placeholder with broker values.
        *
        * @param   args
        *          Command arguments read from server.
        *
        * @return  Command arguments having placeholders replaced.
        */
       private static String[] remote(String[] args)
       {
          // Get environment variables
          Map<String, String> env = System.getenv();
          // host
          String host = env.get(BrokerCore.BROKER_HOST);
          // secure port
          String port = env.get(BrokerCore.BROKER_PORT);
          // JVM arguments
          String jvmargs = env.get(BrokerCore.BROKER_JVMARGS);
          // classpath
          String classpath = env.get(BrokerCore.BROKER_CLASSPATH);

          // Command line arguments
          List<String> command = new LinkedList<String>();

          // Replace placeholders
          for (String cmd : args)
          {
             // replace host
             if (cmd.indexOf(BrokerCore.PARAM_HOST) > -1)
             {
                if (host == null)
                {
                   throw new NullPointerException("Null value not allowed for host.");
                }
                // host
                command.add(cmd.replace(BrokerCore.PARAM_HOST, host));
             }
             // replace port
             else if (cmd.indexOf(BrokerCore.PARAM_PORT) > -1)
             {
                if (port == null)
                {
                   throw new NullPointerException("Null value not allowed for secure port.");
                }
                // secure port
                command.add(cmd.replace(BrokerCore.PARAM_PORT, port));
             }
             else if (BrokerCore.PARAM_JVM.equals(cmd))
             {
                if (jvmargs == null)
                {
                   throw new NullPointerException("Null value not allowed for JVM arguments.");
                }
                // add JVM arguments
                String[] tokens = jvmargs.split("\\" + BrokerCore.BROKER_JVMARGS_SEPARATOR);
                command.addAll(Arrays.asList(tokens));
             }
             else if (BrokerCore.PARAM_CP.equals(cmd))
             {
                if (classpath == null)
                {
                   throw new NullPointerException("Null value not allowed for CLASSPATH.");
                }
                // add CLASSAPTH
                command.add("-classpath");
                command.add(classpath);
             }
             else
             {
                command.add(cmd);
             }
          }

          return command.toArray(new String[0]);
       }
    }