/* {{{ 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 (0 if nothing added), or -1 on failure */ int libssh2_packet_read(LIBSSH2_SESSION *session, int should_block) { int packet_type = -1; if (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) { return 0; } #ifndef WIN32 fcntl(session->socket_fd, F_SETFL, O_NONBLOCK); #else { u_long non_block = TRUE; ioctlsocket(session->socket_fd, FIONBIO, &non_block); } #endif #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Checking for packet: will%s block", should_block ? "" : " not"); #endif if (session->state & LIBSSH2_STATE_NEWKEYS) { /* Temporary Buffer * The largest blocksize (currently) is 32, the largest MAC (currently) is 20 */ unsigned char block[2 * 32], *payload, *s, tmp[6]; long read_len; unsigned long blocksize = session->remote.crypt->blocksize; unsigned long packet_len, payload_len; int padding_len; int macstate; int free_payload = 1; /* Safely ignored in CUSTOM cipher mode */ EVP_CIPHER_CTX *ctx = (EVP_CIPHER_CTX *)session->remote.crypt_abstract; /* Note: If we add any cipher with a blocksize less than 6 we'll need to get more creative with this * For now, all blocksize sizes are 8+ */ if (should_block) { read_len = libssh2_blocking_read(session, block, blocksize); } else { read_len = recv(session->socket_fd, block, 1, LIBSSH2_SOCKET_RECV_FLAGS(session)); if (read_len <= 0) { return 0; } read_len += libssh2_blocking_read(session, block + read_len, blocksize - read_len); } if (read_len < blocksize) { return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; } if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { EVP_Cipher(ctx, block + blocksize, block, blocksize); memcpy(block, block + blocksize, blocksize); } else { if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); return -1; } } packet_len = libssh2_ntohu32(block); padding_len = block[4]; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Processing packet %lu bytes long (with %lu bytes padding)", packet_len, padding_len); #endif memcpy(tmp, block, 5); /* Use this for MAC later */ payload_len = packet_len - 1; /* padding_len(1) */ /* Sanity Check */ if ((payload_len > LIBSSH2_PACKET_MAXPAYLOAD) || ((packet_len + 4) % blocksize)) { /* If something goes horribly wrong during the decryption phase, just bailout and die gracefully */ session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; libssh2_error(session, LIBSSH2_ERROR_PROTO, "Fatal protocol error, invalid payload size", 0); return -1; } s = payload = LIBSSH2_ALLOC(session, payload_len); memcpy(s, block + 5, blocksize - 5); s += blocksize - 5; while ((s - payload) < payload_len) { read_len = libssh2_blocking_read(session, block, blocksize); if (read_len < blocksize) { LIBSSH2_FREE(session, payload); return -1; } if (session->remote.crypt->flags & LIBSSH2_CRYPT_METHOD_FLAG_EVP) { EVP_Cipher(ctx, block + blocksize, block, blocksize); memcpy(s, block + blocksize, blocksize); } else { if (session->remote.crypt->crypt(session, block, &session->remote.crypt_abstract)) { libssh2_error(session, LIBSSH2_ERROR_DECRYPT, "Error decrypting packet preamble", 0); LIBSSH2_FREE(session, payload); return -1; } memcpy(s, block, blocksize); } s += blocksize; } read_len = libssh2_blocking_read(session, block, session->remote.mac->mac_len); if (read_len < session->remote.mac->mac_len) { LIBSSH2_FREE(session, payload); return -1; } /* Calculate MAC hash */ session->remote.mac->hash(session, block + session->remote.mac->mac_len, session->remote.seqno, tmp, 5, payload, payload_len, &session->remote.mac_abstract); macstate = (strncmp(block, block + session->remote.mac->mac_len, session->remote.mac->mac_len) == 0) ? LIBSSH2_MAC_CONFIRMED : LIBSSH2_MAC_INVALID; session->remote.seqno++; /* Ignore padding */ payload_len -= padding_len; if (session->remote.comp && strcmp(session->remote.comp->name, "none")) { /* Decompress */ unsigned char *data; unsigned long data_len; if (session->remote.comp->comp(session, 0, &data, &data_len, LIBSSH2_PACKET_MAXDECOMP, &free_payload, payload, payload_len, &session->remote.comp_abstract)) { LIBSSH2_FREE(session, payload); return -1; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Payload decompressed: %lu bytes(compressed) to %lu bytes(uncompressed)", data_len, payload_len); #endif if (free_payload) { LIBSSH2_FREE(session, payload); payload = data; payload_len = data_len; } else { if (data == 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, payload); /* We need a freeable struct otherwise the brigade won't know what to do with it */ payload = LIBSSH2_ALLOC(session, data_len); if (!payload) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for copy of uncompressed data", 0); return -1; } memcpy(payload, data, data_len); payload_len = data_len; } } } packet_type = payload[0]; libssh2_packet_add(session, payload, payload_len, macstate); } else { /* No cipher active */ unsigned char *payload; unsigned char buf[24]; unsigned long buf_len, payload_len; unsigned long packet_length; unsigned long padding_length; if (should_block) { buf_len = libssh2_blocking_read(session, buf, 5); } else { buf_len = recv(session->socket_fd, buf, 1, LIBSSH2_SOCKET_RECV_FLAGS(session)); if (buf_len <= 0) { return 0; } buf_len += libssh2_blocking_read(session, buf, 5 - buf_len); } if (buf_len < 5) { /* Something bad happened */ return -1; } packet_length = libssh2_ntohu32(buf); padding_length = buf[4]; #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Processing plaintext packet %lu bytes long (with %lu bytes padding)", packet_length, padding_length); #endif payload_len = packet_length - padding_length - 1; /* padding_length(1) */ payload = LIBSSH2_ALLOC(session, payload_len); if (!payload) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for copy of plaintext data", 0); return -1; } if (libssh2_blocking_read(session, payload, payload_len) < payload_len) { return (session->socket_state == LIBSSH2_SOCKET_DISCONNECTED) ? 0 : -1; } while (padding_length) { int l; /* Flush padding */ l = libssh2_blocking_read(session, buf, padding_length); if (l > 0) padding_length -= l; else break; } packet_type = payload[0]; /* MACs don't exist in non-encrypted mode */ libssh2_packet_add(session, payload, payload_len, LIBSSH2_MAC_CONFIRMED); session->remote.seqno++; } return packet_type; }
/* * 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 encrypted = 1; /* * =============================== 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_packet_read_point1; } 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 */ } /* 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 = 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 */ #ifdef WIN32 switch (WSAGetLastError()) { case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAENOTSOCK: errno = EBADF; break; case WSAENOTCONN: case WSAECONNABORTED: errno = WSAENOTCONN; break; case WSAEINTR: errno = EINTR; break; } #endif /* WIN32 */ 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 (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 */ return PACKET_EAGAIN; } 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 */ libssh2_packet_read_point1: rc = fullpacket(session, encrypted); if (rc == PACKET_EAGAIN) { session->readPack_encrypted = encrypted; session->readPack_state = libssh2_NB_state_jump1; return PACKET_EAGAIN; } p->total_num = 0; /* no packet buffer available */ return rc; } } while (1); /* loop */ return PACKET_FAIL; /* we never reach this point */ }
/* {{{ libssh2_blocking_read * Force a blocking read, regardless of socket settings */ static int libssh2_blocking_read(LIBSSH2_SESSION *session, unsigned char *buf, size_t count) { size_t bytes_read = 0; #if !defined(HAVE_POLL) && !defined(HAVE_SELECT) int polls = 0; #endif #ifndef WIN32 fcntl(session->socket_fd, F_SETFL, 0); #else { u_long block = FALSE; ioctlsocket(session->socket_fd, FIONBIO, &block); } #endif #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking read: %d bytes", (int)count); #endif while (bytes_read < count) { int ret; ret = recv(session->socket_fd, buf + bytes_read, count - bytes_read, LIBSSH2_SOCKET_RECV_FLAGS(session)); if (ret < 0) { #ifdef WIN32 switch (WSAGetLastError()) { case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAENOTSOCK: errno = EBADF; break; case WSAENOTCONN: case WSAECONNABORTED: errno = ENOTCONN; break; case WSAEINTR: errno = EINTR; break; } #endif if (errno == EAGAIN) { #ifdef HAVE_POLL struct pollfd read_socket; read_socket.fd = session->socket_fd; read_socket.events = POLLIN; if (poll(&read_socket, 1, 30000) <= 0) { return -1; } #elif defined(HAVE_SELECT) fd_set read_socket; struct timeval timeout; FD_ZERO(&read_socket); FD_SET(session->socket_fd, &read_socket); timeout.tv_sec = 30; timeout.tv_usec = 0; if (select(session->socket_fd + 1, &read_socket, NULL, NULL, &timeout) <= 0) { return -1; } #else if (polls++ > LIBSSH2_SOCKET_POLL_MAXLOOPS) { return -1; } u_sleep(LIBSSH2_SOCKET_POLL_UDELAY); #endif /* POLL/SELECT/SLEEP */ continue; } if (errno == EINTR) { continue; } if ((errno == EBADF) || (errno == EIO) || (errno == ENOTCONN)) { session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; } return -1; } if (ret == 0) continue; bytes_read += ret; } #ifdef LIBSSH2_DEBUG_TRANSPORT _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Blocking read: %d bytes actually read", (int)bytes_read); #endif return bytes_read; }
/* {{{ libssh2_banner_receive * Wait for a hello from the remote host * Allocate a buffer and store the banner in session->remote.banner * Returns: 0 on success, PACKET_EAGAIN if read would block, 1 on failure */ static int libssh2_banner_receive(LIBSSH2_SESSION * session) { int ret; int banner_len; 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'; ret = recv(session->socket_fd, &c, 1, LIBSSH2_SOCKET_RECV_FLAGS(session)); if (ret < 0) { #ifdef WIN32 switch (WSAGetLastError()) { case WSAEWOULDBLOCK: errno = EAGAIN; break; case WSAENOTSOCK: errno = EBADF; break; case WSAENOTCONN: case WSAECONNABORTED: errno = WSAENOTCONN; break; case WSAEINTR: errno = EINTR; break; } #endif /* WIN32 */ if (errno == EAGAIN) { session->banner_TxRx_total_send = banner_len; return PACKET_EAGAIN; } /* Some kinda error */ session->banner_TxRx_state = libssh2_NB_state_idle; session->banner_TxRx_total_send = 0; return 1; } if (ret == 0) { session->socket_state = LIBSSH2_SOCKET_DISCONNECTED; return PACKET_FAIL; } 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 1; } 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 1; session->remote.banner = LIBSSH2_ALLOC(session, banner_len + 1); if (!session->remote.banner) { libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Error allocating space for remote banner", 0); return 1; } memcpy(session->remote.banner, session->banner_TxRx_banner, banner_len); session->remote.banner[banner_len] = '\0'; _libssh2_debug(session, LIBSSH2_DBG_TRANS, "Received Banner: %s", session->remote.banner); return 0; }