081f0814f017e09646e90699d9b98d1c7983c63b
[appc.git] / appc-config / appc-config-adaptor / provider / src / main / java / org / onap / appc / ccadaptor / SshJcraftWrapper.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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.
20  *
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.ccadaptor;
26
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;
46 import java.io.File;
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;
62
63 public class SshJcraftWrapper {
64
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 = "|";
75
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();
99
100     public SshJcraftWrapper() {
101         this.jsch = new JSch();
102         this.charBuffer = new char[readBufferSizeBytes];
103     }
104
105     SshJcraftWrapper(JSch jsch, int readIntervalMs, int readBufferSizeBytes) {
106         this.readIntervalMs = readIntervalMs;
107         this.jsch = jsch;
108         this.readBufferSizeBytes = readBufferSizeBytes;
109         this.charBuffer = new char[readBufferSizeBytes];
110     }
111
112     public void connect(String hostname, String username, String password, String prompt, int timeOut)
113         throws IOException {
114         log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3}",
115             hostname, username, prompt, timeOut);
116         routerName = hostname;
117         hostName = hostname;
118         userName = username;
119         passWord = password;
120         try {
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);
125             channel.connect();
126             log.info("Successfully connected. Flushing input buffer.");
127             try {
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);
131             }
132         } catch (JSchException e) {
133             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(DEFAULT_PORT), username);
134             throw new IOException(e.toString());
135         }
136     }
137
138     // User specifies the port number.
139     public void connect(String hostname, String username, String password, String prompt, int timeOut, int portNum)
140         throws IOException {
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;
144         hostName = hostname;
145         userName = username;
146         passWord = password;
147         try {
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);
152             channel.connect();
153             log.info("Successfully connected. Flushing input buffer.");
154             try {
155                 if ("]]>]]>".equals(prompt)) {
156                     receiveUntil("]]>]]>", 10000, "No cmd was sent, just waiting");
157                 } else {
158                     receiveUntil(":~#", 5000, "No cmd was sent, just waiting");
159                 }
160             } catch (IOException e) {
161                 log.warn("Caught an Exception: Nothing to flush out.", e);
162             }
163         } catch (JSchException e) {
164             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(portNum), username);
165             throw new IOException(e.toString());
166         }
167     }
168
169
170     public String receiveUntil(String delimeters, int timeout, String cmdThatWasSent) throws IOException {
171         checkConnection();
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);
177         int readCounts = 0;
178         aggregatedReceivedString = "";
179         FileWriter fileWriter = null;
180
181         long deadline = new Date().getTime() + timeout;
182         try {
183             session.setTimeout(timeout);  // This is the socket timeout value.
184             while (!match) {
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");
189                 }
190                 sleep(readIntervalMs);
191                 int len = reader.read(charBuffer, 0, readBufferSizeBytes);
192                 log.trace("After reader. Read command len={0}", len);
193                 if (len <= 0) {
194                     log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, cmdThatWasSent);
195                     throw new TimedOutException("Received a SocketTimeoutException router=" + routerName);
196                 }
197                 if (!cliPromptCmd) {
198                     if (cmdThatWasSent.indexOf("IOS_XR_uploadedSwConfigCmd") != -1) {
199                         if (out == null) {
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);
205                             st.nextToken();
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);
212                         }
213                         out.write(charBuffer, 0, len);
214                         out.flush();
215                         log.debug("{0} bytes has been written to the disk", len);
216                         if (tmpFile.exists()) {
217                             appendToRouterFile(routerLogFileName, len);
218                         }
219                         match = checkIfReceivedStringMatchesDelimeter(len, "\nXML>");
220                         if (match) {
221                             out.flush();
222                             out.close();
223                             out = null;
224                             return null;
225                         }
226                     } else {
227                         readCounts++;
228                         log.debug("Reader read {0} of data within {1} read iteration", len, readCounts);
229                         int c;
230                         sb.setLength(0);
231                         for (int i = 0; i < len; i++) {
232                             c = charBuffer[i];
233                             if ((c != 7) && (c != 13) && (c != 0) && (c != 27)) {
234                                 sbReceive.append(charBuffer[i]);
235                                 sb.append(charBuffer[i]);
236                             }
237                         }
238                         appendToRouterFile("/tmp/" + routerName, len);
239                         if (listener != null) {
240                             listener.receivedString(sb.toString());
241                         }
242                         match = checkIfReceivedStringMatchesDelimeter(delimeters, sb.toString(), cmdThatWasSent);
243                         if (match) {
244                             log.trace("Match was true, breaking the loop.");
245                             break;
246                         }
247                     }
248                 } else {
249                     log.trace("cliPromptCmd");
250                     sb.setLength(0);
251                     for (int i = 0; i < len; i++) {
252                         sbReceive.append(charBuffer[i]);
253                         sb.append(charBuffer[i]);
254                     }
255                     appendToRouterFile("/tmp/" + routerName, sb);
256                     if (listener != null) {
257                         listener.receivedString(sb.toString());
258                     }
259                     log.debug("sb2={0}  delimiters={1}", sb.toString(), delimeters);
260                     if (sb.toString().contains("\nariPrompt>")) {
261                         log.debug("Found ari prompt");
262                         break;
263                     }
264                 }
265             }
266         } catch (JSchException | IOException e) {
267             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
268             throw new TimedOutException(e.getMessage());
269         } finally {
270             try {
271                 if (fileWriter != null) {
272                     fileWriter.close();
273                 }
274             } catch (IOException ex) {
275                 log.warn("Failed to close fileWriter output stream", ex);
276             }
277         }
278         return stripOffCmdFromRouterResponse(sbReceive.toString());
279     }
280
281     private void sleep(long timeoutMs) {
282         try {
283             TimeUnit.MILLISECONDS.sleep(timeoutMs);
284         } catch (java.lang.InterruptedException ee) {
285             Thread.currentThread().interrupt();
286         }
287     }
288
289     private void checkConnection() {
290         try {
291             if (!isConnected() || !reader.ready()) {
292                 throw new IllegalStateException("Connection not established. Cannot perform action.");
293             }
294         } catch (IOException e) {
295             throw new IllegalStateException("Reader stream is closed. Cannot perform action.", e);
296         }
297     }
298
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);
305
306         if ((delimeters.contains("#$")) || ("CLI".equals(routerCmdType)))  // This would be an IOS XR, CLI command.
307         {
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);
313             }
314             return (x != -1) && (y == x);
315         }
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 " #".
321                 // Proc #0
322                 //   # signaling-local-address ipv6 FD00:F4D5:EA06:1::110:136:254
323                 // LAAR2#
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.");
328                     return true;
329                 }
330             }
331         } else {
332             aggregatedReceivedString = aggregatedReceivedString + receivedString;
333             appendToFile("/tmp/aggregatedReceivedString.debug", aggregatedReceivedString);
334
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 = "";
342                     return true;
343                 }
344             }
345         }
346         return false;
347     }
348
349     public boolean checkIfReceivedStringMatchesDelimeter(int len, String delimeter) {
350         int x;
351         int c;
352         String str = StringUtils.EMPTY;
353
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");
358             try {
359                 str = getLastFewLinesOfFile(fileName, 3);
360             } catch (IOException e) {
361                 log.warn("IOException occurred, while reading file=" + fileName, e);
362             }
363         } else {
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--) {
366                 c = charBuffer[x];
367                 if ((c != 10) && (c != 32)) // Not a line feed nor a space.
368                 {
369                     break;
370                 }
371             }
372             if ((x + 1 - 13) >= 0) {
373                 str = new String(charBuffer, x + 1 - 13, 13);
374                 log.debug("str:{0}", str);
375             } else {
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);
379                 try {
380                     str = getLastFewLinesOfFile(fileName, 3);
381                 } catch (IOException e) {
382                     log.warn("IOException occurred, while reading file=" + fileName, e);
383                 }
384             }
385         }
386
387         log.debug("Parsed string was str='{0}', searched delimiter was {1}");
388         return str.contains(delimeter);
389     }
390
391     public void closeConnection() {
392         log.info("Closing connection");
393         try {
394             if (reader != null) {
395                 reader.close();
396             }
397         } catch (IOException ex) {
398             log.warn("Could not close reader instance", ex);
399         } finally {
400             if (isConnected()) {
401                 channel.disconnect();
402                 session.disconnect();
403                 channel = null;
404                 session = null;
405             }
406             reader = null;
407         }
408     }
409
410     boolean isConnected() {
411         return channel != null && session != null;
412     }
413
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);
421             } else {
422                 sendSshCommand(command, dos);
423             }
424         } catch (IOException e) {
425             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
426             throw e;
427         }
428     }
429
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);
434             }
435             dos.writeChar(v);
436             dos.flush();
437         } catch (IOException e) {
438             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
439             throw e;
440         }
441     }
442
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);
446             dos.flush();
447         } catch (IOException e) {
448             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
449             throw e;
450         }
451     }
452
453     public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
454
455         @Override
456         public String getPassword() {
457             return null;
458         }
459
460         @Override
461         public boolean promptYesNo(String str) {
462             return false;
463         }
464
465         @Override
466         public String getPassphrase() {
467             return null;
468         }
469
470         @Override
471         public boolean promptPassphrase(String message) {
472             return false;
473         }
474
475         @Override
476         public boolean promptPassword(String message) {
477             return false;
478         }
479
480         @Override
481         public void showMessage(String message) {
482             //stub
483         }
484
485         @Override
486         public String[] promptKeyboardInteractive(String destination,
487             String name,
488             String instruction,
489             String[] prompt,
490             boolean[] echo) {
491             return new String[0];
492         }
493     }
494
495     public void addListener(TelnetListener listener) {
496         this.listener = listener;
497     }
498
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);
504                 ow.close();
505             } catch (IOException e) {
506                 log.warn("IOException occurred while writing to file=" + fileName, e);
507             }
508         }
509     }
510
511     public String getTheDate() {
512         DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy H:mm:ss  ");
513         return dateFormat.format(Calendar.getInstance().getTime());
514     }
515
516
517     public void appendToRouterFile(String fileName, StringBuilder dataToWrite) {
518         appendToFile(fileName, dataToWrite.toString());
519     }
520
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);
526                 ow.close();
527             } catch (IOException e) {
528                 log.warn("Could not write data to router file:" + fileName, e);
529             }
530         }
531     }
532
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();
537
538             while (strTok.hasMoreTokens()) {
539                 String line = strTok.nextToken();
540                 sb.append(line);
541             }
542             return sb.toString().trim();
543         }
544         return StringUtils.EMPTY;
545     }
546
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.
551
552         String[] responseTokens = routerResponse.split(EOL, 2);
553         return responseTokens[responseTokens.length - 1];
554     }
555
556     public void setRouterCommandType(String type) {
557         this.routerCmdType = type;
558         log.debug("Router command type is set to: {0}", type);
559     }
560
561     public String getLastFewLinesOfFile(File file, int linesToRead) throws IOException {
562         String tail = "";
563         try(RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
564             int lines = 0;
565             StringBuilder builder = new StringBuilder();
566             long length = file.length();
567             length--;
568             randomAccessFile.seek(length);
569             for (long seek = length; seek >= 0; --seek) {
570                 randomAccessFile.seek(seek);
571                 char c = (char) randomAccessFile.read();
572                 builder.append(c);
573                 if (c == '\n') {
574                     builder = builder.reverse();
575                     tail = builder.append(tail).toString();
576                     lines++;
577                     builder.setLength(0);
578                     if (lines == linesToRead) {
579                         break;
580                     }
581                 }
582             }
583         }
584         if (log.isDebugEnabled()) {
585             log.debug("Content read from file={0} was tail={1}", file.getName(), tail);
586         }
587         return tail;
588     }
589
590     public boolean jcraftReadSwConfigFileFromDisk() {
591         return jcraftReadSwConfigFileFromDisk.exists();
592     }
593
594     public String getEquipNameCode() {
595         return equipNameCode;
596     }
597
598     public void setEquipNameCode(String equipNameCode) {
599         this.equipNameCode = equipNameCode;
600     }
601
602     public String getRouterName() {
603         return routerName;
604     }
605
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;
610         int ncharsRead = 0;
611         StringBuilder received = new StringBuilder();
612
613         long deadline = new Date().getTime() + timeout;
614         logMemoryUsage();
615         try {
616             session.setTimeout(timeout);  // This is the socket timeout value.
617             while (true) {
618                 if (new Date().getTime() > deadline) {
619                     log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, command);
620                     throw new TimedOutException("Routine has timed out");
621                 }
622                 ncharsRead = reader.read(charBuffer, 0, readBufferSizeBytes);
623                 if(ncharsRead >=0) {
624                     received.append(charBuffer, 0, ncharsRead);
625                 }
626                 if (listener != null) {
627                     listener.receivedString(String.copyValueOf(charBuffer, 0, ncharsRead));
628                 }
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);
634                     logMemoryUsage();
635                     return received.toString();
636                 }
637             }
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());
643         }
644     }
645
646     public String getHostName() {
647         return hostName;
648     }
649
650     public String getUserName() {
651         return userName;
652     }
653
654     public String getPassWord() {
655         return passWord;
656     }
657
658     public void sftpPutStringData(String stringOfData, String fullPathDest) throws IOException {
659         ChannelSftp sftp = null;
660         try {
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());
671         } finally {
672             disconnectSftp(sftp);
673         }
674     }
675
676     public String sftpGet(String fullFilePathName) throws IOException {
677         return get(fullFilePathName, hostName, userName, passWord);
678     }
679
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;
686             buf.write(b);
687             result = bis.read();
688         }
689         return buf.toString();
690     }
691
692     public void logMemoryUsage() {
693         int mb = 1024 * 1024;
694         long usedMemory;
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);
702     }
703
704     public void connect(String hostname, String username, String password, int timeOut, int portNum,
705         String subsystem) throws IOException {
706
707         if (log.isDebugEnabled()) {
708             log.debug(
709                 "Attempting to connect to {0} username={1} timeOut={2} portNum={3} subsystem={4}",
710                 hostname, username, timeOut, portNum, subsystem);
711         }
712         this.routerName = hostname;
713         this.hostName = hostname;
714         this.userName = username;
715         this.passWord = password;
716         try {
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());
726         }
727     }
728
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;
735         try {
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);
740             channel.connect();
741             try {
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);
745             }
746
747         } catch (JSchException e) {
748             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), username);
749             throw new IOException(e.getMessage());
750         }
751     }
752
753
754     public void put(String sourcePath, String destDirectory) throws IOException {
755         ChannelSftp sftp = null;
756         try {
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());
766         } finally {
767             disconnectSftp(sftp);
768         }
769     }
770
771     public void put(InputStream is, String fullPathDest, String hostName, String userName, String passWord)
772         throws IOException {
773         ChannelSftp sftp = null;
774         try {
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());
788         } finally {
789             disconnectSftp(sftp);
790         }
791     }
792
793     private void removeOldFiles(ChannelSftp sftp, String oldFiles, String hostname, String username) throws SftpException {
794         try {
795             sftp.rm(oldFiles);
796         } catch (SftpException ex) {
797             if (ChannelSftp.SSH_FX_NO_SUCH_FILE == ex.id) {
798                 log.warn("No files found, continue");
799             } else {
800                 log.error(Msg.SFTP_TRANSFER_FAILED, hostname, username, "RM", ex.getMessage());
801                 throw ex;
802             }
803         }
804     }
805
806     public String get(String fullFilePathName, String hostName, String userName, String passWord) throws IOException {
807         ChannelSftp sftp = null;
808         try {
809             log.debug("Sftp get invoked, connection details: username={1} hostname={2}",
810                 userName, hostName);
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());
820         } finally {
821             disconnectSftp(sftp);
822         }
823     }
824
825     private void disconnectSftp (ChannelSftp sftp) {
826         if (sftp != null) {
827             sftp.disconnect();
828         }
829         if (session != null) {
830             session.disconnect();
831             session = null;
832         }
833     }
834
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);
838         sftp.connect();
839         return sftp;
840     }
841
842     ChannelSftp openSftpChannel(Session sftpSession) throws JSchException {
843         return (ChannelSftp) sftpSession.openChannel(CHANNEL_SFTP_TYPE);
844     }
845
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);
853             } else {
854                 sendSshCommand(command, dos);
855                 return receiveUntil(delimiter, 300000, cmd);
856             }
857         }
858     }
859
860     private void sendSshCommand(@Nonnull String command, @Nonnull DataOutputStream channelOutputStream)
861         throws IOException {
862         channelOutputStream.writeBytes(command);
863         channelOutputStream.flush();
864     }
865
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();
877             try {
878                 if (numCharsSentInChunk < length) {
879                     received.append(receiveUntilBufferFlush(numCharsSentInChunk, sessionTimeoutMs, command));
880                 } else {
881                     log.trace("i={0}, flush immediately", i);
882                     channelOutputStream.flush();
883                 }
884             } catch (IOException ex) {
885                 log.warn("IOException occurred: nothing to flush out", ex);
886             }
887         }
888         return received.toString();
889     }
890
891     public void setSessionTimeoutMs(int sessionTimeoutMs) {
892         this.sessionTimeoutMs = sessionTimeoutMs;
893     }
894
895     void setCharsChunkSize(int charsChunkSize) {
896         this.charsChunkSize = charsChunkSize;
897     }
898
899     private boolean isCmdLengthEnoughToSendInChunks(int length, int chunkSize) {
900         return length > 2 * chunkSize;
901     }
902
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;
907         }
908         return originalCommand;
909     }
910
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);
916     }
917
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);
924     }
925
926 }