Bibliothèque SSH pour Java [fermé]

190

Quelqu'un connaît-il une bonne bibliothèque pour la connexion SSH à partir de Java.

rperez
la source
J'utilisais Trilead SSH mais quand j'ai vérifié le site aujourd'hui, il semble qu'ils y renoncent. :( C'était mon préféré.
Peter D
1
BTW, il semble que Trilead SSH2 soit activement maintenu (en octobre 2013): [ github.com/jenkinsci/trilead-ssh2]
Mike Godin
Trilead SSH2 a un fork sur github.com/connectbot/sshlib
user7610

Réponses:

120

Le Java Secure Channel (JSCH) est une bibliothèque très populaire, utilisée par maven, ant et eclipse. Il est open source avec une licence de style BSD.

David Rabinowitz
la source
2
Vous devez télécharger la source depuis sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/… et lancer "ant javadoc"
David Rabinowitz
73
J'ai essayé d'utiliser JSch il y a quelque temps et je ne peux pas comprendre comment il est devenu si populaire. Il n'offre absolument aucune documentation (même pas dans la source) et une conception d'API horrible ( techtavern.wordpress.com/2008/09/30/… résume assez bien)
rluba
15
Oui Jsch est horrible, c'est mieux: github.com/shikhar/sshj
anio
3
Une variante de JSch avec javadoc pour les méthodes publiques: github.com/ePaul/jsch-documentation
user423430
4
stackoverflow.com/questions/2405885/any-good-jsch-examples/... contient un exemple d'utilisation de JSCH pour exécuter des commandes et obtenir la sortie.
Charity Leschinski
65

Mise à jour: le projet GSOC et le code n'y sont pas actifs, mais c'est: https://github.com/hierynomus/sshj

hierynomus a pris la relève en tant que mainteneur depuis début 2015. Voici l'ancien lien Github, qui n'est plus maintenu:

https://github.com/shikhar/sshj


Il y avait un projet GSOC:

http://code.google.com/p/commons-net-ssh/

La qualité du code semble meilleure que JSch, qui, bien qu'une implémentation complète et fonctionnelle, manque de documentation. La page du projet repère une prochaine version bêta, le dernier commit dans le référentiel a eu lieu à la mi-août.

Comparez les API:

http://code.google.com/p/commons-net-ssh/

    SSHClient ssh = new SSHClient();
    //ssh.useCompression(); 
    ssh.loadKnownHosts();
    ssh.connect("localhost");
    try {
        ssh.authPublickey(System.getProperty("user.name"));
        new SCPDownloadClient(ssh).copy("ten", "/tmp");
    } finally {
        ssh.disconnect();
    }

http://www.jcraft.com/jsch/

Session session = null;
Channel channel = null;

try {

JSch jsch = new JSch();
session = jsch.getSession(username, host, 22);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
session.connect();

// exec 'scp -f rfile' remotely
String command = "scp -f " + remoteFilename;
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);

// get I/O streams for remote scp
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();

channel.connect();

byte[] buf = new byte[1024];

// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();

while (true) {
    int c = checkAck(in);
    if (c != 'C') {
        break;
    }

    // read '0644 '
    in.read(buf, 0, 5);

    long filesize = 0L;
    while (true) {
        if (in.read(buf, 0, 1) < 0) {
            // error
            break;
        }
        if (buf[0] == ' ') {
            break;
        }
        filesize = filesize * 10L + (long) (buf[0] - '0');
    }

    String file = null;
    for (int i = 0;; i++) {
        in.read(buf, i, 1);
        if (buf[i] == (byte) 0x0a) {
            file = new String(buf, 0, i);
            break;
        }
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    // read a content of lfile
    FileOutputStream fos = null;

    fos = new FileOutputStream(localFilename);
    int foo;
    while (true) {
        if (buf.length < filesize) {
            foo = buf.length;
        } else {
            foo = (int) filesize;
        }
        foo = in.read(buf, 0, foo);
        if (foo < 0) {
            // error
            break;
        }
        fos.write(buf, 0, foo);
        filesize -= foo;
        if (filesize == 0L) {
            break;
        }
    }
    fos.close();
    fos = null;

    if (checkAck(in) != 0) {
        System.exit(0);
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    channel.disconnect();
    session.disconnect();
}

} catch (JSchException jsche) {
    System.err.println(jsche.getLocalizedMessage());
} catch (IOException ioe) {
    System.err.println(ioe.getLocalizedMessage());
} finally {
    channel.disconnect();
    session.disconnect();
}

}
Miku
la source
2
Merci! J'ai utilisé le code Apache SSHD (qui offre une API asynchrone) comme graine, ce qui a donné un coup de pouce au projet.
shikhar
1
Génial. J'ai commencé un projet en utilisant JSch, mais j'aime vraiment changer, si j'entends des commentaires plus positifs sur commons-net-ssh.
miku
5
J'aurais dû mentionner que j'étais l'élève du GSOC :)
shikhar
2
heureux d'annoncer github.com/shikhar/sshj - vous pouvez trouver des pots là-bas, découvrir comment l'obtenir sur un repo
maven
1
Le SFTP de jsch est beaucoup plus simple que ce qui est donné ici. C'est peut-être un ancien code, mais ce n'est certainement pas un exemple de l'API moderne.
Charles Duffy
24

Je viens de découvrir sshj , qui semble avoir une API beaucoup plus concise que JSCH (mais cela nécessite Java 6). La documentation est principalement basée sur des exemples dans le dépôt à ce stade, et cela me suffit généralement pour regarder ailleurs, mais cela me semble assez bon pour essayer un projet que je viens de démarrer.

Ed Brannin
la source
3
SSHJ est en fait réalisable par quelqu'un du monde extérieur. JSCH est un gâchis de mauvaise documentation et de conception d'API avec des dépendances cachées et largement indéchiffrables. À moins que vous ne souhaitiez passer beaucoup de temps à parcourir le code pour essayer de comprendre ce qui se passe, utilisez SSHJ. (Et j'aimerais être dur ou facétieux à propos de JSCH. Je le fais vraiment.)
Robert Fischer
1
Oui, sshj. Tout ce que j'ai essayé avec cela fonctionnait: SCP, exécution de processus à distance, redirection de port local et distant, proxy d'agent avec jsch-agent-proxy . JSCH était un gâchis.
Laurent Caillette
1
Le problème avec SSHJ est qu'il est très difficile d'exécuter plusieurs commandes. SSHJ peut être idéal pour lancer et oublier les commandes, mais c'est pénible si vous voulez programmer des interactions plus compliquées. (Je viens de perdre une demi-journée dessus)
bvdb
18

Jetez un œil au tout récent SSHD , basé sur le projet Apache MINA.

mshafrir
la source
2
En l'utilisant, cependant, il manque de documentation et d'exemples.
Andreas Mattisson
On dirait qu'un manque de documentation le met à la légère: /
Amalgovinus
5

Il existe une toute nouvelle version de Jsch up sur github: https://github.com/vngx/vngx-jsch Certaines des améliorations incluent: javadoc complet, des performances améliorées, une meilleure gestion des exceptions et une meilleure adhérence aux spécifications RFC. Si vous souhaitez contribuer de quelque manière que ce soit, veuillez ouvrir un problème ou envoyer une pull request.

Scott
la source
4
Dommage qu'il n'y ait pas eu de nouveau commit depuis plus de 3 ans maintenant.
Mike Lowery
0

J'ai pris la réponse de miku et l'exemple de code jsch. J'ai ensuite dû télécharger plusieurs fichiers pendant la session et conserver les horodatages d'origine . Voici mon exemple de code pour le faire, probablement beaucoup de gens le trouvent utile. Veuillez ignorer la fonction filenameHack () qui est mon propre cas d'utilisation.

package examples;

import com.jcraft.jsch.*;
import java.io.*;
import java.util.*;

public class ScpFrom2 {

    public static void main(String[] args) throws Exception {
        Map<String,String> params = parseParams(args);
        if (params.isEmpty()) {
            System.err.println("usage: java ScpFrom2 "
                    + " user=myid password=mypwd"
                    + " host=myhost.com port=22"
                    + " encoding=<ISO-8859-1,UTF-8,...>"
                    + " \"remotefile1=/some/file.png\""
                    + " \"localfile1=file.png\""
                    + " \"remotefile2=/other/file.txt\""
                    + " \"localfile2=file.txt\""

            );
            return;
        }

        // default values
        if (params.get("port") == null)
            params.put("port", "22");
        if (params.get("encoding") == null)
            params.put("encoding", "ISO-8859-1"); //"UTF-8"

        Session session = null;
        try {
            JSch jsch=new JSch();
            session=jsch.getSession(
                    params.get("user"),  // myuserid
                    params.get("host"),  // my.server.com
                    Integer.parseInt(params.get("port")) // 22
            );
            session.setPassword( params.get("password") );
            session.setConfig("StrictHostKeyChecking", "no"); // do not prompt for server signature

            session.connect();

            // this is exec command and string reply encoding
            String encoding = params.get("encoding");

            int fileIdx=0;
            while(true) {
                fileIdx++;

                String remoteFile = params.get("remotefile"+fileIdx);
                String localFile = params.get("localfile"+fileIdx);
                if (remoteFile == null || remoteFile.equals("")
                        || localFile == null || localFile.equals("") )
                    break;

                remoteFile = filenameHack(remoteFile);
                localFile  = filenameHack(localFile);

                try {
                    downloadFile(session, remoteFile, localFile, encoding);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }

        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            try{ session.disconnect(); } catch(Exception ex){}
        }
    }

    private static void downloadFile(Session session, 
            String remoteFile, String localFile, String encoding) throws Exception {
        // send exec command: scp -p -f "/some/file.png"
        // -p = read file timestamps
        // -f = From remote to local
        String command = String.format("scp -p -f \"%s\"", remoteFile); 
        System.console().printf("send command: %s%n", command);
        Channel channel=session.openChannel("exec");
        ((ChannelExec)channel).setCommand(command.getBytes(encoding));

        // get I/O streams for remote scp
        byte[] buf=new byte[32*1024];
        OutputStream out=channel.getOutputStream();
        InputStream in=channel.getInputStream();

        channel.connect();

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: T<mtime> 0 <atime> 0\n
        // times are in seconds, since 1970-01-01 00:00:00 UTC 
        int c=checkAck(in);
        if(c!='T')
            throw new IOException("Invalid timestamp reply from server");

        long tsModified = -1; // millis
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(tsModified < 0 && buf[idx]==' ') {
                tsModified = Long.parseLong(new String(buf, 0, idx))*1000;
            } else if(buf[idx]=='\n') {
                break;
            }
        }

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: C0644 <binary length> <filename>\n
        // length is given as a text "621873" bytes
        c=checkAck(in);
        if(c!='C')
            throw new IOException("Invalid filename reply from server");

        in.read(buf, 0, 5); // read '0644 ' bytes

        long filesize=-1;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]==' ') {
                filesize = Long.parseLong(new String(buf, 0, idx));
                break;
            }
        }

        // read remote filename
        String origFilename=null;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]=='\n') {
                origFilename=new String(buf, 0, idx, encoding); // UTF-8, ISO-8859-1
                break;
            }
        }

        System.console().printf("size=%d, modified=%d, filename=%s%n"
                , filesize, tsModified, origFilename);

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // read binary data, write to local file
        FileOutputStream fos = null;
        try {
            File file = new File(localFile);
            fos = new FileOutputStream(file);
            while(filesize > 0) {
                int read = Math.min(buf.length, (int)filesize);
                read=in.read(buf, 0, read);
                if(read < 0)
                    throw new IOException("Reading data failed");

                fos.write(buf, 0, read);
                filesize -= read;
            }
            fos.close(); // we must close file before updating timestamp
            fos = null;
            if (tsModified > 0)
                file.setLastModified(tsModified);               
        } finally {
            try{ if (fos!=null) fos.close(); } catch(Exception ex){}
        }

        if(checkAck(in) != 0)
            return;

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'
        System.out.println("Binary data read");     
    }

    private static int checkAck(InputStream in) throws IOException {
        // b may be 0 for success
        //          1 for error,
        //          2 for fatal error,
        //          -1
        int b=in.read();
        if(b==0) return b;
        else if(b==-1) return b;
        if(b==1 || b==2) {
            StringBuilder sb=new StringBuilder();
            int c;
            do {
                c=in.read();
                sb.append((char)c);
            } while(c!='\n');
            throw new IOException(sb.toString());
        }
        return b;
    }


    /**
     * Parse key=value pairs to hashmap.
     * @param args
     * @return
     */
    private static Map<String,String> parseParams(String[] args) throws Exception {
        Map<String,String> params = new HashMap<String,String>();
        for(String keyval : args) {
            int idx = keyval.indexOf('=');
            params.put(
                    keyval.substring(0, idx),
                    keyval.substring(idx+1)
            );
        }
        return params;
    }

    private static String filenameHack(String filename) {
        // It's difficult reliably pass unicode input parameters 
        // from Java dos command line.
        // This dirty hack is my very own test use case. 
        if (filename.contains("${filename1}"))
            filename = filename.replace("${filename1}", "Korilla ABC ÅÄÖ.txt");
        else if (filename.contains("${filename2}"))
            filename = filename.replace("${filename2}", "test2 ABC ÅÄÖ.txt");           
        return filename;
    }

}
Qui
la source
Avez-vous pu réutiliser une session et éviter la surcharge de connexion / déconnexion?
Sridhar Sarnobat