aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/com/trilead/ssh2/Connection.java
blob: 163fdb509715003dc5ff3a5860b70d1cd3f3a1cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
package com.trilead.ssh2;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketTimeoutException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Set;
import java.util.Vector;

import com.trilead.ssh2.auth.AuthenticationManager;
import com.trilead.ssh2.channel.ChannelManager;
import com.trilead.ssh2.crypto.CryptoWishList;
import com.trilead.ssh2.crypto.cipher.BlockCipherFactory;
import com.trilead.ssh2.crypto.digest.MAC;
import com.trilead.ssh2.log.Logger;
import com.trilead.ssh2.packets.PacketIgnore;
import com.trilead.ssh2.transport.KexManager;
import com.trilead.ssh2.transport.TransportManager;
import com.trilead.ssh2.util.TimeoutService;
import com.trilead.ssh2.util.TimeoutService.TimeoutToken;

/**
 * A <code>Connection</code> is used to establish an encrypted TCP/IP
 * connection to a SSH-2 server.
 * <p>
 * Typically, one
 * <ol>
 * <li>creates a {@link #Connection(String) Connection} object.</li>
 * <li>calls the {@link #connect() connect()} method.</li>
 * <li>calls some of the authentication methods (e.g.,
 * {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}).</li>
 * <li>calls one or several times the {@link #openSession() openSession()}
 * method.</li>
 * <li>finally, one must close the connection and release resources with the
 * {@link #close() close()} method.</li>
 * </ol>
 * 
 * @author Christian Plattner, plattner@trilead.com
 * @version $Id: Connection.java,v 1.3 2008/04/01 12:38:09 cplattne Exp $
 */

public class Connection
{
	/**
	 * The identifier presented to the SSH-2 server.
	 */
	public final static String identification = "TrileadSSH2Java_213";

	/**
	 * Will be used to generate all random data needed for the current
	 * connection. Note: SecureRandom.nextBytes() is thread safe.
	 */
	private SecureRandom generator;

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @return The list of supported cipher algorithms by this implementation.
	 */
	public static synchronized String[] getAvailableCiphers()
	{
		return BlockCipherFactory.getDefaultCipherList();
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @return The list of supported MAC algorthims by this implementation.
	 */
	public static synchronized String[] getAvailableMACs()
	{
		return MAC.getMacList();
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @return The list of supported server host key algorthims by this
	 *         implementation.
	 */
	public static synchronized String[] getAvailableServerHostKeyAlgorithms()
	{
		return KexManager.getDefaultServerHostkeyAlgorithmList();
	}

	private AuthenticationManager am;

	private boolean authenticated = false;
	private boolean compression = false;
	private ChannelManager cm;

	private CryptoWishList cryptoWishList = new CryptoWishList();

	private DHGexParameters dhgexpara = new DHGexParameters();

	private final String hostname;

	private final int port;

	private TransportManager tm;

	private boolean tcpNoDelay = false;

	private ProxyData proxyData = null;

	private Vector<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>();

	/**
	 * Prepares a fresh <code>Connection</code> object which can then be used
	 * to establish a connection to the specified SSH-2 server.
	 * <p>
	 * Same as {@link #Connection(String, int) Connection(hostname, 22)}.
	 * 
	 * @param hostname
	 *            the hostname of the SSH-2 server.
	 */
	public Connection(String hostname)
	{
		this(hostname, 22);
	}

	/**
	 * Prepares a fresh <code>Connection</code> object which can then be used
	 * to establish a connection to the specified SSH-2 server.
	 * 
	 * @param hostname
	 *            the host where we later want to connect to.
	 * @param port
	 *            port on the server, normally 22.
	 */
	public Connection(String hostname, int port)
	{
		this.hostname = hostname;
		this.port = port;
	}

	/**
	 * After a successful connect, one has to authenticate oneself. This method
	 * is based on DSA (it uses DSA to sign a challenge sent by the server).
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If the server does not accept the request (or if further
	 * authentication steps are needed), <code>false</code> is returned and
	 * one can retry either by using this or any other authentication method
	 * (use the <code>getRemainingAuthMethods</code> method to get a list of
	 * the remaining possible methods).
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param pem
	 *            A <code>String</code> containing the DSA private key of the
	 *            user in OpenSSH key format (PEM, you can't miss the
	 *            "-----BEGIN DSA PRIVATE KEY-----" tag). The string may contain
	 *            linefeeds.
	 * @param password
	 *            If the PEM string is 3DES encrypted ("DES-EDE3-CBC"), then you
	 *            must specify the password. Otherwise, this argument will be
	 *            ignored and can be set to <code>null</code>.
	 * 
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 * 
	 * @deprecated You should use one of the
	 *             {@link #authenticateWithPublicKey(String, File, String) authenticateWithPublicKey()}
	 *             methods, this method is just a wrapper for it and will
	 *             disappear in future builds.
	 * 
	 */
	public synchronized boolean authenticateWithDSA(String user, String pem, String password) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		if (pem == null)
			throw new IllegalArgumentException("pem argument is null");

		authenticated = am.authenticatePublicKey(user, pem.toCharArray(), password, getOrCreateSecureRND());

		return authenticated;
	}

	/**
	 * A wrapper that calls
	 * {@link #authenticateWithKeyboardInteractive(String, String[], InteractiveCallback)
	 * authenticateWithKeyboardInteractivewith} a <code>null</code> submethod
	 * list.
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param cb
	 *            An <code>InteractiveCallback</code> which will be used to
	 *            determine the responses to the questions asked by the server.
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithKeyboardInteractive(String user, InteractiveCallback cb)
			throws IOException
	{
		return authenticateWithKeyboardInteractive(user, null, cb);
	}

	/**
	 * After a successful connect, one has to authenticate oneself. This method
	 * is based on "keyboard-interactive", specified in
	 * draft-ietf-secsh-auth-kbdinteract-XX. Basically, you have to define a
	 * callback object which will be feeded with challenges generated by the
	 * server. Answers are then sent back to the server. It is possible that the
	 * callback will be called several times during the invocation of this
	 * method (e.g., if the server replies to the callback's answer(s) with
	 * another challenge...)
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If the server does not accept the request (or if further
	 * authentication steps are needed), <code>false</code> is returned and
	 * one can retry either by using this or any other authentication method
	 * (use the <code>getRemainingAuthMethods</code> method to get a list of
	 * the remaining possible methods).
	 * <p>
	 * Note: some SSH servers advertise "keyboard-interactive", however, any
	 * interactive request will be denied (without having sent any challenge to
	 * the client).
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param submethods
	 *            An array of submethod names, see
	 *            draft-ietf-secsh-auth-kbdinteract-XX. May be <code>null</code>
	 *            to indicate an empty list.
	 * @param cb
	 *            An <code>InteractiveCallback</code> which will be used to
	 *            determine the responses to the questions asked by the server.
	 * 
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithKeyboardInteractive(String user, String[] submethods,
			InteractiveCallback cb) throws IOException
	{
		if (cb == null)
			throw new IllegalArgumentException("Callback may not ne NULL!");

		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		authenticated = am.authenticateInteractive(user, submethods, cb);

		return authenticated;
	}

	/**
	 * After a successful connect, one has to authenticate oneself. This method
	 * sends username and password to the server.
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If the server does not accept the request (or if further
	 * authentication steps are needed), <code>false</code> is returned and
	 * one can retry either by using this or any other authentication method
	 * (use the <code>getRemainingAuthMethods</code> method to get a list of
	 * the remaining possible methods).
	 * <p>
	 * Note: if this method fails, then please double-check that it is actually
	 * offered by the server (use
	 * {@link #getRemainingAuthMethods(String) getRemainingAuthMethods()}.
	 * <p>
	 * Often, password authentication is disabled, but users are not aware of
	 * it. Many servers only offer "publickey" and "keyboard-interactive".
	 * However, even though "keyboard-interactive" *feels* like password
	 * authentication (e.g., when using the putty or openssh clients) it is
	 * *not* the same mechanism.
	 * 
	 * @param user
	 * @param password
	 * @return if the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithPassword(String user, String password) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		if (password == null)
			throw new IllegalArgumentException("password argument is null");

		authenticated = am.authenticatePassword(user, password);

		return authenticated;
	}

	/**
	 * After a successful connect, one has to authenticate oneself. This method
	 * can be used to explicitly use the special "none" authentication method
	 * (where only a username has to be specified).
	 * <p>
	 * Note 1: The "none" method may always be tried by clients, however as by
	 * the specs, the server will not explicitly announce it. In other words,
	 * the "none" token will never show up in the list returned by
	 * {@link #getRemainingAuthMethods(String)}.
	 * <p>
	 * Note 2: no matter which one of the authenticateWithXXX() methods you
	 * call, the library will always issue exactly one initial "none"
	 * authentication request to retrieve the initially allowed list of
	 * authentication methods by the server. Please read RFC 4252 for the
	 * details.
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If further authentication steps are needed, <code>false</code>
	 * is returned and one can retry by any other authentication method (use the
	 * <code>getRemainingAuthMethods</code> method to get a list of the
	 * remaining possible methods).
	 * 
	 * @param user
	 * @return if the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithNone(String user) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		/* Trigger the sending of the PacketUserauthRequestNone packet */
		/* (if not already done) */

		authenticated = am.authenticateNone(user);

		return authenticated;
	}

	/**
	 * After a successful connect, one has to authenticate oneself. The
	 * authentication method "publickey" works by signing a challenge sent by
	 * the server. The signature is either DSA or RSA based - it just depends on
	 * the type of private key you specify, either a DSA or RSA private key in
	 * PEM format. And yes, this is may seem to be a little confusing, the
	 * method is called "publickey" in the SSH-2 protocol specification, however
	 * since we need to generate a signature, you actually have to supply a
	 * private key =).
	 * <p>
	 * The private key contained in the PEM file may also be encrypted
	 * ("Proc-Type: 4,ENCRYPTED"). The library supports DES-CBC and DES-EDE3-CBC
	 * encryption, as well as the more exotic PEM encrpytions AES-128-CBC,
	 * AES-192-CBC and AES-256-CBC.
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If the server does not accept the request (or if further
	 * authentication steps are needed), <code>false</code> is returned and
	 * one can retry either by using this or any other authentication method
	 * (use the <code>getRemainingAuthMethods</code> method to get a list of
	 * the remaining possible methods).
	 * <p>
	 * NOTE PUTTY USERS: Event though your key file may start with
	 * "-----BEGIN..." it is not in the expected format. You have to convert it
	 * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
	 * from the Putty website). Simply load your key and then use the
	 * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param pemPrivateKey
	 *            A <code>char[]</code> containing a DSA or RSA private key of
	 *            the user in OpenSSH key format (PEM, you can't miss the
	 *            "-----BEGIN DSA PRIVATE KEY-----" or "-----BEGIN RSA PRIVATE
	 *            KEY-----" tag). The char array may contain
	 *            linebreaks/linefeeds.
	 * @param password
	 *            If the PEM structure is encrypted ("Proc-Type: 4,ENCRYPTED")
	 *            then you must specify a password. Otherwise, this argument
	 *            will be ignored and can be set to <code>null</code>.
	 * 
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithPublicKey(String user, char[] pemPrivateKey, String password)
			throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		if (pemPrivateKey == null)
			throw new IllegalArgumentException("pemPrivateKey argument is null");

		authenticated = am.authenticatePublicKey(user, pemPrivateKey, password, getOrCreateSecureRND());

		return authenticated;
	}
	
	/**
	 * After a successful connect, one has to authenticate oneself. The
	 * authentication method "publickey" works by signing a challenge sent by
	 * the server. The signature is either DSA or RSA based - it just depends on
	 * the type of private key you specify, either a DSA or RSA private key in
	 * PEM format. And yes, this is may seem to be a little confusing, the
	 * method is called "publickey" in the SSH-2 protocol specification, however
	 * since we need to generate a signature, you actually have to supply a
	 * private key =).
	 * <p>
	 * If the authentication phase is complete, <code>true</code> will be
	 * returned. If the server does not accept the request (or if further
	 * authentication steps are needed), <code>false</code> is returned and
	 * one can retry either by using this or any other authentication method
	 * (use the <code>getRemainingAuthMethods</code> method to get a list of
	 * the remaining possible methods).
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param key
	 *            A <code>RSAPrivateKey</code> or <code>DSAPrivateKey</code>
	 *            containing a DSA or RSA private key of
	 *            the user in Trilead object format.
	 * 
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithPublicKey(String user, KeyPair pair)
			throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		if (user == null)
			throw new IllegalArgumentException("user argument is null");

		if (pair == null)
			throw new IllegalArgumentException("Key pair argument is null");

		authenticated = am.authenticatePublicKey(user, pair, getOrCreateSecureRND());

		return authenticated;
	}
	/**
	 * A convenience wrapper function which reads in a private key (PEM format,
	 * either DSA or RSA) and then calls
	 * <code>authenticateWithPublicKey(String, char[], String)</code>.
	 * <p>
	 * NOTE PUTTY USERS: Event though your key file may start with
	 * "-----BEGIN..." it is not in the expected format. You have to convert it
	 * to the OpenSSH key format by using the "puttygen" tool (can be downloaded
	 * from the Putty website). Simply load your key and then use the
	 * "Conversions/Export OpenSSH key" functionality to get a proper PEM file.
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param pemFile
	 *            A <code>File</code> object pointing to a file containing a
	 *            DSA or RSA private key of the user in OpenSSH key format (PEM,
	 *            you can't miss the "-----BEGIN DSA PRIVATE KEY-----" or
	 *            "-----BEGIN RSA PRIVATE KEY-----" tag).
	 * @param password
	 *            If the PEM file is encrypted then you must specify the
	 *            password. Otherwise, this argument will be ignored and can be
	 *            set to <code>null</code>.
	 * 
	 * @return whether the connection is now authenticated.
	 * @throws IOException
	 */
	public synchronized boolean authenticateWithPublicKey(String user, File pemFile, String password)
			throws IOException
	{
		if (pemFile == null)
			throw new IllegalArgumentException("pemFile argument is null");

		char[] buff = new char[256];

		CharArrayWriter cw = new CharArrayWriter();

		FileReader fr = new FileReader(pemFile);

		while (true)
		{
			int len = fr.read(buff);
			if (len < 0)
				break;
			cw.write(buff, 0, len);
		}

		fr.close();

		return authenticateWithPublicKey(user, cw.toCharArray(), password);
	}

	/**
	 * Add a {@link ConnectionMonitor} to this connection. Can be invoked at any
	 * time, but it is best to add connection monitors before invoking
	 * <code>connect()</code> to avoid glitches (e.g., you add a connection
	 * monitor after a successful connect(), but the connection has died in the
	 * mean time. Then, your connection monitor won't be notified.)
	 * <p>
	 * You can add as many monitors as you like.
	 * 
	 * @see ConnectionMonitor
	 * 
	 * @param cmon
	 *            An object implementing the <code>ConnectionMonitor</code>
	 *            interface.
	 */
	public synchronized void addConnectionMonitor(ConnectionMonitor cmon)
	{
		if (cmon == null)
			throw new IllegalArgumentException("cmon argument is null");

		connectionMonitors.addElement(cmon);

		if (tm != null)
			tm.setConnectionMonitors(connectionMonitors);
	}

	/**
	 * Controls whether compression is used on the link or not.
	 * <p>
	 * Note: This can only be called before connect()
	 * @param enabled whether to enable compression
	 * @throws IOException
	 */
	public synchronized void setCompression(boolean enabled) throws IOException {
		if (tm != null)
			throw new IOException("Connection to " + hostname + " is already in connected state!");
		
		compression = enabled;
	}
	
	/**
	 * Close the connection to the SSH-2 server. All assigned sessions will be
	 * closed, too. Can be called at any time. Don't forget to call this once
	 * you don't need a connection anymore - otherwise the receiver thread may
	 * run forever.
	 */
	public synchronized void close()
	{
		Throwable t = new Throwable("Closed due to user request.");
		close(t, false);
	}

	private void close(Throwable t, boolean hard)
	{
		if (cm != null)
			cm.closeAllChannels();

		if (tm != null)
		{
			tm.close(t, hard == false);
			tm = null;
		}
		am = null;
		cm = null;
		authenticated = false;
	}

	/**
	 * Same as
	 * {@link #connect(ServerHostKeyVerifier, int, int) connect(null, 0, 0)}.
	 * 
	 * @return see comments for the
	 *         {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
	 *         method.
	 * @throws IOException
	 */
	public synchronized ConnectionInfo connect() throws IOException
	{
		return connect(null, 0, 0);
	}

	/**
	 * Same as
	 * {@link #connect(ServerHostKeyVerifier, int, int) connect(verifier, 0, 0)}.
	 * 
	 * @return see comments for the
	 *         {@link #connect(ServerHostKeyVerifier, int, int) connect(ServerHostKeyVerifier, int, int)}
	 *         method.
	 * @throws IOException
	 */
	public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier) throws IOException
	{
		return connect(verifier, 0, 0);
	}

	/**
	 * Connect to the SSH-2 server and, as soon as the server has presented its
	 * host key, use the
	 * {@link ServerHostKeyVerifier#verifyServerHostKey(String, int, String,
	 * byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method of the
	 * <code>verifier</code> to ask for permission to proceed. If
	 * <code>verifier</code> is <code>null</code>, then any host key will
	 * be accepted - this is NOT recommended, since it makes man-in-the-middle
	 * attackes VERY easy (somebody could put a proxy SSH server between you and
	 * the real server).
	 * <p>
	 * Note: The verifier will be called before doing any crypto calculations
	 * (i.e., diffie-hellman). Therefore, if you don't like the presented host
	 * key then no CPU cycles are wasted (and the evil server has less
	 * information about us).
	 * <p>
	 * However, it is still possible that the server presented a fake host key:
	 * the server cheated (typically a sign for a man-in-the-middle attack) and
	 * is not able to generate a signature that matches its host key. Don't
	 * worry, the library will detect such a scenario later when checking the
	 * signature (the signature cannot be checked before having completed the
	 * diffie-hellman exchange).
	 * <p>
	 * Note 2: The {@link ServerHostKeyVerifier#verifyServerHostKey(String, int,
	 * String, byte[]) ServerHostKeyVerifier.verifyServerHostKey()} method will
	 * *NOT* be called from the current thread, the call is being made from a
	 * background thread (there is a background dispatcher thread for every
	 * established connection).
	 * <p>
	 * Note 3: This method will block as long as the key exchange of the
	 * underlying connection has not been completed (and you have not specified
	 * any timeouts).
	 * <p>
	 * Note 4: If you want to re-use a connection object that was successfully
	 * connected, then you must call the {@link #close()} method before invoking
	 * <code>connect()</code> again.
	 * 
	 * @param verifier
	 *            An object that implements the {@link ServerHostKeyVerifier}
	 *            interface. Pass <code>null</code> to accept any server host
	 *            key - NOT recommended.
	 * 
	 * @param connectTimeout
	 *            Connect the underlying TCP socket to the server with the given
	 *            timeout value (non-negative, in milliseconds). Zero means no
	 *            timeout. If a proxy is being used (see
	 *            {@link #setProxyData(ProxyData)}), then this timeout is used
	 *            for the connection establishment to the proxy.
	 * 
	 * @param kexTimeout
	 *            Timeout for complete connection establishment (non-negative,
	 *            in milliseconds). Zero means no timeout. The timeout counts
	 *            from the moment you invoke the connect() method and is
	 *            cancelled as soon as the first key-exchange round has
	 *            finished. It is possible that the timeout event will be fired
	 *            during the invocation of the <code>verifier</code> callback,
	 *            but it will only have an effect after the
	 *            <code>verifier</code> returns.
	 * 
	 * @return A {@link ConnectionInfo} object containing the details of the
	 *         established connection.
	 * 
	 * @throws IOException
	 *             If any problem occurs, e.g., the server's host key is not
	 *             accepted by the <code>verifier</code> or there is problem
	 *             during the initial crypto setup (e.g., the signature sent by
	 *             the server is wrong).
	 *             <p>
	 *             In case of a timeout (either connectTimeout or kexTimeout) a
	 *             SocketTimeoutException is thrown.
	 *             <p>
	 *             An exception may also be thrown if the connection was already
	 *             successfully connected (no matter if the connection broke in
	 *             the mean time) and you invoke <code>connect()</code> again
	 *             without having called {@link #close()} first.
	 *             <p>
	 *             If a HTTP proxy is being used and the proxy refuses the
	 *             connection, then a {@link HTTPProxyException} may be thrown,
	 *             which contains the details returned by the proxy. If the
	 *             proxy is buggy and does not return a proper HTTP response,
	 *             then a normal IOException is thrown instead.
	 */
	public synchronized ConnectionInfo connect(ServerHostKeyVerifier verifier, int connectTimeout, int kexTimeout)
			throws IOException
	{
		final class TimeoutState
		{
			boolean isCancelled = false;
			boolean timeoutSocketClosed = false;
		}

		if (tm != null)
			throw new IOException("Connection to " + hostname + " is already in connected state!");

		if (connectTimeout < 0)
			throw new IllegalArgumentException("connectTimeout must be non-negative!");

		if (kexTimeout < 0)
			throw new IllegalArgumentException("kexTimeout must be non-negative!");

		final TimeoutState state = new TimeoutState();

		tm = new TransportManager(hostname, port);

		tm.setConnectionMonitors(connectionMonitors);

		// Don't offer compression if not requested
		if (!compression) {
			cryptoWishList.c2s_comp_algos = new String[] { "none" };
			cryptoWishList.s2c_comp_algos = new String[] { "none" };
		}
		
		/*
		 * Make sure that the runnable below will observe the new value of "tm"
		 * and "state" (the runnable will be executed in a different thread,
		 * which may be already running, that is why we need a memory barrier
		 * here). See also the comment in Channel.java if you are interested in
		 * the details.
		 * 
		 * OKOK, this is paranoid since adding the runnable to the todo list of
		 * the TimeoutService will ensure that all writes have been flushed
		 * before the Runnable reads anything (there is a synchronized block in
		 * TimeoutService.addTimeoutHandler).
		 */

		synchronized (tm)
		{
			/* We could actually synchronize on anything. */
		}

		try
		{
			TimeoutToken token = null;

			if (kexTimeout > 0)
			{
				final Runnable timeoutHandler = new Runnable()
				{
					public void run()
					{
						synchronized (state)
						{
							if (state.isCancelled)
								return;
							state.timeoutSocketClosed = true;
							tm.close(new SocketTimeoutException("The connect timeout expired"), false);
						}
					}
				};

				long timeoutHorizont = System.currentTimeMillis() + kexTimeout;

				token = TimeoutService.addTimeoutHandler(timeoutHorizont, timeoutHandler);
			}

			try
			{
				tm.initialize(cryptoWishList, verifier, dhgexpara, connectTimeout, getOrCreateSecureRND(), proxyData);
			}
			catch (SocketTimeoutException se)
			{
				throw (SocketTimeoutException) new SocketTimeoutException(
						"The connect() operation on the socket timed out.").initCause(se);
			}

			tm.setTcpNoDelay(tcpNoDelay);

			/* Wait until first KEX has finished */

			ConnectionInfo ci = tm.getConnectionInfo(1);

			/* Now try to cancel the timeout, if needed */

			if (token != null)
			{
				TimeoutService.cancelTimeoutHandler(token);

				/* Were we too late? */

				synchronized (state)
				{
					if (state.timeoutSocketClosed)
						throw new IOException("This exception will be replaced by the one below =)");
					/*
					 * Just in case the "cancelTimeoutHandler" invocation came
					 * just a little bit too late but the handler did not enter
					 * the semaphore yet - we can still stop it.
					 */
					state.isCancelled = true;
				}
			}

			return ci;
		}
		catch (SocketTimeoutException ste)
		{
			throw ste;
		}
		catch (IOException e1)
		{
			/* This will also invoke any registered connection monitors */
			close(new Throwable("There was a problem during connect."), false);

			synchronized (state)
			{
				/*
				 * Show a clean exception, not something like "the socket is
				 * closed!?!"
				 */
				if (state.timeoutSocketClosed)
					throw new SocketTimeoutException("The kexTimeout (" + kexTimeout + " ms) expired.");
			}

			/* Do not wrap a HTTPProxyException */
			if (e1 instanceof HTTPProxyException)
				throw e1;

			throw (IOException) new IOException("There was a problem while connecting to " + hostname + ":" + port)
					.initCause(e1);
		}
	}

	/**
	 * Creates a new {@link LocalPortForwarder}. A
	 * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive
	 * at a local port via the secure tunnel to another host (which may or may
	 * not be identical to the remote SSH-2 server).
	 * <p>
	 * This method must only be called after one has passed successfully the
	 * authentication step. There is no limit on the number of concurrent
	 * forwardings.
	 * 
	 * @param local_port
	 *            the local port the LocalPortForwarder shall bind to.
	 * @param host_to_connect
	 *            target address (IP or hostname)
	 * @param port_to_connect
	 *            target port
	 * @return A {@link LocalPortForwarder} object.
	 * @throws IOException
	 */
	public synchronized LocalPortForwarder createLocalPortForwarder(int local_port, String host_to_connect,
			int port_to_connect) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");

		return new LocalPortForwarder(cm, local_port, host_to_connect, port_to_connect);
	}

	/**
	 * Creates a new {@link LocalPortForwarder}. A
	 * <code>LocalPortForwarder</code> forwards TCP/IP connections that arrive
	 * at a local port via the secure tunnel to another host (which may or may
	 * not be identical to the remote SSH-2 server).
	 * <p>
	 * This method must only be called after one has passed successfully the
	 * authentication step. There is no limit on the number of concurrent
	 * forwardings.
	 * 
	 * @param addr
	 *            specifies the InetSocketAddress where the local socket shall
	 *            be bound to.
	 * @param host_to_connect
	 *            target address (IP or hostname)
	 * @param port_to_connect
	 *            target port
	 * @return A {@link LocalPortForwarder} object.
	 * @throws IOException
	 */
	public synchronized LocalPortForwarder createLocalPortForwarder(InetSocketAddress addr, String host_to_connect,
			int port_to_connect) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");

		return new LocalPortForwarder(cm, addr, host_to_connect, port_to_connect);
	}

	/**
	 * Creates a new {@link LocalStreamForwarder}. A
	 * <code>LocalStreamForwarder</code> manages an Input/Outputstream pair
	 * that is being forwarded via the secure tunnel into a TCP/IP connection to
	 * another host (which may or may not be identical to the remote SSH-2
	 * server).
	 * 
	 * @param host_to_connect
	 * @param port_to_connect
	 * @return A {@link LocalStreamForwarder} object.
	 * @throws IOException
	 */
	public synchronized LocalStreamForwarder createLocalStreamForwarder(String host_to_connect, int port_to_connect)
			throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot forward, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot forward, connection is not authenticated.");

		return new LocalStreamForwarder(cm, host_to_connect, port_to_connect);
	}

	/**
	 * Creates a new {@link DynamicPortForwarder}. A
	 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
	 * at a local port via the secure tunnel to another host that is chosen via
	 * the SOCKS protocol.
	 * <p>
	 * This method must only be called after one has passed successfully the
	 * authentication step. There is no limit on the number of concurrent
	 * forwardings.
	 * 
	 * @param local_port
	 * @return A {@link DynamicPortForwarder} object.
	 * @throws IOException
	 */
	public synchronized DynamicPortForwarder createDynamicPortForwarder(int local_port) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");

		return new DynamicPortForwarder(cm, local_port);
	}
	
	/**
	 * Creates a new {@link DynamicPortForwarder}. A
	 * <code>DynamicPortForwarder</code> forwards TCP/IP connections that arrive
	 * at a local port via the secure tunnel to another host that is chosen via
	 * the SOCKS protocol.
	 * <p>
	 * This method must only be called after one has passed successfully the
	 * authentication step. There is no limit on the number of concurrent
	 * forwardings.
	 * 
	 * @param addr
	 *            specifies the InetSocketAddress where the local socket shall
	 *            be bound to.
	 * @return A {@link DynamicPortForwarder} object.
	 * @throws IOException
	 */
	public synchronized DynamicPortForwarder createDynamicPortForwarder(InetSocketAddress addr) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot forward ports, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot forward ports, connection is not authenticated.");

		return new DynamicPortForwarder(cm, addr);
	}
	
	/**
	 * Create a very basic {@link SCPClient} that can be used to copy files
	 * from/to the SSH-2 server.
	 * <p>
	 * Works only after one has passed successfully the authentication step.
	 * There is no limit on the number of concurrent SCP clients.
	 * <p>
	 * Note: This factory method will probably disappear in the future.
	 * 
	 * @return A {@link SCPClient} object.
	 * @throws IOException
	 */
	public synchronized SCPClient createSCPClient() throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot create SCP client, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot create SCP client, connection is not authenticated.");

		return new SCPClient(this);
	}

	/**
	 * Force an asynchronous key re-exchange (the call does not block). The
	 * latest values set for MAC, Cipher and DH group exchange parameters will
	 * be used. If a key exchange is currently in progress, then this method has
	 * the only effect that the so far specified parameters will be used for the
	 * next (server driven) key exchange.
	 * <p>
	 * Note: This implementation will never start a key exchange (other than the
	 * initial one) unless you or the SSH-2 server ask for it.
	 * 
	 * @throws IOException
	 *             In case of any failure behind the scenes.
	 */
	public synchronized void forceKeyExchange() throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("You need to establish a connection first.");

		tm.forceKeyExchange(cryptoWishList, dhgexpara);
	}

	/**
	 * Returns the hostname that was passed to the constructor.
	 * 
	 * @return the hostname
	 */
	public synchronized String getHostname()
	{
		return hostname;
	}

	/**
	 * Returns the port that was passed to the constructor.
	 * 
	 * @return the TCP port
	 */
	public synchronized int getPort()
	{
		return port;
	}

	/**
	 * Returns a {@link ConnectionInfo} object containing the details of the
	 * connection. Can be called as soon as the connection has been established
	 * (successfully connected).
	 * 
	 * @return A {@link ConnectionInfo} object.
	 * @throws IOException
	 *             In case of any failure behind the scenes.
	 */
	public synchronized ConnectionInfo getConnectionInfo() throws IOException
	{
		if (tm == null)
			throw new IllegalStateException(
					"Cannot get details of connection, you need to establish a connection first.");
		return tm.getConnectionInfo(1);
	}

	/**
	 * After a successful connect, one has to authenticate oneself. This method
	 * can be used to tell which authentication methods are supported by the
	 * server at a certain stage of the authentication process (for the given
	 * username).
	 * <p>
	 * Note 1: the username will only be used if no authentication step was done
	 * so far (it will be used to ask the server for a list of possible
	 * authentication methods by sending the initial "none" request). Otherwise,
	 * this method ignores the user name and returns a cached method list (which
	 * is based on the information contained in the last negative server
	 * response).
	 * <p>
	 * Note 2: the server may return method names that are not supported by this
	 * implementation.
	 * <p>
	 * After a successful authentication, this method must not be called
	 * anymore.
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * 
	 * @return a (possibly emtpy) array holding authentication method names.
	 * @throws IOException
	 */
	public synchronized String[] getRemainingAuthMethods(String user) throws IOException
	{
		if (user == null)
			throw new IllegalArgumentException("user argument may not be NULL!");

		if (tm == null)
			throw new IllegalStateException("Connection is not established!");

		if (authenticated)
			throw new IllegalStateException("Connection is already authenticated!");

		if (am == null)
			am = new AuthenticationManager(tm);

		if (cm == null)
			cm = new ChannelManager(tm);

		return am.getRemainingMethods(user);
	}

	/**
	 * Determines if the authentication phase is complete. Can be called at any
	 * time.
	 * 
	 * @return <code>true</code> if no further authentication steps are
	 *         needed.
	 */
	public synchronized boolean isAuthenticationComplete()
	{
		return authenticated;
	}

	/**
	 * Returns true if there was at least one failed authentication request and
	 * the last failed authentication request was marked with "partial success"
	 * by the server. This is only needed in the rare case of SSH-2 server
	 * setups that cannot be satisfied with a single successful authentication
	 * request (i.e., multiple authentication steps are needed.)
	 * <p>
	 * If you are interested in the details, then have a look at RFC4252.
	 * 
	 * @return if the there was a failed authentication step and the last one
	 *         was marked as a "partial success".
	 */
	public synchronized boolean isAuthenticationPartialSuccess()
	{
		if (am == null)
			return false;

		return am.getPartialSuccess();
	}

	/**
	 * Checks if a specified authentication method is available. This method is
	 * actually just a wrapper for {@link #getRemainingAuthMethods(String)
	 * getRemainingAuthMethods()}.
	 * 
	 * @param user
	 *            A <code>String</code> holding the username.
	 * @param method
	 *            An authentication method name (e.g., "publickey", "password",
	 *            "keyboard-interactive") as specified by the SSH-2 standard.
	 * @return if the specified authentication method is currently available.
	 * @throws IOException
	 */
	public synchronized boolean isAuthMethodAvailable(String user, String method) throws IOException
	{
		if (method == null)
			throw new IllegalArgumentException("method argument may not be NULL!");

		String methods[] = getRemainingAuthMethods(user);

		for (int i = 0; i < methods.length; i++)
		{
			if (methods[i].compareTo(method) == 0)
				return true;
		}

		return false;
	}

	private final SecureRandom getOrCreateSecureRND()
	{
		if (generator == null)
			generator = new SecureRandom();

		return generator;
	}

	/**
	 * Open a new {@link Session} on this connection. Works only after one has
	 * passed successfully the authentication step. There is no limit on the
	 * number of concurrent sessions.
	 * 
	 * @return A {@link Session} object.
	 * @throws IOException
	 */
	public synchronized Session openSession() throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("Cannot open session, you need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("Cannot open session, connection is not authenticated.");

		return new Session(cm, getOrCreateSecureRND());
	}

	/**
	 * Send an SSH_MSG_IGNORE packet. This method will generate a random data
	 * attribute (length between 0 (invlusive) and 16 (exclusive) bytes,
	 * contents are random bytes).
	 * <p>
	 * This method must only be called once the connection is established.
	 * 
	 * @throws IOException
	 */
	public synchronized void sendIgnorePacket() throws IOException
	{
		SecureRandom rnd = getOrCreateSecureRND();

		byte[] data = new byte[rnd.nextInt(16)];
		rnd.nextBytes(data);

		sendIgnorePacket(data);
	}

	/**
	 * Send an SSH_MSG_IGNORE packet with the given data attribute.
	 * <p>
	 * This method must only be called once the connection is established.
	 * 
	 * @throws IOException
	 */
	public synchronized void sendIgnorePacket(byte[] data) throws IOException
	{
		if (data == null)
			throw new IllegalArgumentException("data argument must not be null.");

		if (tm == null)
			throw new IllegalStateException(
					"Cannot send SSH_MSG_IGNORE packet, you need to establish a connection first.");

		PacketIgnore pi = new PacketIgnore();
		pi.setData(data);

		tm.sendMessage(pi.getPayload());
	}

	/**
	 * Removes duplicates from a String array, keeps only first occurence of
	 * each element. Does not destroy order of elements; can handle nulls. Uses
	 * a very efficient O(N^2) algorithm =)
	 * 
	 * @param list
	 *            a String array.
	 * @return a cleaned String array.
	 */
	private String[] removeDuplicates(String[] list)
	{
		if ((list == null) || (list.length < 2))
			return list;

		String[] list2 = new String[list.length];

		int count = 0;

		for (int i = 0; i < list.length; i++)
		{
			boolean duplicate = false;

			String element = list[i];

			for (int j = 0; j < count; j++)
			{
				if (((element == null) && (list2[j] == null)) || ((element != null) && (element.equals(list2[j]))))
				{
					duplicate = true;
					break;
				}
			}

			if (duplicate)
				continue;

			list2[count++] = list[i];
		}

		if (count == list2.length)
			return list2;

		String[] tmp = new String[count];
		System.arraycopy(list2, 0, tmp, 0, count);

		return tmp;
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @param ciphers
	 */
	public synchronized void setClient2ServerCiphers(String[] ciphers)
	{
		if ((ciphers == null) || (ciphers.length == 0))
			throw new IllegalArgumentException();
		ciphers = removeDuplicates(ciphers);
		BlockCipherFactory.checkCipherList(ciphers);
		cryptoWishList.c2s_enc_algos = ciphers;
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @param macs
	 */
	public synchronized void setClient2ServerMACs(String[] macs)
	{
		if ((macs == null) || (macs.length == 0))
			throw new IllegalArgumentException();
		macs = removeDuplicates(macs);
		MAC.checkMacList(macs);
		cryptoWishList.c2s_mac_algos = macs;
	}

	/**
	 * Sets the parameters for the diffie-hellman group exchange. Unless you
	 * know what you are doing, you will never need this. Default values are
	 * defined in the {@link DHGexParameters} class.
	 * 
	 * @param dgp
	 *            {@link DHGexParameters}, non null.
	 * 
	 */
	public synchronized void setDHGexParameters(DHGexParameters dgp)
	{
		if (dgp == null)
			throw new IllegalArgumentException();

		dhgexpara = dgp;
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @param ciphers
	 */
	public synchronized void setServer2ClientCiphers(String[] ciphers)
	{
		if ((ciphers == null) || (ciphers.length == 0))
			throw new IllegalArgumentException();
		ciphers = removeDuplicates(ciphers);
		BlockCipherFactory.checkCipherList(ciphers);
		cryptoWishList.s2c_enc_algos = ciphers;
	}

	/**
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @param macs
	 */
	public synchronized void setServer2ClientMACs(String[] macs)
	{
		if ((macs == null) || (macs.length == 0))
			throw new IllegalArgumentException();

		macs = removeDuplicates(macs);
		MAC.checkMacList(macs);
		cryptoWishList.s2c_mac_algos = macs;
	}

	/**
	 * Define the set of allowed server host key algorithms to be used for the
	 * following key exchange operations.
	 * <p>
	 * Unless you know what you are doing, you will never need this.
	 * 
	 * @param algos
	 *            An array of allowed server host key algorithms. SSH-2 defines
	 *            <code>ssh-dss</code> and <code>ssh-rsa</code>. The
	 *            entries of the array must be ordered after preference, i.e.,
	 *            the entry at index 0 is the most preferred one. You must
	 *            specify at least one entry.
	 */
	public synchronized void setServerHostKeyAlgorithms(String[] algos)
	{
		if ((algos == null) || (algos.length == 0))
			throw new IllegalArgumentException();

		algos = removeDuplicates(algos);
		KexManager.checkServerHostkeyAlgorithmsList(algos);
		cryptoWishList.serverHostKeyAlgorithms = algos;
	}

	/**
	 * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm) on the
	 * underlying socket.
	 * <p>
	 * Can be called at any time. If the connection has not yet been established
	 * then the passed value will be stored and set after the socket has been
	 * set up. The default value that will be used is <code>false</code>.
	 * 
	 * @param enable
	 *            the argument passed to the <code>Socket.setTCPNoDelay()</code>
	 *            method.
	 * @throws IOException
	 */
	public synchronized void setTCPNoDelay(boolean enable) throws IOException
	{
		tcpNoDelay = enable;

		if (tm != null)
			tm.setTcpNoDelay(enable);
	}

	/**
	 * Used to tell the library that the connection shall be established through
	 * a proxy server. It only makes sense to call this method before calling
	 * the {@link #connect() connect()} method.
	 * <p>
	 * At the moment, only HTTP proxies are supported.
	 * <p>
	 * Note: This method can be called any number of times. The
	 * {@link #connect() connect()} method will use the value set in the last
	 * preceding invocation of this method.
	 * 
	 * @see HTTPProxyData
	 * 
	 * @param proxyData
	 *            Connection information about the proxy. If <code>null</code>,
	 *            then no proxy will be used (non surprisingly, this is also the
	 *            default).
	 */
	public synchronized void setProxyData(ProxyData proxyData)
	{
		this.proxyData = proxyData;
	}

	/**
	 * Request a remote port forwarding. If successful, then forwarded
	 * connections will be redirected to the given target address. You can
	 * cancle a requested remote port forwarding by calling
	 * {@link #cancelRemotePortForwarding(int) cancelRemotePortForwarding()}.
	 * <p>
	 * A call of this method will block until the peer either agreed or
	 * disagreed to your request-
	 * <p>
	 * Note 1: this method typically fails if you
	 * <ul>
	 * <li>pass a port number for which the used remote user has not enough
	 * permissions (i.e., port &lt; 1024)</li>
	 * <li>or pass a port number that is already in use on the remote server</li>
	 * <li>or if remote port forwarding is disabled on the server.</li>
	 * </ul>
	 * <p>
	 * Note 2: (from the openssh man page): By default, the listening socket on
	 * the server will be bound to the loopback interface only. This may be
	 * overriden by specifying a bind address. Specifying a remote bind address
	 * will only succeed if the server's <b>GatewayPorts</b> option is enabled
	 * (see sshd_config(5)).
	 * 
	 * @param bindAddress
	 *            address to bind to on the server:
	 *            <ul>
	 *            <li>"" means that connections are to be accepted on all
	 *            protocol families supported by the SSH implementation</li>
	 *            <li>"0.0.0.0" means to listen on all IPv4 addresses</li>
	 *            <li>"::" means to listen on all IPv6 addresses</li>
	 *            <li>"localhost" means to listen on all protocol families
	 *            supported by the SSH implementation on loopback addresses
	 *            only, [RFC3330] and RFC3513]</li>
	 *            <li>"127.0.0.1" and "::1" indicate listening on the loopback
	 *            interfaces for IPv4 and IPv6 respectively</li>
	 *            </ul>
	 * @param bindPort
	 *            port number to bind on the server (must be &gt; 0)
	 * @param targetAddress
	 *            the target address (IP or hostname)
	 * @param targetPort
	 *            the target port
	 * @throws IOException
	 */
	public synchronized void requestRemotePortForwarding(String bindAddress, int bindPort, String targetAddress,
			int targetPort) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("You need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("The connection is not authenticated.");

		if ((bindAddress == null) || (targetAddress == null) || (bindPort <= 0) || (targetPort <= 0))
			throw new IllegalArgumentException();

		cm.requestGlobalForward(bindAddress, bindPort, targetAddress, targetPort);
	}

	/**
	 * Cancel an earlier requested remote port forwarding. Currently active
	 * forwardings will not be affected (e.g., disrupted). Note that further
	 * connection forwarding requests may be received until this method has
	 * returned.
	 * 
	 * @param bindPort
	 *            the allocated port number on the server
	 * @throws IOException
	 *             if the remote side refuses the cancel request or another low
	 *             level error occurs (e.g., the underlying connection is
	 *             closed)
	 */
	public synchronized void cancelRemotePortForwarding(int bindPort) throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("You need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("The connection is not authenticated.");

		cm.requestCancelGlobalForward(bindPort);
	}

	/**
	 * Provide your own instance of SecureRandom. Can be used, e.g., if you want
	 * to seed the used SecureRandom generator manually.
	 * <p>
	 * The SecureRandom instance is used during key exchanges, public key
	 * authentication, x11 cookie generation and the like.
	 * 
	 * @param rnd
	 *            a SecureRandom instance
	 */
	public synchronized void setSecureRandom(SecureRandom rnd)
	{
		if (rnd == null)
			throw new IllegalArgumentException();

		this.generator = rnd;
	}

	/**
	 * Enable/disable debug logging. <b>Only do this when requested by Trilead
	 * support.</b>
	 * <p>
	 * For speed reasons, some static variables used to check whether debugging
	 * is enabled are not protected with locks. In other words, if you
	 * dynamicaly enable/disable debug logging, then some threads may still use
	 * the old setting. To be on the safe side, enable debugging before doing
	 * the <code>connect()</code> call.
	 * 
	 * @param enable
	 *            on/off
	 * @param logger
	 *            a {@link DebugLogger DebugLogger} instance, <code>null</code>
	 *            means logging using the simple logger which logs all messages
	 *            to to stderr. Ignored if enabled is <code>false</code>
	 */
	public synchronized void enableDebugging(boolean enable, DebugLogger logger)
	{
		Logger.enabled = enable;

		if (enable == false)
		{
			Logger.logger = null;
		}
		else
		{
			if (logger == null)
			{
				logger = new DebugLogger()
				{

					public void log(int level, String className, String message)
					{
						long now = System.currentTimeMillis();
						System.err.println(now + " : " + className + ": " + message);
					}
				};
			}

			Logger.logger = logger;
		}
	}

	/**
	 * This method can be used to perform end-to-end connection testing. It
	 * sends a 'ping' message to the server and waits for the 'pong' from the
	 * server.
	 * <p>
	 * When this method throws an exception, then you can assume that the
	 * connection should be abandoned.
	 * <p>
	 * Note: Works only after one has passed successfully the authentication
	 * step.
	 * <p>
	 * Implementation details: this method sends a SSH_MSG_GLOBAL_REQUEST
	 * request ('trilead-ping') to the server and waits for the
	 * SSH_MSG_REQUEST_FAILURE reply packet from the server.
	 * 
	 * @throws IOException
	 *             in case of any problem
	 */
	public synchronized void ping() throws IOException
	{
		if (tm == null)
			throw new IllegalStateException("You need to establish a connection first.");

		if (!authenticated)
			throw new IllegalStateException("The connection is not authenticated.");

		cm.requestGlobalTrileadPing();
	}
}