apdu_send_direct (int slot, size_t extended_length,
                  const unsigned char *apdudata, size_t apdudatalen,
                  int handle_more,
                  unsigned char **retbuf, size_t *retbuflen)
{
#define SHORT_RESULT_BUFFER_SIZE 258
  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+10];
  unsigned char *apdu_buffer = NULL;
  unsigned char *apdu;
  size_t apdulen;
  int sw;
  long rc; /* we need a long here due to PC/SC. */
  int class;

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

  if (apdudatalen > 65535)
    return SW_HOST_INV_VALUE;

  if (apdudatalen > sizeof short_apdu_buffer - 5)
    {
      apdu_buffer = xtrymalloc (apdudatalen + 5);
      if (!apdu_buffer)
        return SW_HOST_OUT_OF_CORE;
      apdu = apdu_buffer;
    }
  else
    {
      apdu = short_apdu_buffer;
    }
  apdulen = apdudatalen;
  memcpy (apdu, apdudata, apdudatalen);
  class = apdulen? *apdu : 0;

  if (extended_length >= 256 && extended_length <= 65536)
    {
      result_buffer_size = extended_length;
      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 = trylock_slot (slot)))
    {
      xfree (apdu_buffer);
      xfree (result_buffer);
      return sw;
    }

  resultlen = result_buffer_size;
  rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
  xfree (apdu_buffer);
  apdu_buffer = NULL;
  if (rc || resultlen < 2)
    {
      log_error ("apdu_send_direct(%d) 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];
  /* 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 (handle_more && (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 + 2);
          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_direct(%d): %d more bytes available\n",
                       slot, len);
          apdu = short_apdu_buffer;
          apdulen = 0;
          apdu[apdulen++] = class;
          apdu[apdulen++] = 0xC0;
          apdu[apdulen++] = 0;
          apdu[apdulen++] = 0;
          apdu[apdulen++] = len;
          memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen);
          resultlen = result_buffer_size;
          rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
          if (rc || resultlen < 2)
            {
              log_error ("apdu_send_direct(%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 + 2);
                      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_direct(%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 + 2);
          if (tmp)
            *retbuf = tmp;
        }
    }
  else
    {
      if (retbuf)
        {
          *retbuf = xtrymalloc ((resultlen? resultlen : 1)+2);
          if (!*retbuf)
            {
              unlock_slot (slot);
              xfree (result_buffer);
              return SW_HOST_OUT_OF_CORE;
            }
          *retbuflen = resultlen;
          memcpy (*retbuf, result, resultlen);
        }
    }

  unlock_slot (slot);
  xfree (result_buffer);

  /* Append the status word.  Note that we reserved the two extra
     bytes while allocating the buffer.  */
  if (retbuf)
    {
      (*retbuf)[(*retbuflen)++] = (sw >> 8);
      (*retbuf)[(*retbuflen)++] = sw;
    }

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

  return 0;
}