//yacySeedUploadScp.java
//-------------------------------------
//part of YACY
//(C) by Michael Peter Christen; mc@yacy.net
//first published on http://www.anomic.de
//Frankfurt, Germany, 2004
//
//This file ist contributed by Martin Thelian
//last major change: $LastChangedDate$ by $LastChangedBy$
//Revision: $LastChangedRevision$
//
//This program is free software; you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation; either version 2 of the License, or
//(at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

package net.yacy.peers.operation;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import net.yacy.cora.document.UTF8;
import net.yacy.server.serverSwitch;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;


public class yacySeedUploadScp implements yacySeedUploader {

    public static final String CONFIG_SCP_SERVER = "seedScpServer";
    public static final String CONFIG_SCP_SERVER_PORT = "seedScpServerPort";
    public static final String CONFIG_SCP_ACCOUNT = "seedScpAccount";
    public static final String CONFIG_SCP_PASSWORD = "seedScpPassword";
    public static final String CONFIG_SCP_PATH = "seedScpPath";

    @Override
    public String uploadSeedFile(final serverSwitch sb, final File seedFile) throws Exception {
        try {
            if (sb == null) throw new NullPointerException("Reference to serverSwitch nut not be null.");
            if ((seedFile == null)||(!seedFile.exists())) throw new Exception("Seed file does not exist.");

            final String  seedScpServer   = sb.getConfig(CONFIG_SCP_SERVER,null);
            final String  seedScpServerPort =  sb.getConfig(CONFIG_SCP_SERVER_PORT,"22");
            final String  seedScpAccount  = sb.getConfig(CONFIG_SCP_ACCOUNT,null);
            final String  seedScpPassword = sb.getConfig(CONFIG_SCP_PASSWORD,null);
            final String  seedScpPath = sb.getConfig(CONFIG_SCP_PATH,null);

            if (seedScpServer == null || seedScpServer.isEmpty())
                throw new Exception("Seed SCP upload settings not configured properly. Servername must not be null or empty.");
            else if (seedScpAccount == null || seedScpAccount.isEmpty())
                throw new Exception("Seed SCP upload settings not configured properly. Username must not be null or empty.");
            else if (seedScpPassword == null || seedScpPassword.isEmpty())
                throw new Exception("Seed SCP upload settings not configured properly. Password must not be null or empty.");
            else if (seedScpPath == null || seedScpPath.isEmpty())
                throw new Exception("Seed SCP upload settings not configured properly. File path must not be null or empty.");
            else if (seedScpServerPort == null || seedScpServerPort.isEmpty())
            throw new Exception("Seed SCP upload settings not configured properly. Server port must not be null or empty.");
            int port = 22;
            try {
                port = Integer.parseInt(seedScpServerPort);
            } catch (final NumberFormatException ex) {
                throw new Exception("Seed SCP upload settings not configured properly. Server port is not a vaild integer.");
            }

            return sshc.put(seedScpServer, port, seedFile, seedScpPath, seedScpAccount, seedScpPassword);
        } catch (final Exception e) {
            throw e;
        }
    }

    @Override
    public String[] getConfigurationOptions() {
        return new String[] {CONFIG_SCP_SERVER,CONFIG_SCP_SERVER_PORT,CONFIG_SCP_ACCOUNT,CONFIG_SCP_PASSWORD,CONFIG_SCP_PATH};
    }

}

class sshc {
    public static String put(
            final String host,
            final int port,
            final File localFile,
            final String remoteName,
            final String account,
            final String password
    ) throws Exception {

        Session session = null;
        try {
            // Creating a new secure channel object
            final JSch jsch=new JSch();

            // setting hostname, username, userpassword
            session = jsch.getSession(account, host, port);
            session.setPassword(password);

            /*
             * Setting the StrictHostKeyChecking to ignore unknown
             * hosts because of a missing known_hosts file ...
             */
            final java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking","no");
            session.setConfig(config);

            /*
             * we need this user interaction interface to support
             * the interactive-keyboard mode
             */
            final UserInfo ui=new SchUserInfo(password);
            session.setUserInfo(ui);

            // trying to connect ...
            session.connect();

            String command="scp -p -t " + remoteName;
            final Channel channel = session.openChannel("exec");
            ((ChannelExec)channel).setCommand(command);

            // get I/O streams for remote scp
            final OutputStream out=channel.getOutputStream();
            final InputStream in=channel.getInputStream();

            channel.connect();

            checkAck(in);

            // send "C0644 filesize filename", where filename should not include '/'
            final int filesize=(int)(localFile).length();
            command="C0644 "+filesize+" ";
            if(localFile.toString().lastIndexOf('/')>0){
                command+=localFile.toString().substring(localFile.toString().lastIndexOf('/')+1);
            }
            else{
                command+=localFile.toString();
            }
            command+="\n";
            out.write(UTF8.getBytes(command)); out.flush();

            checkAck(in);

            // send a content of lfile
            final byte[] buf=new byte[1024];
            BufferedInputStream bufferedIn = null;
            try {
                bufferedIn=new BufferedInputStream(new FileInputStream(localFile));
                while(true){
                    final int len=bufferedIn.read(buf, 0, buf.length);
                    if(len<=0) break;
                    out.write(buf, 0, len); out.flush();
                }
            } finally {
                if (bufferedIn != null) try{bufferedIn.close();}catch(final Exception e){}
            }

            // send '\0'
            buf[0]=0; out.write(buf, 0, 1); out.flush();

            checkAck(in);

            return "SCP: File uploaded successfully.";
        } catch (final Exception e) {
            throw new Exception("SCP: File uploading failed: " + e.getMessage());
        } finally {
            if ((session != null) && (session.isConnected())) session.disconnect();
        }
    }

    static int checkAck(final InputStream in) throws IOException{
        final int b=in.read();
        // b may be 0 for success,
        //          1 for error,
        //          2 for fatal error,
        //          -1
        if(b==0) return b;
        if(b==-1) return b;

        if(b==1 || b==2){
            final StringBuilder sb=new StringBuilder();
            int c;
            do {
                c=in.read();
                sb.append((char)c);
            }
            while(c!='\n');

            if(b==1){ // error
                throw new IOException(sb.toString());
            }
            if(b==2){ // fatal error
                throw new IOException(sb.toString());
            }
        }
        return b;
    }
}


class SchUserInfo
implements UserInfo, UIKeyboardInteractive {
    String passwd;

    public SchUserInfo(final String password) {
        this.passwd = password;
    }

    @Override
    public String getPassword() {
        return this.passwd;
    }

    @Override
    public boolean promptYesNo(final String str){
        System.err.println("User was prompted from: " + str);
        return true;
    }

    @Override
    public String getPassphrase() {
        return null;
    }

    @Override
    public boolean promptPassphrase(final String message) {
        System.out.println("promptPassphrase : " + message);
        return false;
    }

    @Override
    public boolean promptPassword(final String message) {
        System.out.println("promptPassword : " + message);
        return true;
    }

    /**
     * @see com.jcraft.jsch.UserInfo#showMessage(java.lang.String)
     */
    @Override
    public void showMessage(final String message) {
        System.out.println("Sch has tried to show the following message to the user: " + message);
    }

    @Override
    public String[] promptKeyboardInteractive(final String destination,
            final String name,
            final String instruction,
            final String[] prompt,
            final boolean[] echo) {
        System.out.println("User was prompted using interactive-keyboard: "  +
                "\n\tDestination: " + destination +
                "\n\tName:        " + name +
                "\n\tInstruction: " + instruction +
                "\n\tPrompt:      " + arrayToString2(prompt,"|") +
                "\n\techo:        " + arrayToString2(echo,"|"));

        if ((prompt.length >= 1) && (prompt[0].startsWith("Password")))
            return new String[]{this.passwd};
        return new String[]{};
    }

    static String arrayToString2(final String[] a, final String separator) {
        final StringBuilder result = new StringBuilder();// start with first element
        if (a.length > 0) {
            result.append(a[0]);
            for (int i=1; i<a.length; i++) {
                result.append(separator);
                result.append(a[i]);
            }
        }
        return result.toString();
    }

    static String arrayToString2(final boolean[] a, final String separator) {
        final StringBuilder result = new StringBuilder();// start with first element
        if (a.length > 0) {
            result.append(a[0]);
            for (int i=1; i<a.length; i++) {
                result.append(separator);
                result.append(a[i]);
            }
        }
        return result.toString();
    }
}