    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         * ケースでは、最初に１度だけ呼び出すだけです。
    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;             // 区切り文字を＋１する。
    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                        }
    319                }
    320                channel.rmdir( remoteDir );
    321        }
    322
    323        /**
    324         * 公開キー暗号化方式を利用する場合のキーファイル名を指定します。
    325         *
    326         * @param       keyFile 秘密キーファイル名
    327         */
    328        public void setKeyFile( final String keyFile ) {
    329                if( keyFile != null ) {
    330                        this.keyFile = keyFile ;
    331                }
    332        }
    333
    334        // ******************************************************************************************************* //
    335        //       以下、単独で使用する場合の main処理
    336        // ******************************************************************************************************* //
    337
    338        private static final Map<String,String> mustProparty   ;                // ［プロパティ］必須チェック用 Map
    339        private static final Map<String,String> usableProparty ;                // ［プロパティ］整合性チェック Map
    340
    341        static {
    342                mustProparty = new LinkedHashMap<String,String>();
    343                mustProparty.put( "host",               "接続先のSFTPサーバーのアドレスまたは、サーバー名(必須)" );
    344                mustProparty.put( "user",               "接続するユーザー名(必須)" );
    345                mustProparty.put( "remoteFile", "接続先のSFTPサーバー側のファイル名(必須)" );
    346
    347                usableProparty = new LinkedHashMap<String,String>();
    348                usableProparty.put( "passwd",           "接続するユーザーのパスワード" );
    349                usableProparty.put( "localFile",        "ローカルのファイル名" );
    350                usableProparty.put( "port",                     "接続に利用するポート番号を設定します。" );
    351                usableProparty.put( "keyFile",          "公開キー暗号化方式を利用する場合のキーファイル名を指定します。" );
    352                usableProparty.put( "command",          "SFTPサーバー側での処理の方法(GET/PUT/DEL)を指定します(初期値:GET)" );
    353                usableProparty.put( "mkdirs",           "受け側ファイル(GET時:LOCAL、PUT時:SFTPサーバー)にディレクトリを作成するかどうか(初期値:true)" );
    354                usableProparty.put( "timeout",          "Dataタイムアウト(初期値:600 [秒])" );
    355                usableProparty.put( "display",          "[false/true]:trueは、検索状況を表示します(初期値:false)" );
    356                usableProparty.put( "debug",            "デバッグ情報を標準出力に表示する(true)かしない(false)か" +
    357                                                                                        CR + "(初期値:false:表示しない)" );
    358        }
    359
    360        private static final String[] CMD_LST  = new String[] { "GET","PUT","DEL","GETDIR","PUTDIR","DELDIR" };
    361
    362        /**
    363         * このクラスの動作確認用の、main メソッドです。
    364         *
    365         * @param       args    コマンド引数配列
    366         */
    367        public static void main( final String[] args ) {
    368                Argument arg = new Argument( "org.opengion.fukurou.util.SFTPConnect" );
    369                arg.setMustProparty( mustProparty );
    370                arg.setUsableProparty( usableProparty );
    371                arg.setArgument( args );
    372
    373                SFTPConnect sftp = new SFTPConnect();
    374
    375                String host   = arg.getProparty( "host");                       // SFTPサーバー
    376                String user   = arg.getProparty( "user" );                      // ユーザー
    377                String passwd = arg.getProparty( "passwd" );            // パスワード
    378
    379                sftp.setHostUserPass( host , user , passwd );
    380
    381                sftp.setPort(           arg.getProparty( "port"                                 ) );    // 接続に利用するポート番号を設定します。
    382                sftp.setKeyFile(        arg.getProparty( "keyFile"                              ) );    // 公開キー暗号化方式を利用する場合のキーファイル名を指定します。
    383                sftp.setMkdirs(         arg.getProparty( "mkdirs"       ,true           ) );    // 受け側ファイルにディレクトリを作成するかどうか
    384                sftp.setTimeout(        arg.getProparty( "timeout"      ,TIMEOUT        ) );    // Dataタイムアウト(初期値:600 [秒])
    385                sftp.setDisplay(        arg.getProparty( "display"      ,false          ) );    // trueは、検索状況を表示します(初期値:false)
    386                sftp.setDebug(          arg.getProparty( "debug"        ,false          ) );    // デバッグ情報を標準出力に表示する(true)かしない(false)か
    387
    388                try {
    389                        // コネクトします。
    390                        sftp.connect();
    391
    392                        String command          = arg.getProparty( "command" ,"GET" ,CMD_LST  );        // SFTP処理の方法を指定します。
    393                        String localFile        = arg.getProparty( "localFile"  );                                      // ローカルのファイル名
    394                        String remoteFile       = arg.getProparty( "remoteFile" );                                      // SFTP先のファイル名
    395
    396                        // command , localFile , remoteFile を元に、SFTP処理を行います。
    397                        sftp.action( command,localFile,remoteFile );
    398                }
    399                catch( RuntimeException ex ) {
    400                        System.err.println( sftp.getErrMsg() );
    401                }
    402                finally {
    403                        // ホストとの接続を終了します。
    404                        sftp.disconnect();
    405                }
    406        }
    407}