/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * Copyright (C) Jay Sorg 2013 jay.sorg@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/*
 * smartcard redirection support, PCSC daemon standin
 * this will act like pcsc daemon
 * pcsc lib and daemon write struct on unix domain socket for communication
 *
 * Currently this file implements some of the PDUs detailed in [MS-RDPESC].
 *
 * The PDUs use DCE IDL structs. These are required to be re-interpreted
 * in DCE NDR (Netword Data Representation)
 *
 * For more information on this subject see DCE publication C706
 * "DCE 1.1: Remote Procedure Call" 1997. In particular:-
 * Section 4.2 : Describes the IDL
 * Section 14 : Describes the NDR
 */

#if defined(HAVE_CONFIG_H)
#include <config_ac.h>
#endif

#define JAY_TODO_CONTEXT    0
#define JAY_TODO_WIDE       1

#define PCSC_STANDIN 1

#include "os_calls.h"
#include "string_calls.h"
#include "smartcard.h"
#include "log.h"
#include "irp.h"
#include "devredir.h"
#include "trans.h"
#include "chansrv.h"
#include "list.h"

#if PCSC_STANDIN


extern int g_display_num; /* in chansrv.c */

static int g_autoinc = 0; /* general purpose autoinc */

struct pcsc_card /* item for list of open cards in one context */
{
    tui32 app_card;  /* application card, always 4 bytes */
    int card_bytes;  /* client card bytes */
    char card[16];   /* client card */
};

struct pcsc_context
{
    tui32 app_context;  /* application context, always 4 byte */
    int context_bytes;  /* client context bytes */
    char context[16];   /* client context */
    struct list *cards; /* these need to be released on close */
};

/*****************************************************************************/
struct pcsc_uds_client
{
    int uds_client_id;     /* unique id represents each app */
    struct trans *con;     /* the connection to the app */
    struct list *contexts; /* list of struct pcsc_context */
    struct pcsc_context *connect_context;
};

static struct list *g_uds_clients = 0; /* struct pcsc_uds_client */

static struct trans *g_lis = 0;
static char g_pcsclite_ipc_dir[256] = "";
static char g_pcsclite_ipc_file[256] = "";

/*****************************************************************************/
/* got a new unix domain socket connection */
static struct pcsc_uds_client *
create_uds_client(struct trans *con)
{
    struct pcsc_uds_client *uds_client;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "create_uds_client:");
    if (con == 0)
    {
        return 0;
    }
    uds_client = g_new0(struct pcsc_uds_client, 1);
    if (uds_client == 0)
    {
        return 0;
    }
    g_autoinc++;
    uds_client->uds_client_id = g_autoinc;
    uds_client->con = con;
    con->callback_data = uds_client;
    return uds_client;
}

/*****************************************************************************/
static struct pcsc_uds_client *
get_uds_client_by_id(int uds_client_id)
{
    struct pcsc_uds_client *uds_client;
    int index;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "get_uds_client_by_id:");
    if (uds_client_id == 0)
    {
        LOG(LOG_LEVEL_ERROR, "get_uds_client_by_id: uds_client_id is zero");
        return 0;
    }
    if (g_uds_clients == 0)
    {
        LOG(LOG_LEVEL_ERROR, "get_uds_client_by_id: g_uds_clients is nil");
        return 0;
    }
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  count %d", g_uds_clients->count);
    for (index = 0; index < g_uds_clients->count; index++)
    {
        uds_client = (struct pcsc_uds_client *)
                     list_get_item(g_uds_clients, index);
        if (uds_client->uds_client_id == uds_client_id)
        {
            return uds_client;
        }
    }
    LOG(LOG_LEVEL_ERROR, "get_uds_client_by_id: can't find uds_client_id %d",
        uds_client_id);
    return 0;
}

/*****************************************************************************/
struct pcsc_context *
get_pcsc_context_by_app_context(struct pcsc_uds_client *uds_client,
                                tui32 app_context)
{
    struct pcsc_context *rv;
    int index;

    if (uds_client == 0)
    {
        return 0;
    }
    if (uds_client->contexts == 0)
    {
        return 0;
    }
    for (index = 0; index < uds_client->contexts->count; index++)
    {
        rv = (struct pcsc_context *)
             list_get_item(uds_client->contexts, index);
        if (rv->app_context == app_context)
        {
            return rv;
        }
    }
    return 0;
}

/*****************************************************************************/
struct pcsc_card *
get_pcsc_card_by_app_card(struct pcsc_uds_client *uds_client,
                          tui32 app_card, struct pcsc_context **acontext)
{
    struct pcsc_card *lcard;
    struct pcsc_context *lcontext;
    int index;
    int index1;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "get_pcsc_card_by_app_card: app_card %d",
              app_card);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "get_pcsc_card_by_app_card: uds_client is null");
        return 0;
    }
    if (uds_client->contexts == 0)
    {
        LOG(LOG_LEVEL_ERROR, "get_pcsc_card_by_app_card: uds_client->contexts is null");
        return 0;
    }
    for (index = 0; index < uds_client->contexts->count; index++)
    {
        lcontext = (struct pcsc_context *)
                   list_get_item(uds_client->contexts, index);
        if (lcontext != 0)
        {
            if (lcontext->cards != 0)
            {
                for (index1 = 0; index1 < lcontext->cards->count; index1++)
                {
                    lcard = (struct pcsc_card *)
                            list_get_item(lcontext->cards, index1);
                    if (lcard != 0)
                    {
                        if (lcard->app_card == app_card)
                        {
                            if (acontext != 0)
                            {
                                *acontext = lcontext;
                            }
                            return lcard;
                        }
                    }
                }
            }
        }
    }
    LOG(LOG_LEVEL_ERROR, "get_pcsc_card_by_app_card: app_card %d "
        "not found in uds_client->contexts->cards", app_card);
    return 0;
}

/*****************************************************************************/
static int
free_uds_client(struct pcsc_uds_client *uds_client)
{
    int i;
    int j;
    struct pcsc_context *context;
    struct pcsc_card *card;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "free_uds_client:");
    if (uds_client == 0)
    {
        return 0;
    }
    if (uds_client->contexts != 0)
    {
        for (i = 0; i < uds_client->contexts->count; i++)
        {
            context = (struct pcsc_context *)
                      list_get_item(uds_client->contexts, i);
            if (context != 0)
            {
                if (context->cards != 0)
                {
                    for (j = 0; j < context->cards->count; j++)
                    {
                        card = (struct pcsc_card *)
                               list_get_item(context->cards, j);
                        if (card != 0)
                        {
                            /* TODO: send free card to client */
                            g_free(card);
                        }
                    }
                    list_delete(context->cards);
                }
                LOG_DEVEL(LOG_LEVEL_DEBUG, "  left over context %p", context->context);
                scard_send_cancel(0, context->context, context->context_bytes);
                scard_send_release_context(0, context->context,
                                           context->context_bytes);
                g_free(context);
            }
        }
        list_delete(uds_client->contexts);
    }
    trans_delete(uds_client->con);
    g_free(uds_client);
    return 0;
}

/*****************************************************************************/
static struct pcsc_context *
uds_client_add_context(struct pcsc_uds_client *uds_client,
                       char *context, int context_bytes)
{
    struct pcsc_context *pcscContext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "uds_client_add_context:");
    pcscContext = (struct pcsc_context *)
                  g_malloc(sizeof(struct pcsc_context), 1);
    if (pcscContext == 0)
    {
        LOG(LOG_LEVEL_ERROR,
            "uds_client_add_context: failed to allocate memory for pcsc_context");
        return 0;
    }
    g_autoinc++;
    pcscContext->app_context = g_autoinc;
    pcscContext->context_bytes = context_bytes;
    g_memcpy(pcscContext->context, context, context_bytes);
    if (uds_client->contexts == 0)
    {
        uds_client->contexts = list_create();
        if (uds_client->contexts == 0)
        {
            LOG(LOG_LEVEL_ERROR,
                "uds_client_add_context: failed to allocate memory for "
                "uds_client->contexts");
            return 0;
        }
    }
    list_add_item(uds_client->contexts, (tintptr) pcscContext);
    return pcscContext;
}

/*****************************************************************************/
static int
uds_client_remove_context(struct pcsc_uds_client *uds_client,
                          struct pcsc_context *acontext)
{
    int index;

    if (uds_client->contexts == 0)
    {
        LOG(LOG_LEVEL_ERROR,
            "uds_client_remove_context: uds_client->contexts is null");
        return 1;
    }
    index = list_index_of(uds_client->contexts, (tintptr) acontext);
    if (index < 0)
    {
        LOG(LOG_LEVEL_ERROR,
            "uds_client_remove_context: pcsc_context not found in uds_client->contexts");
        return 1;
    }
    list_remove_item(uds_client->contexts, index);
    g_free(acontext); // TODO free cards
    return 0;
}

/*****************************************************************************/
static struct pcsc_card *
context_add_card(struct pcsc_uds_client *uds_client,
                 struct pcsc_context *acontext,
                 char *card, int card_bytes)
{
    struct pcsc_card *pcscCard;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "context_add_card: card_bytes %d", card_bytes);
    pcscCard = (struct pcsc_card *)
               g_malloc(sizeof(struct pcsc_card), 1);
    if (pcscCard == 0)
    {
        LOG(LOG_LEVEL_ERROR,
            "context_add_card: failed to allocate memory for pcsc_card");
        return 0;
    }
    g_autoinc++;
    pcscCard->app_card = g_autoinc;
    pcscCard->card_bytes = card_bytes;
    g_memcpy(pcscCard->card, card, card_bytes);
    if (acontext->cards == 0)
    {
        acontext->cards = list_create();
        if (acontext->cards == 0)
        {
            LOG(LOG_LEVEL_ERROR, "context_add_card: failed to allocate "
                "memory for uds_client->contexts->cards");
            return 0;
        }
    }
    list_add_item(acontext->cards, (tintptr) pcscCard);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  new app_card %d", pcscCard->app_card);
    return pcscCard;
}

/*****************************************************************************/
int
scard_pcsc_get_wait_objs(tbus *objs, int *count, int *timeout)
{
    struct pcsc_uds_client *uds_client;
    int index;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_pcsc_get_wait_objs:");
    if (g_lis != 0)
    {
        trans_get_wait_objs(g_lis, objs, count);
    }
    if (g_uds_clients != 0)
    {
        for (index = 0; index < g_uds_clients->count; index++)
        {
            uds_client = (struct pcsc_uds_client *)
                         list_get_item(g_uds_clients, index);
            if (uds_client != 0)
            {
                trans_get_wait_objs(uds_client->con, objs, count);
            }
        }
    }
    return 0;
}

/*****************************************************************************/
int
scard_pcsc_check_wait_objs(void)
{
    struct pcsc_uds_client *uds_client;
    int index;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_pcsc_check_wait_objs:");
    if (g_lis != 0)
    {
        if (trans_check_wait_objs(g_lis) != 0)
        {
            LOG(LOG_LEVEL_ERROR,
                "scard_pcsc_check_wait_objs: g_lis trans_check_wait_objs error");
        }
    }
    if (g_uds_clients != 0)
    {
        index = 0;
        while (index < g_uds_clients->count)
        {
            uds_client = (struct pcsc_uds_client *)
                         list_get_item(g_uds_clients, index);
            if (uds_client != 0)
            {
                if (trans_check_wait_objs(uds_client->con) != 0)
                {
                    free_uds_client(uds_client);
                    list_remove_item(g_uds_clients, index);
                    continue;
                }
            }
            index++;
        }
    }
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_process_establish_context(struct trans *con, struct stream *in_s)
{
    int dwScope;
    struct pcsc_uds_client *uds_client;
    void *user_data;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_establish_context:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, dwScope);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_establish_context: dwScope 0x%8.8x", dwScope);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    scard_send_establish_context(user_data, dwScope);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_establish_context_return(void *user_data,
                                        struct stream *in_s,
                                        int len, int status)
{
    int bytes;
    int uds_client_id;
    int context_bytes;
    int app_context;
    char context[16];
    struct stream *out_s;
    struct pcsc_uds_client *uds_client;
    struct trans *con;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_establish_context_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_establish_context_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    lcontext = 0;
    app_context = 0;
    g_memset(context, 0, 16);
    if (status == 0)
    {
        in_uint8s(in_s, 28);
        in_uint32_le(in_s, context_bytes);
        if (context_bytes > 16)
        {
            LOG(LOG_LEVEL_ERROR, "scard_function_establish_context_return: opps "
                "context_bytes %d", context_bytes);
            LOG_DEVEL_HEXDUMP(LOG_LEVEL_TRACE, "", in_s->p, context_bytes);
            return 1;
        }
        in_uint8a(in_s, context, context_bytes);
        lcontext = uds_client_add_context(uds_client, context, context_bytes);
        app_context = lcontext->app_context;
        LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_establish_context_return: "
                  "app_context %d", app_context);
    }
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, app_context);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x01); /* SCARD_ESTABLISH_CONTEXT 0x01 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_release_context(struct trans *con, struct stream *in_s)
{
    int hContext;
    struct pcsc_uds_client *uds_client;
    struct pcsc_context *lcontext;
    void *user_data;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_release_context:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hContext);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_release_context: hContext 0x%8.8x", hContext);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcontext = get_pcsc_context_by_app_context(uds_client, hContext);
    if (lcontext == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_release_context: "
            "get_pcsc_context_by_app_context failed");
        return 1;
    }
    scard_send_release_context(user_data, lcontext->context,
                               lcontext->context_bytes);
    uds_client_remove_context(uds_client, lcontext);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_release_context_return(void *user_data,
                                      struct stream *in_s,
                                      int len, int status)
{
    int bytes;
    int uds_client_id;
    struct stream *out_s;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_release_context_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_release_context_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x02); /* SCARD_RELEASE_CONTEXT 0x02 */
    return trans_force_write(con);
}

/*****************************************************************************/
struct pcsc_list_readers
{
    int uds_client_id;
    int cchReaders;
};

/*****************************************************************************/
/* returns error */
int
scard_process_list_readers(struct trans *con, struct stream *in_s)
{
    int hContext;
    unsigned int bytes_groups;
    int cchReaders;
    /*
     * At the time of writing, the groups strings which can be sent
     * over this interface are all small:-
     *
     * "SCard$AllReaders", "SCard$DefaultReaders", "SCard$LocalReaders" and
     * "SCard$SystemReaders"
     *
     * We'll allow a bit extra in case the interface changes
     */
    char groups[256];
    struct pcsc_uds_client *uds_client;
    struct pcsc_context *lcontext;
    struct pcsc_list_readers *pcscListReaders;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_list_readers:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hContext);
    in_uint32_le(in_s, bytes_groups);
    if (bytes_groups > (sizeof(groups) - 1))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_list_readers: Unreasonable string length %u",
            bytes_groups);
        return 1;
    }
    in_uint8a(in_s, groups, bytes_groups);
    groups[bytes_groups] = '\0';
    in_uint32_le(in_s, cchReaders);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_list_readers: hContext 0x%8.8x cchReaders %d",
              hContext, cchReaders);
    lcontext = get_pcsc_context_by_app_context(uds_client, hContext);
    if (lcontext == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_list_readers: "
            "get_pcsc_context_by_app_context failed");
        return 1;
    }
    pcscListReaders = g_new0(struct pcsc_list_readers, 1);
    pcscListReaders->uds_client_id = uds_client->uds_client_id;
    pcscListReaders->cchReaders = cchReaders;
    scard_send_list_readers(pcscListReaders, lcontext->context,
                            lcontext->context_bytes, groups, cchReaders, 1);
    return 0;
}

/*****************************************************************************/
/**
 * Counts the number of non-NULL strings in a multistring
 *
 * [MS-RDPESC] A multistring is "A series of null-terminated character
 * strings terminated by a final null character stored in a contiguous
 * block of memory."
 *
 * The string is guaranteed to have at least the returned number of NULL
 * characters in it
 */
unsigned int
count_multistring_elements(const char *str, unsigned int len)
{
    unsigned int rv = 0;

    if (str != NULL)
    {
        while (len > 0)
        {
            // Look for a terminator
            const char *p = (const char *)memchr(str, '\0', len);
            if (!p || p == str)
            {
                // No terminator, or an empty string encountered */
                break;
            }

            ++rv;
            ++p; // Skip terminator
            len -= (p - str);
            str = p;
        }
    }

    return rv;
}

/*****************************************************************************/
int
scard_function_list_readers_return(void *user_data,
                                   struct stream *in_s,
                                   int len, int status)
{
    /* see [MS-RDPESC] 2.2.3.4
     *
     * IDL:-
     *
     * typedef struct _longAndMultiString_Return {
     *     long ReturnCode;
     *     [range(0,65536)] unsigned long cBytes;
     *     [unique] [size_is(cBytes)] byte *msz;
     *     } ListReaderGroups_Return, ListReaders_Return;
     *
     * Type summary:-
     *
     * ReturnCode         32-bit word
     * CBytes             Unsigned 32-bit word
     * msz                Embedded full pointer to conformant array of bytes
     *
     * NDR:-
     *
     * Offset   Decription
     * 0        ReturnCode
     * 4        cBytes
     * 8        msz pointer Referent Identifier
     * 12       length of multistring in bytes
     * 16       Multistring data
     */
    struct stream *out_s;
    int            readers;
    int            index;
    int            bytes;
    int            cchReaders;
    int            llen;
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;
    struct pcsc_list_readers *pcscListReaders;
    char *msz_readers = NULL;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_list_readers_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    pcscListReaders = (struct pcsc_list_readers *) user_data;
    if (pcscListReaders == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_list_readers_return: "
            "pcscListReaders is nil");
        return 1;
    }
    uds_client_id = pcscListReaders->uds_client_id;
    cchReaders = pcscListReaders->cchReaders;
    g_free(pcscListReaders);
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_list_readers_return: "
            "get_uds_client_by_id failed, could not find id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    readers = 0;
    llen = 0;
    if (status == 0)
    {
        // Skip [C706] PDU Header
        in_uint8s(in_s, 16);
        // Move to length of multistring in bytes
        in_uint8s(in_s, 12);

        in_uint32_le(in_s, llen);
        if (cchReaders > 0)
        {
            // Convert the wide multistring to a UTF-8 multistring
            unsigned int u8len;
            u8len = in_utf16_le_fixed_as_utf8_length(in_s, len / 2);
            msz_readers = (char *)malloc(u8len);
            if (msz_readers == NULL)
            {
                LOG(LOG_LEVEL_ERROR, "scard_function_list_readers_return: "
                    "Can't allocate %u bytes of memory", u8len);
                readers = 0;
            }
            else
            {
                in_utf16_le_fixed_as_utf8(in_s, len / 2, msz_readers, u8len);
                readers = count_multistring_elements(msz_readers, u8len);
            }
        }
    }

    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, llen);
    out_uint32_le(out_s, readers);
    {
        const char *p = msz_readers;
        for (index = 0; index < readers; index++)
        {
            unsigned int slen = strlen(p);
            if (slen < 100)
            {
                out_uint8a(out_s, p, slen);
                out_uint8s(out_s, 100 - slen);
            }
            else
            {
                out_uint8a(out_s, p, 99);
                out_uint8s(out_s, 1);
            }
            p += (slen + 1);
        }
    }
    free(msz_readers);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x03); /* SCARD_LIST_READERS 0x03 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_connect(struct trans *con, struct stream *in_s)
{
    int hContext;
    READER_STATE rs;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_connect:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    g_memset(&rs, 0, sizeof(rs));
    in_uint32_le(in_s, hContext);
    in_uint8a(in_s, rs.reader_name, 100);
    in_uint32_le(in_s, rs.dwShareMode);
    in_uint32_le(in_s, rs.dwPreferredProtocols);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_connect: rs.reader_name %s dwShareMode 0x%8.8x "
              "dwPreferredProtocols 0x%8.8x", rs.reader_name, rs.dwShareMode,
              rs.dwPreferredProtocols);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcontext = get_pcsc_context_by_app_context(uds_client, hContext);
    if (lcontext == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_connect: "
            "get_pcsc_context_by_app_context failed");
        return 1;
    }
    uds_client->connect_context = lcontext;
    scard_send_connect(user_data, lcontext->context, lcontext->context_bytes,
                       1, &rs);
    return 0;
}

/*****************************************************************************/
int
scard_function_connect_return(void *user_data,
                              struct stream *in_s,
                              int len, int status)
{
    int dwActiveProtocol;
    int hCard;
    int bytes;
    int uds_client_id;
    struct stream *out_s;
    struct pcsc_uds_client *uds_client;
    struct trans *con;
    char *card;
    int card_bytes;
    struct pcsc_card *lcard;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_connect_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_connect_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    dwActiveProtocol = 0;
    hCard = 0;
    if (status == 0)
    {
        in_uint8s(in_s, 36);
        in_uint32_le(in_s, dwActiveProtocol);
        if (len > 40)
        {
            in_uint32_le(in_s, card_bytes);
            in_uint8p(in_s, card, card_bytes);
            lcard = context_add_card(uds_client, uds_client->connect_context,
                                     card, card_bytes);
            hCard = lcard->app_card;
            LOG_DEVEL(LOG_LEVEL_DEBUG, "  hCard %d dwActiveProtocol %d", hCard,
                      dwActiveProtocol);
        }
        else
        {
            status = 0x8010000F; /* SCARD_E_PROTO_MISMATCH */
        }
    }
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, hCard);
    out_uint32_le(out_s, dwActiveProtocol);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x04); /* SCARD_CONNECT 0x04 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_disconnect(struct trans *con, struct stream *in_s)
{
    int hCard;
    int dwDisposition;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_context *lcontext;
    struct pcsc_card *lcard;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_disconnect:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hCard);
    in_uint32_le(in_s, dwDisposition);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcontext == 0) || (lcard == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_disconnect: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }
    scard_send_disconnect(user_data,
                          lcontext->context, lcontext->context_bytes,
                          lcard->card, lcard->card_bytes, dwDisposition);
    return 0;
}

/*****************************************************************************/
int
scard_function_disconnect_return(void *user_data,
                                 struct stream *in_s,
                                 int len, int status)
{
    int bytes;
    int uds_client_id;
    struct stream *out_s;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_disconnect_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_disconnect_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x06); /* SCARD_DISCONNECT 0x06 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_begin_transaction(struct trans *con, struct stream *in_s)
{
    int hCard;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_card *lcard;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_begin_transaction:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hCard);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_begin_transaction: hCard 0x%8.8x", hCard);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcard == 0) || (lcontext == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_begin_transaction: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }
    scard_send_begin_transaction(user_data,
                                 lcontext->context, lcontext->context_bytes,
                                 lcard->card, lcard->card_bytes);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_begin_transaction_return(void *user_data,
                                        struct stream *in_s,
                                        int len, int status)
{
    struct stream *out_s;
    int bytes;
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_begin_transaction_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_begin_transaction_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x07); /* SCARD_BEGIN_TRANSACTION 0x07 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_end_transaction(struct trans *con, struct stream *in_s)
{
    int hCard;
    int dwDisposition;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_card *lcard;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_end_transaction:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hCard);
    in_uint32_le(in_s, dwDisposition);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_end_transaction: hCard 0x%8.8x", hCard);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcard == 0) || (lcontext == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_end_transaction: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }
    scard_send_end_transaction(user_data,
                               lcontext->context, lcontext->context_bytes,
                               lcard->card, lcard->card_bytes,
                               dwDisposition);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_end_transaction_return(void *user_data,
                                      struct stream *in_s,
                                      int len, int status)
{
    struct stream *out_s;
    int bytes;
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_end_transaction_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_end_transaction_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;

    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x08); /* SCARD_END_TRANSACTION 0x08 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_function_get_attrib_return(void *user_data,
                                 struct stream *in_s,
                                 int len, int status)
{
    return 0;
}

/*****************************************************************************/
struct pcsc_transmit
{
    int uds_client_id;
    struct xrdp_scard_io_request recv_ior;
    int cbRecvLength;
};

/*****************************************************************************/
/* returns error */
int
scard_process_transmit(struct trans *con, struct stream *in_s)
{
    int hCard;
    int recv_bytes;
    int send_bytes;
    char *send_data;
    struct xrdp_scard_io_request send_ior;
    struct xrdp_scard_io_request recv_ior;
    struct pcsc_uds_client *uds_client;
    struct pcsc_card *lcard;
    struct pcsc_context *lcontext;
    struct pcsc_transmit *pcscTransmit;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_transmit:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_transmit:");
    in_uint32_le(in_s, hCard);
    in_uint32_le(in_s, send_ior.dwProtocol);
    in_uint32_le(in_s, send_ior.cbPciLength);
    in_uint32_le(in_s, send_ior.extra_bytes);
    in_uint8p(in_s, send_ior.extra_data, send_ior.extra_bytes);
    in_uint32_le(in_s, send_bytes);
    in_uint8p(in_s, send_data, send_bytes);
    in_uint32_le(in_s, recv_ior.dwProtocol);
    in_uint32_le(in_s, recv_ior.cbPciLength);
    in_uint32_le(in_s, recv_ior.extra_bytes);
    in_uint8p(in_s, recv_ior.extra_data, recv_ior.extra_bytes);
    in_uint32_le(in_s, recv_bytes);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_transmit: send dwProtocol %d cbPciLength %d "
              "recv dwProtocol %d cbPciLength %d send_bytes %d ",
              send_ior.dwProtocol, send_ior.cbPciLength, recv_ior.dwProtocol,
              recv_ior.cbPciLength, send_bytes);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_transmit: recv_bytes %d", recv_bytes);
    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcard == 0) || (lcontext == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_transmit: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }

    pcscTransmit = (struct pcsc_transmit *)
                   g_malloc(sizeof(struct pcsc_transmit), 1);
    pcscTransmit->uds_client_id = uds_client->uds_client_id;
    pcscTransmit->recv_ior = recv_ior;
    pcscTransmit->cbRecvLength = recv_bytes;

    scard_send_transmit(pcscTransmit,
                        lcontext->context, lcontext->context_bytes,
                        lcard->card, lcard->card_bytes,
                        send_data, send_bytes, recv_bytes,
                        &send_ior, &recv_ior);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_transmit_return(void *user_data,
                               struct stream *in_s,
                               int len, int status)
{
    struct stream *out_s;
    int bytes;
    int val;
    int cbRecvLength;
    char *recvBuf;
    struct xrdp_scard_io_request recv_ior;
    struct pcsc_uds_client *uds_client;
    struct trans *con;
    struct pcsc_transmit *pcscTransmit;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_transmit_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    pcscTransmit = (struct pcsc_transmit *) user_data;
    recv_ior = pcscTransmit->recv_ior;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(pcscTransmit->uds_client_id);
    g_free(pcscTransmit);

    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_transmit_return: "
            "get_uds_client_by_id failed");
        return 1;
    }
    con = uds_client->con;
    cbRecvLength = 0;
    recvBuf = 0;
    if (status == 0)
    {
        in_uint8s(in_s, 20);
        in_uint32_le(in_s, val);
        if (val != 0)
        {
            /* pioRecvPci */
            in_uint8s(in_s, 8);
            in_uint32_le(in_s, recv_ior.dwProtocol);
            in_uint32_le(in_s, recv_ior.cbPciLength);
            recv_ior.cbPciLength += 8;
            in_uint32_le(in_s, recv_ior.extra_bytes);
            if (recv_ior.extra_bytes > 0)
            {
                in_uint8p(in_s, recv_ior.extra_data, recv_ior.extra_bytes);
            }
        }

        in_uint8s(in_s, 4);
        in_uint32_le(in_s, val);
        if (val != 0)
        {
            in_uint32_le(in_s, cbRecvLength);
            in_uint8p(in_s, recvBuf, cbRecvLength);
        }

    }
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_transmit_return: cbRecvLength %d", cbRecvLength);
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, recv_ior.dwProtocol);
    out_uint32_le(out_s, recv_ior.cbPciLength);
    out_uint32_le(out_s, recv_ior.extra_bytes);
    out_uint8a(out_s, recv_ior.extra_data, recv_ior.extra_bytes);
    out_uint32_le(out_s, cbRecvLength);
    out_uint8a(out_s, recvBuf, cbRecvLength);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x09); /* SCARD_TRANSMIT 0x09 */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_control(struct trans *con, struct stream *in_s)
{
    int hCard;
    int send_bytes;
    int recv_bytes;
    int control_code;
    char *send_data;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_context *lcontext;
    struct pcsc_card *lcard;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_control:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_control:");

    in_uint32_le(in_s, hCard);
    in_uint32_le(in_s, control_code);
    in_uint32_le(in_s, send_bytes);
    in_uint8p(in_s, send_data, send_bytes);
    in_uint32_le(in_s, recv_bytes);

    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcard == 0) || (lcontext == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_control: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }
    scard_send_control(user_data, lcontext->context, lcontext->context_bytes,
                       lcard->card, lcard->card_bytes,
                       send_data, send_bytes, recv_bytes,
                       control_code);

    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_control_return(void *user_data,
                              struct stream *in_s,
                              int len, int status)
{
    struct stream *out_s;
    int bytes;
    int cbRecvLength;
    char *recvBuf;
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_control_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_control_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    cbRecvLength = 0;
    recvBuf = 0;
    if (status == 0)
    {
        in_uint8s(in_s, 28);
        in_uint32_le(in_s, cbRecvLength);
        in_uint8p(in_s, recvBuf, cbRecvLength);
    }
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_control_return: cbRecvLength %d", cbRecvLength);
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, cbRecvLength);
    out_uint8a(out_s, recvBuf, cbRecvLength);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x0A); /* SCARD_CONTROL 0x0A */
    return trans_force_write(con);
}

/*****************************************************************************/
struct pcsc_status
{
    int uds_client_id;
    int cchReaderLen;
};

/*****************************************************************************/
/* returns error */
int
scard_process_status(struct trans *con, struct stream *in_s)
{
    int hCard;
    int cchReaderLen;
    int cbAtrLen;
    struct pcsc_uds_client *uds_client;
    struct pcsc_card *lcard;
    struct pcsc_context *lcontext;
    struct pcsc_status *pcscStatus;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_status:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);

    in_uint32_le(in_s, hCard);
    in_uint32_le(in_s, cchReaderLen);
    in_uint32_le(in_s, cbAtrLen);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_status: hCard 0x%8.8x cchReaderLen %d "
              "cbAtrLen %d", hCard, cchReaderLen, cbAtrLen);

    lcard = get_pcsc_card_by_app_card(uds_client, hCard, &lcontext);
    if ((lcard == 0) || (lcontext == 0))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_status: "
            "get_pcsc_card_by_app_card failed");
        return 1;
    }
    pcscStatus = (struct pcsc_status *) g_malloc(sizeof(struct pcsc_status), 1);
    pcscStatus->uds_client_id = uds_client->uds_client_id;
    pcscStatus->cchReaderLen = cchReaderLen;
    scard_send_status(pcscStatus, 1,
                      lcontext->context, lcontext->context_bytes,
                      lcard->card, lcard->card_bytes,
                      cchReaderLen, cbAtrLen);

    return 0;
}

#define MS_SCARD_UNKNOWN    0
#define MS_SCARD_ABSENT     1
#define MS_SCARD_PRESENT    2
#define MS_SCARD_SWALLOWED  3
#define MS_SCARD_POWERED    4
#define MS_SCARD_NEGOTIABLE 5
#define MS_SCARD_SPECIFIC   6

#define PC_SCARD_UNKNOWN    0x0001 /**< Unknown state */
#define PC_SCARD_ABSENT     0x0002 /**< Card is absent */
#define PC_SCARD_PRESENT    0x0004 /**< Card is present */
#define PC_SCARD_SWALLOWED  0x0008 /**< Card not powered */
#define PC_SCARD_POWERED    0x0010 /**< Card is powered */
#define PC_SCARD_NEGOTIABLE 0x0020 /**< Ready for PTS */
#define PC_SCARD_SPECIFIC   0x0040 /**< PTS has been set */

static int g_ms2pc[] = { PC_SCARD_UNKNOWN, PC_SCARD_ABSENT,
                         PC_SCARD_PRESENT, PC_SCARD_SWALLOWED,
                         PC_SCARD_POWERED, PC_SCARD_NEGOTIABLE,
                         PC_SCARD_SPECIFIC
                       };

/*****************************************************************************/
/* returns error */
int
scard_function_status_return(void *user_data,
                             struct stream *in_s,
                             int len, int status)
{
    /* see [MS-RDPESC] 2.2.3.10
     *
     * IDL:-
     *
     * typedef struct _Status_Return {
     *     long ReturnCode;
     *     unsigned long cBytes;
     *     [unique] [size_is(cBytes)] byte *mszReaderNames;
     *     unsigned long dwState;
     *     unsigned long dwProtocol;
     *     byte pbAtr[32];
     *     [range(0,32)] unsigned long cbAtrLen;
     * } Status_Return;
     *
     * NDR:-
     *
     * Offset   Decription
     * 0        ReturnCode
     * 4        cBytes
     * 8        Referent Identifier for mszReaderNames;
     * 12       dwState
     * 16       dwProtocol
     * 20       pbAtr
     * 52       cbAtrLen
     * 56       length of multistring in bytes (same as cBytes)
     * 60       Multistring data
     */
    struct stream *out_s;
    int bytes;
    int dwReaderLen;
    int dwState;
    int dwProtocol;
    int dwAtrLen;
    char attr[32];
    char lreader_name[100];
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;
    struct pcsc_status *pcscStatus;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_status_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);

    pcscStatus = (struct pcsc_status *) user_data;
    if (pcscStatus == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_status_return: pcscStatus is nil");
        return 1;
    }

    uds_client_id = pcscStatus->uds_client_id;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_status_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        g_free(pcscStatus);
        return 1;
    }
    g_free(pcscStatus);
    con = uds_client->con;
    dwReaderLen = 0;
    dwState = 0;
    dwProtocol = 0;
    dwAtrLen = 0;
    lreader_name[0] = 0;
    if (status == 0)
    {
        in_uint8s(in_s, 16); // Skip [C706] PDU Header
        in_uint8s(in_s, 4);  // ReturnCode
        in_uint32_le(in_s, dwReaderLen);
        in_uint8s(in_s, 4); // Referent Identifier
        in_uint32_le(in_s, dwState);
        dwState = g_ms2pc[dwState % 6];
        in_uint32_le(in_s, dwProtocol);
        in_uint8a(in_s, attr, 32);
        in_uint32_le(in_s, dwAtrLen);

        // Length of multistring and multistring data
        if (dwReaderLen <= 0)
        {
            lreader_name[0] = '\0';
        }
        else
        {
            in_uint8s(in_s, 4);  // Skip length of msz in bytes

            // TODO: why are we just returning the first name of the card?
            in_utf16_le_terminated_as_utf8(in_s, lreader_name,
                                           sizeof(lreader_name));
        }
    }
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_status_return: dwAtrLen %d dwReaderLen %d "
              "dwProtocol %d dwState %d name %s",
              dwAtrLen, dwReaderLen, dwProtocol, dwState, lreader_name);
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    dwReaderLen = g_strlen(lreader_name);
    out_uint32_le(out_s, dwReaderLen);
    out_uint8a(out_s, lreader_name, dwReaderLen);
    out_uint32_le(out_s, dwState);
    out_uint32_le(out_s, dwProtocol);
    out_uint32_le(out_s, dwAtrLen);
    out_uint8a(out_s, attr, dwAtrLen);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x0B); /* SCARD_STATUS 0x0B */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_get_status_change(struct trans *con, struct stream *in_s)
{
    int index;
    int hContext;
    int dwTimeout;
    int cReaders;
    READER_STATE *rsa;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hContext);
    in_uint32_le(in_s, dwTimeout);
    in_uint32_le(in_s, cReaders);
    if ((cReaders < 0) || (cReaders > 16))
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_get_status_change: bad cReaders %d", cReaders);
        return 1;
    }
    rsa = (READER_STATE *) g_malloc(sizeof(READER_STATE) * cReaders, 1);

    for (index = 0; index < cReaders; index++)
    {
        in_uint8a(in_s, rsa[index].reader_name, 100);
        LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change: reader_name %s",
                  rsa[index].reader_name);
        in_uint32_le(in_s, rsa[index].current_state);
        LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change: current_state %d",
                  rsa[index].current_state);
        in_uint32_le(in_s, rsa[index].event_state);
        LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change: event_state %d",
                  rsa[index].event_state);
        in_uint32_le(in_s, rsa[index].atr_len);
        LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change: atr_len %d",
                  rsa[index].atr_len);
        in_uint8a(in_s, rsa[index].atr, 36);
    }

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_get_status_change: hContext 0x%8.8x dwTimeout "
              "%d cReaders %d", hContext, dwTimeout, cReaders);

    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcontext = get_pcsc_context_by_app_context(uds_client, hContext);
    if (lcontext == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_get_status_change: "
            "get_pcsc_context_by_app_context failed");
        g_free(rsa);
        return 1;
    }
    scard_send_get_status_change(user_data,
                                 lcontext->context, lcontext->context_bytes,
                                 1, dwTimeout, cReaders, rsa);
    g_free(rsa);

    return 0;
}

/*****************************************************************************/
int
scard_function_get_status_change_return(void *user_data,
                                        struct stream *in_s,
                                        int len, int status)
{
    int bytes;
    int index;
    int cReaders;
    tui32 current_state;
    tui32 event_state;
    tui32 atr_len; /* number of bytes in atr[] */
    tui8 atr[36];
    struct stream *out_s;
    int uds_client_id;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_get_status_change_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_get_status_change_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;

    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    if (status != 0)
    {
        out_uint32_le(out_s, 0); /* cReaders */
        out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    }
    else
    {
        in_uint8s(in_s, 28);
        in_uint32_le(in_s, cReaders);
        LOG_DEVEL(LOG_LEVEL_DEBUG, "  cReaders %d", cReaders);
        out_uint32_le(out_s, cReaders);
        if (cReaders > 0)
        {
            for (index = 0; index < cReaders; index++)
            {
                in_uint32_le(in_s, current_state);
                out_uint32_le(out_s, current_state);
                in_uint32_le(in_s, event_state);
                out_uint32_le(out_s, event_state);
                in_uint32_le(in_s, atr_len);
                out_uint32_le(out_s, atr_len);
                in_uint8a(in_s, atr, 36);
                out_uint8a(out_s, atr, 36);
            }
        }
        out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    }

    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x0C); /* SCARD_ESTABLISH_CONTEXT 0x0C */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_process_cancel(struct trans *con, struct stream *in_s)
{
    int hContext;
    struct pcsc_uds_client *uds_client;
    void *user_data;
    struct pcsc_context *lcontext;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_cancel:");
    uds_client = (struct pcsc_uds_client *) (con->callback_data);
    in_uint32_le(in_s, hContext);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_cancel: hContext 0x%8.8x", hContext);
    user_data = (void *) (tintptr) (uds_client->uds_client_id);
    lcontext = get_pcsc_context_by_app_context(uds_client, hContext);
    if (lcontext == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_process_cancel: "
            "get_pcsc_context_by_app_context failed");
        return 1;
    }
    scard_send_cancel(user_data, lcontext->context, lcontext->context_bytes);
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_function_cancel_return(void *user_data,
                             struct stream *in_s,
                             int len, int status)
{
    int bytes;
    int uds_client_id;
    struct stream *out_s;
    struct pcsc_uds_client *uds_client;
    struct trans *con;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_function_cancel_return:");
    LOG_DEVEL(LOG_LEVEL_DEBUG, "  status 0x%8.8x", status);
    uds_client_id = (int) (tintptr) user_data;
    uds_client = (struct pcsc_uds_client *)
                 get_uds_client_by_id(uds_client_id);
    if (uds_client == 0)
    {
        LOG(LOG_LEVEL_ERROR, "scard_function_cancel_return: "
            "get_uds_client_by_id failed to find uds_client_id %d",
            uds_client_id);
        return 1;
    }
    con = uds_client->con;
    out_s = trans_get_out_s(con, 8192);
    if (out_s == NULL)
    {
        return 1;
    }
    s_push_layer(out_s, iso_hdr, 8);
    out_uint32_le(out_s, status); /* SCARD_S_SUCCESS status */
    s_mark_end(out_s);
    bytes = (int) (out_s->end - out_s->data);
    s_pop_layer(out_s, iso_hdr);
    out_uint32_le(out_s, bytes - 8);
    out_uint32_le(out_s, 0x0D); /* SCARD_CANCEL 0x0D */
    return trans_force_write(con);
}

/*****************************************************************************/
/* returns error */
int
scard_function_is_context_valid_return(void *user_data,
                                       struct stream *in_s,
                                       int len, int status)
{
    return 0;
}

/*****************************************************************************/
/* returns error */
int scard_function_reconnect_return(void *user_data,
                                    struct stream *in_s,
                                    int len, int status)
{
    return 0;
}

/*****************************************************************************/
/* returns error */
int
scard_process_msg(struct trans *con, struct stream *in_s, int command)
{
    int rv;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_process_msg: command 0x%4.4x", command);
    rv = 0;
    switch (command)
    {
        case 0x01: /* SCARD_ESTABLISH_CONTEXT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_ESTABLISH_CONTEXT");
            rv = scard_process_establish_context(con, in_s);
            break;
        case 0x02: /* SCARD_RELEASE_CONTEXT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_RELEASE_CONTEXT");
            rv = scard_process_release_context(con, in_s);
            break;

        case 0x03: /* SCARD_LIST_READERS */
            /* This is only called from xrdp_pcsc.c */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_LIST_READERS");
            rv = scard_process_list_readers(con, in_s);
            break;

        case 0x04: /* SCARD_CONNECT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_CONNECT");
            rv = scard_process_connect(con, in_s);
            break;

        case 0x05: /* SCARD_RECONNECT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_RECONNECT");
            break;

        case 0x06: /* SCARD_DISCONNECT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_DISCONNECT");
            rv = scard_process_disconnect(con, in_s);
            break;

        case 0x07: /* SCARD_BEGIN_TRANSACTION */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_BEGIN_TRANSACTION");
            rv = scard_process_begin_transaction(con, in_s);
            break;

        case 0x08: /* SCARD_END_TRANSACTION */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_END_TRANSACTION");
            rv = scard_process_end_transaction(con, in_s);
            break;

        case 0x09: /* SCARD_TRANSMIT */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_TRANSMIT");
            rv = scard_process_transmit(con, in_s);
            break;

        case 0x0A: /* SCARD_CONTROL */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_CONTROL");
            rv = scard_process_control(con, in_s);
            break;

        case 0x0B: /* SCARD_STATUS */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_STATUS");
            rv = scard_process_status(con, in_s);
            break;

        case 0x0C: /* SCARD_GET_STATUS_CHANGE */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_GET_STATUS_CHANGE");
            rv = scard_process_get_status_change(con, in_s);
            break;

        case 0x0D: /* SCARD_CANCEL */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_CANCEL");
            rv = scard_process_cancel(con, in_s);
            break;

        case 0x0E: /* SCARD_CANCEL_TRANSACTION */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_CANCEL_TRANSACTION");
            break;

        case 0x0F: /* SCARD_GET_ATTRIB */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_GET_ATTRIB");
            break;

        case 0x10: /* SCARD_SET_ATTRIB */
            LOG_DEVEL(LOG_LEVEL_INFO, "scard_process_msg: SCARD_SET_ATTRIB");
            break;

        default:
            LOG_DEVEL(LOG_LEVEL_WARNING, "scard_process_msg: unknown mtype 0x%4.4x", command);
            rv = 1;
            break;
    }
    return rv;
}

/*****************************************************************************/
/* returns error */
int
my_pcsc_trans_data_in(struct trans *trans)
{
    struct stream *s;
    int size;
    int command;
    int error;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "my_pcsc_trans_data_in:");
    if (trans == 0)
    {
        return 0;
    }
    s = trans_get_in_s(trans);
    in_uint32_le(s, size);
    in_uint32_le(s, command);
    LOG_DEVEL(LOG_LEVEL_DEBUG, "my_pcsc_trans_data_in: size %d command %d", size, command);
    error = trans_force_read(trans, size);
    if (error == 0)
    {
        error = scard_process_msg(trans, s, command);
    }
    return error;
}

/*****************************************************************************/
/* got a new connection from libpcsclite */
int
my_pcsc_trans_conn_in(struct trans *trans, struct trans *new_trans)
{
    struct pcsc_uds_client *uds_client;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "my_pcsc_trans_conn_in:");

    if (trans == 0)
    {
        return 1;
    }

    if (trans != g_lis)
    {
        return 1;
    }

    if (new_trans == 0)
    {
        return 1;
    }

    uds_client = create_uds_client(new_trans);
    if (uds_client == 0)
    {
        return 1;
    }
    uds_client->con->trans_data_in = my_pcsc_trans_data_in;
    uds_client->con->header_size = 8;

    if (g_uds_clients == 0)
    {
        g_uds_clients = list_create();
    }
    list_add_item(g_uds_clients, (tbus)uds_client);

    return 0;
}

/*****************************************************************************/
int
scard_pcsc_init(void)
{
    char *home;
    int disp;
    int error;

    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_pcsc_init:");
    if (g_lis == 0)
    {
        g_lis = trans_create(2, 8192, 8192);
        // TODO: See #2501. Use needs a way to move PCSCLITE_CSOCK_NAME
        // to a location not under $HOME.
        home = g_getenv("HOME");
        disp = g_display_num;
        g_snprintf(g_pcsclite_ipc_dir, 255, "%s/.pcsc%d", home, disp);

        if (g_directory_exist(g_pcsclite_ipc_dir))
        {
            if (!g_remove_dir(g_pcsclite_ipc_dir))
            {
                LOG_DEVEL(LOG_LEVEL_WARNING, "scard_pcsc_init: g_remove_dir failed");
            }
        }
        if (!g_directory_exist(g_pcsclite_ipc_dir))
        {
            if (!g_create_dir(g_pcsclite_ipc_dir))
            {
                if (!g_directory_exist(g_pcsclite_ipc_dir))
                {
                    LOG_DEVEL(LOG_LEVEL_WARNING, "scard_pcsc_init: g_create_dir failed");
                }
            }
        }
        /* Only the current user should be able to access the remote
         * smartcard */
        g_chmod_hex(g_pcsclite_ipc_dir, 0x700);
        g_snprintf(g_pcsclite_ipc_file, 255, "%s/pcscd.comm", g_pcsclite_ipc_dir);
        g_lis->trans_conn_in = my_pcsc_trans_conn_in;
        error = trans_listen(g_lis, g_pcsclite_ipc_file);
        if (error != 0)
        {
            LOG(LOG_LEVEL_ERROR, "scard_pcsc_init: trans_listen failed for port %s",
                g_pcsclite_ipc_file);
            return 1;
        }
    }
    return 0;
}

/*****************************************************************************/
int
scard_pcsc_deinit(void)
{
    LOG_DEVEL(LOG_LEVEL_DEBUG, "scard_pcsc_deinit:");

    if (g_lis != 0)
    {
        trans_delete(g_lis);
        g_lis = 0;
    }

    if (g_pcsclite_ipc_dir[0] != 0)
    {
        g_file_delete(g_pcsclite_ipc_file);
        if (!g_remove_dir(g_pcsclite_ipc_dir))
        {
            LOG_DEVEL(LOG_LEVEL_WARNING, "scard_pcsc_deinit: g_remove_dir failed");
        }
        g_pcsclite_ipc_dir[0] = 0;
    }

    return 0;
}

#else

int
scard_pcsc_get_wait_objs(tbus *objs, int *count, int *timeout)
{
    return 0;
}
int
scard_pcsc_check_wait_objs(void)
{
    return 0;
}
int
scard_pcsc_init(void)
{
    return 0;
}
int
scard_pcsc_deinit(void)
{
    return 0;
}

#endif
