| /* 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 */ |
| } |