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