send_le (int slot, int class, int ins, int p0, int p1,
         int lc, const char *data, int le,
         unsigned char **retbuf, size_t *retbuflen,
         pininfo_t *pininfo, int extended_mode)
{
#define SHORT_RESULT_BUFFER_SIZE 258
  /* We allocate 8 extra bytes as a safety margin towards a driver bug.  */
  unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
  unsigned char *result_buffer = NULL;
  size_t result_buffer_size;
  unsigned char *result;
  size_t resultlen;
  unsigned char short_apdu_buffer[5+256+1];
  unsigned char *apdu_buffer = NULL;
  size_t apdu_buffer_size;
  unsigned char *apdu;
  size_t apdulen;
  int sw;
  long rc; /* We need a long here due to PC/SC. */
  int did_exact_length_hack = 0;
  int use_chaining = 0;
  int use_extended_length = 0;
  int lc_chunk;

  if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
    return SW_HOST_NO_DRIVER;

  if (DBG_CARD_IO)
    log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n",
               class, ins, p0, p1, lc, le, extended_mode);

  if (lc != -1 && (lc > 255 || lc < 0))
    {
      /* Data does not fit into an APDU.  What we do now depends on
         the EXTENDED_MODE parameter.  */
      if (!extended_mode)
        return SW_WRONG_LENGTH; /* No way to send such an APDU.  */
      else if (extended_mode > 0)
        use_extended_length = 1;
      else if (extended_mode < 0)
        {
          /* Send APDU using chaining mode.  */
          if (lc > 16384)
            return SW_WRONG_LENGTH;   /* Sanity check.  */
          if ((class&0xf0) != 0)
            return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0.  */
          use_chaining = extended_mode == -1? 255 : -extended_mode;
          use_chaining &= 0xff;
        }
      else
        return SW_HOST_INV_VALUE;
    }
  else if (lc == -1 && extended_mode > 0)
    use_extended_length = 1;

  if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0))
    {
      /* Expected Data does not fit into an APDU.  What we do now
         depends on the EXTENDED_MODE parameter.  Note that a check
         for command chaining does not make sense because we are
         looking at Le.  */
      if (!extended_mode)
        return SW_WRONG_LENGTH; /* No way to send such an APDU.  */
      else if (use_extended_length)
        ; /* We are already using extended length.  */
      else if (extended_mode > 0)
        use_extended_length = 1;
      else
        return SW_HOST_INV_VALUE;
    }

  if ((!data && lc != -1) || (data && lc == -1))
    return SW_HOST_INV_VALUE;

  if (use_extended_length)
    {
      if (reader_table[slot].is_t0)
        return SW_HOST_NOT_SUPPORTED;

      /* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le.  */
      apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2;
      apdu_buffer = xtrymalloc (apdu_buffer_size + 10);
      if (!apdu_buffer)
        return SW_HOST_OUT_OF_CORE;
      apdu = apdu_buffer;
    }
  else
    {
      apdu_buffer_size = sizeof short_apdu_buffer;
      apdu = short_apdu_buffer;
    }

  if (use_extended_length && (le > 256 || le < 0))
    {
      result_buffer_size = le < 0? 4096 : le;
      result_buffer = xtrymalloc (result_buffer_size + 10);
      if (!result_buffer)
        {
          xfree (apdu_buffer);
          return SW_HOST_OUT_OF_CORE;
        }
      result = result_buffer;
    }
  else
    {
      result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
      result = short_result_buffer;
    }
#undef SHORT_RESULT_BUFFER_SIZE

  if ((sw = lock_slot (slot)))
    {
      xfree (apdu_buffer);
      xfree (result_buffer);
      return sw;
    }

  do
    {
      if (use_extended_length)
        {
          use_chaining = 0;
          apdulen = 0;
          apdu[apdulen++] = class;
          apdu[apdulen++] = ins;
          apdu[apdulen++] = p0;
          apdu[apdulen++] = p1;
          apdu[apdulen++] = 0;  /* Z byte: Extended length marker.  */
          if (lc >= 0)
            {
              apdu[apdulen++] = ((lc >> 8) & 0xff);
              apdu[apdulen++] = (lc & 0xff);
              memcpy (apdu+apdulen, data, lc);
              data += lc;
              apdulen += lc;
            }
          if (le != -1)
            {
              apdu[apdulen++] = ((le >> 8) & 0xff);
              apdu[apdulen++] = (le & 0xff);
            }
        }
      else
        {
          apdulen = 0;
          apdu[apdulen] = class;
          if (use_chaining && lc > 255)
            {
              apdu[apdulen] |= 0x10;
              assert (use_chaining < 256);
              lc_chunk = use_chaining;
              lc -= use_chaining;
            }
          else
            {
              use_chaining = 0;
              lc_chunk = lc;
            }
          apdulen++;
          apdu[apdulen++] = ins;
          apdu[apdulen++] = p0;
          apdu[apdulen++] = p1;
          if (lc_chunk != -1)
            {
              apdu[apdulen++] = lc_chunk;
              memcpy (apdu+apdulen, data, lc_chunk);
              data += lc_chunk;
              apdulen += lc_chunk;
              /* T=0 does not allow the use of Lc together with Le;
                 thus disable Le in this case.  */
              if (reader_table[slot].is_t0)
                le = -1;
            }
          if (le != -1 && !use_chaining)
            apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */
        }

    exact_length_hack:
      /* As a safeguard don't pass any garbage to the driver.  */
      assert (apdulen <= apdu_buffer_size);
      memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
      resultlen = result_buffer_size;
      rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
      if (rc || resultlen < 2)
        {
          log_info ("apdu_send_simple(%d) failed: %s\n",
                    slot, apdu_strerror (rc));
          unlock_slot (slot);
          xfree (apdu_buffer);
          xfree (result_buffer);
          return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
        }
      sw = (result[resultlen-2] << 8) | result[resultlen-1];
      if (!use_extended_length
          && !did_exact_length_hack && SW_EXACT_LENGTH_P (sw))
        {
          apdu[apdulen-1] = (sw & 0x00ff);
          did_exact_length_hack = 1;
          goto exact_length_hack;
        }
    }
  while (use_chaining && sw == SW_SUCCESS);

  if (apdu_buffer)
    {
      xfree (apdu_buffer);
      apdu_buffer = NULL;
      apdu_buffer_size = 0;
    }

  /* Store away the returned data but strip the statusword. */
  resultlen -= 2;
  if (DBG_CARD_IO)
    {
      log_debug (" response: sw=%04X  datalen=%d\n",
                 sw, (unsigned int)resultlen);
      if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
        log_printhex ("    dump: ", result, resultlen);
    }

  if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
    {
      if (retbuf)
        {
          *retbuf = xtrymalloc (resultlen? resultlen : 1);
          if (!*retbuf)
            {
              unlock_slot (slot);
              xfree (result_buffer);
              return SW_HOST_OUT_OF_CORE;
            }
          *retbuflen = resultlen;
          memcpy (*retbuf, result, resultlen);
        }
    }
  else if ((sw & 0xff00) == SW_MORE_DATA)
    {
      unsigned char *p = NULL, *tmp;
      size_t bufsize = 4096;

      /* It is likely that we need to return much more data, so we
         start off with a large buffer. */
      if (retbuf)
        {
          *retbuf = p = xtrymalloc (bufsize);
          if (!*retbuf)
            {
              unlock_slot (slot);
              xfree (result_buffer);
              return SW_HOST_OUT_OF_CORE;
            }
          assert (resultlen < bufsize);
          memcpy (p, result, resultlen);
          p += resultlen;
        }

      do
        {
          int len = (sw & 0x00ff);

          if (DBG_CARD_IO)
            log_debug ("apdu_send_simple(%d): %d more bytes available\n",
                       slot, len);
          apdu_buffer_size = sizeof short_apdu_buffer;
          apdu = short_apdu_buffer;
          apdulen = 0;
          apdu[apdulen++] = class;
          apdu[apdulen++] = 0xC0;
          apdu[apdulen++] = 0;
          apdu[apdulen++] = 0;
          apdu[apdulen++] = len;
          assert (apdulen <= apdu_buffer_size);
          memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
          resultlen = result_buffer_size;
          rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
          if (rc || resultlen < 2)
            {
              log_error ("apdu_send_simple(%d) for get response failed: %s\n",
                         slot, apdu_strerror (rc));
              unlock_slot (slot);
              xfree (result_buffer);
              return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
            }
          sw = (result[resultlen-2] << 8) | result[resultlen-1];
          resultlen -= 2;
          if (DBG_CARD_IO)
            {
              log_debug ("     more: sw=%04X  datalen=%d\n",
                         sw, (unsigned int)resultlen);
              if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
                log_printhex ("     dump: ", result, resultlen);
            }

          if ((sw & 0xff00) == SW_MORE_DATA
              || sw == SW_SUCCESS
              || sw == SW_EOF_REACHED )
            {
              if (retbuf && resultlen)
                {
                  if (p - *retbuf + resultlen > bufsize)
                    {
                      bufsize += resultlen > 4096? resultlen: 4096;
                      tmp = xtryrealloc (*retbuf, bufsize);
                      if (!tmp)
                        {
                          unlock_slot (slot);
                          xfree (result_buffer);
                          return SW_HOST_OUT_OF_CORE;
                        }
                      p = tmp + (p - *retbuf);
                      *retbuf = tmp;
                    }
                  memcpy (p, result, resultlen);
                  p += resultlen;
                }
            }
          else
            log_info ("apdu_send_simple(%d) "
                      "got unexpected status %04X from get response\n",
                      slot, sw);
        }
      while ((sw & 0xff00) == SW_MORE_DATA);

      if (retbuf)
        {
          *retbuflen = p - *retbuf;
          tmp = xtryrealloc (*retbuf, *retbuflen);
          if (tmp)
            *retbuf = tmp;
        }
    }

  unlock_slot (slot);
  xfree (result_buffer);

  if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
    log_printhex ("      dump: ", *retbuf, *retbuflen);

  return sw;
}