text: /*
   * Binary packet protocol for SSH-2.
   */
  
  #include <assert.h>
  
  #include "putty.h"
  #include "ssh.h"
  #include "sshbpp.h"
  #include "sshcr.h"
  
  struct ssh2_bpp_direction {
      unsigned long sequence;
      ssh_cipher *cipher;
      ssh2_mac *mac;
      bool etm_mode;
      const ssh_compression_alg *pending_compression;
  };
  
  struct ssh2_bpp_state {
      int crState;
      long len, pad, payload, packetlen, maclen, length, maxlen;
      unsigned char *buf;
      size_t bufsize;
      unsigned char *data;
      unsigned cipherblk;
      PktIn *pktin;
      struct DataTransferStats *stats;
      bool cbc_ignore_workaround;
  
      struct ssh2_bpp_direction in, out;
      /* comp and decomp logically belong in the per-direction
       * substructure, except that they have different types */
      ssh_decompressor *in_decomp;
      ssh_compressor *out_comp;
  
      bool is_server;
      bool pending_newkeys;
      bool pending_compression, seen_userauth_success;
      bool enforce_next_packet_is_userauth_success;
      unsigned nnewkeys;
      int prev_type;
  
      BinaryPacketProtocol bpp;
  };
  
  static void ssh2_bpp_free(BinaryPacketProtocol *bpp);
  static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp);
  static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp);
  static PktOut *ssh2_bpp_new_pktout(int type);
  
  static const BinaryPacketProtocolVtable ssh2_bpp_vtable = {
      .free = ssh2_bpp_free,
      .handle_input = ssh2_bpp_handle_input,
      .handle_output = ssh2_bpp_handle_output,
      .new_pktout = ssh2_bpp_new_pktout,
      .queue_disconnect = ssh2_bpp_queue_disconnect, /* in sshcommon.c */
      .packet_size_limit = 0xFFFFFFFF, /* no special limit for this bpp */
  };
  
  BinaryPacketProtocol *ssh2_bpp_new(
      LogContext *logctx, struct DataTransferStats *stats, bool is_server)
  {
      struct ssh2_bpp_state *s = snew(struct ssh2_bpp_state);
      memset(s, 0, sizeof(*s));
      s->bpp.vt = &ssh2_bpp_vtable;
      s->bpp.logctx = logctx;
      s->stats = stats;
      s->is_server = is_server;
      ssh_bpp_common_setup(&s->bpp);
      return &s->bpp;
  }
  
  static void ssh2_bpp_free_outgoing_crypto(struct ssh2_bpp_state *s)
  {
      /*
       * We must free the MAC before the cipher, because sometimes the
       * MAC is not actually separately allocated but just a different
       * facet of the same object as the cipher, in which case
       * ssh2_mac_free does nothing and ssh_cipher_free does the actual
       * freeing. So if we freed the cipher first and then tried to
       * dereference the MAC's vtable pointer to find out how to free
       * that too, we'd be accessing freed memory.
       */
      if (s->out.mac)
          ssh2_mac_free(s->out.mac);
      if (s->out.cipher)
          ssh_cipher_free(s->out.cipher);
      if (s->out_comp)
          ssh_compressor_free(s->out_comp);
  }
  
  static void ssh2_bpp_free_incoming_crypto(struct ssh2_bpp_state *s)
  {
      /* As above, take care to free in.mac before in.cipher */
      if (s->in.mac)
          ssh2_mac_free(s->in.mac);
      if (s->in.cipher)
          ssh_cipher_free(s->in.cipher);
      if (s->in_decomp)
          ssh_decompressor_free(s->in_decomp);
  }
  
  static void ssh2_bpp_free(BinaryPacketProtocol *bpp)
  {
      struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
      sfree(s->buf);
      ssh2_bpp_free_outgoing_crypto(s);
      ssh2_bpp_free_incoming_crypto(s);
      sfree(s->pktin);
      sfree(s);
  }
  
  void ssh2_bpp_new_outgoing_crypto(
      BinaryPacketProtocol *bpp,
      const ssh_cipheralg *cipher, const void *ckey, const void *iv,
      const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
      const ssh_compression_alg *compression, bool delayed_compression)
  {
      struct ssh2_bpp_state *s;
      assert(bpp->vt == &ssh2_bpp_vtable);
      s = container_of(bpp, struct ssh2_bpp_state, bpp);
  
      ssh2_bpp_free_outgoing_crypto(s);
  
      if (cipher) {
          s->out.cipher = ssh_cipher_new(cipher);
          ssh_cipher_setkey(s->out.cipher, ckey);
          ssh_cipher_setiv(s->out.cipher, iv);
  
          s->cbc_ignore_workaround = (
              (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_IS_CBC) &&
              !(s->bpp.remote_bugs & BUG_CHOKES_ON_SSH2_IGNORE));
  
          bpp_logevent("Initialised %s outbound encryption",
                       ssh_cipher_alg(s->out.cipher)->text_name);
      } else {
          s->out.cipher = NULL;
          s->cbc_ignore_workaround = false;
      }
      s->out.etm_mode = etm_mode;
      if (mac) {
          s->out.mac = ssh2_mac_new(mac, s->out.cipher);
          ssh2_mac_setkey(s->out.mac, make_ptrlen(mac_key, mac->keylen));
  
          bpp_logevent("Initialised %s outbound MAC algorithm%s%s",
                       ssh2_mac_text_name(s->out.mac),
                       etm_mode ? " (in ETM mode)" : "",
                       (s->out.cipher &&
                        ssh_cipher_alg(s->out.cipher)->required_mac ?
                        " (required by cipher)" : ""));
      } else {
          s->out.mac = NULL;
      }
  
      if (delayed_compression && !s->seen_userauth_success) {
          s->out.pending_compression = compression;
          s->out_comp = NULL;
  
          bpp_logevent("Will enable %s compression after user authentication",
                       s->out.pending_compression->text_name);
      } else {
          s->out.pending_compression = NULL;
  
          /* 'compression' is always non-NULL, because no compression is
           * indicated by ssh_comp_none. But this setup call may return a
           * null out_comp. */
          s->out_comp = ssh_compressor_new(compression);
  
          if (s->out_comp)
              bpp_logevent("Initialised %s compression",
                           ssh_compressor_alg(s->out_comp)->text_name);
      }
  }
  
  void ssh2_bpp_new_incoming_crypto(
      BinaryPacketProtocol *bpp,
      const ssh_cipheralg *cipher, const void *ckey, const void *iv,
      const ssh2_macalg *mac, bool etm_mode, const void *mac_key,
      const ssh_compression_alg *compression, bool delayed_compression)
  {
      struct ssh2_bpp_state *s;
      assert(bpp->vt == &ssh2_bpp_vtable);
      s = container_of(bpp, struct ssh2_bpp_state, bpp);
  
      ssh2_bpp_free_incoming_crypto(s);
  
      if (cipher) {
          s->in.cipher = ssh_cipher_new(cipher);
          ssh_cipher_setkey(s->in.cipher, ckey);
          ssh_cipher_setiv(s->in.cipher, iv);
  
          bpp_logevent("Initialised %s inbound encryption",
                       ssh_cipher_alg(s->in.cipher)->text_name);
      } else {
          s->in.cipher = NULL;
      }
      s->in.etm_mode = etm_mode;
      if (mac) {
          s->in.mac = ssh2_mac_new(mac, s->in.cipher);
          ssh2_mac_setkey(s->in.mac, make_ptrlen(mac_key, mac->keylen));
  
          bpp_logevent("Initialised %s inbound MAC algorithm%s%s",
                       ssh2_mac_text_name(s->in.mac),
                       etm_mode ? " (in ETM mode)" : "",
                       (s->in.cipher &&
                        ssh_cipher_alg(s->in.cipher)->required_mac ?
                        " (required by cipher)" : ""));
      } else {
          s->in.mac = NULL;
      }
  
      if (delayed_compression && !s->seen_userauth_success) {
          s->in.pending_compression = compression;
          s->in_decomp = NULL;
  
          bpp_logevent("Will enable %s decompression after user authentication",
                       s->in.pending_compression->text_name);
      } else {
          s->in.pending_compression = NULL;
  
          /* 'compression' is always non-NULL, because no compression is
           * indicated by ssh_comp_none. But this setup call may return a
           * null in_decomp. */
          s->in_decomp = ssh_decompressor_new(compression);
  
          if (s->in_decomp)
              bpp_logevent("Initialised %s decompression",
                           ssh_decompressor_alg(s->in_decomp)->text_name);
      }
  
      /* Clear the pending_newkeys flag, so that handle_input below will
       * start consuming the input data again. */
      s->pending_newkeys = false;
  
      /* And schedule a run of handle_input, in case there's already
       * input data in the queue. */
      queue_idempotent_callback(&s->bpp.ic_in_raw);
  }
  
  bool ssh2_bpp_rekey_inadvisable(BinaryPacketProtocol *bpp)
  {
      struct ssh2_bpp_state *s;
      assert(bpp->vt == &ssh2_bpp_vtable);
      s = container_of(bpp, struct ssh2_bpp_state, bpp);
  
      return s->pending_compression;
  }
  
  static void ssh2_bpp_enable_pending_compression(struct ssh2_bpp_state *s)
  {
      BinaryPacketProtocol *bpp = &s->bpp; /* for bpp_logevent */
  
      if (s->in.pending_compression) {
          s->in_decomp = ssh_decompressor_new(s->in.pending_compression);
          bpp_logevent("Initialised delayed %s decompression",
                       ssh_decompressor_alg(s->in_decomp)->text_name);
          s->in.pending_compression = NULL;
      }
      if (s->out.pending_compression) {
          s->out_comp = ssh_compressor_new(s->out.pending_compression);
          bpp_logevent("Initialised delayed %s compression",
                       ssh_compressor_alg(s->out_comp)->text_name);
          s->out.pending_compression = NULL;
      }
  }
  
  #define BPP_READ(ptr, len) do                                           \
      {                                                                   \
          bool success;                                                   \
          crMaybeWaitUntilV((success = bufchain_try_fetch_consume(        \
                                 s->bpp.in_raw, ptr, len)) ||             \
                            s->bpp.input_eof);                            \
          if (!success)                                                   \
              goto eof;                                                   \
          ssh_check_frozen(s->bpp.ssh);                                   \
      } while (0)
  
  #define userauth_range(pkttype) ((unsigned)((pkttype) - 50) < 20)
  
  static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
  {
      struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
  
      crBegin(s->crState);
  
      while (1) {
          s->maxlen = 0;
          s->length = 0;
          if (s->in.cipher)
              s->cipherblk = ssh_cipher_alg(s->in.cipher)->blksize;
          else
              s->cipherblk = 8;
          if (s->cipherblk < 8)
              s->cipherblk = 8;
          s->maclen = s->in.mac ? ssh2_mac_alg(s->in.mac)->len : 0;
  
          if (s->in.cipher &&
              (ssh_cipher_alg(s->in.cipher)->flags & SSH_CIPHER_IS_CBC) &&
              s->in.mac && !s->in.etm_mode) {
              /*
               * When dealing with a CBC-mode cipher, we want to avoid the
               * possibility of an attacker's tweaking the ciphertext stream
               * so as to cause us to feed the same block to the block
               * cipher more than once and thus leak information
               * (VU#958563).  The way we do this is not to take any
               * decisions on the basis of anything we've decrypted until
               * we've verified it with a MAC.  That includes the packet
               * length, so we just read data and check the MAC repeatedly,
               * and when the MAC passes, see if the length we've got is
               * plausible.
               *
               * This defence is unnecessary in OpenSSH ETM mode, because
               * the whole point of ETM mode is that the attacker can't
               * tweak the ciphertext stream at all without the MAC
               * detecting it before we decrypt anything.
               */
  
              /*
               * Make sure we have buffer space for a maximum-size packet.
               */
              unsigned buflimit = OUR_V2_PACKETLIMIT + s->maclen;
              if (s->bufsize < buflimit) {
                  s->bufsize = buflimit;
                  s->buf = sresize(s->buf, s->bufsize, unsigned char);
              }
  
              /* Read an amount corresponding to the MAC. */
              BPP_READ(s->buf, s->maclen);
  
              s->packetlen = 0;
              ssh2_mac_start(s->in.mac);
              put_uint32(s->in.mac, s->in.sequence);
  
              for (;;) { /* Once around this loop per cipher block. */
                  /* Read another cipher-block's worth, and tack it on to
                   * the end. */
                  BPP_READ(s->buf + (s->packetlen + s->maclen), s->cipherblk);
                  /* Decrypt one more block (a little further back in
                   * the stream). */
                  ssh_cipher_decrypt(s->in.cipher,
                                     s->buf + s->packetlen, s->cipherblk);
  
                  /* Feed that block to the MAC. */
                  put_data(s->in.mac,
                           s->buf + s->packetlen, s->cipherblk);
                  s->packetlen += s->cipherblk;
  
                  /* See if that gives us a valid packet. */
                  if (ssh2_mac_verresult(s->in.mac, s->buf + s->packetlen) &&
                      ((s->len = toint(GET_32BIT_MSB_FIRST(s->buf))) ==
                       s->packetlen-4))
                      break;
                  if (s->packetlen >= (long)OUR_V2_PACKETLIMIT) {
                      ssh_sw_abort(s->bpp.ssh,
                                   "No valid incoming packet found");
                      crStopV;
                  }
              }
              s->maxlen = s->packetlen + s->maclen;
  
              /*
               * Now transfer the data into an output packet.
               */
              s->pktin = snew_plus(PktIn, s->maxlen);
              s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
              s->pktin->type = 0;
              s->pktin->qnode.on_free_queue = false;
              s->data = snew_plus_get_aux(s->pktin);
              memcpy(s->data, s->buf, s->maxlen);
          } else if (s->in.mac && s->in.etm_mode) {
              if (s->bufsize < 4) {
                  s->bufsize = 4;
                  s->buf = sresize(s->buf, s->bufsize, unsigned char);
              }
  
              /*
               * OpenSSH encrypt-then-MAC mode: the packet length is
               * unencrypted, unless the cipher supports length encryption.
               */
              BPP_READ(s->buf, 4);
  
              /* Cipher supports length decryption, so do it */
              if (s->in.cipher && (ssh_cipher_alg(s->in.cipher)->flags &
                                   SSH_CIPHER_SEPARATE_LENGTH)) {
                  /* Keep the packet the same though, so the MAC passes */
                  unsigned char len[4];
                  memcpy(len, s->buf, 4);
                  ssh_cipher_decrypt_length(
                      s->in.cipher, len, 4, s->in.sequence);
                  s->len = toint(GET_32BIT_MSB_FIRST(len));
              } else {
                  s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
              }
  
              /*
               * _Completely_ silly lengths should be stomped on before they
               * do us any more damage.
               */
              if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
                  s->len % s->cipherblk != 0) {
                  ssh_sw_abort(s->bpp.ssh,
                               "Incoming packet length field was garbled");
                  crStopV;
              }
  
              /*
               * So now we can work out the total packet length.
               */
              s->packetlen = s->len + 4;
  
              /*
               * Allocate the packet to return, now we know its length.
               */
              s->pktin = snew_plus(PktIn, OUR_V2_PACKETLIMIT + s->maclen);
              s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
              s->pktin->type = 0;
              s->pktin->qnode.on_free_queue = false;
              s->data = snew_plus_get_aux(s->pktin);
              memcpy(s->data, s->buf, 4);
  
              /*
               * Read the remainder of the packet.
               */
              BPP_READ(s->data + 4, s->packetlen + s->maclen - 4);
  
              /*
               * Check the MAC.
               */
              if (s->in.mac && !ssh2_mac_verify(
                      s->in.mac, s->data, s->len + 4, s->in.sequence)) {
                  ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
                  crStopV;
              }
  
              /* Decrypt everything between the length field and the MAC. */
              if (s->in.cipher)
                  ssh_cipher_decrypt(
                      s->in.cipher, s->data + 4, s->packetlen - 4);
          } else {
              if (s->bufsize < s->cipherblk) {
                  s->bufsize = s->cipherblk;
                  s->buf = sresize(s->buf, s->bufsize, unsigned char);
              }
  
              /*
               * Acquire and decrypt the first block of the packet. This will
               * contain the length and padding details.
               */
              BPP_READ(s->buf, s->cipherblk);
  
              if (s->in.cipher)
                  ssh_cipher_decrypt(s->in.cipher, s->buf, s->cipherblk);
  
              /*
               * Now get the length figure.
               */
              s->len = toint(GET_32BIT_MSB_FIRST(s->buf));
  
              /*
               * _Completely_ silly lengths should be stomped on before they
               * do us any more damage.
               */
              if (s->len < 0 || s->len > (long)OUR_V2_PACKETLIMIT ||
                  (s->len + 4) % s->cipherblk != 0) {
                  ssh_sw_abort(s->bpp.ssh,
                               "Incoming packet was garbled on decryption");
                  crStopV;
              }
  
              /*
               * So now we can work out the total packet length.
               */
              s->packetlen = s->len + 4;
  
              /*
               * Allocate the packet to return, now we know its length.
               */
              s->maxlen = s->packetlen + s->maclen;
              s->pktin = snew_plus(PktIn, s->maxlen);
              s->pktin->qnode.prev = s->pktin->qnode.next = NULL;
              s->pktin->type = 0;
              s->pktin->qnode.on_free_queue = false;
              s->data = snew_plus_get_aux(s->pktin);
              memcpy(s->data, s->buf, s->cipherblk);
  
              /*
               * Read and decrypt the remainder of the packet.
               */
              BPP_READ(s->data + s->cipherblk,
                       s->packetlen + s->maclen - s->cipherblk);
  
              /* Decrypt everything _except_ the MAC. */
              if (s->in.cipher)
                  ssh_cipher_decrypt(
                      s->in.cipher,
                      s->data + s->cipherblk, s->packetlen - s->cipherblk);
  
              /*
               * Check the MAC.
               */
              if (s->in.mac && !ssh2_mac_verify(
                      s->in.mac, s->data, s->len + 4, s->in.sequence)) {
                  ssh_sw_abort(s->bpp.ssh, "Incorrect MAC received on packet");
                  crStopV;
              }
          }
          /* Get and sanity-check the amount of random padding. */
          s->pad = s->data[4];
          if (s->pad < 4 || s->len - s->pad < 1) {
              ssh_sw_abort(s->bpp.ssh,
                           "Invalid padding length on received packet");
              crStopV;
          }
          /*
           * This enables us to deduce the payload length.
           */
          s->payload = s->len - s->pad - 1;
  
          s->length = s->payload + 5;
  
          dts_consume(&s->stats->in, s->packetlen);
  
          s->pktin->sequence = s->in.sequence++;
  
          s->length = s->packetlen - s->pad;
          assert(s->length >= 0);
  
          /*
           * Decompress packet payload.
           */
          {
              unsigned char *newpayload;
              int newlen;
              if (s->in_decomp && ssh_decompressor_decompress(
                      s->in_decomp, s->data + 5, s->length - 5,
                      &newpayload, &newlen)) {
                  if (s->maxlen < newlen + 5) {
                      PktIn *old_pktin = s->pktin;
  
                      s->maxlen = newlen + 5;
                      s->pktin = snew_plus(PktIn, s->maxlen);
                      *s->pktin = *old_pktin; /* structure copy */
                      s->data = snew_plus_get_aux(s->pktin);
  
                      smemclr(old_pktin, s->packetlen + s->maclen);
                      sfree(old_pktin);
                  }
                  s->length = 5 + newlen;
                  memcpy(s->data + 5, newpayload, newlen);
                  sfree(newpayload);
              }
          }
  
          /*
           * Now we can identify the semantic content of the packet,
           * and also the initial type byte.
           */
          if (s->length <= 5) { /* == 5 we hope, but robustness */
              /*
               * RFC 4253 doesn't explicitly say that completely empty
               * packets with no type byte are forbidden. We handle them
               * here by giving them a type code larger than 0xFF, which
               * will be picked up at the next layer and trigger
               * SSH_MSG_UNIMPLEMENTED.
               */
              s->pktin->type = SSH_MSG_NO_TYPE_CODE;
              s->data += 5;
              s->length = 0;
          } else {
              s->pktin->type = s->data[5];
              s->data += 6;
              s->length -= 6;
          }
          BinarySource_INIT(s->pktin, s->data, s->length);
  
          if (s->bpp.logctx) {
              logblank_t blanks[MAX_BLANKS];
              int nblanks = ssh2_censor_packet(
                  s->bpp.pls, s->pktin->type, false,
                  make_ptrlen(s->data, s->length), blanks);
              log_packet(s->bpp.logctx, PKT_INCOMING, s->pktin->type,
                         ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
                                       s->pktin->type),
                         s->data, s->length, nblanks, blanks,
                         &s->pktin->sequence, 0, NULL);
          }
  
          if (ssh2_bpp_check_unimplemented(&s->bpp, s->pktin)) {
              sfree(s->pktin);
              s->pktin = NULL;
              continue;
          }
  
          s->pktin->qnode.formal_size = get_avail(s->pktin);
          pq_push(&s->bpp.in_pq, s->pktin);
  
          {
              int type = s->pktin->type;
              int prev_type = s->prev_type;
              s->prev_type = type;
              s->pktin = NULL;
  
              if (s->enforce_next_packet_is_userauth_success) {
                  /* See EXT_INFO handler below */
                  if (type != SSH2_MSG_USERAUTH_SUCCESS) {
                      ssh_proto_error(s->bpp.ssh,
                                      "Remote side sent SSH2_MSG_EXT_INFO "
                                      "not either preceded by NEWKEYS or "
                                      "followed by USERAUTH_SUCCESS");
                      return;
                  }
                  s->enforce_next_packet_is_userauth_success = false;
              }
  
              if (type == SSH2_MSG_NEWKEYS) {
                  if (s->nnewkeys < 2)
                      s->nnewkeys++;
                  /*
                   * Mild layer violation: in this situation we must
                   * suspend processing of the input byte stream until
                   * the transport layer has initialised the new keys by
                   * calling ssh2_bpp_new_incoming_crypto above.
                   */
                  s->pending_newkeys = true;
                  crWaitUntilV(!s->pending_newkeys);
                  continue;
              }
  
              if (type == SSH2_MSG_USERAUTH_SUCCESS && !s->is_server) {
                  /*
                   * Another one: if we were configured with OpenSSH's
                   * deferred compression which is triggered on receipt
                   * of USERAUTH_SUCCESS, then this is the moment to
                   * turn on compression.
                   */
                  ssh2_bpp_enable_pending_compression(s);
  
                  /*
                   * Whether or not we were doing delayed compression in
                   * _this_ set of crypto parameters, we should set a
                   * flag indicating that we're now authenticated, so
                   * that a delayed compression method enabled in any
                   * future rekey will be treated as un-delayed.
                   */
                  s->seen_userauth_success = true;
              }
  
              if (type == SSH2_MSG_EXT_INFO) {
                  /*
                   * And another: enforce that an incoming EXT_INFO is
                   * either the message immediately after the initial
                   * NEWKEYS, or (if we're the client) the one
                   * immediately before USERAUTH_SUCCESS.
                   */
                  if (prev_type == SSH2_MSG_NEWKEYS && s->nnewkeys == 1) {
                      /* OK - this is right after the first NEWKEYS. */
                  } else if (s->is_server) {
                      /* We're the server, so they're the client.
                       * Clients may not send EXT_INFO at _any_ other
                       * time. */
                      ssh_proto_error(s->bpp.ssh,
                                      "Remote side sent SSH2_MSG_EXT_INFO "
                                      "that was not immediately after the "
                                      "initial NEWKEYS");
                      return;
                  } else if (s->nnewkeys > 0 && s->seen_userauth_success) {
                      /* We're the client, so they're the server. In
                       * that case they may also send EXT_INFO
                       * immediately before USERAUTH_SUCCESS. Error out
                       * immediately if this can't _possibly_ be that
                       * moment (because we haven't even seen NEWKEYS
                       * yet, or because we've already seen
                       * USERAUTH_SUCCESS). */
                      ssh_proto_error(s->bpp.ssh,
                                      "Remote side sent SSH2_MSG_EXT_INFO "
                                      "after USERAUTH_SUCCESS");
                      return;
                  } else {
                      /* This _could_ be OK, provided the next packet is
                       * USERAUTH_SUCCESS. Set a flag to remember to
                       * fault it if not. */
                      s->enforce_next_packet_is_userauth_success = true;
                  }
              }
  
              if (s->pending_compression && userauth_range(type)) {
                  /*
                   * Receiving any userauth message at all indicates
                   * that we're not about to turn on delayed compression
                   * - either because we just _have_ done, or because
                   * this message is a USERAUTH_FAILURE or some kind of
                   * intermediate 'please send more data' continuation
                   * message. Either way, we turn off the outgoing
                   * packet blockage for now, and release any queued
                   * output packets, so that we can make another attempt
                   * to authenticate. The next userauth packet we send
                   * will re-block the output direction.
                   */
                  s->pending_compression = false;
                  queue_idempotent_callback(&s->bpp.ic_out_pq);
              }
          }
      }
  
    eof:
      /*
       * We've seen EOF. But we might have pushed stuff on the outgoing
       * packet queue first, and that stuff _might_ include a DISCONNECT
       * message, in which case we'd like to use that as the diagnostic.
       * So first wait for the queue to have been processed.
       */
      crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
      if (!s->bpp.expect_close) {
          ssh_remote_error(s->bpp.ssh,
                           "Remote side unexpectedly closed network connection");
      } else {
          ssh_remote_eof(s->bpp.ssh, "Remote side closed network connection");
      }
      return;  /* avoid touching s now it's been freed */
  
      crFinishV;
  }
  
  static PktOut *ssh2_bpp_new_pktout(int pkt_type)
  {
      PktOut *pkt = ssh_new_packet();
      pkt->length = 5; /* space for packet length + padding length */
      pkt->minlen = 0;
      pkt->type = pkt_type;
      put_byte(pkt, pkt_type);
      pkt->prefix = pkt->length;
      return pkt;
  }
  
  static void ssh2_bpp_format_packet_inner(struct ssh2_bpp_state *s, PktOut *pkt)
  {
      int origlen, cipherblk, maclen, padding, unencrypted_prefix, i;
  
      if (s->bpp.logctx) {
          ptrlen pktdata = make_ptrlen(pkt->data + pkt->prefix,
                                       pkt->length - pkt->prefix);
          logblank_t blanks[MAX_BLANKS];
          int nblanks = ssh2_censor_packet(
              s->bpp.pls, pkt->type, true, pktdata, blanks);
          log_packet(s->bpp.logctx, PKT_OUTGOING, pkt->type,
                     ssh2_pkt_type(s->bpp.pls->kctx, s->bpp.pls->actx,
                                   pkt->type),
                     pktdata.ptr, pktdata.len, nblanks, blanks, &s->out.sequence,
                     pkt->downstream_id, pkt->additional_log_text);
      }
  
      cipherblk = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 8;
      cipherblk = cipherblk < 8 ? 8 : cipherblk;  /* or 8 if blksize < 8 */
  
      if (s->out_comp) {
          unsigned char *newpayload;
          int minlen, newlen;
  
          /*
           * Compress packet payload.
           */
          minlen = pkt->minlen;
          if (minlen) {
              /*
               * Work out how much compressed data we need (at least) to
               * make the overall packet length come to pkt->minlen.
               */
              if (s->out.mac)
                  minlen -= ssh2_mac_alg(s->out.mac)->len;
              minlen -= 8;              /* length field + min padding */
          }
  
          ssh_compressor_compress(s->out_comp, pkt->data + 5, pkt->length - 5,
                                  &newpayload, &newlen, minlen);
          pkt->length = 5;
          put_data(pkt, newpayload, newlen);
          sfree(newpayload);
      }
  
      /*
       * Add padding. At least four bytes, and must also bring total
       * length (minus MAC) up to a multiple of the block size.
       * If pkt->forcepad is set, make sure the packet is at least that size
       * after padding.
       */
      padding = 4;
      unencrypted_prefix = (s->out.mac && s->out.etm_mode) ? 4 : 0;
      padding +=
          (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
          % cipherblk;
      assert(padding <= 255);
      maclen = s->out.mac ? ssh2_mac_alg(s->out.mac)->len : 0;
      origlen = pkt->length;
      for (i = 0; i < padding; i++)
          put_byte(pkt, 0);              /* make space for random padding */
      random_read(pkt->data + origlen, padding);
      pkt->data[4] = padding;
      PUT_32BIT_MSB_FIRST(pkt->data, origlen + padding - 4);
  
      /* Encrypt length if the scheme requires it */
      if (s->out.cipher &&
          (ssh_cipher_alg(s->out.cipher)->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
          ssh_cipher_encrypt_length(s->out.cipher, pkt->data, 4,
                                    s->out.sequence);
      }
  
      put_padding(pkt, maclen, 0);
  
      if (s->out.mac && s->out.etm_mode) {
          /*
           * OpenSSH-defined encrypt-then-MAC protocol.
           */
          if (s->out.cipher)
              ssh_cipher_encrypt(s->out.cipher,
                                 pkt->data + 4, origlen + padding - 4);
          ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
                            s->out.sequence);
      } else {
          /*
           * SSH-2 standard protocol.
           */
          if (s->out.mac)
              ssh2_mac_generate(s->out.mac, pkt->data, origlen + padding,
                                s->out.sequence);
          if (s->out.cipher)
              ssh_cipher_encrypt(s->out.cipher, pkt->data, origlen + padding);
      }
  
      s->out.sequence++;       /* whether or not we MACed */
  
      dts_consume(&s->stats->out, origlen + padding);
  }
  
  static void ssh2_bpp_format_packet(struct ssh2_bpp_state *s, PktOut *pkt)
  {
      if (pkt->minlen > 0 && !s->out_comp) {
          /*
           * If we've been told to pad the packet out to a given minimum
           * length, but we're not compressing (and hence can't get the
           * compression to do the padding by pointlessly opening and
           * closing zlib blocks), then our other strategy is to precede
           * this message with an SSH_MSG_IGNORE that makes it up to the
           * right length.
           *
           * A third option in principle, and the most obviously
           * sensible, would be to set the explicit padding field in the
           * packet to more than its minimum value. Sadly, that turns
           * out to break some servers (our institutional memory thinks
           * Cisco in particular) and so we abandoned that idea shortly
           * after trying it.
           */
  
          /*
           * Calculate the length we expect the real packet to have.
           */
          int block, length;
          PktOut *ignore_pkt;
  
          block = s->out.cipher ? ssh_cipher_alg(s->out.cipher)->blksize : 0;
          if (block < 8)
              block = 8;
          length = pkt->length;
          length += 4;       /* minimum 4 byte padding */
          length += block-1;
          length -= (length % block);
          if (s->out.mac)
              length += ssh2_mac_alg(s->out.mac)->len;
  
          if (length < pkt->minlen) {
              /*
               * We need an ignore message. Calculate its length.
               */
              length = pkt->minlen - length;
  
              /*
               * And work backwards from that to the length of the
               * contained string.
               */
              if (s->out.mac)
                  length -= ssh2_mac_alg(s->out.mac)->len;
              length -= 8;               /* length field + min padding */
              length -= 5;               /* type code + string length prefix */
  
              if (length < 0)
                  length = 0;
  
              ignore_pkt = ssh2_bpp_new_pktout(SSH2_MSG_IGNORE);
              put_uint32(ignore_pkt, length);
              size_t origlen = ignore_pkt->length;
              for (size_t i = 0; i < length; i++)
                  put_byte(ignore_pkt, 0);  /* make space for random padding */
              random_read(ignore_pkt->data + origlen, length);
              ssh2_bpp_format_packet_inner(s, ignore_pkt);
              bufchain_add(s->bpp.out_raw, ignore_pkt->data, ignore_pkt->length);
              ssh_free_pktout(ignore_pkt);
          }
      }
  
      ssh2_bpp_format_packet_inner(s, pkt);
      bufchain_add(s->bpp.out_raw, pkt->data, pkt->length);
  }
  
  static void ssh2_bpp_handle_output(BinaryPacketProtocol *bpp)
  {
      struct ssh2_bpp_state *s = container_of(bpp, struct ssh2_bpp_state, bpp);
      PktOut *pkt;
      int n_userauth;
  
      /*
       * Count the userauth packets in the queue.
       */
      n_userauth = 0;
      for (pkt = pq_first(&s->bpp.out_pq); pkt != NULL;
           pkt = pq_next(&s->bpp.out_pq, pkt))
          if (userauth_range(pkt->type))
              n_userauth++;
  
      if (s->pending_compression && !n_userauth) {
          /*
           * We're currently blocked from sending any outgoing packets
           * until the other end tells us whether we're going to have to
           * enable compression or not.
           *
           * If our end has pushed a userauth packet on the queue, that
           * must mean it knows that a USERAUTH_SUCCESS is not
           * immediately forthcoming, so we unblock ourselves and send
           * up to and including that packet. But in this if statement,
           * there aren't any, so we're still blocked.
           */
          return;
      }
  
      if (s->cbc_ignore_workaround) {
          /*
           * When using a CBC-mode cipher in SSH-2, it's necessary to
           * ensure that an attacker can't provide data to be encrypted
           * using an IV that they know. We ensure this by inserting an
           * SSH_MSG_IGNORE if the last cipher block of the previous
           * packet has already been sent to the network (which we
           * approximate conservatively by checking if it's vanished
           * from out_raw).
           */
          if (bufchain_size(s->bpp.out_raw) <
              (ssh_cipher_alg(s->out.cipher)->blksize +
               ssh2_mac_alg(s->out.mac)->len)) {
              /*
               * There's less data in out_raw than the MAC size plus the
               * cipher block size, which means at least one byte of
               * that cipher block must already have left. Add an
               * IGNORE.
               */
              pkt = ssh_bpp_new_pktout(&s->bpp, SSH2_MSG_IGNORE);
              put_stringz(pkt, "");
              ssh2_bpp_format_packet(s, pkt);
          }
      }
  
      while ((pkt = pq_pop(&s->bpp.out_pq)) != NULL) {
          int type = pkt->type;
  
          if (userauth_range(type))
              n_userauth--;
  
          ssh2_bpp_format_packet(s, pkt);
          ssh_free_pktout(pkt);
  
          if (n_userauth == 0 && s->out.pending_compression && !s->is_server) {
              /*
               * This is the last userauth packet in the queue, so
               * unless our side decides to send another one in future,
               * we have to assume will potentially provoke
               * USERAUTH_SUCCESS. Block (non-userauth) outgoing packets
               * until we see the reply.
               */
              s->pending_compression = true;
              return;
          } else if (type == SSH2_MSG_USERAUTH_SUCCESS && s->is_server) {
              ssh2_bpp_enable_pending_compression(s);
          }
      }
  }
  