libssh2/src/transport.c

892 lines
33 KiB
C
Raw Normal View History

/* Copyright (C) 2007 The Written Word, Inc. All rights reserved.
* Copyright (C) 2009-2010 by Daniel Stenberg
* Author: Daniel Stenberg <daniel@haxx.se>
*
* 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.
*
* This file handles reading and writing to the SECSH transport layer. RFC4253.
*/
#include "libssh2_priv.h"
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#ifdef LIBSSH2DEBUG
#include <stdio.h>
#endif
#include <assert.h>
#include "transport.h"
#include "mac.h"
#define MAX_BLOCKSIZE 32 /* MUST fit biggest crypto block size we use/get */
#define MAX_MACSIZE 64 /* MUST fit biggest MAC length we support */
#ifdef LIBSSH2DEBUG
#define UNPRINTABLE_CHAR '.'
static void
debugdump(LIBSSH2_SESSION * session,
const char *desc, const unsigned char *ptr, size_t size)
{
size_t i;
size_t c;
unsigned int width = 0x10;
char buffer[256]; /* Must be enough for width*4 + about 30 or so */
size_t used;
static const char* hex_chars = "0123456789ABCDEF";
if (!(session->showmask & LIBSSH2_TRACE_TRANS)) {
/* not asked for, bail out */
return;
}
used = snprintf(buffer, sizeof(buffer), "=> %s (%d bytes)\n",
desc, (int) size);
if (session->tracehandler)
(session->tracehandler)(session, session->tracehandler_context,
buffer, used);
else
fprintf(stderr, "%s", buffer);
for(i = 0; i < size; i += width) {
used = snprintf(buffer, sizeof(buffer), "%04lx: ", (long)i);
/* hex not disabled, show it */
for(c = 0; c < width; c++) {
if (i + c < size) {
buffer[used++] = hex_chars[(ptr[i+c] >> 4) & 0xF];
buffer[used++] = hex_chars[ptr[i+c] & 0xF];
}
else {
buffer[used++] = ' ';
buffer[used++] = ' ';
}
buffer[used++] = ' ';
if ((width/2) - 1 == c)
buffer[used++] = ' ';
}
buffer[used++] = ':';
buffer[used++] = ' ';
for(c = 0; (c < width) && (i + c < size); c++) {
buffer[used++] = isprint(ptr[i + c]) ?
ptr[i + c] : UNPRINTABLE_CHAR;
}
buffer[used++] = '\n';
buffer[used] = 0;
if (session->tracehandler)
(session->tracehandler)(session, session->tracehandler_context,
buffer, used);
else
fprintf(stderr, "%s", buffer);
}
}
#else
#define debugdump(a,x,y,z)
#endif
/* decrypt() decrypts 'len' bytes from 'source' to 'dest'.
*
2009-08-24 23:57:15 +02:00
* returns 0 on success and negative on failure
*/
2009-08-24 23:57:15 +02:00
static int
decrypt(LIBSSH2_SESSION * session, unsigned char *source,
unsigned char *dest, int len)
{
struct transportpacket *p = &session->packet;
int blocksize = session->remote.crypt->blocksize;
/* if we get called with a len that isn't an even number of blocksizes
we risk losing those extra bytes */
assert((len % blocksize) == 0);
while (len >= blocksize) {
if (session->remote.crypt->crypt(session, source, blocksize,
&session->remote.crypt_abstract)) {
LIBSSH2_FREE(session, p->payload);
return LIBSSH2_ERROR_DECRYPT;
}
/* if the crypt() function would write to a given address it
wouldn't have to memcpy() and we could avoid this memcpy()
too */
memcpy(dest, source, blocksize);
len -= blocksize; /* less bytes left */
dest += blocksize; /* advance write pointer */
source += blocksize; /* advance read pointer */
}
return LIBSSH2_ERROR_NONE; /* all is fine */
}
/*
* fullpacket() gets called when a full packet has been received and properly
* collected.
*/
2009-08-24 23:57:15 +02:00
static int
fullpacket(LIBSSH2_SESSION * session, int encrypted /* 1 or 0 */ )
{
unsigned char macbuf[MAX_MACSIZE];
struct transportpacket *p = &session->packet;
int rc;
int compressed;
if (session->fullpacket_state == libssh2_NB_state_idle) {
session->fullpacket_macstate = LIBSSH2_MAC_CONFIRMED;
session->fullpacket_payload_len = p->packet_length - 1;
if (encrypted) {
/* Calculate MAC hash */
session->remote.mac->hash(session, macbuf, /* store hash here */
session->remote.seqno,
p->init, 5,
p->payload,
session->fullpacket_payload_len,
&session->remote.mac_abstract);
/* Compare the calculated hash with the MAC we just read from
* the network. The read one is at the very end of the payload
* buffer. Note that 'payload_len' here is the packet_length
* field which includes the padding but not the MAC.
*/
if (memcmp(macbuf, p->payload + session->fullpacket_payload_len,
session->remote.mac->mac_len)) {
session->fullpacket_macstate = LIBSSH2_MAC_INVALID;
}
}
session->remote.seqno++;
/* ignore the padding */
session->fullpacket_payload_len -= p->padding_length;
/* Check for and deal with decompression */
compressed =
session->local.comp != NULL &&
session->local.comp->compress &&
((session->state & LIBSSH2_STATE_AUTHENTICATED) ||
session->local.comp->use_in_auth);
if (compressed && session->remote.comp_abstract) {
/*
* The buffer for the decompression (remote.comp_abstract) is
* initialised in time when it is needed so as long it is NULL we
* cannot decompress.
*/
unsigned char *data;
size_t data_len;
rc = session->remote.comp->decomp(session,
&data, &data_len,
LIBSSH2_PACKET_MAXDECOMP,
p->payload,
session->fullpacket_payload_len,
&session->remote.comp_abstract);
LIBSSH2_FREE(session, p->payload);
if(rc)
return rc;
p->payload = data;
session->fullpacket_payload_len = data_len;
}
session->fullpacket_packet_type = p->payload[0];
debugdump(session, "libssh2_transport_read() plain",
p->payload, session->fullpacket_payload_len);
session->fullpacket_state = libssh2_NB_state_created;
}
if (session->fullpacket_state == libssh2_NB_state_created) {
rc = _libssh2_packet_add(session, p->payload,
session->fullpacket_payload_len,
session->fullpacket_macstate);
if (rc == LIBSSH2_ERROR_EAGAIN)
return rc;
if (rc) {
session->fullpacket_state = libssh2_NB_state_idle;
return rc;
}
}
session->fullpacket_state = libssh2_NB_state_idle;
return session->fullpacket_packet_type;
}
/*
* _libssh2_transport_read
*
2009-08-24 23:57:15 +02:00
* Collect a packet into the input queue.
*
2009-08-24 23:57:15 +02:00
* Returns packet type added to input queue (0 if nothing added), or a
* negative error number.
*/
/*
* This function reads the binary stream as specified in chapter 6 of RFC4253
* "The Secure Shell (SSH) Transport Layer Protocol"
*
* DOES NOT call _libssh2_error() for ANY error case.
*/
int _libssh2_transport_read(LIBSSH2_SESSION * session)
{
int rc;
struct transportpacket *p = &session->packet;
int remainbuf;
int remainpack;
int numbytes;
int numdecrypt;
unsigned char block[MAX_BLOCKSIZE];
int blocksize;
int encrypted = 1;
size_t total_num;
/* default clear the bit */
session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_INBOUND;
/*
* All channels, systems, subsystems, etc eventually make it down here
* when looking for more incoming data. If a key exchange is going on
* (LIBSSH2_STATE_EXCHANGING_KEYS bit is set) then the remote end will
* ONLY send key exchange related traffic. In non-blocking mode, there is
* a chance to break out of the kex_exchange function with an EAGAIN
* status, and never come back to it. If LIBSSH2_STATE_EXCHANGING_KEYS is
* active, then we must redirect to the key exchange. However, if
* kex_exchange is active (as in it is the one that calls this execution
* of packet_read, then don't redirect, as that would be an infinite loop!
*/
if (session->state & LIBSSH2_STATE_EXCHANGING_KEYS &&
!(session->state & LIBSSH2_STATE_KEX_ACTIVE)) {
/* Whoever wants a packet won't get anything until the key re-exchange
* is done!
*/
_libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the"
" key re-exchange from _libssh2_transport_read");
rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state);
if (rc)
return rc;
}
/*
* =============================== NOTE ===============================
* I know this is very ugly and not a really good use of "goto", but
* this case statement would be even uglier to do it any other way
*/
if (session->readPack_state == libssh2_NB_state_jump1) {
session->readPack_state = libssh2_NB_state_idle;
encrypted = session->readPack_encrypted;
goto libssh2_transport_read_point1;
}
do {
if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) {
return LIBSSH2_ERROR_NONE;
}
if (session->state & LIBSSH2_STATE_NEWKEYS) {
blocksize = session->remote.crypt->blocksize;
} else {
encrypted = 0; /* not encrypted */
blocksize = 5; /* not strictly true, but we can use 5 here to
make the checks below work fine still */
}
/* read/use a whole big chunk into a temporary area stored in
the LIBSSH2_SESSION struct. We will decrypt data from that
buffer into the packet buffer so this temp one doesn't have
to be able to keep a whole SSH packet, just be large enough
so that we can read big chunks from the network layer. */
/* how much data there is remaining in the buffer to deal with
before we should read more from the network */
remainbuf = p->writeidx - p->readidx;
/* if remainbuf turns negative we have a bad internal error */
assert(remainbuf >= 0);
if (remainbuf < blocksize) {
/* If we have less than a blocksize left, it is too
little data to deal with, read more */
ssize_t nread;
/* move any remainder to the start of the buffer so
that we can do a full refill */
if (remainbuf) {
memmove(p->buf, &p->buf[p->readidx], remainbuf);
p->readidx = 0;
p->writeidx = remainbuf;
} else {
/* nothing to move, just zero the indexes */
p->readidx = p->writeidx = 0;
}
/* now read a big chunk from the network into the temp buffer */
nread =
LIBSSH2_RECV(session, &p->buf[remainbuf],
PACKETBUFSIZE - remainbuf,
LIBSSH2_SOCKET_RECV_FLAGS(session));
if (nread <= 0) {
/* check if this is due to EAGAIN and return the special
return code if so, error out normally otherwise */
if ((nread < 0) && (nread == -EAGAIN)) {
session->socket_block_directions |=
LIBSSH2_SESSION_BLOCK_INBOUND;
return LIBSSH2_ERROR_EAGAIN;
}
_libssh2_debug(session, LIBSSH2_TRACE_SOCKET,
"Error recving %d bytes (got %d)",
PACKETBUFSIZE - remainbuf, -nread);
return LIBSSH2_ERROR_SOCKET_RECV;
}
_libssh2_debug(session, LIBSSH2_TRACE_SOCKET,
"Recved %d/%d bytes to %p+%d", nread,
PACKETBUFSIZE - remainbuf, p->buf, remainbuf);
debugdump(session, "libssh2_transport_read() raw",
&p->buf[remainbuf], nread);
/* advance write pointer */
p->writeidx += nread;
/* update remainbuf counter */
remainbuf = p->writeidx - p->readidx;
}
/* how much data to deal with from the buffer */
numbytes = remainbuf;
if (!p->total_num) {
/* No payload package area allocated yet. To know the
size of this payload, we need to decrypt the first
blocksize data. */
if (numbytes < blocksize) {
/* we can't act on anything less than blocksize, but this
check is only done for the initial block since once we have
got the start of a block we can in fact deal with fractions
*/
session->socket_block_directions |=
LIBSSH2_SESSION_BLOCK_INBOUND;
return LIBSSH2_ERROR_EAGAIN;
}
if (encrypted) {
rc = decrypt(session, &p->buf[p->readidx], block, blocksize);
if (rc != LIBSSH2_ERROR_NONE) {
return rc;
}
/* save the first 5 bytes of the decrypted package, to be
used in the hash calculation later down. */
memcpy(p->init, &p->buf[p->readidx], 5);
} else {
/* the data is plain, just copy it verbatim to
the working block buffer */
memcpy(block, &p->buf[p->readidx], blocksize);
}
/* advance the read pointer */
p->readidx += blocksize;
/* we now have the initial blocksize bytes decrypted,
* and we can extract packet and padding length from it
*/
p->packet_length = _libssh2_ntohu32(block);
if (p->packet_length < 1)
return LIBSSH2_ERROR_DECRYPT;
p->padding_length = block[4];
/* total_num is the number of bytes following the initial
(5 bytes) packet length and padding length fields */
total_num =
p->packet_length - 1 +
(encrypted ? session->remote.mac->mac_len : 0);
/* RFC4253 section 6.1 Maximum Packet Length says:
*
* "All implementations MUST be able to process
* packets with uncompressed payload length of 32768
* bytes or less and total packet size of 35000 bytes
* or less (including length, padding length, payload,
* padding, and MAC.)."
*/
if (total_num > LIBSSH2_PACKET_MAXPAYLOAD) {
return LIBSSH2_ERROR_OUT_OF_BOUNDARY;
}
/* Get a packet handle put data into. We get one to
hold all data, including padding and MAC. */
p->payload = LIBSSH2_ALLOC(session, total_num);
if (!p->payload) {
return LIBSSH2_ERROR_ALLOC;
}
p->total_num = total_num;
/* init write pointer to start of payload buffer */
p->wptr = p->payload;
if (blocksize > 5) {
/* copy the data from index 5 to the end of
the blocksize from the temporary buffer to
the start of the decrypted buffer */
memcpy(p->wptr, &block[5], blocksize - 5);
p->wptr += blocksize - 5; /* advance write pointer */
}
/* init the data_num field to the number of bytes of
the package read so far */
p->data_num = p->wptr - p->payload;
/* we already dealt with a blocksize worth of data */
numbytes -= blocksize;
}
/* how much there is left to add to the current payload
package */
remainpack = p->total_num - p->data_num;
if (numbytes > remainpack) {
/* if we have more data in the buffer than what is going into this
particular packet, we limit this round to this packet only */
numbytes = remainpack;
}
if (encrypted) {
/* At the end of the incoming stream, there is a MAC,
and we don't want to decrypt that since we need it
"raw". We MUST however decrypt the padding data
since it is used for the hash later on. */
int skip = session->