2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Copyright (C) 2017 Amdocs
8 * =============================================================================
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
21 * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22 * ============LICENSE_END=========================================================
25 package org.onap.appc.ccadaptor;
27 import com.att.eelf.configuration.EELFLogger;
28 import com.att.eelf.configuration.EELFManager;
29 import com.jcraft.jsch.Channel;
30 import com.jcraft.jsch.ChannelSftp;
31 import com.jcraft.jsch.ChannelShell;
32 import com.jcraft.jsch.ChannelSubsystem;
33 import com.jcraft.jsch.JSch;
34 import com.jcraft.jsch.JSchException;
35 import com.jcraft.jsch.Session;
36 import com.jcraft.jsch.SftpException;
37 import com.jcraft.jsch.UIKeyboardInteractive;
38 import com.jcraft.jsch.UserInfo;
39 import java.io.BufferedInputStream;
40 import java.io.BufferedReader;
41 import java.io.BufferedWriter;
42 import java.io.ByteArrayInputStream;
43 import java.io.ByteArrayOutputStream;
44 import java.io.DataInputStream;
45 import java.io.DataOutputStream;
47 import java.io.FileWriter;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.io.InputStreamReader;
51 import java.io.OutputStream;
52 import java.io.RandomAccessFile;
53 import java.text.DateFormat;
54 import java.text.SimpleDateFormat;
55 import java.util.Calendar;
56 import java.util.Date;
57 import java.util.StringTokenizer;
58 import java.util.concurrent.TimeUnit;
59 import javax.annotation.Nonnull;
60 import org.apache.commons.lang.StringUtils;
61 import org.onap.appc.i18n.Msg;
63 public class SshJcraftWrapper {
65 private static final EELFLogger log = EELFManager.getInstance().getLogger(SshJcraftWrapper.class);
66 static final int DEFAULT_PORT = 22;
67 static final String EOL = "\n";
68 static final String CHANNEL_SHELL_TYPE = "shell";
69 static final String CHANNEL_SUBSYSTEM_TYPE = "subsystem";
70 static final String CHANNEL_SFTP_TYPE = "sftp";
71 private static final String TERMINAL_BASIC_MODE = "vt102";
72 static final String STRICT_HOST_CHECK_KEY = "StrictHostKeyChecking";
73 static final String STRICT_HOST_CHECK_VALUE = "no";
74 static final String DELIMITERS_SEPARATOR = "|";
76 private TelnetListener listener = null;
77 private String routerLogFileName = null;
78 private BufferedReader reader = null;
79 private BufferedWriter out = null;
80 private File tmpFile = null;
81 private JSch jsch = null;
82 private Session session = null;
83 private Channel channel = null;
84 private String aggregatedReceivedString = "";
85 private String routerCmdType = "XML";
86 private String routerFileName = null;
87 private File jcraftReadSwConfigFileFromDisk = new File("/tmp/jcraftReadSwConfigFileFromDisk");
88 private String equipNameCode = null;
89 private String routerName = null;
90 private String hostName = null;
91 private String userName = null;
92 private String passWord = null;
93 private int readIntervalMs = 500;
94 private int readBufferSizeBytes = 512_000;
95 private int charsChunkSize = 300_000;
96 private int sessionTimeoutMs = 9_000;
97 private char[] charBuffer;
98 private Runtime runtime = Runtime.getRuntime();
100 public SshJcraftWrapper() {
101 this.jsch = new JSch();
102 this.charBuffer = new char[readBufferSizeBytes];
105 SshJcraftWrapper(JSch jsch, int readIntervalMs, int readBufferSizeBytes) {
106 this.readIntervalMs = readIntervalMs;
108 this.readBufferSizeBytes = readBufferSizeBytes;
109 this.charBuffer = new char[readBufferSizeBytes];
112 public void connect(String hostname, String username, String password, String prompt, int timeOut)
114 log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3}",
115 hostname, username, prompt, timeOut);
116 routerName = hostname;
121 channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, timeOut);
122 ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
123 reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
124 readBufferSizeBytes);
126 log.info("Successfully connected. Flushing input buffer.");
128 receiveUntil(prompt, 3000, "No cmd was sent, just waiting");
129 } catch (IOException e) {
130 log.warn("Caught an Exception: Nothing to flush out.", e);
132 } catch (JSchException e) {
133 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(DEFAULT_PORT), username);
134 throw new IOException(e.toString());
138 // User specifies the port number.
139 public void connect(String hostname, String username, String password, String prompt, int timeOut, int portNum)
141 log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3} portNum={4}",
142 hostname, username, prompt, timeOut, portNum);
143 routerName = hostname;
148 channel = provideSessionChannel(CHANNEL_SHELL_TYPE, portNum, timeOut);
149 ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
150 reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
151 readBufferSizeBytes);
153 log.info("Successfully connected. Flushing input buffer.");
155 if ("]]>]]>".equals(prompt)) {
156 receiveUntil("]]>]]>", 10000, "No cmd was sent, just waiting");
158 receiveUntil(":~#", 5000, "No cmd was sent, just waiting");
160 } catch (IOException e) {
161 log.warn("Caught an Exception: Nothing to flush out.", e);
163 } catch (JSchException e) {
164 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(portNum), username);
165 throw new IOException(e.toString());
170 public String receiveUntil(String delimeters, int timeout, String cmdThatWasSent) throws IOException {
172 boolean match = false;
173 boolean cliPromptCmd = false;
174 StringBuilder sb = new StringBuilder();
175 StringBuilder sbReceive = new StringBuilder();
176 log.debug("delimeters='{0}' timeout={1} cmdThatWasSent='{2}'", delimeters, timeout, cmdThatWasSent);
178 aggregatedReceivedString = "";
179 FileWriter fileWriter = null;
181 long deadline = new Date().getTime() + timeout;
183 session.setTimeout(timeout); // This is the socket timeout value.
185 if (new Date().getTime() > deadline) {
186 String formattedCmd = removeWhiteSpaceAndNewLineCharactersAroundString(cmdThatWasSent);
187 log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, formattedCmd);
188 throw new TimedOutException("Routine has timed out");
190 sleep(readIntervalMs);
191 int len = reader.read(charBuffer, 0, readBufferSizeBytes);
192 log.trace("After reader. Read command len={0}", len);
194 log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, cmdThatWasSent);
195 throw new TimedOutException("Received a SocketTimeoutException router=" + routerName);
198 if (cmdThatWasSent.indexOf("IOS_XR_uploadedSwConfigCmd") != -1) {
200 // This is a IOS XR sw config file. We will write it to the disk.
201 timeout = timeout * 2;
202 deadline = new Date().getTime() + timeout;
203 log.debug("IOS XR upload for software config: timeout={0}", timeout);
204 StringTokenizer st = new StringTokenizer(cmdThatWasSent);
206 routerFileName = st.nextToken();
207 fileWriter = new FileWriter(routerFileName);
208 out = new BufferedWriter(fileWriter);
209 routerLogFileName = "/tmp/" + routerName;
210 tmpFile = new File(routerLogFileName);
211 log.debug("Prepared for writing swConfigFile to disk, routerFileName=" + routerFileName);
213 out.write(charBuffer, 0, len);
215 log.debug("{0} bytes has been written to the disk", len);
216 if (tmpFile.exists()) {
217 appendToRouterFile(routerLogFileName, len);
219 match = checkIfReceivedStringMatchesDelimeter(len, "\nXML>");
228 log.debug("Reader read {0} of data within {1} read iteration", len, readCounts);
231 for (int i = 0; i < len; i++) {
233 if ((c != 7) && (c != 13) && (c != 0) && (c != 27)) {
234 sbReceive.append(charBuffer[i]);
235 sb.append(charBuffer[i]);
238 appendToRouterFile("/tmp/" + routerName, len);
239 if (listener != null) {
240 listener.receivedString(sb.toString());
242 match = checkIfReceivedStringMatchesDelimeter(delimeters, sb.toString(), cmdThatWasSent);
244 log.trace("Match was true, breaking the loop.");
249 log.trace("cliPromptCmd");
251 for (int i = 0; i < len; i++) {
252 sbReceive.append(charBuffer[i]);
253 sb.append(charBuffer[i]);
255 appendToRouterFile("/tmp/" + routerName, sb);
256 if (listener != null) {
257 listener.receivedString(sb.toString());
259 log.debug("sb2={0} delimiters={1}", sb.toString(), delimeters);
260 if (sb.toString().contains("\nariPrompt>")) {
261 log.debug("Found ari prompt");
266 } catch (JSchException | IOException e) {
267 log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
268 throw new TimedOutException(e.getMessage());
271 if (fileWriter != null) {
274 } catch (IOException ex) {
275 log.warn("Failed to close fileWriter output stream", ex);
278 return stripOffCmdFromRouterResponse(sbReceive.toString());
281 private void sleep(long timeoutMs) {
283 TimeUnit.MILLISECONDS.sleep(timeoutMs);
284 } catch (java.lang.InterruptedException ee) {
285 Thread.currentThread().interrupt();
289 private void checkConnection() {
291 if (!isConnected() || !reader.ready()) {
292 throw new IllegalStateException("Connection not established. Cannot perform action.");
294 } catch (IOException e) {
295 throw new IllegalStateException("Reader stream is closed. Cannot perform action.", e);
299 public boolean checkIfReceivedStringMatchesDelimeter(String delimeters, String receivedString,
300 String cmdThatWasSent) {
301 // The delimeters are in a '|' seperated string. Return true on the first match.
302 log.debug("Entered checkIfReceivedStringMatchesDelimeter: delimeters={0} cmdThatWasSent={1} receivedString={2}",
303 delimeters, cmdThatWasSent, receivedString);
304 StringTokenizer st = new StringTokenizer(delimeters, DELIMITERS_SEPARATOR);
306 if ((delimeters.contains("#$")) || ("CLI".equals(routerCmdType))) // This would be an IOS XR, CLI command.
308 int x = receivedString.lastIndexOf('#');
309 int y = receivedString.length() - 1;
310 log.debug("IOS XR, CLI command");
311 if (log.isTraceEnabled()) {
312 log.trace("cmdThatWasSent={0}, lastIndexOf hash delimiter={1}, maxIndexNum={2}", cmdThatWasSent, x, y);
314 return (x != -1) && (y == x);
316 if (cmdThatWasSent.contains("show config")) {
317 log.trace("In the block for 'show config'");
318 while (st.hasMoreTokens()) {
319 String delimeter = st.nextToken();
320 // Make sure we don't get faked out by a response of " #".
322 // # signaling-local-address ipv6 FD00:F4D5:EA06:1::110:136:254
324 int x = receivedString.lastIndexOf(delimeter);
325 if ((receivedString.lastIndexOf(delimeter) != -1) && (receivedString.lastIndexOf(" #") != x - 1)) {
326 log.debug("receivedString={0}", receivedString);
327 log.trace("Found ending for 'show config' command, exiting.");
332 aggregatedReceivedString = aggregatedReceivedString + receivedString;
333 appendToFile("/tmp/aggregatedReceivedString.debug", aggregatedReceivedString);
335 log.debug("receivedString={0}", receivedString);
336 while (st.hasMoreTokens()) {
337 String delimeter = st.nextToken();
338 log.debug("Looking for an delimiter of:{0}", delimeter);
339 if (aggregatedReceivedString.indexOf(delimeter) != -1) {
340 log.debug("Found delimiter={0}, exiting", delimeter);
341 aggregatedReceivedString = "";
349 public boolean checkIfReceivedStringMatchesDelimeter(int len, String delimeter) {
352 String str = StringUtils.EMPTY;
354 if (jcraftReadSwConfigFileFromDisk()) {
355 log.trace("jcraftReadSwConfigFileFromDisk block");
356 File fileName = new File(routerFileName);
357 log.debug("jcraftReadSwConfigFileFromDisk::: Will read the tail end of the file from the disk");
359 str = getLastFewLinesOfFile(fileName, 3);
360 } catch (IOException e) {
361 log.warn("IOException occurred, while reading file=" + fileName, e);
364 // When looking at the end of the charBuffer, don't include any linefeeds or spaces. We only want to make the smallest string possible.
365 for (x = len - 1; x >= 0; x--) {
367 if ((c != 10) && (c != 32)) // Not a line feed nor a space.
372 if ((x + 1 - 13) >= 0) {
373 str = new String(charBuffer, x + 1 - 13, 13);
374 log.debug("str:{0}", str);
376 File fileName = new File(routerFileName);
377 log.debug("Will read the tail end of the file from the disk, x={0} len={1} str={2} routerFileName={3}",
378 x, len, str, routerFileName);
380 str = getLastFewLinesOfFile(fileName, 3);
381 } catch (IOException e) {
382 log.warn("IOException occurred, while reading file=" + fileName, e);
387 log.debug("Parsed string was str='{0}', searched delimiter was {1}");
388 return str.contains(delimeter);
391 public void closeConnection() {
392 log.info("Closing connection");
394 if (reader != null) {
397 } catch (IOException ex) {
398 log.warn("Could not close reader instance", ex);
401 channel.disconnect();
402 session.disconnect();
410 boolean isConnected() {
411 return channel != null && session != null;
414 public void send(String cmd) throws IOException {
415 try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
416 String command = enhanceCommandWithEOL(cmd);
417 int length = command.length();
418 log.debug("Sending ssh command: length={0}, payload: {1}", command.length(), command);
419 if(isCmdLengthEnoughToSendInChunks(length, charsChunkSize)) {
420 sendSshCommandInChunks(command, dos);
422 sendSshCommand(command, dos);
424 } catch (IOException e) {
425 log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
430 public void sendChar(int v) throws IOException {
431 try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
432 if (log.isTraceEnabled()) {
433 log.trace("Sending charCode: {0}", v);
437 } catch (IOException e) {
438 log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
443 public void send(byte[] b, int off, int len) throws IOException {
444 try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
445 dos.write(b, off, len);
447 } catch (IOException e) {
448 log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
453 public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
456 public String getPassword() {
461 public boolean promptYesNo(String str) {
466 public String getPassphrase() {
471 public boolean promptPassphrase(String message) {
476 public boolean promptPassword(String message) {
481 public void showMessage(String message) {
486 public String[] promptKeyboardInteractive(String destination,
491 return new String[0];
495 public void addListener(TelnetListener listener) {
496 this.listener = listener;
499 private void appendToFile(String fileName, String dataToWrite) {
500 File outputFile = new File(fileName);
501 if (outputFile.exists()) {
502 try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
503 ow.write(dataToWrite);
505 } catch (IOException e) {
506 log.warn("IOException occurred while writing to file=" + fileName, e);
511 public String getTheDate() {
512 DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy H:mm:ss ");
513 return dateFormat.format(Calendar.getInstance().getTime());
517 public void appendToRouterFile(String fileName, StringBuilder dataToWrite) {
518 appendToFile(fileName, dataToWrite.toString());
521 public void appendToRouterFile(String fileName, int len) {
522 File outputFile = new File(fileName);
523 if (outputFile.exists()) {
524 try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
525 ow.write(charBuffer, 0, len);
527 } catch (IOException e) {
528 log.warn("Could not write data to router file:" + fileName, e);
533 public String removeWhiteSpaceAndNewLineCharactersAroundString(String str) {
534 if (str != null && !StringUtils.EMPTY.equals(str)) {
535 StringTokenizer strTok = new StringTokenizer(str, EOL);
536 StringBuilder sb = new StringBuilder();
538 while (strTok.hasMoreTokens()) {
539 String line = strTok.nextToken();
542 return sb.toString().trim();
544 return StringUtils.EMPTY;
547 public String stripOffCmdFromRouterResponse(String routerResponse) {
548 // The session of SSH will echo the command sent to the router, in the router's response.
549 // Since all our commands are terminated by a '\n', strip off the first line
550 // of the response from the router. This first line contains the orginal command.
552 String[] responseTokens = routerResponse.split(EOL, 2);
553 return responseTokens[responseTokens.length - 1];
556 public void setRouterCommandType(String type) {
557 this.routerCmdType = type;
558 log.debug("Router command type is set to: {0}", type);
561 public String getLastFewLinesOfFile(File file, int linesToRead) throws IOException {
563 try(RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
565 StringBuilder builder = new StringBuilder();
566 long length = file.length();
568 randomAccessFile.seek(length);
569 for (long seek = length; seek >= 0; --seek) {
570 randomAccessFile.seek(seek);
571 char c = (char) randomAccessFile.read();
574 builder = builder.reverse();
575 tail = builder.append(tail).toString();
577 builder.setLength(0);
578 if (lines == linesToRead) {
584 if (log.isDebugEnabled()) {
585 log.debug("Content read from file={0} was tail={1}", file.getName(), tail);
590 public boolean jcraftReadSwConfigFileFromDisk() {
591 return jcraftReadSwConfigFileFromDisk.exists();
594 public String getEquipNameCode() {
595 return equipNameCode;
598 public void setEquipNameCode(String equipNameCode) {
599 this.equipNameCode = equipNameCode;
602 public String getRouterName() {
606 // Routine does reads until it has read 'nchars' or times out.
607 public String receiveUntilBufferFlush(int ncharsSent, int timeout, String command) throws IOException {
608 log.debug("ncharsSent={0}, timeout={1}, message={2}", ncharsSent, timeout, command);
609 int ncharsTotalReceived = 0;
611 StringBuilder received = new StringBuilder();
613 long deadline = new Date().getTime() + timeout;
616 session.setTimeout(timeout); // This is the socket timeout value.
618 if (new Date().getTime() > deadline) {
619 log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, command);
620 throw new TimedOutException("Routine has timed out");
622 ncharsRead = reader.read(charBuffer, 0, readBufferSizeBytes);
624 received.append(charBuffer, 0, ncharsRead);
626 if (listener != null) {
627 listener.receivedString(String.copyValueOf(charBuffer, 0, ncharsRead));
629 appendToRouterFile("/tmp/" + routerName, ncharsRead);
630 ncharsTotalReceived = ncharsTotalReceived + ncharsRead;
631 if (ncharsTotalReceived >= ncharsSent) {
632 log.debug("Received the correct number of characters, ncharsSent={0}, ncharsTotalReceived={1}",
633 ncharsSent, ncharsTotalReceived);
635 return received.toString();
638 } catch (JSchException e) {
639 log.error(Msg.SSH_SESSION_CONFIG_ERROR, e.getMessage());
640 log.debug("ncharsSent={0}, ncharsTotalReceived={1}, ncharsRead={2} until error occurred",
641 ncharsSent, ncharsTotalReceived, ncharsRead);
642 throw new TimedOutException(e.getMessage());
646 public String getHostName() {
650 public String getUserName() {
654 public String getPassWord() {
658 public void sftpPutStringData(String stringOfData, String fullPathDest) throws IOException {
659 ChannelSftp sftp = null;
661 InputStream is = new ByteArrayInputStream(stringOfData.getBytes());
662 sftp = getSftpConnection(hostName, userName, passWord);
663 log.debug("Sending via sftp stringOfData to destination: {0}", fullPathDest);
664 sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
665 } catch (JSchException ex) {
666 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
667 throw new IOException(ex.getMessage());
668 } catch (SftpException ex) {
669 log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
670 throw new IOException(ex.getMessage());
672 disconnectSftp(sftp);
676 public String sftpGet(String fullFilePathName) throws IOException {
677 return get(fullFilePathName, hostName, userName, passWord);
680 public static String readInputStreamAsString(InputStream in) throws IOException {
681 BufferedInputStream bis = new BufferedInputStream(in);
682 ByteArrayOutputStream buf = new ByteArrayOutputStream();
683 int result = bis.read();
684 while (result != -1) {
685 byte b = (byte) result;
689 return buf.toString();
692 public void logMemoryUsage() {
693 int mb = 1024 * 1024;
695 long maxMemoryAvailable;
696 long memoryLeftOnHeap;
697 maxMemoryAvailable = runtime.maxMemory() / mb;
698 usedMemory = (runtime.totalMemory() / mb) - (runtime.freeMemory() / mb);
699 memoryLeftOnHeap = maxMemoryAvailable - usedMemory;
700 log.info("Memory usage: maxMemoryAvailable={0}, usedMemory={1}, memoryLeftOnHeap={2}",
701 maxMemoryAvailable, usedMemory, memoryLeftOnHeap);
704 public void connect(String hostname, String username, String password, int timeOut, int portNum,
705 String subsystem) throws IOException {
707 if (log.isDebugEnabled()) {
709 "Attempting to connect to {0} username={1} timeOut={2} portNum={3} subsystem={4}",
710 hostname, username, timeOut, portNum, subsystem);
712 this.routerName = hostname;
713 this.hostName = hostname;
714 this.userName = username;
715 this.passWord = password;
717 channel = provideSessionChannel(CHANNEL_SUBSYSTEM_TYPE, portNum, timeOut);
718 ((ChannelSubsystem) channel).setSubsystem(subsystem);
719 ((ChannelSubsystem) channel).setPty(true); //expected ptyType vt102
720 reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
721 readBufferSizeBytes);
722 channel.connect(5000);
723 } catch (JSchException e) {
724 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(portNum), username);
725 throw new IOException(e.getMessage());
729 public void connect(String hostName, String username, String password) throws IOException {
730 log.debug("Attempting to connect to {0} username={1} portNumber={2}", hostName, username, DEFAULT_PORT);
731 this.routerName = hostName;
732 this.hostName = hostName;
733 this.userName = username;
734 this.passWord = password;
736 channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, 30000);
737 ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
738 reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
739 readBufferSizeBytes);
742 receiveUntil(":~#", 9000, "No cmd was sent, just waiting, but we can stop on a '~#'");
743 } catch (Exception e) {
744 log.warn("Caught an Exception: Nothing to flush out.", e);
747 } catch (JSchException e) {
748 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), username);
749 throw new IOException(e.getMessage());
754 public void put(String sourcePath, String destDirectory) throws IOException {
755 ChannelSftp sftp = null;
757 sftp = getSftpConnection(hostName, userName, passWord);
758 log.debug("Sending via sftp from source: {0} to destination: {1}", sourcePath, destDirectory);
759 sftp.put(sourcePath, destDirectory, ChannelSftp.OVERWRITE);
760 } catch (JSchException ex) {
761 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
762 throw new IOException(ex.getMessage());
763 } catch (SftpException ex) {
764 log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
765 throw new IOException(ex.getMessage());
767 disconnectSftp(sftp);
771 public void put(InputStream is, String fullPathDest, String hostName, String userName, String passWord)
773 ChannelSftp sftp = null;
775 log.debug("Sftp put invoked, connection details: username={1} hostname={2}", userName, hostName);
776 sftp = getSftpConnection(hostName, userName, passWord);
777 String oldFiles = fullPathDest + "*";
778 log.debug("Deleting old files: {0}", oldFiles);
779 removeOldFiles(sftp, oldFiles, hostName, userName);
780 log.debug("Sending stringOfData to destination {0}", fullPathDest);
781 sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
782 } catch (JSchException ex) {
783 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
784 throw new IOException(ex.getMessage());
785 } catch (SftpException ex) {
786 log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
787 throw new IOException(ex.getMessage());
789 disconnectSftp(sftp);
793 private void removeOldFiles(ChannelSftp sftp, String oldFiles, String hostname, String username) throws SftpException {
796 } catch (SftpException ex) {
797 if (ChannelSftp.SSH_FX_NO_SUCH_FILE == ex.id) {
798 log.warn("No files found, continue");
800 log.error(Msg.SFTP_TRANSFER_FAILED, hostname, username, "RM", ex.getMessage());
806 public String get(String fullFilePathName, String hostName, String userName, String passWord) throws IOException {
807 ChannelSftp sftp = null;
809 log.debug("Sftp get invoked, connection details: username={1} hostname={2}",
811 sftp = getSftpConnection(hostName, userName, passWord);
812 InputStream in = sftp.get(fullFilePathName);
813 return readInputStreamAsString(in);
814 } catch (JSchException ex) {
815 log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
816 throw new IOException(ex.getMessage());
817 } catch (SftpException ex) {
818 log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "GET", ex.getMessage());
819 throw new IOException(ex.getMessage());
821 disconnectSftp(sftp);
825 private void disconnectSftp (ChannelSftp sftp) {
829 if (session != null) {
830 session.disconnect();
835 ChannelSftp getSftpConnection(String hostname, String username, String password) throws JSchException {
836 connectSession(hostname, username, password, DEFAULT_PORT, 30_000);
837 ChannelSftp sftp = openSftpChannel(session);
842 ChannelSftp openSftpChannel(Session sftpSession) throws JSchException {
843 return (ChannelSftp) sftpSession.openChannel(CHANNEL_SFTP_TYPE);
846 public String send(String cmd, String delimiter) throws IOException {
847 try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
848 String command = enhanceCommandWithEOL(cmd);
849 int length = command.length();
850 log.debug("Sending ssh command: length={0}, payload: {1}", command.length(), command);
851 if(isCmdLengthEnoughToSendInChunks(length, charsChunkSize)) {
852 return sendSshCommandInChunks(command, dos);
854 sendSshCommand(command, dos);
855 return receiveUntil(delimiter, 300000, cmd);
860 private void sendSshCommand(@Nonnull String command, @Nonnull DataOutputStream channelOutputStream)
862 channelOutputStream.writeBytes(command);
863 channelOutputStream.flush();
866 private String sendSshCommandInChunks(@Nonnull String command, @Nonnull DataOutputStream channelOutputStream) throws IOException {
867 StringBuilder received = new StringBuilder();
868 int charsTotalSent = 0;
869 int length = command.length();
870 for (int i = 0; i < length; i += charsChunkSize) {
871 String commandChunk = command.substring(i, Math.min(length, i + charsChunkSize));
872 int numCharsSentInChunk = commandChunk.length();
873 charsTotalSent = charsTotalSent + commandChunk.length();
874 log.debug("Iteration nr:{0}, sending command chunk: {1}", i, numCharsSentInChunk);
875 channelOutputStream.writeBytes(commandChunk);
876 channelOutputStream.flush();
878 if (numCharsSentInChunk < length) {
879 received.append(receiveUntilBufferFlush(numCharsSentInChunk, sessionTimeoutMs, command));
881 log.trace("i={0}, flush immediately", i);
882 channelOutputStream.flush();
884 } catch (IOException ex) {
885 log.warn("IOException occurred: nothing to flush out", ex);
888 return received.toString();
891 public void setSessionTimeoutMs(int sessionTimeoutMs) {
892 this.sessionTimeoutMs = sessionTimeoutMs;
895 void setCharsChunkSize(int charsChunkSize) {
896 this.charsChunkSize = charsChunkSize;
899 private boolean isCmdLengthEnoughToSendInChunks(int length, int chunkSize) {
900 return length > 2 * chunkSize;
903 private String enhanceCommandWithEOL(@Nonnull String originalCommand) {
904 char commandEnding = originalCommand.charAt(originalCommand.length() - 1);
905 if (commandEnding != '\n' && commandEnding != '\r') {
906 return originalCommand + EOL;
908 return originalCommand;
911 private Channel provideSessionChannel(String channelType, int port, int timeout) throws JSchException {
912 connectSession(this.hostName, this.userName, this.passWord, port, timeout);
913 session.setServerAliveCountMax(
914 0); // If this is not set to '0', then socket timeout on all reads will not work!!!!
915 return session.openChannel(channelType);
918 private void connectSession(String hostname, String username, String password, int port, int timeout) throws JSchException {
919 session = jsch.getSession(username, hostname, port);
920 session.setPassword(password);
921 session.setUserInfo(new MyUserInfo()); //needed?
922 session.setConfig(STRICT_HOST_CHECK_KEY, STRICT_HOST_CHECK_VALUE);
923 session.connect(timeout);