Mercurial > 510Connectbot
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 } |