1861 lines
56 KiB
C
1861 lines
56 KiB
C
#if defined(ESP32)
|
|
/* Copyright (c) 2004-2007 Sara Golemon <sarag@libssh2.org>
|
|
* Copyright (c) 2009-2015 by Daniel Stenberg
|
|
* Copyright (c) 2010 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.
|
|
*/
|
|
|
|
#include "libssh2_priv.h"
|
|
#include <errno.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
|
|
#ifdef HAVE_GETTIMEOFDAY
|
|
#include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_ALLOCA_H
|
|
#include <alloca.h>
|
|
#endif
|
|
|
|
#include "transport.h"
|
|
#include "session.h"
|
|
#include "channel.h"
|
|
#include "mac.h"
|
|
#include "misc.h"
|
|
|
|
/* 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)
|
|
{
|
|
int ret;
|
|
int 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 < (int) 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: %d", 1, -ret);
|
|
}
|
|
else
|
|
_libssh2_debug(session, LIBSSH2_TRACE_SOCKET,
|
|
"Recved %d bytes banner", 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;
|
|
int banner_len = sizeof(LIBSSH2_SSH_DEFAULT_BANNER_WITH_CRLF) - 1;
|
|
ssize_t ret;
|
|
#ifdef LIBSSH2DEBUG
|
|
char banner_dup[256];
|
|
#endif
|
|
|
|
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
|
|
/* 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 %d bytes: %d",
|
|
banner_len - session->banner_TxRx_total_send, -ret);
|
|
else
|
|
_libssh2_debug(session, LIBSSH2_TRACE_SOCKET,
|
|
"Sent %d/%d bytes at %p+%d", ret,
|
|
banner_len - session->banner_TxRx_total_send,
|
|
banner, session->banner_TxRx_total_send);
|
|
|
|
if(ret != (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 */ )
|
|
{
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 0
|
|
#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));
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 1
|
|
#endif
|
|
|
|
#if defined(HAVE_FIONBIO) && (SETBLOCK == 0)
|
|
/* older unix versions and VMS*/
|
|
int flags;
|
|
|
|
flags = nonblock;
|
|
return ioctl(sockfd, FIONBIO, &flags);
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 2
|
|
#endif
|
|
|
|
#if defined(HAVE_IOCTLSOCKET) && (SETBLOCK == 0)
|
|
/* Windows? */
|
|
unsigned long flags;
|
|
flags = nonblock;
|
|
|
|
return ioctlsocket(sockfd, FIONBIO, &flags);
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 3
|
|
#endif
|
|
|
|
#if defined(HAVE_IOCTLSOCKET_CASE) && (SETBLOCK == 0)
|
|
/* presumably for Amiga */
|
|
return IoctlSocket(sockfd, FIONBIO, (long) nonblock);
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 4
|
|
#endif
|
|
|
|
#if defined(HAVE_SO_NONBLOCK) && (SETBLOCK == 0)
|
|
/* BeOS */
|
|
long b = nonblock ? 1 : 0;
|
|
return setsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b));
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 5
|
|
#endif
|
|
|
|
#ifdef HAVE_DISABLED_NONBLOCKING
|
|
return 0; /* returns success */
|
|
#undef SETBLOCK
|
|
#define SETBLOCK 6
|
|
#endif
|
|
|
|
#if(SETBLOCK == 0)
|
|
#error "no non-blocking method was found/used/set"
|
|
#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 */
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 0
|
|
#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);
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 1
|
|
#endif
|
|
|
|
#if defined(WSAEWOULDBLOCK) && (GETBLOCK == 0)
|
|
/* Windows? */
|
|
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;
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 2
|
|
#endif
|
|
|
|
#if defined(HAVE_SO_NONBLOCK) && (GETBLOCK == 0)
|
|
/* BeOS */
|
|
long b;
|
|
if(getsockopt(sockfd, SOL_SOCKET, SO_NONBLOCK, &b, sizeof(b))) {
|
|
/* Assume blocking on error */
|
|
return 1;
|
|
}
|
|
return (int) b;
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 5
|
|
#endif
|
|
|
|
#if defined(SO_STATE) && defined(__VMS) && (GETBLOCK == 0)
|
|
|
|
/* 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;
|
|
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 6
|
|
#endif
|
|
|
|
#ifdef HAVE_DISABLED_NONBLOCKING
|
|
return 1; /* returns blocking */
|
|
#undef GETBLOCK
|
|
#define GETBLOCK 7
|
|
#endif
|
|
|
|
#if(GETBLOCK == 0)
|
|
#error "no non-blocking method was found/used/get"
|
|
#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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/*
|
|
* 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 */
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS,
|
|
"New session resource allocated");
|
|
_libssh2_init_if_needed();
|
|
}
|
|
return session;
|
|
}
|
|
|
|
/*
|
|
* libssh2_session_callback_set
|
|
*
|
|
* 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)
|
|
/* nonstandard extension, function/data pointer conversion in expression */
|
|
#pragma warning(disable:4152)
|
|
#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)
|
|
{
|
|
void *oldcb;
|
|
|
|
switch(cbtype) {
|
|
case LIBSSH2_CALLBACK_IGNORE:
|
|
oldcb = session->ssh_msg_ignore;
|
|
session->ssh_msg_ignore = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_DEBUG:
|
|
oldcb = session->ssh_msg_debug;
|
|
session->ssh_msg_debug = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_DISCONNECT:
|
|
oldcb = session->ssh_msg_disconnect;
|
|
session->ssh_msg_disconnect = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_MACERROR:
|
|
oldcb = session->macerror;
|
|
session->macerror = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_X11:
|
|
oldcb = session->x11;
|
|
session->x11 = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_SEND:
|
|
oldcb = session->send;
|
|
session->send = callback;
|
|
return oldcb;
|
|
|
|
case LIBSSH2_CALLBACK_RECV:
|
|
oldcb = session->recv;
|
|
session->recv = callback;
|
|
return oldcb;
|
|
}
|
|
_libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Setting Callback %d",
|
|
cbtype);
|
|
|
|
return NULL;
|
|
}
|
|
#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?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;
|
|
tv.tv_usec = (ms_to_next - tv.tv_sec*1000) * 1000;
|
|
|
|
if(dir & LIBSSH2_SESSION_BLOCK_INBOUND) {
|
|
FD_ZERO(&rfd);
|
|
FD_SET(session->socket_fd, &rfd);
|
|
readfd = &rfd;
|
|
}
|
|
|
|
if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND) {
|
|
FD_ZERO(&wfd);
|
|
FD_SET(session->socket_fd, &wfd);
|
|
writefd = &wfd;
|
|
}
|
|
|
|
rc = select(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) {
|
|
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 %d", 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-", (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", (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;
|
|
}
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
/*
|
|
* libssh2_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",
|
|
session->remote.banner);
|
|
|
|
session->free_state = libssh2_NB_state_created;
|
|
}
|
|
|
|
if(session->free_state == libssh2_NB_state_created) {
|
|
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) {
|
|
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->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 */
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* libssh2_session_disconnect_ex
|
|
*/
|
|
static int
|
|
session_disconnect(LIBSSH2_SESSION *session, int reason,
|
|
const char *description,
|
|
const char *lang)
|
|
{
|
|
unsigned char *s;
|
|
unsigned long 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");
|
|
|
|
/* 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, lang_len);
|
|
|
|
session->disconnect_state = libssh2_NB_state_created;
|
|
}
|
|
|
|
rc = _libssh2_transport_send(session, session->disconnect_data,
|
|
session->disconnect_data_len,
|
|
(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_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 = 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;
|
|
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_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 = 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) {
|
|
FD_SET(fds[i].fd.socket, &rfds);
|
|
if(fds[i].fd.socket > maxfd)
|
|
maxfd = fds[i].fd.socket;
|
|
}
|
|
if(fds[i].events & LIBSSH2_POLLFD_POLLOUT) {
|
|
FD_SET(fds[i].fd.socket, &wfds);
|
|
if(fds[i].fd.socket > maxfd)
|
|
maxfd = fds[i].fd.socket;
|
|
}
|
|
break;
|
|
|
|
case LIBSSH2_POLLFD_CHANNEL:
|
|
FD_SET(fds[i].fd.channel->session->socket_fd, &rfds);
|
|
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:
|
|
FD_SET(fds[i].fd.listener->session->socket_fd, &rfds);
|
|
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
|
|
|
|
#ifdef HAVE_LIBSSH2_GETTIMEOFDAY
|
|
{
|
|
struct timeval tv_begin, tv_end;
|
|
|
|
_libssh2_gettimeofday((struct timeval *) &tv_begin, NULL);
|
|
sysret = poll(sockets, nfds, timeout_remaining);
|
|
_libssh2_gettimeofday((struct timeval *) &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;
|
|
}
|
|
#else
|
|
/* If the platform doesn't support gettimeofday,
|
|
* then just make the call non-blocking and walk away
|
|
*/
|
|
sysret = poll(sockets, nfds, timeout_remaining);
|
|
timeout_remaining = 0;
|
|
#endif /* HAVE_GETTIMEOFDAY */
|
|
|
|
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;
|
|
tv.tv_usec = (timeout_remaining % 1000) * 1000;
|
|
#ifdef HAVE_LIBSSH2_GETTIMEOFDAY
|
|
{
|
|
struct timeval tv_begin, tv_end;
|
|
|
|
_libssh2_gettimeofday((struct timeval *) &tv_begin, NULL);
|
|
sysret = select(maxfd + 1, &rfds, &wfds, NULL, &tv);
|
|
_libssh2_gettimeofday((struct timeval *) &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;
|
|
}
|
|
#else
|
|
/* If the platform doesn't support gettimeofday,
|
|
* then just make the call non-blocking and walk away
|
|
*/
|
|
sysret = select(maxfd + 1, &rfds, &wfds, NULL, &tv);
|
|
timeout_remaining = 0;
|
|
#endif
|
|
|
|
if(sysret > 0) {
|
|
for(i = 0; i < nfds; i++) {
|
|
switch(fds[i].type) {
|
|
case LIBSSH2_POLLFD_SOCKET:
|
|
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++;
|
|
}
|
|
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(NULL == session)
|
|
return NULL;
|
|
|
|
if(NULL == session->remote.banner)
|
|
return NULL;
|
|
|
|
return (const char *) session->remote.banner;
|
|
}
|
|
#endif
|