001/* 002 * Copyright (c) 2009 The openGion Project. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 013 * either express or implied. See the License for the specific language 014 * governing permissions and limitations under the License. 015 */ 016package org.opengion.fukurou.util; 017 018import java.io.IOException; 019import java.util.Map; 020import java.util.LinkedHashMap ; 021import java.util.Vector; 022import java.util.Hashtable; 023 024import com.jcraft.jsch.JSch; 025import com.jcraft.jsch.Session; 026import com.jcraft.jsch.ChannelSftp; 027import com.jcraft.jsch.ChannelSftp.LsEntry; 028import com.jcraft.jsch.SftpATTRS; 029import com.jcraft.jsch.JSchException; 030import com.jcraft.jsch.SftpException; 031 032 033/** 034 * SFTPConnect.java は、共通的に使用される SFTP関連の基本機能を実装した、クラスです。 035 * 036 * これは、org.apache.commons.net.ftp.FTPClient をベースに開発されています。 037 * このクラスの実行には、commons-net-ftp-2.0.jar が必要です。 038 * 039 * -host=SFTPサーバー -user=ユーザー -passwd=パスワード -remoteFile=SFTP先のファイル名 を必須設定します。 040 * -localFile=ローカルのファイル名は、必須ではありませんが、-command=DEL の場合にのみ不要であり、 041 * それ以外の command の場合は、必要です。 042 * 043 * -command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] は、SFTPサーバーに対しての処理の方法を指定します。 044 * GET:SFTPサーバーからローカルにファイル転送します(初期値)。 045 * PUT:ローカルファイルをSFTPサーバーに PUT(STORE、SAVE、UPLOAD、などと同意語)します。 046 * DEL:SFTPサーバーの指定のファイルを削除します。この場合のみ、-localFile 属性の指定は不要です。 047 * GETDIR,PUTDIR,DELDIR:指定のフォルダ以下のファイルを処理します。 048 * 049 * -mkdirs=[true/false] は、受け側のファイル(GET時:LOCAL、PUT時:SFTPサーバー)に取り込むファイルのディレクトリが 050 * 存在しない場合に、作成するかどうかを指定します(初期値:true)。 051 * 通常、SFTPサーバーに、フォルダ階層を作成してPUTする場合、動的にフォルダ階層を作成したいケースで便利です。 052 * 逆に、フォルダは確定しており、指定フォルダ以外に PUT するのはバグっていると事が分かっている場合には 053 * false に設定して、存在しないフォルダにPUT しようとすると、エラーになるようにします。 054 * 055 * 引数文字列中に空白を含む場合は、ダブルコーテーション("") で括って下さい。 056 * 引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に 057 * 繋げてください。 058 * 059 * @og.formSample 060 * SFTPConnect -host=SFTPサーバー -user=ユーザー -passwd=パスワード -remoteFile=SFTP先のファイル名 [-localFile=ローカルのファイル名] 061 * [-mode=[ASCII/BINARY] ] [-command=[GET/PUT/DEL/GETDIR/PUTDIR/DELDIR] ] [-passive=[true/false] ] 062 * 063 * -host=SFTPサーバー :接続先のSFTPサーバーのアドレスまたは、サーバー名 064 * -user=ユーザー :接続するユーザー名 065 * -remoteFile=SFTP先のファイル名 :接続先のSFTPサーバー側のファイル名。PUT,GET 関係なくSFTP側として指定します。 066 * [-passwd=パスワード] :接続するユーザーのパスワード 067 * [-localFile=ローカルのファイル名] :ローカルのファイル名。PUT,GET 関係なくローカルファイルを指定します。 068 * [-port=ポート ] :接続するサーバーのポートを指定します。 069 * [-keyFile=秘密キーファイル ] :公開キー暗号化方式を利用する場合のキーファイル名を指定します。 070 * [-command=[GET/PUT/DEL] ] :SFTPサーバー側での処理の方法を指定します。 071 * [GETDIR/PUTDIR/DELDIR]] GET:SFTP⇒LOCAL、PUT:LOCAL⇒SFTP への転送です(初期値:GET) 072 * DEL:SFTPファイルを削除します。 073 * GETDIR,PUTDIR,DELDIR 指定のフォルダ以下のファイルを処理します。 074 * [-mkdirs=[true/false] ] :受け側ファイル(GET時:LOCAL、PUT時:SFTPサーバー)にディレクトリを作成するかどうか(初期値:true) 075 * (false:ディレクトリが無ければ、エラーにします。) 076 * [-timeout=タイムアウト[秒] ] :Dataタイムアウト(初期値:600 [秒]) 077 * [-display=[false/true] ] :trueは、検索状況を表示します(初期値:false) 078 * [-debug=[false|true] ] :デバッグ情報を標準出力に表示する(true)かしない(false)か(初期値:false[表示しない]) 079 * 080 * @og.rev 5.1.6.0 (2010/05/01) 新規追加 081 * 082 * @version 5.0 083 * @author Kazuhiko Hasegawa 084 * @since JDK5.0, 085 */ 086public final class SFTPConnect extends AbstractConnect { 087 private final JSch jsch; 088 089 private static final int DEF_PORT = 22; // ポート 090 091 private boolean isConnect = false; // コネクト済みかどうか。 092 093 private String lastRemoteDir = "/"; // SFTP先の最後に登録したフォルダ名(mkdir の高速化のため) 094 private String keyFile = null; // 公開キー暗号化方式を利用する場合のキーファイル名を指定します。 095 096 private Session session = null; 097 private ChannelSftp channel = null; 098 099 /** 100 * デフォルトコンストラクター 101 */ 102 public SFTPConnect() { 103 jsch = new JSch(); 104 } 105 106 /** 107 * SFTPサーバーへの接続、ログインを行います。 108 * 109 * このメソッドは、初期化メソッドです。 110 * SFTPサーバーへの接続、ログインを行いますので、複数ファイルを転送する 111 * ケースでは、最初に1度だけ呼び出すだけです。 112 * 接続先を変更する場合は、もう一度このメソッドをコールする必要があります。 113 * (そのような場合は、通常、オブジェクトを構築しなおす方がよいと思います。) 114 * 115 */ 116 @Override 117 public void connect() { 118 if( isDisplay ) { System.out.println( "CONNECT: HOST=" + host + ",USER=" + user + ",PORT=" + port ); } 119 120 // もし、すでに接続されていた場合は、クロース処理を行います。 121 if( isConnect ) { disconnect(); } 122 123 // HostKeyチェックを行わない 124 Hashtable<String,String> config = new Hashtable<String,String>(); 125 config.put( "StrictHostKeyChecking", "no" ); 126 JSch.setConfig( config ); 127 128 // サーバーに対して接続を行います。 129 try { 130 if( keyFile == null ) { 131 // パスワード認証 132 session=jsch.getSession( user, host, getPort( DEF_PORT ) ); 133 session.setPassword( passwd ); 134 } 135 else { 136 // 公開キー、秘密キー認証 137 jsch.addIdentity( keyFile ); 138 session=jsch.getSession( user, host, getPort( DEF_PORT ) ); 139 // session.setUserInfo(new MyUserInfo()); 140 } 141 142 session.connect( timeout*1000 ); // タイムアウトの設定 143 144 channel=(ChannelSftp)session.openChannel("sftp"); 145 channel.connect(); 146 } 147 catch ( JSchException ex ) { 148 errAppend( "SFTP server refused connection. " ); 149 errAppend( " host = [" , host , "]" ); 150 errAppend( " user = [" , user , "]" ); 151 errAppend( " port = [" , port , "]" ); 152 errAppend( ex ); 153 if( isDebug ) { ex.printStackTrace(); } 154 disconnect(); 155 throw new RuntimeException( getErrMsg(),ex ); 156 } 157 158 isConnect = true; 159 } 160 161 /** 162 * SFTPサーバーとの接続をクローズします。 163 * 164 * ログインされている場合は、ログアウトも行います。 165 * コネクトされている場合は、ディスコネクトします。 166 * 167 */ 168 @Override 169 public void disconnect() { 170 if( isDisplay ) { System.out.println( "DISCONNECT:" ); } 171 172 if( isConnect ) { 173 isConnect = false; 174 try { 175 channel.disconnect(); 176 session.disconnect(); 177 } 178 catch( Throwable th ) { 179 errAppend( "disconnect Error." ); 180 errAppend( th ); 181 if( isDebug ) { th.printStackTrace(); } 182 throw new RuntimeException( getErrMsg(),th ); 183 } 184 } 185 } 186 187 /** 188 * command="GET" が指定されたときの処理を行います。 189 * 190 * ローカルファイルを、接続先のSFTPサーバー側にアップロードします。 191 * 192 * @param localFile ローカルのファイル名 193 * @param remoteFile SFTP先のファイル名 194 * @throws JSchException JSCHエラーが発生したとき 195 * @throws SftpException SFTPエラーが発生したとき 196 * @throws IOException 入出力エラーが発生したとき 197 */ 198 @Override 199 protected void actionGET( final String localFile, final String remoteFile ) throws JSchException, SftpException, IOException { 200 if( isDebug ) { System.out.println( "GET: " + remoteFile + " => " + localFile ); } 201 202 // GET(DOWNLOAD)取得時は、ローカルファイルのディレクトリを作成する必要がある。 203 if( isMkdirs ) { 204 makeLocalDir( localFile ); 205 } 206 207 channel.get( remoteFile,localFile ); 208 } 209 210 /** 211 * command="GETDIR" が指定されたときの処理を行います。 212 * 213 * 接続先のSFTPサーバー側のディレクトリ以下をローカルディレクトリに階層構造のままダウンロードします。 214 * 215 * @param localDir ローカルのディレクトリ名 216 * @param remoteDir SFTP先のディレクトリ名 217 * @throws JSchException JSCHエラーが発生したとき 218 * @throws SftpException SFTPエラーが発生したとき 219 * @throws IOException 入出力エラーが発生したとき 220 */ 221 @Override 222 protected void actionGETdir( final String localDir, final String remoteDir ) throws IOException,JSchException,SftpException { 223 Vector<?> list = channel.ls( remoteDir ); 224 for (int i=0;i<list.size();i++) { 225 LsEntry entry = (LsEntry)list.get(i); 226 String rmt = entry.getFilename(); 227 if( ".".equals( rmt ) || "..".equals( rmt ) ) { continue; } // "." で始まるファイルもあるので、equasl 判定 228 SftpATTRS stat = entry.getAttrs(); 229 if( stat.isDir() ) { 230 actionGETdir( addFile( localDir,rmt ),addFile( remoteDir,rmt ) ); 231 } 232 else { 233 actionGET( addFile( localDir,rmt ),addFile( remoteDir,rmt ) ); 234 } 235 } 236 } 237 238 /** 239 * command="PUT" が指定されたときの処理を行います。 240 * 241 * 接続先のSFTPサーバー側のファイル名をローカルにダウンロードします。 242 * 243 * @param localFile ローカルのファイル名 244 * @param remoteFile SFTP先のファイル名 245 * @throws JSchException 処理中に JSch エラーが発生した場合 246 * @throws SftpException 処理中に Sftp エラーが発生した場合 247 */ 248 @Override 249 protected void actionPUT( final String localFile, final String remoteFile ) throws JSchException,SftpException { 250 if( isDebug ) { System.out.println( "PUT: " + localFile + " => " + remoteFile ); } 251 252 // PUT(UPLOAD)登録時は、リモートファイルのディレクトリを作成する必要がある。 253 if( isMkdirs ) { 254 // 前回のDIRとの比較で、すでに存在していれば、makeDirectory 処理をパスする。 255 int ad = remoteFile.lastIndexOf( '/' ) + 1; // 区切り文字を+1する。 256 String tmp = remoteFile.substring( 0,ad ); 257 258 if( ad > 0 && !lastRemoteDir.startsWith( tmp ) ) { 259 lastRemoteDir = tmp; 260 if( remoteFile.startsWith( "/" ) ) { 261 String[] fls = remoteFile.split( "/" ); 262 channel.cd( "/" ); 263 for( int i=1; i<fls.length-1; i++ ) { 264 try { 265 // SftpATTRS stat = channel.lstat(fls[i]); // 存在しないと、SftpException 266 channel.cd( fls[i] ); // 存在しないと、SftpException 267 continue; 268 } catch (SftpException ex) { 269 // ファイルが存在しないとき 270 channel.mkdir( fls[i] ); 271 channel.cd( fls[i] ); 272 } 273 } 274 } 275 } 276 } 277 278 channel.put( localFile,remoteFile ); 279 } 280 281 /** 282 * command="DEL" が指定されたときの処理を行います。 283 * 284 * 接続先のSFTPサーバー側のファイル名を削除します。 285 * 286 * @param remoteFile SFTP先のファイル名 287 * @throws SftpException SFTPサーバー側のファイル名の削除に失敗したとき 288 */ 289 @Override 290 protected void actionDEL( final String remoteFile ) throws SftpException { 291 if( isDebug ) { System.out.println( "DEL: " + remoteFile ); } 292 293 channel.rm( remoteFile ); 294 } 295 296 /** 297 * command="DELDIR" が指定されたときの処理を行います。 298 * 299 * 接続先のSFTPサーバー側のディレクトリ名を削除します。 300 * 301 * @param remoteDir SFTP先のディレクトリ名 302 * @throws SftpException SFTPサーバー側のディレクトリ名の削除に失敗したとき 303 */ 304 @Override 305 protected void actionDELdir( final String remoteDir ) throws SftpException { 306 307 Vector<?> list = channel.ls( remoteDir ); 308 for (int i=0;i<list.size();i++) { 309 LsEntry entry = (LsEntry)list.get(i); 310 String rmt = entry.getFilename(); 311 if( ".".equals( rmt ) || "..".equals( rmt ) ) { continue; } // "." で始まるファイルもあるので、equasl 判定 312 SftpATTRS stat = entry.getAttrs(); 313 if( stat.isDir() ) { 314 actionDELdir( addFile( remoteDir,rmt ) ); 315 } 316 else { 317 actionDEL( addFile( remoteDir,rmt ) ); 318// channel.rm( addFile( remoteDir,rmt ) ); 319 } 320 } 321 channel.rmdir( remoteDir ); 322 } 323 324 /** 325 * 公開キー暗号化方式を利用する場合のキーファイル名を指定します。 326 * 327 * @param keyFile 秘密キーファイル名 328 */ 329 public void setKeyFile( final String keyFile ) { 330 if( keyFile != null ) { 331 this.keyFile = keyFile ; 332 } 333 } 334 335// private static class MyUserInfo implements UserInfo { 336// public String getPassword() { 337// return null; 338// } 339// // パスフレーズは空 340// public String getPassphrase() { 341// return ""; 342// } 343// public boolean promptPassword(String arg0) { 344// return true; 345// } 346// public boolean promptPassphrase(String arg0) { 347// return true; 348// } 349// public boolean promptYesNo(String arg0) { 350// return true; 351// } 352// public void showMessage(String arg0) { 353// } 354// } 355 356// ******************************************************************************************************* // 357// 以下、単独で使用する場合の main処理 358// ******************************************************************************************************* // 359 360 private static final Map<String,String> mustProparty ; // [プロパティ]必須チェック用 Map 361 private static final Map<String,String> usableProparty ; // [プロパティ]整合性チェック Map 362 363 static { 364 mustProparty = new LinkedHashMap<String,String>(); 365 mustProparty.put( "host", "接続先のSFTPサーバーのアドレスまたは、サーバー名(必須)" ); 366 mustProparty.put( "user", "接続するユーザー名(必須)" ); 367 mustProparty.put( "remoteFile", "接続先のSFTPサーバー側のファイル名(必須)" ); 368 369 usableProparty = new LinkedHashMap<String,String>(); 370 usableProparty.put( "passwd", "接続するユーザーのパスワード" ); 371 usableProparty.put( "localFile", "ローカルのファイル名" ); 372 usableProparty.put( "port", "接続に利用するポート番号を設定します。" ); 373 usableProparty.put( "keyFile", "公開キー暗号化方式を利用する場合のキーファイル名を指定します。" ); 374 usableProparty.put( "command", "SFTPサーバー側での処理の方法(GET/PUT/DEL)を指定します(初期値:GET)" ); 375 usableProparty.put( "mkdirs", "受け側ファイル(GET時:LOCAL、PUT時:SFTPサーバー)にディレクトリを作成するかどうか(初期値:true)" ); 376 usableProparty.put( "timeout", "Dataタイムアウト(初期値:600 [秒])" ); 377 usableProparty.put( "display", "[false/true]:trueは、検索状況を表示します(初期値:false)" ); 378 usableProparty.put( "debug", "デバッグ情報を標準出力に表示する(true)かしない(false)か" + 379 CR + "(初期値:false:表示しない)" ); 380 } 381 382 private static final String[] CMD_LST = new String[] { "GET","PUT","DEL","GETDIR","PUTDIR","DELDIR" }; 383 384 /** 385 * このクラスの動作確認用の、main メソッドです。 386 * 387 * @param args コマンド引数配列 388 */ 389 public static void main( final String[] args ) { 390 Argument arg = new Argument( "org.opengion.fukurou.util.SFTPConnect" ); 391 arg.setMustProparty( mustProparty ); 392 arg.setUsableProparty( usableProparty ); 393 arg.setArgument( args ); 394 395 SFTPConnect sftp = new SFTPConnect(); 396 397 String host = arg.getProparty( "host"); // SFTPサーバー 398 String user = arg.getProparty( "user" ); // ユーザー 399 String passwd = arg.getProparty( "passwd" ); // パスワード 400 401 sftp.setHostUserPass( host , user , passwd ); 402 403 sftp.setPort( arg.getProparty( "port" ) ); // 接続に利用するポート番号を設定します。 404 sftp.setKeyFile( arg.getProparty( "keyFile" ) ); // 公開キー暗号化方式を利用する場合のキーファイル名を指定します。 405 sftp.setMkdirs( arg.getProparty( "mkdirs" ,true ) ); // 受け側ファイルにディレクトリを作成するかどうか 406 sftp.setTimeout( arg.getProparty( "timeout" ,TIMEOUT ) ); // Dataタイムアウト(初期値:600 [秒]) 407 sftp.setDisplay( arg.getProparty( "display" ,false ) ); // trueは、検索状況を表示します(初期値:false) 408 sftp.setDebug( arg.getProparty( "debug" ,false ) ); // デバッグ情報を標準出力に表示する(true)かしない(false)か 409 410 try { 411 // コネクトします。 412 sftp.connect(); 413 414 String command = arg.getProparty( "command" ,"GET" ,CMD_LST ); // SFTP処理の方法を指定します。 415 String localFile = arg.getProparty( "localFile" ); // ローカルのファイル名 416 String remoteFile = arg.getProparty( "remoteFile" ); // SFTP先のファイル名 417 418 // command , localFile , remoteFile を元に、SFTP処理を行います。 419 sftp.action( command,localFile,remoteFile ); 420 } 421 catch( RuntimeException ex ) { 422 System.err.println( sftp.getErrMsg() ); 423 } 424 finally { 425 // ホストとの接続を終了します。 426 sftp.disconnect(); 427 } 428 } 429}