CAMEL-7999: More components include documentation
Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/d7cb0178 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/d7cb0178 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/d7cb0178 Branch: refs/heads/master Commit: d7cb0178ab184a55f63a04b4e5defe556f2c0f86 Parents: 9fc7cef Author: Claus Ibsen <davscl...@apache.org> Authored: Thu Dec 18 11:15:15 2014 +0100 Committer: Claus Ibsen <davscl...@apache.org> Committed: Thu Dec 18 11:15:15 2014 +0100 ---------------------------------------------------------------------- components/camel-jsch/pom.xml | 2 +- .../camel/component/jsch/JschComponent.java | 84 ---- .../camel/component/jsch/ScpConfiguration.java | 137 ------ .../camel/component/jsch/ScpEndpoint.java | 71 --- .../apache/camel/component/jsch/ScpFile.java | 71 --- .../camel/component/jsch/ScpOperations.java | 449 ------------------- .../jsch/ScpProcessStrategyFactory.java | 85 ---- .../camel/component/jsch/ScpProducer.java | 27 -- .../camel/component/scp/ScpComponent.java | 78 ++++ .../camel/component/scp/ScpConfiguration.java | 149 ++++++ .../apache/camel/component/scp/ScpEndpoint.java | 77 ++++ .../org/apache/camel/component/scp/ScpFile.java | 71 +++ .../camel/component/scp/ScpOperations.java | 449 +++++++++++++++++++ .../scp/ScpProcessStrategyFactory.java | 85 ++++ .../apache/camel/component/scp/ScpProducer.java | 27 ++ .../services/org/apache/camel/component/scp | 4 +- .../component/jsch/ScpServerTestSupport.java | 226 ---------- .../component/jsch/ScpSimpleProduceTest.java | 99 ---- .../component/scp/ScpServerTestSupport.java | 226 ++++++++++ .../component/scp/ScpSimpleProduceTest.java | 99 ++++ .../src/test/resources/log4j.properties | 2 +- 21 files changed, 1265 insertions(+), 1253 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/pom.xml ---------------------------------------------------------------------- diff --git a/components/camel-jsch/pom.xml b/components/camel-jsch/pom.xml index d00e6f6..22ddf24 100644 --- a/components/camel-jsch/pom.xml +++ b/components/camel-jsch/pom.xml @@ -36,7 +36,7 @@ com.jcraft.jsch.*;version="[0.1.40,0.2)", org.apache.camel.component.file.strategy;${camel.osgi.import.strict.version} </camel.osgi.import.before.defaults> - <camel.osgi.export.pkg>org.apache.camel.component.jsch.*</camel.osgi.export.pkg> + <camel.osgi.export.pkg>org.apache.camel.component.scp.*</camel.osgi.export.pkg> <camel.osgi.export.service>org.apache.camel.spi.ComponentResolver;component=scp</camel.osgi.export.service> </properties> http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/JschComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/JschComponent.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/JschComponent.java deleted file mode 100644 index b52ccb0..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/JschComponent.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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.camel.component.jsch; - -import java.net.URI; -import java.util.Map; - -import com.jcraft.jsch.JSch; - -import org.apache.camel.CamelContext; -import org.apache.camel.component.file.GenericFileEndpoint; -import org.apache.camel.component.file.remote.RemoteFileComponent; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Component providing secure messaging using JSch - */ -public class JschComponent extends RemoteFileComponent<ScpFile> { - private static final Logger LOG = LoggerFactory.getLogger(JschComponent.class); - static { - JSch.setConfig("StrictHostKeyChecking", "yes"); - JSch.setLogger(new com.jcraft.jsch.Logger() { - @Override - public boolean isEnabled(int level) { - return level == FATAL || level == ERROR ? LOG.isErrorEnabled() - : level == WARN ? LOG.isWarnEnabled() - : level == INFO ? LOG.isInfoEnabled() : LOG.isDebugEnabled(); - } - @Override - public void log(int level, String message) { - if (level == FATAL || level == ERROR) { - LOG.error("[JSCH] {}", message); - } else if (level == WARN) { - LOG.warn("[JSCH] {}", message); - } else if (level == INFO) { - LOG.info("[JSCH] {}", message); - } else { - LOG.debug("[JSCH] {}", message); - } - } - }); - } - - public JschComponent() { - } - - public JschComponent(CamelContext context) { - super(context); - } - - @Override - protected GenericFileEndpoint<ScpFile> buildFileEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { - // TODO: revisit stripping the query part; should not be needed with valid uris - int query = uri.indexOf("?"); - return new ScpEndpoint(uri, this, new ScpConfiguration(new URI(query >= 0 ? uri.substring(0, query) : uri))); - } - - protected void afterPropertiesSet(GenericFileEndpoint<ScpFile> endpoint) throws Exception { - // noop - } - - @Override - public void doStop() throws Exception { - // TODO: close all sessions - super.doStop(); - } -} - http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpConfiguration.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpConfiguration.java deleted file mode 100644 index 9bcb2d2..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpConfiguration.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * 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.camel.component.jsch; - -import java.net.URI; - -import org.apache.camel.component.file.remote.RemoteFileConfiguration; - -/** - * Secure FTP configuration - */ -public class ScpConfiguration extends RemoteFileConfiguration { - - public static final int DEFAULT_SFTP_PORT = 22; - public static final String DEFAULT_MOD = "664"; - private String knownHostsFile; - private String privateKeyFile; - private String privateKeyFilePassphrase; - private String strictHostKeyChecking; - private int serverAliveInterval; - private int serverAliveCountMax = 1; - private String chmod = DEFAULT_MOD; - // comma separated list of ciphers. - // null means default jsch list will be used - private String ciphers; - private int compression; - - public ScpConfiguration() { - setProtocol("sftp"); - } - - public ScpConfiguration(URI uri) { - super(uri); - } - - @Override - protected void setDefaultPort() { - setPort(DEFAULT_SFTP_PORT); - } - - public String getKnownHostsFile() { - return knownHostsFile; - } - - public void setKnownHostsFile(String knownHostsFile) { - this.knownHostsFile = knownHostsFile; - } - - public String getPrivateKeyFile() { - return privateKeyFile; - } - - public void setPrivateKeyFile(String privateKeyFile) { - this.privateKeyFile = privateKeyFile; - } - - public String getPrivateKeyFilePassphrase() { - return privateKeyFilePassphrase; - } - - public void setPrivateKeyFilePassphrase(String privateKeyFilePassphrase) { - this.privateKeyFilePassphrase = privateKeyFilePassphrase; - } - - public String getStrictHostKeyChecking() { - return strictHostKeyChecking; - } - - public void setStrictHostKeyChecking(String strictHostKeyChecking) { - this.strictHostKeyChecking = strictHostKeyChecking; - } - - public void setServerAliveInterval(int serverAliveInterval) { - this.serverAliveInterval = serverAliveInterval; - } - - public int getServerAliveInterval() { - return serverAliveInterval; - } - - public void setServerAliveCountMax(int serverAliveCountMax) { - this.serverAliveCountMax = serverAliveCountMax; - } - - public int getServerAliveCountMax() { - return serverAliveCountMax; - } - - public void setChmod(String chmod) { - if (chmod.length() == 3) { - for (byte c : chmod.getBytes()) { - if (c < '0' || c > '7') { - chmod = DEFAULT_MOD; - break; - } - } - } else { - chmod = DEFAULT_MOD; - } - // May be interesting to log the fallback to DEFAULT_MOD for invalid configuration - this.chmod = chmod; - } - - public String getChmod() { - return chmod; - } - - public void setCiphers(String ciphers) { - this.ciphers = ciphers; - } - - public String getCiphers() { - return ciphers; - } - - public int getCompression() { - return compression; - } - - public void setCompression(int compression) { - this.compression = compression; - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpEndpoint.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpEndpoint.java deleted file mode 100644 index 4131f73..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpEndpoint.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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.camel.component.jsch; - -import org.apache.camel.Expression; -import org.apache.camel.Processor; -import org.apache.camel.component.file.GenericFileProducer; -import org.apache.camel.component.file.remote.RemoteFileConfiguration; -import org.apache.camel.component.file.remote.RemoteFileConsumer; -import org.apache.camel.component.file.remote.RemoteFileEndpoint; -import org.apache.camel.component.file.remote.RemoteFileOperations; - -/** - * Secure Copy Endpoint - */ -public class ScpEndpoint extends RemoteFileEndpoint<ScpFile> { - - public ScpEndpoint() { - } - - public ScpEndpoint(String uri, JschComponent component, RemoteFileConfiguration configuration) { - super(uri, component, configuration); - } - - @Override - public ScpConfiguration getConfiguration() { - return (ScpConfiguration) this.configuration; - } - - @Override - protected RemoteFileConsumer<ScpFile> buildConsumer(Processor processor) { - throw new UnsupportedOperationException("This component does not support consuming from this endpoint"); - } - - @Override - protected GenericFileProducer<ScpFile> buildProducer() { - return new ScpProducer(this, createRemoteFileOperations()); - } - - @Override - public RemoteFileOperations<ScpFile> createRemoteFileOperations() { - ScpOperations operations = new ScpOperations(); - operations.setEndpoint(this); - return operations; - } - - @Override - public String getScheme() { - return "scp"; - } - - @Override - public Expression getTempFileName() { - log.debug("Creation of temporary files not supported by the scp: protocol."); - return null; - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpFile.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpFile.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpFile.java deleted file mode 100644 index f21cb81..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpFile.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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.camel.component.jsch; - - -/** - * SFTP remote file operations - */ -public class ScpFile { - private boolean directory; - private int attrs; - private int length; - private String name; - private String parent; - - public String header() { - StringBuilder buffer = new StringBuilder(); - buffer.append(directory ? "D" : "C"); - buffer.append(" "); - return buffer.toString(); - } - - public boolean isDirectory() { - return directory; - } - public void setDirectory(boolean directory) { - this.directory = directory; - } - public int getAttrs() { - return attrs; - } - public void setAttrs(int attrs) { - this.attrs = attrs; - } - public int getLength() { - return length; - } - public void setLength(int length) { - this.length = length; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getParent() { - return parent; - } - - public void setParent(String parent) { - this.parent = parent; - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpOperations.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpOperations.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpOperations.java deleted file mode 100644 index 5759c55..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpOperations.java +++ /dev/null @@ -1,449 +0,0 @@ -/** - * 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.camel.component.jsch; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Hashtable; -import java.util.List; - -import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import com.jcraft.jsch.UIKeyboardInteractive; -import com.jcraft.jsch.UserInfo; - -import org.apache.camel.Exchange; -import org.apache.camel.InvalidPayloadException; -import org.apache.camel.component.file.GenericFileEndpoint; -import org.apache.camel.component.file.GenericFileOperationFailedException; -import org.apache.camel.component.file.remote.RemoteFileConfiguration; -import org.apache.camel.component.file.remote.RemoteFileOperations; -import org.apache.camel.util.IOHelper; -import org.apache.camel.util.ObjectHelper; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * SCP remote file operations - */ -public class ScpOperations implements RemoteFileOperations<ScpFile> { - private static final String DEFAULT_KNOWN_HOSTS = "META-INF/.ssh/known_hosts"; - private static final Logger LOG = LoggerFactory.getLogger(ScpOperations.class); - - private ScpEndpoint endpoint; - private Session session; - private ChannelExec channel; - - @Override - public void setEndpoint(GenericFileEndpoint<ScpFile> endpoint) { - this.endpoint = (ScpEndpoint)endpoint; - } - - @Override - public boolean deleteFile(String name) throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'delete' not supported by the scp: protocol"); - } - - @Override - public boolean existsFile(String name) throws GenericFileOperationFailedException { - // maybe... cannot determine using the scp: protocol - return false; - } - - @Override - public boolean renameFile(String from, String to) throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'rename' not supported by the scp: protocol"); - } - - @Override - public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException { - // done by the server - return true; - } - - @Override - public boolean retrieveFile(String name, Exchange exchange) throws GenericFileOperationFailedException { - // TODO: implement - return false; - } - - @Override - public void releaseRetreivedFileResources(Exchange exchange) throws GenericFileOperationFailedException { - // No-op - } - - @Override - public boolean storeFile(String name, Exchange exchange) throws GenericFileOperationFailedException { - ObjectHelper.notNull(session, "session"); - ScpConfiguration cfg = endpoint.getConfiguration(); - - int timeout = cfg.getConnectTimeout(); - if (LOG.isTraceEnabled()) { - LOG.trace("Opening channel to {} with {} timeout...", cfg.remoteServerInformation(), - timeout > 0 ? (Integer.toString(timeout) + " ms") : "no"); - } - String file = getRemoteFile(name, cfg); - - - InputStream is = null; - if (exchange.getIn().getBody() == null) { - // Do an explicit test for a null body and decide what to do - if (endpoint.isAllowNullBody()) { - LOG.trace("Writing empty file."); - is = new ByteArrayInputStream(new byte[]{}); - } else { - throw new GenericFileOperationFailedException("Cannot write null body to file: " + name); - } - } - - try { - channel = (ChannelExec) session.openChannel("exec"); - channel.setCommand(getScpCommand(cfg, file)); - channel.connect(timeout); - LOG.trace("Channel connected to {}", cfg.remoteServerInformation()); - - try { - if (is == null) { - is = exchange.getIn().getMandatoryBody(InputStream.class); - } - write(channel, file, is, cfg); - } catch (InvalidPayloadException e) { - throw new GenericFileOperationFailedException("Cannot store file: " + name, e); - } catch (IOException e) { - throw new GenericFileOperationFailedException("Failed to write file " + file, e); - } finally { - // must close stream after usage - IOHelper.close(is); - } - } catch (JSchException e) { - throw new GenericFileOperationFailedException("Failed to write file " + file, e); - } finally { - if (channel != null) { - LOG.trace("Disconnecting 'exec' scp channel"); - channel.disconnect(); - channel = null; - LOG.trace("Channel disconnected from {}", cfg.remoteServerInformation()); - } - } - return true; - } - - @Override - public String getCurrentDirectory() throws GenericFileOperationFailedException { - return endpoint.getConfiguration().getDirectory(); - } - - @Override - public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'cd " + path + "' not supported by the scp: protocol"); - } - - @Override - public void changeToParentDirectory() throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'cd ..' not supported by the scp: protocol"); - } - - @Override - public List<ScpFile> listFiles() throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'ls' not supported by the scp: protocol"); - } - - @Override - public List<ScpFile> listFiles(String path) throws GenericFileOperationFailedException { - throw new GenericFileOperationFailedException("Operation 'ls " + path + "' not supported by the scp: protocol"); - } - - @Override - public boolean connect(RemoteFileConfiguration configuration) throws GenericFileOperationFailedException { - if (!isConnected()) { - session = createSession(configuration instanceof ScpConfiguration ? (ScpConfiguration)configuration : null); - // TODO: deal with reconnection attempts - if (!isConnected()) { - session = null; - throw new GenericFileOperationFailedException("Failed to connect to " + configuration.remoteServerInformation()); - } - } - return true; - } - - @Override - public boolean isConnected() throws GenericFileOperationFailedException { - return session != null && session.isConnected(); - } - - @Override - public void disconnect() throws GenericFileOperationFailedException { - if (isConnected()) { - session.disconnect(); - } - session = null; - } - - @Override - public boolean sendNoop() throws GenericFileOperationFailedException { - // not supported by scp: - return true; - } - - @Override - public boolean sendSiteCommand(String command) throws GenericFileOperationFailedException { - // TODO: not really used, maybe implement at a later time - return true; - } - - private Session createSession(ScpConfiguration config) { - ObjectHelper.notNull(config, "ScpConfiguration"); - try { - final JSch jsch = new JSch(); - // get from configuration - if (ObjectHelper.isNotEmpty(config.getCiphers())) { - LOG.debug("Using ciphers: {}", config.getCiphers()); - Hashtable<String, String> ciphers = new Hashtable<String, String>(); - ciphers.put("cipher.s2c", config.getCiphers()); - ciphers.put("cipher.c2s", config.getCiphers()); - JSch.setConfig(ciphers); - } - if (ObjectHelper.isNotEmpty(config.getPrivateKeyFile())) { - LOG.debug("Using private keyfile: {}", config.getPrivateKeyFile()); - String pkfp = config.getPrivateKeyFilePassphrase(); - jsch.addIdentity(config.getPrivateKeyFile(), ObjectHelper.isNotEmpty(pkfp) ? pkfp : null); - } - - String knownHostsFile = config.getKnownHostsFile(); - jsch.setKnownHosts(ObjectHelper.isEmpty(knownHostsFile) ? DEFAULT_KNOWN_HOSTS : knownHostsFile); - session = jsch.getSession(config.getUsername(), config.getHost(), config.getPort()); - session.setTimeout(config.getTimeout()); - session.setUserInfo(new SessionUserInfo(config)); - - if (ObjectHelper.isNotEmpty(config.getStrictHostKeyChecking())) { - LOG.debug("Using StrickHostKeyChecking: {}", config.getStrictHostKeyChecking()); - session.setConfig("StrictHostKeyChecking", config.getStrictHostKeyChecking()); - } - - int timeout = config.getConnectTimeout(); - LOG.debug("Connecting to {} with {} timeout...", config.remoteServerInformation(), - timeout > 0 ? (Integer.toString(timeout) + " ms") : "no"); - if (timeout > 0) { - session.connect(timeout); - } else { - session.connect(); - } - } catch (JSchException e) { - session = null; - LOG.warn("Could not create ssh session for " + config.remoteServerInformation(), e); - } - return session; - } - - private void write(ChannelExec c, String name, InputStream data, ScpConfiguration cfg) throws IOException { - OutputStream os = c.getOutputStream(); - InputStream is = c.getInputStream(); - - try { - writeFile(name, data, os, is, cfg); - } finally { - IOHelper.close(is, os); - } - } - - private void writeFile(String filename, InputStream data, OutputStream os, InputStream is, ScpConfiguration cfg) throws IOException { - final int lineFeed = '\n'; - String bytes; - int pos = filename.indexOf('/'); - if (pos >= 0) { - // write to child directory - String dir = filename.substring(0, pos); - bytes = "D0775 0 " + dir; - LOG.trace("[scp:sink] {}", bytes); - os.write(bytes.getBytes()); - os.write(lineFeed); - os.flush(); - readAck(is, false); - - writeFile(filename.substring(pos + 1), data, os, is, cfg); - - bytes = "E"; - LOG.trace("[scp:sink] {}", bytes); - os.write(bytes.getBytes()); - os.write(lineFeed); - os.flush(); - readAck(is, false); - } else { - int count = 0; - int read; - int size = endpoint.getBufferSize(); - byte[] reply = new byte[size]; - - // figure out the stream size as we need to pass it in the header - BufferedInputStream buffer = new BufferedInputStream(data, size); - try { - buffer.mark(Integer.MAX_VALUE); - while ((read = buffer.read(reply)) != -1) { - count += read; - } - - // send the header - bytes = "C0" + cfg.getChmod() + " " + count + " " + filename; - LOG.trace("[scp:sink] {}", bytes); - os.write(bytes.getBytes()); - os.write(lineFeed); - os.flush(); - readAck(is, false); - - // now send the stream - buffer.reset(); - while ((read = buffer.read(reply)) != -1) { - os.write(reply, 0, read); - } - writeAck(os); - readAck(is, false); - } finally { - IOHelper.close(buffer); - } - } - } - - private void writeAck(OutputStream os) throws IOException { - os.write(0); - os.flush(); - } - - private int readAck(InputStream is, boolean failOnEof) throws IOException { - String message; - int answer = is.read(); - switch (answer) { - case -1: - if (failOnEof) { - message = "[scp] Unexpected end of stream"; - throw new EOFException(message); - } - break; - case 1: - message = "[scp] WARN " + readLine(is); - LOG.warn(message); - break; - case 2: - message = "[scp] NACK " + readLine(is); - throw new IOException(message); - default: - // case 0: - break; - } - return answer; - } - - private String readLine(InputStream is) throws IOException { - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - try { - int c; - do { - c = is.read(); - if (c == '\n') { - return bytes.toString(); - } - bytes.write(c); - } while (c != -1); - } finally { - IOHelper.close(bytes); - } - - String message = "[scp] Unexpected end of stream"; - throw new IOException(message); - } - - private static String getRemoteTarget(ScpConfiguration config) { - // use current dir (".") if target directory not specified in uri - return config.getDirectory().isEmpty() ? "." : config.getDirectory(); - } - - private static String getRemoteFile(String name, ScpConfiguration config) { - String dir = config.getDirectory(); - dir = dir.endsWith("/") ? dir : dir + "/"; - return name.startsWith(dir) ? name.substring(dir.length()) : name; - } - - private static boolean isRecursiveScp(String name) { - return name.indexOf('/') > 0; - } - - private static String getScpCommand(ScpConfiguration config, String name) { - StringBuilder cmd = new StringBuilder(); - cmd.append("scp "); - // TODO: need config for scp *-p* (preserves modification times, access times, and modes from the original file) - // String command="scp " + (ptimestamp ? "-p " : "") + "-t " + configuration.getDirectory(); - // TODO: refactor to use generic command - cmd.append(isRecursiveScp(name) ? "-r " : ""); - cmd.append("-t "); - cmd.append(getRemoteTarget(config)); - return cmd.toString(); - } - - protected static final class SessionUserInfo implements UserInfo, UIKeyboardInteractive { - private final ScpConfiguration config; - public SessionUserInfo(ScpConfiguration config) { - ObjectHelper.notNull(config, "config"); - this.config = config; - } - - @Override - public String getPassphrase() { - LOG.warn("Private Key authentication not supported"); - return null; - } - @Override - public String getPassword() { - LOG.debug("Providing password for ssh authentication of user '{}'", config.getUsername()); - return config.getPassword(); - } - @Override - public boolean promptPassword(String message) { - LOG.debug(message); - return true; - } - @Override - public boolean promptPassphrase(String message) { - LOG.debug(message); - return true; - } - @Override - public boolean promptYesNo(String message) { - LOG.debug(message); - return false; - } - @Override - public void showMessage(String message) { - LOG.debug(message); - } - - @Override - public String[] promptKeyboardInteractive(String destination, String name, - String instruction, String[] prompt, boolean[] echo) { - LOG.debug(instruction); - // Called for either SSH_MSG_USERAUTH_INFO_REQUEST or SSH_MSG_USERAUTH_PASSWD_CHANGEREQ - // The most secure choice (especially for the second case) is to return null - return null; - } - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProcessStrategyFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProcessStrategyFactory.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProcessStrategyFactory.java deleted file mode 100644 index 64445d6..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProcessStrategyFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/** - * 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.camel.component.jsch; - -import java.util.Map; - -import org.apache.camel.CamelContext; -import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy; -import org.apache.camel.component.file.GenericFileProcessStrategy; -import org.apache.camel.component.file.strategy.GenericFileNoOpProcessStrategy; - -public final class ScpProcessStrategyFactory { - - private ScpProcessStrategyFactory() { - } - - @SuppressWarnings("unchecked") - public static <LsEntry> GenericFileProcessStrategy<LsEntry> createGenericFileProcessStrategy(CamelContext context, Map<String, Object> params) { - - /* - // We assume a value is present only if its value not null for String and 'true' for boolean - Expression moveExpression = (Expression) params.get("move"); - Expression moveFailedExpression = (Expression) params.get("moveFailed"); - Expression preMoveExpression = (Expression) params.get("preMove"); - boolean isNoop = params.get("noop") != null; - boolean isDelete = params.get("delete") != null; - boolean isMove = moveExpression != null || preMoveExpression != null || moveFailedExpression != null; - */ - - // default strategy will do nothing - GenericFileNoOpProcessStrategy<LsEntry> strategy = new GenericFileNoOpProcessStrategy<LsEntry>(); - strategy.setExclusiveReadLockStrategy((GenericFileExclusiveReadLockStrategy<LsEntry>) getExclusiveReadLockStrategy(params)); - return strategy; - } - - @SuppressWarnings({"unchecked"}) - private static <LsEntry> GenericFileExclusiveReadLockStrategy<LsEntry> getExclusiveReadLockStrategy(Map<String, Object> params) { - GenericFileExclusiveReadLockStrategy<LsEntry> strategy = (GenericFileExclusiveReadLockStrategy<LsEntry>) params.get("exclusiveReadLockStrategy"); - if (strategy != null) { - return strategy; - } -/* - // no explicit strategy set then fallback to readLock option - String readLock = (String) params.get("readLock"); - if (ObjectHelper.isNotEmpty(readLock)) { - if ("none".equals(readLock) || "false".equals(readLock)) { - return null; - } else if ("rename".equals(readLock)) { - GenericFileRenameExclusiveReadLockStrategy<LsEntry> readLockStrategy = new GenericFileRenameExclusiveReadLockStrategy<LsEntry>(); - Long timeout = (Long) params.get("readLockTimeout"); - if (timeout != null) { - readLockStrategy.setTimeout(timeout); - } - return readLockStrategy; - } else if ("changed".equals(readLock)) { - GenericFileExclusiveReadLockStrategy readLockStrategy = new SftpChangedExclusiveReadLockStrategy(); - Long timeout = (Long) params.get("readLockTimeout"); - if (timeout != null) { - readLockStrategy.setTimeout(timeout); - } - Long checkInterval = (Long) params.get("readLockCheckInterval"); - if (checkInterval != null) { - readLockStrategy.setCheckInterval(checkInterval); - } - return readLockStrategy; - } - } -*/ - return null; - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProducer.java b/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProducer.java deleted file mode 100644 index c7c8924..0000000 --- a/components/camel-jsch/src/main/java/org/apache/camel/component/jsch/ScpProducer.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * 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.camel.component.jsch; - -import org.apache.camel.component.file.remote.RemoteFileOperations; -import org.apache.camel.component.file.remote.RemoteFileProducer; - -public class ScpProducer extends RemoteFileProducer<ScpFile> { - - protected ScpProducer(ScpEndpoint endpoint, RemoteFileOperations<ScpFile> operations) { - super(endpoint, operations); - } -} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpComponent.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpComponent.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpComponent.java new file mode 100644 index 0000000..ff3283b --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpComponent.java @@ -0,0 +1,78 @@ +/** + * 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.camel.component.scp; + +import java.net.URI; +import java.util.Map; + +import com.jcraft.jsch.JSch; +import org.apache.camel.CamelContext; +import org.apache.camel.component.file.GenericFileEndpoint; +import org.apache.camel.component.file.remote.RemoteFileComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Component providing secure messaging using JSch + */ +public class ScpComponent extends RemoteFileComponent<ScpFile> { + + private static final Logger LOG = LoggerFactory.getLogger(ScpComponent.class); + + static { + JSch.setConfig("StrictHostKeyChecking", "yes"); + JSch.setLogger(new com.jcraft.jsch.Logger() { + @Override + public boolean isEnabled(int level) { + return level == FATAL || level == ERROR ? LOG.isErrorEnabled() + : level == WARN ? LOG.isWarnEnabled() + : level == INFO ? LOG.isInfoEnabled() : LOG.isDebugEnabled(); + } + @Override + public void log(int level, String message) { + if (level == FATAL || level == ERROR) { + LOG.error("[JSCH] {}", message); + } else if (level == WARN) { + LOG.warn("[JSCH] {}", message); + } else if (level == INFO) { + LOG.info("[JSCH] {}", message); + } else { + LOG.debug("[JSCH] {}", message); + } + } + }); + } + + public ScpComponent() { + } + + public ScpComponent(CamelContext context) { + super(context); + } + + @Override + protected GenericFileEndpoint<ScpFile> buildFileEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception { + int query = uri.indexOf("?"); + return new ScpEndpoint(uri, this, new ScpConfiguration(new URI(query >= 0 ? uri.substring(0, query) : uri))); + } + + protected void afterPropertiesSet(GenericFileEndpoint<ScpFile> endpoint) throws Exception { + // noop + } + +} + http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpConfiguration.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpConfiguration.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpConfiguration.java new file mode 100644 index 0000000..11ba2ac --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpConfiguration.java @@ -0,0 +1,149 @@ +/** + * 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.camel.component.scp; + +import java.net.URI; + +import org.apache.camel.component.file.remote.RemoteFileConfiguration; +import org.apache.camel.spi.UriParam; +import org.apache.camel.spi.UriParams; + +/** + * SCP configuration + */ +@UriParams +public class ScpConfiguration extends RemoteFileConfiguration { + + public static final int DEFAULT_SFTP_PORT = 22; + public static final String DEFAULT_MOD = "664"; + @UriParam + private String knownHostsFile; + @UriParam + private String privateKeyFile; + @UriParam + private String privateKeyFilePassphrase; + @UriParam + private String strictHostKeyChecking; + @UriParam + private int serverAliveInterval; + @UriParam(defaultValue = "1") + private int serverAliveCountMax = 1; + @UriParam(defaultValue = DEFAULT_MOD) + private String chmod = DEFAULT_MOD; + // comma separated list of ciphers. + // null means default jsch list will be used + @UriParam + private String ciphers; + @UriParam + private int compression; + + public ScpConfiguration() { + setProtocol("scp"); + } + + public ScpConfiguration(URI uri) { + super(uri); + } + + @Override + protected void setDefaultPort() { + setPort(DEFAULT_SFTP_PORT); + } + + public String getKnownHostsFile() { + return knownHostsFile; + } + + public void setKnownHostsFile(String knownHostsFile) { + this.knownHostsFile = knownHostsFile; + } + + public String getPrivateKeyFile() { + return privateKeyFile; + } + + public void setPrivateKeyFile(String privateKeyFile) { + this.privateKeyFile = privateKeyFile; + } + + public String getPrivateKeyFilePassphrase() { + return privateKeyFilePassphrase; + } + + public void setPrivateKeyFilePassphrase(String privateKeyFilePassphrase) { + this.privateKeyFilePassphrase = privateKeyFilePassphrase; + } + + public String getStrictHostKeyChecking() { + return strictHostKeyChecking; + } + + public void setStrictHostKeyChecking(String strictHostKeyChecking) { + this.strictHostKeyChecking = strictHostKeyChecking; + } + + public void setServerAliveInterval(int serverAliveInterval) { + this.serverAliveInterval = serverAliveInterval; + } + + public int getServerAliveInterval() { + return serverAliveInterval; + } + + public void setServerAliveCountMax(int serverAliveCountMax) { + this.serverAliveCountMax = serverAliveCountMax; + } + + public int getServerAliveCountMax() { + return serverAliveCountMax; + } + + public void setChmod(String chmod) { + if (chmod.length() == 3) { + for (byte c : chmod.getBytes()) { + if (c < '0' || c > '7') { + chmod = DEFAULT_MOD; + break; + } + } + } else { + chmod = DEFAULT_MOD; + } + // May be interesting to log the fallback to DEFAULT_MOD for invalid configuration + this.chmod = chmod; + } + + public String getChmod() { + return chmod; + } + + public void setCiphers(String ciphers) { + this.ciphers = ciphers; + } + + public String getCiphers() { + return ciphers; + } + + public int getCompression() { + return compression; + } + + public void setCompression(int compression) { + this.compression = compression; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpEndpoint.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpEndpoint.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpEndpoint.java new file mode 100644 index 0000000..f4c74f6 --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpEndpoint.java @@ -0,0 +1,77 @@ +/** + * 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.camel.component.scp; + +import org.apache.camel.Expression; +import org.apache.camel.Processor; +import org.apache.camel.component.file.GenericFileProducer; +import org.apache.camel.component.file.remote.RemoteFileConsumer; +import org.apache.camel.component.file.remote.RemoteFileEndpoint; +import org.apache.camel.component.file.remote.RemoteFileOperations; +import org.apache.camel.spi.UriEndpoint; +import org.apache.camel.spi.UriParam; + +/** + * Secure Copy Endpoint + */ +@UriEndpoint(scheme = "scp", label = "file") +public class ScpEndpoint extends RemoteFileEndpoint<ScpFile> { + + @UriParam + private ScpConfiguration configuration; + + public ScpEndpoint() { + } + + public ScpEndpoint(String uri, ScpComponent component, ScpConfiguration configuration) { + super(uri, component, configuration); + this.configuration = configuration; + } + + @Override + public ScpConfiguration getConfiguration() { + return configuration; + } + + @Override + protected RemoteFileConsumer<ScpFile> buildConsumer(Processor processor) { + throw new UnsupportedOperationException("This component does not support consuming from this endpoint"); + } + + @Override + protected GenericFileProducer<ScpFile> buildProducer() { + return new ScpProducer(this, createRemoteFileOperations()); + } + + @Override + public RemoteFileOperations<ScpFile> createRemoteFileOperations() { + ScpOperations operations = new ScpOperations(); + operations.setEndpoint(this); + return operations; + } + + @Override + public String getScheme() { + return "scp"; + } + + @Override + public Expression getTempFileName() { + log.debug("Creation of temporary files not supported by the scp: protocol."); + return null; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpFile.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpFile.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpFile.java new file mode 100644 index 0000000..847f6fe --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpFile.java @@ -0,0 +1,71 @@ +/** + * 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.camel.component.scp; + + +/** + * SFTP remote file operations + */ +public class ScpFile { + private boolean directory; + private int attrs; + private int length; + private String name; + private String parent; + + public String header() { + StringBuilder buffer = new StringBuilder(); + buffer.append(directory ? "D" : "C"); + buffer.append(" "); + return buffer.toString(); + } + + public boolean isDirectory() { + return directory; + } + public void setDirectory(boolean directory) { + this.directory = directory; + } + public int getAttrs() { + return attrs; + } + public void setAttrs(int attrs) { + this.attrs = attrs; + } + public int getLength() { + return length; + } + public void setLength(int length) { + this.length = length; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpOperations.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpOperations.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpOperations.java new file mode 100644 index 0000000..ce69c39 --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpOperations.java @@ -0,0 +1,449 @@ +/** + * 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.camel.component.scp; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.List; + +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; +import com.jcraft.jsch.UIKeyboardInteractive; +import com.jcraft.jsch.UserInfo; + +import org.apache.camel.Exchange; +import org.apache.camel.InvalidPayloadException; +import org.apache.camel.component.file.GenericFileEndpoint; +import org.apache.camel.component.file.GenericFileOperationFailedException; +import org.apache.camel.component.file.remote.RemoteFileConfiguration; +import org.apache.camel.component.file.remote.RemoteFileOperations; +import org.apache.camel.util.IOHelper; +import org.apache.camel.util.ObjectHelper; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * SCP remote file operations + */ +public class ScpOperations implements RemoteFileOperations<ScpFile> { + private static final String DEFAULT_KNOWN_HOSTS = "META-INF/.ssh/known_hosts"; + private static final Logger LOG = LoggerFactory.getLogger(ScpOperations.class); + + private ScpEndpoint endpoint; + private Session session; + private ChannelExec channel; + + @Override + public void setEndpoint(GenericFileEndpoint<ScpFile> endpoint) { + this.endpoint = (ScpEndpoint)endpoint; + } + + @Override + public boolean deleteFile(String name) throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'delete' not supported by the scp: protocol"); + } + + @Override + public boolean existsFile(String name) throws GenericFileOperationFailedException { + // maybe... cannot determine using the scp: protocol + return false; + } + + @Override + public boolean renameFile(String from, String to) throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'rename' not supported by the scp: protocol"); + } + + @Override + public boolean buildDirectory(String directory, boolean absolute) throws GenericFileOperationFailedException { + // done by the server + return true; + } + + @Override + public boolean retrieveFile(String name, Exchange exchange) throws GenericFileOperationFailedException { + // TODO: implement + return false; + } + + @Override + public void releaseRetreivedFileResources(Exchange exchange) throws GenericFileOperationFailedException { + // No-op + } + + @Override + public boolean storeFile(String name, Exchange exchange) throws GenericFileOperationFailedException { + ObjectHelper.notNull(session, "session"); + ScpConfiguration cfg = endpoint.getConfiguration(); + + int timeout = cfg.getConnectTimeout(); + if (LOG.isTraceEnabled()) { + LOG.trace("Opening channel to {} with {} timeout...", cfg.remoteServerInformation(), + timeout > 0 ? (Integer.toString(timeout) + " ms") : "no"); + } + String file = getRemoteFile(name, cfg); + + + InputStream is = null; + if (exchange.getIn().getBody() == null) { + // Do an explicit test for a null body and decide what to do + if (endpoint.isAllowNullBody()) { + LOG.trace("Writing empty file."); + is = new ByteArrayInputStream(new byte[]{}); + } else { + throw new GenericFileOperationFailedException("Cannot write null body to file: " + name); + } + } + + try { + channel = (ChannelExec) session.openChannel("exec"); + channel.setCommand(getScpCommand(cfg, file)); + channel.connect(timeout); + LOG.trace("Channel connected to {}", cfg.remoteServerInformation()); + + try { + if (is == null) { + is = exchange.getIn().getMandatoryBody(InputStream.class); + } + write(channel, file, is, cfg); + } catch (InvalidPayloadException e) { + throw new GenericFileOperationFailedException("Cannot store file: " + name, e); + } catch (IOException e) { + throw new GenericFileOperationFailedException("Failed to write file " + file, e); + } finally { + // must close stream after usage + IOHelper.close(is); + } + } catch (JSchException e) { + throw new GenericFileOperationFailedException("Failed to write file " + file, e); + } finally { + if (channel != null) { + LOG.trace("Disconnecting 'exec' scp channel"); + channel.disconnect(); + channel = null; + LOG.trace("Channel disconnected from {}", cfg.remoteServerInformation()); + } + } + return true; + } + + @Override + public String getCurrentDirectory() throws GenericFileOperationFailedException { + return endpoint.getConfiguration().getDirectory(); + } + + @Override + public void changeCurrentDirectory(String path) throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'cd " + path + "' not supported by the scp: protocol"); + } + + @Override + public void changeToParentDirectory() throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'cd ..' not supported by the scp: protocol"); + } + + @Override + public List<ScpFile> listFiles() throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'ls' not supported by the scp: protocol"); + } + + @Override + public List<ScpFile> listFiles(String path) throws GenericFileOperationFailedException { + throw new GenericFileOperationFailedException("Operation 'ls " + path + "' not supported by the scp: protocol"); + } + + @Override + public boolean connect(RemoteFileConfiguration configuration) throws GenericFileOperationFailedException { + if (!isConnected()) { + session = createSession(configuration instanceof ScpConfiguration ? (ScpConfiguration)configuration : null); + // TODO: deal with reconnection attempts + if (!isConnected()) { + session = null; + throw new GenericFileOperationFailedException("Failed to connect to " + configuration.remoteServerInformation()); + } + } + return true; + } + + @Override + public boolean isConnected() throws GenericFileOperationFailedException { + return session != null && session.isConnected(); + } + + @Override + public void disconnect() throws GenericFileOperationFailedException { + if (isConnected()) { + session.disconnect(); + } + session = null; + } + + @Override + public boolean sendNoop() throws GenericFileOperationFailedException { + // not supported by scp: + return true; + } + + @Override + public boolean sendSiteCommand(String command) throws GenericFileOperationFailedException { + // TODO: not really used, maybe implement at a later time + return true; + } + + private Session createSession(ScpConfiguration config) { + ObjectHelper.notNull(config, "ScpConfiguration"); + try { + final JSch jsch = new JSch(); + // get from configuration + if (ObjectHelper.isNotEmpty(config.getCiphers())) { + LOG.debug("Using ciphers: {}", config.getCiphers()); + Hashtable<String, String> ciphers = new Hashtable<String, String>(); + ciphers.put("cipher.s2c", config.getCiphers()); + ciphers.put("cipher.c2s", config.getCiphers()); + JSch.setConfig(ciphers); + } + if (ObjectHelper.isNotEmpty(config.getPrivateKeyFile())) { + LOG.debug("Using private keyfile: {}", config.getPrivateKeyFile()); + String pkfp = config.getPrivateKeyFilePassphrase(); + jsch.addIdentity(config.getPrivateKeyFile(), ObjectHelper.isNotEmpty(pkfp) ? pkfp : null); + } + + String knownHostsFile = config.getKnownHostsFile(); + jsch.setKnownHosts(ObjectHelper.isEmpty(knownHostsFile) ? DEFAULT_KNOWN_HOSTS : knownHostsFile); + session = jsch.getSession(config.getUsername(), config.getHost(), config.getPort()); + session.setTimeout(config.getTimeout()); + session.setUserInfo(new SessionUserInfo(config)); + + if (ObjectHelper.isNotEmpty(config.getStrictHostKeyChecking())) { + LOG.debug("Using StrickHostKeyChecking: {}", config.getStrictHostKeyChecking()); + session.setConfig("StrictHostKeyChecking", config.getStrictHostKeyChecking()); + } + + int timeout = config.getConnectTimeout(); + LOG.debug("Connecting to {} with {} timeout...", config.remoteServerInformation(), + timeout > 0 ? (Integer.toString(timeout) + " ms") : "no"); + if (timeout > 0) { + session.connect(timeout); + } else { + session.connect(); + } + } catch (JSchException e) { + session = null; + LOG.warn("Could not create ssh session for " + config.remoteServerInformation(), e); + } + return session; + } + + private void write(ChannelExec c, String name, InputStream data, ScpConfiguration cfg) throws IOException { + OutputStream os = c.getOutputStream(); + InputStream is = c.getInputStream(); + + try { + writeFile(name, data, os, is, cfg); + } finally { + IOHelper.close(is, os); + } + } + + private void writeFile(String filename, InputStream data, OutputStream os, InputStream is, ScpConfiguration cfg) throws IOException { + final int lineFeed = '\n'; + String bytes; + int pos = filename.indexOf('/'); + if (pos >= 0) { + // write to child directory + String dir = filename.substring(0, pos); + bytes = "D0775 0 " + dir; + LOG.trace("[scp:sink] {}", bytes); + os.write(bytes.getBytes()); + os.write(lineFeed); + os.flush(); + readAck(is, false); + + writeFile(filename.substring(pos + 1), data, os, is, cfg); + + bytes = "E"; + LOG.trace("[scp:sink] {}", bytes); + os.write(bytes.getBytes()); + os.write(lineFeed); + os.flush(); + readAck(is, false); + } else { + int count = 0; + int read; + int size = endpoint.getBufferSize(); + byte[] reply = new byte[size]; + + // figure out the stream size as we need to pass it in the header + BufferedInputStream buffer = new BufferedInputStream(data, size); + try { + buffer.mark(Integer.MAX_VALUE); + while ((read = buffer.read(reply)) != -1) { + count += read; + } + + // send the header + bytes = "C0" + cfg.getChmod() + " " + count + " " + filename; + LOG.trace("[scp:sink] {}", bytes); + os.write(bytes.getBytes()); + os.write(lineFeed); + os.flush(); + readAck(is, false); + + // now send the stream + buffer.reset(); + while ((read = buffer.read(reply)) != -1) { + os.write(reply, 0, read); + } + writeAck(os); + readAck(is, false); + } finally { + IOHelper.close(buffer); + } + } + } + + private void writeAck(OutputStream os) throws IOException { + os.write(0); + os.flush(); + } + + private int readAck(InputStream is, boolean failOnEof) throws IOException { + String message; + int answer = is.read(); + switch (answer) { + case -1: + if (failOnEof) { + message = "[scp] Unexpected end of stream"; + throw new EOFException(message); + } + break; + case 1: + message = "[scp] WARN " + readLine(is); + LOG.warn(message); + break; + case 2: + message = "[scp] NACK " + readLine(is); + throw new IOException(message); + default: + // case 0: + break; + } + return answer; + } + + private String readLine(InputStream is) throws IOException { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + try { + int c; + do { + c = is.read(); + if (c == '\n') { + return bytes.toString(); + } + bytes.write(c); + } while (c != -1); + } finally { + IOHelper.close(bytes); + } + + String message = "[scp] Unexpected end of stream"; + throw new IOException(message); + } + + private static String getRemoteTarget(ScpConfiguration config) { + // use current dir (".") if target directory not specified in uri + return config.getDirectory().isEmpty() ? "." : config.getDirectory(); + } + + private static String getRemoteFile(String name, ScpConfiguration config) { + String dir = config.getDirectory(); + dir = dir.endsWith("/") ? dir : dir + "/"; + return name.startsWith(dir) ? name.substring(dir.length()) : name; + } + + private static boolean isRecursiveScp(String name) { + return name.indexOf('/') > 0; + } + + private static String getScpCommand(ScpConfiguration config, String name) { + StringBuilder cmd = new StringBuilder(); + cmd.append("scp "); + // TODO: need config for scp *-p* (preserves modification times, access times, and modes from the original file) + // String command="scp " + (ptimestamp ? "-p " : "") + "-t " + configuration.getDirectory(); + // TODO: refactor to use generic command + cmd.append(isRecursiveScp(name) ? "-r " : ""); + cmd.append("-t "); + cmd.append(getRemoteTarget(config)); + return cmd.toString(); + } + + protected static final class SessionUserInfo implements UserInfo, UIKeyboardInteractive { + private final ScpConfiguration config; + public SessionUserInfo(ScpConfiguration config) { + ObjectHelper.notNull(config, "config"); + this.config = config; + } + + @Override + public String getPassphrase() { + LOG.warn("Private Key authentication not supported"); + return null; + } + @Override + public String getPassword() { + LOG.debug("Providing password for ssh authentication of user '{}'", config.getUsername()); + return config.getPassword(); + } + @Override + public boolean promptPassword(String message) { + LOG.debug(message); + return true; + } + @Override + public boolean promptPassphrase(String message) { + LOG.debug(message); + return true; + } + @Override + public boolean promptYesNo(String message) { + LOG.debug(message); + return false; + } + @Override + public void showMessage(String message) { + LOG.debug(message); + } + + @Override + public String[] promptKeyboardInteractive(String destination, String name, + String instruction, String[] prompt, boolean[] echo) { + LOG.debug(instruction); + // Called for either SSH_MSG_USERAUTH_INFO_REQUEST or SSH_MSG_USERAUTH_PASSWD_CHANGEREQ + // The most secure choice (especially for the second case) is to return null + return null; + } + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProcessStrategyFactory.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProcessStrategyFactory.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProcessStrategyFactory.java new file mode 100644 index 0000000..a8d8ef1 --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProcessStrategyFactory.java @@ -0,0 +1,85 @@ +/** + * 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.camel.component.scp; + +import java.util.Map; + +import org.apache.camel.CamelContext; +import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy; +import org.apache.camel.component.file.GenericFileProcessStrategy; +import org.apache.camel.component.file.strategy.GenericFileNoOpProcessStrategy; + +public final class ScpProcessStrategyFactory { + + private ScpProcessStrategyFactory() { + } + + @SuppressWarnings("unchecked") + public static <LsEntry> GenericFileProcessStrategy<LsEntry> createGenericFileProcessStrategy(CamelContext context, Map<String, Object> params) { + + /* + // We assume a value is present only if its value not null for String and 'true' for boolean + Expression moveExpression = (Expression) params.get("move"); + Expression moveFailedExpression = (Expression) params.get("moveFailed"); + Expression preMoveExpression = (Expression) params.get("preMove"); + boolean isNoop = params.get("noop") != null; + boolean isDelete = params.get("delete") != null; + boolean isMove = moveExpression != null || preMoveExpression != null || moveFailedExpression != null; + */ + + // default strategy will do nothing + GenericFileNoOpProcessStrategy<LsEntry> strategy = new GenericFileNoOpProcessStrategy<LsEntry>(); + strategy.setExclusiveReadLockStrategy((GenericFileExclusiveReadLockStrategy<LsEntry>) getExclusiveReadLockStrategy(params)); + return strategy; + } + + @SuppressWarnings({"unchecked"}) + private static <LsEntry> GenericFileExclusiveReadLockStrategy<LsEntry> getExclusiveReadLockStrategy(Map<String, Object> params) { + GenericFileExclusiveReadLockStrategy<LsEntry> strategy = (GenericFileExclusiveReadLockStrategy<LsEntry>) params.get("exclusiveReadLockStrategy"); + if (strategy != null) { + return strategy; + } +/* + // no explicit strategy set then fallback to readLock option + String readLock = (String) params.get("readLock"); + if (ObjectHelper.isNotEmpty(readLock)) { + if ("none".equals(readLock) || "false".equals(readLock)) { + return null; + } else if ("rename".equals(readLock)) { + GenericFileRenameExclusiveReadLockStrategy<LsEntry> readLockStrategy = new GenericFileRenameExclusiveReadLockStrategy<LsEntry>(); + Long timeout = (Long) params.get("readLockTimeout"); + if (timeout != null) { + readLockStrategy.setTimeout(timeout); + } + return readLockStrategy; + } else if ("changed".equals(readLock)) { + GenericFileExclusiveReadLockStrategy readLockStrategy = new SftpChangedExclusiveReadLockStrategy(); + Long timeout = (Long) params.get("readLockTimeout"); + if (timeout != null) { + readLockStrategy.setTimeout(timeout); + } + Long checkInterval = (Long) params.get("readLockCheckInterval"); + if (checkInterval != null) { + readLockStrategy.setCheckInterval(checkInterval); + } + return readLockStrategy; + } + } +*/ + return null; + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProducer.java ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProducer.java b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProducer.java new file mode 100644 index 0000000..68f9173 --- /dev/null +++ b/components/camel-jsch/src/main/java/org/apache/camel/component/scp/ScpProducer.java @@ -0,0 +1,27 @@ +/** + * 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.camel.component.scp; + +import org.apache.camel.component.file.remote.RemoteFileOperations; +import org.apache.camel.component.file.remote.RemoteFileProducer; + +public class ScpProducer extends RemoteFileProducer<ScpFile> { + + protected ScpProducer(ScpEndpoint endpoint, RemoteFileOperations<ScpFile> operations) { + super(endpoint, operations); + } +} http://git-wip-us.apache.org/repos/asf/camel/blob/d7cb0178/components/camel-jsch/src/main/resources/META-INF/services/org/apache/camel/component/scp ---------------------------------------------------------------------- diff --git a/components/camel-jsch/src/main/resources/META-INF/services/org/apache/camel/component/scp b/components/camel-jsch/src/main/resources/META-INF/services/org/apache/camel/component/scp index 52e6f95..4053833 100644 --- a/components/camel-jsch/src/main/resources/META-INF/services/org/apache/camel/component/scp +++ b/components/camel-jsch/src/main/resources/META-INF/services/org/apache/camel/component/scp @@ -15,5 +15,5 @@ # limitations under the License. # -class=org.apache.camel.component.jsch.JschComponent -strategy.factory.class=org.apache.camel.component.jsch.ScpProcessStrategyFactory +class=org.apache.camel.component.scp.ScpComponent +strategy.factory.class=org.apache.camel.component.scp.ScpProcessStrategyFactory