/**
 * xrdp: A Remote Desktop Protocol server.
 *
 * Copyright (C) Jay Sorg 2009-2013
 * Copyright (C) Laxmikant Rashinkar 2009-2012
 *
 * 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.
 */

#include "arch.h"
#include "os_calls.h"
#include "thread_calls.h"
#include "trans.h"
#include "chansrv.h"
#include "defines.h"
#include "sound.h"
#include "clipboard.h"
#include "devredir.h"
#include "list.h"
#include "file.h"
#include "file_loc.h"
#include "log.h"
#include "rail.h"
#include "xcommon.h"
#include "chansrv_fuse.h"
#include "drdynvc.h"

static struct trans *g_lis_trans = 0;
static struct trans *g_con_trans = 0;
static struct trans *g_api_lis_trans = 0;
static struct list *g_api_con_trans_list = 0; /* list of apps using api functions */
static struct chan_item g_chan_items[32];
static int g_num_chan_items = 0;
static int g_cliprdr_index = -1;
static int g_rdpsnd_index = -1;
static int g_rdpdr_index = -1;
static int g_rail_index = -1;
static int g_drdynvc_index = -1;

/* state info for dynamic virtual channels */
static struct xrdp_api_data *g_dvc_channels[MAX_DVC_CHANNELS];

static tbus g_term_event = 0;
static tbus g_thread_done_event = 0;

static int g_use_unix_socket = 0;

static const unsigned char g_xrdpapi_magic[12] =
{ 0x78, 0x32, 0x10, 0x67, 0x00, 0x92, 0x30, 0x56, 0xff, 0xd8, 0xa9, 0x1f };

int g_display_num = 0;
int g_cliprdr_chan_id = -1; /* cliprdr */
int g_rdpsnd_chan_id = -1;  /* rdpsnd  */
int g_rdpdr_chan_id = -1;   /* rdpdr   */
int g_rail_chan_id = -1;    /* rail    */
int g_drdynvc_chan_id = -1; /* drdynvc */

char *g_exec_name;
tbus g_exec_event;
tbus g_exec_mutex;
tbus g_exec_sem;
int g_exec_pid = 0;

#define ARRAYSIZE(x) (sizeof(x)/sizeof(*(x)))

/* each time we create a DVC we need a unique DVC channel id */
/* this variable gets bumped up once per DVC we create       */
tui32 g_dvc_chan_id = 100;

struct timeout_obj
{
    tui32 mstime;
    void *data;
    void (*callback)(void *data);
    struct timeout_obj *next;
};

static struct timeout_obj *g_timeout_head = 0;
static struct timeout_obj *g_timeout_tail = 0;

/*****************************************************************************/
int APP_CC
add_timeout(int msoffset, void (*callback)(void *data), void *data)
{
    struct timeout_obj *tobj;
    tui32 now;

    LOG(10, ("add_timeout:"));
    now = g_time3();
    tobj = g_new0(struct timeout_obj, 1);
    tobj->mstime = now + msoffset;
    tobj->callback = callback;
    tobj->data = data;
    if (g_timeout_tail == 0)
    {
        g_timeout_head = tobj;
        g_timeout_tail = tobj;
    }
    else
    {
        g_timeout_tail->next = tobj;
        g_timeout_tail = tobj;
    }
    return 0;
}

/*****************************************************************************/
static int APP_CC
get_timeout(int *timeout)
{
    struct timeout_obj *tobj;
    tui32 now;
    int ltimeout;

    LOG(10, ("get_timeout:"));
    ltimeout = *timeout;
    if (ltimeout < 1)
    {
        ltimeout = 0;
    }
    tobj = g_timeout_head;
    if (tobj != 0)
    {
        now = g_time3();
        while (tobj != 0)
        {
            LOG(10, ("  now %u tobj->mstime %u", now, tobj->mstime));
            if (now < tobj->mstime)
            {
                ltimeout = tobj->mstime - now;
            }
            tobj = tobj->next;
        }
    }
    if (ltimeout > 0)
    {
        LOG(10, ("  ltimeout %d", ltimeout));
        if (*timeout < 1)
        {
            *timeout = ltimeout;
        }
        else
        {
            if (*timeout > ltimeout)
            {
                *timeout = ltimeout;
            }
        }
    }
    return 0;
}

/*****************************************************************************/
static int APP_CC
check_timeout(void)
{
    struct timeout_obj *tobj;
    struct timeout_obj *last_tobj;
    struct timeout_obj *temp_tobj;
    int count;
    tui32 now;

    LOG(10, ("check_timeout:"));
    count = 0;
    tobj = g_timeout_head;
    if (tobj != 0)
    {
        last_tobj = 0;
        while (tobj != 0)
        {
            count++;
            now = g_time3();
            if (now >= tobj->mstime)
            {
                tobj->callback(tobj->data);
                if (last_tobj == 0)
                {
                    g_timeout_head = tobj->next;
                    if (g_timeout_head == 0)
                    {
                        g_timeout_tail = 0;
                    }
                }
                else
                {
                    last_tobj->next = tobj->next;
                    if (g_timeout_tail == tobj)
                    {
                        g_timeout_tail = last_tobj;
                    }
                }
                temp_tobj = tobj;
                tobj = tobj->next;
                g_free(temp_tobj);
            }
            else
            {
                last_tobj = tobj;
                tobj = tobj->next;
            }
        }
    }
    LOG(10, ("  count %d", count));
    return 0;
}

/*****************************************************************************/
int DEFAULT_CC
g_is_term(void)
{
    return g_is_wait_obj_set(g_term_event);
}

/*****************************************************************************/
/* add data to chan_item, on its way to the client */
/* returns error */
static int APP_CC
add_data_to_chan_item(struct chan_item *chan_item, char *data, int size)
{
    struct stream *s;
    struct chan_out_data *cod;

    make_stream(s);
    init_stream(s, size);
    g_memcpy(s->data, data, size);
    s->end = s->data + size;
    cod = (struct chan_out_data *)g_malloc(sizeof(struct chan_out_data), 1);
    cod->s = s;

    if (chan_item->tail == 0)
    {
        chan_item->tail = cod;
        chan_item->head = cod;
    }
    else
    {
        chan_item->tail->next = cod;
        chan_item->tail = cod;
    }

    return 0;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
send_data_from_chan_item(struct chan_item *chan_item)
{
    struct stream *s;
    struct chan_out_data *cod;
    int bytes_left;
    int size;
    int chan_flags;
    int error;

    if (chan_item->head == 0)
    {
        return 0;
    }

    cod = chan_item->head;
    bytes_left = (int)(cod->s->end - cod->s->p);
    size = MIN(1600, bytes_left);
    chan_flags = 0;

    if (cod->s->p == cod->s->data)
    {
        chan_flags |= 1; /* first */
    }

    if (cod->s->p + size >= cod->s->end)
    {
        chan_flags |= 2; /* last */
    }

    s = trans_get_out_s(g_con_trans, 8192);
    out_uint32_le(s, 0); /* version */
    out_uint32_le(s, 8 + 8 + 2 + 2 + 2 + 4 + size); /* size */
    out_uint32_le(s, 8); /* msg id */
    out_uint32_le(s, 8 + 2 + 2 + 2 + 4 + size); /* size */
    out_uint16_le(s, chan_item->id);
    out_uint16_le(s, chan_flags);
    out_uint16_le(s, size);
    out_uint32_le(s, cod->s->size);
    out_uint8a(s, cod->s->p, size);
    s_mark_end(s);
    LOGM((LOG_LEVEL_DEBUG, "chansrv::send_data_from_chan_item: -- "
          "size %d chan_flags 0x%8.8x", size, chan_flags));

    error = trans_write_copy(g_con_trans);
    if (error != 0)
    {
        return 1;
    }

    cod->s->p += size;

    if (cod->s->p >= cod->s->end)
    {
        free_stream(cod->s);
        chan_item->head = chan_item->head->next;

        if (chan_item->head == 0)
        {
            chan_item->tail = 0;
        }

        g_free(cod);
    }

    return 0;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
check_chan_items(void)
{
    int index;

    for (index = 0; index < g_num_chan_items; index++)
    {
        if (g_chan_items[index].head != 0)
        {
            send_data_from_chan_item(g_chan_items + index);
        }
    }

    return 0;
}

/*****************************************************************************/
/* returns error */
int APP_CC
send_channel_data(int chan_id, char *data, int size)
{
    int index;

    //g_writeln("send_channel_data chan_id %d size %d", chan_id, size);

    LOGM((LOG_LEVEL_DEBUG, "chansrv::send_channel_data: size %d", size));

    if (chan_id == -1)
    {
        g_writeln("send_channel_data: error, chan_id is -1");
        return 1;
    }

    for (index = 0; index < g_num_chan_items; index++)
    {
        if (g_chan_items[index].id == chan_id)
        {
            add_data_to_chan_item(g_chan_items + index, data, size);
            check_chan_items();
            return 0;
        }
    }

    return 1;
}

/*****************************************************************************/
/* returns error */
int APP_CC
send_rail_drawing_orders(char* data, int size)
{
    LOGM((LOG_LEVEL_DEBUG, "chansrv::send_rail_drawing_orders: size %d", size));

    struct stream* s;
    int error;

    s = trans_get_out_s(g_con_trans, 8192);
    out_uint32_le(s, 0); /* version */
    out_uint32_le(s, 8 + 8 + size); /* size */
    out_uint32_le(s, 10); /* msg id */
    out_uint32_le(s, 8 + size); /* size */
    out_uint8a(s, data, size);
    s_mark_end(s);
    error = trans_force_write(g_con_trans);
    if (error != 0)
    {
        return 1;
    }
    return 0;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
send_init_response_message(void)
{
    struct stream *s = (struct stream *)NULL;

    LOGM((LOG_LEVEL_INFO, "send_init_response_message:"));
    s = trans_get_out_s(g_con_trans, 8192);

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

    out_uint32_le(s, 0); /* version */
    out_uint32_le(s, 8 + 8); /* size */
    out_uint32_le(s, 2); /* msg id */
    out_uint32_le(s, 8); /* size */
    s_mark_end(s);
    return trans_write_copy(g_con_trans);
}

/*****************************************************************************/
/* returns error */
static int APP_CC
send_channel_setup_response_message(void)
{
    struct stream *s = (struct stream *)NULL;

    LOGM((LOG_LEVEL_DEBUG, "send_channel_setup_response_message:"));
    s = trans_get_out_s(g_con_trans, 8192);

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

    out_uint32_le(s, 0); /* version */
    out_uint32_le(s, 8 + 8); /* size */
    out_uint32_le(s, 4); /* msg id */
    out_uint32_le(s, 8); /* size */
    s_mark_end(s);
    return trans_write_copy(g_con_trans);
}

/*****************************************************************************/
/* returns error */
static int APP_CC
send_channel_data_response_message(void)
{
    struct stream *s = (struct stream *)NULL;

    LOGM((LOG_LEVEL_DEBUG, "send_channel_data_response_message:"));
    s = trans_get_out_s(g_con_trans, 8192);

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

    out_uint32_le(s, 0); /* version */
    out_uint32_le(s, 8 + 8); /* size */
    out_uint32_le(s, 6); /* msg id */
    out_uint32_le(s, 8); /* size */
    s_mark_end(s);
    return trans_write_copy(g_con_trans);
}

/*****************************************************************************/
/* returns error */
static int APP_CC
process_message_init(struct stream *s)
{
    LOGM((LOG_LEVEL_DEBUG, "process_message_init:"));
    return send_init_response_message();
}

/*****************************************************************************/
/* returns error */
static int APP_CC
process_message_channel_setup(struct stream *s)
{
    int num_chans;
    int index;
    int rv;
    struct chan_item *ci;
    struct chan_out_data *cod;
    struct chan_out_data *old_cod;

    g_num_chan_items = 0;
    g_cliprdr_index = -1;
    g_rdpsnd_index = -1;
    g_rdpdr_index = -1;
    g_rail_index = -1;
    g_cliprdr_chan_id = -1;
    g_rdpsnd_chan_id = -1;
    g_rdpdr_chan_id = -1;
    g_rail_chan_id = -1;
    g_drdynvc_chan_id = -1;
    LOGM((LOG_LEVEL_DEBUG, "process_message_channel_setup:"));
    in_uint16_le(s, num_chans);
    LOGM((LOG_LEVEL_DEBUG, "process_message_channel_setup: num_chans %d",
          num_chans));

    for (index = 0; index < num_chans; index++)
    {
        ci = &(g_chan_items[g_num_chan_items]);
        g_memset(ci->name, 0, sizeof(ci->name));
        in_uint8a(s, ci->name, 8);
        in_uint16_le(s, ci->id);
        /* there might be leftover data from last session after reconnecting
           so free it */
        if (ci->head != 0)
        {
            cod = ci->head;
            while (1)
            {
                free_stream(cod->s);
                old_cod = cod;
                cod = cod->next;
                g_free(old_cod);
                if (ci->tail == old_cod)
                {
                    break;
                }
            }
        }
        ci->head = 0;
        ci->tail = 0;
        in_uint16_le(s, ci->flags);
        LOGM((LOG_LEVEL_DEBUG, "process_message_channel_setup: chan name '%s' "
              "id %d flags %8.8x", ci->name, ci->id, ci->flags));

        g_writeln("process_message_channel_setup: chan name '%s' "
                  "id %d flags %8.8x", ci->name, ci->id, ci->flags);

        if (g_strcasecmp(ci->name, "cliprdr") == 0)
        {
            g_cliprdr_index = g_num_chan_items;
            g_cliprdr_chan_id = ci->id;
        }
        else if (g_strcasecmp(ci->name, "rdpsnd") == 0)
        {
            g_rdpsnd_index = g_num_chan_items;
            g_rdpsnd_chan_id = ci->id;
        }
        else if (g_strcasecmp(ci->name, "rdpdr") == 0)
        {
            g_rdpdr_index = g_num_chan_items;
            g_rdpdr_chan_id = ci->id;
        }
        /* disabled for now */
        else if (g_strcasecmp(ci->name, "rail") == 0)
        {
            g_rail_index = g_num_chan_items;
            g_rail_chan_id = ci->id;
        }
        else if (g_strcasecmp(ci->name, "drdynvc") == 0)
        {
            g_drdynvc_index = g_num_chan_items; // LK_TODO use  this
            g_drdynvc_chan_id = ci->id;         // LK_TODO use this
        }
        else
        {
            LOG(10, ("other %s", ci->name));
        }

        g_num_chan_items++;
    }

    rv = send_channel_setup_response_message();

    if (g_cliprdr_index >= 0)
    {
        clipboard_init();
        xfuse_init();
    }

    if (g_rdpsnd_index >= 0)
    {
        sound_init();
    }

    if (g_rdpdr_index >= 0)
    {
        dev_redir_init();
        xfuse_init();
    }

    if (g_rail_index >= 0)
    {
        rail_init();
    }

    if (g_drdynvc_index >= 0)
    {
        g_memset(&g_dvc_channels[0], 0, sizeof(g_dvc_channels));
        drdynvc_init();
    }

    return rv;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
process_message_channel_data(struct stream *s)
{
    int chan_id = 0;
    int chan_flags = 0;
    int rv = 0;
    int length = 0;
    int total_length = 0;
    int index;
    struct stream *ls;
    struct trans *ltran;
    struct xrdp_api_data *api_data;

    in_uint16_le(s, chan_id);
    in_uint16_le(s, chan_flags);
    in_uint16_le(s, length);
    in_uint32_le(s, total_length);
    LOGM((LOG_LEVEL_DEBUG, "process_message_channel_data: chan_id %d "
          "chan_flags %d", chan_id, chan_flags));
    LOG(10, ("process_message_channel_data"));
    rv = send_channel_data_response_message();

    if (rv == 0)
    {
        if (chan_id == g_cliprdr_chan_id)
        {
            rv = clipboard_data_in(s, chan_id, chan_flags, length, total_length);
        }
        else if (chan_id == g_rdpsnd_chan_id)
        {
            rv = sound_data_in(s, chan_id, chan_flags, length, total_length);
        }
        else if (chan_id == g_rdpdr_chan_id)
        {
            rv = dev_redir_data_in(s, chan_id, chan_flags, length, total_length);
        }
        else if (chan_id == g_rail_chan_id)
        {
            rv = rail_data_in(s, chan_id, chan_flags, length, total_length);
        }
        else if (chan_id == g_drdynvc_chan_id)
        {
            rv = drdynvc_data_in(s, chan_id, chan_flags, length, total_length);
        }
        else if (g_api_con_trans_list != 0)
        {
            for (index = 0; index < g_api_con_trans_list->count; index++)
            {
                ltran = (struct trans *) list_get_item(g_api_con_trans_list, index);
                if (ltran != 0)
                {
                    api_data = (struct xrdp_api_data *) (ltran->callback_data);
                    if (api_data != 0)
                    {
                        if (api_data->chan_id == chan_id)
                        {
                            ls = ltran->out_s;
                            if (chan_flags & 1) /* first */
                            {
                                init_stream(ls, total_length);
                            }
                            out_uint8a(ls, s->p, length);
                            if (chan_flags & 2) /* last */
                            {
                                s_mark_end(ls);
                                rv = trans_force_write(ltran);
                            }
                            break;
                        }
                    }
                }
            }
        }
    }
    return rv;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
process_message_channel_data_response(struct stream *s)
{
    LOG(10, ("process_message_channel_data_response:"));
    check_chan_items();
    return 0;
}

/*****************************************************************************/
/* returns error */
static int APP_CC
process_message(void)
{
    struct stream *s = (struct stream *)NULL;
    int size = 0;
    int id = 0;
    int rv = 0;
    char *next_msg = (char *)NULL;

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

    s = trans_get_in_s(g_con_trans);

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

    rv = 0;

    while (s_check_rem(s, 8))
    {
        next_msg = s->p;
        in_uint32_le(s, id);
        in_uint32_le(s, size);
        next_msg += size;

        switch (id)
        {
            case 1: /* init */
                rv = process_message_init(s);
                break;
            case 3: /* channel setup */
                rv = process_message_channel_setup(s);
                break;
            case 5: /* channel data */
                rv = process_message_channel_data(s);
                break;
            case 7: /* channel data response */
                rv = process_message_channel_data_response(s);
                break;
            default:
                LOGM((LOG_LEVEL_ERROR, "process_message: unknown msg %d", id));
                break;
        }

        if (rv != 0)
        {
            break;
        }

        s->p = next_msg;
    }

    return rv;
}

/*****************************************************************************/
/* returns error */
int DEFAULT_CC
my_trans_data_in(struct trans *trans)
{
    struct stream *s = (struct stream *)NULL;
    int size = 0;
    int error = 0;

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

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

    LOGM((LOG_LEVEL_DEBUG, "my_trans_data_in:"));
    s = trans_get_in_s(trans);
    in_uint8s(s, 4); /* id */
    in_uint32_le(s, size);
    error = trans_force_read(trans, size - 8);

    if (error == 0)
    {
        /* here, the entire message block is read in, process it */
        error = process_message();
    }

    return error;
}

/*
 * called when WTSVirtualChannelWrite() is invoked in xrdpapi.c
 *
 ******************************************************************************/
int DEFAULT_CC
my_api_trans_data_in(struct trans *trans)
{
    struct stream        *s;
    int                   bytes_read;
    int                   i32;
    struct xrdp_api_data *ad;

    //g_writeln("my_api_trans_data_in:");

    LOG(10, ("my_api_trans_data_in:"));

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

    if (g_api_con_trans_list != 0)
    {
        if (list_index_of(g_api_con_trans_list, (tintptr) trans) == -1)
        {
            return 1;
        }
    }

    LOGM((LOG_LEVEL_DEBUG, "my_api_trans_data_in:"));

    s = trans_get_in_s(trans);
    bytes_read = g_tcp_recv(trans->sck, s->data, 16, 0);
    if (bytes_read == 16)
    {
        if (g_memcmp(s->data, g_xrdpapi_magic, 12) == 0)
        {
            in_uint8s(s, 12);
            in_uint32_le(s, bytes_read);
            init_stream(s, bytes_read);
            if (trans_force_read(trans, bytes_read))
                log_message(LOG_LEVEL_ERROR, "chansrv.c: error reading from transport");
        }
        else if (g_tcp_select(trans->sck, 0) & 1)
        {
            i32 = bytes_read;
            bytes_read = g_tcp_recv(trans->sck, s->data + bytes_read,
                                    8192 * 4 - bytes_read, 0);
            if (bytes_read > 0)
            {
                bytes_read += i32;
            }
        }
    }

    //g_writeln("bytes_read %d", bytes_read);

    if (bytes_read > 0)
    {
        LOG(10, ("my_api_trans_data_in: got data %d", bytes_read));
        ad = (struct xrdp_api_data *) trans->callback_data;

        if (ad->dvc_chan_id < 0)
        {
            /* writing data to a static virtual channel */
            if (send_channel_data(ad->chan_id, s->data, bytes_read) != 0)
            {
                LOG(0, ("my_api_trans_data_in: send_channel_data failed"));
            }
        }
        else
        {
            /* writing data to a dynamic virtual channel */
            drdynvc_write_data(ad->dvc_chan_id, s->data, bytes_read);
        }
    }
    else
    {
        ad = (struct xrdp_api_data *) (trans->callback_data);
        if ((ad != NULL) && (ad->dvc_chan_id > 0))
        {
            /* WTSVirtualChannelClose() was invoked, or connection dropped */
            LOG(10, ("my_api_trans_data_in: g_tcp_recv failed or disconnected for DVC"));
            ad->transp = NULL;
            ad->is_connected = 0;
            remove_struct_with_chan_id(ad->dvc_chan_id);
        }
        else
        {
            LOG(10, ("my_api_trans_data_in: g_tcp_recv failed or disconnected for SVC"));
        }
        return 1;
    }

    return 0;
}

/*****************************************************************************/
int DEFAULT_CC
my_trans_conn_in(struct trans *trans, struct trans *new_trans)
{
    if (trans == 0)
    {
        return 1;
    }

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

    if (g_con_trans != 0) /* if already set, error */
    {
        return 1;
    }

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

    LOGM((LOG_LEVEL_DEBUG, "my_trans_conn_in:"));
    g_con_trans = new_trans;
    g_con_trans->trans_data_in = my_trans_data_in;
    g_con_trans->header_size = 8;
    /* stop listening */
    trans_delete(g_lis_trans);
    g_lis_trans = 0;
    return 0;
}

/*
 * called when WTSVirtualChannelOpenEx is invoked in xrdpapi.c
 *
 ******************************************************************************/
int DEFAULT_CC
my_api_trans_conn_in(struct trans *trans, struct trans *new_trans)
{
    struct xrdp_api_data *ad;
    struct stream        *s;
    int                   error;
    int                   index;
    char                  chan_pri;

    if ((trans == 0) || (trans != g_api_lis_trans) || (new_trans == 0))
    {
        return 1;
    }

    LOGM((LOG_LEVEL_DEBUG, "my_api_trans_conn_in:"));
    LOG(10, ("my_api_trans_conn_in: got incoming"));

    s = trans_get_in_s(new_trans);
    s->end = s->data;

    error = trans_force_read(new_trans, 64);

    if (error != 0)
    {
        LOG(0, ("my_api_trans_conn_in: trans_force_read failed"));
        trans_delete(new_trans);
        return 1;
    }

    s->end = s->data;

    ad = (struct xrdp_api_data *) g_malloc(sizeof(struct xrdp_api_data), 1);
    g_memcpy(ad->header, s->data, 64);

    ad->flags = GGET_UINT32(ad->header, 16);
    ad->chan_id = -1;
    ad->dvc_chan_id = -1;

    if (ad->flags > 0)
    {
        /* opening a dynamic virtual channel */

        if ((index = find_empty_slot_in_dvc_channels()) < 0)
        {
            /* exceeded MAX_DVC_CHANNELS */
            LOG(0, ("my_api_trans_conn_in: MAX_DVC_CHANNELS reached; giving up!"))
            g_free(ad);
            trans_delete(new_trans);
            return 1;
        }

        g_dvc_channels[index] = ad;
        chan_pri = 4 - ad->flags;
        ad->dvc_chan_id = g_dvc_chan_id++;
        ad->is_connected = 0;
        ad->transp = new_trans;
        drdynvc_send_open_channel_request(chan_pri, ad->dvc_chan_id, ad->header);
    }
    else
    {
        /* opening a static virtual channel */

        for (index = 0; index < g_num_chan_items; index++)
        {
            LOG(10, ("my_api_trans_conn_in:  %s %s", ad->header,
                    g_chan_items[index].name));

            if (g_strcasecmp(ad->header, g_chan_items[index].name) == 0)
            {
                LOG(10, ("my_api_trans_conn_in: found it at %d", index));
                ad->chan_id = g_chan_items[index].id;
                break;
            }
        }
        if (index == g_num_chan_items)
        {
            g_writeln("did not find SVC named %s", ad->header);
        }
    }

    new_trans->callback_data = ad;

    if (g_api_con_trans_list == 0)
    {
        g_api_con_trans_list = list_create();
    }
    new_trans->trans_data_in = my_api_trans_data_in;
    new_trans->header_size = 0;
    list_add_item(g_api_con_trans_list, (tintptr) new_trans);

    return 0;
}

/*****************************************************************************/
static int APP_CC
setup_listen(void)
{
    char port[256];
    int error = 0;

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

    if (g_use_unix_socket)
    {
        g_lis_trans = trans_create(TRANS_MODE_UNIX, 8192, 8192);
        g_lis_trans->is_term = g_is_term;
        g_snprintf(port, 255, XRDP_CHANSRV_STR, g_display_num);
    }
    else
    {
        g_lis_trans = trans_create(TRANS_MODE_TCP, 8192, 8192);
        g_lis_trans->is_term = g_is_term;
        g_snprintf(port, 255, "%d", 7200 + g_display_num);
    }

    g_lis_trans->trans_conn_in = my_trans_conn_in;
    error = trans_listen(g_lis_trans, port);

    if (error != 0)
    {
        LOGM((LOG_LEVEL_ERROR, "setup_listen: trans_listen failed for port %s",
              port));
        return 1;
    }

    return 0;
}

/*****************************************************************************/
static int APP_CC
setup_api_listen(void)
{
    char port[256];
    int error = 0;

    g_api_lis_trans = trans_create(TRANS_MODE_UNIX, 8192 * 4, 8192 * 4);
    g_api_lis_trans->is_term = g_is_term;
    g_snprintf(port, 255, CHANSRV_API_STR, g_display_num);
    g_api_lis_trans->trans_conn_in = my_api_trans_conn_in;
    error = trans_listen(g_api_lis_trans, port);

    if (error != 0)
    {
        LOGM((LOG_LEVEL_ERROR, "setup_api_listen: trans_listen failed for port %s",
              port));
        return 1;
    }

    return 0;
}

/*****************************************************************************/
THREAD_RV THREAD_CC
channel_thread_loop(void *in_val)
{
    tbus objs[32];
    tbus wobjs[32];
    int num_objs;
    int num_wobjs;
    int timeout;
    int error;
    int index;
    THREAD_RV rv;
    struct trans *ltran;

    LOGM((LOG_LEVEL_INFO, "channel_thread_loop: thread start"));
    rv = 0;
    setup_api_listen();
    error = setup_listen();

    if (error == 0)
    {
        timeout = -1;
        num_objs = 0;
        num_wobjs = 0;
        objs[num_objs] = g_term_event;
        num_objs++;
        trans_get_wait_objs(g_lis_trans, objs, &num_objs);
        trans_get_wait_objs(g_api_lis_trans, objs, &num_objs);

        while (g_obj_wait(objs, num_objs, wobjs, num_wobjs, timeout) == 0)
        {
            check_timeout();
            if (g_is_wait_obj_set(g_term_event))
            {
                LOGM((LOG_LEVEL_INFO, "channel_thread_loop: g_term_event set"));
                clipboard_deinit();
                sound_deinit();
                dev_redir_deinit();
                rail_deinit();
                break;
            }

            if (g_lis_trans != 0)
            {
                if (trans_check_wait_objs(g_lis_trans) != 0)
                {
                    LOGM((LOG_LEVEL_INFO, "channel_thread_loop: "
                          "trans_check_wait_objs error"));
                }
            }

            if (g_con_trans != 0)
            {
                if (trans_check_wait_objs(g_con_trans) != 0)
                {
                    LOGM((LOG_LEVEL_INFO, "channel_thread_loop: "
                          "trans_check_wait_objs error resetting"));
                    clipboard_deinit();
                    sound_deinit();
                    dev_redir_deinit();
                    rail_deinit();
                    /* delete g_con_trans */
                    trans_delete(g_con_trans);
                    g_con_trans = 0;
                    /* create new listener */
                    error = setup_listen();

                    if (error != 0)
                    {
                        break;
                    }
                }
            }

            if (g_api_lis_trans != 0)
            {
                if (trans_check_wait_objs(g_api_lis_trans) != 0)
                {
                    LOG(0, ("channel_thread_loop: trans_check_wait_objs failed"));
                }
            }

            if (g_api_con_trans_list != 0)
            {
                for (index = g_api_con_trans_list->count - 1; index >= 0; index--)
                {
                    ltran = (struct trans *) list_get_item(g_api_con_trans_list, index);
                    if (ltran != 0)
                    {
                        if (trans_check_wait_objs(ltran) != 0)
                        {
                            list_remove_item(g_api_con_trans_list, index);
                            g_free(ltran->callback_data);
                            trans_delete(ltran);
                        }
                    }
                }
            }

            xcommon_check_wait_objs();
            sound_check_wait_objs();
            dev_redir_check_wait_objs();
            xfuse_check_wait_objs();
            timeout = -1;
            num_objs = 0;
            num_wobjs = 0;
            objs[num_objs] = g_term_event;
            num_objs++;
            trans_get_wait_objs(g_lis_trans, objs, &num_objs);
            trans_get_wait_objs_rw(g_con_trans, objs, &num_objs,
                                   wobjs, &num_wobjs, &timeout);
            trans_get_wait_objs(g_api_lis_trans, objs, &num_objs);

            if (g_api_con_trans_list != 0)
            {
                for (index = g_api_con_trans_list->count - 1; index >= 0; index--)
                {
                    ltran = (struct trans *) list_get_item(g_api_con_trans_list, index);
                    if (ltran != 0)
                    {
                        trans_get_wait_objs(ltran, objs, &num_objs);
                    }
                }
            }

            xcommon_get_wait_objs(objs, &num_objs, &timeout);
            sound_get_wait_objs(objs, &num_objs, &timeout);
            dev_redir_get_wait_objs(objs, &num_objs, &timeout);
            xfuse_get_wait_objs(objs, &num_objs, &timeout);
            get_timeout(&timeout);
        } /* end while (g_obj_wait(objs, num_objs, 0, 0, timeout) == 0) */
    }

    trans_delete(g_lis_trans);
    g_lis_trans = 0;
    trans_delete(g_con_trans);
    g_con_trans = 0;
    trans_delete(g_api_lis_trans);
    g_api_lis_trans = 0;
    if (g_api_con_trans_list != 0)
    {
        for (index = g_api_con_trans_list->count - 1; index >= 0; index--)
        {
            ltran = (struct trans *) list_get_item(g_api_con_trans_list, index);
            if (ltran != 0)
            {
                list_remove_item(g_api_con_trans_list, index);
                g_free(ltran->callback_data);
                trans_delete(ltran);
            }
        }
        list_delete(g_api_con_trans_list);
    }
    LOGM((LOG_LEVEL_INFO, "channel_thread_loop: thread stop"));
    g_set_wait_obj(g_thread_done_event);
    return rv;
}

/*****************************************************************************/
void DEFAULT_CC
term_signal_handler(int sig)
{
    LOGM((LOG_LEVEL_INFO, "term_signal_handler: got signal %d", sig));
    g_set_wait_obj(g_term_event);
}

/*****************************************************************************/
void DEFAULT_CC
nil_signal_handler(int sig)
{
    LOGM((LOG_LEVEL_INFO, "nil_signal_handler: got signal %d", sig));
}

/*****************************************************************************/
void DEFAULT_CC
child_signal_handler(int sig)
{
    int pid;

    LOG(0, ("child_signal_handler:"));
    do
    {
        pid = g_waitchild();
        LOG(0, ("child_signal_handler: child pid %d", pid));
        if ((pid == g_exec_pid) && (pid > 0))
        {
            LOG(0, ("child_signal_handler: found pid %d", pid));
            //shutdownx();
        }
    }
    while (pid >= 0);
}

/*****************************************************************************/
void DEFAULT_CC
segfault_signal_handler(int sig)
{
    LOG(0, ("segfault_signal_handler: entered......."));
    xfuse_deinit();
    exit(0);
}

/*****************************************************************************/
static int APP_CC
get_display_num_from_display(char *display_text)
{
    int index;
    int mode;
    int host_index;
    int disp_index;
    int scre_index;
    char host[256];
    char disp[256];
    char scre[256];

    g_memset(host, 0, 256);
    g_memset(disp, 0, 256);
    g_memset(scre, 0, 256);

    index = 0;
    host_index = 0;
    disp_index = 0;
    scre_index = 0;
    mode = 0;

    while (display_text[index] != 0)
    {
        if (display_text[index] == ':')
        {
            mode = 1;
        }
        else if (display_text[index] == '.')
        {
            mode = 2;
        }
        else if (mode == 0)
        {
            host[host_index] = display_text[index];
            host_index++;
        }
        else if (mode == 1)
        {
            disp[disp_index] = display_text[index];
            disp_index++;
        }
        else if (mode == 2)
        {
            scre[scre_index] = display_text[index];
            scre_index++;
        }

        index++;
    }

    host[host_index] = 0;
    disp[disp_index] = 0;
    scre[scre_index] = 0;
    g_display_num = g_atoi(disp);
    return 0;
}

/*****************************************************************************/
int APP_CC
main_cleanup(void)
{
    g_delete_wait_obj(g_term_event);
    g_delete_wait_obj(g_thread_done_event);
    g_delete_wait_obj(g_exec_event);
    tc_mutex_delete(g_exec_mutex);
    g_deinit(); /* os_calls */
    return 0;
}

/*****************************************************************************/
static int APP_CC
read_ini(void)
{
    char filename[256];
    struct list *names;
    struct list *values;
    char *name;
    char *value;
    int index;

    g_memset(filename, 0, (sizeof(char) * 256));
    names = list_create();
    names->auto_free = 1;
    values = list_create();
    values->auto_free = 1;
    g_use_unix_socket = 0;
    g_snprintf(filename, 255, "%s/sesman.ini", XRDP_CFG_PATH);

    if (file_by_name_read_section(filename, "Globals", names, values) == 0)
    {
        for (index = 0; index < names->count; index++)
        {
            name = (char *)list_get_item(names, index);
            value = (char *)list_get_item(values, index);

            if (g_strcasecmp(name, "ListenAddress") == 0)
            {
                if (g_strcasecmp(value, "127.0.0.1") == 0)
                {
                    g_use_unix_socket = 1;
                }
            }
        }
    }

    list_delete(names);
    list_delete(values);
    return 0;
}

/*****************************************************************************/
static int APP_CC
get_log_path(char *path, int bytes)
{
    char* log_path;
    int rv;

    rv = 1;
    log_path = g_getenv("CHANSRV_LOG_PATH");
    if (log_path == 0)
    {
        log_path = g_getenv("XDG_DATA_HOME");
        if (log_path != 0)
        {
            g_snprintf(path, bytes, "%s%s", log_path, "/xrdp");
            if (g_directory_exist(path) || (g_mkdir(path) == 0))
            {
                rv = 0;
            }
        }
    }
    else
    {
        g_snprintf(path, bytes, "%s", log_path);
        if (g_directory_exist(path) || (g_mkdir(path) == 0))
        {
            rv = 0;
        }
    }
    if (rv != 0)
    {
        log_path = g_getenv("HOME");
        if (log_path != 0)
        {
            g_snprintf(path, bytes, "%s%s", log_path, "/.local");
            if (g_directory_exist(path) || (g_mkdir(path) == 0))
            {
                g_snprintf(path, bytes, "%s%s", log_path, "/.local/share");
                if (g_directory_exist(path) || (g_mkdir(path) == 0))
                {
                    g_snprintf(path, bytes, "%s%s", log_path, "/.local/share/xrdp");
                    if (g_directory_exist(path) || (g_mkdir(path) == 0))
                    {
                        rv = 0;
                    }
                }
            }
        }
    }
    return rv;
}

/*****************************************************************************/
static enum logLevels APP_CC
get_log_level(const char* level_str, enum logLevels default_level)
{
    static const char* levels[] = {
        "LOG_LEVEL_ALWAYS",
        "LOG_LEVEL_ERROR",
        "LOG_LEVEL_WARNING",
        "LOG_LEVEL_INFO",
        "LOG_LEVEL_DEBUG"
    };
    unsigned int i;

    if (level_str == NULL || level_str[0] == 0)
    {
        return default_level;
    }
    for (i = 0; i < ARRAYSIZE(levels); ++i)
    {
        if (g_strcasecmp(levels[i], level_str) == 0)
        {
            return (enum logLevels) i;
        }
    }
    return default_level;
}

/*****************************************************************************/
static int APP_CC
run_exec(void)
{
    int pid;

    LOG(10, ("run_exec:"));
    pid = g_fork();

    if (pid == 0)
    {
        trans_delete(g_con_trans);
        g_close_wait_obj(g_term_event);
        g_close_wait_obj(g_thread_done_event);
        g_close_wait_obj(g_exec_event);
        tc_mutex_delete(g_exec_mutex);
        tc_sem_delete(g_exec_sem);
        g_execlp3(g_exec_name, g_exec_name, 0);
        g_exit(0);
    }

    g_exec_pid = pid;
    tc_sem_inc(g_exec_sem);

    return 0;
}

/*****************************************************************************/
int DEFAULT_CC
main(int argc, char **argv)
{
    tbus waiters[4];
    int pid = 0;
    char text[256];
    char log_path[256];
    char *display_text;
    char log_file[256];
    enum logReturns error;
    struct log_config logconfig;
    enum logLevels log_level;

    g_init("xrdp-chansrv"); /* os_calls */

    log_path[255] = 0;
    if (get_log_path(log_path, 255) != 0)
    {
        g_writeln("error reading CHANSRV_LOG_PATH and HOME environment variable");
        g_deinit();
        return 1;
    }

    read_ini();
    pid = g_getpid();

    log_level = get_log_level(g_getenv("CHANSRV_LOG_LEVEL"), LOG_LEVEL_ERROR);

    /* starting logging subsystem */
    g_memset(&logconfig, 0, sizeof(struct log_config));
    logconfig.program_name = "xrdp-chansrv";
    g_snprintf(log_file, 255, "%s/xrdp-chansrv.log", log_path);
    g_writeln("chansrv::main: using log file [%s]", log_file);

    if (g_file_exist(log_file))
    {
        g_file_delete(log_file);
    }

    logconfig.log_file = log_file;
    logconfig.fd = -1;
    logconfig.log_level = log_level;
    logconfig.enable_syslog = 0;
    logconfig.syslog_level = LOG_LEVEL_ALWAYS;
    error = log_start_from_param(&logconfig);

    if (error != LOG_STARTUP_OK)
    {
        switch (error)
        {
            case LOG_ERROR_MALLOC:
                g_writeln("error on malloc. cannot start logging. quitting.");
                break;
            case LOG_ERROR_FILE_OPEN:
                g_writeln("error opening log file [%s]. quitting.",
                          getLogFile(text, 255));
                break;
            default:
                g_writeln("log_start error");
                break;
        }

        g_deinit();
        return 1;
    }

    LOGM((LOG_LEVEL_ALWAYS, "main: app started pid %d(0x%8.8x)", pid, pid));
    /*  set up signal handler  */
    g_signal_terminate(term_signal_handler); /* SIGTERM */
    g_signal_user_interrupt(term_signal_handler); /* SIGINT */
    g_signal_pipe(nil_signal_handler); /* SIGPIPE */
    g_signal_child_stop(child_signal_handler); /* SIGCHLD */
    g_signal_segfault(segfault_signal_handler);

    display_text = g_getenv("DISPLAY");
    LOGM((LOG_LEVEL_INFO, "main: DISPLAY env var set to %s", display_text));

    if (display_text)
        get_display_num_from_display(display_text);

    if (g_display_num == 0)
    {
        LOGM((LOG_LEVEL_ERROR, "main: error, display is zero"));
        g_deinit();
        return 1;
    }

    LOGM((LOG_LEVEL_INFO, "main: using DISPLAY %d", g_display_num));
    g_snprintf(text, 255, "xrdp_chansrv_%8.8x_main_term", pid);
    g_term_event = g_create_wait_obj(text);
    g_snprintf(text, 255, "xrdp_chansrv_%8.8x_thread_done", pid);
    g_thread_done_event = g_create_wait_obj(text);
    g_snprintf(text, 255, "xrdp_chansrv_%8.8x_exec", pid);
    g_exec_event = g_create_wait_obj(text);
    g_exec_mutex = tc_mutex_create();
    g_exec_sem = tc_sem_create(0);
    tc_thread_create(channel_thread_loop, 0);

    while (g_term_event > 0 && !g_is_wait_obj_set(g_term_event))
    {
        waiters[0] = g_term_event;
        waiters[1] = g_exec_event;

        if (g_obj_wait(waiters, 2, 0, 0, 0) != 0)
        {
            LOGM((LOG_LEVEL_ERROR, "main: error, g_obj_wait failed"));
            break;
        }

        if (g_is_wait_obj_set(g_term_event))
        {
            break;
        }

        if (g_is_wait_obj_set(g_exec_event))
        {
            g_reset_wait_obj(g_exec_event);
            run_exec();
        }
    }

    while (g_thread_done_event > 0 && !g_is_wait_obj_set(g_thread_done_event))
    {
        /* wait for thread to exit */
        if (g_obj_wait(&g_thread_done_event, 1, 0, 0, 0) != 0)
        {
            LOGM((LOG_LEVEL_ERROR, "main: error, g_obj_wait failed"));
            break;
        }
    }

    /* cleanup */
    main_cleanup();
    LOGM((LOG_LEVEL_INFO, "main: app exiting pid %d(0x%8.8x)", pid, pid));
    g_deinit();
    return 0;
}

/*
 * return unused slot in dvc_channels[]
 *
 * @return unused slot index on success, -1 on failure
 ******************************************************************************/
int APP_CC
find_empty_slot_in_dvc_channels()
{
    int i;

    for (i = 0; i < MAX_DVC_CHANNELS; i++)
    {
        if (g_dvc_channels[i] == NULL)
        {
            return i;
        }
    }

    return -1;
}

/*
 * return struct xrdp_api_data that contains specified dvc_chan_id
 *
 * @param  dvc_chan_id  channel id to look for
 *
 * @return xrdp_api_data struct containing dvc_chan_id or NULL on failure
 ******************************************************************************/
struct xrdp_api_data *APP_CC
struct_from_dvc_chan_id(tui32 dvc_chan_id)
{
    int i;

    for (i = 0; i < MAX_DVC_CHANNELS; i++)
    {
        if (g_dvc_channels[i]->dvc_chan_id >= 0 &&
            (tui32) g_dvc_channels[i]->dvc_chan_id == dvc_chan_id)
        {
            return g_dvc_channels[i];
        }
    }

    return NULL;
}

int
remove_struct_with_chan_id(tui32 dvc_chan_id)
{
    int i;

    for (i = 0; i < MAX_DVC_CHANNELS; i++)
    {
        if (g_dvc_channels[i]->dvc_chan_id >= 0 &&
            (tui32) g_dvc_channels[i]->dvc_chan_id == dvc_chan_id)
        {
            g_dvc_channels[i] = NULL;
            return 0;
        }
    }
    return -1;
}
