001: /******************************************************************************
002: * $Source: /cvsroot/sshwebproxy/src/java/com/ericdaugherty/sshwebproxy/SshConnection.java,v $
003: * $Revision: 1.2 $
004: * $Author: edaugherty $
005: * $Date: 2003/11/23 00:18:10 $
006: ******************************************************************************
007: * Copyright (c) 2003, Eric Daugherty (http://www.ericdaugherty.com)
008: * All rights reserved.
009: *
010: * Redistribution and use in source and binary forms, with or without
011: * modification, are permitted provided that the following conditions are met:
012: *
013: * * Redistributions of source code must retain the above copyright notice,
014: * this list of conditions and the following disclaimer.
015: * * Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in the
017: * documentation and/or other materials provided with the distribution.
018: * * Neither the name of the Eric Daugherty nor the names of its
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
023: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
024: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
025: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
026: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
027: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
028: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
029: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
030: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
031: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
032: * THE POSSIBILITY OF SUCH DAMAGE.
033: * *****************************************************************************
034: * For current versions and more information, please visit:
035: * http://www.ericdaugherty.com/dev/sshwebproxy
036: *
037: * or contact the author at:
038: * web@ericdaugherty.com
039: *****************************************************************************/package com.ericdaugherty.sshwebproxy;
040:
041: import com.sshtools.j2ssh.SshClient;
042: import com.sshtools.j2ssh.transport.HostKeyVerification;
043: import com.sshtools.j2ssh.transport.TransportProtocolException;
044: import com.sshtools.j2ssh.transport.publickey.SshPublicKey;
045: import com.sshtools.j2ssh.transport.publickey.SshPrivateKeyFile;
046: import com.sshtools.j2ssh.transport.publickey.SshPrivateKey;
047: import com.sshtools.j2ssh.transport.publickey.InvalidSshKeyException;
048: import com.sshtools.j2ssh.authentication.PasswordAuthenticationClient;
049: import com.sshtools.j2ssh.authentication.AuthenticationProtocolState;
050: import com.sshtools.j2ssh.authentication.PublicKeyAuthenticationClient;
051: import com.sshtools.j2ssh.session.SessionChannelClient;
052: import com.sshtools.j2ssh.configuration.SshConnectionProperties;
053: import com.sshtools.j2ssh.configuration.ConfigurationLoader;
054: import com.sshtools.j2ssh.configuration.ConfigurationException;
055:
056: import java.text.MessageFormat;
057: import java.io.IOException;
058: import java.util.*;
059: import java.net.UnknownHostException;
060:
061: import org.apache.commons.logging.Log;
062: import org.apache.commons.logging.LogFactory;
063:
064: /**
065: * SshConnection represents an Ssh connection
066: * between the local host and a remote ssh daemon.
067: * A single SshConnection may contain multiple channels,
068: * which may be file transfer channels or shell channels.
069: *
070: * @author Eric Daugherty
071: */
072: public class SshConnection implements SshConstants {
073:
074: //***************************************************************
075: // Variables
076: //***************************************************************
077:
078: /** The SSHClient instance */
079: private SshClient sshClient;
080:
081: /** Information about the current connection */
082: private String connectionInfo;
083:
084: /** Stores all active SshChannels. */
085: private Map channelMap;
086:
087: /** The next ID to assign to a channel */
088: private int nextChannelId = 0;
089:
090: /** Logger */
091: private static final Log log = LogFactory
092: .getLog(SshConnection.class);
093:
094: //***************************************************************
095: // Static Initialization
096: //***************************************************************
097:
098: // Initialize the SSH Library
099: static {
100: try {
101: ConfigurationLoader.initialize(false);
102: } catch (ConfigurationException e) {
103: log.error("Error configuring SSH Library: " + e, e);
104: throw new RuntimeException(
105: "Unable to initialize SSH Library: " + e, e);
106: }
107: }
108:
109: //***************************************************************
110: // Constructors
111: //***************************************************************
112:
113: /**
114: * Performs common constructor logic.
115: */
116: private SshConnection() {
117: channelMap = new HashMap();
118: }
119:
120: /**
121: * Initialize a new SshConnection with the SshClient connection.
122: *
123: * @param sshClient the sshClient that represents the connection.
124: */
125: public SshConnection(SshClient sshClient, String connectionInfo) {
126: this ();
127:
128: this .connectionInfo = connectionInfo;
129:
130: this .sshClient = sshClient;
131: }
132:
133: /**
134: * Create a new SshConnection to the specified location
135: * with the specified username and password.
136: *
137: * @param host the remote host to connect to.
138: * @param port the port to connect to.
139: * @param username the username to login with.
140: * @param password the password to login with.
141: * @throws SshConnectException thrown if the connection attempt failes for any reason.
142: */
143: public SshConnection(String host, int port, String username,
144: String password) throws SshConnectException {
145: this ();
146:
147: // Verify the parameters are not null or invalid.
148: if (host == null || host.trim().length() == 0 || port < 1
149: || username == null || username.trim().length() == 0
150: || password == null || password.trim().length() == 0) {
151: throw new SshConnectException(
152: "Missing parameter. All parameters must be at least one character.");
153: }
154:
155: connectionInfo = getConnectionInfo(host, port, username);
156:
157: if (log.isDebugEnabled())
158: log.debug(connectionInfo
159: + " - Attempting to Open Connection.");
160:
161: // Initialize the SSH library
162: sshClient = new SshClient();
163: sshClient.setSocketTimeout(30000);
164: SshConnectionProperties properties = new SshConnectionProperties();
165: properties.setHost(host);
166: properties.setPort(port);
167: properties.setPrefPublicKey("ssh-dss");
168:
169: // Connect to the host
170: try {
171: sshClient
172: .connect(properties, new HostKeyVerificationImpl());
173:
174: log.debug("Connect Successful.");
175:
176: // Initialize the authentication data.
177: PasswordAuthenticationClient pwd = new PasswordAuthenticationClient();
178: pwd.setUsername(username);
179: pwd.setPassword(password);
180:
181: // Authenticate
182: int result = sshClient.authenticate(pwd);
183: if (result != AuthenticationProtocolState.COMPLETE) {
184: throw new SshConnectException(
185: "Authentication Error. Invalid username or password.");
186: }
187:
188: log.debug("Authentication Successful.");
189: } catch (UnknownHostException unknownHostException) {
190: throw new SshConnectException(
191: "Unable to connect. Unknown host.");
192: } catch (IOException ioException) {
193: log.warn(
194: "IOException occured in SshConnection constructor. "
195: + ioException, ioException);
196: throw new SshConnectException("Unable to connect to host.");
197: }
198:
199: // Success!
200: if (log.isInfoEnabled())
201: log.info(connectionInfo
202: + " - Connection opened successfully.");
203: }
204:
205: /**
206: * Create a new SshConnection to the specified location
207: * with the specified username and key.
208: *
209: * @param host the remote host to connect to.
210: * @param port the port to connect to.
211: * @param username the username to login with.
212: * @param key the SSH Key as a byte array.
213: * @param keyPassPhrase the passPharse for the key (optional)
214: * @throws SshConnectException thrown if the connection attempt failes for any reason.
215: */
216: public SshConnection(String host, int port, String username,
217: byte[] key, String keyPassPhrase)
218: throws SshConnectException {
219: this ();
220:
221: // Verify the parameters are not null or invalid.
222: if (host == null || host.trim().length() == 0 || port < 1
223: || username == null || username.trim().length() == 0
224: || key == null) {
225: throw new SshConnectException(
226: "Missing parameter. All parameters must be at least one character.");
227: }
228:
229: connectionInfo = getConnectionInfo(host, port, username);
230:
231: if (log.isDebugEnabled())
232: log.debug(connectionInfo
233: + " - Attempting to Open Connection.");
234:
235: // Initialize the SSH library
236: sshClient = new SshClient();
237: sshClient.setSocketTimeout(30000);
238: SshConnectionProperties properties = new SshConnectionProperties();
239: properties.setHost(host);
240: properties.setPort(port);
241:
242: // Connect to the host
243: try {
244: sshClient
245: .connect(properties, new HostKeyVerificationImpl());
246:
247: log.debug("Connect Successful.");
248:
249: // Initialize the authentication data.
250: PublicKeyAuthenticationClient publicKeyAuth = new PublicKeyAuthenticationClient();
251:
252: publicKeyAuth.setUsername(username);
253:
254: SshPrivateKeyFile file = SshPrivateKeyFile.parse(key);
255: SshPrivateKey privateKey = file.toPrivateKey(keyPassPhrase);
256: publicKeyAuth.setKey(privateKey);
257:
258: // Authenticate
259: int result = sshClient.authenticate(publicKeyAuth);
260: if (result != AuthenticationProtocolState.COMPLETE) {
261: throw new SshConnectException(
262: "Authentication Error. Invalid username or password.");
263: }
264:
265: log.debug("Authentication Successful.");
266: } catch (InvalidSshKeyException invalidSshKeyException) {
267: throw new SshConnectException(
268: "Unable to connect. Invalid SSH Key. "
269: + invalidSshKeyException.getMessage());
270: } catch (UnknownHostException unknownHostException) {
271: throw new SshConnectException(
272: "Unable to connect. Unknown host.");
273: } catch (IOException ioException) {
274: log.warn(
275: "IOException occured in SshConnection constructor. "
276: + ioException, ioException);
277: throw new SshConnectException("Unable to connect to host.");
278: }
279:
280: // Success!
281: if (log.isInfoEnabled())
282: log.info(connectionInfo
283: + " - Connection opened successfully.");
284: }
285:
286: //***************************************************************
287: // Parameter Access Methods
288: //***************************************************************
289:
290: /**
291: * Returns information about this connection. The information
292: * consists of the username, the host, and the port. The result
293: * is formatted as: username@host:port
294: *
295: * @return formated string: username@host:port
296: */
297: public String getConnectionInfo() {
298: return connectionInfo;
299: }
300:
301: /**
302: * Helper method to return the connection info.
303: *
304: * @param host
305: * @param port
306: * @param username
307: * @return a propertly formatted connection info string.
308: */
309: public static String getConnectionInfo(String host, String port,
310: String username) {
311: return MessageFormat.format("{0}@{1}:{2}", new String[] {
312: username.trim(), host.trim(), port.trim() });
313: }
314:
315: /**
316: * Helper method to return the connection info.
317: *
318: * @param host
319: * @param port
320: * @param username
321: * @return a propertly formatted connection info string.
322: */
323: public static String getConnectionInfo(String host, int port,
324: String username) {
325: return getConnectionInfo(host, String.valueOf(port), username);
326: }
327:
328: //***************************************************************
329: // Public Methods
330: //***************************************************************
331:
332: /**
333: * Returns true if this SshConnection is open.
334: *
335: * @return true if it is open.
336: */
337: public boolean isOpen() {
338: return sshClient.isConnected();
339: }
340:
341: /**
342: * Closes all open channels and the current SshConnection.
343: */
344: public void close() {
345: if (log.isInfoEnabled())
346: log.info(connectionInfo + " - Closing Connection.");
347:
348: Iterator shellChannels = channelMap.values().iterator();
349: SshChannel shellChannel;
350: while (shellChannels.hasNext()) {
351: shellChannel = (SshChannel) shellChannels.next();
352: shellChannel.close();
353: }
354:
355: channelMap.clear();
356:
357: sshClient.disconnect();
358: }
359:
360: /**
361: * Returns the requested channel.
362: *
363: * @param channelId the channel's unique id.
364: * @return the requested channel, or null if it does not exist.
365: */
366: public SshChannel getChannel(String channelId) {
367: return (SshChannel) channelMap.get(channelId);
368: }
369:
370: /**
371: * Returns all channels
372: */
373: public Collection getChannels() {
374: return channelMap.values();
375: }
376:
377: /**
378: * Open a new Shell Channel for this connection.
379: *
380: * @return a newly opened ShellChannel
381: * @throws SshConnectException if the channel could not be opened.
382: */
383: public ShellChannel openShellChannel() throws SshConnectException {
384: if (log.isInfoEnabled())
385: log.info(connectionInfo + " - Opening new ShellChannel");
386:
387: try {
388: SessionChannelClient sessionChannelClient = sshClient
389: .openSessionChannel();
390: ShellChannel shellChannel = new VT100ShellChannel(this ,
391: sessionChannelClient);
392:
393: // Generate a channelId for the channel and add it to the local map.
394: String channelId = String.valueOf(nextChannelId++);
395: shellChannel.setChannelId(channelId);
396: channelMap.put(channelId, shellChannel);
397:
398: return shellChannel;
399: } catch (IOException ioException) {
400: log.warn(
401: "openShellChannel failed, unable to open Session Channel: "
402: + ioException, ioException);
403: throw new SshConnectException(
404: "Unable to open SessionChannel.");
405: }
406: }
407:
408: /**
409: * Open a new File Channel for this connection.
410: *
411: * @return a newly opened FileChannel
412: * @throws SshConnectException if the channel could not be opened.
413: */
414: public FileChannel openFileChannel() throws SshConnectException {
415: if (log.isInfoEnabled())
416: log.info(connectionInfo + " - Opening new FileChannel");
417:
418: FileChannel fileChannel = new FileChannel(this , sshClient);
419:
420: // Generate a channelId for the channel and add it to the local map.
421: String channelId = String.valueOf(nextChannelId++);
422: fileChannel.setChannelId(channelId);
423: channelMap.put(channelId, fileChannel);
424:
425: return fileChannel;
426: }
427:
428: /**
429: * Returns the requested channel.
430: *
431: * @param channelId the channel's unique id.
432: * @return the requested channel, or null if it does not exist.
433: */
434: public ShellChannel getShellChannel(String channelId) {
435: SshChannel channel = (SshChannel) channelMap.get(channelId);
436:
437: // Return null if it does not exist or is the wrong type of channel.
438: if (channel == null || !(channel instanceof ShellChannel)) {
439: return null;
440: }
441:
442: return (ShellChannel) channel;
443: }
444:
445: /**
446: * Returns a collection of all ShellChannels associated with this
447: * connection.
448: *
449: * @return will never be null.
450: */
451: public Collection getShellChannels() {
452: ArrayList shellChannels = new ArrayList();
453: Iterator channelIterator = channelMap.values().iterator();
454: SshChannel sshChannel;
455: while (channelIterator.hasNext()) {
456: sshChannel = (SshChannel) channelIterator.next();
457: if (CHANNEL_TYPE_SHELL.equals(sshChannel.getChannelType())) {
458: shellChannels.add(sshChannel);
459: }
460:
461: }
462:
463: return shellChannels;
464: }
465:
466: /**
467: * Returns the requested channel.
468: *
469: * @param channelId the channel's unique id.
470: * @return the requested channel, or null if it does not exist.
471: */
472: public FileChannel getFileChannel(String channelId) {
473: SshChannel channel = (SshChannel) channelMap.get(channelId);
474:
475: // Return null if it does not exist or is the wrong type of channel.
476: if (channel == null || !(channel instanceof FileChannel)) {
477: return null;
478: }
479:
480: return (FileChannel) channel;
481: }
482:
483: /**
484: * Close a specfic channel. This calls channel.close()
485: * and removes it from the channel list.
486: *
487: * @param channelId the channel to remove.
488: */
489: public void closeChannel(String channelId) {
490: SshChannel sshChannel = getChannel(channelId);
491: if (sshChannel != null) {
492: sshChannel.close();
493: channelMap.remove(sshChannel.getChannelId());
494: }
495: }
496:
497: /**
498: * Close a specfic channel. This calls channel.close()
499: * and removes it from the channel list.
500: *
501: * @param sshChannel the channel to remove.
502: */
503: public void closeChannel(SshChannel sshChannel) {
504: sshChannel.close();
505: channelMap.remove(sshChannel.getChannelId());
506: }
507:
508: //***************************************************************
509: // Object Methods
510: //***************************************************************
511:
512: /**
513: * Return a string representation of this connection.
514: * @return
515: */
516: public String toString() {
517: String[] args = new String[] { getConnectionInfo(),
518: String.valueOf(sshClient.getActiveChannelCount()) };
519: return MessageFormat.format(
520: "Connected to {0} with {1} open channels.", args);
521: }
522:
523: //***************************************************************
524: // Inner Classes
525: //***************************************************************
526:
527: /**
528: * Handles the HostKeyVerification. Current implementation accepts
529: * all keys.
530: *
531: * @author Eric Daugherty
532: */
533: private class HostKeyVerificationImpl implements
534: HostKeyVerification {
535: /**
536: * Determines if the host key should be accepted.
537: * @param string
538: * @param sshPublicKey
539: * @return
540: * @throws com.sshtools.j2ssh.transport.TransportProtocolException
541: */
542: public boolean verifyHost(String string,
543: SshPublicKey sshPublicKey)
544: throws TransportProtocolException {
545: //TODO: Add real logic here to handle Host Key Validation.
546: log.debug("Verifying Host: " + string);
547: return true;
548: }
549: }
550: }
|