diff options
Diffstat (limited to 'libs/libssh2/src/transport.c')
-rw-r--r-- | libs/libssh2/src/transport.c | 891 |
1 files changed, 891 insertions, 0 deletions
diff --git a/libs/libssh2/src/transport.c b/libs/libssh2/src/transport.c new file mode 100644 index 0000000000..8725da0950 --- /dev/null +++ b/libs/libssh2/src/transport.c @@ -0,0 +1,891 @@ +/* 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'. + * + * returns 0 on success and negative on failure + */ + +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. + */ +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 + * + * Collect a packet into the input queue. + * + * 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->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 != LIBSSH2_ERROR_NONE) { + p->total_num = 0; /* no packet buffer available */ + return rc; + } + + /* advance the read pointer */ + p->readidx += numdecrypt; + /* advance write pointer */ + p->wptr += numdecrypt; + /* increase 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; + /* increase 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 */ + libssh2_transport_read_point1: + rc = fullpacket(session, encrypted); + if (rc == LIBSSH2_ERROR_EAGAIN) { + + if (session->packAdd_state != libssh2_NB_state_idle) + { + /* fullpacket only returns LIBSSH2_ERROR_EAGAIN if + * libssh2_packet_add returns LIBSSH2_ERROR_EAGAIN. If that + * returns LIBSSH2_ERROR_EAGAIN but the packAdd_state is idle, + * then the packet has been added to the brigade, but some + * immediate action that was taken based on the packet + * type (such as key re-exchange) is not yet complete. + * Clear the way for a new packet to be read in. + */ + session->readPack_encrypted = encrypted; + session->readPack_state = libssh2_NB_state_jump1; + } + + return rc; + } + + p->total_num = 0; /* no packet buffer available */ + + return rc; + } + } while (1); /* loop */ + + return LIBSSH2_ERROR_SOCKET_RECV; /* we never reach this point */ +} + +static int +send_existing(LIBSSH2_SESSION *session, const unsigned char *data, + size_t data_len, ssize_t *ret) +{ + ssize_t rc; + ssize_t length; + struct transportpacket *p = &session->packet; + + if (!p->olen) { + *ret = 0; + return LIBSSH2_ERROR_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 LIBSSH2_ERROR_BAD_USE; + } + + *ret = 1; /* set to make our parent return */ + + /* number of bytes left to send */ + length = p->ototal_num - p->osent; + + rc = LIBSSH2_SEND(session, &p->outbuf[p->osent], length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if (rc < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", length, -rc); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Sent %d/%d bytes at %p+%d", rc, length, p->outbuf, + p->osent); + debugdump(session, "libssh2_transport_write send()", + &p->outbuf[p->osent], rc); + } + + if (rc == length) { + /* the remainder of the package was sent */ + p->ototal_num = 0; + p->olen = 0; + /* we leave *ret set so that the parent returns as we MUST return back + a send success now, so that we don't risk sending EAGAIN later + which then would confuse the parent function */ + return LIBSSH2_ERROR_NONE; + + } + else if (rc < 0) { + /* nothing was sent */ + if (rc != -EAGAIN) + /* send failure! */ + return LIBSSH2_ERROR_SOCKET_SEND; + + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + return LIBSSH2_ERROR_EAGAIN; + } + + p->osent += rc; /* we sent away this much data */ + + return rc < length ? LIBSSH2_ERROR_EAGAIN : LIBSSH2_ERROR_NONE; +} + +/* + * libssh2_transport_send + * + * Send a packet, encrypting it and adding a MAC code if necessary + * Returns 0 on success, non-zero on failure. + * + * The data is provided as _two_ data areas that are combined by this + * function. The 'data' part is sent immediately before 'data2'. 'data2' may + * be set to NULL to only use a single part. + * + * Returns LIBSSH2_ERROR_EAGAIN if it would block or if the whole packet was + * not sent yet. If it does so, the caller should call this function again as + * soon as it is likely that more data can be sent, and this function MUST + * then be called with the same argument set (same data pointer and same + * data_len) until ERROR_NONE or failure is returned. + * + * This function DOES NOT call _libssh2_error() on any errors. + */ +int _libssh2_transport_send(LIBSSH2_SESSION *session, + const unsigned char *data, size_t data_len, + const unsigned char *data2, size_t data2_len) +{ + int blocksize = + (session->state & LIBSSH2_STATE_NEWKEYS) ? + session->local.crypt->blocksize : 8; + int padding_length; + size_t packet_length; + int total_length; +#ifdef RANDOM_PADDING + int rand_max; + int seed = data[0]; /* FIXME: make this random */ +#endif + struct transportpacket *p = &session->packet; + int encrypted; + int compressed; + ssize_t ret; + int rc; + const unsigned char *orgdata = data; + size_t orgdata_len = data_len; + + /* + * If the last read operation was interrupted in the middle of a key + * exchange, we must complete that key exchange before continuing to write + * further data. + * + * See the similar block in _libssh2_transport_read for more details. + */ + if (session->state & LIBSSH2_STATE_EXCHANGING_KEYS && + !(session->state & LIBSSH2_STATE_KEX_ACTIVE)) { + /* Don't write any new packets if we're still in the middle of a key + * exchange. */ + _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Redirecting into the" + " key re-exchange from _libssh2_transport_send"); + rc = _libssh2_kex_exchange(session, 1, &session->startup_key_state); + if (rc) + return rc; + } + + debugdump(session, "libssh2_transport_write plain", data, data_len); + if(data2) + debugdump(session, "libssh2_transport_write plain2", data2, data2_len); + + /* FIRST, check if we have a pending write to complete. send_existing + only sanity-check data and data_len and not data2 and data2_len!! */ + rc = send_existing(session, data, data_len, &ret); + if (rc) + return rc; + + session->socket_block_directions &= ~LIBSSH2_SESSION_BLOCK_OUTBOUND; + + if (ret) + /* set by send_existing if data was sent */ + return rc; + + encrypted = (session->state & LIBSSH2_STATE_NEWKEYS) ? 1 : 0; + + compressed = + session->local.comp != NULL && + session->local.comp->compress && + ((session->state & LIBSSH2_STATE_AUTHENTICATED) || + session->local.comp->use_in_auth); + + if (encrypted && compressed) { + /* the idea here is that these function must fail if the output gets + larger than what fits in the assigned buffer so thus they don't + check the input size as we don't know how much it compresses */ + size_t dest_len = MAX_SSH_PACKET_LEN-5-256; + size_t dest2_len = dest_len; + + /* compress directly to the target buffer */ + rc = session->local.comp->comp(session, + &p->outbuf[5], &dest_len, + data, data_len, + &session->local.comp_abstract); + if(rc) + return rc; /* compression failure */ + + if(data2 && data2_len) { + /* compress directly to the target buffer right after where the + previous call put data */ + dest2_len -= dest_len; + + rc = session->local.comp->comp(session, + &p->outbuf[5+dest_len], &dest2_len, + data2, data2_len, + &session->local.comp_abstract); + } + else + dest2_len = 0; + if(rc) + return rc; /* compression failure */ + + data_len = dest_len + dest2_len; /* use the combined length */ + } + else { + if((data_len + data2_len) >= (MAX_SSH_PACKET_LEN-0x100)) + /* too large packet, return error for this until we make this + function split it up and send multiple SSH packets */ + return LIBSSH2_ERROR_INVAL; + + /* copy the payload data */ + memcpy(&p->outbuf[5], data, data_len); + if(data2 && data2_len) + memcpy(&p->outbuf[5+data_len], data2, data2_len); + data_len += data2_len; /* use the combined length */ + } + + + /* 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); + + /* 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] = (unsigned char)padding_length; + + /* fill the padding area with random junk */ + _libssh2_random(p->outbuf + 5 + data_len, padding_length); + + if (encrypted) { + size_t i; + + /* 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->blocksize, + &session->local.crypt_abstract)) + return LIBSSH2_ERROR_ENCRYPT; /* encryption failure */ + } + } + + session->local.seqno++; + + ret = LIBSSH2_SEND(session, p->outbuf, total_length, + LIBSSH2_SOCKET_SEND_FLAGS(session)); + if (ret < 0) + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, + "Error sending %d bytes: %d", total_length, -ret); + else { + _libssh2_debug(session, LIBSSH2_TRACE_SOCKET, "Sent %d/%d bytes at %p", + ret, total_length, p->outbuf); + debugdump(session, "libssh2_transport_write send()", p->outbuf, ret); + } + + if (ret != total_length) { + if (ret >= 0 || ret == -EAGAIN) { + /* the whole packet could not be sent, save the rest */ + session->socket_block_directions |= LIBSSH2_SESSION_BLOCK_OUTBOUND; + p->odata = orgdata; + p->olen = orgdata_len; + p->osent = ret <= 0 ? 0 : ret; + p->ototal_num = total_length; + return LIBSSH2_ERROR_EAGAIN; + } + return LIBSSH2_ERROR_SOCKET_SEND; + } + + /* the whole thing got sent away */ + p->odata = NULL; + p->olen = 0; + + return LIBSSH2_ERROR_NONE; /* all is good */ +} |