aboutsummaryrefslogtreecommitdiffstats
path: root/src/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
From cd68d1b12b5ea4c01a664c064179ada42bf55d3d Mon Sep 17 00:00:00 2001
From: Daniel Golle <daniel@makrotopia.org>
Date: Thu, 15 May 2014 20:55:42 +0200
Subject: [PATCH 5/5] ubi: set ROOT_DEV to ubiblock "rootfs" if unset
To: openwrt-devel@lists.openwrt.org

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/mtd/ubi/block.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

--- a/drivers/mtd/ubi/block.c
+++ b/drivers/mtd/ubi/block.c
@@ -50,6 +50,7 @@
 #include <linux/scatterlist.h>
 #include <linux/idr.h>
 #include <asm/div64.h>
+#include <linux/root_dev.h>
 
 #include "ubi-media.h"
 #include "ubi.h"
@@ -447,6 +448,15 @@ int ubiblock_create(struct ubi_volume_in
 	add_disk(dev->gd);
 	dev_info(disk_to_dev(dev->gd), "created from ubi%d:%d(%s)",
 		 dev->ubi_num, dev->vol_id, vi->name);
+
+	if (!strcmp(vi->name, "rootfs") &&
+	    IS_ENABLED(CONFIG_MTD_ROOTFS_ROOT_DEV) &&
+	    ROOT_DEV == 0) {
+		pr_notice("ubiblock: device ubiblock%d_%d (%s) set to be root filesystem\n",
+			  dev->ubi_num, dev->vol_id, vi->name);
+		ROOT_DEV = MKDEV(gd->major, gd->first_minor);
+	}
+
 	return 0;
 
 out_free_queue:
ref='#n274'>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();
	}
}