comparison src/ch/ethz/ssh2/ServerConnection.java @ 273:91a31873c42a ganymed

start conversion from trilead to ganymed
author Carl Byington <carl@five-ten-sg.com>
date Fri, 18 Jul 2014 11:21:46 -0700
parents
children d7e088fa2123
comparison
equal deleted inserted replaced
272:ce2f4e397703 273:91a31873c42a
1 /*
2 * Copyright (c) 2012-2013 Christian Plattner. All rights reserved.
3 * Please refer to the LICENSE.txt for licensing details.
4 */
5
6 package ch.ethz.ssh2;
7
8 import java.io.CharArrayWriter;
9 import java.io.File;
10 import java.io.FileReader;
11 import java.io.IOException;
12 import java.net.Socket;
13
14 import ch.ethz.ssh2.crypto.CryptoWishList;
15 import ch.ethz.ssh2.crypto.PEMDecoder;
16 import ch.ethz.ssh2.server.ServerConnectionState;
17 import ch.ethz.ssh2.signature.DSAPrivateKey;
18 import ch.ethz.ssh2.signature.RSAPrivateKey;
19 import ch.ethz.ssh2.transport.ServerTransportManager;
20
21 /**
22 * A server-side SSH-2 connection.
23 *
24 * @author Christian
25 *
26 */
27 public class ServerConnection
28 {
29 /**
30 * The softwareversion presented to the SSH-2 client.
31 */
32 private String softwareversion = String.format("Ganymed_SSHD_%s", Version.getSpecification());
33
34 private final ServerConnectionState state = new ServerConnectionState(this);
35
36 /**
37 * Creates a new <code>ServerConnection</code> that will communicate
38 * with the client over the given <code>Socket</code>.
39 * <p>
40 * Note: you need to call {@link #connect()} or {@link #connect(int)} to
41 * perform the initial handshake and establish the encrypted communication.
42 *
43 * @see #connect(int)
44 *
45 * @param s The socket
46 */
47 public ServerConnection(Socket s)
48 {
49 this(s, null, null);
50 }
51
52 public ServerConnection(Socket s, String softwareversion) {
53 this(s, null, null);
54 this.softwareversion = softwareversion;
55 }
56
57 /**
58 * Creates a new <code>ServerConnection</code> that will communicate
59 * with the client over the given <code>Socket</code>.
60 * <p>
61 * Note: you need to call {@link #connect()} or {@link #connect(int)} to
62 * perform the initial handshake and establish the encrypted communication.
63 * <p>
64 * Please read the javadoc for the {@link #connect(int)} method.
65 *
66 * @see #connect(int)
67 *
68 * @param s The socket
69 * @param dsa_key The DSA hostkey, may be <code>NULL</code>
70 * @param rsa_key The RSA hostkey, may be <code>NULL</code>
71 */
72 public ServerConnection(Socket s, DSAPrivateKey dsa_key, RSAPrivateKey rsa_key)
73 {
74 state.s = s;
75 state.softwareversion = softwareversion;
76 state.next_dsa_key = dsa_key;
77 state.next_rsa_key = rsa_key;
78 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
79 }
80
81 /**
82 * Establish the connection and block until the first handshake has completed.
83 * <p>
84 * Note: this is a wrapper that calls <code>connect(0)</code> (i.e., connect with no timeout).
85 * <p>
86 * Please read the javadoc for the {@link #connect(int)} method.
87 *
88 * @see #connect(int)
89 *
90 * @throws IOException
91 */
92 public synchronized void connect() throws IOException
93 {
94 connect(0);
95 }
96
97 /**
98 * Establish the connection and block until the first handshake has completed.
99 * <p>
100 * Note 1: either a DSA or a RSA (or both) hostkey must be set before calling this method.
101 * <p>
102 * Note 2: You must set the callbacks for authentication ({@link #setAuthenticationCallback(ServerAuthenticationCallback)})
103 * and connection events ({@link #setServerConnectionCallback(ServerConnectionCallback)}).
104 *
105 * @see #setPEMHostKey(char[], String)
106 * @see #setPEMHostKey(File, String)
107 * @see #setRsaHostKey(RSAPrivateKey)
108 * @see #setDsaHostKey(DSAPrivateKey)
109 *
110 * @param timeout_milliseconds Timeout in milliseconds, <code>0</code> means no timeout.
111 * @throws IOException
112 */
113 public synchronized void connect(int timeout_milliseconds) throws IOException
114 {
115 synchronized (state)
116 {
117 if (state.cb_conn == null)
118 throw new IllegalStateException("The callback for connection events has not been set.");
119
120 if (state.cb_auth == null)
121 throw new IllegalStateException("The callback for authentication events has not been set.");
122
123 if (state.tm != null)
124 throw new IllegalStateException("The initial handshake has already been started.");
125
126 if ((state.next_dsa_key == null) && (state.next_rsa_key == null))
127 throw new IllegalStateException("Neither a RSA nor a DSA host key has been specified!");
128
129 state.tm = new ServerTransportManager(state.s);
130 }
131
132 state.tm.connect(state);
133
134 /* Wait until first KEX has finished */
135
136 state.tm.getConnectionInfo(1);
137 }
138
139 /**
140 * Retrieve the underlying socket.
141 *
142 * @return the socket that has been passed to the constructor.
143 */
144 public Socket getSocket()
145 {
146 return state.s;
147 }
148
149 /**
150 * Force an asynchronous key re-exchange (the call does not block). The
151 * latest values set for MAC, Cipher and DH group exchange parameters will
152 * be used. If a key exchange is currently in progress, then this method has
153 * the only effect that the so far specified parameters will be used for the
154 * next (client driven) key exchange. You may call this method only after
155 * the initial key exchange has been established.
156 * <p>
157 * Note: This implementation will never start automatically a key exchange (other than the initial one)
158 * unless you or the connected SSH-2 client ask for it.
159 *
160 * @throws IOException
161 * In case of any failure behind the scenes.
162 */
163 public synchronized void forceKeyExchange() throws IOException
164 {
165 synchronized (state)
166 {
167 if (state.tm == null)
168 throw new IllegalStateException(
169 "Cannot force another key exchange, you need to start the key exchange first.");
170
171 state.tm.forceKeyExchange(state.next_cryptoWishList, null, state.next_dsa_key, state.next_rsa_key);
172 }
173 }
174
175 /**
176 * Returns a {@link ConnectionInfo} object containing the details of
177 * the connection. May be called as soon as the first key exchange has been
178 * started. The method blocks in case the first key exchange has not been completed.
179 * <p>
180 * Note: upon return of this method, authentication may still be pending.
181 *
182 * @return A {@link ConnectionInfo} object.
183 * @throws IOException
184 * In case of any failure behind the scenes; e.g., first key exchange was aborted.
185 */
186 public synchronized ConnectionInfo getConnectionInfo() throws IOException
187 {
188 synchronized (state)
189 {
190 if (state.tm == null)
191 throw new IllegalStateException(
192 "Cannot get details of connection, you need to start the key exchange first.");
193 }
194
195 return state.tm.getConnectionInfo(1);
196 }
197
198 /**
199 * Change the current DSA hostkey. Either a DSA or RSA private key must be set for a successful handshake with
200 * the client.
201 * <p>
202 * Note: You can change an existing DSA hostkey after the initial kex exchange (the new value will
203 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
204 * current DSA key, otherwise the next key exchange may fail in case the client supports only DSA hostkeys.
205 *
206 * @param dsa_hostkey
207 */
208 public synchronized void setDsaHostKey(DSAPrivateKey dsa_hostkey)
209 {
210 synchronized (state)
211 {
212 if ((dsa_hostkey == null) && (state.next_dsa_key != null) && (state.tm != null))
213 throw new IllegalStateException("Cannot remove DSA hostkey after first key exchange.");
214
215 state.next_dsa_key = dsa_hostkey;
216 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
217 }
218 }
219
220 /**
221 * Change the current RSA hostkey. Either a DSA or RSA private key must be set for a successful handshake with
222 * the client.
223 * <p>
224 * Note: You can change an existing RSA hostkey after the initial kex exchange (the new value will
225 * be used during the next server initiated key exchange), but you cannot remove (i.e., set to <code>null</code>) the
226 * current RSA key, otherwise the next key exchange may fail in case the client supports only RSA hostkeys.
227 *
228 * @param rsa_hostkey
229 */
230 public synchronized void setRsaHostKey(RSAPrivateKey rsa_hostkey)
231 {
232 synchronized (state)
233 {
234 if ((rsa_hostkey == null) && (state.next_rsa_key != null) && (state.tm != null))
235 throw new IllegalStateException("Cannot remove RSA hostkey after first key exchange.");
236
237 state.next_rsa_key = rsa_hostkey;
238 fixCryptoWishList(state.next_cryptoWishList, state.next_dsa_key, state.next_rsa_key);
239 }
240 }
241
242 /**
243 * Utility method that loads a PEM based hostkey (either RSA or DSA based) and
244 * calls either <code>setRsaHostKey()</code> or <code>setDsaHostKey()</code>.
245 *
246 * @param pemdata The PEM data
247 * @param password Password, may be null in case the PEM data is not password protected
248 * @throws IOException In case of any error.
249 */
250 public void setPEMHostKey(char[] pemdata, String password) throws IOException
251 {
252 Object key = PEMDecoder.decode(pemdata, password);
253
254 if (key instanceof DSAPrivateKey)
255 setDsaHostKey((DSAPrivateKey) key);
256
257 if (key instanceof RSAPrivateKey)
258 setRsaHostKey((RSAPrivateKey) key);
259 }
260
261 /**
262 * Utility method that loads a hostkey from a PEM file (either RSA or DSA based) and
263 * calls either <code>setRsaHostKey()</code> or <code>setDsaHostKey()</code>.
264 *
265 * @param pemFile The PEM file
266 * @param password Password, may be null in case the PEM file is not password protected
267 * @throws IOException
268 */
269 public void setPEMHostKey(File pemFile, String password) throws IOException
270 {
271 if (pemFile == null)
272 throw new IllegalArgumentException("pemfile argument is null");
273
274 char[] buff = new char[256];
275
276 CharArrayWriter cw = new CharArrayWriter();
277
278 FileReader fr = new FileReader(pemFile);
279
280 while (true)
281 {
282 int len = fr.read(buff);
283 if (len < 0)
284 break;
285 cw.write(buff, 0, len);
286 }
287
288 fr.close();
289
290 setPEMHostKey(cw.toCharArray(), password);
291 }
292
293 private void fixCryptoWishList(CryptoWishList next_cryptoWishList, DSAPrivateKey next_dsa_key,
294 RSAPrivateKey next_rsa_key)
295 {
296 if ((next_dsa_key != null) && (next_rsa_key != null))
297 next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-rsa", "ssh-dss" };
298 else if (next_dsa_key != null)
299 next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-dss" };
300 else if (next_rsa_key != null)
301 next_cryptoWishList.serverHostKeyAlgorithms = new String[] { "ssh-rsa" };
302 else
303 next_cryptoWishList.serverHostKeyAlgorithms = new String[0];
304 }
305
306 /**
307 * Callback interface with methods that will be called upon events
308 * generated by the client (e.g., client opens a new Session which results in a <code>ServerSession</code>).
309 * <p>
310 * Note: This must be set before the first handshake.
311 *
312 * @param cb The callback implementation
313 */
314 public synchronized void setServerConnectionCallback(ServerConnectionCallback cb)
315 {
316 synchronized (state)
317 {
318 state.cb_conn = cb;
319 }
320 }
321
322 /**
323 * Callback interface with methods that will be called upon authentication events.
324 * <p>
325 * Note: This must be set before the first handshake.
326 *
327 * @param cb The callback implementation
328 */
329 public synchronized void setAuthenticationCallback(ServerAuthenticationCallback cb)
330 {
331 synchronized (state)
332 {
333 state.cb_auth = cb;
334 }
335 }
336
337 /**
338 * Close the connection to the SSH-2 server. All assigned sessions will be
339 * closed, too. Can be called at any time. Don't forget to call this once
340 * you don't need a connection anymore - otherwise the receiver thread may
341 * run forever.
342 */
343 public void close()
344 {
345 synchronized (state)
346 {
347 if (state.cm != null)
348 state.cm.closeAllChannels();
349
350 if (state.tm != null)
351 {
352 state.tm.close();
353 }
354 }
355 }
356
357 public void close(IOException t)
358 {
359 synchronized (state)
360 {
361 if (state.cm != null)
362 state.cm.closeAllChannels();
363
364 if (state.tm != null)
365 {
366 state.tm.close(t);
367 }
368 }
369 }
370 }