/* {{{ 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; }
/* * 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 rc; 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 */ 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, session->fullpacket_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; session->fullpacket_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! */ session->fullpacket_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); session->fullpacket_payload_len = data_len; } } } session->fullpacket_packet_type = p->payload[0]; debugdump(session, "libssh2_packet_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 == PACKET_EAGAIN) { return PACKET_EAGAIN; } else if (rc < 0) { return PACKET_FAIL; } } session->fullpacket_state = libssh2_NB_state_idle; return session->fullpacket_packet_type; }