//yacySeedUploadScp.java 
//-------------------------------------
//part of YACY
//(C) by Michael Peter Christen; mc@anomic.de
//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
//
//Using this software in any meaning (reading, learning, copying, compiling,
//running) means that you agree that the Author(s) is (are) not responsible
//for cost, loss of data or any harm that may be caused directly or indirectly
//by usage of this softare or this documentation. The usage of this software
//is on your own risk. The installation and usage (starting/running) of this
//software may allow other people or application to access your computer and
//any attached devices and is highly dependent on the configuration of the
//software which must be done by the user of the software; the author(s) is
//(are) also not responsible for proper configuration and usage of the
//software, even if provoked by documentation provided together with
//the software.
//
//Any changes to this file according to the GPL as documented in the file
//gpl.txt aside this file in the shipment you received can be done to the
//lines that follows this copyright notice here, but changes must not be
//done inside the copyright notive above. A re-distribution must contain
//the intact and unchanged copyright notice.
//Contributions and changes to the program code must be marked as such.


package de.anomic.yacy.seedUpload;

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 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;

import de.anomic.server.serverSwitch;
import de.anomic.yacy.yacySeedDB;
import de.anomic.yacy.yacySeedUploader;

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";
    
    public String uploadSeedFile(serverSwitch sb, yacySeedDB seedDB, File seedFile) throws Exception {
        try {        
            if (sb == null) throw new NullPointerException("Reference to serverSwitch nut not be null.");
            if (seedDB == null) throw new NullPointerException("Reference to seedDB must not be null.");
            if ((seedFile == null)||(!seedFile.exists())) throw new Exception("Seed file does not exist.");
            
            String  seedScpServer   = sb.getConfig(CONFIG_SCP_SERVER,null);
            String  seedScpServerPort =  sb.getConfig(CONFIG_SCP_SERVER_PORT,"22");
            String  seedScpAccount  = sb.getConfig(CONFIG_SCP_ACCOUNT,null);
            String  seedScpPassword = sb.getConfig(CONFIG_SCP_PASSWORD,null);
            String  seedScpPath = sb.getConfig(CONFIG_SCP_PATH,null);       
            
            if (seedScpServer == null || seedScpServer.length() == 0)
                throw new Exception("Seed SCP upload settings not configured properly. Servername must not be null or empty.");
            else if (seedScpAccount == null || seedScpAccount.length() == 0)
                throw new Exception("Seed SCP upload settings not configured properly. Username must not be null or empty.");            
            else if (seedScpPassword == null || seedScpPassword.length() == 0)
                throw new Exception("Seed SCP upload settings not configured properly. Password must not be null or empty.");            
            else if (seedScpPath == null || seedScpPath.length() == 0)
                throw new Exception("Seed SCP upload settings not configured properly. File path must not be null or empty.");
            else if (seedScpServerPort == null || seedScpServerPort.length() == 0)
            throw new Exception("Seed SCP upload settings not configured properly. Server port must not be null or empty.");
            int port = 22;
            try {
                port = Integer.valueOf(seedScpServerPort).intValue();
            } catch (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 (Exception e) {
            throw e;
        }
    }
    
    public String[] getConfigurationOptions() {
        return new String[] {CONFIG_SCP_SERVER,CONFIG_SCP_SERVER_PORT,CONFIG_SCP_ACCOUNT,CONFIG_SCP_PASSWORD,CONFIG_SCP_PATH};
    }
    
    public String[] getLibxDependencies() {
        return new String[]{"jsch-0.1.21.jar"};
    }
    
}

class sshc {
    public static String put(
            String host,
            int port,
            File localFile, 
            String remoteName,
            String account, 
            String password
    ) throws Exception {
        
        Session session = null;
        try {    
            // Creating a new secure channel object
            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 ...
             */
            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
             */             
            UserInfo ui=new SchUserInfo(password);
            session.setUserInfo(ui);        
            
            // trying to connect ...
            session.connect();        
            
            String command="scp -p -t " + remoteName;
            Channel 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();
            
            checkAck(in);
            
            // send "C0644 filesize filename", where filename should not include '/'
            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(command.getBytes()); out.flush();
            
            checkAck(in);
            
            // send a content of lfile
            byte[] buf=new byte[1024];
            BufferedInputStream bufferedIn = null;
            try {
                bufferedIn=new BufferedInputStream(new FileInputStream(localFile));                
                while(true){
                    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(Exception e){}
            }
            
            // send '\0'
            buf[0]=0; out.write(buf, 0, 1); out.flush();
            
            checkAck(in);       
            
            return "SCP: File uploaded successfully.";
        } catch (Exception e) {
            throw new Exception("SCP: File uploading failed: " + e.getMessage());
        } finally {
            if ((session != null) && (session.isConnected())) session.disconnect();
        }
    }
    
    static int checkAck(InputStream in) throws IOException{
        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){
            StringBuffer sb=new StringBuffer();
            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(String password) {
        this.passwd = password;
    }
    
    public String getPassword() { 
        return this.passwd; 
    }
    
    public boolean promptYesNo(String str){   
        System.err.println("User was prompted from: " + str);
        return true;
    }
    
    public String getPassphrase() { 
        return null; 
    }
    
    public boolean promptPassphrase(String message) {
        System.out.println("promptPassphrase : " + message);            
        return false;
    }
    
    public boolean promptPassword(String message) {
        System.out.println("promptPassword : " + message);      
        return true;
    }
    
    /**
     * @see com.jcraft.jsch.UserInfo#showMessage(java.lang.String)
     */
    public void showMessage(String message) {
        System.out.println("Sch has tried to show the following message to the user: " + message);
    }
    
    public String[] promptKeyboardInteractive(String destination,
            String name,
            String instruction,
            String[] prompt,
            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[]{};
    }
    
    String arrayToString2(String[] a, String separator) {
        StringBuffer result = new StringBuffer();// 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();        
    }
    
    String arrayToString2(boolean[] a, String separator) {
        StringBuffer result = new StringBuffer();// 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();        
    }
}