Unit tests for send methods
[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     private static final String TERMINAL_BASIC_MODE = "vt102";
71     static final String STRICT_HOST_CHECK_KEY = "StrictHostKeyChecking";
72     static final String STRICT_HOST_CHECK_VALUE = "no";
73     static final String DELIMITERS_SEPARATOR = "|";
74
75     private TelnetListener listener = null;
76     private String routerLogFileName = null;
77     private BufferedReader reader = null;
78     private BufferedWriter out = null;
79     private File tmpFile = null;
80     private JSch jsch = null;
81     private Session session = null;
82     private Channel channel = null;
83     private String aggregatedReceivedString = "";
84     private String routerCmdType = "XML";
85     private String routerFileName = null;
86     private File jcraftReadSwConfigFileFromDisk = new File("/tmp/jcraftReadSwConfigFileFromDisk");
87     private String equipNameCode = null;
88     private String routerName = null;
89     private String hostName = null;
90     private String userName = null;
91     private String passWord = null;
92     private int readIntervalMs = 500;
93     private int readBufferSizeBytes = 512_000;
94     private int charsChunkSize = 300_000;
95     private int sessionTimeoutMs = 9_000;
96     private char[] charBuffer;
97     private Runtime runtime = Runtime.getRuntime();
98
99     public SshJcraftWrapper() {
100         this.jsch = new JSch();
101         this.charBuffer = new char[readBufferSizeBytes];
102     }
103
104     SshJcraftWrapper(JSch jsch, int readIntervalMs, int readBufferSizeBytes) {
105         this.readIntervalMs = readIntervalMs;
106         this.jsch = jsch;
107         this.readBufferSizeBytes = readBufferSizeBytes;
108         this.charBuffer = new char[readBufferSizeBytes];
109     }
110
111     public void connect(String hostname, String username, String password, String prompt, int timeOut)
112         throws IOException {
113         log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3}",
114             hostname, username, prompt, timeOut);
115         routerName = hostname;
116         hostName = hostname;
117         userName = username;
118         passWord = password;
119         try {
120             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, timeOut);
121             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
122             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
123                 readBufferSizeBytes);
124             channel.connect();
125             log.info("Successfully connected. Flushing input buffer.");
126             try {
127                 receiveUntil(prompt, 3000, "No cmd was sent, just waiting");
128             } catch (IOException e) {
129                 log.warn("Caught an Exception: Nothing to flush out.", e);
130             }
131         } catch (JSchException e) {
132             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(DEFAULT_PORT), username);
133             throw new IOException(e.toString());
134         }
135     }
136
137     // User specifies the port number.
138     public void connect(String hostname, String username, String password, String prompt, int timeOut, int portNum)
139         throws IOException {
140         log.debug("Attempting to connect to {0} username={1} prompt='{2}' timeOut={3} portNum={4}",
141             hostname, username, prompt, timeOut, portNum);
142         routerName = hostname;
143         hostName = hostname;
144         userName = username;
145         passWord = password;
146         try {
147             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, portNum, timeOut);
148             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
149             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
150                 readBufferSizeBytes);
151             channel.connect();
152             log.info("Successfully connected. Flushing input buffer.");
153             try {
154                 if ("]]>]]>".equals(prompt)) {
155                     receiveUntil("]]>]]>", 10000, "No cmd was sent, just waiting");
156                 } else {
157                     receiveUntil(":~#", 5000, "No cmd was sent, just waiting");
158                 }
159             } catch (IOException e) {
160                 log.warn("Caught an Exception: Nothing to flush out.", e);
161             }
162         } catch (JSchException e) {
163             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(portNum), username);
164             throw new IOException(e.toString());
165         }
166     }
167
168
169     public String receiveUntil(String delimeters, int timeout, String cmdThatWasSent) throws IOException {
170         checkConnection();
171         boolean match = false;
172         boolean cliPromptCmd = false;
173         StringBuilder sb = new StringBuilder();
174         StringBuilder sbReceive = new StringBuilder();
175         log.debug("delimeters='{0}' timeout={1} cmdThatWasSent='{2}'", delimeters, timeout, cmdThatWasSent);
176         int readCounts = 0;
177         aggregatedReceivedString = "";
178         FileWriter fileWriter = null;
179
180         long deadline = new Date().getTime() + timeout;
181         try {
182             session.setTimeout(timeout);  // This is the socket timeout value.
183             while (!match) {
184                 if (new Date().getTime() > deadline) {
185                     String formattedCmd = removeWhiteSpaceAndNewLineCharactersAroundString(cmdThatWasSent);
186                     log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, formattedCmd);
187                     throw new TimedOutException("Routine has timed out");
188                 }
189                 sleep(readIntervalMs);
190                 int len = reader.read(charBuffer, 0, readBufferSizeBytes);
191                 log.trace("After reader. Read command len={0}", len);
192                 if (len <= 0) {
193                     log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, cmdThatWasSent);
194                     throw new TimedOutException("Received a SocketTimeoutException router=" + routerName);
195                 }
196                 if (!cliPromptCmd) {
197                     if (cmdThatWasSent.indexOf("IOS_XR_uploadedSwConfigCmd") != -1) {
198                         if (out == null) {
199                             // This is a IOS XR sw config file. We will write it to the disk.
200                             timeout = timeout * 2;
201                             deadline = new Date().getTime() + timeout;
202                             log.debug("IOS XR upload for software config: timeout={0}", timeout);
203                             StringTokenizer st = new StringTokenizer(cmdThatWasSent);
204                             st.nextToken();
205                             routerFileName = st.nextToken();
206                             fileWriter = new FileWriter(routerFileName);
207                             out = new BufferedWriter(fileWriter);
208                             routerLogFileName = "/tmp/" + routerName;
209                             tmpFile = new File(routerLogFileName);
210                             log.debug("Prepared for writing swConfigFile to disk, routerFileName=" + routerFileName);
211                         }
212                         out.write(charBuffer, 0, len);
213                         out.flush();
214                         log.debug("{0} bytes has been written to the disk", len);
215                         if (tmpFile.exists()) {
216                             appendToRouterFile(routerLogFileName, len);
217                         }
218                         match = checkIfReceivedStringMatchesDelimeter(len, "\nXML>");
219                         if (match) {
220                             out.flush();
221                             out.close();
222                             out = null;
223                             return null;
224                         }
225                     } else {
226                         readCounts++;
227                         log.debug("Reader read {0} of data within {1} read iteration", len, readCounts);
228                         int c;
229                         sb.setLength(0);
230                         for (int i = 0; i < len; i++) {
231                             c = charBuffer[i];
232                             if ((c != 7) && (c != 13) && (c != 0) && (c != 27)) {
233                                 sbReceive.append(charBuffer[i]);
234                                 sb.append(charBuffer[i]);
235                             }
236                         }
237                         appendToRouterFile("/tmp/" + routerName, len);
238                         if (listener != null) {
239                             listener.receivedString(sb.toString());
240                         }
241                         match = checkIfReceivedStringMatchesDelimeter(delimeters, sb.toString(), cmdThatWasSent);
242                         if (match) {
243                             log.trace("Match was true, breaking the loop.");
244                             break;
245                         }
246                     }
247                 } else {
248                     log.trace("cliPromptCmd");
249                     sb.setLength(0);
250                     for (int i = 0; i < len; i++) {
251                         sbReceive.append(charBuffer[i]);
252                         sb.append(charBuffer[i]);
253                     }
254                     appendToRouterFile("/tmp/" + routerName, sb);
255                     if (listener != null) {
256                         listener.receivedString(sb.toString());
257                     }
258                     log.debug("sb2={0}  delimiters={1}", sb.toString(), delimeters);
259                     if (sb.toString().contains("\nariPrompt>")) {
260                         log.debug("Found ari prompt");
261                         break;
262                     }
263                 }
264             }
265         } catch (JSchException | IOException e) {
266             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
267             throw new TimedOutException(e.getMessage());
268         } finally {
269             try {
270                 if (fileWriter != null) {
271                     fileWriter.close();
272                 }
273             } catch (IOException ex) {
274                 log.warn("Failed to close fileWriter output stream", ex);
275             }
276         }
277         return stripOffCmdFromRouterResponse(sbReceive.toString());
278     }
279
280     private void sleep(long timeoutMs) {
281         try {
282             TimeUnit.MILLISECONDS.sleep(timeoutMs);
283         } catch (java.lang.InterruptedException ee) {
284             Thread.currentThread().interrupt();
285         }
286     }
287
288     private void checkConnection() {
289         try {
290             if (!isConnected() || !reader.ready()) {
291                 throw new IllegalStateException("Connection not established. Cannot perform action.");
292             }
293         } catch (IOException e) {
294             throw new IllegalStateException("Reader stream is closed. Cannot perform action.", e);
295         }
296     }
297
298     public boolean checkIfReceivedStringMatchesDelimeter(String delimeters, String receivedString,
299         String cmdThatWasSent) {
300         // The delimeters are in a '|' seperated string. Return true on the first match.
301         log.debug("Entered checkIfReceivedStringMatchesDelimeter: delimeters={0} cmdThatWasSent={1} receivedString={2}",
302             delimeters, cmdThatWasSent, receivedString);
303         StringTokenizer st = new StringTokenizer(delimeters, DELIMITERS_SEPARATOR);
304
305         if ((delimeters.contains("#$")) || ("CLI".equals(routerCmdType)))  // This would be an IOS XR, CLI command.
306         {
307             int x = receivedString.lastIndexOf('#');
308             int y = receivedString.length() - 1;
309             log.debug("IOS XR, CLI command");
310             if (log.isTraceEnabled()) {
311                 log.trace("cmdThatWasSent={0}, lastIndexOf hash delimiter={1}, maxIndexNum={2}", cmdThatWasSent, x, y);
312             }
313             return (x != -1) && (y == x);
314         }
315         if (cmdThatWasSent.contains("show config")) {
316             log.trace("In the block for 'show config'");
317             while (st.hasMoreTokens()) {
318                 String delimeter = st.nextToken();
319                 // Make sure we don't get faked out by a response of " #".
320                 // Proc #0
321                 //   # signaling-local-address ipv6 FD00:F4D5:EA06:1::110:136:254
322                 // LAAR2#
323                 int x = receivedString.lastIndexOf(delimeter);
324                 if ((receivedString.lastIndexOf(delimeter) != -1) && (receivedString.lastIndexOf(" #") != x - 1)) {
325                     log.debug("receivedString={0}", receivedString);
326                     log.trace("Found ending for 'show config' command, exiting.");
327                     return true;
328                 }
329             }
330         } else {
331             aggregatedReceivedString = aggregatedReceivedString + receivedString;
332             appendToFile("/tmp/aggregatedReceivedString.debug", aggregatedReceivedString);
333
334             log.debug("receivedString={0}", receivedString);
335             while (st.hasMoreTokens()) {
336                 String delimeter = st.nextToken();
337                 log.debug("Looking for an delimiter of:{0}", delimeter);
338                 if (aggregatedReceivedString.indexOf(delimeter) != -1) {
339                     log.debug("Found delimiter={0}, exiting", delimeter);
340                     aggregatedReceivedString = "";
341                     return true;
342                 }
343             }
344         }
345         return false;
346     }
347
348     public boolean checkIfReceivedStringMatchesDelimeter(int len, String delimeter) {
349         int x;
350         int c;
351         String str = StringUtils.EMPTY;
352
353         if (jcraftReadSwConfigFileFromDisk()) {
354             log.trace("jcraftReadSwConfigFileFromDisk block");
355             File fileName = new File(routerFileName);
356             log.debug("jcraftReadSwConfigFileFromDisk::: Will read the tail end of the file from the disk");
357             try {
358                 str = getLastFewLinesOfFile(fileName, 3);
359             } catch (IOException e) {
360                 log.warn("IOException occurred, while reading file=" + fileName, e);
361             }
362         } else {
363             // When looking at the end of the charBuffer, don't include any linefeeds or spaces. We only want to make the smallest string possible.
364             for (x = len - 1; x >= 0; x--) {
365                 c = charBuffer[x];
366                 if ((c != 10) && (c != 32)) // Not a line feed nor a space.
367                 {
368                     break;
369                 }
370             }
371             if ((x + 1 - 13) >= 0) {
372                 str = new String(charBuffer, x + 1 - 13, 13);
373                 log.debug("str:{0}", str);
374             } else {
375                 File fileName = new File(routerFileName);
376                 log.debug("Will read the tail end of the file from the disk, x={0} len={1} str={2} routerFileName={3}",
377                     x, len, str, routerFileName);
378                 try {
379                     str = getLastFewLinesOfFile(fileName, 3);
380                 } catch (IOException e) {
381                     log.warn("IOException occurred, while reading file=" + fileName, e);
382                 }
383             }
384         }
385
386         log.debug("Parsed string was str='{0}', searched delimiter was {1}");
387         return str.contains(delimeter);
388     }
389
390     public void closeConnection() {
391         log.info("Closing connection");
392         try {
393             if (reader != null) {
394                 reader.close();
395             }
396         } catch (IOException ex) {
397             log.warn("Could not close reader instance", ex);
398         } finally {
399             if (isConnected()) {
400                 channel.disconnect();
401                 session.disconnect();
402                 channel = null;
403                 session = null;
404             }
405             reader = null;
406         }
407     }
408
409     boolean isConnected() {
410         return channel != null && session != null;
411     }
412
413     public void send(String cmd) throws IOException {
414         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
415             String command = enhanceCommandWithEOL(cmd);
416             int length = command.length();
417             log.debug("Sending ssh command: length={0}, payload: {1}", command.length(), command);
418             if(isCmdLengthEnoughToSendInChunks(length, charsChunkSize)) {
419                 sendSshCommandInChunks(command, dos);
420             } else {
421                 sendSshCommand(command, dos);
422             }
423         } catch (IOException e) {
424             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
425             throw e;
426         }
427     }
428
429     public void sendChar(int v) throws IOException {
430         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
431             if (log.isTraceEnabled()) {
432                 log.trace("Sending charCode: {0}", v);
433             }
434             dos.writeChar(v);
435             dos.flush();
436         } catch (IOException e) {
437             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
438             throw e;
439         }
440     }
441
442     public void send(byte[] b, int off, int len) throws IOException {
443         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
444             dos.write(b, off, len);
445             dos.flush();
446         } catch (IOException e) {
447             log.error(Msg.SSH_DATA_EXCEPTION, e.getMessage());
448             throw e;
449         }
450     }
451
452     public static class MyUserInfo implements UserInfo, UIKeyboardInteractive {
453
454         @Override
455         public String getPassword() {
456             return null;
457         }
458
459         @Override
460         public boolean promptYesNo(String str) {
461             return false;
462         }
463
464         @Override
465         public String getPassphrase() {
466             return null;
467         }
468
469         @Override
470         public boolean promptPassphrase(String message) {
471             return false;
472         }
473
474         @Override
475         public boolean promptPassword(String message) {
476             return false;
477         }
478
479         @Override
480         public void showMessage(String message) {
481             //stub
482         }
483
484         @Override
485         public String[] promptKeyboardInteractive(String destination,
486             String name,
487             String instruction,
488             String[] prompt,
489             boolean[] echo) {
490             return new String[0];
491         }
492     }
493
494     public void addListener(TelnetListener listener) {
495         this.listener = listener;
496     }
497
498     private void appendToFile(String fileName, String dataToWrite) {
499         File outputFile = new File(fileName);
500         if (outputFile.exists()) {
501             try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
502                 ow.write(dataToWrite);
503                 ow.close();
504             } catch (IOException e) {
505                 log.warn("IOException occurred while writing to file=" + fileName, e);
506             }
507         }
508     }
509
510     public String getTheDate() {
511         DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy H:mm:ss  ");
512         return dateFormat.format(Calendar.getInstance().getTime());
513     }
514
515
516     public void appendToRouterFile(String fileName, StringBuilder dataToWrite) {
517         appendToFile(fileName, dataToWrite.toString());
518     }
519
520     public void appendToRouterFile(String fileName, int len) {
521         File outputFile = new File(fileName);
522         if (outputFile.exists()) {
523             try (FileWriter fw = new FileWriter(fileName, true); BufferedWriter ow = new BufferedWriter(fw)) {
524                 ow.write(charBuffer, 0, len);
525                 ow.close();
526             } catch (IOException e) {
527                 log.warn("Could not write data to router file:" + fileName, e);
528             }
529         }
530     }
531
532     public String removeWhiteSpaceAndNewLineCharactersAroundString(String str) {
533         if (str != null && !StringUtils.EMPTY.equals(str)) {
534             StringTokenizer strTok = new StringTokenizer(str, EOL);
535             StringBuilder sb = new StringBuilder();
536
537             while (strTok.hasMoreTokens()) {
538                 String line = strTok.nextToken();
539                 sb.append(line);
540             }
541             return sb.toString().trim();
542         }
543         return StringUtils.EMPTY;
544     }
545
546     public String stripOffCmdFromRouterResponse(String routerResponse) {
547         // The session of SSH will echo the command sent to the router, in the router's response.
548         // Since all our commands are terminated by a '\n', strip off the first line
549         // of the response from the router. This first line contains the orginal command.
550
551         String[] responseTokens = routerResponse.split(EOL, 2);
552         return responseTokens[responseTokens.length - 1];
553     }
554
555     public void setRouterCommandType(String type) {
556         this.routerCmdType = type;
557         log.debug("Router command type is set to: {0}", type);
558     }
559
560     public String getLastFewLinesOfFile(File file, int linesToRead) throws IOException {
561         String tail = "";
562         try(RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
563             int lines = 0;
564             StringBuilder builder = new StringBuilder();
565             long length = file.length();
566             length--;
567             randomAccessFile.seek(length);
568             for (long seek = length; seek >= 0; --seek) {
569                 randomAccessFile.seek(seek);
570                 char c = (char) randomAccessFile.read();
571                 builder.append(c);
572                 if (c == '\n') {
573                     builder = builder.reverse();
574                     tail = builder.append(tail).toString();
575                     lines++;
576                     builder.setLength(0);
577                     if (lines == linesToRead) {
578                         break;
579                     }
580                 }
581             }
582         }
583         if (log.isDebugEnabled()) {
584             log.debug("Content read from file={0} was tail={1}", file.getName(), tail);
585         }
586         return tail;
587     }
588
589     public boolean jcraftReadSwConfigFileFromDisk() {
590         return jcraftReadSwConfigFileFromDisk.exists();
591     }
592
593     public String getEquipNameCode() {
594         return equipNameCode;
595     }
596
597     public void setEquipNameCode(String equipNameCode) {
598         this.equipNameCode = equipNameCode;
599     }
600
601     public String getRouterName() {
602         return routerName;
603     }
604
605     // Routine does reads until it has read 'nchars' or times out.
606     public String receiveUntilBufferFlush(int ncharsSent, int timeout, String command) throws IOException {
607         log.debug("ncharsSent={0}, timeout={1}, message={2}", ncharsSent, timeout, command);
608         int ncharsTotalReceived = 0;
609         int ncharsRead = 0;
610         StringBuilder received = new StringBuilder();
611
612         long deadline = new Date().getTime() + timeout;
613         logMemoryUsage();
614         try {
615             session.setTimeout(timeout);  // This is the socket timeout value.
616             while (true) {
617                 if (new Date().getTime() > deadline) {
618                     log.error(Msg.SSH_CONNECTION_TIMEOUT, routerName, command);
619                     throw new TimedOutException("Routine has timed out");
620                 }
621                 ncharsRead = reader.read(charBuffer, 0, readBufferSizeBytes);
622                 if(ncharsRead >=0) {
623                     received.append(charBuffer, 0, ncharsRead);
624                 }
625                 if (listener != null) {
626                     listener.receivedString(String.copyValueOf(charBuffer, 0, ncharsRead));
627                 }
628                 appendToRouterFile("/tmp/" + routerName, ncharsRead);
629                 ncharsTotalReceived = ncharsTotalReceived + ncharsRead;
630                 if (ncharsTotalReceived >= ncharsSent) {
631                     log.debug("Received the correct number of characters, ncharsSent={0}, ncharsTotalReceived={1}",
632                         ncharsSent, ncharsTotalReceived);
633                     logMemoryUsage();
634                     return received.toString();
635                 }
636             }
637         } catch (JSchException e) {
638             log.error(Msg.SSH_SESSION_CONFIG_ERROR, e.getMessage());
639             log.debug("ncharsSent={0}, ncharsTotalReceived={1}, ncharsRead={2} until error occurred",
640                 ncharsSent, ncharsTotalReceived, ncharsRead);
641             throw new TimedOutException(e.getMessage());
642         }
643     }
644
645     public String getHostName() {
646         return hostName;
647     }
648
649     public String getUserName() {
650         return userName;
651     }
652
653     public String getPassWord() {
654         return passWord;
655     }
656
657     public void sftpPutFile(String sourcePath, String destDirectory) throws IOException {
658         try {
659             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
660             UserInfo ui = new MyUserInfo();
661             sftpSession.setPassword(passWord);
662             sftpSession.setUserInfo(ui);
663             sftpSession.connect(30 * 1000);
664             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
665             sftp.connect();
666             log.debug("Sending via sftp from source: {0} to destination: {1}", sourcePath, destDirectory);
667             sftp.put(sourcePath, destDirectory, ChannelSftp.OVERWRITE);
668             sftpSession.disconnect();
669         } catch (JSchException ex) {
670             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
671             throw new IOException(ex.getMessage());
672         } catch (SftpException ex) {
673             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
674             throw new IOException(ex.getMessage());
675         }
676     }
677
678     public void sftpPutStringData(String stringOfData, String fullPathDest) throws IOException {
679         try {
680             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
681             UserInfo ui = new MyUserInfo();
682             sftpSession.setPassword(passWord);
683             sftpSession.setUserInfo(ui);
684             sftpSession.connect(30 * 1000);
685             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
686             sftp.connect();
687             InputStream is = new ByteArrayInputStream(stringOfData.getBytes());
688             log.debug("Sending via sftp stringOfData to destination: {0}", fullPathDest);
689             sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
690             sftpSession.disconnect();
691         } catch (JSchException ex) {
692             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
693             throw new IOException(ex.getMessage());
694         } catch (SftpException ex) {
695             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
696             throw new IOException(ex.getMessage());
697         }
698     }
699
700     public String sftpGet(String fullFilePathName) throws IOException {
701         try {
702             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
703             UserInfo ui = new MyUserInfo();
704             sftpSession.setPassword(passWord);
705             sftpSession.setUserInfo(ui);
706             sftpSession.connect(30 * 1000);
707             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
708             sftp.connect();
709             InputStream in = sftp.get(fullFilePathName);
710             String sftpFileString = readInputStreamAsString(in);
711             log.debug("Received data via sftp connection sftpFileString={0} from fullFilePathName={1}",
712                 sftpFileString, fullFilePathName);
713             sftpSession.disconnect();
714             return sftpFileString;
715         } catch (JSchException ex) {
716             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
717             throw new IOException(ex.getMessage());
718         } catch (SftpException ex) {
719             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "GET", ex.getMessage());
720             throw new IOException(ex.getMessage());
721         }
722     }
723
724     public static String readInputStreamAsString(InputStream in) throws IOException {
725         BufferedInputStream bis = new BufferedInputStream(in);
726         ByteArrayOutputStream buf = new ByteArrayOutputStream();
727         int result = bis.read();
728         while (result != -1) {
729             byte b = (byte) result;
730             buf.write(b);
731             result = bis.read();
732         }
733         return buf.toString();
734     }
735
736
737     public void logMemoryUsage() {
738         int mb = 1024 * 1024;
739         long usedMemory;
740         long maxMemoryAvailable;
741         long memoryLeftOnHeap;
742         maxMemoryAvailable = runtime.maxMemory() / mb;
743         usedMemory = (runtime.totalMemory() / mb) - (runtime.freeMemory() / mb);
744         memoryLeftOnHeap = maxMemoryAvailable - usedMemory;
745         log.info("Memory usage: maxMemoryAvailable={0}, usedMemory={1}, memoryLeftOnHeap={2}",
746             maxMemoryAvailable, usedMemory, memoryLeftOnHeap);
747     }
748
749     public void connect(String hostname, String username, String password, int timeOut, int portNum,
750         String subsystem) throws IOException {
751
752         if (log.isDebugEnabled()) {
753             log.debug(
754                 "Attempting to connect to {0} username={1} timeOut={2} portNum={3} subsystem={4}",
755                 hostname, username, timeOut, portNum, subsystem);
756         }
757         this.routerName = hostname;
758         this.hostName = hostname;
759         this.userName = username;
760         this.passWord = password;
761         try {
762             channel = provideSessionChannel(CHANNEL_SUBSYSTEM_TYPE, portNum, timeOut);
763             ((ChannelSubsystem) channel).setSubsystem(subsystem);
764             ((ChannelSubsystem) channel).setPty(true); //expected ptyType vt102
765             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
766                 readBufferSizeBytes);
767             channel.connect(5000);
768         } catch (JSchException e) {
769             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostname, String.valueOf(portNum), username);
770             throw new IOException(e.getMessage());
771         }
772     }
773
774     public void connect(String hostName, String username, String password) throws IOException {
775         log.debug("Attempting to connect to {0} username={1} portNumber={2}", hostName, username, DEFAULT_PORT);
776         this.routerName = hostName;
777         this.hostName = hostName;
778         this.userName = username;
779         this.passWord = password;
780         try {
781             channel = provideSessionChannel(CHANNEL_SHELL_TYPE, DEFAULT_PORT, 30000);
782             ((ChannelShell) channel).setPtyType(TERMINAL_BASIC_MODE);
783             reader = new BufferedReader(new InputStreamReader(new DataInputStream(channel.getInputStream())),
784                 readBufferSizeBytes);
785             channel.connect();
786             try {
787                 receiveUntil(":~#", 9000, "No cmd was sent, just waiting, but we can stop on a '~#'");
788             } catch (Exception e) {
789                 log.warn("Caught an Exception: Nothing to flush out.", e);
790             }
791
792         } catch (JSchException e) {
793             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), username);
794             throw new IOException(e.getMessage());
795         }
796     }
797
798
799     public void put(String sourcePath, String destDirectory) throws IOException {
800         try {
801             Session sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
802             UserInfo ui = new MyUserInfo();
803             sftpSession.setPassword(passWord);
804             sftpSession.setUserInfo(ui);
805             sftpSession.connect(30 * 1000);
806             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
807             sftp.connect();
808             log.debug("Sending via sftp from source: {0} to destination: {1}", sourcePath, destDirectory);
809             sftp.put(sourcePath, destDirectory, ChannelSftp.OVERWRITE);
810             sftpSession.disconnect();
811         } catch (JSchException ex) {
812             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
813             throw new IOException(ex.getMessage());
814         } catch (SftpException ex) {
815             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
816             throw new IOException(ex.getMessage());
817         }
818     }
819
820     public void put(InputStream is, String fullPathDest, String hostName, String userName, String passWord)
821         throws IOException {
822         Session sftpSession = null;
823         try {
824             log.debug("Sftp put invoked, connection details: username={1} hostname={2}",
825                 userName, hostName);
826             jsch = new JSch();
827             java.util.Properties config = new java.util.Properties();
828             config.put("StrictHostKeyChecking", "no");
829             sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
830             UserInfo ui = new MyUserInfo();
831             sftpSession.setPassword(passWord);
832             sftpSession.setUserInfo(ui);
833             sftpSession.setConfig(config);
834             sftpSession.connect(30 * 1000);
835             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
836             sftp.connect();
837             String oldFiles = fullPathDest + "*";
838             log.debug("Deleting old files: {0}", oldFiles);
839             try {
840                 sftp.rm(oldFiles);
841             } catch (SftpException ex) {
842                 String exp = "No such file";
843                 if (ex.getMessage() != null && ex.getMessage().contains(exp)) {
844                     log.warn("No files found, continue");
845                 } else {
846                     log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "RM", ex.getMessage());
847                     throw ex;
848                 }
849             }
850             log.debug("Sending stringOfData to destination {0}", fullPathDest);
851             sftp.put(is, fullPathDest, ChannelSftp.OVERWRITE);
852         } catch (JSchException ex) {
853             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
854             throw new IOException(ex.getMessage());
855         } catch (SftpException ex) {
856             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "PUT", ex.getMessage());
857             throw new IOException(ex.getMessage());
858         } finally {
859             if (sftpSession != null) {
860                 sftpSession.disconnect();
861             }
862         }
863     }
864
865     public String get(String fullFilePathName, String hostName, String userName, String passWord) throws IOException {
866         Session sftpSession = null;
867         try {
868             log.debug("Sftp get invoked, connection details: username={1} hostname={2}",
869                 userName, hostName);
870             jsch = new JSch();
871             sftpSession = jsch.getSession(userName, hostName, DEFAULT_PORT);
872             java.util.Properties config = new java.util.Properties();
873             config.put("StrictHostKeyChecking", "no");
874             UserInfo ui = new MyUserInfo();
875             sftpSession.setPassword(passWord);
876             sftpSession.setUserInfo(ui);
877             sftpSession.setConfig(config);
878             sftpSession.connect(30 * 1000);
879             ChannelSftp sftp = (ChannelSftp) sftpSession.openChannel("sftp");
880             sftp.connect();
881             InputStream in = sftp.get(fullFilePathName);
882             return readInputStreamAsString(in);
883         } catch (JSchException ex) {
884             log.error(Msg.CANNOT_ESTABLISH_CONNECTION, hostName, String.valueOf(DEFAULT_PORT), userName);
885             throw new IOException(ex.getMessage());
886         } catch (SftpException ex) {
887             log.error(Msg.SFTP_TRANSFER_FAILED, hostName, userName, "GET", ex.getMessage());
888             throw new IOException(ex.getMessage());
889         } finally {
890             if (sftpSession != null) {
891                 sftpSession.disconnect();
892             }
893         }
894     }
895
896     public String send(String cmd, String delimiter) throws IOException {
897         try (OutputStream os = channel.getOutputStream(); DataOutputStream dos = new DataOutputStream(os)) {
898             String command = enhanceCommandWithEOL(cmd);
899             int length = command.length();
900             log.debug("Sending ssh command: length={0}, payload: {1}", command.length(), command);
901             if(isCmdLengthEnoughToSendInChunks(length, charsChunkSize)) {
902                 return sendSshCommandInChunks(command, dos);
903             } else {
904                 sendSshCommand(command, dos);
905                 return receiveUntil(delimiter, 300000, cmd);
906             }
907         }
908     }
909
910     private void sendSshCommand(@Nonnull String command, @Nonnull DataOutputStream channelOutputStream)
911         throws IOException {
912         channelOutputStream.writeBytes(command);
913         channelOutputStream.flush();
914     }
915
916     private String sendSshCommandInChunks(@Nonnull String command, @Nonnull DataOutputStream channelOutputStream) throws IOException {
917         StringBuilder received =  new StringBuilder();
918         int charsTotalSent = 0;
919         int length = command.length();
920         for (int i = 0; i < length; i += charsChunkSize) {
921             String commandChunk = command.substring(i, Math.min(length, i + charsChunkSize));
922             int numCharsSentInChunk = commandChunk.length();
923             charsTotalSent = charsTotalSent + commandChunk.length();
924             log.debug("Iteration nr:{0}, sending command chunk: {1}", i, numCharsSentInChunk);
925             channelOutputStream.writeBytes(commandChunk);
926             channelOutputStream.flush();
927             try {
928                 if (numCharsSentInChunk < length) {
929                     received.append(receiveUntilBufferFlush(numCharsSentInChunk, sessionTimeoutMs, command));
930                 } else {
931                     log.trace("i={0}, flush immediately", i);
932                     channelOutputStream.flush();
933                 }
934             } catch (IOException ex) {
935                 log.warn("IOException occurred: nothing to flush out", ex);
936             }
937         }
938         return received.toString();
939     }
940
941     public void setSessionTimeoutMs(int sessionTimeoutMs) {
942         this.sessionTimeoutMs = sessionTimeoutMs;
943     }
944
945     void setCharsChunkSize(int charsChunkSize) {
946         this.charsChunkSize = charsChunkSize;
947     }
948
949     private boolean isCmdLengthEnoughToSendInChunks(int length, int chunkSize) {
950         return length > 2 * chunkSize;
951     }
952
953     private String enhanceCommandWithEOL(@Nonnull String originalCommand) {
954         char commandEnding = originalCommand.charAt(originalCommand.length() - 1);
955         if (commandEnding != '\n' && commandEnding != '\r') {
956             return originalCommand + EOL;
957         }
958         return originalCommand;
959     }
960
961     private Channel provideSessionChannel(String channelType, int port, int timeout) throws JSchException {
962         session = jsch.getSession(this.userName, this.hostName, port);
963         session.setPassword(this.passWord);
964         session.setUserInfo(new MyUserInfo()); //needed?
965         session.setConfig(STRICT_HOST_CHECK_KEY, STRICT_HOST_CHECK_VALUE);
966         session.connect(timeout);
967         session.setServerAliveCountMax(
968             0); // If this is not set to '0', then socket timeout on all reads will not work!!!!
969         return session.openChannel(channelType);
970     }
971
972 }