/* Copyright (C) Sara Golemon <sarag@libssh2.org>
 * Copyright (C) Daniel Stenberg
 * Copyright (C) Simon Josefsson <simon@josefsson.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *   Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 *
 *   Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 *   Neither the name of the copyright holder nor the names
 *   of any other contributors may be used to endorse or
 *   promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "libssh2_priv.h"

#ifdef _WIN32
#include <ws2tcpip.h>  /* for socklen_t */
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif

#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>

#include "transport.h"
#include "session.h"
#include "channel.h"
#include "mac.h"

#if defined(_WIN32)
#define libssh2_usec_t long
#elif defined(__APPLE__)
#define libssh2_usec_t suseconds_t
#else
#undef libssh2_usec_t
#endif

/* libssh2_default_alloc
 */
static
LIBSSH2_ALLOC_FUNC(libssh2_default_alloc)
{
    (void)abstract;
    return malloc(count);
}

/* libssh2_default_free
 */
static
LIBSSH2_FREE_FUNC(libssh2_default_free)
{
    (void)abstract;
    free(ptr);
}

/* libssh2_default_realloc
 */
static
LIBSSH2_REALLOC_FUNC(libssh2_default_realloc)
{
    (void)abstract;
    return realloc(ptr, count);
}

/*
 * banner_receive
 *
 * Wait for a hello from the remote host
 * Allocate a buffer and store the banner in session->remote.banner
 * Returns: 0 on success, LIBSSH2_ERROR_EAGAIN if read would block, negative
 * on failure
 */
static int
banner_receive(LIBSSH2_SESSION * session)
{
    ssize_t ret;
    size_t banner_len;

    if(session->banner_TxRx_state == libssh2_NB_state_idle) {
        banner_len = 0;

        session->banner_TxRx_state = libssh2_NB_state_created;
    }
    else {
        banner_len = session->banner_TxRx_total_send;
    }

    while((banner_len < sizeof(session->banner_TxRx_banner)) &&
          ((banner_len == 0)
           || (session->banner_TxRx_banner[banner_len - 1] != '\n'))) {
        char c = '\0';

        /* no incoming block yet! */
        session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND;

        ret = LIBSSH2_RECV(session, &c, 1,
                           LIBSSH2_SOCKET_RECV_FLAGS(session));
        if(ret < 0) {
            if(session->api_block_mode || (ret != -EAGAIN))
                /* ignore EAGAIN when non-blocking */
                _libssh2_debug((session, LIBSSH2_TRACE_SOCKET,
                               "Error recving %d bytes: %ld", 1, (long)-ret));
        }
        else
            _libssh2_debug((session, LIBSSH2_TRACE_SOCKET,
                           "Recved %ld bytes banner", (long)ret));

        if(ret < 0) {
            if(ret == -EAGAIN) {
                session->socket_block_directions =
                    LIBSSH2_SESSION_BLOCK_INBOUND;
                session->banner_TxRx_total_send = banner_len;
                return LIBSSH2_ERROR_EAGAIN;
            }

            /* Some kinda error */
            session->banner_TxRx_state = libssh2_NB_state_idle;
            session->banner_TxRx_total_send = 0;
            return LIBSSH2_ERROR_SOCKET_RECV;
        }

        if(ret == 0) {
            session->socket_state = LIBSSH2_SOCKET_DISCONNECTED;
            return LIBSSH2_ERROR_SOCKET_DISCONNECT;
        }

        if((c == '\r' || c == '\n') && banner_len == 0) {
            continue;
        }

        if(c == '\0') {
            /* NULLs are not allowed in SSH banners */
            session->banner_TxRx_state = libssh2_NB_state_idle;
            session->banner_TxRx_total_send = 0;
            return LIBSSH2_ERROR_BANNER_RECV;
        }

        session->banner_TxRx_banner[banner_len++] = c;
    }

    while(banner_len &&
          ((session->banner_TxRx_banner[banner_len - 1] == '\n') ||
           (session->banner_TxRx_banner[banner_len - 1] == '\r'))) {
        banner_len--;
    }

    /* From this point on, we are done here */
    session->banner_TxRx_state = libssh2_NB_state_idle;
    session->banner_TxRx_total_send = 0;

    if(!banner_len)
        return LIBSSH2_ERROR_BANNER_RECV;

    if(session->remote.banner)
        LIBSSH2_FREE(session, session->remote.banner);

    session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1);
    if(!session->remote.banner) {
        return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                              "Error allocating space for remote banner");
    }
    memcpy(session->remote.banner, session->banner_TxRx_banner, banner_len);
    session->remote.banner[banner_len] = '\0';
    _libssh2_debug((session, LIBSSH2_TRACE_TRANS, "Received Banner: %s",
                   session->remote.banner));
    return LIBSSH2_ERROR_NONE;
}

/*
 * banner_send
 *
 * Send the default banner, or the one set via libssh2_setopt_string
 *
 * Returns LIBSSH2_ERROR_EAGAIN if it would block - and if it does so, you
 * should call this function again as soon as it is likely that more data can
 * be sent, and this function should then be called with the same argument set
 * (same data pointer and same data_len) until zero or failure is returned.
 */
static int
banner_send(LIBSSH2_SESSION * session)
{
    char *banner = (char *) LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF;
    size_t banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1;
    ssize_t ret;

    if(session->banner_TxRx_state == libssh2_NB_state_idle) {
        if(session->local.banner) {
            /* setopt_string will have given us our \r\n characters */
            banner_len = strlen((char *) session->local.banner);
            banner = (char *) session->local.banner;
        }
#ifdef LIBSSH2DEBUG
        {
            char banner_dup[256];

            /* Hack and slash to avoid sending CRLF in debug output */
            if(banner_len < 256) {
                memcpy(banner_dup, banner, banner_len - 2);
                banner_dup[banner_len - 2] = '\0';
            }
            else {
                memcpy(banner_dup, banner, 255);
                banner_dup[255] = '\0';
            }

            _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                           "Sending Banner: %s", banner_dup));
        }
#endif

        session->banner_TxRx_state = libssh2_NB_state_created;
    }

    /* no outgoing block yet! */
    session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND;

    ret = LIBSSH2_SEND(session,
                       banner + session->banner_TxRx_total_send,
                       banner_len - session->banner_TxRx_total_send,
                       LIBSSH2_SOCKET_SEND_FLAGS(session));
    if(ret < 0)
        _libssh2_debug((session, LIBSSH2_TRACE_SOCKET,
                       "Error sending %ld bytes: %ld",
                       (long)(banner_len - session->banner_TxRx_total_send),
                       (long)-ret));
    else
        _libssh2_debug((session, LIBSSH2_TRACE_SOCKET,
                       "Sent %ld/%ld bytes at %p+%ld", (long)ret,
                       (long)(banner_len - session->banner_TxRx_total_send),
                       (void *)banner, (long)session->banner_TxRx_total_send));

    if(ret != (ssize_t)(banner_len - session->banner_TxRx_total_send)) {
        if(ret >= 0 || ret == -EAGAIN) {
            /* the whole packet could not be sent, save the what was */
            session->socket_block_directions =
                LIBSSH2_SESSION_BLOCK_OUTBOUND;
            if(ret > 0)
                session->banner_TxRx_total_send += ret;
            return LIBSSH2_ERROR_EAGAIN;
        }
        session->banner_TxRx_state = libssh2_NB_state_idle;
        session->banner_TxRx_total_send = 0;
        return LIBSSH2_ERROR_SOCKET_RECV;
    }

    /* Set the state back to idle */
    session->banner_TxRx_state = libssh2_NB_state_idle;
    session->banner_TxRx_total_send = 0;

    return 0;
}

/*
 * session_nonblock() sets the given socket to either blocking or
 * non-blocking mode based on the 'nonblock' boolean argument. This function
 * is copied from the libcurl sources with permission.
 */
static int
session_nonblock(libssh2_socket_t sockfd,   /* operate on this */
                 int nonblock /* TRUE or FALSE */ )
{
#ifdef HAVE_O_NONBLOCK
    /* most recent unix versions */
    int flags;

    flags = fcntl(sockfd, F_GETFL, 0);
    if(nonblock)
        return fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    else
        return fcntl(sockfd, F_SETFL, flags & (~O_NONBLOCK));
#elif defined(HAVE_FIONBIO)
    /* older unix versions and VMS */
    int flags;

    flags = nonblock;
    return ioctl(sockfd, FIONBIO, &flags);
#elif defined(HAVE_IOCTLSOCKET_CASE)
    /* presumably for Amiga */
    return IoctlSocket(sockfd, FIONBIO, (long) nonblock);
#elif defined(HAVE_SO_NONBLOCK)
    /* BeOS */
    long b = nonblock ? 1 : 0;
    return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
#elif defined(_WIN32)
    unsigned long flags;

    flags = nonblock;
    return ioctlsocket(sockfd, FIONBIO, &flags);
#else
    (void)sockfd;
    (void)nonblock;
    return 0;                   /* returns success */
#endif
}

/*
 * get_socket_nonblocking
 *
 * gets the given blocking or non-blocking state of the socket.
 */
static int
get_socket_nonblocking(libssh2_socket_t sockfd)
{                               /* operate on this */
#ifdef HAVE_O_NONBLOCK
    /* most recent unix versions */
    int flags = fcntl(sockfd, F_GETFL, 0);

    if(flags == -1) {
        /* Assume blocking on error */
        return 1;
    }
    return (flags & O_NONBLOCK);
#elif defined(HAVE_SO_NONBLOCK)
    /* BeOS */
    long b;
    if(getsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b))) {
        /* Assume blocking on error */
        return 1;
    }
    return (int) b;
#elif defined(SO_STATE) && defined(__VMS)
    /* VMS TCP/IP Services */

    size_t sockstat = 0;
    int    callstat = 0;
    size_t size = sizeof(int);

    callstat = getsockopt(sockfd, SOL_SOCKET, SO_STATE,
                          (char *)&sockstat, &size);
    if(callstat == -1) {
        return 0;
    }
    if((sockstat&SS_NBIO) != 0) {
        return 1;
    }
    return 0;
#elif defined(_WIN32)
    unsigned int option_value;
    socklen_t option_len = sizeof(option_value);

    if(getsockopt(sockfd, SOL_SOCKET, SO_ERROR,
                  (void *) &option_value, &option_len)) {
        /* Assume blocking on error */
        return 1;
    }
    return (int) option_value;
#else
    (void)sockfd;
    return 1;                   /* returns blocking */
#endif
}

/* libssh2_session_banner_set
 * Set the local banner to use in the server handshake.
 */
LIBSSH2_API int
libssh2_session_banner_set(LIBSSH2_SESSION * session, const char *banner)
{
    size_t banner_len = banner ? strlen(banner) : 0;

    if(session->local.banner) {
        LIBSSH2_FREE(session, session->local.banner);
        session->local.banner = NULL;
    }

    if(!banner_len)
        return 0;

    session->local.banner = LIBSSH2_ALLOC(session, banner_len + 3);
    if(!session->local.banner) {
        return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
                              "Unable to allocate memory for local banner");
    }

    memcpy(session->local.banner, banner, banner_len);

    /* first zero terminate like this so that the debug output is nice */
    session->local.banner[banner_len] = '\0';
    _libssh2_debug((session, LIBSSH2_TRACE_TRANS, "Setting local Banner: %s",
                   session->local.banner));
    session->local.banner[banner_len++] = '\r';
    session->local.banner[banner_len++] = '\n';
    session->local.banner[banner_len] = '\0';

    return 0;
}

#ifndef LIBSSH2_NO_DEPRECATED
/* libssh2_banner_set
 * Set the local banner. DEPRECATED VERSION
 */
LIBSSH2_API int
libssh2_banner_set(LIBSSH2_SESSION * session, const char *banner)
{
    return libssh2_session_banner_set(session, banner);
}
#endif

/*
 * libssh2_session_init_ex
 *
 * Allocate and initialize a libssh2 session structure. Allows for malloc
 * callbacks in case the calling program has its own memory manager It's
 * allowable (but unadvisable) to define some but not all of the malloc
 * callbacks An additional pointer value may be optionally passed to be sent
 * to the callbacks (so they know who's asking)
 */
LIBSSH2_API LIBSSH2_SESSION *
libssh2_session_init_ex(LIBSSH2_ALLOC_FUNC((*my_alloc)),
                        LIBSSH2_FREE_FUNC((*my_free)),
                        LIBSSH2_REALLOC_FUNC((*my_realloc)), void *abstract)
{
    LIBSSH2_ALLOC_FUNC((*local_alloc)) = libssh2_default_alloc;
    LIBSSH2_FREE_FUNC((*local_free)) = libssh2_default_free;
    LIBSSH2_REALLOC_FUNC((*local_realloc)) = libssh2_default_realloc;
    LIBSSH2_SESSION *session;

    if(my_alloc) {
        local_alloc = my_alloc;
    }
    if(my_free) {
        local_free = my_free;
    }
    if(my_realloc) {
        local_realloc = my_realloc;
    }

    session = local_alloc(sizeof(LIBSSH2_SESSION), &abstract);
    if(session) {
        memset(session, 0, sizeof(LIBSSH2_SESSION));
        session->alloc = local_alloc;
        session->free = local_free;
        session->realloc = local_realloc;
        session->send = _libssh2_send;
        session->recv = _libssh2_recv;
        session->abstract = abstract;
        session->api_timeout = 0; /* timeout-free API by default */
        session->api_block_mode = 1; /* blocking API by default */
        session->state = LIBSSH2_STATE_INITIAL_KEX;
        session->fullpacket_required_type = 0;
        session->packet_read_timeout = LIBSSH2_DEFAULT_READ_TIMEOUT;
        session->flag.quote_paths = 1; /* default behavior is to quote paths
                                          for the scp subsystem */
        session->kex = NULL;
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "New session resource allocated"));
        _libssh2_init_if_needed();
    }
    return session;
}

/*
 * libssh2_session_callback_set2
 *
 * Set (or reset) a callback function
 * Returns the prior address
 */
LIBSSH2_API libssh2_cb_generic *
libssh2_session_callback_set2(LIBSSH2_SESSION *session, int cbtype,
                              libssh2_cb_generic *callback)
{
    libssh2_cb_generic *oldcb;

    switch(cbtype) {
    case LIBSSH2_CALLBACK_IGNORE:
        oldcb = (libssh2_cb_generic *)session->ssh_msg_ignore;
        session->ssh_msg_ignore = (LIBSSH2_IGNORE_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_DEBUG:
        oldcb = (libssh2_cb_generic *)session->ssh_msg_debug;
        session->ssh_msg_debug = (LIBSSH2_DEBUG_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_DISCONNECT:
        oldcb = (libssh2_cb_generic *)session->ssh_msg_disconnect;
        session->ssh_msg_disconnect = (LIBSSH2_DISCONNECT_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_MACERROR:
        oldcb = (libssh2_cb_generic *)session->macerror;
        session->macerror = (LIBSSH2_MACERROR_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_X11:
        oldcb = (libssh2_cb_generic *)session->x11;
        session->x11 = (LIBSSH2_X11_OPEN_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_SEND:
        oldcb = (libssh2_cb_generic *)session->send;
        session->send = (LIBSSH2_SEND_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_RECV:
        oldcb = (libssh2_cb_generic *)session->recv;
        session->recv = (LIBSSH2_RECV_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_AUTHAGENT:
        oldcb = (libssh2_cb_generic *)session->authagent;
        session->authagent = (LIBSSH2_AUTHAGENT_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_AUTHAGENT_IDENTITIES:
        oldcb = (libssh2_cb_generic *)session->addLocalIdentities;
        session->addLocalIdentities =
            (LIBSSH2_ADD_IDENTITIES_FUNC((*)))callback;
        return oldcb;

    case LIBSSH2_CALLBACK_AUTHAGENT_SIGN:
        oldcb = (libssh2_cb_generic *)session->agentSignCallback;
        session->agentSignCallback =
            (LIBSSH2_AUTHAGENT_SIGN_FUNC((*)))callback;
        return oldcb;
    }
    _libssh2_debug((session, LIBSSH2_TRACE_TRANS, "Setting Callback %d",
                   cbtype));

    return NULL;
}

/*
 * libssh2_session_callback_set (DEPRECATED, DO NOT USE!)
 *
 * Set (or reset) a callback function
 * Returns the prior address
 *
 * ALERT: this function relies on that we can typecast function pointers
 * to void pointers, which isn't allowed in ISO C!
 */
#ifdef _MSC_VER
#pragma warning(push)
/* 'type cast': from data pointer to function pointer */
#pragma warning(disable:4054)
/* 'type cast': from function pointer to data pointer */
#pragma warning(disable:4055)
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
LIBSSH2_API void *
libssh2_session_callback_set(LIBSSH2_SESSION * session,
                             int cbtype, void *callback)
{
    return (void *)libssh2_session_callback_set2(session, cbtype,
                                               (libssh2_cb_generic *)callback);
}
#ifdef _MSC_VER
#pragma warning(pop)
#else
#pragma GCC diagnostic pop
#endif

/*
 * _libssh2_wait_socket
 *
 * Utility function that waits for action on the socket. Returns 0 when ready
 * to run again or error on timeout.
 */
int _libssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time)
{
    int rc;
    int seconds_to_next;
    int dir;
    int has_timeout;
    long ms_to_next = 0;
    long elapsed_ms;

    /* since libssh2 often sets EAGAIN internally before this function is
       called, we can decrease some amount of confusion in user programs by
       resetting the error code in this function to reduce the risk of EAGAIN
       being stored as error when a blocking function has returned */
    session->err_code = LIBSSH2_ERROR_NONE;

    rc = libssh2_keepalive_send(session, &seconds_to_next);
    if(rc)
        return rc;

    ms_to_next = seconds_to_next * 1000;

    /* figure out what to wait for */
    dir = libssh2_session_block_directions(session);

    if(!dir) {
        _libssh2_debug((session, LIBSSH2_TRACE_SOCKET,
                       "Nothing to wait for in wait_socket"));
        /* To avoid that we hang below just because there's nothing set to
           wait for, we timeout on 1 second to also avoid busy-looping
           during this condition */
        ms_to_next = 1000;
    }

    if(session->api_timeout > 0 &&
        (seconds_to_next == 0 ||
         ms_to_next > session->api_timeout)) {
        time_t now = time(NULL);
        elapsed_ms = (long)(1000*difftime(now, start_time));
        if(elapsed_ms > session->api_timeout) {
            return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT,
                                  "API timeout expired");
        }
        ms_to_next = (session->api_timeout - elapsed_ms);
        has_timeout = 1;
    }
    else if(ms_to_next > 0) {
        has_timeout = 1;
    }
    else
        has_timeout = 0;

#ifdef HAVE_POLL
    {
        struct pollfd sockets[1];

        sockets[0].fd = session->socket_fd;
        sockets[0].events = 0;
        sockets[0].revents = 0;

        if(dir & LIBSSH2_SESSION_BLOCK_INBOUND)
            sockets[0].events |= POLLIN;

        if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
            sockets[0].events |= POLLOUT;

        rc = poll(sockets, 1, has_timeout ? (int)ms_to_next : -1);
    }
#else
    {
        fd_set rfd;
        fd_set wfd;
        fd_set *writefd = NULL;
        fd_set *readfd = NULL;
        struct timeval tv;

        tv.tv_sec = ms_to_next / 1000;
#ifdef libssh2_usec_t
        tv.tv_usec = (libssh2_usec_t)((ms_to_next - tv.tv_sec*1000) * 1000);
#else
        tv.tv_usec = (ms_to_next - tv.tv_sec*1000) * 1000;
#endif

        if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) {
            FD_ZERO(&rfd);
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
            FD_SET(session->socket_fd, &rfd);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            readfd = &rfd;
        }

        if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) {
            FD_ZERO(&wfd);
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
            FD_SET(session->socket_fd, &wfd);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            writefd = &wfd;
        }

        rc = select((int)(session->socket_fd + 1), readfd, writefd, NULL,
                    has_timeout ? &tv : NULL);
    }
#endif
    if(rc == 0) {
        return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT,
                              "Timed out waiting on socket");
    }
    if(rc < 0) {
        int err;
#ifdef _WIN32
        err = _libssh2_wsa2errno();
#else
        err = errno;
#endif
        /* Profiling tools that use SIGPROF can cause EINTR responses.
           poll() / select() do not set any descriptor states on EINTR,
           but some fds may be ready, so the caller should try again */
        if(err == EINTR)
            return 0;

        return _libssh2_error(session, LIBSSH2_ERROR_TIMEOUT,
                              "Error waiting on socket");
    }

    return 0; /* ready to try again */
}

static int
session_startup(LIBSSH2_SESSION *session, libssh2_socket_t sock)
{
    int rc;

    if(session->startup_state == libssh2_NB_state_idle) {
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "session_startup for socket %ld", (long)sock));
        if(LIBSSH2_INVALID_SOCKET == sock) {
            /* Did we forget something? */
            return _libssh2_error(session, LIBSSH2_ERROR_BAD_SOCKET,
                                  "Bad socket provided");
        }
        session->socket_fd = sock;

        session->socket_prev_blockstate =
            !get_socket_nonblocking(session->socket_fd);

        if(session->socket_prev_blockstate) {
            /* If in blocking state change to non-blocking */
            rc = session_nonblock(session->socket_fd, 1);
            if(rc) {
                return _libssh2_error(session, rc,
                                      "Failed changing socket's "
                                      "blocking state to non-blocking");
            }
        }

        session->startup_state = libssh2_NB_state_created;
    }

    if(session->startup_state == libssh2_NB_state_created) {
        rc = banner_send(session);
        if(rc == LIBSSH2_ERROR_EAGAIN)
            return rc;
        else if(rc) {
            return _libssh2_error(session, rc,
                                  "Failed sending banner");
        }
        session->startup_state = libssh2_NB_state_sent;
        session->banner_TxRx_state = libssh2_NB_state_idle;
    }

    if(session->startup_state == libssh2_NB_state_sent) {
        do {
            rc = banner_receive(session);
            if(rc == LIBSSH2_ERROR_EAGAIN)
                return rc;
            else if(rc)
                return _libssh2_error(session, rc,
                                      "Failed getting banner");
        } while(strncmp("SSH-", (const char *)session->remote.banner, 4));

        session->startup_state = libssh2_NB_state_sent1;
    }

    if(session->startup_state == libssh2_NB_state_sent1) {
        rc = _libssh2_kex_exchange(session, 0, &session->startup_key_state);
        if(rc == LIBSSH2_ERROR_EAGAIN)
            return rc;
        else if(rc)
            return _libssh2_error(session, rc,
                                  "Unable to exchange encryption keys");

        session->startup_state = libssh2_NB_state_sent2;
    }

    if(session->startup_state == libssh2_NB_state_sent2) {
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "Requesting userauth service"));

        /* Request the userauth service */
        session->startup_service[0] = SSH_MSG_SERVICE_REQUEST;
        _libssh2_htonu32(session->startup_service + 1,
                         sizeof("ssh-userauth") - 1);
        memcpy(session->startup_service + 5, "ssh-userauth",
               sizeof("ssh-userauth") - 1);

        session->startup_state = libssh2_NB_state_sent3;
    }

    if(session->startup_state == libssh2_NB_state_sent3) {
        rc = _libssh2_transport_send(session, session->startup_service,
                                     sizeof("ssh-userauth") + 5 - 1,
                                     NULL, 0);
        if(rc == LIBSSH2_ERROR_EAGAIN)
            return rc;
        else if(rc) {
            return _libssh2_error(session, rc,
                                  "Unable to ask for ssh-userauth service");
        }

        session->startup_state = libssh2_NB_state_sent4;
    }

    if(session->startup_state == libssh2_NB_state_sent4) {
        rc = _libssh2_packet_require(session, SSH_MSG_SERVICE_ACCEPT,
                                     &session->startup_data,
                                     &session->startup_data_len, 0, NULL, 0,
                                     &session->startup_req_state);
        if(rc)
            return _libssh2_error(session, rc,
                                  "Failed to get response to "
                                  "ssh-userauth request");

        if(session->startup_data_len < 5) {
            return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                                  "Unexpected packet length");
        }

        session->startup_service_length =
            _libssh2_ntohu32(session->startup_data + 1);


        if((session->startup_service_length != (sizeof("ssh-userauth") - 1))
            || strncmp("ssh-userauth",
                       (const char *) session->startup_data + 5,
                       session->startup_service_length)) {
            LIBSSH2_FREE(session, session->startup_data);
            session->startup_data = NULL;
            return _libssh2_error(session, LIBSSH2_ERROR_PROTO,
                                  "Invalid response received from server");
        }
        LIBSSH2_FREE(session, session->startup_data);
        session->startup_data = NULL;

        session->startup_state = libssh2_NB_state_idle;

        return 0;
    }

    /* just for safety return some error */
    return LIBSSH2_ERROR_INVAL;
}

/*
 * libssh2_session_handshake
 *
 * session: LIBSSH2_SESSION struct allocated and owned by the calling program
 * sock:    *must* be populated with an opened and connected socket.
 *
 * Returns: 0 on success, or non-zero on failure
 */
LIBSSH2_API int
libssh2_session_handshake(LIBSSH2_SESSION *session, libssh2_socket_t sock)
{
    int rc;

    BLOCK_ADJUST(rc, session, session_startup(session, sock));

    return rc;
}

#ifndef LIBSSH2_NO_DEPRECATED
/*
 * libssh2_session_startup
 *
 * DEPRECATED. Use libssh2_session_handshake() instead! This function is not
 * portable enough.
 *
 * session: LIBSSH2_SESSION struct allocated and owned by the calling program
 * sock:    *must* be populated with an opened and connected socket.
 *
 * Returns: 0 on success, or non-zero on failure
 */
LIBSSH2_API int
libssh2_session_startup(LIBSSH2_SESSION *session, int sock)
{
    return libssh2_session_handshake(session, (libssh2_socket_t) sock);
}
#endif

/*
 * session_free
 *
 * Frees the memory allocated to the session
 * Also closes and frees any channels attached to this session
 */
static int
session_free(LIBSSH2_SESSION *session)
{
    int rc;
    LIBSSH2_PACKET *pkg;
    LIBSSH2_CHANNEL *ch;
    LIBSSH2_LISTENER *l;
    int packets_left = 0;

    if(session->free_state == libssh2_NB_state_idle) {
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "Freeing session resource %p",
                       (void *)session->remote.banner));

        session->free_state = libssh2_NB_state_created;
    }

    if(session->free_state == libssh2_NB_state_created) {
        /* !checksrc! disable EQUALSNULL 1 */
        while((ch = _libssh2_list_first(&session->channels)) != NULL) {
            rc = _libssh2_channel_free(ch);
            if(rc == LIBSSH2_ERROR_EAGAIN)
                return rc;
        }

        session->free_state = libssh2_NB_state_sent;
    }

    if(session->free_state == libssh2_NB_state_sent) {
        /* !checksrc! disable EQUALSNULL 1 */
        while((l = _libssh2_list_first(&session->listeners)) != NULL) {
            rc = _libssh2_channel_forward_cancel(l);
            if(rc == LIBSSH2_ERROR_EAGAIN)
                return rc;
        }

        session->free_state = libssh2_NB_state_sent1;
    }

    if(session->kex && session->kex->cleanup) {
        session->kex->cleanup(session,
                              &session->startup_key_state.key_state_low);
    }

    if(session->state & LIBSSH2_STATE_NEWKEYS) {
        /* hostkey */
        if(session->hostkey && session->hostkey->dtor) {
            session->hostkey->dtor(session, &session->server_hostkey_abstract);
        }

        /* Client to Server */
        /* crypt */
        if(session->local.crypt && session->local.crypt->dtor) {
            session->local.crypt->dtor(session,
                                       &session->local.crypt_abstract);
        }
        /* comp */
        if(session->local.comp && session->local.comp->dtor) {
            session->local.comp->dtor(session, 1,
                                      &session->local.comp_abstract);
        }
        /* mac */
        if(session->local.mac && session->local.mac->dtor) {
            session->local.mac->dtor(session, &session->local.mac_abstract);
        }

        /* Server to Client */
        /* crypt */
        if(session->remote.crypt && session->remote.crypt->dtor) {
            session->remote.crypt->dtor(session,
                                        &session->remote.crypt_abstract);
        }
        /* comp */
        if(session->remote.comp && session->remote.comp->dtor) {
            session->remote.comp->dtor(session, 0,
                                       &session->remote.comp_abstract);
        }
        /* mac */
        if(session->remote.mac && session->remote.mac->dtor) {
            session->remote.mac->dtor(session, &session->remote.mac_abstract);
        }

        /* session_id */
        if(session->session_id) {
            LIBSSH2_FREE(session, session->session_id);
        }
    }

    /* Free banner(s) */
    if(session->remote.banner) {
        LIBSSH2_FREE(session, session->remote.banner);
    }
    if(session->local.banner) {
        LIBSSH2_FREE(session, session->local.banner);
    }

    /* Free preference(s) */
    if(session->kex_prefs) {
        LIBSSH2_FREE(session, session->kex_prefs);
    }
    if(session->hostkey_prefs) {
        LIBSSH2_FREE(session, session->hostkey_prefs);
    }

    if(session->local.kexinit) {
        LIBSSH2_FREE(session, session->local.kexinit);
    }
    if(session->local.crypt_prefs) {
        LIBSSH2_FREE(session, session->local.crypt_prefs);
    }
    if(session->local.mac_prefs) {
        LIBSSH2_FREE(session, session->local.mac_prefs);
    }
    if(session->local.comp_prefs) {
        LIBSSH2_FREE(session, session->local.comp_prefs);
    }
    if(session->local.lang_prefs) {
        LIBSSH2_FREE(session, session->local.lang_prefs);
    }

    if(session->remote.kexinit) {
        LIBSSH2_FREE(session, session->remote.kexinit);
    }
    if(session->remote.crypt_prefs) {
        LIBSSH2_FREE(session, session->remote.crypt_prefs);
    }
    if(session->remote.mac_prefs) {
        LIBSSH2_FREE(session, session->remote.mac_prefs);
    }
    if(session->remote.comp_prefs) {
        LIBSSH2_FREE(session, session->remote.comp_prefs);
    }
    if(session->remote.lang_prefs) {
        LIBSSH2_FREE(session, session->remote.lang_prefs);
    }
    if(session->server_sign_algorithms) {
        LIBSSH2_FREE(session, session->server_sign_algorithms);
    }
    if(session->sign_algo_prefs) {
        LIBSSH2_FREE(session, session->sign_algo_prefs);
    }

    /*
     * Make sure all memory used in the state variables are free
     */
    if(session->kexinit_data) {
        LIBSSH2_FREE(session, session->kexinit_data);
    }
    if(session->startup_data) {
        LIBSSH2_FREE(session, session->startup_data);
    }
    if(session->userauth_list_data) {
        LIBSSH2_FREE(session, session->userauth_list_data);
    }
    if(session->userauth_banner) {
        LIBSSH2_FREE(session, session->userauth_banner);
    }
    if(session->userauth_pswd_data) {
        LIBSSH2_FREE(session, session->userauth_pswd_data);
    }
    if(session->userauth_pswd_newpw) {
        LIBSSH2_FREE(session, session->userauth_pswd_newpw);
    }
    if(session->userauth_host_packet) {
        LIBSSH2_FREE(session, session->userauth_host_packet);
    }
    if(session->userauth_host_method) {
        LIBSSH2_FREE(session, session->userauth_host_method);
    }
    if(session->userauth_host_data) {
        LIBSSH2_FREE(session, session->userauth_host_data);
    }
    if(session->userauth_pblc_data) {
        LIBSSH2_FREE(session, session->userauth_pblc_data);
    }
    if(session->userauth_pblc_packet) {
        LIBSSH2_FREE(session, session->userauth_pblc_packet);
    }
    if(session->userauth_pblc_method) {
        LIBSSH2_FREE(session, session->userauth_pblc_method);
    }
    if(session->userauth_kybd_data) {
        LIBSSH2_FREE(session, session->userauth_kybd_data);
    }
    if(session->userauth_kybd_packet) {
        LIBSSH2_FREE(session, session->userauth_kybd_packet);
    }
    if(session->userauth_kybd_auth_instruction) {
        LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction);
    }
    if(session->open_packet) {
        LIBSSH2_FREE(session, session->open_packet);
    }
    if(session->open_data) {
        LIBSSH2_FREE(session, session->open_data);
    }
    if(session->direct_message) {
        LIBSSH2_FREE(session, session->direct_message);
    }
    if(session->fwdLstn_packet) {
        LIBSSH2_FREE(session, session->fwdLstn_packet);
    }
    if(session->pkeyInit_data) {
        LIBSSH2_FREE(session, session->pkeyInit_data);
    }
    if(session->scpRecv_command) {
        LIBSSH2_FREE(session, session->scpRecv_command);
    }
    if(session->scpSend_command) {
        LIBSSH2_FREE(session, session->scpSend_command);
    }
    if(session->sftpInit_sftp) {
        LIBSSH2_FREE(session, session->sftpInit_sftp);
    }

    /* Free payload buffer */
    if(session->packet.total_num) {
        LIBSSH2_FREE(session, session->packet.payload);
    }

    /* Cleanup all remaining packets */
    /* !checksrc! disable EQUALSNULL 1 */
    while((pkg = _libssh2_list_first(&session->packets)) != NULL) {
        packets_left++;
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "packet left with id %d", pkg->data[0]));
        /* unlink the node */
        _libssh2_list_remove(&pkg->node);

        /* free */
        LIBSSH2_FREE(session, pkg->data);
        LIBSSH2_FREE(session, pkg);
    }
    (void)packets_left;
    _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                   "Extra packets left %d", packets_left));

    if(session->socket_prev_blockstate) {
        /* if the socket was previously blocking, put it back so */
        rc = session_nonblock(session->socket_fd, 0);
        if(rc) {
            _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                           "unable to reset socket's blocking state"));
        }
    }

    if(session->server_hostkey) {
        LIBSSH2_FREE(session, session->server_hostkey);
    }

    /* error string */
    if(session->err_msg &&
       ((session->err_flags & LIBSSH2_ERR_FLAG_DUP) != 0)) {
        LIBSSH2_FREE(session, (char *)session->err_msg);
    }

    LIBSSH2_FREE(session, session);

    return 0;
}

/*
 * libssh2_session_free
 *
 * Frees the memory allocated to the session
 * Also closes and frees any channels attached to this session
 */
LIBSSH2_API int
libssh2_session_free(LIBSSH2_SESSION * session)
{
    int rc;

    BLOCK_ADJUST(rc, session, session_free(session));

    return rc;
}

/*
 * session_disconnect
 */
static int
session_disconnect(LIBSSH2_SESSION *session, int reason,
                   const char *description,
                   const char *lang)
{
    unsigned char *s;
    size_t descr_len = 0, lang_len = 0;
    int rc;

    if(session->disconnect_state == libssh2_NB_state_idle) {
        _libssh2_debug((session, LIBSSH2_TRACE_TRANS,
                       "Disconnecting: reason=%d, desc=%s, lang=%s", reason,
                       description, lang));
        if(description)
            descr_len = strlen(description);

        if(lang)
            lang_len = strlen(lang);

        if(descr_len > 256)
            return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
                                  "too long description");

        if(lang_len > 256)
            return _libssh2_error(session, LIBSSH2_ERROR_INVAL,
                                  "too long language string");

        /* 13 = packet_type(1) + reason code(4) + descr_len(4) + lang_len(4) */
        session->disconnect_data_len = descr_len + lang_len + 13;

        s = session->disconnect_data;

        *(s++) = SSH_MSG_DISCONNECT;
        _libssh2_store_u32(&s, reason);
        _libssh2_store_str(&s, description, descr_len);
        /* store length only, lang is sent separately */
        _libssh2_store_u32(&s, (uint32_t)lang_len);

        session->disconnect_state = libssh2_NB_state_created;
    }

    rc = _libssh2_transport_send(session, session->disconnect_data,
                                 session->disconnect_data_len,
                                 (const unsigned char *)lang, lang_len);
    if(rc == LIBSSH2_ERROR_EAGAIN)
        return rc;

    session->disconnect_state = libssh2_NB_state_idle;

    return 0;
}

/*
 * libssh2_session_disconnect_ex
 */
LIBSSH2_API int
libssh2_session_disconnect_ex(LIBSSH2_SESSION *session, int reason,
                              const char *desc, const char *lang)
{
    int rc;
    session->state &= ~LIBSSH2_STATE_INITIAL_KEX;
    session->state &= ~LIBSSH2_STATE_EXCHANGING_KEYS;
    BLOCK_ADJUST(rc, session,
                 session_disconnect(session, reason, desc, lang));

    return rc;
}

/* libssh2_session_methods
 *
 * Return the currently active methods for method_type
 *
 * NOTE: Currently lang_cs and lang_sc are ALWAYS set to empty string
 * regardless of actual negotiation Strings should NOT be freed
 */
LIBSSH2_API const char *
libssh2_session_methods(LIBSSH2_SESSION * session, int method_type)
{
    /* All methods have char *name as their first element */
    const LIBSSH2_KEX_METHOD *method = NULL;

    switch(method_type) {
    case LIBSSH2_METHOD_KEX:
        method = session->kex;
        break;

    case LIBSSH2_METHOD_HOSTKEY:
        method = (LIBSSH2_KEX_METHOD *) session->hostkey;
        break;

    case LIBSSH2_METHOD_CRYPT_CS:
        method = (LIBSSH2_KEX_METHOD *) session->local.crypt;
        break;

    case LIBSSH2_METHOD_CRYPT_SC:
        method = (LIBSSH2_KEX_METHOD *) session->remote.crypt;
        break;

    case LIBSSH2_METHOD_MAC_CS:
        method = (LIBSSH2_KEX_METHOD *) session->local.mac;
        break;

    case LIBSSH2_METHOD_MAC_SC:
        method = (LIBSSH2_KEX_METHOD *) session->remote.mac;
        break;

    case LIBSSH2_METHOD_COMP_CS:
        method = (LIBSSH2_KEX_METHOD *) session->local.comp;
        break;

    case LIBSSH2_METHOD_COMP_SC:
        method = (LIBSSH2_KEX_METHOD *) session->remote.comp;
        break;

    case LIBSSH2_METHOD_LANG_CS:
        return "";

    case LIBSSH2_METHOD_LANG_SC:
        return "";

    default:
        _libssh2_error(session, LIBSSH2_ERROR_INVAL,
                       "Invalid parameter specified for method_type");
        return NULL;
    }

    if(!method) {
        _libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE,
                       "No method negotiated");
        return NULL;
    }

    return method->name;
}

/* libssh2_session_abstract
 * Retrieve a pointer to the abstract property
 */
LIBSSH2_API void **
libssh2_session_abstract(LIBSSH2_SESSION * session)
{
    return &session->abstract;
}

/* libssh2_session_last_error
 *
 * Returns error code and populates an error string into errmsg If want_buf is
 * non-zero then the string placed into errmsg must be freed by the calling
 * program. Otherwise it is assumed to be owned by libssh2
 */
LIBSSH2_API int
libssh2_session_last_error(LIBSSH2_SESSION * session, char **errmsg,
                           int *errmsg_len, int want_buf)
{
    size_t msglen = 0;

    /* No error to report */
    if(!session->err_code) {
        if(errmsg) {
            if(want_buf) {
                *errmsg = LIBSSH2_ALLOC(session, 1);
                if(*errmsg) {
                    **errmsg = 0;
                }
            }
            else {
                *errmsg = (char *) "";
            }
        }
        if(errmsg_len) {
            *errmsg_len = 0;
        }
        return 0;
    }

    if(errmsg) {
        const char *error = session->err_msg ? session->err_msg : "";

        msglen = strlen(error);

        if(want_buf) {
            /* Make a copy so the calling program can own it */
            *errmsg = LIBSSH2_ALLOC(session, msglen + 1);
            if(*errmsg) {
                memcpy(*errmsg, error, msglen);
                (*errmsg)[msglen] = 0;
            }
        }
        else
            *errmsg = (char *)error;
    }

    if(errmsg_len) {
        *errmsg_len = (int)msglen;
    }

    return session->err_code;
}

/* libssh2_session_last_errno
 *
 * Returns error code
 */
LIBSSH2_API int
libssh2_session_last_errno(LIBSSH2_SESSION * session)
{
    return session->err_code;
}

/* libssh2_session_set_last_error
 *
 * Sets the internal error code for the session.
 *
 * This function is available specifically to be used by high level
 * language wrappers (i.e. Python or Perl) that may extend the library
 * features while still relying on its error reporting mechanism.
 */
LIBSSH2_API int
libssh2_session_set_last_error(LIBSSH2_SESSION* session,
                               int errcode,
                               const char *errmsg)
{
    return _libssh2_error_flags(session, errcode, errmsg,
                                LIBSSH2_ERR_FLAG_DUP);
}

/* libssh2_session_flag
 *
 * Set/Get session flags
 *
 * Return error code.
 */
LIBSSH2_API int
libssh2_session_flag(LIBSSH2_SESSION * session, int flag, int value)
{
    switch(flag) {
    case LIBSSH2_FLAG_SIGPIPE:
        session->flag.sigpipe = value;
        break;
    case LIBSSH2_FLAG_COMPRESS:
        session->flag.compress = value;
        break;
    case LIBSSH2_FLAG_QUOTE_PATHS:
        session->flag.quote_paths = value;
        break;
    default:
        /* unknown flag */
        return LIBSSH2_ERROR_INVAL;
    }

    return LIBSSH2_ERROR_NONE;
}

/* _libssh2_session_set_blocking
 *
 * Set a session's blocking mode on or off, return the previous status when
 * this function is called. Note this function does not alter the state of the
 * actual socket involved.
 */
int
_libssh2_session_set_blocking(LIBSSH2_SESSION *session, int blocking)
{
    int bl = session->api_block_mode;
    _libssh2_debug((session, LIBSSH2_TRACE_CONN,
                   "Setting blocking mode %s", blocking ? "ON" : "OFF"));
    session->api_block_mode = blocking;

    return bl;
}

/* libssh2_session_set_blocking
 *
 * Set a channel's blocking mode on or off, similar to a socket's
 * fcntl(fd, F_SETFL, O_NONBLOCK); type command
 */
LIBSSH2_API void
libssh2_session_set_blocking(LIBSSH2_SESSION * session, int blocking)
{
    (void)_libssh2_session_set_blocking(session, blocking);
}

/* libssh2_session_get_blocking
 *
 * Returns a session's blocking mode on or off
 */
LIBSSH2_API int
libssh2_session_get_blocking(LIBSSH2_SESSION * session)
{
    return session->api_block_mode;
}


/* libssh2_session_set_timeout
 *
 * Set a session's timeout (in msec) for blocking mode,
 * or 0 to disable timeouts.
 */
LIBSSH2_API void
libssh2_session_set_timeout(LIBSSH2_SESSION * session, long timeout)
{
    session->api_timeout = timeout;
}

/* libssh2_session_get_timeout
 *
 * Returns a session's timeout, or 0 if disabled
 */
LIBSSH2_API long
libssh2_session_get_timeout(LIBSSH2_SESSION * session)
{
    return session->api_timeout;
}

/* libssh2_session_set_read_timeout
 *
 * Set a session's timeout (in sec) when reading packets,
 * or 0 to use default of 60 seconds.
 */
LIBSSH2_API void
libssh2_session_set_read_timeout(LIBSSH2_SESSION * session, long timeout)
{
    if(timeout <= 0) {
        timeout = LIBSSH2_DEFAULT_READ_TIMEOUT;
    }
    session->packet_read_timeout = timeout;
}

/* libssh2_session_get_read_timeout
 *
 * Returns a session's timeout. Default is 60 seconds.
 */
LIBSSH2_API long
libssh2_session_get_read_timeout(LIBSSH2_SESSION * session)
{
    return session->packet_read_timeout;
}

/*
 * libssh2_poll_channel_read
 *
 * Returns 0 if no data is waiting on channel,
 * non-0 if data is available
 */
LIBSSH2_API int
libssh2_poll_channel_read(LIBSSH2_CHANNEL *channel, int extended)
{
    LIBSSH2_SESSION *session;
    LIBSSH2_PACKET *packet;

    if(!channel)
        return LIBSSH2_ERROR_BAD_USE;

    session = channel->session;
    packet = _libssh2_list_first(&session->packets);

    while(packet) {
        if(packet->data_len < 5) {
            return _libssh2_error(session, LIBSSH2_ERROR_BUFFER_TOO_SMALL,
                                  "Packet too small");
        }

        if(channel->local.id == _libssh2_ntohu32(packet->data + 1)) {
            if(extended == 1 &&
                (packet->data[0] == SSH_MSG_CHANNEL_EXTENDED_DATA
                 || packet->data[0] == SSH_MSG_CHANNEL_DATA)) {
                return 1;
            }
            else if(extended == 0 &&
                    packet->data[0] == SSH_MSG_CHANNEL_DATA) {
                return 1;
            }
            /* else - no data of any type is ready to be read */
        }
        packet = _libssh2_list_next(&packet->node);
    }

    return 0;
}

/*
 * poll_channel_write
 *
 * Returns 0 if writing to channel would block,
 * non-0 if data can be written without blocking
 */
static inline int
poll_channel_write(LIBSSH2_CHANNEL * channel)
{
    return channel->local.window_size ? 1 : 0;
}

/* poll_listener_queued
 *
 * Returns 0 if no connections are waiting to be accepted
 * non-0 if one or more connections are available
 */
static inline int
poll_listener_queued(LIBSSH2_LISTENER * listener)
{
    return _libssh2_list_first(&listener->queue) ? 1 : 0;
}

/*
 * libssh2_poll
 *
 * Poll sockets, channels, and listeners for activity
 */
LIBSSH2_API int
libssh2_poll(LIBSSH2_POLLFD * fds, unsigned int nfds, long timeout)
{
    long timeout_remaining;
    unsigned int i, active_fds;
#ifdef HAVE_POLL
    LIBSSH2_SESSION *session = NULL;
#ifdef HAVE_ALLOCA
    struct pollfd *sockets = alloca(sizeof(struct pollfd) * nfds);
#else
    struct pollfd sockets[256];

    if(nfds > 256)
        /* systems without alloca use a fixed-size array, this can be fixed if
           we really want to, at least if the compiler is a C99 capable one */
        return -1;
#endif
    /* Setup sockets for polling */
    for(i = 0; i < nfds; i++) {
        fds[i].revents = 0;
        switch(fds[i].type) {
        case LIBSSH2_POLLFD_SOCKET:
            sockets[i].fd = fds[i].fd.socket;
            sockets[i].events = (short)fds[i].events;
            sockets[i].revents = 0;
            break;

        case LIBSSH2_POLLFD_CHANNEL:
            sockets[i].fd = fds[i].fd.channel->session->socket_fd;
            sockets[i].events = POLLIN;
            sockets[i].revents = 0;
            if(!session)
                session = fds[i].fd.channel->session;
            break;

        case LIBSSH2_POLLFD_LISTENER:
            sockets[i].fd = fds[i].fd.listener->session->socket_fd;
            sockets[i].events = POLLIN;
            sockets[i].revents = 0;
            if(!session)
                session = fds[i].fd.listener->session;
            break;

        default:
            if(session)
                _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE,
                               "Invalid descriptor passed to libssh2_poll()");
            return -1;
        }
    }
#elif defined(HAVE_SELECT)
    LIBSSH2_SESSION *session = NULL;
    libssh2_socket_t maxfd = 0;
    fd_set rfds, wfds;
    struct timeval tv;

    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    for(i = 0; i < nfds; i++) {
        fds[i].revents = 0;
        switch(fds[i].type) {
        case LIBSSH2_POLLFD_SOCKET:
            if(fds[i].events & LIBSSH2_POLLFD_POLLIN) {
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
                FD_SET(fds[i].fd.socket, &rfds);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
                if(fds[i].fd.socket > maxfd)
                    maxfd = fds[i].fd.socket;
            }
            if(fds[i].events & LIBSSH2_POLLFD_POLLOUT) {
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
                FD_SET(fds[i].fd.socket, &wfds);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
                if(fds[i].fd.socket > maxfd)
                    maxfd = fds[i].fd.socket;
            }
            break;

        case LIBSSH2_POLLFD_CHANNEL:
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
            FD_SET(fds[i].fd.channel->session->socket_fd, &rfds);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            if(fds[i].fd.channel->session->socket_fd > maxfd)
                maxfd = fds[i].fd.channel->session->socket_fd;
            if(!session)
                session = fds[i].fd.channel->session;
            break;

        case LIBSSH2_POLLFD_LISTENER:
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
            FD_SET(fds[i].fd.listener->session->socket_fd, &rfds);
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
            if(fds[i].fd.listener->session->socket_fd > maxfd)
                maxfd = fds[i].fd.listener->session->socket_fd;
            if(!session)
                session = fds[i].fd.listener->session;
            break;

        default:
            if(session)
                _libssh2_error(session, LIBSSH2_ERROR_INVALID_POLL_TYPE,
                               "Invalid descriptor passed to libssh2_poll()");
            return -1;
        }
    }
#else
    /* No select() or poll()
     * no sockets structure to setup
     */

    timeout = 0;
#endif /* HAVE_POLL or HAVE_SELECT */

    timeout_remaining = timeout;
    do {
#if defined(HAVE_POLL) || defined(HAVE_SELECT)
        int sysret;
#endif

        active_fds = 0;

        for(i = 0; i < nfds; i++) {
            if(fds[i].events != fds[i].revents) {
                switch(fds[i].type) {
                case LIBSSH2_POLLFD_CHANNEL:
                    if((fds[i].events & LIBSSH2_POLLFD_POLLIN) &&
                        /* Want to be ready for read */
                        ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) {
                        /* Not yet known to be ready for read */
                        fds[i].revents |=
                            libssh2_poll_channel_read(fds[i].fd.channel,
                                                      0) ?
                            LIBSSH2_POLLFD_POLLIN : 0;
                    }
                    if((fds[i].events & LIBSSH2_POLLFD_POLLEXT) &&
                        /* Want to be ready for extended read */
                        ((fds[i].revents & LIBSSH2_POLLFD_POLLEXT) == 0)) {
                        /* Not yet known to be ready for extended read */
                        fds[i].revents |=
                            libssh2_poll_channel_read(fds[i].fd.channel,
                                                      1) ?
                            LIBSSH2_POLLFD_POLLEXT : 0;
                    }
                    if((fds[i].events & LIBSSH2_POLLFD_POLLOUT) &&
                        /* Want to be ready for write */
                        ((fds[i].revents & LIBSSH2_POLLFD_POLLOUT) == 0)) {
                        /* Not yet known to be ready for write */
                        fds[i].revents |=
                            poll_channel_write(fds[i].fd. channel) ?
                            LIBSSH2_POLLFD_POLLOUT : 0;
                    }
                    if(fds[i].fd.channel->remote.close
                        || fds[i].fd.channel->local.close) {
                        fds[i].revents |= LIBSSH2_POLLFD_CHANNEL_CLOSED;
                    }
                    if(fds[i].fd.channel->session->socket_state ==
                        LIBSSH2_SOCKET_DISCONNECTED) {
                        fds[i].revents |=
                            LIBSSH2_POLLFD_CHANNEL_CLOSED |
                            LIBSSH2_POLLFD_SESSION_CLOSED;
                    }
                    break;

                case LIBSSH2_POLLFD_LISTENER:
                    if((fds[i].events & LIBSSH2_POLLFD_POLLIN) &&
                        /* Want a connection */
                        ((fds[i].revents & LIBSSH2_POLLFD_POLLIN) == 0)) {
                        /* No connections known of yet */
                        fds[i].revents |=
                            poll_listener_queued(fds[i].fd. listener) ?
                            LIBSSH2_POLLFD_POLLIN : 0;
                    }
                    if(fds[i].fd.listener->session->socket_state ==
                        LIBSSH2_SOCKET_DISCONNECTED) {
                        fds[i].revents |=
                            LIBSSH2_POLLFD_LISTENER_CLOSED |
                            LIBSSH2_POLLFD_SESSION_CLOSED;
                    }
                    break;
                }
            }
            if(fds[i].revents) {
                active_fds++;
            }
        }

        if(active_fds) {
            /* Don't block on the sockets if we have channels/listeners which
               are ready */
            timeout_remaining = 0;
        }
#ifdef HAVE_POLL

        {
            struct timeval tv_begin, tv_end;

            gettimeofday(&tv_begin, NULL);
            sysret = poll(sockets, nfds, (int)timeout_remaining);
            gettimeofday(&tv_end, NULL);
            timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000;
            timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000;
        }

        if(sysret > 0) {
            for(i = 0; i < nfds; i++) {
                switch(fds[i].type) {
                case LIBSSH2_POLLFD_SOCKET:
                    fds[i].revents = sockets[i].revents;
                    sockets[i].revents = 0; /* In case we loop again, be
                                               nice */
                    if(fds[i].revents) {
                        active_fds++;
                    }
                    break;
                case LIBSSH2_POLLFD_CHANNEL:
                    if(sockets[i].events & POLLIN) {
                        /* Spin session until no data available */
                        while(_libssh2_transport_read(fds[i].fd.
                                                      channel->session)
                              > 0);
                    }
                    if(sockets[i].revents & POLLHUP) {
                        fds[i].revents |=
                            LIBSSH2_POLLFD_CHANNEL_CLOSED |
                            LIBSSH2_POLLFD_SESSION_CLOSED;
                    }
                    sockets[i].revents = 0;
                    break;
                case LIBSSH2_POLLFD_LISTENER:
                    if(sockets[i].events & POLLIN) {
                        /* Spin session until no data available */
                        while(_libssh2_transport_read(fds[i].fd.
                                                      listener->session)
                              > 0);
                    }
                    if(sockets[i].revents & POLLHUP) {
                        fds[i].revents |=
                            LIBSSH2_POLLFD_LISTENER_CLOSED |
                            LIBSSH2_POLLFD_SESSION_CLOSED;
                    }
                    sockets[i].revents = 0;
                    break;
                }
            }
        }
#elif defined(HAVE_SELECT)
        tv.tv_sec = timeout_remaining / 1000;
#ifdef libssh2_usec_t
        tv.tv_usec = (libssh2_usec_t)((timeout_remaining % 1000) * 1000);
#else
        tv.tv_usec = (timeout_remaining % 1000) * 1000;
#endif

        {
            struct timeval tv_begin, tv_end;

            gettimeofday(&tv_begin, NULL);
            sysret = select((int)(maxfd + 1), &rfds, &wfds, NULL, &tv);
            gettimeofday(&tv_end, NULL);

            timeout_remaining -= (tv_end.tv_sec - tv_begin.tv_sec) * 1000;
            timeout_remaining -= (tv_end.tv_usec - tv_begin.tv_usec) / 1000;
        }

        if(sysret > 0) {
            for(i = 0; i < nfds; i++) {
                switch(fds[i].type) {
                case LIBSSH2_POLLFD_SOCKET:
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
                    if(FD_ISSET(fds[i].fd.socket, &rfds)) {
                        fds[i].revents |= LIBSSH2_POLLFD_POLLIN;
                    }
                    if(FD_ISSET(fds[i].fd.socket, &wfds)) {
                        fds[i].revents |= LIBSSH2_POLLFD_POLLOUT;
                    }
                    if(fds[i].revents) {
                        active_fds++;
                    }
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#endif
                    break;

                case LIBSSH2_POLLFD_CHANNEL:
                    if(FD_ISSET(fds[i].fd.channel->session->socket_fd,
                                &rfds)) {
                        /* Spin session until no data available */
                        while(_libssh2_transport_read(fds[i].fd.
                                                      channel->session)
                              > 0);
                    }
                    break;

                case LIBSSH2_POLLFD_LISTENER:
                    if(FD_ISSET
                        (fds[i].fd.listener->session->socket_fd, &rfds)) {
                        /* Spin session until no data available */
                        while(_libssh2_transport_read(fds[i].fd.
                                                      listener->session)
                              > 0);
                    }
                    break;
                }
            }
        }
#endif /* else no select() or poll() -- timeout (and by extension
        * timeout_remaining) will be equal to 0 */
    } while((timeout_remaining > 0) && !active_fds);

    return active_fds;
}

/*
 * libssh2_session_block_directions
 *
 * Get blocked direction when a function returns LIBSSH2_ERROR_EAGAIN
 * Returns LIBSSH2_SOCKET_BLOCK_INBOUND if recv() blocked
 * or LIBSSH2_SOCKET_BLOCK_OUTBOUND if send() blocked
 */
LIBSSH2_API int
libssh2_session_block_directions(LIBSSH2_SESSION *session)
{
    return session->socket_block_directions;
}

/* libssh2_session_banner_get
 * Get the remote banner (server ID string)
 */

LIBSSH2_API const char *
libssh2_session_banner_get(LIBSSH2_SESSION *session)
{
    /* to avoid a coredump when session is NULL */
    if(!session)
        return NULL;

    if(!session->remote.banner)
        return NULL;

    return (const char *) session->remote.banner;
}
