public int receiveMessage(byte buffer[], int off, int len) throws IOException { if (recv_packet_header_present == false) { cis.read(recv_packet_header_buffer, 0, 5); } else recv_packet_header_present = false; int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) | ((recv_packet_header_buffer[3] & 0xff)); int padding_length = recv_packet_header_buffer[4] & 0xff; if (packet_length > 35000 || packet_length < 12) throw new IOException("Illegal packet size! (" + packet_length + ")"); int payload_length = packet_length - padding_length - 1; if (payload_length < 0) throw new IOException( "Illegal padding_length in packet from remote (" + padding_length + ")"); if (payload_length >= len) throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); cis.read(buffer, off, payload_length); cis.read(recv_padding_buffer, 0, padding_length); if (recv_mac != null) { cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); recv_mac.initMac(recv_seq_number); recv_mac.update(recv_packet_header_buffer, 0, 5); recv_mac.update(buffer, off, payload_length); recv_mac.update(recv_padding_buffer, 0, padding_length); recv_mac.getMac(recv_mac_buffer_cmp, 0); for (int i = 0; i < recv_mac_buffer.length; i++) { if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) throw new IOException("Remote sent corrupt MAC."); } } recv_seq_number++; if (log.isEnabled()) { log.log( 90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + " bytes payload"); } return payload_length; }
public void requestCancelGlobalForward(int bindPort) throws IOException { RemoteForwardingData rfd = null; synchronized (remoteForwardings) { rfd = remoteForwardings.get(new Integer(bindPort)); if (rfd == null) throw new IOException( "Sorry, there is no known remote forwarding for remote port " + bindPort); } synchronized (channels) { globalSuccessCounter = globalFailedCounter = 0; } PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, rfd.bindPort); tm.sendMessage(pgcf.getPayload()); log.debug( "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); waitForGlobalSuccessOrFailure(); /* Only now we are sure that no more forwarded connections will arrive */ synchronized (remoteForwardings) { remoteForwardings.remove(rfd); } }
public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException { PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); Channel c = getChannel(sm.recipientChannelID); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + sm.recipientChannelID); synchronized (c) { if (c.state != Channel.STATE_OPENING) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + sm.recipientChannelID); c.remoteID = sm.senderChannelID; c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = sm.maxPacketSize; c.state = Channel.STATE_OPEN; c.notifyAll(); } log.debug( "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + sm.senderChannelID + ")"); }
public void msgChannelClose(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed("Close requested by remote"); c.closeMessageRecv = true; removeChannel(c.localID); c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); }
/** * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings */ public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException { PacketSessionExecCommand sm; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException( "Cannot execute command on this channel (" + c.getReasonClosed() + ")"); sm = new PacketSessionExecCommand(c.remoteID, true, cmd); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException( "Cannot execute command on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(sm.getPayload(charsetName)); } log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The execute request failed.").initCause(e); } }
public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) { if (hexFakeCookie == null) throw new IllegalStateException("hexFakeCookie may not be null"); synchronized (x11_magic_cookies) { x11_magic_cookies.remove(hexFakeCookie); } if (killChannels == false) return; log.debug("Closing all X11 channels for the given fake cookie"); List<Channel> channel_copy = new Vector<Channel>(); synchronized (channels) { channel_copy.addAll(channels); } for (Channel c : channel_copy) { synchronized (c) { if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) continue; } try { closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); } catch (IOException ignored) { } } }
public void closeChannel(Channel c, String reason, boolean force) throws IOException { byte msg[] = new byte[5]; synchronized (c) { if (force) { c.state = Channel.STATE_CLOSED; c.EOF = true; } c.setReasonClosed(reason); msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; msg[1] = (byte) (c.remoteID >> 24); msg[2] = (byte) (c.remoteID >> 16); msg[3] = (byte) (c.remoteID >> 8); msg[4] = (byte) (c.remoteID); c.notifyAll(); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) return; tm.sendMessage(msg); c.closeMessageSent = true; } log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); }
public void msgGlobalFailure() throws IOException { synchronized (channels) { globalFailedCounter++; channels.notifyAll(); } log.debug("Got SSH_MSG_REQUEST_FAILURE"); }
public void msgGlobalSuccess() throws IOException { synchronized (channels) { globalSuccessCounter++; channels.notifyAll(); } log.debug("Got SSH_MSG_REQUEST_SUCCESS"); }
public void msgChannelData(byte[] msg, int msglen) throws IOException { if (msglen <= 9) throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); if (len != (msglen - 9)) throw new IOException( "SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + len + ")"); log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); synchronized (c) { if (c.state == Channel.STATE_CLOSED) return; // ignore if (c.state != Channel.STATE_OPEN) throw new IOException( "Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); if (c.localWindow < len) throw new IOException("Remote sent too much data, does not fit into window."); c.localWindow -= len; System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); c.stdoutWritepos += len; c.notifyAll(); } }
public void closeAllChannels() { log.debug("Closing all channels"); List<Channel> channel_copy = new Vector<Channel>(); synchronized (channels) { channel_copy.addAll(channels); } for (Channel c : channel_copy) { try { closeChannel(c, "Closing all channels", true); } catch (IOException ignored) { } } }
public Channel openSessionChannel() throws IOException { Channel c = new Channel(this); synchronized (c) { c.localID = addChannel(c); // end of synchronized block forces the writing out to main memory } log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); tm.sendMessage(smo.getPayload()); waitUntilChannelOpen(c); return c; }
public void run() { synchronized (todolist) { while (true) { if (todolist.size() == 0) { timeoutThread = null; return; } long now = System.currentTimeMillis(); TimeoutToken tt = (TimeoutToken) todolist.getFirst(); if (tt.runTime > now) { /* Not ready yet, sleep a little bit */ try { todolist.wait(tt.runTime - now); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } /* We cannot simply go on, since it could be that the token * was removed (cancelled) or another one has been inserted in * the meantime. */ continue; } todolist.removeFirst(); try { tt.handler.run(); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); log.log( 20, "Exeception in Timeout handler:" + e.getMessage() + "(" + sw.toString() + ")"); } } } }
public void sendEOF(Channel c) throws IOException { byte[] msg = new byte[5]; synchronized (c) { if (c.state != Channel.STATE_OPEN) return; msg[0] = Packets.SSH_MSG_CHANNEL_EOF; msg[1] = (byte) (c.remoteID >> 24); msg[2] = (byte) (c.remoteID >> 16); msg[3] = (byte) (c.remoteID >> 8); msg[4] = (byte) (c.remoteID); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) return; tm.sendMessage(msg); } log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); }
public void msgGlobalRequest(byte[] msg, int msglen) throws IOException { /* Currently we do not support any kind of global request */ TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type String requestName = tr.readString(); boolean wantReply = tr.readBoolean(); if (wantReply) { byte[] reply_failure = new byte[1]; reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; tm.sendAsynchronousMessage(reply_failure); } /* We do not clean up the requestName String - that is OK for debug */ log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); }
public int requestGlobalForward( String bindAddress, int bindPort, String targetAddress, int targetPort) throws IOException { RemoteForwardingData rfd = new RemoteForwardingData(); rfd.bindAddress = bindAddress; rfd.bindPort = bindPort; rfd.targetAddress = targetAddress; rfd.targetPort = targetPort; synchronized (remoteForwardings) { Integer key = new Integer(bindPort); if (remoteForwardings.get(key) != null) { throw new IOException("There is already a forwarding for remote port " + bindPort); } remoteForwardings.put(key, rfd); } synchronized (channels) { globalSuccessCounter = globalFailedCounter = 0; } PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); tm.sendMessage(pgf.getPayload()); log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); try { waitForGlobalSuccessOrFailure(); } catch (IOException e) { synchronized (remoteForwardings) { remoteForwardings.remove(rfd); } throw e; } return bindPort; }
public void requestX11( Channel c, boolean singleConnection, String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) throws IOException { PacketSessionX11Request psr; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); psr = new PacketSessionX11Request( c.remoteID, true, singleConnection, x11AuthenticationProtocol, x11AuthenticationCookie, x11ScreenNumber); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(psr.getPayload()); } log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The X11 request failed.").initCause(e); } }
public void msgChannelFailure(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); synchronized (c) { c.failedCounter++; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); }
public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException { if (msglen != 9) throw new IOException( "SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); synchronized (c) { final long huge = 0xFFFFffffL; /* 2^32 - 1 */ c.remoteWindow += (windowChange & huge); /* avoid sign extension */ /* TODO - is this a good heuristic? */ if ((c.remoteWindow > huge)) c.remoteWindow = huge; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); }
/** * TransportConnection. * * @author Christian Plattner * @version 2.50, 03/15/10 */ public class TransportConnection { private static final Logger log = Logger.getLogger(TransportConnection.class); int send_seq_number = 0; int recv_seq_number = 0; CipherInputStream cis; CipherOutputStream cos; boolean useRandomPadding = false; /* Depends on current MAC and CIPHER */ MAC send_mac; byte[] send_mac_buffer; int send_padd_blocksize = 8; MAC recv_mac; byte[] recv_mac_buffer; byte[] recv_mac_buffer_cmp; int recv_padd_blocksize = 8; /* won't change */ final byte[] send_padding_buffer = new byte[256]; final byte[] send_packet_header_buffer = new byte[5]; final byte[] recv_padding_buffer = new byte[256]; final byte[] recv_packet_header_buffer = new byte[5]; boolean recv_packet_header_present = false; ClientServerHello csh; final SecureRandom rnd; public TransportConnection(InputStream is, OutputStream os, SecureRandom rnd) { this.cis = new CipherInputStream(new NullCipher(), is); this.cos = new CipherOutputStream(new NullCipher(), os); this.rnd = rnd; } public void changeRecvCipher(BlockCipher bc, MAC mac) { cis.changeCipher(bc); recv_mac = mac; recv_mac_buffer = (mac != null) ? new byte[mac.size()] : null; recv_mac_buffer_cmp = (mac != null) ? new byte[mac.size()] : null; recv_padd_blocksize = bc.getBlockSize(); if (recv_padd_blocksize < 8) recv_padd_blocksize = 8; } public void changeSendCipher(BlockCipher bc, MAC mac) { if ((bc instanceof NullCipher) == false) { /* Only use zero byte padding for the first few packets */ useRandomPadding = true; /* Once we start encrypting, there is no way back */ } cos.changeCipher(bc); send_mac = mac; send_mac_buffer = (mac != null) ? new byte[mac.size()] : null; send_padd_blocksize = bc.getBlockSize(); if (send_padd_blocksize < 8) send_padd_blocksize = 8; } public void sendMessage(byte[] message) throws IOException { sendMessage(message, 0, message.length, 0); } public void sendMessage(byte[] message, int off, int len) throws IOException { sendMessage(message, off, len, 0); } public int getPacketOverheadEstimate() { // return an estimate for the paket overhead (for send operations) return 5 + 4 + (send_padd_blocksize - 1) + send_mac_buffer.length; } public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { if (padd < 4) padd = 4; else if (padd > 64) padd = 64; int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ int slack = packet_len % send_padd_blocksize; if (slack != 0) { packet_len += (send_padd_blocksize - slack); } if (packet_len < 16) packet_len = 16; int padd_len = packet_len - (5 + len); if (useRandomPadding) { for (int i = 0; i < padd_len; i = i + 4) { /* * don't waste calls to rnd.nextInt() (by using only 8bit of the * output). just believe me: even though we may write here up to 3 * bytes which won't be used, there is no "buffer overflow" (i.e., * arrayindexoutofbounds). the padding buffer is big enough =) (256 * bytes, and that is bigger than any current cipher block size + 64). */ int r = rnd.nextInt(); send_padding_buffer[i] = (byte) r; send_padding_buffer[i + 1] = (byte) (r >> 8); send_padding_buffer[i + 2] = (byte) (r >> 16); send_padding_buffer[i + 3] = (byte) (r >> 24); } } else { /* use zero padding for unencrypted traffic */ for (int i = 0; i < padd_len; i++) send_padding_buffer[i] = 0; /* Actually this code is paranoid: we never filled any * bytes into the padding buffer so far, therefore it should * consist of zeros only. */ } send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24); send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16); send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8); send_packet_header_buffer[3] = (byte) ((packet_len - 4)); send_packet_header_buffer[4] = (byte) padd_len; cos.write(send_packet_header_buffer, 0, 5); cos.write(message, off, len); cos.write(send_padding_buffer, 0, padd_len); if (send_mac != null) { send_mac.initMac(send_seq_number); send_mac.update(send_packet_header_buffer, 0, 5); send_mac.update(message, off, len); send_mac.update(send_padding_buffer, 0, padd_len); send_mac.getMac(send_mac_buffer, 0); cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); } cos.flush(); if (log.isEnabled()) { log.log( 90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); } send_seq_number++; } public int peekNextMessageLength() throws IOException { if (recv_packet_header_present == false) { cis.read(recv_packet_header_buffer, 0, 5); recv_packet_header_present = true; } int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) | ((recv_packet_header_buffer[3] & 0xff)); int padding_length = recv_packet_header_buffer[4] & 0xff; if (packet_length > 35000 || packet_length < 12) throw new IOException("Illegal packet size! (" + packet_length + ")"); int payload_length = packet_length - padding_length - 1; if (payload_length < 0) throw new IOException( "Illegal padding_length in packet from remote (" + padding_length + ")"); return payload_length; } public int receiveMessage(byte buffer[], int off, int len) throws IOException { if (recv_packet_header_present == false) { cis.read(recv_packet_header_buffer, 0, 5); } else recv_packet_header_present = false; int packet_length = ((recv_packet_header_buffer[0] & 0xff) << 24) | ((recv_packet_header_buffer[1] & 0xff) << 16) | ((recv_packet_header_buffer[2] & 0xff) << 8) | ((recv_packet_header_buffer[3] & 0xff)); int padding_length = recv_packet_header_buffer[4] & 0xff; if (packet_length > 35000 || packet_length < 12) throw new IOException("Illegal packet size! (" + packet_length + ")"); int payload_length = packet_length - padding_length - 1; if (payload_length < 0) throw new IOException( "Illegal padding_length in packet from remote (" + padding_length + ")"); if (payload_length >= len) throw new IOException("Receive buffer too small (" + len + ", need " + payload_length + ")"); cis.read(buffer, off, payload_length); cis.read(recv_padding_buffer, 0, padding_length); if (recv_mac != null) { cis.readPlain(recv_mac_buffer, 0, recv_mac_buffer.length); recv_mac.initMac(recv_seq_number); recv_mac.update(recv_packet_header_buffer, 0, 5); recv_mac.update(buffer, off, payload_length); recv_mac.update(recv_padding_buffer, 0, padding_length); recv_mac.getMac(recv_mac_buffer_cmp, 0); for (int i = 0; i < recv_mac_buffer.length; i++) { if (recv_mac_buffer[i] != recv_mac_buffer_cmp[i]) throw new IOException("Remote sent corrupt MAC."); } } recv_seq_number++; if (log.isEnabled()) { log.log( 90, "Received " + Packets.getMessageName(buffer[off] & 0xff) + " " + payload_length + " bytes payload"); } return payload_length; } }
public void sendMessage(byte[] message, int off, int len, int padd) throws IOException { if (padd < 4) padd = 4; else if (padd > 64) padd = 64; int packet_len = 5 + len + padd; /* Minimum allowed padding is 4 */ int slack = packet_len % send_padd_blocksize; if (slack != 0) { packet_len += (send_padd_blocksize - slack); } if (packet_len < 16) packet_len = 16; int padd_len = packet_len - (5 + len); if (useRandomPadding) { for (int i = 0; i < padd_len; i = i + 4) { /* * don't waste calls to rnd.nextInt() (by using only 8bit of the * output). just believe me: even though we may write here up to 3 * bytes which won't be used, there is no "buffer overflow" (i.e., * arrayindexoutofbounds). the padding buffer is big enough =) (256 * bytes, and that is bigger than any current cipher block size + 64). */ int r = rnd.nextInt(); send_padding_buffer[i] = (byte) r; send_padding_buffer[i + 1] = (byte) (r >> 8); send_padding_buffer[i + 2] = (byte) (r >> 16); send_padding_buffer[i + 3] = (byte) (r >> 24); } } else { /* use zero padding for unencrypted traffic */ for (int i = 0; i < padd_len; i++) send_padding_buffer[i] = 0; /* Actually this code is paranoid: we never filled any * bytes into the padding buffer so far, therefore it should * consist of zeros only. */ } send_packet_header_buffer[0] = (byte) ((packet_len - 4) >> 24); send_packet_header_buffer[1] = (byte) ((packet_len - 4) >> 16); send_packet_header_buffer[2] = (byte) ((packet_len - 4) >> 8); send_packet_header_buffer[3] = (byte) ((packet_len - 4)); send_packet_header_buffer[4] = (byte) padd_len; cos.write(send_packet_header_buffer, 0, 5); cos.write(message, off, len); cos.write(send_padding_buffer, 0, padd_len); if (send_mac != null) { send_mac.initMac(send_seq_number); send_mac.update(send_packet_header_buffer, 0, 5); send_mac.update(message, off, len); send_mac.update(send_padding_buffer, 0, padd_len); send_mac.getMac(send_mac_buffer, 0); cos.writePlain(send_mac_buffer, 0, send_mac_buffer.length); } cos.flush(); if (log.isEnabled()) { log.log( 90, "Sent " + Packets.getMessageName(message[off] & 0xff) + " " + len + " bytes payload"); } send_seq_number++; }
public void msgChannelOpen(byte[] msg, int msglen) throws IOException { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type String channelType = tr.readString(); int remoteID = tr.readUINT32(); /* sender channel */ int remoteWindow = tr.readUINT32(); /* initial window size */ int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ if ("x11".equals(channelType)) { synchronized (x11_magic_cookies) { /* If we did not request X11 forwarding, then simply ignore this bogus request. */ if (x11_magic_cookies.size() == 0) { PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); tm.sendAsynchronousMessage(pcof.getPayload()); log.warning("Unexpected X11 request, denying it!"); return; } } String remoteOriginatorAddress = tr.readString(); int remoteOriginatorPort = tr.readUINT32(); Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); } /* * The open confirmation message will be sent from another thread */ RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); rxat.setDaemon(true); rxat.start(); return; } if ("forwarded-tcpip".equals(channelType)) { String remoteConnectedAddress = tr.readString(); /* address that was connected */ int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ String remoteOriginatorAddress = tr.readString(); /* originator IP address */ int remoteOriginatorPort = tr.readUINT32(); /* originator port */ RemoteForwardingData rfd = null; synchronized (remoteForwardings) { rfd = remoteForwardings.get(new Integer(remoteConnectedPort)); } if (rfd == null) { PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "No thanks, unknown port in forwarded-tcpip request", ""); /* Always try to be polite. */ tm.sendAsynchronousMessage(pcof.getPayload()); log.debug("Unexpected forwarded-tcpip request, denying it!"); return; } Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); } /* * The open confirmation message will be sent from another thread. */ RemoteAcceptThread rat = new RemoteAcceptThread( c, remoteConnectedAddress, remoteConnectedPort, remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); rat.setDaemon(true); rat.start(); return; } if ((server_state != null) && ("session".equals(channelType))) { ServerConnectionCallback cb = null; synchronized (server_state) { cb = server_state.cb_conn; } if (cb == null) { tm.sendAsynchronousMessage( new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "Sessions are currently not enabled", "en") .getPayload()); return; } final Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); c.state = Channel.STATE_OPEN; c.ss = new ServerSessionImpl(c); } PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation( c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); tm.sendAsynchronousMessage(pcoc.getPayload()); c.ss.sscb = cb.acceptSession(c.ss); return; } /* Tell the server that we have no idea what it is talking about */ PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, "Unknown channel type", ""); tm.sendAsynchronousMessage(pcof.getPayload()); log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); }
public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException { boolean wasInterrupted = false; try { int copylen = 0; int increment = 0; int remoteID = 0; int localID = 0; synchronized (c) { int stdoutAvail = 0; int stderrAvail = 0; while (true) { /* * Data available? We have to return remaining data even if the * channel is already closed. */ stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; stderrAvail = c.stderrWritepos - c.stderrReadpos; if ((!extended) && (stdoutAvail != 0)) break; if ((extended) && (stderrAvail != 0)) break; /* Do not wait if more data will never arrive (EOF or CLOSED) */ if ((c.EOF) || (c.state != Channel.STATE_OPEN)) return -1; try { c.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } /* OK, there is some data. Return it. */ if (!extended) { copylen = (stdoutAvail > len) ? len : stdoutAvail; System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); c.stdoutReadpos += copylen; if (c.stdoutReadpos != c.stdoutWritepos) System.arraycopy( c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - c.stdoutReadpos); c.stdoutWritepos -= c.stdoutReadpos; c.stdoutReadpos = 0; } else { copylen = (stderrAvail > len) ? len : stderrAvail; System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); c.stderrReadpos += copylen; if (c.stderrReadpos != c.stderrWritepos) System.arraycopy( c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - c.stderrReadpos); c.stderrWritepos -= c.stderrReadpos; c.stderrReadpos = 0; } if (c.state != Channel.STATE_OPEN) return copylen; if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) { int minFreeSpace = Math.min( Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); increment = minFreeSpace - c.localWindow; c.localWindow = minFreeSpace; } remoteID = c.remoteID; /* read while holding the lock */ localID = c.localID; /* read while holding the lock */ } /* * If a consumer reads stdout and stdin in parallel, we may end up with * sending two msgWindowAdjust messages. Luckily, it * does not matter in which order they arrive at the server. */ if (increment > 0) { log.debug( "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); synchronized (c.channelSendLock) { byte[] msg = c.msgWindowAdjust; msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; msg[1] = (byte) (remoteID >> 24); msg[2] = (byte) (remoteID >> 16); msg[3] = (byte) (remoteID >> 8); msg[4] = (byte) (remoteID); msg[5] = (byte) (increment >> 24); msg[6] = (byte) (increment >> 16); msg[7] = (byte) (increment >> 8); msg[8] = (byte) (increment); if (c.closeMessageSent == false) tm.sendMessage(msg); } } return copylen; } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } }
public void msgChannelRequest(byte[] msg, int msglen) throws IOException { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type int id = tr.readUINT32(); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); ServerSessionImpl server_session = null; if (server_state != null) { synchronized (c) { server_session = c.ss; } } String type = tr.readString("US-ASCII"); boolean wantReply = tr.readBoolean(); log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); if (type.equals("exit-status")) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); int exit_status = tr.readUINT32(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_status = new Integer(exit_status); c.notifyAll(); } log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); return; } if ((server_state == null) && (type.equals("exit-signal"))) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); String signame = tr.readString("US-ASCII"); tr.readBoolean(); tr.readString(); tr.readString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_signal = signame; c.notifyAll(); } log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); return; } if ((server_session != null) && (type.equals("pty-req"))) { PtySettings pty = new PtySettings(); pty.term = tr.readString(); pty.term_width_characters = tr.readUINT32(); pty.term_height_characters = tr.readUINT32(); pty.term_width_pixels = tr.readUINT32(); pty.term_height_pixels = tr.readUINT32(); pty.terminal_modes = tr.readByteString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestPtyReq(server_session, pty); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } if ((server_session != null) && (type.equals("shell"))) { if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestShell(server_session); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } if ((server_session != null) && (type.equals("exec"))) { String command = tr.readString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestExec(server_session, command); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } /* We simply ignore unknown channel requests, however, if the server wants a reply, * then we signal that we have no idea what it is about. */ if (wantReply) { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } log.debug("Channel request '" + type + "' is not known, ignoring it"); }
public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException { if (msglen < 5) throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type int id = tr.readUINT32(); /* sender channel */ Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); int reasonCode = tr.readUINT32(); String description = tr.readString("UTF-8"); String reasonCodeSymbolicName = null; switch (reasonCode) { case 1: reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; break; case 2: reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; break; case 3: reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; break; case 4: reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; break; default: reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; } StringBuilder descriptionBuffer = new StringBuilder(); descriptionBuffer.append(description); for (int i = 0; i < descriptionBuffer.length(); i++) { char cc = descriptionBuffer.charAt(i); if ((cc >= 32) && (cc <= 126)) continue; descriptionBuffer.setCharAt(i, '\uFFFD'); } synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed( "The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + descriptionBuffer.toString() + "')"); c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); }
/** * ChannelManager. Please read the comments in Channel.java. * * <p>Besides the crypto part, this is the core of the library. * * @author Christian Plattner * @version $Id: ChannelManager.java 48 2013-08-01 12:22:33Z [email protected] $ */ public class ChannelManager implements MessageHandler { private static final Logger log = Logger.getLogger(ChannelManager.class); private final ServerConnectionState server_state; private final TransportManager tm; private final HashMap<String, X11ServerData> x11_magic_cookies = new HashMap<String, X11ServerData>(); private final List<Channel> channels = new Vector<Channel>(); private int nextLocalChannel = 100; private boolean shutdown = false; private int globalSuccessCounter = 0; private int globalFailedCounter = 0; private final HashMap<Integer, RemoteForwardingData> remoteForwardings = new HashMap<Integer, RemoteForwardingData>(); private final List<IChannelWorkerThread> listenerThreads = new Vector<IChannelWorkerThread>(); private boolean listenerThreadsAllowed = true; /** * Constructor for client-mode. * * @param tm */ public ChannelManager(TransportManager tm) { this.server_state = null; this.tm = tm; tm.registerMessageHandler(this, 80, 100); } /** * Constructor for server-mode. * * @param state */ public ChannelManager(ServerConnectionState state) { this.server_state = state; this.tm = state.tm; tm.registerMessageHandler(this, 80, 100); } private Channel getChannel(int id) { synchronized (channels) { for (Channel c : channels) { if (c.localID == id) return c; } } return null; } private void removeChannel(int id) { synchronized (channels) { for (Channel c : channels) { if (c.localID == id) { channels.remove(c); break; } } } } private int addChannel(Channel c) { synchronized (channels) { channels.add(c); return nextLocalChannel++; } } private void waitUntilChannelOpen(Channel c) throws IOException { boolean wasInterrupted = false; synchronized (c) { while (c.state == Channel.STATE_OPENING) { try { c.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } if (c.state != Channel.STATE_OPEN) { removeChannel(c.localID); String detail = c.getReasonClosed(); if (detail == null) detail = "state: " + c.state; throw new IOException("Could not open channel (" + detail + ")"); } } if (wasInterrupted) Thread.currentThread().interrupt(); } private void waitForGlobalSuccessOrFailure() throws IOException { boolean wasInterrupted = false; try { synchronized (channels) { while ((globalSuccessCounter == 0) && (globalFailedCounter == 0)) { if (shutdown) { throw new IOException("The connection is being shutdown"); } try { channels.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } if (globalFailedCounter != 0) { throw new IOException("The server denied the request (did you enable port forwarding?)"); } if (globalSuccessCounter == 0) { throw new IOException("Illegal state."); } } } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } private void waitForChannelSuccessOrFailure(Channel c) throws IOException { boolean wasInterrupted = false; try { synchronized (c) { while ((c.successCounter == 0) && (c.failedCounter == 0)) { if (c.state != Channel.STATE_OPEN) { String detail = c.getReasonClosed(); if (detail == null) detail = "state: " + c.state; throw new IOException("This SSH2 channel is not open (" + detail + ")"); } try { c.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } if (c.failedCounter != 0) { throw new IOException("The server denied the request."); } } } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } public void registerX11Cookie(String hexFakeCookie, X11ServerData data) { synchronized (x11_magic_cookies) { x11_magic_cookies.put(hexFakeCookie, data); } } public void unRegisterX11Cookie(String hexFakeCookie, boolean killChannels) { if (hexFakeCookie == null) throw new IllegalStateException("hexFakeCookie may not be null"); synchronized (x11_magic_cookies) { x11_magic_cookies.remove(hexFakeCookie); } if (killChannels == false) return; log.debug("Closing all X11 channels for the given fake cookie"); List<Channel> channel_copy = new Vector<Channel>(); synchronized (channels) { channel_copy.addAll(channels); } for (Channel c : channel_copy) { synchronized (c) { if (hexFakeCookie.equals(c.hexX11FakeCookie) == false) continue; } try { closeChannel(c, "Closing X11 channel since the corresponding session is closing", true); } catch (IOException ignored) { } } } public X11ServerData checkX11Cookie(String hexFakeCookie) { synchronized (x11_magic_cookies) { if (hexFakeCookie != null) return x11_magic_cookies.get(hexFakeCookie); } return null; } public void closeAllChannels() { log.debug("Closing all channels"); List<Channel> channel_copy = new Vector<Channel>(); synchronized (channels) { channel_copy.addAll(channels); } for (Channel c : channel_copy) { try { closeChannel(c, "Closing all channels", true); } catch (IOException ignored) { } } } public void closeChannel(Channel c, String reason, boolean force) throws IOException { byte msg[] = new byte[5]; synchronized (c) { if (force) { c.state = Channel.STATE_CLOSED; c.EOF = true; } c.setReasonClosed(reason); msg[0] = Packets.SSH_MSG_CHANNEL_CLOSE; msg[1] = (byte) (c.remoteID >> 24); msg[2] = (byte) (c.remoteID >> 16); msg[3] = (byte) (c.remoteID >> 8); msg[4] = (byte) (c.remoteID); c.notifyAll(); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) return; tm.sendMessage(msg); c.closeMessageSent = true; } log.debug("Sent SSH_MSG_CHANNEL_CLOSE (channel " + c.localID + ")"); } public void sendEOF(Channel c) throws IOException { byte[] msg = new byte[5]; synchronized (c) { if (c.state != Channel.STATE_OPEN) return; msg[0] = Packets.SSH_MSG_CHANNEL_EOF; msg[1] = (byte) (c.remoteID >> 24); msg[2] = (byte) (c.remoteID >> 16); msg[3] = (byte) (c.remoteID >> 8); msg[4] = (byte) (c.remoteID); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) return; tm.sendMessage(msg); } log.debug("Sent EOF (Channel " + c.localID + "/" + c.remoteID + ")"); } public void sendOpenConfirmation(Channel c) throws IOException { PacketChannelOpenConfirmation pcoc = null; synchronized (c) { if (c.state != Channel.STATE_OPENING) return; c.state = Channel.STATE_OPEN; pcoc = new PacketChannelOpenConfirmation( c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) return; tm.sendMessage(pcoc.getPayload()); } } public void sendData(Channel c, byte[] buffer, int pos, int len) throws IOException { boolean wasInterrupted = false; try { while (len > 0) { int thislen = 0; byte[] msg; synchronized (c) { while (true) { if (c.state == Channel.STATE_CLOSED) throw new ChannelClosedException( "SSH channel is closed. (" + c.getReasonClosed() + ")"); if (c.state != Channel.STATE_OPEN) throw new ChannelClosedException("SSH channel in strange state. (" + c.state + ")"); if (c.remoteWindow != 0) break; try { c.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } /* len > 0, no sign extension can happen when comparing */ thislen = (c.remoteWindow >= len) ? len : (int) c.remoteWindow; int estimatedMaxDataLen = c.remoteMaxPacketSize - (tm.getPacketOverheadEstimate() + 9); /* The worst case scenario =) a true bottleneck */ if (estimatedMaxDataLen <= 0) { estimatedMaxDataLen = 1; } if (thislen > estimatedMaxDataLen) thislen = estimatedMaxDataLen; c.remoteWindow -= thislen; msg = new byte[1 + 8 + thislen]; msg[0] = Packets.SSH_MSG_CHANNEL_DATA; msg[1] = (byte) (c.remoteID >> 24); msg[2] = (byte) (c.remoteID >> 16); msg[3] = (byte) (c.remoteID >> 8); msg[4] = (byte) (c.remoteID); msg[5] = (byte) (thislen >> 24); msg[6] = (byte) (thislen >> 16); msg[7] = (byte) (thislen >> 8); msg[8] = (byte) (thislen); System.arraycopy(buffer, pos, msg, 9, thislen); } synchronized (c.channelSendLock) { if (c.closeMessageSent == true) throw new ChannelClosedException( "SSH channel is closed. (" + c.getReasonClosed() + ")"); tm.sendMessage(msg); } pos += thislen; len -= thislen; } } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } public int requestGlobalForward( String bindAddress, int bindPort, String targetAddress, int targetPort) throws IOException { RemoteForwardingData rfd = new RemoteForwardingData(); rfd.bindAddress = bindAddress; rfd.bindPort = bindPort; rfd.targetAddress = targetAddress; rfd.targetPort = targetPort; synchronized (remoteForwardings) { Integer key = new Integer(bindPort); if (remoteForwardings.get(key) != null) { throw new IOException("There is already a forwarding for remote port " + bindPort); } remoteForwardings.put(key, rfd); } synchronized (channels) { globalSuccessCounter = globalFailedCounter = 0; } PacketGlobalForwardRequest pgf = new PacketGlobalForwardRequest(true, bindAddress, bindPort); tm.sendMessage(pgf.getPayload()); log.debug("Requesting a remote forwarding ('" + bindAddress + "', " + bindPort + ")"); try { waitForGlobalSuccessOrFailure(); } catch (IOException e) { synchronized (remoteForwardings) { remoteForwardings.remove(rfd); } throw e; } return bindPort; } public void requestCancelGlobalForward(int bindPort) throws IOException { RemoteForwardingData rfd = null; synchronized (remoteForwardings) { rfd = remoteForwardings.get(new Integer(bindPort)); if (rfd == null) throw new IOException( "Sorry, there is no known remote forwarding for remote port " + bindPort); } synchronized (channels) { globalSuccessCounter = globalFailedCounter = 0; } PacketGlobalCancelForwardRequest pgcf = new PacketGlobalCancelForwardRequest(true, rfd.bindAddress, rfd.bindPort); tm.sendMessage(pgcf.getPayload()); log.debug( "Requesting cancelation of remote forward ('" + rfd.bindAddress + "', " + rfd.bindPort + ")"); waitForGlobalSuccessOrFailure(); /* Only now we are sure that no more forwarded connections will arrive */ synchronized (remoteForwardings) { remoteForwardings.remove(rfd); } } public void registerThread(IChannelWorkerThread thr) throws IOException { synchronized (listenerThreads) { if (listenerThreadsAllowed == false) throw new IOException("Too late, this connection is closed."); listenerThreads.add(thr); } } public Channel openDirectTCPIPChannel( String host_to_connect, int port_to_connect, String originator_IP_address, int originator_port) throws IOException { Channel c = new Channel(this); synchronized (c) { c.localID = addChannel(c); // end of synchronized block forces writing out to main memory } PacketOpenDirectTCPIPChannel dtc = new PacketOpenDirectTCPIPChannel( c.localID, c.localWindow, c.localMaxPacketSize, host_to_connect, port_to_connect, originator_IP_address, originator_port); tm.sendMessage(dtc.getPayload()); waitUntilChannelOpen(c); return c; } public Channel openSessionChannel() throws IOException { Channel c = new Channel(this); synchronized (c) { c.localID = addChannel(c); // end of synchronized block forces the writing out to main memory } log.debug("Sending SSH_MSG_CHANNEL_OPEN (Channel " + c.localID + ")"); PacketOpenSessionChannel smo = new PacketOpenSessionChannel(c.localID, c.localWindow, c.localMaxPacketSize); tm.sendMessage(smo.getPayload()); waitUntilChannelOpen(c); return c; } public void requestPTY( Channel c, String term, int term_width_characters, int term_height_characters, int term_width_pixels, int term_height_pixels, byte[] terminal_modes) throws IOException { PacketSessionPtyRequest spr; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); spr = new PacketSessionPtyRequest( c.remoteID, true, term, term_width_characters, term_height_characters, term_width_pixels, term_height_pixels, terminal_modes); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException("Cannot request PTY on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(spr.getPayload()); } try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("PTY request failed").initCause(e); } } public void requestX11( Channel c, boolean singleConnection, String x11AuthenticationProtocol, String x11AuthenticationCookie, int x11ScreenNumber) throws IOException { PacketSessionX11Request psr; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); psr = new PacketSessionX11Request( c.remoteID, true, singleConnection, x11AuthenticationProtocol, x11AuthenticationCookie, x11ScreenNumber); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException("Cannot request X11 on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(psr.getPayload()); } log.debug("Requesting X11 forwarding (Channel " + c.localID + "/" + c.remoteID + ")"); try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The X11 request failed.").initCause(e); } } public void requestSubSystem(Channel c, String subSystemName) throws IOException { PacketSessionSubsystemRequest ssr; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException( "Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); ssr = new PacketSessionSubsystemRequest(c.remoteID, true, subSystemName); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException( "Cannot request subsystem on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(ssr.getPayload()); } try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The subsystem request failed.").initCause(e); } } public void requestExecCommand(Channel c, String cmd) throws IOException { this.requestExecCommand(c, cmd, null); } /** * @param charsetName The charset used to convert between Java Unicode Strings and byte encodings */ public void requestExecCommand(Channel c, String cmd, String charsetName) throws IOException { PacketSessionExecCommand sm; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException( "Cannot execute command on this channel (" + c.getReasonClosed() + ")"); sm = new PacketSessionExecCommand(c.remoteID, true, cmd); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException( "Cannot execute command on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(sm.getPayload(charsetName)); } log.debug("Executing command (channel " + c.localID + ", '" + cmd + "')"); try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The execute request failed.").initCause(e); } } public void requestShell(Channel c) throws IOException { PacketSessionStartShell sm; synchronized (c) { if (c.state != Channel.STATE_OPEN) throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); sm = new PacketSessionStartShell(c.remoteID, true); c.successCounter = c.failedCounter = 0; } synchronized (c.channelSendLock) { if (c.closeMessageSent) throw new IOException("Cannot start shell on this channel (" + c.getReasonClosed() + ")"); tm.sendMessage(sm.getPayload()); } try { waitForChannelSuccessOrFailure(c); } catch (IOException e) { throw (IOException) new IOException("The shell request failed.").initCause(e); } } public void msgChannelExtendedData(byte[] msg, int msglen) throws IOException { if (msglen <= 13) throw new IOException( "SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); int dataType = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); int len = ((msg[9] & 0xff) << 24) | ((msg[10] & 0xff) << 16) | ((msg[11] & 0xff) << 8) | (msg[12] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_EXTENDED_DATA message for non-existent channel " + id); if (dataType != Packets.SSH_EXTENDED_DATA_STDERR) throw new IOException( "SSH_MSG_CHANNEL_EXTENDED_DATA message has unknown type (" + dataType + ")"); if (len != (msglen - 13)) throw new IOException( "SSH_MSG_CHANNEL_EXTENDED_DATA message has wrong len (calculated " + (msglen - 13) + ", got " + len + ")"); log.debug("Got SSH_MSG_CHANNEL_EXTENDED_DATA (channel " + id + ", " + len + ")"); synchronized (c) { if (c.state == Channel.STATE_CLOSED) return; // ignore if (c.state != Channel.STATE_OPEN) throw new IOException( "Got SSH_MSG_CHANNEL_EXTENDED_DATA, but channel is not in correct state (" + c.state + ")"); if (c.localWindow < len) throw new IOException("Remote sent too much data, does not fit into window."); c.localWindow -= len; System.arraycopy(msg, 13, c.stderrBuffer, c.stderrWritepos, len); c.stderrWritepos += len; c.notifyAll(); } } /** * Wait until for a condition. * * @param c Channel * @param timeout in ms, 0 means no timeout. * @param condition_mask minimum event mask (at least one of the conditions must be fulfilled) * @return all current events */ public int waitForCondition(Channel c, long timeout, int condition_mask) { boolean wasInterrupted = false; try { long end_time = 0; boolean end_time_set = false; synchronized (c) { while (true) { int current_cond = 0; int stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; int stderrAvail = c.stderrWritepos - c.stderrReadpos; if (stdoutAvail > 0) current_cond = current_cond | ChannelCondition.STDOUT_DATA; if (stderrAvail > 0) current_cond = current_cond | ChannelCondition.STDERR_DATA; if (c.EOF) current_cond = current_cond | ChannelCondition.EOF; if (c.getExitStatus() != null) current_cond = current_cond | ChannelCondition.EXIT_STATUS; if (c.getExitSignal() != null) current_cond = current_cond | ChannelCondition.EXIT_SIGNAL; if (c.state == Channel.STATE_CLOSED) return current_cond | ChannelCondition.CLOSED | ChannelCondition.EOF; if ((current_cond & condition_mask) != 0) return current_cond; if (timeout > 0) { if (!end_time_set) { end_time = System.currentTimeMillis() + timeout; end_time_set = true; } else { timeout = end_time - System.currentTimeMillis(); if (timeout <= 0) return current_cond | ChannelCondition.TIMEOUT; } } try { if (timeout > 0) c.wait(timeout); else c.wait(); } catch (InterruptedException e) { wasInterrupted = true; } } } } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } public int getAvailable(Channel c, boolean extended) throws IOException { synchronized (c) { int avail; if (extended) avail = c.stderrWritepos - c.stderrReadpos; else avail = c.stdoutWritepos - c.stdoutReadpos; return ((avail > 0) ? avail : (c.EOF ? -1 : 0)); } } public int getChannelData(Channel c, boolean extended, byte[] target, int off, int len) throws IOException { boolean wasInterrupted = false; try { int copylen = 0; int increment = 0; int remoteID = 0; int localID = 0; synchronized (c) { int stdoutAvail = 0; int stderrAvail = 0; while (true) { /* * Data available? We have to return remaining data even if the * channel is already closed. */ stdoutAvail = c.stdoutWritepos - c.stdoutReadpos; stderrAvail = c.stderrWritepos - c.stderrReadpos; if ((!extended) && (stdoutAvail != 0)) break; if ((extended) && (stderrAvail != 0)) break; /* Do not wait if more data will never arrive (EOF or CLOSED) */ if ((c.EOF) || (c.state != Channel.STATE_OPEN)) return -1; try { c.wait(); } catch (InterruptedException ignore) { wasInterrupted = true; } } /* OK, there is some data. Return it. */ if (!extended) { copylen = (stdoutAvail > len) ? len : stdoutAvail; System.arraycopy(c.stdoutBuffer, c.stdoutReadpos, target, off, copylen); c.stdoutReadpos += copylen; if (c.stdoutReadpos != c.stdoutWritepos) System.arraycopy( c.stdoutBuffer, c.stdoutReadpos, c.stdoutBuffer, 0, c.stdoutWritepos - c.stdoutReadpos); c.stdoutWritepos -= c.stdoutReadpos; c.stdoutReadpos = 0; } else { copylen = (stderrAvail > len) ? len : stderrAvail; System.arraycopy(c.stderrBuffer, c.stderrReadpos, target, off, copylen); c.stderrReadpos += copylen; if (c.stderrReadpos != c.stderrWritepos) System.arraycopy( c.stderrBuffer, c.stderrReadpos, c.stderrBuffer, 0, c.stderrWritepos - c.stderrReadpos); c.stderrWritepos -= c.stderrReadpos; c.stderrReadpos = 0; } if (c.state != Channel.STATE_OPEN) return copylen; if (c.localWindow < ((Channel.CHANNEL_BUFFER_SIZE + 1) / 2)) { int minFreeSpace = Math.min( Channel.CHANNEL_BUFFER_SIZE - c.stdoutWritepos, Channel.CHANNEL_BUFFER_SIZE - c.stderrWritepos); increment = minFreeSpace - c.localWindow; c.localWindow = minFreeSpace; } remoteID = c.remoteID; /* read while holding the lock */ localID = c.localID; /* read while holding the lock */ } /* * If a consumer reads stdout and stdin in parallel, we may end up with * sending two msgWindowAdjust messages. Luckily, it * does not matter in which order they arrive at the server. */ if (increment > 0) { log.debug( "Sending SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + localID + ", " + increment + ")"); synchronized (c.channelSendLock) { byte[] msg = c.msgWindowAdjust; msg[0] = Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST; msg[1] = (byte) (remoteID >> 24); msg[2] = (byte) (remoteID >> 16); msg[3] = (byte) (remoteID >> 8); msg[4] = (byte) (remoteID); msg[5] = (byte) (increment >> 24); msg[6] = (byte) (increment >> 16); msg[7] = (byte) (increment >> 8); msg[8] = (byte) (increment); if (c.closeMessageSent == false) tm.sendMessage(msg); } } return copylen; } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } public void msgChannelData(byte[] msg, int msglen) throws IOException { if (msglen <= 9) throw new IOException("SSH_MSG_CHANNEL_DATA message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); int len = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_DATA message for non-existent channel " + id); if (len != (msglen - 9)) throw new IOException( "SSH_MSG_CHANNEL_DATA message has wrong len (calculated " + (msglen - 9) + ", got " + len + ")"); log.debug("Got SSH_MSG_CHANNEL_DATA (channel " + id + ", " + len + ")"); synchronized (c) { if (c.state == Channel.STATE_CLOSED) return; // ignore if (c.state != Channel.STATE_OPEN) throw new IOException( "Got SSH_MSG_CHANNEL_DATA, but channel is not in correct state (" + c.state + ")"); if (c.localWindow < len) throw new IOException("Remote sent too much data, does not fit into window."); c.localWindow -= len; System.arraycopy(msg, 9, c.stdoutBuffer, c.stdoutWritepos, len); c.stdoutWritepos += len; c.notifyAll(); } } public void msgChannelWindowAdjust(byte[] msg, int msglen) throws IOException { if (msglen != 9) throw new IOException( "SSH_MSG_CHANNEL_WINDOW_ADJUST message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); int windowChange = ((msg[5] & 0xff) << 24) | ((msg[6] & 0xff) << 16) | ((msg[7] & 0xff) << 8) | (msg[8] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_WINDOW_ADJUST message for non-existent channel " + id); synchronized (c) { final long huge = 0xFFFFffffL; /* 2^32 - 1 */ c.remoteWindow += (windowChange & huge); /* avoid sign extension */ /* TODO - is this a good heuristic? */ if ((c.remoteWindow > huge)) c.remoteWindow = huge; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_WINDOW_ADJUST (channel " + id + ", " + windowChange + ")"); } public void msgChannelOpen(byte[] msg, int msglen) throws IOException { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type String channelType = tr.readString(); int remoteID = tr.readUINT32(); /* sender channel */ int remoteWindow = tr.readUINT32(); /* initial window size */ int remoteMaxPacketSize = tr.readUINT32(); /* maximum packet size */ if ("x11".equals(channelType)) { synchronized (x11_magic_cookies) { /* If we did not request X11 forwarding, then simply ignore this bogus request. */ if (x11_magic_cookies.size() == 0) { PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "X11 forwarding not activated", ""); tm.sendAsynchronousMessage(pcof.getPayload()); log.warning("Unexpected X11 request, denying it!"); return; } } String remoteOriginatorAddress = tr.readString(); int remoteOriginatorPort = tr.readUINT32(); Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* properly convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); } /* * The open confirmation message will be sent from another thread */ RemoteX11AcceptThread rxat = new RemoteX11AcceptThread(c, remoteOriginatorAddress, remoteOriginatorPort); rxat.setDaemon(true); rxat.start(); return; } if ("forwarded-tcpip".equals(channelType)) { String remoteConnectedAddress = tr.readString(); /* address that was connected */ int remoteConnectedPort = tr.readUINT32(); /* port that was connected */ String remoteOriginatorAddress = tr.readString(); /* originator IP address */ int remoteOriginatorPort = tr.readUINT32(); /* originator port */ RemoteForwardingData rfd = null; synchronized (remoteForwardings) { rfd = remoteForwardings.get(new Integer(remoteConnectedPort)); } if (rfd == null) { PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "No thanks, unknown port in forwarded-tcpip request", ""); /* Always try to be polite. */ tm.sendAsynchronousMessage(pcof.getPayload()); log.debug("Unexpected forwarded-tcpip request, denying it!"); return; } Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); } /* * The open confirmation message will be sent from another thread. */ RemoteAcceptThread rat = new RemoteAcceptThread( c, remoteConnectedAddress, remoteConnectedPort, remoteOriginatorAddress, remoteOriginatorPort, rfd.targetAddress, rfd.targetPort); rat.setDaemon(true); rat.start(); return; } if ((server_state != null) && ("session".equals(channelType))) { ServerConnectionCallback cb = null; synchronized (server_state) { cb = server_state.cb_conn; } if (cb == null) { tm.sendAsynchronousMessage( new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, "Sessions are currently not enabled", "en") .getPayload()); return; } final Channel c = new Channel(this); synchronized (c) { c.remoteID = remoteID; c.remoteWindow = remoteWindow & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = remoteMaxPacketSize; c.localID = addChannel(c); c.state = Channel.STATE_OPEN; c.ss = new ServerSessionImpl(c); } PacketChannelOpenConfirmation pcoc = new PacketChannelOpenConfirmation( c.remoteID, c.localID, c.localWindow, c.localMaxPacketSize); tm.sendAsynchronousMessage(pcoc.getPayload()); c.ss.sscb = cb.acceptSession(c.ss); return; } /* Tell the server that we have no idea what it is talking about */ PacketChannelOpenFailure pcof = new PacketChannelOpenFailure( remoteID, Packets.SSH_OPEN_UNKNOWN_CHANNEL_TYPE, "Unknown channel type", ""); tm.sendAsynchronousMessage(pcof.getPayload()); log.warning("The peer tried to open an unsupported channel type (" + channelType + ")"); } /* Starts the given runnable in a foreground (non-daemon) thread */ private void runAsync(Runnable r) { Thread t = new Thread(r); t.start(); } public void msgChannelRequest(byte[] msg, int msglen) throws IOException { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type int id = tr.readUINT32(); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); ServerSessionImpl server_session = null; if (server_state != null) { synchronized (c) { server_session = c.ss; } } String type = tr.readString("US-ASCII"); boolean wantReply = tr.readBoolean(); log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); if (type.equals("exit-status")) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-status message, 'want reply' is true"); int exit_status = tr.readUINT32(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_status = new Integer(exit_status); c.notifyAll(); } log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); return; } if ((server_state == null) && (type.equals("exit-signal"))) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST exit-signal message, 'want reply' is true"); String signame = tr.readString("US-ASCII"); tr.readBoolean(); tr.readString(); tr.readString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_signal = signame; c.notifyAll(); } log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); return; } if ((server_session != null) && (type.equals("pty-req"))) { PtySettings pty = new PtySettings(); pty.term = tr.readString(); pty.term_width_characters = tr.readUINT32(); pty.term_height_characters = tr.readUINT32(); pty.term_width_pixels = tr.readUINT32(); pty.term_height_pixels = tr.readUINT32(); pty.terminal_modes = tr.readByteString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestPtyReq(server_session, pty); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } if ((server_session != null) && (type.equals("shell"))) { if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestShell(server_session); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } if ((server_session != null) && (type.equals("exec"))) { String command = tr.readString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); Runnable run_after_sending_success = null; ServerSessionCallback sscb = server_session.getServerSessionCallback(); if (sscb != null) run_after_sending_success = sscb.requestExec(server_session, command); if (wantReply) { if (run_after_sending_success != null) { tm.sendAsynchronousMessage(new PacketChannelSuccess(c.remoteID).getPayload()); } else { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } } if (run_after_sending_success != null) { runAsync(run_after_sending_success); } return; } /* We simply ignore unknown channel requests, however, if the server wants a reply, * then we signal that we have no idea what it is about. */ if (wantReply) { tm.sendAsynchronousMessage(new PacketChannelFailure(c.remoteID).getPayload()); } log.debug("Channel request '" + type + "' is not known, ignoring it"); } public void msgChannelEOF(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_EOF message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_EOF message for non-existent channel " + id); synchronized (c) { c.EOF = true; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_EOF (channel " + id + ")"); } public void msgChannelClose(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_CLOSE message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_CLOSE message for non-existent channel " + id); synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed("Close requested by remote"); c.closeMessageRecv = true; removeChannel(c.localID); c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_CLOSE (channel " + id + ")"); } public void msgChannelSuccess(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_SUCCESS message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_SUCCESS message for non-existent channel " + id); synchronized (c) { c.successCounter++; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_SUCCESS (channel " + id + ")"); } public void msgChannelFailure(byte[] msg, int msglen) throws IOException { if (msglen != 5) throw new IOException("SSH_MSG_CHANNEL_FAILURE message has wrong size (" + msglen + ")"); int id = ((msg[1] & 0xff) << 24) | ((msg[2] & 0xff) << 16) | ((msg[3] & 0xff) << 8) | (msg[4] & 0xff); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_FAILURE message for non-existent channel " + id); synchronized (c) { c.failedCounter++; c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_FAILURE (channel " + id + ")"); } public void msgChannelOpenConfirmation(byte[] msg, int msglen) throws IOException { PacketChannelOpenConfirmation sm = new PacketChannelOpenConfirmation(msg, 0, msglen); Channel c = getChannel(sm.recipientChannelID); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for non-existent channel " + sm.recipientChannelID); synchronized (c) { if (c.state != Channel.STATE_OPENING) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_CONFIRMATION message for channel " + sm.recipientChannelID); c.remoteID = sm.senderChannelID; c.remoteWindow = sm.initialWindowSize & 0xFFFFffffL; /* convert UINT32 to long */ c.remoteMaxPacketSize = sm.maxPacketSize; c.state = Channel.STATE_OPEN; c.notifyAll(); } log.debug( "Got SSH_MSG_CHANNEL_OPEN_CONFIRMATION (channel " + sm.recipientChannelID + " / remote: " + sm.senderChannelID + ")"); } public void msgChannelOpenFailure(byte[] msg, int msglen) throws IOException { if (msglen < 5) throw new IOException("SSH_MSG_CHANNEL_OPEN_FAILURE message has wrong size (" + msglen + ")"); TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type int id = tr.readUINT32(); /* sender channel */ Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_OPEN_FAILURE message for non-existent channel " + id); int reasonCode = tr.readUINT32(); String description = tr.readString("UTF-8"); String reasonCodeSymbolicName = null; switch (reasonCode) { case 1: reasonCodeSymbolicName = "SSH_OPEN_ADMINISTRATIVELY_PROHIBITED"; break; case 2: reasonCodeSymbolicName = "SSH_OPEN_CONNECT_FAILED"; break; case 3: reasonCodeSymbolicName = "SSH_OPEN_UNKNOWN_CHANNEL_TYPE"; break; case 4: reasonCodeSymbolicName = "SSH_OPEN_RESOURCE_SHORTAGE"; break; default: reasonCodeSymbolicName = "UNKNOWN REASON CODE (" + reasonCode + ")"; } StringBuilder descriptionBuffer = new StringBuilder(); descriptionBuffer.append(description); for (int i = 0; i < descriptionBuffer.length(); i++) { char cc = descriptionBuffer.charAt(i); if ((cc >= 32) && (cc <= 126)) continue; descriptionBuffer.setCharAt(i, '\uFFFD'); } synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed( "The server refused to open the channel (" + reasonCodeSymbolicName + ", '" + descriptionBuffer.toString() + "')"); c.notifyAll(); } log.debug("Got SSH_MSG_CHANNEL_OPEN_FAILURE (channel " + id + ")"); } public void msgGlobalRequest(byte[] msg, int msglen) throws IOException { /* Currently we do not support any kind of global request */ TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type String requestName = tr.readString(); boolean wantReply = tr.readBoolean(); if (wantReply) { byte[] reply_failure = new byte[1]; reply_failure[0] = Packets.SSH_MSG_REQUEST_FAILURE; tm.sendAsynchronousMessage(reply_failure); } /* We do not clean up the requestName String - that is OK for debug */ log.debug("Got SSH_MSG_GLOBAL_REQUEST (" + requestName + ")"); } public void msgGlobalSuccess() throws IOException { synchronized (channels) { globalSuccessCounter++; channels.notifyAll(); } log.debug("Got SSH_MSG_REQUEST_SUCCESS"); } public void msgGlobalFailure() throws IOException { synchronized (channels) { globalFailedCounter++; channels.notifyAll(); } log.debug("Got SSH_MSG_REQUEST_FAILURE"); } public void handleMessage(byte[] msg, int msglen) throws IOException { if (msg == null) { log.debug("HandleMessage: got shutdown"); synchronized (listenerThreads) { for (IChannelWorkerThread lat : listenerThreads) { lat.stopWorking(); } listenerThreadsAllowed = false; } synchronized (channels) { shutdown = true; for (Channel c : channels) { synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed("The connection is being shutdown"); c.closeMessageRecv = true; /* * You never know, perhaps * we are waiting for a * pending close message * from the server... */ c.notifyAll(); } } channels.clear(); channels.notifyAll(); /* Notify global response waiters */ return; } } switch (msg[0]) { case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: msgChannelOpenConfirmation(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: msgChannelWindowAdjust(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_DATA: msgChannelData(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: msgChannelExtendedData(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_REQUEST: msgChannelRequest(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_EOF: msgChannelEOF(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_OPEN: msgChannelOpen(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_CLOSE: msgChannelClose(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_SUCCESS: msgChannelSuccess(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_FAILURE: msgChannelFailure(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: msgChannelOpenFailure(msg, msglen); break; case Packets.SSH_MSG_GLOBAL_REQUEST: msgGlobalRequest(msg, msglen); break; case Packets.SSH_MSG_REQUEST_SUCCESS: msgGlobalSuccess(); break; case Packets.SSH_MSG_REQUEST_FAILURE: msgGlobalFailure(); break; default: throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); } } }
public void msgChannelRequest(byte[] msg, int msglen) throws IOException { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); // skip packet type int id = tr.readUINT32(); Channel c = getChannel(id); if (c == null) throw new IOException( "Unexpected SSH_MSG_CHANNEL_REQUEST message for non-existent channel " + id); String type = tr.readString("US-ASCII"); boolean wantReply = tr.readBoolean(); log.debug("Got SSH_MSG_CHANNEL_REQUEST (channel " + id + ", '" + type + "')"); if (type.equals("exit-status")) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); int exit_status = tr.readUINT32(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_status = new Integer(exit_status); c.notifyAll(); } log.debug("Got EXIT STATUS (channel " + id + ", status " + exit_status + ")"); return; } if (type.equals("exit-signal")) { if (wantReply != false) throw new IOException( "Badly formatted SSH_MSG_CHANNEL_REQUEST message, 'want reply' is true"); String signame = tr.readString("US-ASCII"); tr.readBoolean(); tr.readString(); tr.readString(); if (tr.remain() != 0) throw new IOException("Badly formatted SSH_MSG_CHANNEL_REQUEST message"); synchronized (c) { c.exit_signal = signame; c.notifyAll(); } log.debug("Got EXIT SIGNAL (channel " + id + ", signal " + signame + ")"); return; } /* We simply ignore unknown channel requests, however, if the server wants a reply, * then we signal that we have no idea what it is about. */ if (wantReply) { byte[] reply = new byte[5]; reply[0] = Packets.SSH_MSG_CHANNEL_FAILURE; reply[1] = (byte) (c.remoteID >> 24); reply[2] = (byte) (c.remoteID >> 16); reply[3] = (byte) (c.remoteID >> 8); reply[4] = (byte) (c.remoteID); tm.sendAsynchronousMessage(reply); } log.debug("Channel request '" + type + "' is not known, ignoring it"); }
public void receiveLoop() throws IOException { byte[] msg = new byte[35000]; while (true) { int msglen; try { msglen = tc.receiveMessage(msg, 0, msg.length); } catch (SocketTimeoutException e) { // Timeout in read if (idle) { log.debug("Ignoring socket timeout"); continue; } throw e; } idle = true; int type = msg[0] & 0xff; if (type == Packets.SSH_MSG_IGNORE) { continue; } if (type == Packets.SSH_MSG_DEBUG) { if (log.isDebugEnabled()) { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); tr.readBoolean(); StringBuilder debugMessageBuffer = new StringBuilder(); debugMessageBuffer.append(tr.readString("UTF-8")); for (int i = 0; i < debugMessageBuffer.length(); i++) { char c = debugMessageBuffer.charAt(i); if ((c >= 32) && (c <= 126)) { continue; } debugMessageBuffer.setCharAt(i, '\uFFFD'); } log.debug("DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); } continue; } if (type == Packets.SSH_MSG_UNIMPLEMENTED) { throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); } if (type == Packets.SSH_MSG_DISCONNECT) { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); int reason_code = tr.readUINT32(); StringBuilder reasonBuffer = new StringBuilder(); reasonBuffer.append(tr.readString("UTF-8")); /* * Do not get fooled by servers that send abnormal long error * messages */ if (reasonBuffer.length() > 255) { reasonBuffer.setLength(255); reasonBuffer.setCharAt(254, '.'); reasonBuffer.setCharAt(253, '.'); reasonBuffer.setCharAt(252, '.'); } /* * Also, check that the server did not send characters that may * screw up the receiver -> restrict to reasonable US-ASCII * subset -> "printable characters" (ASCII 32 - 126). Replace * all others with 0xFFFD (UNICODE replacement character). */ for (int i = 0; i < reasonBuffer.length(); i++) { char c = reasonBuffer.charAt(i); if ((c >= 32) && (c <= 126)) { continue; } reasonBuffer.setCharAt(i, '\uFFFD'); } throw new IOException( "Peer sent DISCONNECT message (reason code " + reason_code + "): " + reasonBuffer.toString()); } /* * Is it a KEX Packet? */ if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) || ((type >= 30) && (type <= 49))) { km.handleMessage(msg, msglen); continue; } MessageHandler mh = null; for (int i = 0; i < messageHandlers.size(); i++) { HandlerEntry he = messageHandlers.get(i); if ((he.low <= type) && (type <= he.high)) { mh = he.mh; break; } } if (mh == null) { throw new IOException("Unexpected SSH message (type " + type + ")"); } mh.handleMessage(msg, msglen); } }
/** * TransportManager. * * @author Christian Plattner * @version $Id: * //DTV/MP_BR/DTV_X_IDTV0801_002298_3_001/android/kk-4.x/external/ganymed-ssh2/src/main/java/ch/ethz/ssh2/transport/TransportManager.java#1 * $ */ public class TransportManager { private static final Logger log = Logger.getLogger(TransportManager.class); private static class HandlerEntry { MessageHandler mh; int low; int high; } private final List<byte[]> asynchronousQueue = new Vector<byte[]>(); private Thread asynchronousThread = null; class AsynchronousWorker extends Thread { @Override public void run() { while (true) { byte[] msg = null; synchronized (asynchronousQueue) { if (asynchronousQueue.size() == 0) { /* After the queue is empty for about 2 seconds, stop this thread */ try { asynchronousQueue.wait(2000); } catch (InterruptedException ignore) { } if (asynchronousQueue.size() == 0) { asynchronousThread = null; return; } } msg = asynchronousQueue.remove(0); } /* The following invocation may throw an IOException. * There is no point in handling it - it simply means * that the connection has a problem and we should stop * sending asynchronously messages. We do not need to signal that * we have exited (asynchronousThread = null): further * messages in the queue cannot be sent by this or any * other thread. * Other threads will sooner or later (when receiving or * sending the next message) get the same IOException and * get to the same conclusion. */ try { sendMessage(msg); } catch (IOException e) { return; } } } } private String hostname; private int port; private final Socket sock = new Socket(); private final Object connectionSemaphore = new Object(); private boolean flagKexOngoing = false; private boolean connectionClosed = false; private Throwable reasonClosedCause = null; private TransportConnection tc; private KexManager km; private final List<HandlerEntry> messageHandlers = new Vector<HandlerEntry>(); private Thread receiveThread; private List<ConnectionMonitor> connectionMonitors = new Vector<ConnectionMonitor>(); private boolean monitorsWereInformed = false; /** * There were reports that there are JDKs which use the resolver even though one supplies a dotted * IP address in the Socket constructor. That is why we try to generate the InetAdress "by hand". * * @param host * @return the InetAddress * @throws UnknownHostException */ private InetAddress createInetAddress(String host) throws UnknownHostException { /* Check if it is a dotted IP4 address */ InetAddress addr = parseIPv4Address(host); if (addr != null) { return addr; } return InetAddress.getByName(host); } private InetAddress parseIPv4Address(String host) throws UnknownHostException { if (host == null) { return null; } String[] quad = Tokenizer.parseTokens(host, '.'); if ((quad == null) || (quad.length != 4)) { return null; } byte[] addr = new byte[4]; for (int i = 0; i < 4; i++) { int part = 0; if ((quad[i].length() == 0) || (quad[i].length() > 3)) { return null; } for (int k = 0; k < quad[i].length(); k++) { char c = quad[i].charAt(k); /* No, Character.isDigit is not the same */ if ((c < '0') || (c > '9')) { return null; } part = part * 10 + (c - '0'); } if (part > 255) /* 300.1.2.3 is invalid =) */ { return null; } addr[i] = (byte) part; } return InetAddress.getByAddress(host, addr); } public TransportManager(String host, int port) throws IOException { this.hostname = host; this.port = port; } public int getPacketOverheadEstimate() { return tc.getPacketOverheadEstimate(); } public void setTcpNoDelay(boolean state) throws IOException { sock.setTcpNoDelay(state); } public void setSoTimeout(int timeout) throws IOException { sock.setSoTimeout(timeout); } public ConnectionInfo getConnectionInfo(int kexNumber) throws IOException { return km.getOrWaitForConnectionInfo(kexNumber); } public Throwable getReasonClosedCause() { synchronized (connectionSemaphore) { return reasonClosedCause; } } public byte[] getSessionIdentifier() { return km.sessionId; } public void close(Throwable cause, boolean useDisconnectPacket) { if (useDisconnectPacket == false) { /* OK, hard shutdown - do not aquire the semaphore, * perhaps somebody is inside (and waits until the remote * side is ready to accept new data). */ try { sock.close(); } catch (IOException ignore) { } /* OK, whoever tried to send data, should now agree that * there is no point in further waiting =) * It is safe now to aquire the semaphore. */ } synchronized (connectionSemaphore) { if (connectionClosed == false) { if (useDisconnectPacket == true) { try { byte[] msg = new PacketDisconnect(Packets.SSH_DISCONNECT_BY_APPLICATION, cause.getMessage(), "") .getPayload(); if (tc != null) { tc.sendMessage(msg); } } catch (IOException ignore) { } try { sock.close(); } catch (IOException ignore) { } } connectionClosed = true; reasonClosedCause = cause; /* may be null */ } connectionSemaphore.notifyAll(); } /* No check if we need to inform the monitors */ List<ConnectionMonitor> monitors = new Vector<ConnectionMonitor>(); synchronized (this) { /* Short term lock to protect "connectionMonitors" * and "monitorsWereInformed" * (they may be modified concurrently) */ if (monitorsWereInformed == false) { monitorsWereInformed = true; monitors.addAll(connectionMonitors); } } for (ConnectionMonitor cmon : monitors) { try { cmon.connectionLost(reasonClosedCause); } catch (Exception ignore) { } } } private void establishConnection(ProxyData proxyData, int connectTimeout) throws IOException { /* See the comment for createInetAddress() */ if (proxyData == null) { InetAddress addr = createInetAddress(hostname); sock.connect(new InetSocketAddress(addr, port), connectTimeout); return; } if (proxyData instanceof HTTPProxyData) { HTTPProxyData pd = (HTTPProxyData) proxyData; /* At the moment, we only support HTTP proxies */ InetAddress addr = createInetAddress(pd.proxyHost); sock.connect(new InetSocketAddress(addr, pd.proxyPort), connectTimeout); /* OK, now tell the proxy where we actually want to connect to */ StringBuilder sb = new StringBuilder(); sb.append("CONNECT "); sb.append(hostname); sb.append(':'); sb.append(port); sb.append(" HTTP/1.0\r\n"); if ((pd.proxyUser != null) && (pd.proxyPass != null)) { String credentials = pd.proxyUser + ":" + pd.proxyPass; char[] encoded = Base64.encode(StringEncoder.GetBytes(credentials)); sb.append("Proxy-Authorization: Basic "); sb.append(encoded); sb.append("\r\n"); } if (pd.requestHeaderLines != null) { for (int i = 0; i < pd.requestHeaderLines.length; i++) { if (pd.requestHeaderLines[i] != null) { sb.append(pd.requestHeaderLines[i]); sb.append("\r\n"); } } } sb.append("\r\n"); OutputStream out = sock.getOutputStream(); out.write(StringEncoder.GetBytes(sb.toString())); out.flush(); /* Now parse the HTTP response */ byte[] buffer = new byte[1024]; InputStream in = sock.getInputStream(); int len = ClientServerHello.readLineRN(in, buffer); String httpReponse = StringEncoder.GetString(buffer, 0, len); if (httpReponse.startsWith("HTTP/") == false) { throw new IOException("The proxy did not send back a valid HTTP response."); } /* "HTTP/1.X XYZ X" => 14 characters minimum */ if ((httpReponse.length() < 14) || (httpReponse.charAt(8) != ' ') || (httpReponse.charAt(12) != ' ')) { throw new IOException("The proxy did not send back a valid HTTP response."); } int errorCode = 0; try { errorCode = Integer.parseInt(httpReponse.substring(9, 12)); } catch (NumberFormatException ignore) { throw new IOException("The proxy did not send back a valid HTTP response."); } if ((errorCode < 0) || (errorCode > 999)) { throw new IOException("The proxy did not send back a valid HTTP response."); } if (errorCode != 200) { throw new HTTPProxyException(httpReponse.substring(13), errorCode); } /* OK, read until empty line */ while (true) { len = ClientServerHello.readLineRN(in, buffer); if (len == 0) { break; } } return; } throw new IOException("Unsupported ProxyData"); } public void initialize( String identification, CryptoWishList cwl, ServerHostKeyVerifier verifier, DHGexParameters dhgex, int connectTimeout, SecureRandom rnd, ProxyData proxyData) throws IOException { /* First, establish the TCP connection to the SSH-2 server */ establishConnection(proxyData, connectTimeout); /* Parse the server line and say hello - important: this information is later needed for the * key exchange (to stop man-in-the-middle attacks) - that is why we wrap it into an object * for later use. */ ClientServerHello csh = new ClientServerHello(identification, sock.getInputStream(), sock.getOutputStream()); tc = new TransportConnection(sock.getInputStream(), sock.getOutputStream(), rnd); km = new KexManager(this, csh, cwl, hostname, port, verifier, rnd); km.initiateKEX(cwl, dhgex); receiveThread = new Thread( new Runnable() { public void run() { try { receiveLoop(); } catch (IOException e) { close(e, false); log.warning("Receive thread: error in receiveLoop: " + e.getMessage()); } if (log.isDebugEnabled()) { log.debug("Receive thread: back from receiveLoop"); } /* Tell all handlers that it is time to say goodbye */ if (km != null) { try { km.handleMessage(null, 0); } catch (IOException ignored) { } } for (HandlerEntry he : messageHandlers) { try { he.mh.handleMessage(null, 0); } catch (Exception ignore) { } } } }); receiveThread.setDaemon(true); receiveThread.start(); } public void registerMessageHandler(MessageHandler mh, int low, int high) { HandlerEntry he = new HandlerEntry(); he.mh = mh; he.low = low; he.high = high; synchronized (messageHandlers) { messageHandlers.add(he); } } public void removeMessageHandler(MessageHandler mh, int low, int high) { synchronized (messageHandlers) { for (int i = 0; i < messageHandlers.size(); i++) { HandlerEntry he = messageHandlers.get(i); if ((he.mh == mh) && (he.low == low) && (he.high == high)) { messageHandlers.remove(i); break; } } } } public void sendKexMessage(byte[] msg) throws IOException { synchronized (connectionSemaphore) { if (connectionClosed) { throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); } flagKexOngoing = true; try { tc.sendMessage(msg); } catch (IOException e) { close(e, false); throw e; } } } public void kexFinished() throws IOException { synchronized (connectionSemaphore) { flagKexOngoing = false; connectionSemaphore.notifyAll(); } } public void forceKeyExchange(CryptoWishList cwl, DHGexParameters dhgex) throws IOException { km.initiateKEX(cwl, dhgex); } public void changeRecvCipher(BlockCipher bc, MAC mac) { tc.changeRecvCipher(bc, mac); } public void changeSendCipher(BlockCipher bc, MAC mac) { tc.changeSendCipher(bc, mac); } public void sendAsynchronousMessage(byte[] msg) throws IOException { synchronized (asynchronousQueue) { asynchronousQueue.add(msg); /* This limit should be flexible enough. We need this, otherwise the peer * can flood us with global requests (and other stuff where we have to reply * with an asynchronous message) and (if the server just sends data and does not * read what we send) this will probably put us in a low memory situation * (our send queue would grow and grow and...) */ if (asynchronousQueue.size() > 100) { throw new IOException("Error: the peer is not consuming our asynchronous replies."); } /* Check if we have an asynchronous sending thread */ if (asynchronousThread == null) { asynchronousThread = new AsynchronousWorker(); asynchronousThread.setDaemon(true); asynchronousThread.start(); /* The thread will stop after 2 seconds of inactivity (i.e., empty queue) */ } } } public void setConnectionMonitors(List<ConnectionMonitor> monitors) { synchronized (this) { connectionMonitors = new Vector<ConnectionMonitor>(); connectionMonitors.addAll(monitors); } } /** True if no response message expected. */ private boolean idle; public void sendMessage(byte[] msg) throws IOException { if (Thread.currentThread() == receiveThread) { throw new IOException( "Assertion error: sendMessage may never be invoked by the receiver thread!"); } boolean wasInterrupted = false; try { synchronized (connectionSemaphore) { while (true) { if (connectionClosed) { throw (IOException) new IOException("Sorry, this connection is closed.").initCause(reasonClosedCause); } if (flagKexOngoing == false) { break; } try { connectionSemaphore.wait(); } catch (InterruptedException e) { wasInterrupted = true; } } try { tc.sendMessage(msg); idle = false; } catch (IOException e) { close(e, false); throw e; } } } finally { if (wasInterrupted) Thread.currentThread().interrupt(); } } public void receiveLoop() throws IOException { byte[] msg = new byte[35000]; while (true) { int msglen; try { msglen = tc.receiveMessage(msg, 0, msg.length); } catch (SocketTimeoutException e) { // Timeout in read if (idle) { log.debug("Ignoring socket timeout"); continue; } throw e; } idle = true; int type = msg[0] & 0xff; if (type == Packets.SSH_MSG_IGNORE) { continue; } if (type == Packets.SSH_MSG_DEBUG) { if (log.isDebugEnabled()) { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); tr.readBoolean(); StringBuilder debugMessageBuffer = new StringBuilder(); debugMessageBuffer.append(tr.readString("UTF-8")); for (int i = 0; i < debugMessageBuffer.length(); i++) { char c = debugMessageBuffer.charAt(i); if ((c >= 32) && (c <= 126)) { continue; } debugMessageBuffer.setCharAt(i, '\uFFFD'); } log.debug("DEBUG Message from remote: '" + debugMessageBuffer.toString() + "'"); } continue; } if (type == Packets.SSH_MSG_UNIMPLEMENTED) { throw new IOException("Peer sent UNIMPLEMENTED message, that should not happen."); } if (type == Packets.SSH_MSG_DISCONNECT) { TypesReader tr = new TypesReader(msg, 0, msglen); tr.readByte(); int reason_code = tr.readUINT32(); StringBuilder reasonBuffer = new StringBuilder(); reasonBuffer.append(tr.readString("UTF-8")); /* * Do not get fooled by servers that send abnormal long error * messages */ if (reasonBuffer.length() > 255) { reasonBuffer.setLength(255); reasonBuffer.setCharAt(254, '.'); reasonBuffer.setCharAt(253, '.'); reasonBuffer.setCharAt(252, '.'); } /* * Also, check that the server did not send characters that may * screw up the receiver -> restrict to reasonable US-ASCII * subset -> "printable characters" (ASCII 32 - 126). Replace * all others with 0xFFFD (UNICODE replacement character). */ for (int i = 0; i < reasonBuffer.length(); i++) { char c = reasonBuffer.charAt(i); if ((c >= 32) && (c <= 126)) { continue; } reasonBuffer.setCharAt(i, '\uFFFD'); } throw new IOException( "Peer sent DISCONNECT message (reason code " + reason_code + "): " + reasonBuffer.toString()); } /* * Is it a KEX Packet? */ if ((type == Packets.SSH_MSG_KEXINIT) || (type == Packets.SSH_MSG_NEWKEYS) || ((type >= 30) && (type <= 49))) { km.handleMessage(msg, msglen); continue; } MessageHandler mh = null; for (int i = 0; i < messageHandlers.size(); i++) { HandlerEntry he = messageHandlers.get(i); if ((he.low <= type) && (type <= he.high)) { mh = he.mh; break; } } if (mh == null) { throw new IOException("Unexpected SSH message (type " + type + ")"); } mh.handleMessage(msg, msglen); } } }
public void handleMessage(byte[] msg, int msglen) throws IOException { if (msg == null) { log.debug("HandleMessage: got shutdown"); synchronized (listenerThreads) { for (IChannelWorkerThread lat : listenerThreads) { lat.stopWorking(); } listenerThreadsAllowed = false; } synchronized (channels) { shutdown = true; for (Channel c : channels) { synchronized (c) { c.EOF = true; c.state = Channel.STATE_CLOSED; c.setReasonClosed("The connection is being shutdown"); c.closeMessageRecv = true; /* * You never know, perhaps * we are waiting for a * pending close message * from the server... */ c.notifyAll(); } } channels.clear(); channels.notifyAll(); /* Notify global response waiters */ return; } } switch (msg[0]) { case Packets.SSH_MSG_CHANNEL_OPEN_CONFIRMATION: msgChannelOpenConfirmation(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_WINDOW_ADJUST: msgChannelWindowAdjust(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_DATA: msgChannelData(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_EXTENDED_DATA: msgChannelExtendedData(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_REQUEST: msgChannelRequest(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_EOF: msgChannelEOF(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_OPEN: msgChannelOpen(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_CLOSE: msgChannelClose(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_SUCCESS: msgChannelSuccess(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_FAILURE: msgChannelFailure(msg, msglen); break; case Packets.SSH_MSG_CHANNEL_OPEN_FAILURE: msgChannelOpenFailure(msg, msglen); break; case Packets.SSH_MSG_GLOBAL_REQUEST: msgGlobalRequest(msg, msglen); break; case Packets.SSH_MSG_REQUEST_SUCCESS: msgGlobalSuccess(); break; case Packets.SSH_MSG_REQUEST_FAILURE: msgGlobalFailure(); break; default: throw new IOException("Cannot handle unknown channel message " + (msg[0] & 0xff)); } }