JavaからのSSHログインに適したライブラリを知っている人はいますか。
Java Secure Channel(JSCH) は、Maven、Ant、Eclipseで使用される非常に人気のあるライブラリです。 BSDスタイルのライセンスを持つオープンソースです。
更新:GSOCプロジェクトとそこにあるコードはアクティブではありませんが、これは https://github.com/hierynomus/sshj です
hierynomusは2015年初頭からメンテナーとして引き継いでいます。以下は古いGithubリンクです。
https://github.com/shikhar/sshj
GSOCプロジェクトがありました。
http://code.google.com/p/commons-net-ssh/
コードの品質はJSchよりも優れているようです。JSchは完全で実用的な実装でありながら、ドキュメントがありません。プロジェクトページでは、近日中にベータリリースが予定されており、リポジトリへの最後のコミットは8月中旬でした。
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();
}
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();
}
}
sshj を発見しました。これは、JSCHよりもはるかに簡潔なAPIを持っているようです(ただし、Java 6が必要です)。ドキュメントは、この時点でのレポジトリの例がほとんどであり、通常は他の場所を見るのに十分ですが、開始したばかりのプロジェクトを試してみるのに十分なようです。
Apache MINAプロジェクトに基づく、ごく最近リリースされた SSHD をご覧ください。
Githubに新しいバージョンのJschがあります: https://github.com/vngx/vngx-jsch 改善点の一部は次のとおりです。 javadoc、パフォーマンスの向上、例外処理の改善、RFC仕様の遵守の向上。何らかの形で貢献したい場合は、問題を開くか、プルリクエストを送信してください。
http://code.google.com/p/connectbot/ 、Windows linuxでsrc\com\trilead\ssh2をコンパイルまたはAndroid、ローカルポートフォワーダーを作成または作成できますダイナミックポートフォワーダーまたはその他
Mikuの答えとjschのサンプルコードを取りました。その後、セッション中に複数のファイルをダウンロードすると、元のタイムスタンプを保持するとする必要がありました。これは、それを行う方法のサンプルコードであり、おそらく多くの人が便利だと思うでしょう。 filenameHack()関数の独自のユースケースは無視してください。
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;
}
}