libssh2/src/session.c

1792 lines
54 KiB
C
Raw Normal View History

/* Copyright (c) 2004-2007 Sara Golemon <sarag@libssh2.org>
* Copyright (c) 2009-2015 by Daniel Stenberg
2010-02-25 15:58:52 +01:00
* Copyright (c) 2010 Simon Josefsson <simon@josefsson.org>
2004-12-07 21:17:20 +00:00
* 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
2004-12-07 21:17:20 +00:00
#include <unistd.h>
#endif
2004-12-07 21:17:20 +00:00
#include <stdlib.h>
#include <fcntl.h>
2004-12-07 21:17:20 +00:00
2005-03-23 00:21:26 +00:00
#ifdef HAVE_GETTIMEOFDAY
#include <sys/time.h>
#endif
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif
2005-03-23 00:21:26 +00:00
#include "transport.h"
#include "session.h"
#include "channel.h"
#include "mac.h"
#include "misc.h"
/* libssh2_default_alloc
2004-12-07 21:17:20 +00:00
*/
static
LIBSSH2_ALLOC_FUNC(libssh2_default_alloc)
2004-12-07 21:17:20 +00:00
{
(void) abstract;
return malloc(count);
2004-12-07 21:17:20 +00:00
}
/* libssh2_default_free
2004-12-07 21:17:20 +00:00
*/
static
LIBSSH2_FREE_FUNC(libssh2_default_free)
2004-12-07 21:17:20 +00:00
{
(void) abstract;
free(ptr);
2004-12-07 21:17:20 +00:00
}
/* libssh2_default_realloc
2004-12-07 21:17:20 +00:00
*/
static
LIBSSH2_REALLOC_FUNC(libssh2_default_realloc)
2004-12-07 21:17:20 +00:00
{
(void) abstract;
return realloc(ptr, count);
2004-12-07 21:17:20 +00:00
}
/*
* banner_receive
*
2004-12-07 21:17:20 +00:00
* 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
2004-12-07 21:17:20 +00:00
*/
static int
banner_receive(LIBSSH2_SESSION * session)
2004-12-07 21:17:20 +00:00
{
int ret;
int banner_len;
2004-12-07 21:17:20 +00:00
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';
2004-12-07 21:17:20 +00:00
/* 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);
2004-12-07 21:17:20 +00:00
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 == '\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;
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;
2004-12-07 21:17:20 +00:00
}
/*
* banner_send
*
2004-12-07 21:17:20 +00:00
* 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.
2004-12-07 21:17:20 +00:00
*/
static int
banner_send(LIBSSH2_SESSION * session)
2004-12-07 21:17:20 +00:00
{
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[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
}
2004-12-07 21:17:20 +00:00
/*
* get_socket_nonblocking()
*
* gets the given blocking or non-blocking state of the socket.
*/
static int
get_socket_nonblocking(int sockfd)
{ /* operate on this */
#undef GETBLOCK
#define GETBLOCK 0
#ifdef HAVE_O_NONBLOCK
/* most recent unix versions */
int flags;
if ((flags = fcntl(sockfd, F_GETFL, 0)) == -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) )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
2004-12-07 21:17:20 +00:00
}
/* libssh2_session_banner_set
* Set the local banner to use in the server handshake.
2004-12-24 23:10:15 +00:00
*/
LIBSSH2_API int
libssh2_session_banner_set(LIBSSH2_SESSION * session, const char *banner)
2004-12-24 23:10:15 +00:00
{
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;
2004-12-24 23:10:15 +00:00
}
/* 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)
2004-12-07 21:17:20 +00:00
*/
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)
2004-12-07 21:17:20 +00:00
{
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");
2010-03-19 09:14:21 +01:00
_libssh2_init_if_needed ();
}
return session;
2004-12-07 21:17:20 +00:00
}
/*
* libssh2_session_callback_set
*
2004-12-09 18:24:14 +00:00
* Set (or reset) a callback function
* Returns the prior address
*
* FIXME: this function relies on that we can typecast function pointers
* to void pointers, which isn't allowed in ISO C!
2004-12-09 18:24:14 +00:00
*/
LIBSSH2_API void *
libssh2_session_callback_set(LIBSSH2_SESSION * session,
int cbtype, void *callback)
2004-12-09 18:24:14 +00:00
{
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;