// 
// Decompiled by Procyon v0.6.0
// 

package org.jline.builtins.ssh;

import java.util.Iterator;
import org.apache.sshd.common.NamedResource;
import org.apache.sshd.common.session.SessionContext;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.command.Command;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import java.util.Collections;
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
import org.apache.sshd.server.command.CommandFactory;
import org.apache.sshd.scp.server.ScpCommandFactory;
import org.apache.sshd.server.shell.ShellFactory;
import org.apache.sshd.client.future.ConnectFuture;
import java.io.PrintWriter;
import org.jline.terminal.Size;
import org.apache.sshd.client.channel.ChannelShell;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.session.ClientSession;
import java.util.List;
import java.io.IOException;
import org.apache.sshd.common.util.io.input.NoCloseInputStream;
import java.util.Map;
import org.jline.terminal.Attributes;
import org.apache.sshd.common.channel.PtyMode;
import java.util.HashMap;
import java.util.Collection;
import java.util.EnumSet;
import org.apache.sshd.client.channel.ClientChannelEvent;
import java.io.OutputStream;
import org.apache.sshd.common.util.io.output.NoCloseOutputStream;
import java.io.ByteArrayInputStream;
import org.apache.sshd.client.auth.keyboard.UserInteraction;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.jline.builtins.Options;
import java.io.PrintStream;
import java.io.InputStream;
import org.jline.reader.LineReader;
import org.jline.terminal.Terminal;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.server.SshServer;
import java.util.function.Supplier;
import java.util.function.Consumer;

public class Ssh
{
    public static final String[] functions;
    private static final int defaultPort = 2022;
    private final Consumer<ShellParams> shell;
    private final Consumer<ExecuteParams> execute;
    private final Supplier<SshServer> serverBuilder;
    private final Supplier<SshClient> clientBuilder;
    private SshServer server;
    private int port;
    private String ip;
    
    public Ssh(final Consumer<ShellParams> shell, final Consumer<ExecuteParams> execute, final Supplier<SshServer> serverBuilder, final Supplier<SshClient> clientBuilder) {
        this.shell = shell;
        this.execute = execute;
        this.serverBuilder = serverBuilder;
        this.clientBuilder = clientBuilder;
    }
    
    public void ssh(final Terminal terminal, final LineReader reader, final String user, final InputStream stdin, final PrintStream stdout, final PrintStream stderr, final String[] argv) throws Exception {
        final String[] usage = { "ssh - connect to a server using ssh", "Usage: ssh [user@]hostname [command]", "  -? --help                show help" };
        final Options opt = Options.compile(usage).parse(argv, true);
        final List<String> args = opt.args();
        if (opt.isSet("help") || args.isEmpty()) {
            throw new Options.HelpException(opt.usage());
        }
        String username = user;
        String hostname = args.remove(0);
        int port = this.port;
        String command = null;
        int idx = hostname.indexOf(64);
        if (idx >= 0) {
            username = hostname.substring(0, idx);
            hostname = hostname.substring(idx + 1);
        }
        idx = hostname.indexOf(58);
        if (idx >= 0) {
            port = Integer.parseInt(hostname.substring(idx + 1));
            hostname = hostname.substring(0, idx);
        }
        if (!args.isEmpty()) {
            command = String.join(" ", args);
        }
        try (final SshClient client = this.clientBuilder.get()) {
            final JLineUserInteraction ui = new JLineUserInteraction(terminal, reader, stderr);
            client.setFilePasswordProvider((FilePasswordProvider)ui);
            client.setUserInteraction((UserInteraction)ui);
            client.start();
            try (final ClientSession sshSession = this.connectWithRetries(terminal.writer(), client, username, hostname, port, 3)) {
                sshSession.auth().verify();
                if (command != null) {
                    final ClientChannel channel = sshSession.createChannel("exec", command + "\n");
                    channel.setIn((InputStream)new ByteArrayInputStream(new byte[0]));
                    channel.setOut((OutputStream)new NoCloseOutputStream((OutputStream)stdout));
                    channel.setErr((OutputStream)new NoCloseOutputStream((OutputStream)stderr));
                    channel.open().verify();
                    channel.waitFor((Collection)EnumSet.of(ClientChannelEvent.CLOSED), 0L);
                }
                else {
                    final ChannelShell channel2 = sshSession.createShellChannel();
                    final Attributes attributes = terminal.enterRawMode();
                    try {
                        final Map<PtyMode, Integer> modes = new HashMap<PtyMode, Integer>();
                        setMode(modes, PtyMode.VINTR, attributes.getControlChar(Attributes.ControlChar.VINTR));
                        setMode(modes, PtyMode.VQUIT, attributes.getControlChar(Attributes.ControlChar.VQUIT));
                        setMode(modes, PtyMode.VERASE, attributes.getControlChar(Attributes.ControlChar.VERASE));
                        setMode(modes, PtyMode.VKILL, attributes.getControlChar(Attributes.ControlChar.VKILL));
                        setMode(modes, PtyMode.VEOF, attributes.getControlChar(Attributes.ControlChar.VEOF));
                        setMode(modes, PtyMode.VEOL, attributes.getControlChar(Attributes.ControlChar.VEOL));
                        setMode(modes, PtyMode.VEOL2, attributes.getControlChar(Attributes.ControlChar.VEOL2));
                        setMode(modes, PtyMode.VSTART, attributes.getControlChar(Attributes.ControlChar.VSTART));
                        setMode(modes, PtyMode.VSTOP, attributes.getControlChar(Attributes.ControlChar.VSTOP));
                        setMode(modes, PtyMode.VSUSP, attributes.getControlChar(Attributes.ControlChar.VSUSP));
                        setMode(modes, PtyMode.VDSUSP, attributes.getControlChar(Attributes.ControlChar.VDSUSP));
                        setMode(modes, PtyMode.VREPRINT, attributes.getControlChar(Attributes.ControlChar.VREPRINT));
                        setMode(modes, PtyMode.VWERASE, attributes.getControlChar(Attributes.ControlChar.VWERASE));
                        setMode(modes, PtyMode.VLNEXT, attributes.getControlChar(Attributes.ControlChar.VLNEXT));
                        setMode(modes, PtyMode.VSTATUS, attributes.getControlChar(Attributes.ControlChar.VSTATUS));
                        setMode(modes, PtyMode.VDISCARD, attributes.getControlChar(Attributes.ControlChar.VDISCARD));
                        setMode(modes, PtyMode.IGNPAR, getFlag(attributes, Attributes.InputFlag.IGNPAR));
                        setMode(modes, PtyMode.PARMRK, getFlag(attributes, Attributes.InputFlag.PARMRK));
                        setMode(modes, PtyMode.INPCK, getFlag(attributes, Attributes.InputFlag.INPCK));
                        setMode(modes, PtyMode.ISTRIP, getFlag(attributes, Attributes.InputFlag.ISTRIP));
                        setMode(modes, PtyMode.INLCR, getFlag(attributes, Attributes.InputFlag.INLCR));
                        setMode(modes, PtyMode.IGNCR, getFlag(attributes, Attributes.InputFlag.IGNCR));
                        setMode(modes, PtyMode.ICRNL, getFlag(attributes, Attributes.InputFlag.ICRNL));
                        setMode(modes, PtyMode.IXON, getFlag(attributes, Attributes.InputFlag.IXON));
                        setMode(modes, PtyMode.IXANY, getFlag(attributes, Attributes.InputFlag.IXANY));
                        setMode(modes, PtyMode.IXOFF, getFlag(attributes, Attributes.InputFlag.IXOFF));
                        setMode(modes, PtyMode.ISIG, getFlag(attributes, Attributes.LocalFlag.ISIG));
                        setMode(modes, PtyMode.ICANON, getFlag(attributes, Attributes.LocalFlag.ICANON));
                        setMode(modes, PtyMode.ECHO, getFlag(attributes, Attributes.LocalFlag.ECHO));
                        setMode(modes, PtyMode.ECHOE, getFlag(attributes, Attributes.LocalFlag.ECHOE));
                        setMode(modes, PtyMode.ECHOK, getFlag(attributes, Attributes.LocalFlag.ECHOK));
                        setMode(modes, PtyMode.ECHONL, getFlag(attributes, Attributes.LocalFlag.ECHONL));
                        setMode(modes, PtyMode.NOFLSH, getFlag(attributes, Attributes.LocalFlag.NOFLSH));
                        setMode(modes, PtyMode.TOSTOP, getFlag(attributes, Attributes.LocalFlag.TOSTOP));
                        setMode(modes, PtyMode.IEXTEN, getFlag(attributes, Attributes.LocalFlag.IEXTEN));
                        setMode(modes, PtyMode.OPOST, getFlag(attributes, Attributes.OutputFlag.OPOST));
                        setMode(modes, PtyMode.ONLCR, getFlag(attributes, Attributes.OutputFlag.ONLCR));
                        setMode(modes, PtyMode.OCRNL, getFlag(attributes, Attributes.OutputFlag.OCRNL));
                        setMode(modes, PtyMode.ONOCR, getFlag(attributes, Attributes.OutputFlag.ONOCR));
                        setMode(modes, PtyMode.ONLRET, getFlag(attributes, Attributes.OutputFlag.ONLRET));
                        channel2.setPtyModes((Map)modes);
                        channel2.setPtyColumns(terminal.getWidth());
                        channel2.setPtyLines(terminal.getHeight());
                        channel2.setAgentForwarding(true);
                        channel2.setEnv("TERM", (Object)terminal.getType());
                        channel2.setIn((InputStream)new NoCloseInputStream(stdin));
                        channel2.setOut((OutputStream)new NoCloseOutputStream((OutputStream)stdout));
                        channel2.setErr((OutputStream)new NoCloseOutputStream((OutputStream)stderr));
                        channel2.open().verify();
                        final ClientChannel channel;
                        final Terminal.SignalHandler prevWinchHandler = terminal.handle(Terminal.Signal.WINCH, signal -> {
                            try {
                                final Size size = terminal.getSize();
                                channel.sendWindowChange(size.getColumns(), size.getRows());
                            }
                            catch (final IOException ex) {}
                            return;
                        });
                        final Terminal.SignalHandler prevQuitHandler = terminal.handle(Terminal.Signal.QUIT, signal -> {
                            try {
                                channel.getInvertedIn().write(attributes.getControlChar(Attributes.ControlChar.VQUIT));
                                channel.getInvertedIn().flush();
                            }
                            catch (final IOException ex2) {}
                            return;
                        });
                        final Terminal.SignalHandler prevIntHandler = terminal.handle(Terminal.Signal.INT, signal -> {
                            try {
                                channel.getInvertedIn().write(attributes.getControlChar(Attributes.ControlChar.VINTR));
                                channel.getInvertedIn().flush();
                            }
                            catch (final IOException ex3) {}
                            return;
                        });
                        final Terminal.SignalHandler prevStopHandler = terminal.handle(Terminal.Signal.TSTP, signal -> {
                            try {
                                channel.getInvertedIn().write(attributes.getControlChar(Attributes.ControlChar.VDSUSP));
                                channel.getInvertedIn().flush();
                            }
                            catch (final IOException ex4) {}
                            return;
                        });
                        try {
                            channel2.waitFor((Collection)EnumSet.of(ClientChannelEvent.CLOSED), 0L);
                        }
                        finally {
                            terminal.handle(Terminal.Signal.WINCH, prevWinchHandler);
                            terminal.handle(Terminal.Signal.INT, prevIntHandler);
                            terminal.handle(Terminal.Signal.TSTP, prevStopHandler);
                            terminal.handle(Terminal.Signal.QUIT, prevQuitHandler);
                        }
                    }
                    finally {
                        terminal.setAttributes(attributes);
                    }
                }
            }
        }
    }
    
    private static void setMode(final Map<PtyMode, Integer> modes, final PtyMode vintr, final int attributes) {
        if (attributes >= 0) {
            modes.put(vintr, attributes);
        }
    }
    
    private static int getFlag(final Attributes attributes, final Attributes.InputFlag flag) {
        return attributes.getInputFlag(flag) ? 1 : 0;
    }
    
    private static int getFlag(final Attributes attributes, final Attributes.OutputFlag flag) {
        return attributes.getOutputFlag(flag) ? 1 : 0;
    }
    
    private static int getFlag(final Attributes attributes, final Attributes.LocalFlag flag) {
        return attributes.getLocalFlag(flag) ? 1 : 0;
    }
    
    private ClientSession connectWithRetries(final PrintWriter stdout, final SshClient client, final String username, final String host, final int port, final int maxAttempts) throws Exception {
        ClientSession session = null;
        int retries = 0;
        do {
            final ConnectFuture future = client.connect(username, host, port);
            future.await();
            try {
                session = (ClientSession)future.getSession();
            }
            catch (final Exception ex) {
                if (retries++ >= maxAttempts) {
                    throw ex;
                }
                Thread.sleep(2000L);
                stdout.println("retrying (attempt " + retries + ") ...");
            }
        } while (session == null);
        return session;
    }
    
    public void sshd(final PrintStream stdout, final PrintStream stderr, final String[] argv) throws Exception {
        final String[] usage = { "sshd - start an ssh server", "Usage: sshd [-i ip] [-p port] start | stop | status", "  -i --ip=INTERFACE        listen interface (default=127.0.0.1)", "  -p --port=PORT           listen port (default=2022)", "  -? --help                show help" };
        final Options opt = Options.compile(usage).parse(argv, true);
        final List<String> args = opt.args();
        if (opt.isSet("help") || args.isEmpty()) {
            throw new Options.HelpException(opt.usage());
        }
        final String command = args.get(0);
        if ("start".equals(command)) {
            if (this.server != null) {
                throw new IllegalStateException("sshd is already running on port " + this.port);
            }
            this.ip = opt.get("ip");
            this.port = opt.getNumber("port");
            this.start();
            this.status(stdout);
        }
        else if ("stop".equals(command)) {
            if (this.server == null) {
                throw new IllegalStateException("sshd is not running.");
            }
            this.stop();
        }
        else {
            if (!"status".equals(command)) {
                throw opt.usageError("bad command: " + command);
            }
            this.status(stdout);
        }
    }
    
    private void status(final PrintStream stdout) {
        if (this.server != null) {
            stdout.println("sshd is running on " + this.ip + ":" + this.port);
        }
        else {
            stdout.println("sshd is not running.");
        }
    }
    
    private void start() throws IOException {
        (this.server = this.serverBuilder.get()).setPort(this.port);
        this.server.setHost(this.ip);
        this.server.setShellFactory((ShellFactory)new ShellFactoryImpl(this.shell));
        this.server.setCommandFactory((CommandFactory)new ScpCommandFactory.Builder().withDelegate((channel, command) -> new ShellCommand(this.execute, command)).build());
        this.server.setSubsystemFactories((List)Collections.singletonList(new SftpSubsystemFactory.Builder().build()));
        this.server.setKeyPairProvider((KeyPairProvider)new SimpleGeneratorHostKeyProvider());
        this.server.start();
    }
    
    private void stop() throws IOException {
        try {
            this.server.stop();
        }
        finally {
            this.server = null;
        }
    }
    
    static {
        functions = new String[] { "ssh", "sshd" };
    }
    
    public static class ShellParams
    {
        private final Map<String, String> env;
        private final Terminal terminal;
        private final Runnable closer;
        private final ServerSession session;
        
        public ShellParams(final Map<String, String> env, final ServerSession session, final Terminal terminal, final Runnable closer) {
            this.env = env;
            this.session = session;
            this.terminal = terminal;
            this.closer = closer;
        }
        
        public Map<String, String> getEnv() {
            return this.env;
        }
        
        public ServerSession getSession() {
            return this.session;
        }
        
        public Terminal getTerminal() {
            return this.terminal;
        }
        
        public Runnable getCloser() {
            return this.closer;
        }
    }
    
    public static class ExecuteParams
    {
        private final String command;
        private final Map<String, String> env;
        private final ServerSession session;
        private final InputStream in;
        private final OutputStream out;
        private final OutputStream err;
        
        public ExecuteParams(final String command, final Map<String, String> env, final ServerSession session, final InputStream in, final OutputStream out, final OutputStream err) {
            this.command = command;
            this.session = session;
            this.env = env;
            this.in = in;
            this.out = out;
            this.err = err;
        }
        
        public String getCommand() {
            return this.command;
        }
        
        public Map<String, String> getEnv() {
            return this.env;
        }
        
        public ServerSession getSession() {
            return this.session;
        }
        
        public InputStream getIn() {
            return this.in;
        }
        
        public OutputStream getOut() {
            return this.out;
        }
        
        public OutputStream getErr() {
            return this.err;
        }
    }
    
    private static class JLineUserInteraction implements UserInteraction, FilePasswordProvider
    {
        private final Terminal terminal;
        private final LineReader reader;
        private final PrintStream stderr;
        
        public JLineUserInteraction(final Terminal terminal, final LineReader reader, final PrintStream stderr) {
            this.terminal = terminal;
            this.reader = reader;
            this.stderr = stderr;
        }
        
        public String getPassword(final SessionContext session, final NamedResource resourceKey, final int retryIndex) throws IOException {
            return this.readLine("Enter password for " + resourceKey + ":", false);
        }
        
        public void welcome(final ClientSession session, final String banner, final String lang) {
            this.terminal.writer().println(banner);
        }
        
        public String[] interactive(final ClientSession s, final String name, final String instruction, final String lang, final String[] prompt, final boolean[] echo) {
            final String[] answers = new String[prompt.length];
            try {
                for (int i = 0; i < prompt.length; ++i) {
                    answers[i] = this.readLine(prompt[i], echo[i]);
                }
            }
            catch (final Exception e) {
                this.stderr.append(e.getClass().getSimpleName()).append(" while read prompts: ").println(e.getMessage());
            }
            return answers;
        }
        
        public boolean isInteractionAllowed(final ClientSession session) {
            return true;
        }
        
        public void serverVersionInfo(final ClientSession session, final List<String> lines) {
            for (final String l : lines) {
                this.terminal.writer().append('\t').println(l);
            }
        }
        
        public String getUpdatedPassword(final ClientSession session, final String prompt, final String lang) {
            try {
                return this.readLine(prompt, false);
            }
            catch (final Exception e) {
                this.stderr.append(e.getClass().getSimpleName()).append(" while reading password: ").println(e.getMessage());
                return null;
            }
        }
        
        private String readLine(final String prompt, final boolean echo) {
            return this.reader.readLine(prompt + " ", echo ? null : Character.valueOf('\0'));
        }
    }
}
