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 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 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 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 + ")");
  }