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

net.oneandone.sushi.fs.ssh.SshFilesystem Maven / Gradle / Ivy

/**
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * Licensed 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 net.oneandone.sushi.fs.ssh;

import com.jcraft.jsch.Identity;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import net.oneandone.sushi.fs.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Arrays;

/**
 * Nodes accessible via sftp.
 * Uses Jsch:  http://www.jcraft.com/jsch/
 * See also: http://tools.ietf.org/id/draft-ietf-secsh-filexfer-13.txt
 */
public class SshFilesystem extends Filesystem {
    private int defaultTimeout;
    private final JSch jsch;

    public SshFilesystem(World world, String name) {
        super(world, new Features(true, true, true, true, false, false, true), name);

        defaultTimeout = 0;
        jsch = new JSch();
        jsch.setHostKeyRepository(new AcceptAllHostKeyRepository());
    }

    /** millis */
    public void setDefaultTimeout(int defaultTimeout) {
        this.defaultTimeout = defaultTimeout;
    }

    /** millis */
    public int getDefaultTimeout() {
        return defaultTimeout;
    }

    public JSch getJSch() {
        return jsch;
    }

    @Override
    public SshNode node(URI uri, Object extra) throws NodeInstantiationException {
        if (extra != null) {
            throw new NodeInstantiationException(uri, "unexpected extra argument: " + extra);
        }
        checkHierarchical(uri);
        try {
            return root(uri.getAuthority()).node(getCheckedPath(uri), null);
        } catch (JSchException | IOException e) {
            throw new NodeInstantiationException(uri, "cannot create root", e);
        }
    }

    public SshRoot localhostRoot() throws JSchException, IOException {
        return root("localhost", getWorld().getWorking().getName());
    }

    public SshRoot root(String root) throws JSchException, IOException {
        return root(root, defaultTimeout);
    }

    public SshRoot root(String root, int timeout) throws JSchException, IOException {
        int idx;
        String host;
        String user;

        host = root;
        idx = host.indexOf('@');
        if (idx == -1) {
            user = null;
        } else {
            user = host.substring(0, idx);
            host = host.substring(idx + 1);
        }
        return root(host, user, timeout);
    }

    public SshRoot root(String host, String user) throws JSchException, IOException {
        return root(host, user, defaultTimeout);
    }

    /** @param user null to use current user */
    public SshRoot root(String host, String user, int timeout) throws JSchException, IOException {
        if (user == null) {
            user = getWorld().getHome().getName();
        }
        if (jsch.getIdentityNames().isEmpty()) {
            addDefaultIdentity();
        }
        return new SshRoot(this, host, user, timeout);
    }

    //--

    public void addDefaultIdentity() throws IOException, JSchException {
        addDefaultIdentity(null);
    }

    /** @param passphrase null to try to load passphrase from ~/.ssh/passphrase file */
    public void addDefaultIdentity(String passphrase) throws IOException, JSchException {
        Node dir;
        Node file;
        Node key;

        dir = getWorld().getHome().join(".ssh");
        file = dir.join("passphrase");
        if (passphrase == null && file.exists()) {
            passphrase = file.readString().trim();
        }
        key = dir.join("id_dsa");
        if (!key.exists()) {
            key = dir.join("id_rsa");
            if (!key.exists()) {
                key = dir.join("identity");
            }
        }
        if (!key.isFile()) {
            throw new IOException("private key not found: " + key);
        }
        addIdentity(key, passphrase);
    }

    /** @param passphrase null of none */
    public void addIdentity(Node privateKey, String passphrase) throws IOException, JSchException {
        Identity identity;
        Throwable te;
        Class clz;
        Method m;
        byte[] bytes;

        bytes = privateKey.readBytes();
        // CAUTION: I cannot use
        //   jsch.addIdentity("foo", null, null, null);
        // because in jsch 1.48, there's no way to obtain the resulting identity and the identity.setPassphrase
        // result.
        try {
            clz = Class.forName("com.jcraft.jsch.IdentityFile");
            m = clz.getDeclaredMethod("newInstance", String.class, byte[].class, byte[].class, JSch.class);
            m.setAccessible(true);
            identity = (Identity) m.invoke(null, privateKey.toString(), Arrays.copyOf(bytes, bytes.length), null, jsch);
        } catch (InvocationTargetException e) {
            te = e.getTargetException();
            if (te instanceof JSchException) {
                throw (JSchException) te;
            } else {
                throw new IllegalStateException(e);
            }
        } catch (Exception e) {
            throw new RuntimeException("TODO", e);
        }
        if (passphrase != null) {
            if (!identity.isEncrypted()) {
                throw new JSchException("unexpected passphrase");
            }
            if (!identity.setPassphrase(passphrase.getBytes())) {
                throw new JSchException("invalid passphrase");
            }
        } else {
            if (!identity.setPassphrase(null)) {
                throw new JSchException("missing passphrase");
            }
        }
        jsch.removeIdentity(identity);
        jsch.addIdentity(identity, null);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy