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