blob: cef0581a3cd835c8fb9edc57f1e5a44c4e7af56e [file] [log] [blame]
/* Copyright (C) 2007 The Written Word, Inc. All rights reserved.
* 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 <assert.h>
#define MAX_BLOCKSIZE 32 /* MUST fit biggest crypto block size we use/get */
#define MAX_MACSIZE 20 /* MUST fit biggest MAC length we support */
#ifdef LIBSSH2DEBUG
#define UNPRINTABLE_CHAR '.'
static void debugdump(LIBSSH2_SESSION *session,
const char *desc, unsigned char *ptr,
unsigned long size)
{
size_t i;
size_t c;
FILE *stream = stdout;
unsigned int width=0x10;
if(!(session->showmask & (1<< LIBSSH2_DBG_TRANS))) {
/* not asked for, bail out */
return;
}
fprintf(stream, "=> %s (%d bytes)\n", desc, (int)size);
for(i=0; i<size; i+= width) {
fprintf(stream, "%04x: ", i);
/* hex not disabled, show it */
for(c = 0; c < width; c++) {
if(i+c < size)
fprintf(stream, "%02x ", ptr[i+c]);
else
fputs(" ", stream);
}
for(c = 0; (c < width) && (i+c < size); c++) {
fprintf(stream, "%c",
(ptr[i+c]>=0x20) &&
(ptr[i+c]<0x80)?ptr[i+c]:UNPRINTABLE_CHAR);
}
fputc('\n', stream); /* newline */
}
fflush(stream);
}
#else
#define debugdump(a,x,y,z)
#endif
/* decrypt() decrypts 'len' bytes from 'source' to 'dest'.
*
* returns PACKET_NONE on success and PACKET_FAIL on failure
*/
static libssh2pack_t 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,
&session->remote.crypt_abstract)) {
libssh2_error(session, LIBSSH2_ERROR_DECRYPT,
(char *)"Error decrypting packet", 0);
LIBSSH2_FREE(session, p->payload);
return PACKET_FAIL;
}
/* 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 PACKET_NONE; /* all is fine */
}
/*
* fullpacket() gets called when a full packet has been received and properly
* collected.
*/
static libssh2pack_t fullpacket(LIBSSH2_SESSION *session,
int encrypted /* 1 or 0 */)
{
unsigned char macbuf[MAX_MACSIZE];
struct transportpacket *p = &session->packet;
int payload_len = p->packet_length-1;
libssh2pack_t packet_type;
int macstate = LIBSSH2_MAC_CONFIRMED;
if(encrypted) {
/* Calculate MAC hash */
session->remote.mac->hash(session,
macbuf, /* store hash here */
session->remote.seqno,
p->init, 5,
p->payload, 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 + payload_len,
session->remote.mac->mac_len)) {
macstate = LIBSSH2_MAC_INVALID;
}
}
session->remote.seqno++;
/* ignore the padding */
payload_len -= p->padding_length;
/* Check for and deal with decompression */
if (session->remote.comp &&
strcmp(session->remote.comp->name, "none")) {
unsigned char *data;
unsigned long data_len;
int free_payload = 1;
if (session->remote.comp->comp(session, 0,
&data, &data_len,
LIBSSH2_PACKET_MAXDECOMP,
&free_payload,
p->payload, payload_len,
&session->remote.comp_abstract)) {
LIBSSH2_FREE(session, p->payload);
return PACKET_FAIL;
}
if (free_payload) {
LIBSSH2_FREE(session, p->payload);
p->payload = data;
payload_len = data_len;
}
else {
if (data == p->payload) {
/* It's not to be freed, because the
* compression layer reused payload, So let's
* do the same!
*/
payload_len = data_len;
}
else {
/* No comp_method actually lets this happen,
* but let's prepare for the future */
LIBSSH2_FREE(session, p->payload);
/* We need a freeable struct otherwise the
* brigade won't know what to do with it */
p->payload = LIBSSH2_ALLOC(session, data_len);
if (!p->payload) {
libssh2_error(session,
LIBSSH2_ERROR_ALLOC,
(char *)"Unable to allocate memory for copy of uncompressed data", 0);
return PACKET_ENOMEM;
}
memcpy(p->payload, data, data_len);
payload_len = data_len;
}
}
}
packet_type = p->payload[0];
debugdump(session, "libssh2_packet_read() plain",
p->payload, payload_len);
if (libssh2_packet_add(session, p->payload, payload_len, macstate) < 0)
return PACKET_FAIL;
return packet_type;
}
/* {{{ libssh2_packet_read
* Collect a packet into the input brigade
* block only controls whether or not to wait for a packet to start,
* Once a packet starts, libssh2 will block until it is complete
* Returns packet type added to input brigade (PACKET_NONE if nothing added),
* or PACKET_FAIL on failure and PACKET_EAGAIN if it couldn't process a full
* packet.
*/
/*
* This function reads the binary stream as specified in chapter 6 of RFC4253
* "The Secure Shell (SSH) Transport Layer Protocol"
*/
libssh2pack_t libssh2_packet_read(LIBSSH2_SESSION *session)
{
libssh2pack_t rc;
struct transportpacket *p = &session->packet;
int remainbuf;
int remainpack;
int numbytes;
int numdecrypt;
unsigned char block[MAX_BLOCKSIZE];
int blocksize;
int minimum;
int encrypted = 1;
do {
if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) {
return PACKET_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 */
}
minimum = p->total_num ? p->total_num - p->data_num : blocksize;
/* 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);
while(remainbuf < minimum) {
/* While there 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 = recv(session->socket_fd, &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) && (errno == EAGAIN)) {
return PACKET_EAGAIN;
}
return PACKET_FAIL;
}
debugdump(session, "libssh2_packet_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(encrypted) {
rc = decrypt(session, &p->buf[p->readidx],
block, blocksize);
if(rc != PACKET_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);
p->padding_length = block[4];
/* total_num is the number of bytes following the
initial (5 bytes) packet length and padding length
fields */
p->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(p->total_num > LIBSSH2_PACKET_MAXPAYLOAD) {
return PACKET_TOOBIG;
}
/* Get a packet handle put data into. We get one to
hold all data, including padding and MAC. */
p->payload = LIBSSH2_ALLOC(session, p->total_num);
if(!p->payload) {
return PACKET_ENOMEM;
}
/* 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->remote.mac->mac_len;
/* if what we have plus numbytes is bigger than the
total minus the skip margin, we should lower the
amount to decrypt even more */
if((p->data_num + numbytes) > (p->total_num - skip)) {
numdecrypt = (p->total_num - skip) -
p->data_num;
}
else {
int frac;
numdecrypt = numbytes;
frac = numdecrypt % blocksize;
if(frac) {
/* not an aligned amount of blocks,
align it */
numdecrypt -= frac;
/* and make it no unencrypted data
after it */
numbytes = 0;
}
}
}
else {
/* unencrypted data should not be decrypted at all */
numdecrypt = 0;
}
/* if there are bytes to decrypt, do that */
if(numdecrypt > 0) {
/* now decrypt the lot */
rc = decrypt(session, &p->buf[p->readidx],
p->wptr, numdecrypt);
if(rc != PACKET_NONE) {
return rc;
}
/* advance the read pointer */
p->readidx += numdecrypt;
/* advance write pointer */
p->wptr += numdecrypt;
/* increse data_num */
p->data_num += numdecrypt;
/* bytes left to take care of without decryption */
numbytes -= numdecrypt;
}
/* if there are bytes to copy that aren't decrypted, simply
copy them as-is to the target buffer */
if(numbytes > 0) {
memcpy(p->wptr, &p->buf[p->readidx], numbytes);
/* advance the read pointer */
p->readidx += numbytes;
/* advance write pointer */
p->wptr += numbytes;
/* increse data_num */
p->data_num += numbytes;
}
/* now check how much data there's left to read to finish the
current packet */
remainpack = p->total_num - p->data_num;
if(!remainpack) {
/* we have a full packet */
rc = fullpacket(session, encrypted);
p->total_num = 0; /* no packet buffer available */
return rc;
}
} while (1); /* loop */
return PACKET_FAIL; /* we never reach this point */
}
/* }}} */
#ifndef OLDSEND
static libssh2pack_t send_existing(LIBSSH2_SESSION *session,
unsigned char *data,
unsigned long data_len,
ssize_t *ret)
{
ssize_t rc;
ssize_t length;
struct transportpacket *p = &session->packet;
if(!p->outbuf) {
*ret = 0;
return PACKET_NONE;
}
/* send as much as possible of the existing packet */
if((data != p->odata) || (data_len != p->olen)) {
/* When we are about to complete the sending of a packet, it
is vital that the caller doesn't try to send a
new/different packet since we don't add this one up until
the previous one has been sent. To make the caller really
notice his/hers flaw, we return error for this case */
return PACKET_BADUSE;
}
*ret = 1; /* set to make our parent return */
/* number of bytes left to send */
length = p->ototal_num - p->osent;
rc = send(session->socket_fd,
&p->outbuf[p->osent],
length, LIBSSH2_SOCKET_SEND_FLAGS(session));
if(rc == length) {
/* the remainder of the package was sent */
LIBSSH2_FREE(session, p->outbuf);
p->outbuf = NULL;
p->ototal_num = 0;
}
else if(rc < 0) {
/* nothing was sent */
if(errno != EAGAIN) {
/* send failure! */
return PACKET_FAIL;
}
return PACKET_EAGAIN;
}
debugdump(session, "libssh2_packet_write send()",
&p->outbuf[p->osent], length);
p->osent += length; /* we sent away this much data */
return PACKET_NONE;
}
/* {{{ libssh2_packet_write
* Send a packet, encrypting it and adding a MAC code if necessary
* Returns 0 on success, non-zero on failure.
*
* Returns PACKET_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.
*/
int libssh2_packet_write(LIBSSH2_SESSION *session, unsigned char *data,
unsigned long data_len)
{
int blocksize =
(session->state & LIBSSH2_STATE_NEWKEYS) ?
session->local.crypt->blocksize : 8;
int padding_length;
int packet_length;
int total_length;
int free_data=0;
#ifdef RANDOM_PADDING
int rand_max;
int seed = data[0]; /* FIXME: make this random */
#endif
struct transportpacket *p = &session->packet;
int encrypted;
int i;
ssize_t ret;
libssh2pack_t rc;
unsigned char *orgdata = data;
unsigned long orgdata_len = data_len;
debugdump(session, "libssh2_packet_write plain", data, data_len);
/* FIRST, check if we have a pending write to complete */
rc = send_existing(session, data, data_len, &ret);
if(rc || ret)
return rc;
encrypted = (session->state & LIBSSH2_STATE_NEWKEYS)?1:0;
/* check if we should compress */
if (encrypted && strcmp(session->local.comp->name, "none")) {
if (session->local.comp->comp(session, 1, &data, &data_len,
LIBSSH2_PACKET_MAXCOMP,
&free_data, data, data_len,
&session->local.comp_abstract)) {
return PACKET_COMPRESS; /* compression failure */
}
}
/* RFC4253 says: Note that the length of the concatenation of
'packet_length', 'padding_length', 'payload', and 'random padding'
MUST be a multiple of the cipher block size or 8, whichever is
larger. */
/* Plain math: (4 + 1 + packet_length + padding_length) % blocksize ==
0 */
packet_length = data_len + 1 + 4; /* 1 is for padding_length field
4 for the packet_length field */
/* at this point we have it all except the padding */
/* first figure out our minimum padding amount to make it an even
block size */
padding_length = blocksize - (packet_length % blocksize);
/* if the padding becomes too small we add another blocksize worth
of it (taken from the original libssh2 where it didn't have any
real explanation) */
if (padding_length < 4) {
padding_length += blocksize;
}
#ifdef RANDOM_PADDING
/* FIXME: we can add padding here, but that also makes the packets
bigger etc */
/* now we can add 'blocksize' to the padding_length N number of times
(to "help thwart traffic analysis") but it must be less than 255 in
total */
rand_max = (255 - padding_length)/blocksize + 1;
padding_length += blocksize * (seed % rand_max);
#endif
packet_length += padding_length;
/* append the MAC length to the total_length size */
total_length = packet_length +
(encrypted?session->local.mac->mac_len:0);
/* allocate memory to store the outgoing packet in, in case we can't
send the whole one and thus need to keep it after this function
returns. */
p->outbuf = LIBSSH2_ALLOC(session, total_length);
if(!p->outbuf) {
return PACKET_ENOMEM;
}
/* store packet_length, which is the size of the whole packet except
the MAC and the packet_length field itself */
libssh2_htonu32(p->outbuf, packet_length - 4);
/* store padding_length */
p->outbuf[4] = padding_length;
/* copy the payload data */
memcpy(p->outbuf + 5, data, data_len);
/* fill the padding area with random junk */
libssh2_random(p->outbuf + 5 + data_len, padding_length);
if (free_data) {
LIBSSH2_FREE(session, data);
}
if(encrypted) {
/* Calculate MAC hash. Put the output at index packet_length,
since that size includes the whole packet. The MAC is
calculated on the entire unencrypted packet, including all
fields except the MAC field itself. */
session->local.mac->hash(session,
p->outbuf + packet_length,
session->local.seqno,
p->outbuf, packet_length,
NULL, 0,
&session->local.mac_abstract);
/* Encrypt the whole packet data, one block size at a time.
The MAC field is not encrypted. */
for(i=0; i < packet_length;
i += session->local.crypt->blocksize) {
unsigned char *ptr = &p->outbuf[i];
if(session->local.crypt->crypt(session, ptr,
&session->local.crypt_abstract))
return PACKET_FAIL; /* encryption failure */
}
}
session->local.seqno++;
ret = send(session->socket_fd, p->outbuf,
total_length, LIBSSH2_SOCKET_SEND_FLAGS(session));
if(ret != -1) {
debugdump(session, "libssh2_packet_write send()",
p->outbuf, ret);
}
if(ret != total_length) {
if((ret > 0 ) ||
((ret == -1) && (errno == EAGAIN))) {
/* the whole packet could not be sent, save the rest */
p->odata = orgdata;
p->olen = orgdata_len;
p->osent = (ret == -1)?0:ret;
p->ototal_num = total_length;
return PACKET_EAGAIN;
}
return PACKET_FAIL;
}
/* the whole thing got sent away */
p->odata = NULL;
p->olen = 0;
LIBSSH2_FREE(session, p->outbuf);
p->outbuf = NULL;
return PACKET_NONE; /* all is good */
}
/* }}} */
#endif