1
2
3
4
5
6 package org.metricshub.ssh;
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 import com.trilead.ssh2.ChannelCondition;
29 import com.trilead.ssh2.Connection;
30 import com.trilead.ssh2.InteractiveCallback;
31 import com.trilead.ssh2.SCPClient;
32 import com.trilead.ssh2.SFTPException;
33 import com.trilead.ssh2.SFTPv3Client;
34 import com.trilead.ssh2.SFTPv3DirectoryEntry;
35 import com.trilead.ssh2.SFTPv3FileAttributes;
36 import com.trilead.ssh2.SFTPv3FileHandle;
37 import com.trilead.ssh2.Session;
38 import java.io.BufferedReader;
39 import java.io.ByteArrayOutputStream;
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.InputStream;
43 import java.io.InputStreamReader;
44 import java.io.OutputStream;
45 import java.nio.charset.Charset;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.List;
49 import java.util.Optional;
50 import java.util.regex.Pattern;
51
52
53
54
55
56
57
58
59
60
61
62
63
64 public class SshClient implements AutoCloseable {
65
66 private static final Pattern DEFAULT_MASK_PATTERN = Pattern.compile(".*");
67
68 private static final int READ_BUFFER_SIZE = 8192;
69
70 private String hostname;
71 private Connection sshConnection = null;
72
73 private Charset charset = null;
74
75
76
77
78 private Session sshSession = null;
79
80
81
82
83
84
85 public SshClient(String pHostname) {
86 this(pHostname, "");
87 }
88
89
90
91
92
93
94
95 public SshClient(String pHostname, String pLocale) {
96 this(pHostname, Utils.getCharsetFromLocale(pLocale));
97 }
98
99
100
101
102
103
104
105 public SshClient(String pHostname, Charset pCharset) {
106 hostname = pHostname;
107 charset = pCharset;
108 }
109
110
111
112
113
114
115 public void connect() throws IOException {
116 connect(0);
117 }
118
119
120
121
122
123
124
125 public void connect(final int timeout) throws IOException {
126 this.connect(timeout, 22);
127 }
128
129
130
131
132
133
134
135
136 public void connect(final int timeout, final int port) throws IOException {
137 sshConnection = new Connection(hostname, port);
138 sshConnection.connect(null, timeout, timeout);
139 }
140
141
142
143
144
145
146
147
148
149 @Deprecated
150 public void disconnect() {
151 if (sshSession != null) {
152 sshSession.close();
153 }
154 if (sshConnection != null) {
155 sshConnection.close();
156 }
157 }
158
159
160
161
162
163
164
165 @Override
166 public void close() {
167 if (sshSession != null) {
168 sshSession.close();
169 }
170 if (sshConnection != null) {
171 sshConnection.close();
172 }
173 }
174
175
176
177
178
179
180
181
182
183
184
185 @Deprecated
186 public boolean authenticate(String username, String privateKeyFile, String password) throws IOException {
187 return authenticate(
188 username,
189 privateKeyFile != null ? new File(privateKeyFile) : null,
190 password != null ? password.toCharArray() : null
191 );
192 }
193
194
195
196
197
198
199
200
201
202
203 public boolean authenticate(String username, File privateKeyFile, char[] password) throws IOException {
204 if (sshConnection.isAuthMethodAvailable(username, "publickey")) {
205 return sshConnection.authenticateWithPublicKey(
206 username,
207 privateKeyFile,
208 password != null ? String.valueOf(password) : null
209 );
210 }
211
212 return false;
213 }
214
215
216
217
218
219
220
221
222
223
224 @Deprecated
225 public boolean authenticate(String username, String password) throws IOException {
226 return authenticate(username, password != null ? password.toCharArray() : null);
227 }
228
229
230
231
232
233
234
235
236
237 public boolean authenticate(String username, char[] password) throws IOException {
238
239
240 if (
241 sshConnection.isAuthMethodAvailable(username, "password") &&
242 sshConnection.authenticateWithPassword(username, password != null ? String.valueOf(password) : null)
243 ) {
244 return true;
245 }
246
247
248 if (sshConnection.isAuthMethodAvailable(username, "keyboard-interactive")) {
249 return sshConnection.authenticateWithKeyboardInteractive(
250 username,
251 new InteractiveCallback() {
252 @Override
253 public String[] replyToChallenge(
254 String name,
255 String instruction,
256 int numPrompts,
257 String[] prompt,
258 boolean[] echo
259 ) throws Exception {
260
261 String[] challengeResponse = new String[numPrompts];
262 for (int i = 0; i < numPrompts; i++) {
263
264
265
266
267 if (echo[i]) {
268 challengeResponse[i] = username;
269 } else {
270 challengeResponse[i] = password != null ? String.valueOf(password) : null;
271 }
272 }
273 return challengeResponse;
274 }
275 }
276 );
277 }
278
279
280 return false;
281 }
282
283
284
285
286
287
288
289
290 public boolean authenticate(String username) throws IOException {
291 return sshConnection.authenticateWithNone(username);
292 }
293
294
295
296
297
298
299
300
301
302 public String readFileAttributes(String filePath) throws IOException {
303
304 checkIfAuthenticated();
305
306
307 SFTPv3Client sftpClient = new SFTPv3Client(sshConnection);
308
309
310 SFTPv3FileAttributes fileAttributes = sftpClient.stat(filePath);
311
312
313 String fileType;
314 if (fileAttributes.isRegularFile()) {
315 fileType = "FILE";
316 } else if (fileAttributes.isDirectory()) {
317 fileType = "DIR";
318 } else if (fileAttributes.isSymlink()) {
319 fileType = "LINK";
320 } else {
321 fileType = "UNKNOWN";
322 }
323
324
325 StringBuilder pslFileResult = new StringBuilder();
326 pslFileResult
327 .append(fileAttributes.mtime.toString())
328 .append("\t")
329 .append(fileAttributes.atime.toString())
330 .append("\t-\t")
331 .append(Integer.toString(fileAttributes.permissions & 0000777, 8))
332 .append("\t")
333 .append(fileAttributes.size.toString())
334 .append("\t-\t")
335 .append(fileType)
336 .append("\t")
337 .append(fileAttributes.uid.toString())
338 .append("\t")
339 .append(fileAttributes.gid.toString())
340 .append("\t")
341 .append(sftpClient.canonicalPath(filePath));
342
343
344 sftpClient.close();
345
346
347 return pslFileResult.toString();
348 }
349
350
351
352
353
354
355
356
357 public long fileSize(final String filePath) throws IOException {
358 SFTPv3Client sftpClient = null;
359 try {
360
361 checkIfAuthenticated();
362
363
364 sftpClient = new SFTPv3Client(sshConnection);
365
366
367 final SFTPv3FileAttributes attributes = sftpClient.stat(filePath);
368
369 return attributes.size;
370 } finally {
371
372 if (sftpClient != null) {
373 sftpClient.close();
374 }
375 }
376 }
377
378
379
380
381 public static class FileEntry {
382
383
384
385
386 public final String path;
387
388
389
390
391 public final long size;
392
393
394
395
396 public final long mtime;
397
398
399
400
401
402
403
404
405 public FileEntry(final String path, final long size, final long mtime) {
406 this.path = path;
407 this.size = size;
408 this.mtime = mtime;
409 }
410 }
411
412
413
414
415
416
417
418 private static Pattern compileMask(final String regExpMask) {
419 return regExpMask != null && !regExpMask.isEmpty()
420 ? Pattern.compile(regExpMask, Pattern.CASE_INSENSITIVE)
421 : DEFAULT_MASK_PATTERN;
422 }
423
424
425
426
427
428
429
430 private static String stripTrailingSlash(final String remoteDirectoryPath) {
431 return remoteDirectoryPath.endsWith("/")
432 ? remoteDirectoryPath.substring(0, remoteDirectoryPath.length() - 1)
433 : remoteDirectoryPath;
434 }
435
436
437
438
439
440
441
442
443
444
445 private static SFTPv3FileAttributes followSymlink(
446 final SFTPv3Client sftpClient,
447 final String path,
448 final SFTPv3DirectoryEntry entry
449 ) throws IOException {
450 if (!entry.attributes.isSymlink()) {
451 return entry.attributes;
452 }
453 try {
454 return sftpClient.stat(path);
455 } catch (SFTPException e) {
456 return null;
457 }
458 }
459
460 private void listSubDirectory(
461 SFTPv3Client sftpClient,
462 String remoteDirectoryPath,
463 Pattern fileMaskPattern,
464 boolean includeSubfolders,
465 int depth,
466 List<FileEntry> entries
467 ) throws IOException {
468 if (depth > 15) {
469 return;
470 }
471
472 final String directoryPath = stripTrailingSlash(remoteDirectoryPath);
473 for (SFTPv3DirectoryEntry entry : sftpClient.ls(remoteDirectoryPath)) {
474 final String filename = entry.filename;
475 if (filename.equals(".") || filename.equals("..")) {
476 continue;
477 }
478
479 final String filePath = directoryPath + "/" + filename;
480
481
482 if (entry.attributes.isDirectory()) {
483 if (includeSubfolders) {
484 listSubDirectory(sftpClient, filePath, fileMaskPattern, includeSubfolders, depth + 1, entries);
485 }
486 continue;
487 }
488
489 if (!fileMaskPattern.matcher(filename).find()) {
490 continue;
491 }
492
493 final SFTPv3FileAttributes attributes = followSymlink(sftpClient, filePath, entry);
494 if (attributes != null && attributes.isRegularFile()) {
495 entries.add(new FileEntry(filePath, attributes.size, attributes.mtime));
496 }
497 }
498 }
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514 public List<FileEntry> listFiles(String remoteDirectoryPath, String regExpFileMask, boolean includeSubfolders)
515 throws IOException {
516 checkIfAuthenticated();
517
518 final List<FileEntry> entries = new ArrayList<>();
519 final SFTPv3Client sftpClient = new SFTPv3Client(sshConnection);
520 try {
521 listSubDirectory(sftpClient, remoteDirectoryPath, compileMask(regExpFileMask), includeSubfolders, 1, entries);
522 } finally {
523 sftpClient.close();
524 }
525 return entries;
526 }
527
528
529
530
531
532
533
534
535
536
537
538
539
540 public List<String> listSubdirectories(final String remoteDirectoryPath, final String regExpMask) throws IOException {
541 checkIfAuthenticated();
542
543 final Pattern maskPattern = compileMask(regExpMask);
544 final String directoryPath = stripTrailingSlash(remoteDirectoryPath);
545 final List<String> subdirectories = new ArrayList<>();
546 final SFTPv3Client sftpClient = new SFTPv3Client(sshConnection);
547 try {
548 for (SFTPv3DirectoryEntry entry : sftpClient.ls(remoteDirectoryPath)) {
549 final String name = entry.filename;
550 if (name.equals(".") || name.equals("..") || !maskPattern.matcher(name).find()) {
551 continue;
552 }
553
554 final String path = directoryPath + "/" + name;
555 final SFTPv3FileAttributes attributes = followSymlink(sftpClient, path, entry);
556 if (attributes != null && attributes.isDirectory()) {
557 subdirectories.add(path);
558 }
559 }
560 } finally {
561 sftpClient.close();
562 }
563 return subdirectories;
564 }
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583 public String readFile(String remoteFilePath, Long readOffset, Integer readSize) throws IOException {
584 checkIfAuthenticated();
585
586
587 SFTPv3Client sftpClient = new SFTPv3Client(sshConnection);
588
589
590 long offset = 0;
591 if (readOffset != null) {
592 offset = readOffset;
593 }
594
595
596 SFTPv3FileHandle handle = sftpClient.openFileRO(remoteFilePath);
597
598
599 int remainingBytes;
600 if (readSize == null) {
601
602 SFTPv3FileAttributes attributes = sftpClient.fstat(handle);
603 if (attributes == null) {
604 throw new IOException("Couldn't find file " + remoteFilePath + " and get its attributes");
605 }
606 remainingBytes = (int) (attributes.size - offset);
607 if (remainingBytes < 0) {
608 remainingBytes = 0;
609 }
610 } else {
611 remainingBytes = readSize;
612 }
613
614
615 OutputStream out = new ByteArrayOutputStream();
616 byte[] readBuffer = new byte[READ_BUFFER_SIZE];
617 int bytesRead;
618 int bufferSize;
619
620
621 while (remainingBytes > 0) {
622
623
624 if (remainingBytes < READ_BUFFER_SIZE) {
625 bufferSize = remainingBytes;
626 } else {
627 bufferSize = READ_BUFFER_SIZE;
628 }
629
630
631 bytesRead = sftpClient.read(handle, offset, readBuffer, 0, bufferSize);
632
633
634
635 if (bytesRead < 0) {
636 break;
637 }
638
639
640 out.write(readBuffer, 0, bytesRead);
641
642
643 remainingBytes -= bytesRead;
644 offset += bytesRead;
645 }
646
647
648
649 sftpClient.closeFile(handle);
650
651
652 sftpClient.close();
653
654
655 return out.toString();
656 }
657
658
659
660
661
662
663
664
665 public void removeFile(String[] remoteFilePathArray) throws IOException {
666 checkIfAuthenticated();
667
668
669 SFTPv3Client sftpClient = null;
670 try {
671 sftpClient = new SFTPv3Client(sshConnection);
672
673
674 for (String remoteFilePath : remoteFilePathArray) {
675 sftpClient.rm(remoteFilePath);
676 }
677 } catch (IOException e) {
678
679
680
681 Session rmSession = null;
682 try {
683 for (String remoteFilePath : remoteFilePathArray) {
684 rmSession = sshConnection.openSession();
685 rmSession.execCommand("/usr/bin/rm -f \"" + remoteFilePath + "\"");
686 rmSession.waitForCondition(ChannelCondition.CLOSED | ChannelCondition.EOF, 5000);
687 }
688 } catch (IOException e1) {
689 throw e1;
690 } finally {
691 if (rmSession != null) {
692 rmSession.close();
693 }
694 }
695 } finally {
696
697 if (sftpClient != null) {
698 sftpClient.close();
699 }
700 }
701 }
702
703
704
705
706
707
708
709
710 public void removeFile(String remoteFilePath) throws IOException {
711 removeFile(new String[] { remoteFilePath });
712 }
713
714
715
716
717
718
719
720 public static class CommandResult {
721
722
723
724
725 public boolean success = true;
726
727
728
729
730
731 public float executionTime = 0;
732
733
734
735
736
737 public Integer exitStatus = null;
738
739
740
741
742 public String result = "";
743 }
744
745
746
747
748
749
750
751
752
753 public CommandResult executeCommand(String command) throws IOException {
754 return executeCommand(command, 0);
755 }
756
757
758
759
760
761
762
763
764
765
766 public CommandResult executeCommand(String command, int timeout) throws IOException {
767 openSession();
768
769 InputStream stdout = sshSession.getStdout();
770 InputStream stderr = sshSession.getStderr();
771
772
773
774
775 CommandResult commandResult = new CommandResult();
776
777
778 try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
779
780 long startTime = System.currentTimeMillis();
781 long timeoutTime;
782 if (timeout > 0) {
783 timeoutTime = startTime + timeout;
784 } else {
785
786 timeoutTime = Long.MAX_VALUE;
787 }
788
789
790 sshSession.execCommand(command);
791
792 int waitForCondition = 0;
793 long currentTime;
794
795 while (
796 !hasSessionClosed(waitForCondition) &&
797 !hasEndOfFileSession(waitForCondition) &&
798 ((currentTime = System.currentTimeMillis()) < timeoutTime)
799 ) {
800
801 waitForCondition = waitForNewData(Math.min(timeoutTime - currentTime, 5000));
802
803
804 if (hasStdoutData(waitForCondition)) {
805 transferAllBytes(stdout, output);
806 }
807
808 if (hasStderrData(waitForCondition)) {
809 transferAllBytes(stderr, output);
810 }
811 }
812
813
814
815 currentTime = System.currentTimeMillis();
816 if (currentTime >= timeoutTime) {
817
818
819
820 commandResult.success = false;
821 commandResult.result = "Timeout (" + timeout / 1000 + " seconds)";
822 } else {
823
824
825
826 commandResult.executionTime = (currentTime - startTime) / 1000;
827
828
829 waitForCondition = sshSession.waitForCondition(ChannelCondition.EXIT_STATUS, 5000);
830 if ((waitForCondition & ChannelCondition.EXIT_STATUS) != 0) {
831 commandResult.exitStatus = sshSession.getExitStatus();
832 }
833
834
835 commandResult.result = new String(output.toByteArray(), charset);
836 }
837 }
838
839
840 return commandResult;
841 }
842
843
844
845
846
847
848
849
850
851
852 public void interactiveSession(InputStream in, OutputStream out) throws IOException, InterruptedException {
853 openSession();
854
855 openTerminal();
856
857
858 BufferedReader inputReader = new BufferedReader(new InputStreamReader(in));
859 OutputStream outputWriter = sshSession.getStdin();
860 Thread stdinPipeThread = new Thread() {
861 @Override
862 public void run() {
863 try {
864 String line;
865 while ((line = inputReader.readLine()) != null) {
866 outputWriter.write(line.getBytes());
867 outputWriter.write('\n');
868 }
869 } catch (Exception e) {
870
871 }
872
873
874 sshSession.close();
875 }
876 };
877 stdinPipeThread.setDaemon(true);
878 stdinPipeThread.start();
879
880
881 InputStream stdout = sshSession.getStdout();
882 InputStream stderr = sshSession.getStderr();
883
884 int waitForCondition = 0;
885 while (!hasSessionClosed(waitForCondition) && !hasEndOfFileSession(waitForCondition)) {
886
887 waitForCondition = waitForNewData(5000L);
888
889
890 if (hasStdoutData(waitForCondition)) {
891 transferAllBytes(stdout, out);
892 }
893
894 if (hasStderrData(waitForCondition)) {
895 transferAllBytes(stderr, out);
896 }
897 }
898
899
900
901 if (stdinPipeThread.isAlive()) {
902 stdinPipeThread.interrupt();
903 }
904 }
905
906
907
908
909
910
911
912
913
914
915 public void scp(String localFilePath, String remoteFilename, String remoteDirectory, String fileMode)
916 throws IOException {
917 checkIfAuthenticated();
918
919
920 SCPClient scpClient = new SCPClient(sshConnection);
921
922
923 scpClient.put(localFilePath, remoteFilename, remoteDirectory, fileMode);
924 }
925
926
927
928
929
930
931 public void openSession() throws IOException {
932 checkIfConnected();
933 checkIfAuthenticated();
934
935
936 sshSession = getSshConnection().openSession();
937 }
938
939
940
941
942
943
944
945 public void openTerminal() throws IOException {
946 checkIfConnected();
947 checkIfAuthenticated();
948 checkIfSessionOpened();
949
950 getSshSession().requestPTY("dumb", 10000, 24, 640, 480, new byte[] { 53, 0, 0, 0, 0, 0 });
951 getSshSession().startShell();
952 }
953
954
955
956
957
958
959
960 public void write(final String text) throws IOException {
961 if (text == null || text.isEmpty()) {
962 return;
963 }
964
965 checkIfConnected();
966 checkIfAuthenticated();
967 checkIfSessionOpened();
968
969 Utils.checkNonNullField(charset, "charset");
970
971 final OutputStream outputStream = getSshSession().getStdin();
972 Utils.checkNonNullField(outputStream, "Stdin");
973
974
975 final String[] split = text.split("\\R", -1);
976 if (split.length == 1) {
977 outputStream.write(text.getBytes(charset));
978 } else {
979 for (int i = 0; i < split.length; i++) {
980 if (split[i].length() != 0) {
981 outputStream.write(split[i].getBytes(charset));
982 }
983
984 if (i < split.length - 1) {
985 outputStream.write('\n');
986 }
987 }
988 }
989
990 outputStream.flush();
991 }
992
993
994
995
996
997
998
999
1000
1001
1002 public Optional<String> read(final int size, final int timeout) throws IOException {
1003 Utils.checkArgumentNotZeroOrNegative(timeout, "timeout");
1004
1005 checkIfConnected();
1006 checkIfAuthenticated();
1007 checkIfSessionOpened();
1008
1009 Utils.checkNonNullField(charset, "charset");
1010
1011 final InputStream stdout = getSshSession().getStdout();
1012 final InputStream stderr = getSshSession().getStderr();
1013 Utils.checkNonNullField(stdout, "stdout");
1014 Utils.checkNonNullField(stderr, "stderr");
1015
1016 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
1017
1018 final int waitForCondition = waitForNewData(timeout * 1000L);
1019
1020 final boolean stdoutData = hasStdoutData(waitForCondition);
1021 final boolean stderrData = hasStderrData(waitForCondition);
1022
1023
1024 int stdoutRead = 0;
1025 if (stdoutData) {
1026 stdoutRead = transferBytes(stdout, byteArrayOutputStream, size);
1027 if (size > 0 && stdoutRead >= size) {
1028 return Optional.of(new String(byteArrayOutputStream.toByteArray(), charset));
1029 }
1030 }
1031
1032
1033 if (stderrData) {
1034 transferBytes(stderr, byteArrayOutputStream, size - stdoutRead);
1035 }
1036
1037 return stdoutData || stderrData
1038 ? Optional.of(new String(byteArrayOutputStream.toByteArray(), charset))
1039 : Optional.empty();
1040 }
1041 }
1042
1043
1044
1045
1046
1047
1048
1049 static boolean hasTimeoutSession(final int waitForCondition) {
1050 return (waitForCondition & ChannelCondition.TIMEOUT) != 0;
1051 }
1052
1053
1054
1055
1056
1057
1058
1059 static boolean hasEndOfFileSession(final int waitForCondition) {
1060 return (waitForCondition & ChannelCondition.EOF) != 0;
1061 }
1062
1063
1064
1065
1066
1067
1068
1069 static boolean hasSessionClosed(final int waitForCondition) {
1070 return (waitForCondition & ChannelCondition.CLOSED) != 0;
1071 }
1072
1073
1074
1075
1076
1077
1078
1079 static boolean hasStdoutData(final int waitForCondition) {
1080 return (waitForCondition & ChannelCondition.STDOUT_DATA) != 0;
1081 }
1082
1083
1084
1085
1086
1087
1088
1089 static boolean hasStderrData(final int waitForCondition) {
1090 return (waitForCondition & ChannelCondition.STDERR_DATA) != 0;
1091 }
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103 int waitForNewData(final long timeout) {
1104 return sshSession.waitForCondition(
1105 ChannelCondition.STDOUT_DATA | ChannelCondition.STDERR_DATA | ChannelCondition.EOF | ChannelCondition.CLOSED,
1106 timeout
1107 );
1108 }
1109
1110
1111
1112
1113 void checkIfConnected() {
1114 if (getSshConnection() == null) {
1115 throw new IllegalStateException("Connection is required first");
1116 }
1117 }
1118
1119
1120
1121
1122 void checkIfAuthenticated() {
1123 if (getSshConnection() == null || !getSshConnection().isAuthenticationComplete()) {
1124 throw new IllegalStateException("Authentication is required first");
1125 }
1126 }
1127
1128
1129
1130
1131 public void checkIfSessionOpened() {
1132 if (getSshSession() == null) {
1133 throw new IllegalStateException("SSH session should be opened first");
1134 }
1135 }
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145 static int transferAllBytes(final InputStream inputStream, final OutputStream outputStream) throws IOException {
1146 return transferBytes(inputStream, outputStream, -1);
1147 }
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 static int transferBytes(final InputStream inputStream, final OutputStream outputStream, final int size)
1159 throws IOException {
1160 final int bufferSize = size > 0 && size < READ_BUFFER_SIZE ? size : READ_BUFFER_SIZE;
1161 final byte[] buffer = new byte[bufferSize];
1162
1163 int total = 0;
1164 int bytesRead = 0;
1165
1166 while (inputStream.available() > 0 && (bytesRead = inputStream.read(buffer)) > 0) {
1167 final int bytesCopy = Math.min(bytesRead, READ_BUFFER_SIZE);
1168
1169 outputStream.write(Arrays.copyOf(buffer, bytesCopy));
1170 outputStream.flush();
1171
1172 total += bytesRead;
1173
1174 if (size > 0 && total >= size) {
1175 return total;
1176 }
1177 }
1178 return total;
1179 }
1180
1181 Connection getSshConnection() {
1182 return sshConnection;
1183 }
1184
1185 Session getSshSession() {
1186 return sshSession;
1187 }
1188 }