Biblioteca SSH para Java [cerrado]

190

¿Alguien sabe de una buena biblioteca para el inicio de sesión SSH desde Java?

rperez
fuente
Solía ​​usar Trilead SSH, pero cuando revisé el sitio hoy parece que se están dando por vencidos. :( Fue mi favorito absoluto.
Peter D
1
Por cierto, parece que Trilead SSH2 se mantiene activamente (en octubre de 2013): [ github.com/jenkinsci/trilead-ssh2]
Mike Godin
Trilead SSH2 tiene una bifurcación en github.com/connectbot/sshlib
user7610

Respuestas:

120

El Java Secure Channel (JSCH) es una biblioteca muy popular, utilizada por maven, ant y eclipse. Es de código abierto con una licencia de estilo BSD.

David Rabinowitz
fuente
2
Necesita descargar la fuente de sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/… y ejecutar "ant javadoc"
David Rabinowitz
73
Intenté usar JSch hace algún tiempo y no puedo entender cómo se hizo tan popular. No ofrece absolutamente ninguna documentación (ni siquiera dentro de la fuente) y un diseño de API horrible ( techtavern.wordpress.com/2008/09/30/… lo resume bastante bien)
rluba
15
Sí, Jsch es horrible, esto es mejor: github.com/shikhar/sshj
anio
3
Una variante de JSch con javadoc para los métodos públicos: github.com/ePaul/jsch-documentation
user423430
44
stackoverflow.com/questions/2405885/any-good-jsch-examples/… contiene un ejemplo para usar JSCH para ejecutar comandos y obtener el resultado.
Charity Leschinski
65

Actualización: el proyecto GSOC y el código allí no están activos, pero esto es: https://github.com/hierynomus/sshj

hierynomus asumió el cargo de mantenedor desde principios de 2015. Aquí está el enlace Github más antiguo, que ya no se mantiene:

https://github.com/shikhar/sshj


Hubo un proyecto GSOC:

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

La calidad del código parece ser mejor que JSch, que, aunque es una implementación completa y funcional, carece de documentación. La página del proyecto detecta una próxima versión beta, el último compromiso con el repositorio fue a mediados de agosto.

Compare las 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
fuente
2
¡Gracias! Utilicé el código Apache SSHD (que ofrece una API asíncrona) como semilla que le dio al proyecto un buen comienzo.
shikhar el
1
Excelente. Comencé un proyecto usando JSch, pero realmente me gusta cambiar, si escucho más comentarios positivos sobre commons-net-ssh.
miku
55
Debería haber mencionado que yo era el estudiante de GSOC :)
shikhar
2
feliz de anunciar github.com/shikhar/sshj - puedes encontrar jarras allí, descubriendo cómo conseguirlo en un repositorio maven
shikhar
1
El SFTP de jsch es mucho más simple que lo que se da aquí. Quizás este es un código antiguo, pero ciertamente no es un ejemplo de la API moderna.
Charles Duffy
24

Acabo de descubrir sshj , que parece tener una API mucho más concisa que JSCH (pero requiere Java 6). La documentación es principalmente por ejemplos en el repositorio en este punto, y generalmente es suficiente para que busque en otro lado, pero parece lo suficientemente bueno como para darle una oportunidad a un proyecto que acabo de comenzar.

Ed Brannin
fuente
3
SSHJ es realmente viable por alguien del mundo exterior. JSCH es un desastre de mala documentación y diseño de API con dependencias ocultas y en gran medida indescifrables. A menos que quiera pasar mucho tiempo revisando el código para tratar de descubrir qué pasa, use SSHJ. (Y me gustaría que estaba siendo dura o gracioso acerca JSCH de verdad..)
Robert Fischer
1
Si, sshj Todo lo que probé funcionó: SCP, ejecución remota de procesos, reenvío de puertos local y remoto, proxy de agente con jsch-agent-proxy . JSCH fue un desastre.
Laurent Caillette
1
El problema con SSHJ es que es muy difícil ejecutar múltiples comandos. SSHJ puede ser excelente para disparar y olvidarse de los comandos, pero es un problema si desea programar una interacción más complicada. (Acabo de perder medio día)
bvdb
18

Eche un vistazo al SSHD lanzado recientemente , que se basa en el proyecto Apache MINA.

mshafrir
fuente
2
Al usarlo, sin embargo, falta documentación y ejemplos.
Andreas Mattisson
Parece que la falta de documentación lo está poniendo a la ligera: /
Amalgovinus
5

Hay una nueva versión de Jsch en github: https://github.com/vngx/vngx-jsch Algunas de las mejoras incluyen: javadoc integral, rendimiento mejorado, manejo de excepciones mejorado y mejor adherencia a las especificaciones RFC. Si desea contribuir de alguna manera, abra un problema o envíe una solicitud de extracción.

Scott
fuente
44
Lástima que no haya habido un nuevo compromiso en más de 3 años.
Mike Lowery
0

Tomé la respuesta de miku y el código de ejemplo jsch. Luego tuve que descargar varios archivos durante la sesión y conservar las marcas de tiempo originales . Este es mi código de ejemplo de cómo hacerlo, probablemente muchas personas lo encuentren útil. Ignore la función filenameHack () es mi propio caso de uso.

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;
    }

}
Quien
fuente
¿Pudiste reutilizar una sesión y evitar la sobrecarga de conectar / desconectar?
Sridhar Sarnobat