/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.processors.standard.util; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Deque; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.regex.Matcher; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.processor.Processor; import org.apache.nifi.processor.util.StandardValidators; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.jcraft.jsch.SftpException; public class SFTPUtils { public static final PropertyDescriptor SFTP_PRIVATEKEY_PATH = new PropertyDescriptor.Builder() .required(false) .description("sftp.privatekey.path") .defaultValue(null) .name("sftp.privatekey.path") .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) .sensitive(false) .build(); public static final PropertyDescriptor REMOTE_PASSWORD = new PropertyDescriptor.Builder() .required(false) .description("remote.password") .defaultValue(null) .name("remote.password") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) .build(); public static final PropertyDescriptor SFTP_PRIVATEKEY_PASSPHRASE = new PropertyDescriptor.Builder() .required(false) .description("sftp.privatekey.passphrase") .defaultValue(null) .name("sftp.privatekey.passphrase") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(true) .build(); public static final PropertyDescriptor SFTP_PORT = new PropertyDescriptor.Builder() .required(false) .description("sftp.port") .defaultValue(null) .name("sftp.port") .addValidator(StandardValidators.NON_NEGATIVE_INTEGER_VALIDATOR) .sensitive(false) .build(); public static final PropertyDescriptor NETWORK_DATA_TIMEOUT = new PropertyDescriptor.Builder() .required(false) .description("network.data.timeout") .defaultValue(null) .name("network.data.timeout") .addValidator(StandardValidators.INTEGER_VALIDATOR) .sensitive(false) .build(); public static final PropertyDescriptor SFTP_HOSTKEY_FILENAME = new PropertyDescriptor.Builder() .required(false) .description("sftp.hostkey.filename") .defaultValue(null) .name("sftp.hostkey.filename") .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR) .sensitive(false) .build(); public static final PropertyDescriptor NETWORK_CONNECTION_TIMEOUT = new PropertyDescriptor.Builder() .required(false) .description("network.connection.timeout") .defaultValue(null) .name("network.connection.timeout") .addValidator(StandardValidators.INTEGER_VALIDATOR) .sensitive(false) .build(); // required properties public static final PropertyDescriptor REMOTE_HOSTNAME = new PropertyDescriptor.Builder() .required(true) .description("remote.hostname") .defaultValue(null) .name("remote.hostname") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(false) .build(); public static final PropertyDescriptor REMOTE_USERNAME = new PropertyDescriptor.Builder() .required(true) .description("remote.username") .defaultValue(null) .name("remote.username") .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) .sensitive(false) .build(); private static final List<PropertyDescriptor> propertyDescriptors = new ArrayList<>(); static { JSch.setLogger(SFTPUtils.createLogger()); propertyDescriptors.add(SFTP_PRIVATEKEY_PATH); propertyDescriptors.add(REMOTE_PASSWORD); propertyDescriptors.add(SFTP_PRIVATEKEY_PASSPHRASE); propertyDescriptors.add(SFTP_PORT); propertyDescriptors.add(NETWORK_DATA_TIMEOUT); propertyDescriptors.add(SFTP_HOSTKEY_FILENAME); propertyDescriptors.add(NETWORK_CONNECTION_TIMEOUT); propertyDescriptors.add(REMOTE_USERNAME); propertyDescriptors.add(REMOTE_HOSTNAME); } private static final Log logger = LogFactory.getLog(SFTPUtils.class); public static List<PropertyDescriptor> getPropertyDescriptors() { return propertyDescriptors; } public static SFTPConnection connectSftp(final SFTPConfiguration conf) throws JSchException, SftpException, IOException { final JSch jsch = new JSch(); final Session session = SFTPUtils.createSession(conf, jsch); final ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp"); sftp.connect(); return new SFTPConnection(session, sftp); } public static void changeWorkingDirectory(final ChannelSftp sftp, final String dirPath, final boolean createDirs, final Processor proc) throws IOException { final Deque<String> stack = new LinkedList<>(); File dir = new File(dirPath); String currentWorkingDirectory = null; boolean dirExists = false; final String forwardPaths = dir.getPath().replaceAll(Matcher.quoteReplacement("\\"), Matcher.quoteReplacement("/")); try { currentWorkingDirectory = sftp.pwd(); logger.debug(proc + " attempting to change directory from " + currentWorkingDirectory + " to " + dir.getPath()); //always use forward paths for long string attempt sftp.cd(forwardPaths); dirExists = true; logger.debug(proc + " changed working directory to '" + forwardPaths + "' from '" + currentWorkingDirectory + "'"); } catch (final SftpException sftpe) { logger.debug(proc + " could not change directory to '" + forwardPaths + "' from '" + currentWorkingDirectory + "' so trying the hard way."); } if (dirExists) { return; } if (!createDirs) { throw new IOException("Unable to change to requested working directory \'" + forwardPaths + "\' but not configured to create dirs."); } do { stack.push(dir.getName()); } while ((dir = dir.getParentFile()) != null); String dirName = null; while ((dirName = stack.peek()) != null) { stack.pop(); //find out if exists, if not make it if configured to do so or throw exception dirName = ("".equals(dirName.trim())) ? "/" : dirName; try { sftp.cd(dirName); } catch (final SftpException sftpe) { logger.debug(proc + " creating new directory and changing to it " + dirName); try { sftp.mkdir(dirName); sftp.cd(dirName); } catch (final SftpException e) { throw new IOException(proc + " could not make/change directory to [" + dirName + "] [" + e.getLocalizedMessage() + "]", e); } } } } public static Session createSession(final SFTPConfiguration conf, final JSch jsch) throws JSchException, IOException { if (conf == null || null == jsch) { throw new NullPointerException(); } final Hashtable<String, String> newOptions = new Hashtable<>(); Session session = jsch.getSession(conf.username, conf.hostname, conf.port); final String hostKeyVal = conf.hostkeyFile; if (null != hostKeyVal) { try { jsch.setKnownHosts(hostKeyVal); } catch (final IndexOutOfBoundsException iob) { throw new IOException("Unable to establish connection due to bad known hosts key file " + hostKeyVal, iob); } } else { newOptions.put("StrictHostKeyChecking", "no"); session.setConfig(newOptions); } final String privateKeyVal = conf.privatekeyFile; if (null != privateKeyVal) { jsch.addIdentity(privateKeyVal, conf.privateKeypassphrase); } if (null != conf.password) { session.setPassword(conf.password); } session.setTimeout(conf.connectionTimeout); //set timeout for connection session.connect(); session.setTimeout(conf.dataTimeout); //set timeout for data transfer return session; } public static com.jcraft.jsch.Logger createLogger() { return new com.jcraft.jsch.Logger() { @Override public boolean isEnabled(int level) { return true; } @Override public void log(int level, String message) { logger.debug("SFTP Log: " + message); } }; } public static class SFTPConfiguration { private String hostname; private String username; private int port = 22; private int connectionTimeout = 0; private int dataTimeout = 0; private String hostkeyFile; private String privatekeyFile; private String password; private String privateKeypassphrase; public SFTPConfiguration() { } public void setHostname(final String val) { this.hostname = val; } public String getHostname() { return hostname; } public void setUsername(final String val) { this.username = val; } public String getUsername() { return username; } public void setPort(final String val) { if (val != null) { port = Integer.parseInt(val); } } public void setConnectionTimeout(final String val) { if (val != null) { connectionTimeout = Integer.parseInt(val); } } public void setDataTimeout(final String val) { if (val != null) { dataTimeout = Integer.parseInt(val); } } public void setHostkeyFile(final String val) { this.hostkeyFile = val; } public void setPrivateKeyFile(final String val) { this.privatekeyFile = val; } public void setPassword(final String val) { this.password = val; } public void setPrivateKeyPassphrase(final String val) { this.privateKeypassphrase = val; } } }