/*
 * channels.c - SSH channel functions
 *
 * This file is part of the SSH Library
 *
 * Copyright (c) 2003-2013 by Aris Adamantiadis
 * Copyright (c) 2009-2013 by Andreas Schneider <asn@cryptomilk.org>
 *
 * The SSH Library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 *
 * The SSH Library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the SSH Library; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA.
 */

#include "config.h"

#include <limits.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <stdbool.h>
#include <string.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif /* HAVE_SYS_TIME_H */

#ifndef _WIN32
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include "libssh/priv.h"
#include "libssh/ssh2.h"
#include "libssh/buffer.h"
#include "libssh/packet.h"
#include "libssh/socket.h"
#include "libssh/channels.h"
#include "libssh/session.h"
#include "libssh/misc.h"
#include "libssh/messages.h"
#if WITH_SERVER
#include "libssh/server.h"
#endif

/*
 * All implementations MUST be able to process packets with an
 * uncompressed payload length of 32768 bytes or less and a total packet
 * size of 35000 bytes or less.
 */
#define CHANNEL_MAX_PACKET 32768

/*
 * WINDOW_DEFAULT matches the default OpenSSH session window size.
 * This controls how much data the peer can send before needing to receive
 * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window.
 */
#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET)

/**
 * @defgroup libssh_channel The SSH channel functions
 * @ingroup libssh
 *
 * Functions that manage a SSH channel.
 *
 * @{
 */

static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet);

/**
 * @brief Allocate a new channel.
 *
 * @param[in]  session  The ssh session to use.
 *
 * @return              A pointer to a newly allocated channel, NULL on error.
 *                      The channel needs to be freed with ssh_channel_free().
 *
 * @see ssh_channel_free()
 */
ssh_channel ssh_channel_new(ssh_session session)
{
    ssh_channel channel = NULL;

    if (session == NULL) {
        return NULL;
    }

    /* Check if we have an authenticated session */
    if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) {
        return NULL;
    }

    channel = calloc(1, sizeof(struct ssh_channel_struct));
    if (channel == NULL) {
        ssh_set_error_oom(session);
        return NULL;
    }

    channel->stdout_buffer = ssh_buffer_new();
    if (channel->stdout_buffer == NULL) {
        ssh_set_error_oom(session);
        SAFE_FREE(channel);
        return NULL;
    }

    channel->stderr_buffer = ssh_buffer_new();
    if (channel->stderr_buffer == NULL) {
        ssh_set_error_oom(session);
        SSH_BUFFER_FREE(channel->stdout_buffer);
        SAFE_FREE(channel);
        return NULL;
    }

    channel->session = session;
    channel->exit.code = (uint32_t)-1;
    channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND;

    if (session->channels == NULL) {
        session->channels = ssh_list_new();
        if (session->channels == NULL) {
            ssh_set_error_oom(session);
            SSH_BUFFER_FREE(channel->stdout_buffer);
            SSH_BUFFER_FREE(channel->stderr_buffer);
            SAFE_FREE(channel);
            return NULL;
        }
    }

    ssh_list_prepend(session->channels, channel);

    /* Set states explicitly */
    channel->state = SSH_CHANNEL_STATE_NOT_OPEN;
    channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;

    return channel;
}

/**
 * @internal
 *
 * @brief Create a new channel identifier.
 *
 * @param[in]  session  The SSH session to use.
 *
 * @return              The new channel identifier.
 */
uint32_t ssh_channel_new_id(ssh_session session)
{
    return ++(session->maxchannel);
}

/**
 * @internal
 *
 * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet.
 *
 * Constructs the channel object.
 */
SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf)
{
    uint32_t channelid = 0;
    ssh_channel channel = NULL;
    int rc;
    (void)type;
    (void)user;

    SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION");

    rc = ssh_buffer_unpack(packet, "d", &channelid);
    if (rc != SSH_OK)
        goto error;
    channel = ssh_channel_from_local(session, channelid);
    if (channel == NULL) {
        ssh_set_error(session,
                      SSH_FATAL,
                      "Unknown channel id %" PRIu32,
                      (uint32_t)channelid);
        /* TODO: Set error marking in channel object */

        return SSH_PACKET_USED;
    }

    rc = ssh_buffer_unpack(packet,
                           "ddd",
                           &channel->remote_channel,
                           &channel->remote_window,
                           &channel->remote_maxpacket);
    if (rc != SSH_OK)
        goto error;

    SSH_LOG(SSH_LOG_DEBUG,
            "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32
            ":%" PRIu32,
            channel->local_channel,
            channel->remote_channel);

    if (channel->state != SSH_CHANNEL_STATE_OPENING) {
        SSH_LOG(SSH_LOG_RARE,
                "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect "
                "channel state %d",
                channel->state);
        goto error;
    }

    SSH_LOG(SSH_LOG_DEBUG,
            "Remote window : %" PRIu32 ", maxpacket : %" PRIu32,
            channel->remote_window,
            channel->remote_maxpacket);

    channel->state = SSH_CHANNEL_STATE_OPEN;
    channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND;

    ssh_callbacks_execute_list(channel->callbacks,
                               ssh_channel_callbacks,
                               channel_open_response_function,
                               channel->session,
                               channel,
                               true /* is_success */);

    return SSH_PACKET_USED;

error:
    ssh_set_error(session, SSH_FATAL, "Invalid packet");
    return SSH_PACKET_USED;
}

/**
 * @internal
 *
 * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel.
 */
SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail)
{
    ssh_channel channel = NULL;
    char *error = NULL;
    uint32_t code;
    int rc;
    (void)user;
    (void)type;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_RARE, "Invalid channel in packet");
        return SSH_PACKET_USED;
    }

    rc = ssh_buffer_unpack(packet, "ds", &code, &error);
    if (rc != SSH_OK) {
        ssh_set_error(session, SSH_FATAL, "Invalid packet");
        return SSH_PACKET_USED;
    }

    if (channel->state != SSH_CHANNEL_STATE_OPENING) {
        SSH_LOG(SSH_LOG_RARE,
                "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel "
                "state %d",
                channel->state);
        SAFE_FREE(error);
        goto error;
    }

    ssh_set_error(session,
                  SSH_REQUEST_DENIED,
                  "Channel opening failure: channel %" PRIu32 " error (%" PRIu32
                  ") %s",
                  channel->local_channel,
                  code,
                  error);
    SAFE_FREE(error);
    channel->state = SSH_CHANNEL_STATE_OPEN_DENIED;

    ssh_callbacks_execute_list(channel->callbacks,
                               ssh_channel_callbacks,
                               channel_open_response_function,
                               channel->session,
                               channel,
                               false /* is_success */);

    return SSH_PACKET_USED;

error:
  ssh_set_error(session, SSH_FATAL, "Invalid packet");
  return SSH_PACKET_USED;
}

static int ssh_channel_open_termination(void *c)
{
  ssh_channel channel = (ssh_channel) c;
  if (channel->state != SSH_CHANNEL_STATE_OPENING ||
      channel->session->session_state == SSH_SESSION_STATE_ERROR)
    return 1;
  else
    return 0;
}

/**
 * @internal
 *
 * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and
 *        wait for the reply.
 *
 * @param[in]  channel  The current channel.
 *
 * @param[in]  type   A C string describing the kind of channel (e.g. "exec").
 *
 * @param[in]  window   The receiving window of the channel. The window is the
 *                      maximum size of data that can stay in buffers and
 *                      network.
 *
 * @param[in]  maxpacket The maximum packet size allowed (like MTU).
 *
 * @param[in]  payload   The buffer containing additional payload for the query.
 *
 * @return             `SSH_OK` if successful; `SSH_ERROR` otherwise.
 */
static int
channel_open(ssh_channel channel,
             const char *type,
             uint32_t window,
             uint32_t maxpacket,
             ssh_buffer payload)
{
    ssh_session session = channel->session;
    int err = SSH_ERROR;
    int rc;

    switch (channel->state) {
    case SSH_CHANNEL_STATE_NOT_OPEN:
        break;
    case SSH_CHANNEL_STATE_OPENING:
        goto pending;
    case SSH_CHANNEL_STATE_OPEN:
    case SSH_CHANNEL_STATE_CLOSED:
    case SSH_CHANNEL_STATE_OPEN_DENIED:
        goto end;
    default:
        ssh_set_error(session, SSH_FATAL, "Bad state in channel_open: %d",
                      channel->state);
    }

    channel->local_channel = ssh_channel_new_id(session);
    channel->local_maxpacket = maxpacket;
    channel->local_window = window;

    SSH_LOG(SSH_LOG_DEBUG,
            "Creating a channel %" PRIu32 " with %" PRIu32 " window and %" PRIu32 " max packet",
            channel->local_channel, window, maxpacket);

    rc = ssh_buffer_pack(session->out_buffer,
                         "bsddd",
                         SSH2_MSG_CHANNEL_OPEN,
                         type,
                         channel->local_channel,
                         channel->local_window,
                         channel->local_maxpacket);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        return err;
    }

    if (payload != NULL) {
        if (ssh_buffer_add_buffer(session->out_buffer, payload) < 0) {
            ssh_set_error_oom(session);

            return err;
        }
    }
    channel->state = SSH_CHANNEL_STATE_OPENING;
    if (ssh_packet_send(session) == SSH_ERROR) {
        return err;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %" PRIu32,
            type, channel->local_channel);

pending:
    /* wait until channel is opened by server */
    err = ssh_handle_packets_termination(session,
                                         SSH_TIMEOUT_DEFAULT,
                                         ssh_channel_open_termination,
                                         channel);

    if (session->session_state == SSH_SESSION_STATE_ERROR) {
        err = SSH_ERROR;
    }

end:
    /* This needs to pass the SSH_AGAIN from the above,
     * but needs to catch failed channel states */
    if (channel->state == SSH_CHANNEL_STATE_OPEN) {
        err = SSH_OK;
    } else if (err != SSH_AGAIN) {
        /* Messages were handled correctly, but the channel state is invalid */
        err = SSH_ERROR;
    }

    return err;
}

/* return channel with corresponding local id, or NULL if not found */
ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id)
{
    struct ssh_iterator *it = NULL;
    ssh_channel channel = NULL;

    for (it = ssh_list_get_iterator(session->channels); it != NULL;
         it = it->next) {
        channel = ssh_iterator_value(ssh_channel, it);
        if (channel == NULL) {
            continue;
        }
        if (channel->local_channel == id) {
            return channel;
        }
    }

    return NULL;
}

/**
 * @internal
 * @brief grows the local window and sends a packet to the other party
 * @param session SSH session
 * @param channel SSH channel
 * @return            `SSH_OK` if successful; `SSH_ERROR` otherwise.
 */
static int grow_window(ssh_session session,
                       ssh_channel channel)
{
  uint32_t used;
  uint32_t increment;
  int rc;

  /* Calculate the increment taking into account what the peer may still send
   * (local_window) and what we've already buffered (stdout_buffer and
   * stderr_buffer).
  */
  used = channel->local_window;
  if (channel->stdout_buffer != NULL) {
    used += ssh_buffer_get_len(channel->stdout_buffer);
  }
  if (channel->stderr_buffer != NULL) {
    used += ssh_buffer_get_len(channel->stderr_buffer);
  }
  /* Avoid a negative increment in case the peer sent more than the window allowed */
  increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0;
  /* Don't grow until we can request at least half a window */
  if (increment < (WINDOW_DEFAULT / 2)) {
    SSH_LOG(SSH_LOG_DEBUG,
        "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)",
        channel->local_channel, channel->remote_channel, WINDOW_DEFAULT,
        channel->local_window);

    return SSH_OK;
  }

  rc = ssh_buffer_pack(session->out_buffer,
                       "bdd",
                       SSH2_MSG_CHANNEL_WINDOW_ADJUST,
                       channel->remote_channel,
                       increment);
  if (rc != SSH_OK) {
    ssh_set_error_oom(session);
    goto error;
  }

  if (ssh_packet_send(session) == SSH_ERROR) {
    goto error;
  }

  SSH_LOG(SSH_LOG_DEBUG,
      "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes",
      channel->local_channel,
      channel->remote_channel,
      increment);

  channel->local_window += increment;

  return SSH_OK;
error:
  ssh_buffer_reinit(session->out_buffer);

  return SSH_ERROR;
}

/**
 * @internal
 *
 * @brief Parse a channel-related packet to resolve it to a ssh_channel.
 *
 * @param[in]  session  The current SSH session.
 *
 * @param[in]  packet   The buffer to parse packet from. The read pointer will
 *                      be moved after the call.
 *
 * @return              The related ssh_channel, or NULL if the channel is
 *                      unknown or the packet is invalid.
 */
static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet)
{
    ssh_channel channel = NULL;
    uint32_t chan;
    int rc;

    rc = ssh_buffer_unpack(packet, "d", &chan);
    if (rc != SSH_OK) {
        ssh_set_error(session,
                      SSH_FATAL,
                      "Getting channel from message: short read");
        return NULL;
    }

    channel = ssh_channel_from_local(session, chan);
    if (channel == NULL) {
        ssh_set_error(session,
                      SSH_FATAL,
                      "Server specified invalid channel %" PRIu32,
                      (uint32_t)chan);
    }

    return channel;
}

SSH_PACKET_CALLBACK(channel_rcv_change_window)
{
    ssh_channel channel = NULL;
    uint32_t bytes;
    int rc;
    bool was_empty;

    (void)user;
    (void)type;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
    }

    rc = ssh_buffer_unpack(packet, "d", &bytes);
    if (channel == NULL || rc != SSH_OK) {
        SSH_LOG(SSH_LOG_PACKET,
                "Error getting a window adjust message: invalid packet");

        return SSH_PACKET_USED;
    }

    SSH_LOG(SSH_LOG_DEBUG,
            "Adding %" PRIu32 " bytes to channel (%" PRIu32 ":%" PRIu32
            ") (from %" PRIu32 " bytes)",
            bytes,
            channel->local_channel,
            channel->remote_channel,
            channel->remote_window);

    was_empty = channel->remote_window == 0;

    channel->remote_window += bytes;

    /* Writing to the channel is non-blocking until the receive window is empty.
     * When the receive window becomes non-zero again, call
     * channel_write_wontblock_function. */
    if (was_empty && bytes > 0) {
        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_write_wontblock_function,
                                   session,
                                   channel,
                                   channel->remote_window);
    }

    return SSH_PACKET_USED;
}

/* is_stderr is set to 1 if the data are extended, ie stderr */
SSH_PACKET_CALLBACK(channel_rcv_data)
{
    ssh_channel channel = NULL;
    ssh_string str = NULL;
    ssh_buffer buf = NULL;
    void *data = NULL;
    uint32_t len;
    int extended, is_stderr = 0;
    int rest;

    (void)user;

    if (type == SSH2_MSG_CHANNEL_DATA) {
        extended = 0;
    } else { /* SSH_MSG_CHANNEL_EXTENDED_DATA */
        extended = 1;
    }

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));

        return SSH_PACKET_USED;
    }

    if (extended) {
        uint32_t data_type_code, rc;
        rc = ssh_buffer_get_u32(packet, &data_type_code);
        if (rc != sizeof(uint32_t)) {
            SSH_LOG(SSH_LOG_PACKET,
                    "Failed to read data type code: rc = %" PRIu32, rc);

            return SSH_PACKET_USED;
        }
        is_stderr = 1;
        data_type_code = ntohl(data_type_code);
        if (data_type_code != SSH2_EXTENDED_DATA_STDERR) {
            SSH_LOG(SSH_LOG_PACKET, "Invalid data type code %" PRIu32 "!",
                    data_type_code);
        }
    }

    str = ssh_buffer_get_ssh_string(packet);
    if (str == NULL) {
        SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!");

        return SSH_PACKET_USED;
    }
    /* STRING_SIZE_MAX < UINT32_MAX */
    len = (uint32_t)ssh_string_len(str);

    SSH_LOG(SSH_LOG_PACKET,
            "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32
            " remote win=%" PRIu32 ") on channel %" PRIu32 ":%" PRIu32,
            len,
            is_stderr ? " in stderr" : "",
            channel->local_window,
            channel->remote_window,
            channel->local_channel,
            channel->remote_channel);

    if (len > channel->local_window) {
        SSH_LOG(SSH_LOG_RARE,
                "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")",
                len,
                channel->local_window);

        SSH_STRING_FREE(str);

        ssh_set_error(session, SSH_FATAL, "Window exceeded");

        return SSH_PACKET_USED;
    }

    data = ssh_string_data(str);
    if (channel_default_bufferize(channel, data, len, is_stderr) < 0) {
        SSH_STRING_FREE(str);

        return SSH_PACKET_USED;
    }

    channel->local_window -= len;

    SSH_LOG(SSH_LOG_PACKET,
            "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")",
            channel->local_window,
            channel->remote_window);

    SSH_STRING_FREE(str);

    if (is_stderr) {
        buf = channel->stderr_buffer;
    } else {
        buf = channel->stdout_buffer;
    }

    ssh_callbacks_iterate(channel->callbacks,
                          ssh_channel_callbacks,
                          channel_data_function) {
        if (ssh_buffer_get(buf) == NULL) {
            break;
        }
        rest = ssh_callbacks_iterate_exec(channel_data_function,
                                          channel->session,
                                          channel,
                                          ssh_buffer_get(buf),
                                          ssh_buffer_get_len(buf),
                                          is_stderr);
        if (rest > 0) {
            int rc;
            if (channel->counter != NULL) {
                channel->counter->in_bytes += rest;
            }
            ssh_buffer_pass_bytes(buf, rest);

            rc = grow_window(session, channel);
            if (rc == SSH_ERROR) {
              return -1;
            }
        }
    }
    ssh_callbacks_iterate_end();

    return SSH_PACKET_USED;
}

SSH_PACKET_CALLBACK(channel_rcv_eof)
{
    ssh_channel channel = NULL;
    (void)user;
    (void)type;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));

        return SSH_PACKET_USED;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "Received eof on channel (%" PRIu32 ":%" PRIu32 ")",
            channel->local_channel,
            channel->remote_channel);
    /* channel->remote_window = 0; */
    channel->remote_eof = 1;

    ssh_callbacks_execute_list(channel->callbacks,
                               ssh_channel_callbacks,
                               channel_eof_function,
                               channel->session,
                               channel);

    return SSH_PACKET_USED;
}

static bool ssh_channel_has_unread_data(ssh_channel channel)
{
    if (channel == NULL) {
        return false;
    }

    if ((channel->stdout_buffer &&
         ssh_buffer_get_len(channel->stdout_buffer) > 0) ||
        (channel->stderr_buffer &&
         ssh_buffer_get_len(channel->stderr_buffer) > 0))
    {
        return true;
    }

    return false;
}

SSH_PACKET_CALLBACK(channel_rcv_close)
{
    ssh_channel channel = NULL;
    (void)user;
    (void)type;

    channel = channel_from_msg(session,packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));

        return SSH_PACKET_USED;
    }

    SSH_LOG(SSH_LOG_PACKET,
        "Received close on channel (%" PRIu32 ":%" PRIu32 ")",
        channel->local_channel,
        channel->remote_channel);

    if (!ssh_channel_has_unread_data(channel)) {
        channel->state = SSH_CHANNEL_STATE_CLOSED;
    } else {
        channel->delayed_close = 1;
    }

    if (channel->remote_eof == 0) {
        SSH_LOG(SSH_LOG_PACKET,
            "Remote host not polite enough to send an eof before close");
    }
    /*
    * The remote eof doesn't break things if there was still data into read
    * buffer because the eof is ignored until the buffer is empty.
    */
    channel->remote_eof = 1;

    ssh_callbacks_execute_list(channel->callbacks,
                               ssh_channel_callbacks,
                               channel_close_function,
                               channel->session,
                               channel);

    channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE;
    if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)
        ssh_channel_do_free(channel);

    return SSH_PACKET_USED;
}

SSH_PACKET_CALLBACK(channel_rcv_request)
{
    ssh_channel channel = NULL;
    char *request = NULL;
    uint8_t want_reply;
    int rc;
    (void)user;
    (void)type;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        return SSH_PACKET_USED;
    }

    rc = ssh_buffer_unpack(packet, "sb", &request, &want_reply);
    if (rc != SSH_OK) {
        SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
        return SSH_PACKET_USED;
    }

    if (strcmp(request, "exit-status") == 0) {
        SAFE_FREE(request);
        rc = ssh_buffer_unpack(packet, "d", &channel->exit.code);
        if (rc != SSH_OK) {
            SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet");
            return SSH_PACKET_USED;
        }
        channel->exit.status = true;

        SSH_LOG(SSH_LOG_PACKET,
                "received exit-status %u on channel %" PRIu32 ":%" PRIu32,
                channel->exit.code,
                channel->local_channel,
                channel->remote_channel);

        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_exit_status_function,
                                   channel->session,
                                   channel,
                                   channel->exit.code);

        return SSH_PACKET_USED;
    }

    if (strcmp(request, "signal") == 0) {
        char *sig = NULL;

        SAFE_FREE(request);
        SSH_LOG(SSH_LOG_PACKET, "received signal");

        rc = ssh_buffer_unpack(packet, "s", &sig);
        if (rc != SSH_OK) {
            SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
            return SSH_PACKET_USED;
        }

        SSH_LOG(SSH_LOG_PACKET, "Remote connection sent a signal SIG %s", sig);
        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_signal_function,
                                   channel->session,
                                   channel,
                                   sig);
        SAFE_FREE(sig);

        return SSH_PACKET_USED;
    }

    if (strcmp(request, "exit-signal") == 0) {
        const char *core = "(core dumped)";
        char *sig = NULL;
        char *errmsg = NULL;
        char *lang = NULL;
        uint8_t core_dumped;

        SAFE_FREE(request);

        rc = ssh_buffer_unpack(packet,
                               "sbss",
                               &sig,         /* signal name */
                               &core_dumped, /* core dumped */
                               &errmsg,      /* error message */
                               &lang);
        if (rc != SSH_OK) {
            SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST");
            return SSH_PACKET_USED;
        }

        if (core_dumped == 0) {
            core = "";
        }

        SSH_LOG(SSH_LOG_PACKET,
                "Remote connection closed by signal SIG %s %s",
                sig,
                core);
        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_exit_signal_function,
                                   channel->session,
                                   channel,
                                   sig,
                                   core_dumped,
                                   errmsg,
                                   lang);

        channel->exit.core_dumped = core_dumped;
        if (sig != NULL) {
            SAFE_FREE(channel->exit.signal);
            channel->exit.signal = sig;
        }
        channel->exit.status = true;

        SAFE_FREE(lang);
        SAFE_FREE(errmsg);

        return SSH_PACKET_USED;
    }
    if (strcmp(request, "keepalive@openssh.com") == 0) {
        SAFE_FREE(request);
        SSH_LOG(SSH_LOG_DEBUG, "Responding to Openssh's keepalive");

        rc = ssh_buffer_pack(session->out_buffer,
                             "bd",
                             SSH2_MSG_CHANNEL_FAILURE,
                             channel->remote_channel);
        if (rc != SSH_OK) {
            return SSH_PACKET_USED;
        }
        ssh_packet_send(session);

        return SSH_PACKET_USED;
    }

    if (strcmp(request, "auth-agent-req@openssh.com") == 0) {
        int status;

        SAFE_FREE(request);
        SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request");

        status = SSH2_MSG_CHANNEL_FAILURE;
        ssh_callbacks_iterate (channel->callbacks,
                               ssh_channel_callbacks,
                               channel_auth_agent_req_function) {
            ssh_callbacks_iterate_exec(channel_auth_agent_req_function,
                                       channel->session,
                                       channel);
            /* in lieu of a return value, if the callback exists it's supported
             */
            status = SSH2_MSG_CHANNEL_SUCCESS;
            break;
        }
        ssh_callbacks_iterate_end();

        if (want_reply) {
            rc = ssh_buffer_pack(session->out_buffer,
                                 "bd",
                                 status,
                                 channel->remote_channel);
            if (rc != SSH_OK) {
                return SSH_PACKET_USED;
            }
            ssh_packet_send(session);
        }

        return SSH_PACKET_USED;
    }
#ifdef WITH_SERVER
    /* If we are here, that means we have a request that is not in the
     * understood client requests. That means we need to create a ssh message to
     * be passed to the user code handling ssh messages
     */
    ssh_message_handle_channel_request(session,
                                       channel,
                                       packet,
                                       request,
                                       want_reply);
#else
    SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request);
#endif

    SAFE_FREE(request);

    return SSH_PACKET_USED;
}

/*
 * When data has been received from the ssh server, it can be applied to the
 * known user function, with help of the callback, or inserted here
 *
 * FIXME is the window changed?
 */
int channel_default_bufferize(ssh_channel channel,
                              void *data, uint32_t len,
                              bool is_stderr)
{
    ssh_session session = NULL;

    if (channel == NULL) {
        return -1;
    }

    session = channel->session;

    if (data == NULL) {
        ssh_set_error_invalid(session);
        return -1;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "placing %" PRIu32 " bytes into channel buffer (%s)",
            len,
            is_stderr ? "stderr" : "stdout");
    if (!is_stderr) {
        /* stdout */
        if (channel->stdout_buffer == NULL) {
            channel->stdout_buffer = ssh_buffer_new();
            if (channel->stdout_buffer == NULL) {
                ssh_set_error_oom(session);
                return -1;
            }
        }

        if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) {
            ssh_set_error_oom(session);
            SSH_BUFFER_FREE(channel->stdout_buffer);
            channel->stdout_buffer = NULL;
            return -1;
        }
    } else {
        /* stderr */
        if (channel->stderr_buffer == NULL) {
            channel->stderr_buffer = ssh_buffer_new();
            if (channel->stderr_buffer == NULL) {
                ssh_set_error_oom(session);
                return -1;
            }
        }

        if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) {
            ssh_set_error_oom(session);
            SSH_BUFFER_FREE(channel->stderr_buffer);
            channel->stderr_buffer = NULL;
            return -1;
        }
    }

    return 0;
}

/**
 * @brief Open a session channel (suited for a shell, not TCP forwarding).
 *
 * @param[in]  channel  An allocated channel.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @see ssh_channel_open_forward()
 * @see ssh_channel_request_env()
 * @see ssh_channel_request_shell()
 * @see ssh_channel_request_exec()
 */
int ssh_channel_open_session(ssh_channel channel)
{
  if (channel == NULL) {
      return SSH_ERROR;
  }

  return channel_open(channel,
                      "session",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      NULL);
}

/**
 * @brief Open an agent authentication forwarding channel. This type of channel
 * can be opened by a server towards a client in order to provide SSH-Agent
 * services to the server-side process. This channel can only be opened if the
 * client claimed support by sending a channel request beforehand.
 *
 * @param[in]  channel  An allocated channel.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @see ssh_channel_open_forward()
 */
int ssh_channel_open_auth_agent(ssh_channel channel)
{
  if (channel == NULL) {
      return SSH_ERROR;
  }

  return channel_open(channel,
                      "auth-agent@openssh.com",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      NULL);
}

/**
 * @brief Open a TCP/IP forwarding channel.
 *
 * @param[in]  channel  An allocated channel.
 *
 * @param[in]  remotehost The remote host to connected (host name or IP).
 *
 * @param[in]  remoteport The remote port.
 *
 * @param[in]  sourcehost The numeric IP address of the machine from where the
 *                        connection request originates. This is mostly for
 *                        logging purposes.
 *
 * @param[in]  localport  The port on the host from where the connection
 *                        originated. This is mostly for logging purposes.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @warning This function does not bind the local port and does not
 * automatically forward the content of a socket to the channel. You still have
 * to use ssh_channel_read and ssh_channel_write for this.
 */
int ssh_channel_open_forward(ssh_channel channel, const char *remotehost,
    int remoteport, const char *sourcehost, int localport)
{
    ssh_session session = NULL;
    ssh_buffer payload = NULL;
    ssh_string str = NULL;
    int rc = SSH_ERROR;

    if (channel == NULL) {
        return rc;
    }

    session = channel->session;

    if (remotehost == NULL || sourcehost == NULL) {
        ssh_set_error_invalid(session);
        return rc;
    }

    payload = ssh_buffer_new();
    if (payload == NULL) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_buffer_pack(payload,
                         "sdsd",
                         remotehost,
                         remoteport,
                         sourcehost,
                         localport);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = channel_open(channel,
                      "direct-tcpip",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      payload);

error:
    SSH_BUFFER_FREE(payload);
    SSH_STRING_FREE(str);

    return rc;
}

/**
 * @brief Open a TCP/IP - UNIX domain socket forwarding channel.
 *
 * @param[in]  channel  An allocated channel.
 *
 * @param[in]  remotepath   The UNIX socket path on the remote machine
 *
 * @param[in]  sourcehost   The numeric IP address of the machine from where the
 *                          connection request originates. This is mostly for
 *                          logging purposes.
 *
 * @param[in]  localport    The port on the host from where the connection
 *                          originated. This is mostly for logging purposes.
 *
 * @return              `SSH_OK on` success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @warning This function does not bind the local port and does not
 * automatically forward the content of a socket to the channel.
 * You still have to use ssh_channel_read and ssh_channel_write for this.
 * @warning Requires support of OpenSSH for UNIX domain socket forwarding.
 */
int ssh_channel_open_forward_unix(ssh_channel channel,
                                  const char *remotepath,
                                  const char *sourcehost,
                                  int localport)
{
    ssh_session session = NULL;
    ssh_buffer payload = NULL;
    int rc = SSH_ERROR;
    int version;

    if (channel == NULL) {
        return rc;
    }

    session = channel->session;

    version = ssh_get_openssh_version(session);
    if (version == 0) {
        ssh_set_error(session,
                      SSH_REQUEST_DENIED,
                      "We're not connected to an OpenSSH server!");
        return SSH_ERROR;
    }

    if (remotepath == NULL || sourcehost == NULL) {
        ssh_set_error_invalid(session);
        return rc;
    }

    payload = ssh_buffer_new();
    if (payload == NULL) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_buffer_pack(payload,
                         "ssd",
                         remotepath,
                         sourcehost,
                         localport);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = channel_open(channel,
                      "direct-streamlocal@openssh.com",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      payload);

error:
    SSH_BUFFER_FREE(payload);

    return rc;
}

/**
 * @brief Close and free a channel.
 *
 * @param[in]  channel  The channel to free.
 *
 * @warning Any data unread on this channel will be lost.
 */
void ssh_channel_free(ssh_channel channel)
{
    ssh_session session = NULL;

    if (channel == NULL) {
        return;
    }

    session = channel->session;
    if (session->alive) {
        bool send_close = false;

        switch (channel->state) {
        case SSH_CHANNEL_STATE_OPEN:
            send_close = true;
            break;
        case SSH_CHANNEL_STATE_CLOSED:
            if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) {
                send_close = true;
            }
            if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) {
                send_close = false;
            }
            break;
        default:
            send_close = false;
            break;
        }

        if (send_close) {
            ssh_channel_close(channel);
        }
    }
    channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL;

    if (channel->callbacks != NULL) {
        ssh_list_free(channel->callbacks);
        channel->callbacks = NULL;
    }

    /* The idea behind the flags is the following : it is well possible
     * that a client closes a channel that still exists on the server side.
     * We definitively close the channel when we receive a close message *and*
     * the user closed it.
     */
    if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) ||
        (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) {
        ssh_channel_do_free(channel);
    }
}

/**
 * @internal
 * @brief Effectively free a channel, without caring about flags
 */

void ssh_channel_do_free(ssh_channel channel)
{
    struct ssh_iterator *it = NULL;
    ssh_session session = channel->session;

    it = ssh_list_find(session->channels, channel);
    if (it != NULL) {
        ssh_list_remove(session->channels, it);
    }

    SSH_BUFFER_FREE(channel->stdout_buffer);
    SSH_BUFFER_FREE(channel->stderr_buffer);

    if (channel->callbacks != NULL) {
        ssh_list_free(channel->callbacks);
        channel->callbacks = NULL;
    }
    SAFE_FREE(channel->exit.signal);

    channel->session = NULL;
    SAFE_FREE(channel);
}

/**
 * @brief Send an end of file on the channel.
 *
 * This doesn't close the channel. You may still read from it but not write.
 *
 * @param[in]  channel  The channel to send the eof to.
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred.
 *
 * Example:
@code
   rc = ssh_channel_send_eof(channel);
   if (rc == SSH_ERROR) {
       return -1;
   }
   while(!ssh_channel_is_eof(channel)) {
       rc = ssh_channel_read(channel, buf, sizeof(buf), 0);
       if (rc == SSH_ERROR) {
           return -1;
       }
   }
   ssh_channel_close(channel);
@endcode
 *
 * @see ssh_channel_close()
 * @see ssh_channel_free()
 * @see ssh_channel_is_eof()
 */
int ssh_channel_send_eof(ssh_channel channel)
{
    ssh_session session = NULL;
    int rc = SSH_ERROR;
    int err;

    if (channel == NULL || channel->session == NULL) {
        return rc;
    }

    /* If the EOF has already been sent we're done here. */
    if (channel->local_eof != 0) {
        return SSH_OK;
    }

    session = channel->session;

    err = ssh_buffer_pack(session->out_buffer,
                          "bd",
                          SSH2_MSG_CHANNEL_EOF,
                          channel->remote_channel);
    if (err != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_packet_send(session);
    SSH_LOG(SSH_LOG_PACKET,
        "Sent a EOF on client channel (%" PRIu32 ":%" PRIu32 ")",
        channel->local_channel,
        channel->remote_channel);
    if (rc != SSH_OK) {
        goto error;
    }

    rc = ssh_channel_flush(channel);
    if (rc == SSH_ERROR) {
        goto error;
    }
    channel->local_eof = 1;

    return rc;
error:
    ssh_buffer_reinit(session->out_buffer);

    return rc;
}

/**
 * @brief Close a channel.
 *
 * This sends an end of file and then closes the channel. You won't be able
 * to recover any data the server was going to send or was in buffers.
 *
 * @param[in]  channel  The channel to close.
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred.
 *
 * @see ssh_channel_free()
 * @see ssh_channel_is_eof()
 */
int ssh_channel_close(ssh_channel channel)
{
    ssh_session session = NULL;
    int rc = 0;

    if(channel == NULL) {
        return SSH_ERROR;
    }

    /* If the channel close has already been sent we're done here. */
    if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) {
        return SSH_OK;
    }

    session = channel->session;

    rc = ssh_channel_send_eof(channel);
    if (rc != SSH_OK) {
        return rc;
    }

    rc = ssh_buffer_pack(session->out_buffer,
                         "bd",
                         SSH2_MSG_CHANNEL_CLOSE,
                         channel->remote_channel);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_packet_send(session);
    SSH_LOG(SSH_LOG_PACKET,
            "Sent a close on client channel (%" PRIu32 ":%" PRIu32 ")",
            channel->local_channel,
            channel->remote_channel);

    if (rc == SSH_OK) {
        channel->state = SSH_CHANNEL_STATE_CLOSED;
        channel->flags |= SSH_CHANNEL_FLAG_CLOSED_LOCAL;
    }

    rc = ssh_channel_flush(channel);
    if(rc == SSH_ERROR) {
        goto error;
    }

    return rc;
error:
    ssh_buffer_reinit(session->out_buffer);

    return rc;
}

/* this termination function waits for a window growing condition */
static int ssh_channel_waitwindow_termination(void *c)
{
  ssh_channel channel = (ssh_channel) c;
  if (channel->remote_window > 0 ||
      channel->session->session_state == SSH_SESSION_STATE_ERROR ||
      channel->state == SSH_CHANNEL_STATE_CLOSED)
    return 1;
  else
    return 0;
}

/* This termination function waits until the session is not in blocked status
 * anymore, e.g. because of a key re-exchange.
 */
static int ssh_waitsession_unblocked(void *s)
{
    ssh_session session = (ssh_session)s;
    switch (session->session_state){
        case SSH_SESSION_STATE_DH:
        case SSH_SESSION_STATE_INITIAL_KEX:
        case SSH_SESSION_STATE_KEXINIT_RECEIVED:
            return 0;
        default:
            return 1;
    }
}
/**
 * @internal
 * @brief Flushes a channel (and its session) until the output buffer
 * is empty, or timeout elapsed.
 * @param channel SSH channel
 * @return  `SSH_OK` On success,
 *          `SSH_ERROR` On error.
 *          `SSH_AGAIN` Timeout elapsed (or in nonblocking mode).
 */
int ssh_channel_flush(ssh_channel channel)
{
    return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT);
}

static int channel_write_common(ssh_channel channel,
                                const void *data,
                                uint32_t len, int is_stderr)
{
    ssh_session session = NULL;
    uint32_t origlen = len;
    uint32_t effectivelen;
    int rc;

    if (channel == NULL) {
        return -1;
    }
    session = channel->session;
    if (data == NULL) {
        ssh_set_error_invalid(session);
        return -1;
    }

    if (len > INT_MAX) {
        SSH_LOG(SSH_LOG_TRACE,
                "Length (%" PRIu32 ") is bigger than INT_MAX",
                len);
        return SSH_ERROR;
    }

    if (channel->local_eof) {
        ssh_set_error(session,
                      SSH_REQUEST_DENIED,
                      "Can't write to channel %" PRIu32 ":%" PRIu32
                      " after EOF was sent",
                      channel->local_channel,
                      channel->remote_channel);
        return -1;
    }

    if (channel->state != SSH_CHANNEL_STATE_OPEN ||
        channel->delayed_close != 0) {
        ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed");

        return -1;
    }

    if (session->session_state == SSH_SESSION_STATE_ERROR) {
        return SSH_ERROR;
    }

    if (ssh_waitsession_unblocked(session) == 0) {
        rc = ssh_handle_packets_termination(session,
                                            SSH_TIMEOUT_DEFAULT,
                                            ssh_waitsession_unblocked,
                                            session);
        if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session))
            goto out;
    }
    while (len > 0) {
        if (channel->remote_window < len) {
            SSH_LOG(SSH_LOG_DEBUG,
                    "Remote window is %" PRIu32
                    " bytes. going to write %" PRIu32 " bytes",
                    channel->remote_window,
                    len);
            /* When the window is zero, wait for it to grow */
            if (channel->remote_window == 0) {
                /* nothing can be written */
                SSH_LOG(SSH_LOG_DEBUG, "Wait for a growing window message...");
                rc = ssh_handle_packets_termination(
                    session,
                    SSH_TIMEOUT_DEFAULT,
                    ssh_channel_waitwindow_termination,
                    channel);
                if (rc == SSH_ERROR ||
                    !ssh_channel_waitwindow_termination(channel) ||
                    session->session_state == SSH_SESSION_STATE_ERROR ||
                    channel->state == SSH_CHANNEL_STATE_CLOSED)
                    goto out;
                continue;
            }
            /* When the window is non-zero, accept data up to the window size */
            effectivelen = MIN(len, channel->remote_window);
        } else {
            effectivelen = len;
        }

        /*
         * Like OpenSSH, don't subtract bytes for the header fields
         * and allow to send a payload of remote_maxpacket length.
         */
        effectivelen = MIN(effectivelen, channel->remote_maxpacket);

        rc = ssh_buffer_pack(session->out_buffer,
                             "bd",
                             is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA
                                       : SSH2_MSG_CHANNEL_DATA,
                             channel->remote_channel);
        if (rc != SSH_OK) {
            ssh_set_error_oom(session);
            goto error;
        }

        /* stderr message has an extra field */
        if (is_stderr) {
            rc = ssh_buffer_pack(session->out_buffer,
                                 "d",
                                 SSH2_EXTENDED_DATA_STDERR);
            if (rc != SSH_OK) {
                ssh_set_error_oom(session);
                goto error;
            }
        }

        /* append payload data */
        rc = ssh_buffer_pack(session->out_buffer,
                             "dP",
                             effectivelen,
                             (size_t)effectivelen,
                             data);
        if (rc != SSH_OK) {
            ssh_set_error_oom(session);
            goto error;
        }

        rc = ssh_packet_send(session);
        if (rc == SSH_ERROR) {
            return SSH_ERROR;
        }

        SSH_LOG(SSH_LOG_PACKET,
                "ssh_channel_write wrote %" PRIu32 " bytes",
                effectivelen);

        channel->remote_window -= effectivelen;
        len -= effectivelen;
        data = ((uint8_t *)data + effectivelen);
        if (channel->counter != NULL) {
            channel->counter->out_bytes += effectivelen;
        }
    }

    /* it's a good idea to flush the socket now */
    rc = ssh_channel_flush(channel);
    if (rc == SSH_ERROR) {
        goto error;
    }

out:
    return (int)(origlen - len);

error:
    ssh_buffer_reinit(session->out_buffer);

    return SSH_ERROR;
}

/**
 * @brief Get the remote window size.
 *
 * This is the maximum amount of bytes the remote side expects us to send
 * before growing the window again.
 *
 * @param[in] channel The channel to query.
 *
 * @return            The remote window size
 *
 * @warning A nonzero return value does not guarantee the socket is ready
 *          to send that much data. Buffering may happen in the local SSH
 *          packet buffer, so beware of really big window sizes.
 *
 * @warning A zero return value means ssh_channel_write (default settings)
 *          will block until the window grows back.
 */
uint32_t ssh_channel_window_size(ssh_channel channel)
{
    return channel->remote_window;
}

/**
 * @brief Blocking write on a channel.
 *
 * @param[in]  channel  The channel to write to.
 *
 * @param[in]  data     A pointer to the data to write.
 *
 * @param[in]  len      The length of the buffer to write to.
 *
 * @return              The number of bytes written, `SSH_ERROR` on error.
 *
 * @see ssh_channel_read()
 */
int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len)
{
    return channel_write_common(channel, data, len, 0);
}

/**
 * @brief Check if the channel is open or not.
 *
 * @param[in]  channel  The channel to check.
 *
 * @return              0 if channel is closed, nonzero otherwise.
 *
 * @see ssh_channel_is_closed()
 */
int ssh_channel_is_open(ssh_channel channel)
{
    if (channel == NULL) {
        return 0;
    }
    return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0);
}

/**
 * @brief Check if the channel is closed or not.
 *
 * @param[in]  channel  The channel to check.
 *
 * @return              0 if channel is opened, nonzero otherwise.
 *
 * @see ssh_channel_is_open()
 */
int ssh_channel_is_closed(ssh_channel channel)
{
    if (channel == NULL || channel->session == NULL) {
        return SSH_ERROR;
    }
    return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0);
}

/**
 * @brief Check if remote has sent an EOF.
 *
 * @param[in]  channel  The channel to check.
 *
 * @return              0 if there is no EOF, nonzero otherwise.
 */
int ssh_channel_is_eof(ssh_channel channel)
{
    if (channel == NULL) {
        return SSH_ERROR;
    }
    if (ssh_channel_has_unread_data(channel)) {
        return 0;
    }

    return (channel->remote_eof != 0);
}

/**
 * @brief Put the channel into blocking or nonblocking mode.
 *
 * @param[in]  channel  The channel to use.
 *
 * @param[in]  blocking A boolean for blocking or nonblocking.
 *
 * @warning    A side-effect of this is to put the whole session
 *             in non-blocking mode.
 * @see ssh_set_blocking()
 */
void ssh_channel_set_blocking(ssh_channel channel, int blocking)
{
    if (channel == NULL) {
        return;
    }
    ssh_set_blocking(channel->session, blocking);
}

/**
 * @internal
 *
 * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state.
 */
SSH_PACKET_CALLBACK(ssh_packet_channel_success)
{
    ssh_channel channel = NULL;
    (void)type;
    (void)user;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));
        return SSH_PACKET_USED;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")",
            channel->local_channel,
            channel->remote_channel);
    if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) {
        SSH_LOG(SSH_LOG_RARE,
                "SSH_CHANNEL_SUCCESS received in incorrect state %d",
                channel->request_state);
    } else {
        channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED;

        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_request_response_function,
                                   channel->session,
                                   channel);
    }

    return SSH_PACKET_USED;
}

/**
 * @internal
 *
 * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state.
 */
SSH_PACKET_CALLBACK(ssh_packet_channel_failure)
{
    ssh_channel channel = NULL;
    (void)type;
    (void)user;

    channel = channel_from_msg(session, packet);
    if (channel == NULL) {
        SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session));

        return SSH_PACKET_USED;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")",
            channel->local_channel,
            channel->remote_channel);
    if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) {
        SSH_LOG(SSH_LOG_RARE,
                "SSH_CHANNEL_FAILURE received in incorrect state %d",
                channel->request_state);
    } else {
        channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED;

        ssh_callbacks_execute_list(channel->callbacks,
                                   ssh_channel_callbacks,
                                   channel_request_response_function,
                                   channel->session,
                                   channel);
    }

    return SSH_PACKET_USED;
}

static int ssh_channel_request_termination(void *c)
{
  ssh_channel channel = (ssh_channel)c;
  if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING ||
      channel->session->session_state == SSH_SESSION_STATE_ERROR)
    return 1;
  else
    return 0;
}

static int channel_request(ssh_channel channel, const char *request,
    ssh_buffer buffer, int reply)
{
  ssh_session session = channel->session;
  int rc = SSH_ERROR;
  int ret;

  switch(channel->request_state){
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }

  ret = ssh_buffer_pack(session->out_buffer,
                        "bdsb",
                        SSH2_MSG_CHANNEL_REQUEST,
                        channel->remote_channel,
                        request,
                        reply == 0 ? 0 : 1);
  if (ret != SSH_OK) {
    ssh_set_error_oom(session);
    goto error;
  }

  if (buffer != NULL) {
    if (ssh_buffer_add_data(session->out_buffer, ssh_buffer_get(buffer),
        ssh_buffer_get_len(buffer)) < 0) {
      ssh_set_error_oom(session);
      goto error;
    }
  }
  channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING;
  if (ssh_packet_send(session) == SSH_ERROR) {
    return rc;
  }

  SSH_LOG(SSH_LOG_PACKET,
          "Sent a SSH_MSG_CHANNEL_REQUEST %s on channel %" PRIu32 ":%" PRIu32,
          request,
          channel->local_channel,
          channel->remote_channel);
  if (reply == 0) {
    channel->request_state = SSH_CHANNEL_REQ_STATE_NONE;
    return SSH_OK;
  }
pending:
  rc = ssh_handle_packets_termination(session,
                                      SSH_TIMEOUT_DEFAULT,
                                      ssh_channel_request_termination,
                                      channel);

  if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) {
      channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR;
  }
  /* we received something */
  switch (channel->request_state){
    case SSH_CHANNEL_REQ_STATE_ERROR:
      rc=SSH_ERROR;
      break;
    case SSH_CHANNEL_REQ_STATE_DENIED:
      ssh_set_error(session,
                    SSH_REQUEST_DENIED,
                    "Channel request %s failed on channel %" PRIu32 ":%" PRIu32,
                    request,
                    channel->local_channel,
                    channel->remote_channel);
      rc=SSH_ERROR;
      break;
    case SSH_CHANNEL_REQ_STATE_ACCEPTED:
      SSH_LOG(SSH_LOG_DEBUG,
              "Channel request %s success on channel %" PRIu32 ":%" PRIu32,
              request,
              channel->local_channel,
              channel->remote_channel);
      rc=SSH_OK;
      break;
    case SSH_CHANNEL_REQ_STATE_PENDING:
      rc = SSH_AGAIN;
      return rc;
    case SSH_CHANNEL_REQ_STATE_NONE:
      /* Never reached */
      ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()");
      rc=SSH_ERROR;
      break;
  }
  channel->request_state=SSH_CHANNEL_REQ_STATE_NONE;

  return rc;
error:
  ssh_buffer_reinit(session->out_buffer);

  return rc;
}

/**
 * @brief Request a pty with a specific type and size.
 *
 * @param[in]  channel  The channel to send the request.
 *
 * @param[in]  terminal The terminal type ("vt100, xterm,...").
 *
 * @param[in]  col      The number of columns.
 *
 * @param[in]  row      The number of rows.
 *
 * @param[in]  modes    Encoded SSH terminal modes for the PTY
 *
 * @param[in]  modes_len Number of bytes in 'modes'
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 */
int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal,
    int col, int row, const unsigned char* modes, size_t modes_len)
{
    ssh_session session = NULL;
    ssh_buffer buffer = NULL;
    int rc = SSH_ERROR;

    if (channel == NULL) {
        return SSH_ERROR;
    }
    session = channel->session;

    if (terminal == NULL) {
        ssh_set_error_invalid(channel->session);
        return rc;
    }

    switch (channel->request_state) {
    case SSH_CHANNEL_REQ_STATE_NONE:
        break;
    default:
        goto pending;
    }

    buffer = ssh_buffer_new();
    if (buffer == NULL) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_buffer_pack(buffer,
                         "sdddddP",
                         terminal,
                         col,
                         row,
                         0, /* pix */
                         0, /* pix */
                         (uint32_t)modes_len,
                         (size_t)modes_len,
                         modes);

    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }
pending:
    rc = channel_request(channel, "pty-req", buffer, 1);
error:
    SSH_BUFFER_FREE(buffer);

    return rc;
}

/**
 * @brief Request a PTY with a specific size using current TTY modes.
 *
 * Encodes @p terminal modes from the current TTY and sends a PTY request
 * for the given channel, terminal type, and size in columns/rows.
 *
 * @param[in] channel  The channel to send the request on.
 * @param[in] terminal The terminal type (e.g. "xterm").
 * @param[in] col      Number of columns.
 * @param[in] row      Number of rows.
 *
 * @return `SSH_OK` on success; `SSH_ERROR` on failure.
 */
int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal,
    int col, int row)
{
    /* use modes from the current TTY */
    unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE];
    int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf));
    if (rc < 0) {
        return rc;
    }
    return ssh_channel_request_pty_size_modes(channel,
                                              terminal,
                                              col,
                                              row,
                                              modes_buf,
                                              (size_t)rc);
}

/**
 * @brief Request a PTY.
 *
 * @param[in]  channel  The channel to send the request.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @see ssh_channel_request_pty_size()
 */
int ssh_channel_request_pty(ssh_channel channel)
{
  return ssh_channel_request_pty_size(channel, "xterm", 80, 24);
}

/**
 * @brief Change the size of the terminal associated to a channel.
 *
 * @param[in]  channel  The channel to change the size.
 *
 * @param[in]  cols     The new number of columns.
 *
 * @param[in]  rows     The new number of rows.
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred.
 *
 * @warning Do not call it from a signal handler if you are not sure any other
 *          libssh function using the same channel/session is running at the
 *          same time (not 100% threadsafe).
 */
int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows)
{
  ssh_session session = channel->session;
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer,
                       "dddd",
                       cols,
                       rows,
                       0, /* pix */
                       0 /* pix */);
  if (rc != SSH_OK) {
    ssh_set_error_oom(session);
    goto error;
  }

  rc = channel_request(channel, "window-change", buffer, 0);
error:
  SSH_BUFFER_FREE(buffer);

  return rc;
}

/**
 * @brief Request a shell.
 *
 * @param[in]  channel  The channel to send the request.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 */
int ssh_channel_request_shell(ssh_channel channel)
{
    if (channel == NULL) {
        return SSH_ERROR;
    }

    return channel_request(channel, "shell", NULL, 1);
}

/**
 * @brief Request a subsystem (for example "sftp").
 *
 * @param[in]  channel  The channel to send the request.
 *
 * @param[in]  subsys   The subsystem to request (for example "sftp").
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @warning You normally don't have to call it for sftp, see sftp_new().
 */
int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return SSH_ERROR;
  }
  if(subsys == NULL) {
      ssh_set_error_invalid(channel->session);
      return rc;
  }
  switch(channel->request_state){
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer, "s", subsys);
  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }
pending:
  rc = channel_request(channel, "subsystem", buffer, 1);
error:
  SSH_BUFFER_FREE(buffer);

  return rc;
}

/**
 * @brief Request sftp subsystem on the channel
 *
 * @param[in]  channel The channel to request the sftp subsystem.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @note You should use sftp_new() which does this for you.
 */
int ssh_channel_request_sftp( ssh_channel channel)
{
    if(channel == NULL) {
        return SSH_ERROR;
    }
    return ssh_channel_request_subsystem(channel, "sftp");
}

static char *generate_cookie(void)
{
  static const char *hex = "0123456789abcdef";
  char s[36];
  unsigned char rnd[16];
  int ok;
  int i;

  ok = ssh_get_random(rnd, sizeof(rnd), 0);
  if (!ok) {
      return NULL;
  }

  for (i = 0; i < 16; i++) {
    s[i*2] = hex[rnd[i] & 0x0f];
    s[i*2+1] = hex[rnd[i] >> 4];
  }
  s[32] = '\0';
  return strdup(s);
}

/**
 * @brief Sends the "x11-req" channel request over an existing session channel.
 *
 * This will enable redirecting the display of the remote X11 applications to
 * local X server over a secure tunnel.
 *
 * @param[in]  channel  An existing session channel where the remote X11
 *                      applications are going to be executed.
 *
 * @param[in]  single_connection A boolean to mark only one X11 app will be
 *                               redirected.
 *
 * @param[in]  protocol A x11 authentication protocol. Pass NULL to use the
 *                      default value MIT-MAGIC-COOKIE-1.
 *
 * @param[in]  cookie   A x11 authentication cookie. Pass NULL to generate
 *                      a random cookie.
 *
 * @param[in] screen_number The screen number.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 */
int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol,
    const char *cookie, int screen_number)
{
  ssh_buffer buffer = NULL;
  char *c = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return SSH_ERROR;
  }
  switch(channel->request_state){
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  if (cookie == NULL) {
    c = generate_cookie();
    if (c == NULL) {
      ssh_set_error_oom(channel->session);
      goto error;
    }
  }

  rc = ssh_buffer_pack(buffer,
                       "bssd",
                       single_connection == 0 ? 0 : 1,
                       protocol ? protocol : "MIT-MAGIC-COOKIE-1",
                       cookie ? cookie : c,
                       screen_number);
  if (c != NULL){
      SAFE_FREE(c);
  }
  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }
pending:
  rc = channel_request(channel, "x11-req", buffer, 1);

error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

static ssh_channel ssh_channel_accept(ssh_session session, int channeltype,
    int timeout_ms, int *destination_port, char **originator, int *originator_port)
{
#ifndef _WIN32
  static const struct timespec ts = {
    .tv_sec = 0,
    .tv_nsec = 50000000 /* 50ms */
  };
#endif
  ssh_message msg = NULL;
  ssh_channel channel = NULL;
  struct ssh_iterator *iterator = NULL;
  int t;

  /*
   * We sleep for 50 ms in ssh_handle_packets() and later sleep for
   * 50 ms. So we need to decrement by 100 ms.
   */
  for (t = timeout_ms; t >= 0; t -= 100) {
    if (timeout_ms == 0) {
        ssh_handle_packets(session, 0);
    } else {
        ssh_handle_packets(session, 50);
    }

    if (session->ssh_message_list) {
      iterator = ssh_list_get_iterator(session->ssh_message_list);
      while (iterator) {
        msg = (ssh_message)iterator->data;
        if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN &&
            ssh_message_subtype(msg) == channeltype) {
          ssh_list_remove(session->ssh_message_list, iterator);
          channel = ssh_message_channel_request_open_reply_accept(msg);
          if(destination_port) {
            *destination_port=msg->channel_request_open.destination_port;
          }
          if(originator) {
            *originator=strdup(msg->channel_request_open.originator);
          }
          if(originator_port) {
            *originator_port=msg->channel_request_open.originator_port;
          }

          ssh_message_free(msg);
          return channel;
        }
        iterator = iterator->next;
      }
    }
    if(t>0){
#ifdef _WIN32
      Sleep(50); /* 50ms */
#else
      nanosleep(&ts, NULL);
#endif
    }
  }

  ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server");
  return NULL;
}

/**
 * @brief Accept an X11 forwarding channel.
 *
 * @param[in]  channel  An x11-enabled session channel.
 *
 * @param[in]  timeout_ms Timeout in milliseconds.
 *
 * @return              A newly created channel, or NULL if no X11 request from
 *                      the server.
 */
ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms)
{
    return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL);
}

/**
 * @brief Send an "auth-agent-req" channel request over an existing session
 * channel.
 *
 * This client-side request will enable forwarding the agent over an secure
 * tunnel. When the server is ready to open one authentication agent channel, an
 * ssh_channel_open_request_auth_agent_callback event will be generated.
 *
 * @param[in]  channel  The channel to send signal.
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred
 */
int ssh_channel_request_auth_agent(ssh_channel channel) {
  if (channel == NULL) {
    return SSH_ERROR;
  }

  return channel_request(channel, "auth-agent-req@openssh.com", NULL, 0);
}

/**
 * @internal
 *
 * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global
 * request.
 */
SSH_PACKET_CALLBACK(ssh_request_success){
  (void)type;
  (void)user;
  (void)packet;

  SSH_LOG(SSH_LOG_PACKET,
      "Received SSH_REQUEST_SUCCESS");
  if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
    SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d",
        session->global_req_state);
  } else {
    session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED;
  }

  return SSH_PACKET_USED;
}

/**
 * @internal
 *
 * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global
 * request.
 */
SSH_PACKET_CALLBACK(ssh_request_denied){
  (void)type;
  (void)user;
  (void)packet;

  SSH_LOG(SSH_LOG_PACKET,
      "Received SSH_REQUEST_FAILURE");
  if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){
    SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d",
        session->global_req_state);
  } else {
    session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED;
  }

  return SSH_PACKET_USED;

}

static int ssh_global_request_termination(void *s)
{
  ssh_session session = (ssh_session) s;
  if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING ||
      session->session_state == SSH_SESSION_STATE_ERROR)
    return 1;
  else
    return 0;
}

/**
 * @internal
 *
 * @brief Send a global request (needed for forward listening) and wait for the
 * result.
 *
 * @param[in]  session  The SSH session handle.
 *
 * @param[in]  request  The type of request (defined in RFC).
 *
 * @param[in]  buffer   Additional data to put in packet.
 *
 * @param[in]  reply    Set if you expect a reply from server.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 */
int ssh_global_request(ssh_session session,
                       const char *request,
                       ssh_buffer buffer,
                       int reply)
{
  int rc;

  switch (session->global_req_state) {
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }

  rc = ssh_buffer_pack(session->out_buffer,
                       "bsb",
                       SSH2_MSG_GLOBAL_REQUEST,
                       request,
                       reply == 0 ? 0 : 1);
  if (rc != SSH_OK){
      ssh_set_error_oom(session);
      rc = SSH_ERROR;
      goto error;
  }

  if (buffer != NULL) {
      rc = ssh_buffer_add_data(session->out_buffer,
                           ssh_buffer_get(buffer),
                           ssh_buffer_get_len(buffer));
      if (rc < 0) {
          ssh_set_error_oom(session);
          rc = SSH_ERROR;
          goto error;
      }
  }

  session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING;
  rc = ssh_packet_send(session);
  if (rc == SSH_ERROR) {
      return rc;
  }

  SSH_LOG(SSH_LOG_PACKET,
      "Sent a SSH_MSG_GLOBAL_REQUEST %s", request);

  if (reply == 0) {
      session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE;

      return SSH_OK;
  }
pending:
  rc = ssh_handle_packets_termination(session,
                                      SSH_TIMEOUT_DEFAULT,
                                      ssh_global_request_termination,
                                      session);

  if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){
    session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR;
  }
  switch(session->global_req_state){
    case SSH_CHANNEL_REQ_STATE_ACCEPTED:
      SSH_LOG(SSH_LOG_DEBUG, "Global request %s success",request);
      rc=SSH_OK;
      break;
    case SSH_CHANNEL_REQ_STATE_DENIED:
      SSH_LOG(SSH_LOG_PACKET,
          "Global request %s failed", request);
      ssh_set_error(session, SSH_REQUEST_DENIED,
          "Global request %s failed", request);
      rc=SSH_ERROR;
      break;
    case SSH_CHANNEL_REQ_STATE_ERROR:
    case SSH_CHANNEL_REQ_STATE_NONE:
      rc = SSH_ERROR;
      break;
    case SSH_CHANNEL_REQ_STATE_PENDING:
      return SSH_AGAIN;
  }
  session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE;

  return rc;
error:
  ssh_buffer_reinit(session->out_buffer);

  return rc;
}

/**
 * @brief Sends the "tcpip-forward" global request to ask the server to begin
 * listening for inbound connections.
 *
 * @param[in]  session  The ssh session to send the request.
 *
 * @param[in]  address  The address to bind to on the server. Pass NULL to bind
 *                      to all available addresses on all protocol families
 *                      supported by the server.
 *
 * @param[in]  port     The port to bind to on the server. Pass 0 to ask the
 *                      server to allocate the next available unprivileged port
 *                      number
 *
 * @param[in]  bound_port The pointer to get actual bound port. Pass NULL to
 *                        ignore.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 **/
int ssh_channel_listen_forward(ssh_session session,
                               const char *address,
                               int port,
                               int *bound_port)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE)
    goto pending;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer,
                       "sd",
                       address ? address : "",
                       port);
  if (rc != SSH_OK){
    ssh_set_error_oom(session);
    goto error;
  }
pending:
  rc = ssh_global_request(session, "tcpip-forward", buffer, 1);

  /* TODO: FIXME no guarantee the last packet we received contains
   * that info */
  if (rc == SSH_OK && port == 0 && bound_port != NULL) {
    rc = ssh_buffer_unpack(session->in_buffer, "d", bound_port);
    if (rc != SSH_OK)
        *bound_port = 0;
  }

error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

/* DEPRECATED */
int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port)
{
  return ssh_channel_listen_forward(session, address, port, bound_port);
}

/* DEPRECATED */
ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms)
{
    return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL);
}

/**
 * @brief Accept an incoming TCP/IP forwarding channel and get some information
 * about incoming connection
 *
 * @param[in]  session    The ssh session to use.
 *
 * @param[in]  timeout_ms A timeout in milliseconds.
 *
 * @param[in]  destination_port A pointer to destination port or NULL.
 *
 * @return Newly created channel, or NULL if no incoming channel request from
 *         the server
 */
ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) {
  return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL);
}

/**
 * @brief Accept an incoming TCP/IP forwarding channel and get information
 * about incoming connection
 *
 * @param[in]  session    The ssh session to use.
 *
 * @param[in]  timeout_ms A timeout in milliseconds.
 *
 * @param[out]  destination_port A pointer to destination port or NULL.
 *
 * @param[out]  originator A pointer to a pointer to a string of originator host or NULL.
 *              That the caller is responsible for to ssh_string_free_char().
 *
 * @param[out]  originator_port A pointer to originator port or NULL.
 *
 * @return Newly created channel, or NULL if no incoming channel request from
 *         the server
 *
 * @see ssh_string_free_char()
 */
ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) {
  return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port);
}

/**
 * @brief Sends the "cancel-tcpip-forward" global request to ask the server to
 *        cancel the tcpip-forward request.
 *
 * @param[in]  session  The ssh session to send the request.
 *
 * @param[in]  address  The bound address on the server.
 *
 * @param[in]  port     The bound port on the server.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 */
int ssh_channel_cancel_forward(ssh_session session,
                               const char *address,
                               int port)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE)
    goto pending;

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer, "sd",
                       address ? address : "",
                       port);
  if (rc != SSH_OK){
      ssh_set_error_oom(session);
      goto error;
  }
pending:
  rc = ssh_global_request(session, "cancel-tcpip-forward", buffer, 1);

error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

/* DEPRECATED */
int ssh_forward_cancel(ssh_session session, const char *address, int port)
{
    return ssh_channel_cancel_forward(session, address, port);
}

/**
 * @brief Set environment variables.
 *
 * @param[in]  channel  The channel to set the environment variables.
 *
 * @param[in]  name     The name of the variable.
 *
 * @param[in]  value    The value to set.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 * @warning Some environment variables may be refused by security reasons.
 */
int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return SSH_ERROR;
  }
  if(name == NULL || value == NULL) {
      ssh_set_error_invalid(channel->session);
      return rc;
  }
  switch(channel->request_state){
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }
  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer,
                       "ss",
                       name,
                       value);
  if (rc != SSH_OK){
    ssh_set_error_oom(channel->session);
    goto error;
  }
pending:
  rc = channel_request(channel, "env", buffer,1);
error:
  SSH_BUFFER_FREE(buffer);

  return rc;
}

/**
 * @brief Run a shell command without an interactive shell.
 *
 * This is similar to 'sh -c command'.
 *
 * @param[in]  channel  The channel to execute the command.
 *
 * @param[in]  cmd      The command to execute
 *                      (e.g. "ls ~/ -al | grep -i reports").
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * Example:
@code
   rc = ssh_channel_request_exec(channel, "ps aux");
   if (rc > 0) {
       return -1;
   }

   while ((rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) {
       if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) {
           return -1;
       }
   }
@endcode
 *
 * @warning In a single channel, only ONE command can be executed!
 * If you want to executed multiple commands, allocate separate channels for
 * them or consider opening interactive shell.
 * Attempting to run multiple consecutive commands in one channel will fail.
 * See RFC 4254 Section 6.5.
 *
 * @see ssh_channel_request_shell()
 */
int ssh_channel_request_exec(ssh_channel channel, const char *cmd)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return SSH_ERROR;
  }
  if(cmd == NULL) {
      ssh_set_error_invalid(channel->session);
      return rc;
  }

  switch(channel->request_state){
  case SSH_CHANNEL_REQ_STATE_NONE:
    break;
  default:
    goto pending;
  }
  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer, "s", cmd);

  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }
pending:
  rc = channel_request(channel, "exec", buffer, 1);
error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

/**
 * @brief Send a signal to remote process (as described in RFC 4254,
 * section 6.9).
 *
 * Sends a signal 'sig' to the remote process.
 * Note, that remote system may not support signals concept.
 * In such a case this request will be silently ignored.
 *
 * @param[in]  channel  The channel to send signal.
 *
 * @param[in]  sig      The signal to send (without SIG prefix)
 *                      \n\n
 *                      SIGABRT  -> ABRT \n
 *                      SIGALRM  -> ALRM \n
 *                      SIGFPE   -> FPE  \n
 *                      SIGHUP   -> HUP  \n
 *                      SIGILL   -> ILL  \n
 *                      SIGINT   -> INT  \n
 *                      SIGKILL  -> KILL \n
 *                      SIGPIPE  -> PIPE \n
 *                      SIGQUIT  -> QUIT \n
 *                      SIGSEGV  -> SEGV \n
 *                      SIGTERM  -> TERM \n
 *                      SIGUSR1  -> USR1 \n
 *                      SIGUSR2  -> USR2 \n
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred.
 */
int ssh_channel_request_send_signal(ssh_channel channel, const char *sig)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if (channel == NULL) {
      return SSH_ERROR;
  }
  if (sig == NULL) {
      ssh_set_error_invalid(channel->session);
      return rc;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer, "s", sig);
  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = channel_request(channel, "signal", buffer, 0);
error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

/**
 * @brief Send a break signal to the server (as described in RFC 4335).
 *
 * Sends a break signal to the remote process.
 * Note, that remote system may not support breaks.
 * In such a case this request will be silently ignored.
 *
 * @param[in]  channel  The channel to send the break to.
 *
 * @param[in]  length   The break-length in milliseconds to send.
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred
 */
int ssh_channel_request_send_break(ssh_channel channel, uint32_t length)
{
    ssh_buffer buffer = NULL;
    int rc = SSH_ERROR;

    if (channel == NULL) {
        return SSH_ERROR;
    }

    buffer = ssh_buffer_new();
    if (buffer == NULL) {
        ssh_set_error_oom(channel->session);
        goto error;
    }

    rc = ssh_buffer_pack(buffer, "d", length);
    if (rc != SSH_OK) {
        ssh_set_error_oom(channel->session);
        goto error;
    }

    rc = channel_request(channel, "break", buffer, 0);

error:
    SSH_BUFFER_FREE(buffer);
    return rc;
}

/**
 * @brief Read data from a channel into a buffer.
 *
 * @param[in]  channel  The channel to read from.
 *
 * @param[out]  buffer   The buffer which will get the data.
 *
 * @param[in]  count    The count of bytes to be read. If it is bigger than 0,
 * the exact size will be read, else (bytes=0) it will return once anything is
 * available.
 *
 * @param is_stderr     A boolean value to mark reading from the stderr stream.
 *
 * @return              The number of bytes read, 0 on end of file, `SSH_AGAIN`
 * on timeout and `SSH_ERROR` on error.
 *
 * @deprecated          Please use ssh_channel_read instead
 * @warning             This function doesn't work in nonblocking/timeout mode
 * @see ssh_channel_read
 */
int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count,
    int is_stderr)
{
    ssh_session session = NULL;
    char *buffer_tmp = NULL;
    int r;
    uint32_t total = 0;

    if (channel == NULL) {
        return SSH_ERROR;
    }
    session = channel->session;

    if (buffer == NULL) {
        ssh_set_error_invalid(channel->session);
        return SSH_ERROR;
    }

    ssh_buffer_reinit(buffer);
    if (count == 0) {
        do {
            r = ssh_channel_poll(channel, is_stderr);
            if (r < 0) {
                return r;
            }
            if (r > 0) {
                count = r;
                buffer_tmp = ssh_buffer_allocate(buffer, count);
                if (buffer_tmp == NULL) {
                    ssh_set_error_oom(session);
                    return SSH_ERROR;
                }
                r = ssh_channel_read(channel, buffer_tmp, r, is_stderr);
                if (r < 0) {
                    ssh_buffer_pass_bytes_end(buffer, count);
                    return r;
                }
                /* Rollback the unused space */
                ssh_buffer_pass_bytes_end(buffer, count - r);

                return r;
            }
            if (ssh_channel_is_eof(channel)) {
                return 0;
            }
            ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE);
        } while (r == 0);
    }

    buffer_tmp = ssh_buffer_allocate(buffer, count);
    if (buffer_tmp == NULL) {
        ssh_set_error_oom(session);
        return SSH_ERROR;
    }
    while (total < count) {
        r = ssh_channel_read(channel, buffer_tmp, count - total, is_stderr);
        if (r < 0) {
            ssh_buffer_pass_bytes_end(buffer, count);
            return r;
        }
        if (r == 0) {
            /* Rollback the unused space */
            ssh_buffer_pass_bytes_end(buffer, count - total);
            return total;
        }
        total += r;
    }

    return total;
}

struct ssh_channel_read_termination_struct {
  ssh_channel channel;
  ssh_buffer buffer;
};

static int ssh_channel_read_termination(void *s)
{
  struct ssh_channel_read_termination_struct *ctx = s;
  if (ssh_buffer_get_len(ctx->buffer) >= 1 ||
      ctx->channel->remote_eof ||
      ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR)
    return 1;
  else
    return 0;
}

/**
 * @brief Reads data from a channel.
 *
 * @param[in]  channel  The channel to read from.
 *
 * @param[out] dest     The destination buffer which will get the data.
 *
 * @param[in]  count    The count of bytes to be read.
 *
 * @param[in]  is_stderr A boolean value to mark reading from the stderr flow.
 *
 * @return              The number of bytes read, 0 on end of file, `SSH_AGAIN`
 * on timeout and `SSH_ERROR` on error.
 *
 * @warning This function may return less than count bytes of data, and won't
 * block until count bytes have been read.
 */
int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr)
{
    return ssh_channel_read_timeout(channel,
                                    dest,
                                    count,
                                    is_stderr,
                                    SSH_TIMEOUT_DEFAULT);
}

/**
 * @brief Reads data from a channel.
 *
 * @param[in]  channel     The channel to read from.
 *
 * @param[out] dest        The destination buffer which will get the data.
 *
 * @param[in]  count       The count of bytes to be read.
 *
 * @param[in]  is_stderr   A boolean value to mark reading from the stderr flow.
 *
 * @param[in]  timeout_ms  A timeout in milliseconds. A value of -1 means
 *                         infinite timeout.
 *
 * @return              The number of bytes read, 0 on end of file, `SSH_AGAIN`
 * on timeout, `SSH_ERROR` on error.
 *
 * @warning This function may return less than count bytes of data, and won't
 *          block until count bytes have been read.
 */
int ssh_channel_read_timeout(ssh_channel channel,
                             void *dest,
                             uint32_t count,
                             int is_stderr,
                             int timeout_ms)
{
    ssh_session session = NULL;
    ssh_buffer stdbuf = NULL;
    uint32_t len;
    struct ssh_channel_read_termination_struct ctx;
    int rc;

    if (channel == NULL) {
        return SSH_ERROR;
    }
    if (dest == NULL) {
        ssh_set_error_invalid(channel->session);
        return SSH_ERROR;
    }

    session = channel->session;
    stdbuf = channel->stdout_buffer;

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

    if (is_stderr) {
        stdbuf = channel->stderr_buffer;
    }

    SSH_LOG(SSH_LOG_PACKET,
            "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32,
            count,
            ssh_buffer_get_len(stdbuf),
            channel->local_window);

    /* block reading until at least one byte has been read
     * and ignore the trivial case count=0
     */
    ctx.channel = channel;
    ctx.buffer = stdbuf;

    if (timeout_ms < SSH_TIMEOUT_DEFAULT) {
        timeout_ms = SSH_TIMEOUT_INFINITE;
    }

    rc = ssh_handle_packets_termination(session,
                                        timeout_ms,
                                        ssh_channel_read_termination,
                                        &ctx);
    if (rc == SSH_ERROR || rc == SSH_AGAIN) {
        return rc;
    }

    /*
     * If the channel is closed or in an error state, reading from it is an
     * error
     */
    if (session->session_state == SSH_SESSION_STATE_ERROR) {
        return SSH_ERROR;
    }
    /* If the server closed the channel properly, there is nothing to do */
    if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) {
        return 0;
    }
    if (channel->state == SSH_CHANNEL_STATE_CLOSED) {
        ssh_set_error(session, SSH_FATAL, "Remote channel is closed.");
        return SSH_ERROR;
    }
    len = ssh_buffer_get_len(stdbuf);
    /* Read count bytes if len is greater, everything otherwise */
    len = (len > count ? count : len);
    memcpy(dest, ssh_buffer_get(stdbuf), len);
    ssh_buffer_pass_bytes(stdbuf, len);
    if (channel->counter != NULL) {
        channel->counter->in_bytes += len;
    }
    /* Try completing the delayed_close */
    if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) {
        channel->state = SSH_CHANNEL_STATE_CLOSED;
    }

    rc = grow_window(session, channel);
    if (rc == SSH_ERROR) {
        return -1;
    }

    return len;
}

/**
 * @brief Do a nonblocking read on the channel.
 *
 * A nonblocking read on the specified channel. it will return <= count bytes of
 * data read atomically. It will also trigger any callbacks set on the channel.
 *
 * @param[in]  channel  The channel to read from.
 *
 * @param[out] dest     A pointer to a destination buffer.
 *
 * @param[in]  count    The count of bytes of data to be read.
 *
 * @param[in]  is_stderr A boolean to select the stderr stream.
 *
 * @return              The number of bytes read, `SSH_AGAIN` if nothing is
 * available, `SSH_ERROR` on error, and `SSH_EOF` if the channel is EOF.
 *
 * @see ssh_channel_is_eof()
 */
int ssh_channel_read_nonblocking(ssh_channel channel,
                                 void *dest,
                                 uint32_t count,
                                 int is_stderr)
{
    ssh_session session = NULL;
    uint32_t to_read;
    int rc;
    int blocking;

    if(channel == NULL) {
        return SSH_ERROR;
    }
    if(dest == NULL) {
        ssh_set_error_invalid(channel->session);
        return SSH_ERROR;
    }

    session = channel->session;

    rc = ssh_channel_poll(channel, is_stderr);

    if (rc <= 0) {
        if (session->session_state == SSH_SESSION_STATE_ERROR){
            return SSH_ERROR;
        }

        return rc; /* may be an error code */
    }

    to_read = (unsigned int)rc;

    if (to_read > count) {
        to_read = count;
    }
    blocking = ssh_is_blocking(session);
    ssh_set_blocking(session, 0);
    rc = ssh_channel_read(channel, dest, to_read, is_stderr);
    ssh_set_blocking(session,blocking);

    return rc;
}

/**
 * @brief Polls a channel for data to read.
 *
 * If callbacks are set on the channel, they will be called.
 *
 * @param[in]  channel  The channel to poll.
 *
 * @param[in]  is_stderr A boolean to select the stderr stream.
 *
 * @return              The number of bytes available for reading, 0 if nothing
 *                      is available or `SSH_ERROR` on error.
 *                      When a channel is freed the function returns
 *                      `SSH_ERROR` immediately.
 *
 * @warning When the channel is in EOF state, the function returns `SSH_EOF`.
 *
 * @see ssh_channel_is_eof()
 */
int ssh_channel_poll(ssh_channel channel, int is_stderr)
{
  ssh_buffer stdbuf;

  if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
      return SSH_ERROR;
  }

  stdbuf = channel->stdout_buffer;

  if (is_stderr) {
    stdbuf = channel->stderr_buffer;
  }

  if (channel->remote_eof == 0) {
    if (channel->session->session_state == SSH_SESSION_STATE_ERROR){
      return SSH_ERROR;
    }
    if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) {
      return SSH_ERROR;
    }
  }

  if (ssh_buffer_get_len(stdbuf) > 0){
  	return ssh_buffer_get_len(stdbuf);
  }

  if (channel->remote_eof) {
    return SSH_EOF;
  }

  return ssh_buffer_get_len(stdbuf);
}

/**
 * @brief Polls a channel for data to read, waiting for a certain timeout.
 *
 * @param[in]  channel   The channel to poll.
 * @param[in]  timeout   Set an upper limit on the time for which this function
 *                       will block, in milliseconds. Specifying a negative
 *                       value means an infinite timeout. This parameter is
 *                       passed to the poll() function.
 * @param[in]  is_stderr A boolean to select the stderr stream.
 *
 * @return              The number of bytes available for reading,
 *                      0 if nothing is available (timeout elapsed),
 *                      `SSH_EOF` on end of file,
 *                      `SSH_ERROR` on error.
 *
 * @warning When the channel is in EOF state, the function returns `SSH_EOF`.
 *          When a channel is freed the function returns `SSH_ERROR`
 *          immediately.
 *
 * @see ssh_channel_is_eof()
 */
int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr)
{
    ssh_session session = NULL;
    ssh_buffer stdbuf = NULL;
    struct ssh_channel_read_termination_struct ctx;
    size_t len;
    int rc;

    if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
        return SSH_ERROR;
    }

    session = channel->session;
    stdbuf = channel->stdout_buffer;

    if (is_stderr) {
        stdbuf = channel->stderr_buffer;
    }
    ctx.buffer = stdbuf;
    ctx.channel = channel;
    rc = ssh_handle_packets_termination(channel->session,
                                        timeout,
                                        ssh_channel_read_termination,
                                        &ctx);
    if (rc == SSH_ERROR ||
        session->session_state == SSH_SESSION_STATE_ERROR) {
        rc = SSH_ERROR;
        goto out;
    } else if (rc == SSH_AGAIN) {
        /* If the above timeout expired, it is ok and we do not need to
         * attempt to check the read buffer. The calling functions do not
         * expect us to return SSH_AGAIN either here. */
        rc = SSH_OK;
        goto out;
    }
    len = ssh_buffer_get_len(stdbuf);
    if (len > 0) {
        if (len > INT_MAX) {
            rc = SSH_ERROR;
        } else {
            rc = (int)len;
        }
        goto out;
    }
    if (channel->remote_eof) {
        rc = SSH_EOF;
    }

out:
    return rc;
}

/**
 * @brief Recover the session in which belongs a channel.
 *
 * @param[in]  channel  The channel to recover the session from.
 *
 * @return              The session pointer.
 */
ssh_session ssh_channel_get_session(ssh_channel channel)
{
  if (channel == NULL) {
      return NULL;
  }

  return channel->session;
}

static int ssh_channel_exit_status_termination(void *c)
{
    ssh_channel channel = c;
    if (channel->exit.status ||
        /* When a channel is closed, no exit status message can
         * come anymore */
        (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) ||
        channel->session->session_state == SSH_SESSION_STATE_ERROR)
    {
        return 1;
    }
    return 0;
}

/**
 * @brief Get the exit state of the channel (error code from the executed
 *        instruction or signal).
 *
 * @param[in]  channel  The channel to get the status from.
 *
 * @param[out] pexit_code   A pointer to an uint32_t to store the exit status.
 *
 * @param[out] pexit_signal A pointer to store the exit signal as a string.
 *                         The signal is without the SIG prefix, e.g. "TERM" or
 *                         "KILL"). The caller has to free the memory.
 *
 * @param[out] pcore_dumped A pointer to store a boolean value if it dumped a
 *                          core.
 *
 * @return              `SSH_OK` on success, `SSH_AGAIN` if we don't have a
 *                      status or an SSH error.
 * @warning             This function may block until a timeout (or never)
 *                      if the other side is not willing to close the channel.
 *                      When a channel is freed the function returns
 *                      `SSH_ERROR` immediately.
 *
 * If you're looking for an async handling of this register a callback for the
 * exit status!
 *
 * @see ssh_channel_exit_status_callback
 * @see ssh_channel_exit_signal_callback
 */
int ssh_channel_get_exit_state(ssh_channel channel,
                               uint32_t *pexit_code,
                               char **pexit_signal,
                               int *pcore_dumped)
{
    ssh_session session = NULL;
    int rc;

    if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) {
        return SSH_ERROR;
    }
    session = channel->session;

    rc = ssh_handle_packets_termination(channel->session,
                                        SSH_TIMEOUT_DEFAULT,
                                        ssh_channel_exit_status_termination,
                                        channel);
    if (rc == SSH_ERROR || channel->session->session_state ==
        SSH_SESSION_STATE_ERROR) {
        return SSH_ERROR;
    }

    /* If we don't have any kind of exit state, return SSH_AGAIN */
    if (!channel->exit.status) {
        return SSH_AGAIN;
    }

    if (pexit_code != NULL) {
        *pexit_code = channel->exit.code;
    }

    if (pexit_signal != NULL) {
        *pexit_signal = NULL;
        if (channel->exit.signal != NULL) {
            *pexit_signal = strdup(channel->exit.signal);
            if (pexit_signal == NULL) {
                ssh_set_error_oom(session);
                return SSH_ERROR;
            }
        }
    }

    if (pcore_dumped != NULL) {
        *pcore_dumped = channel->exit.core_dumped;
    }

    return SSH_OK;
}

/**
 * @brief Get the exit status of the channel (error code from the executed
 *        instruction).
 *
 * @param[in]  channel  The channel to get the status from.
 *
 * @return              The exit status, -1 if no exit status has been returned
 *                      (yet), or `SSH_ERROR` on error.
 * @warning             This function may block until a timeout (or never)
 *                      if the other side is not willing to close the channel.
 *                      When a channel is freed the function returns
 *                      `SSH_ERROR` immediately.
 *
 * If you're looking for an async handling of this register a callback for the
 * exit status.
 *
 * @see ssh_channel_exit_status_callback
 * @deprecated Please use ssh_channel_exit_state()
 */
int ssh_channel_get_exit_status(ssh_channel channel)
{
    uint32_t exit_status = (uint32_t)-1;
    int rc;

    rc = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL);
    if (rc != SSH_OK) {
        return SSH_ERROR;
    }

    return exit_status;
}

/*
 * This function acts as a meta select.
 *
 * First, channels are analyzed to seek potential can-write or can-read ones,
 * then if no channel has been elected, it goes in a loop with the posix
 * select(2).
 * This is made in two parts: protocol select and network select. The protocol
 * select does not use the network functions at all
 */
static int
channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans,
                        ssh_channel *echans, ssh_channel *rout,
                        ssh_channel *wout, ssh_channel *eout)
{
    ssh_channel chan = NULL;
    int i;
    int j = 0;

    for (i = 0; rchans[i] != NULL; i++) {
        chan = rchans[i];

        while (ssh_channel_is_open(chan) &&
               ssh_socket_data_available(chan->session->socket)) {
            ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING);
        }

        if ((chan->stdout_buffer &&
             ssh_buffer_get_len(chan->stdout_buffer) > 0) ||
            (chan->stderr_buffer &&
             ssh_buffer_get_len(chan->stderr_buffer) > 0) ||
            chan->remote_eof) {
            rout[j] = chan;
            j++;
        }
    }
    rout[j] = NULL;

    j = 0;
    for (i = 0; wchans[i] != NULL; i++) {
        chan = wchans[i];
        /* It's not our business to seek if the file descriptor is writable */
        if (ssh_socket_data_writable(chan->session->socket) &&
            ssh_channel_is_open(chan) && (chan->remote_window > 0)) {
            wout[j] = chan;
            j++;
        }
    }
    wout[j] = NULL;

    j = 0;
    for (i = 0; echans[i] != NULL; i++) {
        chan = echans[i];

        if (!ssh_socket_is_open(chan->session->socket) ||
            ssh_channel_is_closed(chan)) {
            eout[j] = chan;
            j++;
        }
    }
    eout[j] = NULL;

    return 0;
}

/* Just count number of pointers in the array */
static size_t count_ptrs(ssh_channel *ptrs)
{
  size_t c;
  for (c = 0; ptrs[c] != NULL; c++)
    ;

  return c;
}

/**
 * @brief Act like the standard select(2) on channels.
 *
 * The list of pointers are then actualized and will only contain pointers to
 * channels that are respectively readable, writable or have an exception to
 * trap.
 *
 * @param[in]  readchans A NULL pointer or an array of channel pointers,
 *                       terminated by a NULL.
 *
 * @param[in]  writechans A NULL pointer or an array of channel pointers,
 *                        terminated by a NULL.
 *
 * @param[in]  exceptchans A NULL pointer or an array of channel pointers,
 *                         terminated by a NULL.
 *
 * @param[in]  timeout  Timeout as defined by select(2).
 *
 * @return             `SSH_OK` on a successful operation, `SSH_EINTR` if the
 *                     select(2) syscall was interrupted, then relaunch the
 *                     function, or `SSH_ERROR` on error.
 */
int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans,
                       ssh_channel *exceptchans, struct timeval * timeout)
{
    ssh_channel *rchans = NULL, *wchans = NULL, *echans = NULL;
    ssh_channel dummy = NULL;
    ssh_event event = NULL;
    int rc;
    int i;
    int tm, tm_base;
    int firstround = 1;
    struct ssh_timestamp ts;

    if (timeout != NULL)
        tm_base = timeout->tv_sec * 1000 + timeout->tv_usec / 1000;
    else
        tm_base = SSH_TIMEOUT_INFINITE;
    ssh_timestamp_init(&ts);
    tm = tm_base;
    /* don't allow NULL pointers */
    if (readchans == NULL) {
        readchans = &dummy;
    }

    if (writechans == NULL) {
        writechans = &dummy;
    }

    if (exceptchans == NULL) {
        exceptchans = &dummy;
    }

    if (readchans[0] == NULL && writechans[0] == NULL &&
        exceptchans[0] == NULL) {
        /* No channel to poll?? Go away! */
        return 0;
    }

    /* Prepare the outgoing temporary arrays */
    rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel));
    if (rchans == NULL) {
        return SSH_ERROR;
    }

    wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel));
    if (wchans == NULL) {
        SAFE_FREE(rchans);
        return SSH_ERROR;
    }

    echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel));
    if (echans == NULL) {
        SAFE_FREE(rchans);
        SAFE_FREE(wchans);
        return SSH_ERROR;
    }

    /*
     * First, try without doing network stuff then, use the ssh_poll
     * infrastructure to poll on all sessions.
     */
    do {
        channel_protocol_select(readchans,
                                writechans,
                                exceptchans,
                                rchans,
                                wchans,
                                echans);
        if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) {
            /* At least one channel has an event */
            break;
        }
        /* Add all channels' sessions right into an event object */
        if (event == NULL) {
            event = ssh_event_new();
            if (event == NULL) {
                SAFE_FREE(rchans);
                SAFE_FREE(wchans);
                SAFE_FREE(echans);

                return SSH_ERROR;
            }
            for (i = 0; readchans[i] != NULL; i++) {
                ssh_poll_get_default_ctx(readchans[i]->session);
                ssh_event_add_session(event, readchans[i]->session);
            }
            for (i = 0; writechans[i] != NULL; i++) {
                ssh_poll_get_default_ctx(writechans[i]->session);
                ssh_event_add_session(event, writechans[i]->session);
            }
            for (i = 0; exceptchans[i] != NULL; i++) {
                ssh_poll_get_default_ctx(exceptchans[i]->session);
                ssh_event_add_session(event, exceptchans[i]->session);
            }
        }
        /* Get out if the timeout has elapsed */
        if (!firstround && ssh_timeout_elapsed(&ts, tm_base)) {
            break;
        }
        /* Here we go */
        rc = ssh_event_dopoll(event, tm);
        if (rc != SSH_OK) {
            SAFE_FREE(rchans);
            SAFE_FREE(wchans);
            SAFE_FREE(echans);
            ssh_event_free(event);
            return rc;
        }
        tm = ssh_timeout_update(&ts, tm_base);
        firstround = 0;
    } while (1);

    if (readchans != &dummy) {
        memcpy(readchans,
               rchans,
               (count_ptrs(rchans) + 1) * sizeof(ssh_channel));
    }
    if (writechans != &dummy) {
        memcpy(writechans,
               wchans,
               (count_ptrs(wchans) + 1) * sizeof(ssh_channel));
    }
    if (exceptchans != &dummy) {
        memcpy(exceptchans,
               echans,
               (count_ptrs(echans) + 1) * sizeof(ssh_channel));
    }
    SAFE_FREE(rchans);
    SAFE_FREE(wchans);
    SAFE_FREE(echans);
    if (event)
        ssh_event_free(event);
    return 0;
}

/**
 * @brief Set the channel data counter.
 *
 * @code
 * struct ssh_counter_struct counter = {
 *     .in_bytes = 0,
 *     .out_bytes = 0,
 *     .in_packets = 0,
 *     .out_packets = 0
 * };
 *
 * ssh_channel_set_counter(channel, &counter);
 * @endcode
 *
 * @param[in] channel The SSH channel.
 *
 * @param[in] counter Counter for bytes handled by the channel.
 */
void ssh_channel_set_counter(ssh_channel channel,
                             ssh_counter counter)
{
    if (channel != NULL) {
        channel->counter = counter;
    }
}

/**
 * @brief Blocking write on a channel stderr.
 *
 * @param[in]  channel  The channel to write to.
 *
 * @param[in]  data     A pointer to the data to write.
 *
 * @param[in]  len      The length of the buffer to write to.
 *
 * @return              The number of bytes written, SSH_ERROR on error.
 *
 * @see ssh_channel_read()
 */
int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len)
{
    return channel_write_common(channel, data, len, 1);
}

#if WITH_SERVER

/**
 * @brief Open a TCP/IP reverse forwarding channel.
 *
 * @param[in]  channel  An allocated channel.
 *
 * @param[in]  remotehost The remote host to connected (host name or IP).
 *
 * @param[in]  remoteport The remote port.
 *
 * @param[in]  sourcehost The source host (your local computer). It's optional
 *                        and for logging purpose.
 *
 * @param[in]  localport  The source port (your local computer). It's optional
 *                        and for logging purpose.
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 *
 * @warning This function does not bind the local port and does not
 *          automatically forward the content of a socket to the channel. You
 *          still have to use ssh_channel_read and ssh_channel_write for this.
 */
int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost,
                                     int remoteport, const char *sourcehost, int localport)
{
    ssh_session session = NULL;
    ssh_buffer payload = NULL;
    int rc = SSH_ERROR;

    if (channel == NULL) {
        return rc;
    }
    if (remotehost == NULL || sourcehost == NULL) {
        ssh_set_error_invalid(channel->session);
        return rc;
    }

    session = channel->session;

    if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN)
        goto pending;
    payload = ssh_buffer_new();
    if (payload == NULL) {
        ssh_set_error_oom(session);
        goto error;
    }
    rc = ssh_buffer_pack(payload,
                         "sdsd",
                         remotehost,
                         remoteport,
                         sourcehost,
                         localport);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }
pending:
    rc = channel_open(channel,
                      "forwarded-tcpip",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      payload);

error:
    SSH_BUFFER_FREE(payload);

    return rc;
}

/**
 * @brief Open a X11 channel.
 *
 * @param[in]  channel      An allocated channel.
 *
 * @param[in]  orig_addr    The source host (the local server).
 *
 * @param[in]  orig_port    The source port (the local server).
 *
 * @return              `SSH_OK` on success,
 *                      `SSH_ERROR` if an error occurred,
 *                      `SSH_AGAIN` if in nonblocking mode and call has
 *                      to be done again.
 * @warning This function does not bind the local port and does not
 *          automatically forward the content of a socket to the channel. You
 *          still have to use shh_channel_read and ssh_channel_write for this.
 */
int ssh_channel_open_x11(ssh_channel channel,
                         const char *orig_addr, int orig_port)
{
    ssh_session session = NULL;
    ssh_buffer payload = NULL;
    int rc = SSH_ERROR;

    if (channel == NULL) {
        return rc;
    }
    if (orig_addr == NULL) {
        ssh_set_error_invalid(channel->session);
        return rc;
    }
    session = channel->session;

    if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN)
        goto pending;

    payload = ssh_buffer_new();
    if (payload == NULL) {
        ssh_set_error_oom(session);
        goto error;
    }

    rc = ssh_buffer_pack(payload, "sd", orig_addr, orig_port);
    if (rc != SSH_OK) {
        ssh_set_error_oom(session);
        goto error;
    }
pending:
    rc = channel_open(channel,
                      "x11",
                      WINDOW_DEFAULT,
                      CHANNEL_MAX_PACKET,
                      payload);

error:
    SSH_BUFFER_FREE(payload);

    return rc;
}

/**
 * @brief Send the exit status to the remote process
 *
 * Sends the exit status to the remote process (as described in RFC 4254,
 * section 6.10).
 *
 * @param[in]  channel  The channel to send exit status.
 *
 * @param[in]  exit_status  The exit status to send
 *
 * @return     `SSH_OK` on success, `SSH_ERROR` if an error occurred.
 */
int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return SSH_ERROR;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer, "d", exit_status);
  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = channel_request(channel, "exit-status", buffer, 0);
error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

/**
 * @brief Send an exit signal to remote process (RFC 4254, section 6.10).
 *
 * This sends the exit status of the remote process.
 * Note, that remote system may not support signals concept.
 * In such a case this request will be silently ignored.
 *
 * @param[in]  channel  The channel to send signal.
 *
 * @param[in]  sig      The signal to send (without SIG prefix)
 *                      (e.g. "TERM" or "KILL").
 * @param[in]  core     A boolean to tell if a core was dumped
 * @param[in]  errmsg   A CRLF explanation text about the error condition
 * @param[in]  lang     The language used in the message (format: RFC 3066)
 *
 * @return              `SSH_OK` on success, `SSH_ERROR` if an error occurred
 */
int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig,
                                         int core, const char *errmsg, const char *lang)
{
  ssh_buffer buffer = NULL;
  int rc = SSH_ERROR;

  if(channel == NULL) {
      return rc;
  }
  if(sig == NULL || errmsg == NULL || lang == NULL) {
      ssh_set_error_invalid(channel->session);
      return rc;
  }

  buffer = ssh_buffer_new();
  if (buffer == NULL) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = ssh_buffer_pack(buffer,
                       "sbss",
                       sig,
                       core ? 1 : 0,
                       errmsg,
                       lang);
  if (rc != SSH_OK) {
    ssh_set_error_oom(channel->session);
    goto error;
  }

  rc = channel_request(channel, "exit-signal", buffer, 0);
error:
  SSH_BUFFER_FREE(buffer);
  return rc;
}

#endif

/** @} */
