All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.nativeexecution.jsch.JSchConnectionTask Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.nativeexecution.jsch;

import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import org.netbeans.modules.nativeexecution.api.ExecutionEnvironment;
import org.netbeans.modules.nativeexecution.jsch.JSchConnectionTask.Problem;
import org.netbeans.modules.nativeexecution.spi.JSchAuthenticationSelection;
import org.netbeans.modules.nativeexecution.api.util.Authentication;
import org.netbeans.modules.nativeexecution.support.Logger;
import org.netbeans.modules.nativeexecution.support.NativeTaskExecutorService;
//import org.netbeans.modules.dlight.nativeexecution.ui.AuthTypeSelectorDlg;
import org.openide.util.Cancellable;
import org.openide.util.RequestProcessor;

/**
 * Thread safe. Started only once.
 * @author ak119685
 */
public final class JSchConnectionTask implements Cancellable {

    // Connections are always established sequently in connectorThread
    private static final RequestProcessor connectorThread = new RequestProcessor("ConnectionManager queue", 1); // NOI18N
    private static final int SOCKET_CREATION_TIMEOUT = Integer.getInteger("socket.connection.timeout", 10000); // NOI18N
    private static final java.util.logging.Logger log = Logger.getInstance();
    // ------------------------------------------------------------------------
    private final JSch jsch;
    private final ExecutionEnvironment env;
    private final Object resultLock = new Object();
    private Future result = null;
    private volatile boolean cancelled;

    public JSchConnectionTask(final JSch jsch, final ExecutionEnvironment env) {
        this.jsch = jsch;
        this.env = env;
        cancelled = false;
    }

    public void start() {
        synchronized (resultLock) {
            if (result == null) {
                result = connectorThread.submit(new Callable() {

                    @Override
                    public Result call() throws Exception {
                        return connect();
                    }
                });
            }
        }
    }

    private JSchConnectionTask.Result connect() throws Exception {
        ConnectingProgressHandle.startHandle(env, this);

        try {
            try {
                env.prepareForConnection();
            } catch (Throwable th) {
                return new Result(null, new Problem(ProblemType.ENV_PREPARE_ERROR, th));
            }

            if (cancelled) {
                return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
            }

            if (!isReachable()) {
                if (cancelled) {
                    return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
                } else {
                    return new Result(null, new Problem(ProblemType.HOST_UNREACHABLE));
                }
            }

            if (cancelled) {
                return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
            }

            if (!initJsch(env)) {
                return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
            }

            // Start special shell session that will serve administrative tasks
            // like sending signals to processes...

            JSchChannelsSupport cs = new JSchChannelsSupport(jsch, env);
            try {
                cs.connect();
            } catch (InterruptedException ex) {
                cancelled = true;
            }

            if (cancelled) {
                return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
            }

            // OK. Connection established.

            return new Result(cs, null);
        } catch (JSchException e) {
            log.log(Level.FINE, "JSchException connecting to " + env, e); // NOI18N

            if (e.getMessage().equals("Auth cancel")) { // NOI18N
                return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
            } else if (e.getMessage().contains("java.net.SocketTimeoutException") // NOI18N
                    || e.getMessage().contains("timeout")) { // NOI18N
                return new Result(null, new Problem(ProblemType.CONNECTION_TIMEOUT, e));
            }
            return new Result(null, new Problem(ProblemType.CONNECTION_FAILED, e));
        } catch (java.util.concurrent.CancellationException ex) {
            log.log(Level.FINE, "CancellationException", ex); // NOI18N
            return new Result(null, new Problem(ProblemType.CONNECTION_CANCELLED));
        } catch (Throwable th) {
            return new Result(null, new Problem(ProblemType.CONNECTION_FAILED, th));
        } finally {
            ConnectingProgressHandle.stopHandle(env);
        }
    }

    private static boolean initJsch(ExecutionEnvironment env) {
        Authentication auth = Authentication.getFor(env);

        if (!auth.isDefined()) {
            return JSchAuthenticationSelection.find().initAuthentication(auth);
//            AuthTypeSelectorDlg dlg = new AuthTypeSelectorDlg();
//            if (!dlg.initAuthentication(auth)) {
//                return false;
//            }
        } else {
            auth.apply();
        }

        return true;
    }

    public Problem getProblem() throws InterruptedException, ExecutionException {
        Future r;
        synchronized (resultLock) {
            r = result;
        }

        if (r == null) {
            throw new IllegalStateException("Not started yet"); // NOI18N
        }

        return r.get().problem;
    }

    public JSchChannelsSupport getResult() throws InterruptedException, ExecutionException {
        Future r;
        synchronized (resultLock) {
            r = result;
        }

        if (r == null) {
            throw new IllegalStateException("Not started yet"); // NOI18N
        }

        return r.get().cs;
    }

    private boolean isReachable() throws IOException {
        // IZ#165591 - Trying to connect to wrong host breaks remote host setup (for other hosts)
        // To prevent this first try to just open a socket and
        // go to the jsch code in case of success only.

        // The important thing here is that we still need to be interruptable
        // In case of wrong IP address (unreachable) the SocketImpl's connect()
        // method may hang in system call for a long period of time, being
        // insensitive to interrupts.
        // So do this in a separate thread...

        Callable checker = new Callable() {

            @Override
            public Boolean call() throws Exception {
                final Socket socket = new Socket();
                final SocketAddress addressToConnect =
                        new InetSocketAddress(env.getHostAddress(), env.getSSHPort());
                try {
                    socket.connect(addressToConnect, SOCKET_CREATION_TIMEOUT);
                } catch (Exception ioe) {
                    return false;
                } finally {
                    socket.close();
                }
                return true;
            }
        };

        final Future task = NativeTaskExecutorService.submit(
                checker, "Host " + env.getHost() + " availability test"); // NOI18N

        while (!cancelled && !task.isDone()) {
            try {
                task.get(500, TimeUnit.MILLISECONDS);
            } catch (InterruptedException ex) {
                // normally should never happen
            } catch (ExecutionException ex) {
                // normally should never happen
            } catch (TimeoutException ex) {
                // OK.. still be waiting
            }
        }

        boolean result = false;

        if (task.isDone()) {
            try {
                result = task.get();
            } catch (Exception ex) {
                // normally should never happen
            }
        }

        return result;
    }

    @Override
    public boolean cancel() {
        cancelled = true;
        return true;
    }

    public static final class Problem {

        public final ProblemType type;
        public final Throwable cause;

        public Problem(ProblemType type) {
            this(type, null);
        }

        public Problem(ProblemType type, Throwable cause) {
            this.type = type;
            this.cause = cause;
        }
    }

    public static enum ProblemType {

        ENV_PREPARE_ERROR,
        AUTH_FAIL,
        HOST_UNREACHABLE,
        CONNECTION_CANCELLED,
        CONNECTION_FAILED,
        CONNECTION_TIMEOUT,
    }

    private static final class Result {

        public final JSchChannelsSupport cs;
        public final Problem problem;

        public Result(JSchChannelsSupport cs, Problem problem) {
            this.cs = cs;
            this.problem = problem;
            if (problem != null && isUnitTestMode()) {
                new Exception("That's just a trace: connection failed: " + problem.type, problem.cause).printStackTrace(System.err); //NOI18N
            }
        }
    }
    
    // copy-paste from CndUtils
    private static boolean isUnitTestMode() {
        return Boolean.getBoolean("cnd.mode.unittest"); // NOI18N
    }
    
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy