1 | /******************************************************************************
|
---|
2 | *
|
---|
3 | * Copyright (c) 1998,99 by Mindbright Technology AB, Stockholm, Sweden.
|
---|
4 | * www.mindbright.se, [email protected]
|
---|
5 | *
|
---|
6 | * This program is free software; you can redistribute it and/or modify
|
---|
7 | * it under the terms of the GNU General Public License as published by
|
---|
8 | * the Free Software Foundation; either version 2 of the License, or
|
---|
9 | * (at your option) any later version.
|
---|
10 | *
|
---|
11 | * This program is distributed in the hope that it will be useful,
|
---|
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
14 | * GNU General Public License for more details.
|
---|
15 | *
|
---|
16 | *****************************************************************************
|
---|
17 | * $Author: mats $
|
---|
18 | * $Date: 2000/03/09 10:59:44 $
|
---|
19 | * $Name: rel1-2-1 $
|
---|
20 | *****************************************************************************/
|
---|
21 | package mindbright.ssh;
|
---|
22 |
|
---|
23 | import java.io.*;
|
---|
24 | import java.util.Vector;
|
---|
25 |
|
---|
26 | import mindbright.security.*;
|
---|
27 |
|
---|
28 | public final class SSHChannelController extends SSH implements SSHChannelListener {
|
---|
29 |
|
---|
30 | protected SSHTxChannel txChan;
|
---|
31 | protected SSHRxChannel rxChan;
|
---|
32 | protected SSHConnectChannel cnChan;
|
---|
33 | protected SSHPduQueue txQueue;
|
---|
34 | protected SSHPduQueue cnQueue;
|
---|
35 |
|
---|
36 | protected int totalTunnels;
|
---|
37 | protected int nextEmptyChan;
|
---|
38 | protected Object[] tunnels;
|
---|
39 |
|
---|
40 | protected Vector listenChannels;
|
---|
41 |
|
---|
42 | protected SSH sshHook;
|
---|
43 | protected SSHConsole console;
|
---|
44 |
|
---|
45 | protected Cipher sndCipher;
|
---|
46 | protected Cipher rcvCipher;
|
---|
47 |
|
---|
48 | public SSHChannelController(SSH sshHook, InputStream in, OutputStream out,
|
---|
49 | Cipher sndCipher, Cipher rcvCipher,
|
---|
50 | SSHConsole console, boolean haveCnxWatch) {
|
---|
51 | this.sndCipher = sndCipher;
|
---|
52 | this.rcvCipher = rcvCipher;
|
---|
53 |
|
---|
54 | this.sshHook = sshHook;
|
---|
55 | this.console = console;
|
---|
56 |
|
---|
57 | this.tunnels = new Object[16];
|
---|
58 | this.nextEmptyChan = 0;
|
---|
59 | this.totalTunnels = 0;
|
---|
60 | this.listenChannels = new Vector();
|
---|
61 |
|
---|
62 | txChan = new SSHTxChannel(out, MAIN_CHAN_NUM);
|
---|
63 | rxChan = new SSHRxChannel(in, MAIN_CHAN_NUM);
|
---|
64 |
|
---|
65 | rxChan.setSSHChannelListener(this);
|
---|
66 | txChan.setSSHChannelListener(this);
|
---|
67 | rxChan.setSSHPduFactory(new SSHPduInputStream(MSG_ANY, rcvCipher));
|
---|
68 | txQueue = txChan.getQueue();
|
---|
69 |
|
---|
70 | if(haveCnxWatch) {
|
---|
71 | cnChan = new SSHConnectChannel(this);
|
---|
72 | cnChan.setSSHChannelListener(this);
|
---|
73 | cnQueue = cnChan.getQueue();
|
---|
74 | } else {
|
---|
75 | cnQueue = new SSHPduQueue();
|
---|
76 | }
|
---|
77 |
|
---|
78 | }
|
---|
79 |
|
---|
80 | public void start() {
|
---|
81 | txChan.start();
|
---|
82 | rxChan.start();
|
---|
83 | if(cnChan != null)
|
---|
84 | cnChan.start();
|
---|
85 | }
|
---|
86 |
|
---|
87 | public void waitForExit() throws InterruptedException {
|
---|
88 | waitForExit(0); // Wait forever...
|
---|
89 | }
|
---|
90 | public void waitForExit(long msWait) throws InterruptedException {
|
---|
91 | if(rxChan != null)
|
---|
92 | rxChan.join(msWait);
|
---|
93 | Thread.sleep(100);
|
---|
94 | killAll();
|
---|
95 | }
|
---|
96 |
|
---|
97 | public void killAll() {
|
---|
98 | killAllTunnels();
|
---|
99 | killListenChannels();
|
---|
100 | if(rxChan != null && rxChan.isAlive())
|
---|
101 | rxChan.stop();
|
---|
102 | if(txChan != null && txChan.isAlive())
|
---|
103 | txChan.stop();
|
---|
104 | if(cnChan != null && cnChan.isAlive())
|
---|
105 | cnChan.stop();
|
---|
106 |
|
---|
107 | rxChan = null;
|
---|
108 | txChan = null;
|
---|
109 | cnChan = null;
|
---|
110 | System.runFinalization();
|
---|
111 | }
|
---|
112 |
|
---|
113 | public synchronized int newChannelId() {
|
---|
114 | int newChan = nextEmptyChan;
|
---|
115 | if(nextEmptyChan < tunnels.length) {
|
---|
116 | int i;
|
---|
117 | for(i = nextEmptyChan + 1; i < tunnels.length; i++)
|
---|
118 | if(tunnels[i] == null)
|
---|
119 | break;
|
---|
120 | nextEmptyChan = i;
|
---|
121 | } else {
|
---|
122 | Object[] tmp = new Object[tunnels.length + 16];
|
---|
123 | System.arraycopy(tunnels, 0, tmp, 0, tunnels.length);
|
---|
124 | tunnels = tmp;
|
---|
125 | nextEmptyChan++;
|
---|
126 | }
|
---|
127 |
|
---|
128 | return newChan;
|
---|
129 | }
|
---|
130 |
|
---|
131 | public synchronized String[] listTunnels() {
|
---|
132 | int i, cnt = 0;
|
---|
133 | String[] list1 = new String[tunnels.length];
|
---|
134 |
|
---|
135 | for(i = 0; i < tunnels.length; i++) {
|
---|
136 | if(tunnels[i] == null)
|
---|
137 | continue;
|
---|
138 | list1[cnt++] = ((SSHTunnel)tunnels[i]).getDescription();
|
---|
139 | }
|
---|
140 |
|
---|
141 | String[] list2 = new String[cnt];
|
---|
142 | System.arraycopy(list1, 0, list2, 0, cnt);
|
---|
143 |
|
---|
144 | return list2;
|
---|
145 | }
|
---|
146 |
|
---|
147 | public synchronized void closeTunnelFromList(int listIdx) {
|
---|
148 | int i;
|
---|
149 | for(i = 0; i < tunnels.length; i++) {
|
---|
150 | if(tunnels[i] == null)
|
---|
151 | continue;
|
---|
152 | listIdx--;
|
---|
153 | if(listIdx < 0)
|
---|
154 | break;
|
---|
155 | }
|
---|
156 | if(i < tunnels.length) {
|
---|
157 | ((SSHTunnel)tunnels[i]).terminateNow();
|
---|
158 | }
|
---|
159 | }
|
---|
160 |
|
---|
161 | public synchronized void killAllTunnels() {
|
---|
162 | for(int i = 0; i < tunnels.length; i++) {
|
---|
163 | if(tunnels[i] == null)
|
---|
164 | continue;
|
---|
165 | ((SSHTunnel)tunnels[i]).openFailure(); // !!! Forced close
|
---|
166 | tunnels[i] = null;
|
---|
167 | }
|
---|
168 | tunnels = new Object[16];
|
---|
169 | }
|
---|
170 |
|
---|
171 | public synchronized void addTunnel(SSHTunnel tunnel) throws IOException {
|
---|
172 | totalTunnels++;
|
---|
173 | tunnels[tunnel.channelId] = tunnel;
|
---|
174 | }
|
---|
175 |
|
---|
176 | public synchronized SSHTunnel delTunnel(int channelId) {
|
---|
177 | SSHTunnel tunnelToDelete = (SSHTunnel) tunnels[channelId];
|
---|
178 | tunnels[channelId] = null;
|
---|
179 | nextEmptyChan = (channelId < nextEmptyChan ? channelId : nextEmptyChan);
|
---|
180 | totalTunnels--;
|
---|
181 | return tunnelToDelete;
|
---|
182 | }
|
---|
183 |
|
---|
184 | public boolean haveHostInFwdOpen() {
|
---|
185 | return sshHook.isProtocolFlagSet(PROTOFLAG_HOST_IN_FWD_OPEN);
|
---|
186 | }
|
---|
187 |
|
---|
188 | public SSHListenChannel newListenChannel(String localHost, int localPort,
|
---|
189 | String remoteHost, int remotePort,
|
---|
190 | String plugin) throws IOException {
|
---|
191 | SSHListenChannel newListenChan = null;
|
---|
192 | newListenChan = SSHProtocolPlugin.getPlugin(plugin).localListener(localHost, localPort,
|
---|
193 | remoteHost, remotePort,
|
---|
194 | this);
|
---|
195 | newListenChan.setSSHChannelListener(this);
|
---|
196 | newListenChan.start();
|
---|
197 | synchronized(listenChannels) {
|
---|
198 | listenChannels.addElement(newListenChan);
|
---|
199 | }
|
---|
200 | return newListenChan;
|
---|
201 | }
|
---|
202 |
|
---|
203 | public void killListenChannel(String localHost, int listenPort) {
|
---|
204 | SSHListenChannel listenChan;
|
---|
205 | synchronized(listenChannels) {
|
---|
206 | for(int i = 0; i < listenChannels.size(); i++) {
|
---|
207 | listenChan = (SSHListenChannel) listenChannels.elementAt(i);
|
---|
208 | if(listenChan.getListenPort() == listenPort && listenChan.getListenHost().equals(localHost)) {
|
---|
209 | listenChannels.removeElementAt(i);
|
---|
210 | listenChan.forceClose();
|
---|
211 | break;
|
---|
212 | }
|
---|
213 | }
|
---|
214 | }
|
---|
215 | }
|
---|
216 |
|
---|
217 | public void killListenChannels() {
|
---|
218 | SSHListenChannel listenChan;
|
---|
219 | synchronized(listenChannels) {
|
---|
220 | while(listenChannels.size() > 0) {
|
---|
221 | listenChan = (SSHListenChannel) listenChannels.elementAt(0);
|
---|
222 | listenChan.forceClose();
|
---|
223 | listenChannels.removeElementAt(0);
|
---|
224 | }
|
---|
225 | }
|
---|
226 | }
|
---|
227 |
|
---|
228 | public SSHPdu prepare(SSHPdu pdu) {
|
---|
229 | return pdu;
|
---|
230 | }
|
---|
231 |
|
---|
232 | public void transmit(SSHPdu pdu) {
|
---|
233 | txQueue.putLast(pdu);
|
---|
234 | }
|
---|
235 |
|
---|
236 | public void receive(SSHPdu pdu) {
|
---|
237 | SSHPduInputStream inPdu = (SSHPduInputStream) pdu;
|
---|
238 | SSHTunnel tunnel;
|
---|
239 | int channelNum;
|
---|
240 | try {
|
---|
241 | switch(inPdu.type) {
|
---|
242 | case SMSG_STDOUT_DATA:
|
---|
243 | if(console != null)
|
---|
244 | console.stdoutWriteString(inPdu.readStringAsBytes());
|
---|
245 | break;
|
---|
246 | case SMSG_STDERR_DATA:
|
---|
247 | if(console != null)
|
---|
248 | console.stderrWriteString(inPdu.readStringAsBytes());
|
---|
249 | break;
|
---|
250 | case SMSG_EXITSTATUS:
|
---|
251 | SSHPduOutputStream exitPdu = new SSHPduOutputStream(CMSG_EXIT_CONFIRMATION, sndCipher);
|
---|
252 | int status = inPdu.readInt();
|
---|
253 | if(console != null) {
|
---|
254 | if(status != 0)
|
---|
255 | console.serverDisconnect(sshAsClient().getServerAddr().getHostName() + " disconnected: " + status);
|
---|
256 | else
|
---|
257 | console.serverDisconnect("Connection to " + sshAsClient().getServerAddr().getHostName() + " closed.");
|
---|
258 | }
|
---|
259 | transmit(exitPdu);
|
---|
260 | sshAsClient().disconnect(true);
|
---|
261 | break;
|
---|
262 | case SMSG_X11_OPEN:
|
---|
263 | // Fallthrough
|
---|
264 | case MSG_PORT_OPEN:
|
---|
265 | cnQueue.putLast(inPdu);
|
---|
266 | break;
|
---|
267 | case MSG_CHANNEL_DATA:
|
---|
268 | channelNum = inPdu.readInt();
|
---|
269 | tunnel = (SSHTunnel)tunnels[channelNum];
|
---|
270 | if(tunnel != null)
|
---|
271 | tunnel.transmit(pdu);
|
---|
272 | else
|
---|
273 | throw new Exception("Data on nonexistent channel: " + channelNum);
|
---|
274 | break;
|
---|
275 | case MSG_CHANNEL_OPEN_CONFIRMATION:
|
---|
276 | channelNum = inPdu.readInt();
|
---|
277 | tunnel = (SSHTunnel)tunnels[channelNum];
|
---|
278 | if(tunnel != null) {
|
---|
279 | if(!tunnel.setRemoteChannelId(inPdu.readInt()))
|
---|
280 | throw new Exception("Open confirmation on allready opened channel!");
|
---|
281 | tunnel.start();
|
---|
282 | } else
|
---|
283 | throw new Exception("Open confirm on nonexistent: " + channelNum);
|
---|
284 | break;
|
---|
285 | case MSG_CHANNEL_OPEN_FAILURE:
|
---|
286 | SSHTunnel failTunnel;
|
---|
287 | channelNum = inPdu.readInt();
|
---|
288 | if((failTunnel = delTunnel(channelNum)) != null) {
|
---|
289 | alert("Channel open failure on " + failTunnel.remoteDesc);
|
---|
290 | failTunnel.openFailure();
|
---|
291 | } else
|
---|
292 | throw new Exception("Open failure on nonexistent channel: " + channelNum);
|
---|
293 | break;
|
---|
294 | case MSG_CHANNEL_INPUT_EOF:
|
---|
295 | channelNum = inPdu.readInt();
|
---|
296 | tunnel = (SSHTunnel)tunnels[channelNum];
|
---|
297 | if(tunnel != null) {
|
---|
298 | tunnel.receiveInputEOF();
|
---|
299 | } else
|
---|
300 | throw new Exception("Input eof on nonexistent channel: " + channelNum);
|
---|
301 | break;
|
---|
302 | case MSG_CHANNEL_OUTPUT_CLOSED:
|
---|
303 | channelNum = inPdu.readInt();
|
---|
304 | ;
|
---|
305 | if(channelNum < tunnels.length && ((tunnel = (SSHTunnel)tunnels[channelNum]) != null)) {
|
---|
306 | tunnel.receiveOutputClosed();
|
---|
307 | } else
|
---|
308 | throw new Exception("Output closed on nonexistent channel: " + channelNum);
|
---|
309 | break;
|
---|
310 | case MSG_DISCONNECT:
|
---|
311 | disconnect("Peer disconnected: " + inPdu.readString());
|
---|
312 | break;
|
---|
313 | case CMSG_WINDOW_SIZE:
|
---|
314 | break;
|
---|
315 | case CMSG_STDIN_DATA:
|
---|
316 | break;
|
---|
317 | case CMSG_EOF:
|
---|
318 | System.out.println("!!! EOF received...");
|
---|
319 | break;
|
---|
320 | case CMSG_EXIT_CONFIRMATION:
|
---|
321 | break;
|
---|
322 | default:
|
---|
323 | throw new Exception("Unknown packet type (" + inPdu.type + "), disconnecting...");
|
---|
324 | }
|
---|
325 | } catch(Exception e) {
|
---|
326 | // !!! Are there known BUGS in here?? Nah... :-)
|
---|
327 | StringWriter sw = new StringWriter();
|
---|
328 | e.printStackTrace(new PrintWriter(sw));
|
---|
329 | System.out.println("\nplease send a mail to [email protected] with:");
|
---|
330 | System.out.println("(I found a bug in MindTerm!), error: " + e.getMessage());
|
---|
331 | System.out.println(sw.toString());
|
---|
332 | sendDisconnect("please send a mail to [email protected] with:" + "\n\r" +
|
---|
333 | "(I found a bug in MindTerm!), error: " + e.getMessage() + "\n\r" +
|
---|
334 | kludgeLF2CRLFMap(sw.toString()));
|
---|
335 | // !!!
|
---|
336 | }
|
---|
337 | }
|
---|
338 |
|
---|
339 | static String kludgeLF2CRLFMap(String orig) {
|
---|
340 | int o = 0, n;
|
---|
341 | String result = "";
|
---|
342 | while((n = orig.indexOf('\n', o)) != -1) {
|
---|
343 | result += orig.substring(o, n) + "\n\r";
|
---|
344 | o = n + 1;
|
---|
345 | }
|
---|
346 | result += orig.substring(o);
|
---|
347 | return result;
|
---|
348 | }
|
---|
349 |
|
---|
350 | public void close(SSHChannel chan) {
|
---|
351 | // !!!
|
---|
352 | if(chan instanceof SSHConnectChannel)
|
---|
353 | SSH.logExtra("Controller connect-channel closed");
|
---|
354 | else if(chan instanceof SSHTxChannel)
|
---|
355 | SSH.logExtra("Controller TX-channel closed");
|
---|
356 | else if(chan instanceof SSHRxChannel)
|
---|
357 | SSH.logExtra("Controller RX-channel closed");
|
---|
358 | else if(chan instanceof SSHListenChannel)
|
---|
359 | SSH.logExtra("Listen channel for port " + ((SSHListenChannel)chan).getListenPort() +
|
---|
360 | " closed");
|
---|
361 | else
|
---|
362 | alert("Bug in SSHChannelController.close 'chan' is: " + chan);
|
---|
363 | }
|
---|
364 |
|
---|
365 | public void disconnect(String reason) {
|
---|
366 | if(sshHook.isAnSSHClient)
|
---|
367 | sshAsClient().disconnect(false);
|
---|
368 | if(txChan != null)
|
---|
369 | txChan.setClosePending();
|
---|
370 | if(console != null)
|
---|
371 | console.serverDisconnect("\r\nDisconnecting, " + reason);
|
---|
372 | else
|
---|
373 | SSH.log("\r\nDisconnecting, " + reason);
|
---|
374 |
|
---|
375 | if(!sshHook.isAnSSHClient && rxChan != null) {
|
---|
376 | rxChan.forceClose();
|
---|
377 | }
|
---|
378 | }
|
---|
379 |
|
---|
380 | public void sendDisconnect(String reason) {
|
---|
381 | try {
|
---|
382 | SSHPduOutputStream pdu = new SSHPduOutputStream(MSG_DISCONNECT, sndCipher);
|
---|
383 | pdu.writeString(reason);
|
---|
384 | if(txQueue != null)
|
---|
385 | txQueue.putFirst(pdu);
|
---|
386 | Thread.sleep(300);
|
---|
387 | disconnect(reason);
|
---|
388 | } catch (Exception e) {
|
---|
389 | alert("Error in sendDisconnect: " + e.toString());
|
---|
390 | }
|
---|
391 | }
|
---|
392 |
|
---|
393 | public void alert(String msg) {
|
---|
394 | if(sshHook.isAnSSHClient) {
|
---|
395 | SSHInteractor interactor = sshAsClient().user.getInteractor();
|
---|
396 | if(interactor != null)
|
---|
397 | interactor.alert(msg);
|
---|
398 | } else {
|
---|
399 | SSH.log(msg);
|
---|
400 | }
|
---|
401 | }
|
---|
402 |
|
---|
403 | protected SSHClient sshAsClient() {
|
---|
404 | return (SSHClient)sshHook;
|
---|
405 | }
|
---|
406 |
|
---|
407 | public SSHPduQueue getCnQueue() {
|
---|
408 | return cnQueue;
|
---|
409 | }
|
---|
410 |
|
---|
411 | public void addHostMapTemporary(String fromHost, String toHost, int toPort) {
|
---|
412 | cnChan.addHostMapTemporary(fromHost, toHost, toPort);
|
---|
413 | }
|
---|
414 |
|
---|
415 | public void addHostMapPermanent(String fromHost, String toHost, int toPort) {
|
---|
416 | cnChan.addHostMapPermanent(fromHost, toHost, toPort);
|
---|
417 | }
|
---|
418 |
|
---|
419 | public void delHostMap(String fromHost) {
|
---|
420 | cnChan.delHostMap(fromHost);
|
---|
421 | }
|
---|
422 |
|
---|
423 | public Vector getHostMap(String fromHost) {
|
---|
424 | return cnChan.getHostMap(fromHost);
|
---|
425 | }
|
---|
426 |
|
---|
427 | }
|
---|