version 1.70.2.6, 2002/10/11 14:53:06 |
version 1.71, 2001/11/07 16:03:17 |
|
|
#include "mac.h" |
#include "mac.h" |
#include "log.h" |
#include "log.h" |
#include "canohost.h" |
#include "canohost.h" |
#include "misc.h" |
|
#include "ssh.h" |
|
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
#define DBG(x) x |
#define DBG(x) x |
|
|
static CipherContext send_context; |
static CipherContext send_context; |
|
|
/* Buffer for raw input data from the socket. */ |
/* Buffer for raw input data from the socket. */ |
Buffer input; |
static Buffer input; |
|
|
/* Buffer for raw output data going to the socket. */ |
/* Buffer for raw output data going to the socket. */ |
Buffer output; |
static Buffer output; |
|
|
/* Buffer for the partial outgoing packet being constructed. */ |
/* Buffer for the partial outgoing packet being constructed. */ |
static Buffer outgoing_packet; |
static Buffer outgoing_packet; |
|
|
|
|
/* Session key information for Encryption and MAC */ |
/* Session key information for Encryption and MAC */ |
Newkeys *newkeys[MODE_MAX]; |
Newkeys *newkeys[MODE_MAX]; |
static u_int32_t read_seqnr = 0; |
|
static u_int32_t send_seqnr = 0; |
|
|
|
/* Session key for protocol v1 */ |
|
static u_char ssh1_key[SSH_SESSION_KEY_LENGTH]; |
|
static u_int ssh1_keylen; |
|
|
|
/* roundup current message to extra_pad bytes */ |
/* roundup current message to extra_pad bytes */ |
static u_char extra_pad = 0; |
static u_char extra_pad = 0; |
|
|
|
|
packet_set_connection(int fd_in, int fd_out) |
packet_set_connection(int fd_in, int fd_out) |
{ |
{ |
Cipher *none = cipher_by_name("none"); |
Cipher *none = cipher_by_name("none"); |
|
|
if (none == NULL) |
if (none == NULL) |
fatal("packet_set_connection: cannot load cipher 'none'"); |
fatal("packet_set_connection: cannot load cipher 'none'"); |
connection_in = fd_in; |
connection_in = fd_in; |
connection_out = fd_out; |
connection_out = fd_out; |
cipher_init(&send_context, none, "", 0, NULL, 0, CIPHER_ENCRYPT); |
cipher_init(&send_context, none, (u_char *) "", 0, NULL, 0); |
cipher_init(&receive_context, none, "", 0, NULL, 0, CIPHER_DECRYPT); |
cipher_init(&receive_context, none, (u_char *) "", 0, NULL, 0); |
newkeys[MODE_IN] = newkeys[MODE_OUT] = NULL; |
newkeys[MODE_IN] = newkeys[MODE_OUT] = NULL; |
if (!initialized) { |
if (!initialized) { |
initialized = 1; |
initialized = 1; |
|
|
/* Returns 1 if remote host is connected via socket, 0 if not. */ |
/* Returns 1 if remote host is connected via socket, 0 if not. */ |
|
|
int |
int |
packet_connection_is_on_socket(void) |
packet_connection_is_on_socket() |
{ |
{ |
struct sockaddr_storage from, to; |
struct sockaddr_storage from, to; |
socklen_t fromlen, tolen; |
socklen_t fromlen, tolen; |
|
|
return 1; |
return 1; |
} |
} |
|
|
/* |
|
* Exports an IV from the CipherContext required to export the key |
|
* state back from the unprivileged child to the privileged parent |
|
* process. |
|
*/ |
|
|
|
void |
|
packet_get_keyiv(int mode, u_char *iv, u_int len) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &send_context; |
|
else |
|
cc = &receive_context; |
|
|
|
cipher_get_keyiv(cc, iv, len); |
|
} |
|
|
|
int |
|
packet_get_keycontext(int mode, u_char *dat) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &send_context; |
|
else |
|
cc = &receive_context; |
|
|
|
return (cipher_get_keycontext(cc, dat)); |
|
} |
|
|
|
void |
|
packet_set_keycontext(int mode, u_char *dat) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &send_context; |
|
else |
|
cc = &receive_context; |
|
|
|
cipher_set_keycontext(cc, dat); |
|
} |
|
|
|
int |
|
packet_get_keyiv_len(int mode) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &send_context; |
|
else |
|
cc = &receive_context; |
|
|
|
return (cipher_get_keyiv_len(cc)); |
|
} |
|
void |
|
packet_set_iv(int mode, u_char *dat) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &send_context; |
|
else |
|
cc = &receive_context; |
|
|
|
cipher_set_keyiv(cc, dat); |
|
} |
|
int |
|
packet_get_ssh1_cipher() |
|
{ |
|
return (cipher_get_number(receive_context.cipher)); |
|
} |
|
|
|
|
|
u_int32_t |
|
packet_get_seqnr(int mode) |
|
{ |
|
return (mode == MODE_IN ? read_seqnr : send_seqnr); |
|
} |
|
|
|
void |
|
packet_set_seqnr(int mode, u_int32_t seqnr) |
|
{ |
|
if (mode == MODE_IN) |
|
read_seqnr = seqnr; |
|
else if (mode == MODE_OUT) |
|
send_seqnr = seqnr; |
|
else |
|
fatal("packet_set_seqnr: bad mode %d", mode); |
|
} |
|
|
|
/* returns 1 if connection is via ipv4 */ |
/* returns 1 if connection is via ipv4 */ |
|
|
int |
int |
packet_connection_is_ipv4(void) |
packet_connection_is_ipv4() |
{ |
{ |
struct sockaddr_storage to; |
struct sockaddr_storage to; |
socklen_t tolen = sizeof(to); |
socklen_t tolen = sizeof(to); |
|
|
/* Sets the connection into non-blocking mode. */ |
/* Sets the connection into non-blocking mode. */ |
|
|
void |
void |
packet_set_nonblocking(void) |
packet_set_nonblocking() |
{ |
{ |
/* Set the socket into non-blocking mode. */ |
/* Set the socket into non-blocking mode. */ |
if (fcntl(connection_in, F_SETFL, O_NONBLOCK) < 0) |
if (fcntl(connection_in, F_SETFL, O_NONBLOCK) < 0) |
|
|
/* Returns the socket used for reading. */ |
/* Returns the socket used for reading. */ |
|
|
int |
int |
packet_get_connection_in(void) |
packet_get_connection_in() |
{ |
{ |
return connection_in; |
return connection_in; |
} |
} |
|
|
/* Returns the descriptor used for writing. */ |
/* Returns the descriptor used for writing. */ |
|
|
int |
int |
packet_get_connection_out(void) |
packet_get_connection_out() |
{ |
{ |
return connection_out; |
return connection_out; |
} |
} |
|
|
/* Closes the connection and clears and frees internal data structures. */ |
/* Closes the connection and clears and frees internal data structures. */ |
|
|
void |
void |
packet_close(void) |
packet_close() |
{ |
{ |
if (!initialized) |
if (!initialized) |
return; |
return; |
|
|
buffer_free(&compression_buffer); |
buffer_free(&compression_buffer); |
buffer_compress_uninit(); |
buffer_compress_uninit(); |
} |
} |
cipher_cleanup(&send_context); |
|
cipher_cleanup(&receive_context); |
|
} |
} |
|
|
/* Sets remote side protocol flags. */ |
/* Sets remote side protocol flags. */ |
|
|
/* Returns the remote protocol flags set earlier by the above function. */ |
/* Returns the remote protocol flags set earlier by the above function. */ |
|
|
u_int |
u_int |
packet_get_protocol_flags(void) |
packet_get_protocol_flags() |
{ |
{ |
return remote_protocol_flags; |
return remote_protocol_flags; |
} |
} |
|
|
* key is used for both sending and reception. However, both directions are |
* key is used for both sending and reception. However, both directions are |
* encrypted independently of each other. |
* encrypted independently of each other. |
*/ |
*/ |
|
|
void |
void |
packet_set_encryption_key(const u_char *key, u_int keylen, |
packet_set_encryption_key(const u_char *key, u_int keylen, |
int number) |
int number) |
{ |
{ |
Cipher *cipher = cipher_by_number(number); |
Cipher *cipher = cipher_by_number(number); |
|
|
if (cipher == NULL) |
if (cipher == NULL) |
fatal("packet_set_encryption_key: unknown cipher number %d", number); |
fatal("packet_set_encryption_key: unknown cipher number %d", number); |
if (keylen < 20) |
if (keylen < 20) |
fatal("packet_set_encryption_key: keylen too small: %d", keylen); |
fatal("packet_set_encryption_key: keylen too small: %d", keylen); |
if (keylen > SSH_SESSION_KEY_LENGTH) |
cipher_init(&receive_context, cipher, key, keylen, NULL, 0); |
fatal("packet_set_encryption_key: keylen too big: %d", keylen); |
cipher_init(&send_context, cipher, key, keylen, NULL, 0); |
memcpy(ssh1_key, key, keylen); |
|
ssh1_keylen = keylen; |
|
cipher_init(&send_context, cipher, key, keylen, NULL, 0, CIPHER_ENCRYPT); |
|
cipher_init(&receive_context, cipher, key, keylen, NULL, 0, CIPHER_DECRYPT); |
|
} |
} |
|
|
u_int |
|
packet_get_encryption_key(u_char *key) |
|
{ |
|
if (key == NULL) |
|
return (ssh1_keylen); |
|
memcpy(key, ssh1_key, ssh1_keylen); |
|
return (ssh1_keylen); |
|
} |
|
|
|
/* Start constructing a packet to send. */ |
/* Start constructing a packet to send. */ |
void |
void |
packet_start(u_char type) |
packet_start(u_char type) |
|
|
packet_put_char(int value) |
packet_put_char(int value) |
{ |
{ |
char ch = value; |
char ch = value; |
|
|
buffer_append(&outgoing_packet, &ch, 1); |
buffer_append(&outgoing_packet, &ch, 1); |
} |
} |
void |
void |
|
|
buffer_put_int(&outgoing_packet, value); |
buffer_put_int(&outgoing_packet, value); |
} |
} |
void |
void |
packet_put_string(const void *buf, u_int len) |
packet_put_string(const char *buf, u_int len) |
{ |
{ |
buffer_put_string(&outgoing_packet, buf, len); |
buffer_put_string(&outgoing_packet, buf, len); |
} |
} |
|
|
buffer_put_cstring(&outgoing_packet, str); |
buffer_put_cstring(&outgoing_packet, str); |
} |
} |
void |
void |
packet_put_raw(const void *buf, u_int len) |
packet_put_raw(const char *buf, u_int len) |
{ |
{ |
buffer_append(&outgoing_packet, buf, len); |
buffer_append(&outgoing_packet, buf, len); |
} |
} |
|
|
static void |
static void |
packet_send1(void) |
packet_send1(void) |
{ |
{ |
u_char buf[8], *cp; |
char buf[8], *cp; |
int i, padding, len; |
int i, padding, len; |
u_int checksum; |
u_int checksum; |
u_int32_t rand = 0; |
u_int32_t rand = 0; |
|
|
buffer_compress(&outgoing_packet, &compression_buffer); |
buffer_compress(&outgoing_packet, &compression_buffer); |
buffer_clear(&outgoing_packet); |
buffer_clear(&outgoing_packet); |
buffer_append(&outgoing_packet, buffer_ptr(&compression_buffer), |
buffer_append(&outgoing_packet, buffer_ptr(&compression_buffer), |
buffer_len(&compression_buffer)); |
buffer_len(&compression_buffer)); |
} |
} |
/* Compute packet length without padding (add checksum, remove padding). */ |
/* Compute packet length without padding (add checksum, remove padding). */ |
len = buffer_len(&outgoing_packet) + 4 - 8; |
len = buffer_len(&outgoing_packet) + 4 - 8; |
|
|
/* Insert padding. Initialized to zero in packet_start1() */ |
/* Insert padding. Initialized to zero in packet_start1() */ |
padding = 8 - len % 8; |
padding = 8 - len % 8; |
if (!send_context.plaintext) { |
if (send_context.cipher->number != SSH_CIPHER_NONE) { |
cp = buffer_ptr(&outgoing_packet); |
cp = buffer_ptr(&outgoing_packet); |
for (i = 0; i < padding; i++) { |
for (i = 0; i < padding; i++) { |
if (i % 4 == 0) |
if (i % 4 == 0) |
|
|
buffer_consume(&outgoing_packet, 8 - padding); |
buffer_consume(&outgoing_packet, 8 - padding); |
|
|
/* Add check bytes. */ |
/* Add check bytes. */ |
checksum = ssh_crc32(buffer_ptr(&outgoing_packet), |
checksum = ssh_crc32((u_char *) buffer_ptr(&outgoing_packet), |
buffer_len(&outgoing_packet)); |
buffer_len(&outgoing_packet)); |
PUT_32BIT(buf, checksum); |
PUT_32BIT(buf, checksum); |
buffer_append(&outgoing_packet, buf, 4); |
buffer_append(&outgoing_packet, buf, 4); |
|
|
/* Append to output. */ |
/* Append to output. */ |
PUT_32BIT(buf, len); |
PUT_32BIT(buf, len); |
buffer_append(&output, buf, 4); |
buffer_append(&output, buf, 4); |
cp = buffer_append_space(&output, buffer_len(&outgoing_packet)); |
buffer_append_space(&output, &cp, buffer_len(&outgoing_packet)); |
cipher_crypt(&send_context, cp, buffer_ptr(&outgoing_packet), |
cipher_encrypt(&send_context, cp, buffer_ptr(&outgoing_packet), |
buffer_len(&outgoing_packet)); |
buffer_len(&outgoing_packet)); |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "encrypted: "); |
fprintf(stderr, "encrypted: "); |
|
|
*/ |
*/ |
} |
} |
|
|
void |
static void |
set_newkeys(int mode) |
set_newkeys(int mode) |
{ |
{ |
Enc *enc; |
Enc *enc; |
Mac *mac; |
Mac *mac; |
Comp *comp; |
Comp *comp; |
CipherContext *cc; |
CipherContext *cc; |
int encrypt; |
|
|
|
debug("newkeys: mode %d", mode); |
debug("newkeys: mode %d", mode); |
|
|
if (mode == MODE_OUT) { |
cc = (mode == MODE_OUT) ? &send_context : &receive_context; |
cc = &send_context; |
|
encrypt = CIPHER_ENCRYPT; |
|
} else { |
|
cc = &receive_context; |
|
encrypt = CIPHER_DECRYPT; |
|
} |
|
if (newkeys[mode] != NULL) { |
if (newkeys[mode] != NULL) { |
debug("newkeys: rekeying"); |
debug("newkeys: rekeying"); |
cipher_cleanup(cc); |
/* todo: free old keys, reset compression/cipher-ctxt; */ |
|
memset(cc, 0, sizeof(*cc)); |
enc = &newkeys[mode]->enc; |
enc = &newkeys[mode]->enc; |
mac = &newkeys[mode]->mac; |
mac = &newkeys[mode]->mac; |
comp = &newkeys[mode]->comp; |
comp = &newkeys[mode]->comp; |
|
|
if (mac->md != NULL) |
if (mac->md != NULL) |
mac->enabled = 1; |
mac->enabled = 1; |
DBG(debug("cipher_init_context: %d", mode)); |
DBG(debug("cipher_init_context: %d", mode)); |
cipher_init(cc, enc->cipher, enc->key, enc->key_len, |
cipher_init(cc, enc->cipher, enc->key, enc->cipher->key_len, |
enc->iv, enc->block_size, encrypt); |
enc->iv, enc->cipher->block_size); |
/* Deleting the keys does not gain extra security */ |
memset(enc->iv, 0, enc->cipher->block_size); |
/* memset(enc->iv, 0, enc->block_size); |
memset(enc->key, 0, enc->cipher->key_len); |
memset(enc->key, 0, enc->key_len); */ |
|
if (comp->type != 0 && comp->enabled == 0) { |
if (comp->type != 0 && comp->enabled == 0) { |
packet_init_compression(); |
packet_init_compression(); |
if (mode == MODE_OUT) |
if (mode == MODE_OUT) |
|
|
static void |
static void |
packet_send2(void) |
packet_send2(void) |
{ |
{ |
u_char type, *cp, *macbuf = NULL; |
static u_int32_t seqnr = 0; |
|
u_char type, *ucp, *macbuf = NULL; |
u_char padlen, pad; |
u_char padlen, pad; |
|
char *cp; |
u_int packet_length = 0; |
u_int packet_length = 0; |
u_int i, len; |
u_int i, len; |
u_int32_t rand = 0; |
u_int32_t rand = 0; |
|
|
mac = &newkeys[MODE_OUT]->mac; |
mac = &newkeys[MODE_OUT]->mac; |
comp = &newkeys[MODE_OUT]->comp; |
comp = &newkeys[MODE_OUT]->comp; |
} |
} |
block_size = enc ? enc->block_size : 8; |
block_size = enc ? enc->cipher->block_size : 8; |
|
|
cp = buffer_ptr(&outgoing_packet); |
ucp = (u_char *) buffer_ptr(&outgoing_packet); |
type = cp[5]; |
type = ucp[5]; |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "plain: "); |
fprintf(stderr, "plain: "); |
|
|
/* will wrap if extra_pad+padlen > 255 */ |
/* will wrap if extra_pad+padlen > 255 */ |
extra_pad = roundup(extra_pad, block_size); |
extra_pad = roundup(extra_pad, block_size); |
pad = extra_pad - ((len + padlen) % extra_pad); |
pad = extra_pad - ((len + padlen) % extra_pad); |
debug3("packet_send2: adding %d (len %d padlen %d extra_pad %d)", |
debug("packet_send2: adding %d (len %d padlen %d extra_pad %d)", |
pad, len, padlen, extra_pad); |
pad, len, padlen, extra_pad); |
padlen += pad; |
padlen += pad; |
extra_pad = 0; |
extra_pad = 0; |
} |
} |
cp = buffer_append_space(&outgoing_packet, padlen); |
buffer_append_space(&outgoing_packet, &cp, padlen); |
if (enc && !send_context.plaintext) { |
if (enc && enc->cipher->number != SSH_CIPHER_NONE) { |
/* random padding */ |
/* random padding */ |
for (i = 0; i < padlen; i++) { |
for (i = 0; i < padlen; i++) { |
if (i % 4 == 0) |
if (i % 4 == 0) |
|
|
} |
} |
/* packet_length includes payload, padding and padding length field */ |
/* packet_length includes payload, padding and padding length field */ |
packet_length = buffer_len(&outgoing_packet) - 4; |
packet_length = buffer_len(&outgoing_packet) - 4; |
cp = buffer_ptr(&outgoing_packet); |
ucp = (u_char *)buffer_ptr(&outgoing_packet); |
PUT_32BIT(cp, packet_length); |
PUT_32BIT(ucp, packet_length); |
cp[4] = padlen; |
ucp[4] = padlen; |
DBG(debug("send: len %d (includes padlen %d)", packet_length+4, padlen)); |
DBG(debug("send: len %d (includes padlen %d)", packet_length+4, padlen)); |
|
|
/* compute MAC over seqnr and packet(length fields, payload, padding) */ |
/* compute MAC over seqnr and packet(length fields, payload, padding) */ |
if (mac && mac->enabled) { |
if (mac && mac->enabled) { |
macbuf = mac_compute(mac, send_seqnr, |
macbuf = mac_compute(mac, seqnr, |
buffer_ptr(&outgoing_packet), |
(u_char *) buffer_ptr(&outgoing_packet), |
buffer_len(&outgoing_packet)); |
buffer_len(&outgoing_packet)); |
DBG(debug("done calc MAC out #%d", send_seqnr)); |
DBG(debug("done calc MAC out #%d", seqnr)); |
} |
} |
/* encrypt packet and append to output buffer. */ |
/* encrypt packet and append to output buffer. */ |
cp = buffer_append_space(&output, buffer_len(&outgoing_packet)); |
buffer_append_space(&output, &cp, buffer_len(&outgoing_packet)); |
cipher_crypt(&send_context, cp, buffer_ptr(&outgoing_packet), |
cipher_encrypt(&send_context, cp, buffer_ptr(&outgoing_packet), |
buffer_len(&outgoing_packet)); |
buffer_len(&outgoing_packet)); |
/* append unencrypted MAC */ |
/* append unencrypted MAC */ |
if (mac && mac->enabled) |
if (mac && mac->enabled) |
|
|
buffer_dump(&output); |
buffer_dump(&output); |
#endif |
#endif |
/* increment sequence number for outgoing packets */ |
/* increment sequence number for outgoing packets */ |
if (++send_seqnr == 0) |
if (++seqnr == 0) |
log("outgoing seqnr wraps around"); |
log("outgoing seqnr wraps around"); |
buffer_clear(&outgoing_packet); |
buffer_clear(&outgoing_packet); |
|
|
|
|
} |
} |
|
|
void |
void |
packet_send(void) |
packet_send() |
{ |
{ |
if (compat20) |
if (compat20) |
packet_send2(); |
packet_send2(); |
|
|
*/ |
*/ |
|
|
int |
int |
packet_read_seqnr(u_int32_t *seqnr_p) |
packet_read(int *payload_len_ptr) |
{ |
{ |
int type, len; |
int type, len; |
fd_set *setp; |
fd_set *setp; |
|
|
/* Stay in the loop until we have received a complete packet. */ |
/* Stay in the loop until we have received a complete packet. */ |
for (;;) { |
for (;;) { |
/* Try to read a packet from the buffer. */ |
/* Try to read a packet from the buffer. */ |
type = packet_read_poll_seqnr(seqnr_p); |
type = packet_read_poll(payload_len_ptr); |
if (!compat20 && ( |
if (!compat20 && ( |
type == SSH_SMSG_SUCCESS |
type == SSH_SMSG_SUCCESS |
|| type == SSH_SMSG_FAILURE |
|| type == SSH_SMSG_FAILURE |
|| type == SSH_CMSG_EOF |
|| type == SSH_CMSG_EOF |
|| type == SSH_CMSG_EXIT_CONFIRMATION)) |
|| type == SSH_CMSG_EXIT_CONFIRMATION)) |
packet_check_eom(); |
packet_integrity_check(*payload_len_ptr, 0, type); |
/* If we got a packet, return it. */ |
/* If we got a packet, return it. */ |
if (type != SSH_MSG_NONE) { |
if (type != SSH_MSG_NONE) { |
xfree(setp); |
xfree(setp); |
|
|
/* NOTREACHED */ |
/* NOTREACHED */ |
} |
} |
|
|
int |
|
packet_read(void) |
|
{ |
|
return packet_read_seqnr(NULL); |
|
} |
|
|
|
/* |
/* |
* Waits until a packet has been received, verifies that its type matches |
* Waits until a packet has been received, verifies that its type matches |
* that given, and gives a fatal error and exits if there is a mismatch. |
* that given, and gives a fatal error and exits if there is a mismatch. |
*/ |
*/ |
|
|
void |
void |
packet_read_expect(int expected_type) |
packet_read_expect(int *payload_len_ptr, int expected_type) |
{ |
{ |
int type; |
int type; |
|
|
type = packet_read(); |
type = packet_read(payload_len_ptr); |
if (type != expected_type) |
if (type != expected_type) |
packet_disconnect("Protocol error: expected packet type %d, got %d", |
packet_disconnect("Protocol error: expected packet type %d, got %d", |
expected_type, type); |
expected_type, type); |
|
|
* SSH_MSG_DISCONNECT is handled specially here. Also, |
* SSH_MSG_DISCONNECT is handled specially here. Also, |
* SSH_MSG_IGNORE messages are skipped by this function and are never returned |
* SSH_MSG_IGNORE messages are skipped by this function and are never returned |
* to higher levels. |
* to higher levels. |
|
* |
|
* The returned payload_len does include space consumed by: |
|
* Packet length |
|
* Padding |
|
* Packet type |
|
* Check bytes |
*/ |
*/ |
|
|
static int |
static int |
packet_read_poll1(void) |
packet_read_poll1(int *payload_len_ptr) |
{ |
{ |
u_int len, padded_len; |
u_int len, padded_len; |
u_char *cp, type; |
u_char *ucp, type; |
|
char *cp; |
u_int checksum, stored_checksum; |
u_int checksum, stored_checksum; |
|
|
/* Check if input size is less than minimum packet size. */ |
/* Check if input size is less than minimum packet size. */ |
if (buffer_len(&input) < 4 + 8) |
if (buffer_len(&input) < 4 + 8) |
return SSH_MSG_NONE; |
return SSH_MSG_NONE; |
/* Get length of incoming packet. */ |
/* Get length of incoming packet. */ |
cp = buffer_ptr(&input); |
ucp = (u_char *) buffer_ptr(&input); |
len = GET_32BIT(cp); |
len = GET_32BIT(ucp); |
if (len < 1 + 2 + 2 || len > 256 * 1024) |
if (len < 1 + 2 + 2 || len > 256 * 1024) |
packet_disconnect("Bad packet length %d.", len); |
packet_disconnect("Bad packet length %d.", len); |
padded_len = (len + 8) & ~7; |
padded_len = (len + 8) & ~7; |
|
|
* (C)1998 CORE-SDI, Buenos Aires Argentina |
* (C)1998 CORE-SDI, Buenos Aires Argentina |
* Ariel Futoransky(futo@core-sdi.com) |
* Ariel Futoransky(futo@core-sdi.com) |
*/ |
*/ |
if (!receive_context.plaintext && |
if (receive_context.cipher->number != SSH_CIPHER_NONE && |
detect_attack(buffer_ptr(&input), padded_len, NULL) == DEATTACK_DETECTED) |
detect_attack(buffer_ptr(&input), padded_len, NULL) == DEATTACK_DETECTED) |
packet_disconnect("crc32 compensation attack: network attack detected"); |
packet_disconnect("crc32 compensation attack: network attack detected"); |
|
|
/* Decrypt data to incoming_packet. */ |
/* Decrypt data to incoming_packet. */ |
buffer_clear(&incoming_packet); |
buffer_clear(&incoming_packet); |
cp = buffer_append_space(&incoming_packet, padded_len); |
buffer_append_space(&incoming_packet, &cp, padded_len); |
cipher_crypt(&receive_context, cp, buffer_ptr(&input), padded_len); |
cipher_decrypt(&receive_context, cp, buffer_ptr(&input), padded_len); |
|
|
buffer_consume(&input, padded_len); |
buffer_consume(&input, padded_len); |
|
|
|
|
#endif |
#endif |
|
|
/* Compute packet checksum. */ |
/* Compute packet checksum. */ |
checksum = ssh_crc32(buffer_ptr(&incoming_packet), |
checksum = ssh_crc32((u_char *) buffer_ptr(&incoming_packet), |
buffer_len(&incoming_packet) - 4); |
buffer_len(&incoming_packet) - 4); |
|
|
/* Skip padding. */ |
/* Skip padding. */ |
|
|
|
|
/* Test check bytes. */ |
/* Test check bytes. */ |
if (len != buffer_len(&incoming_packet)) |
if (len != buffer_len(&incoming_packet)) |
packet_disconnect("packet_read_poll1: len %d != buffer_len %d.", |
packet_disconnect("packet_read_poll: len %d != buffer_len %d.", |
len, buffer_len(&incoming_packet)); |
len, buffer_len(&incoming_packet)); |
|
|
cp = (u_char *)buffer_ptr(&incoming_packet) + len - 4; |
ucp = (u_char *) buffer_ptr(&incoming_packet) + len - 4; |
stored_checksum = GET_32BIT(cp); |
stored_checksum = GET_32BIT(ucp); |
if (checksum != stored_checksum) |
if (checksum != stored_checksum) |
packet_disconnect("Corrupted check bytes on input."); |
packet_disconnect("Corrupted check bytes on input."); |
buffer_consume_end(&incoming_packet, 4); |
buffer_consume_end(&incoming_packet, 4); |
|
|
buffer_len(&compression_buffer)); |
buffer_len(&compression_buffer)); |
} |
} |
type = buffer_get_char(&incoming_packet); |
type = buffer_get_char(&incoming_packet); |
|
*payload_len_ptr = buffer_len(&incoming_packet); |
return type; |
return type; |
} |
} |
|
|
static int |
static int |
packet_read_poll2(u_int32_t *seqnr_p) |
packet_read_poll2(int *payload_len_ptr) |
{ |
{ |
|
static u_int32_t seqnr = 0; |
static u_int packet_length = 0; |
static u_int packet_length = 0; |
u_int padlen, need; |
u_int padlen, need; |
u_char *macbuf, *cp, type; |
u_char *macbuf, *ucp, type; |
|
char *cp; |
int maclen, block_size; |
int maclen, block_size; |
Enc *enc = NULL; |
Enc *enc = NULL; |
Mac *mac = NULL; |
Mac *mac = NULL; |
|
|
comp = &newkeys[MODE_IN]->comp; |
comp = &newkeys[MODE_IN]->comp; |
} |
} |
maclen = mac && mac->enabled ? mac->mac_len : 0; |
maclen = mac && mac->enabled ? mac->mac_len : 0; |
block_size = enc ? enc->block_size : 8; |
block_size = enc ? enc->cipher->block_size : 8; |
|
|
if (packet_length == 0) { |
if (packet_length == 0) { |
/* |
/* |
|
|
if (buffer_len(&input) < block_size) |
if (buffer_len(&input) < block_size) |
return SSH_MSG_NONE; |
return SSH_MSG_NONE; |
buffer_clear(&incoming_packet); |
buffer_clear(&incoming_packet); |
cp = buffer_append_space(&incoming_packet, block_size); |
buffer_append_space(&incoming_packet, &cp, block_size); |
cipher_crypt(&receive_context, cp, buffer_ptr(&input), |
cipher_decrypt(&receive_context, cp, buffer_ptr(&input), |
block_size); |
block_size); |
cp = buffer_ptr(&incoming_packet); |
ucp = (u_char *) buffer_ptr(&incoming_packet); |
packet_length = GET_32BIT(cp); |
packet_length = GET_32BIT(ucp); |
if (packet_length < 1 + 4 || packet_length > 256 * 1024) { |
if (packet_length < 1 + 4 || packet_length > 256 * 1024) { |
buffer_dump(&incoming_packet); |
buffer_dump(&incoming_packet); |
packet_disconnect("Bad packet length %d.", packet_length); |
packet_disconnect("Bad packet length %d.", packet_length); |
|
|
fprintf(stderr, "read_poll enc/full: "); |
fprintf(stderr, "read_poll enc/full: "); |
buffer_dump(&input); |
buffer_dump(&input); |
#endif |
#endif |
cp = buffer_append_space(&incoming_packet, need); |
buffer_append_space(&incoming_packet, &cp, need); |
cipher_crypt(&receive_context, cp, buffer_ptr(&input), need); |
cipher_decrypt(&receive_context, cp, buffer_ptr(&input), need); |
buffer_consume(&input, need); |
buffer_consume(&input, need); |
/* |
/* |
* compute MAC over seqnr and packet, |
* compute MAC over seqnr and packet, |
* increment sequence number for incoming packet |
* increment sequence number for incoming packet |
*/ |
*/ |
if (mac && mac->enabled) { |
if (mac && mac->enabled) { |
macbuf = mac_compute(mac, read_seqnr, |
macbuf = mac_compute(mac, seqnr, |
buffer_ptr(&incoming_packet), |
(u_char *) buffer_ptr(&incoming_packet), |
buffer_len(&incoming_packet)); |
buffer_len(&incoming_packet)); |
if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0) |
if (memcmp(macbuf, buffer_ptr(&input), mac->mac_len) != 0) |
packet_disconnect("Corrupted MAC on input."); |
packet_disconnect("Corrupted MAC on input."); |
DBG(debug("MAC #%d ok", read_seqnr)); |
DBG(debug("MAC #%d ok", seqnr)); |
buffer_consume(&input, mac->mac_len); |
buffer_consume(&input, mac->mac_len); |
} |
} |
if (seqnr_p != NULL) |
if (++seqnr == 0) |
*seqnr_p = read_seqnr; |
|
if (++read_seqnr == 0) |
|
log("incoming seqnr wraps around"); |
log("incoming seqnr wraps around"); |
|
|
/* get padlen */ |
/* get padlen */ |
cp = buffer_ptr(&incoming_packet); |
cp = buffer_ptr(&incoming_packet) + 4; |
padlen = cp[4]; |
padlen = (u_char) *cp; |
DBG(debug("input: padlen %d", padlen)); |
DBG(debug("input: padlen %d", padlen)); |
if (padlen < 4) |
if (padlen < 4) |
packet_disconnect("Corrupted padlen %d on input.", padlen); |
packet_disconnect("Corrupted padlen %d on input.", padlen); |
|
|
buffer_clear(&incoming_packet); |
buffer_clear(&incoming_packet); |
buffer_append(&incoming_packet, buffer_ptr(&compression_buffer), |
buffer_append(&incoming_packet, buffer_ptr(&compression_buffer), |
buffer_len(&compression_buffer)); |
buffer_len(&compression_buffer)); |
DBG(debug("input: len after de-compress %d", |
DBG(debug("input: len after de-compress %d", buffer_len(&incoming_packet))); |
buffer_len(&incoming_packet))); |
|
} |
} |
/* |
/* |
* get packet type, implies consume. |
* get packet type, implies consume. |
|
|
type = buffer_get_char(&incoming_packet); |
type = buffer_get_char(&incoming_packet); |
if (type == SSH2_MSG_NEWKEYS) |
if (type == SSH2_MSG_NEWKEYS) |
set_newkeys(MODE_IN); |
set_newkeys(MODE_IN); |
|
*payload_len_ptr = buffer_len(&incoming_packet); |
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "read/plain[%d]:\r\n", type); |
fprintf(stderr, "read/plain[%d]:\r\n", type); |
buffer_dump(&incoming_packet); |
buffer_dump(&incoming_packet); |
|
|
} |
} |
|
|
int |
int |
packet_read_poll_seqnr(u_int32_t *seqnr_p) |
packet_read_poll(int *payload_len_ptr) |
{ |
{ |
u_int reason, seqnr; |
int reason; |
u_char type; |
u_char type; |
char *msg; |
char *msg; |
|
|
for (;;) { |
for (;;) { |
if (compat20) { |
if (compat20) { |
type = packet_read_poll2(seqnr_p); |
type = packet_read_poll2(payload_len_ptr); |
if (type) |
if (type) |
DBG(debug("received packet type %d", type)); |
DBG(debug("received packet type %d", type)); |
switch (type) { |
switch(type) { |
case SSH2_MSG_IGNORE: |
case SSH2_MSG_IGNORE: |
break; |
break; |
case SSH2_MSG_DEBUG: |
case SSH2_MSG_DEBUG: |
|
|
case SSH2_MSG_DISCONNECT: |
case SSH2_MSG_DISCONNECT: |
reason = packet_get_int(); |
reason = packet_get_int(); |
msg = packet_get_string(NULL); |
msg = packet_get_string(NULL); |
log("Received disconnect from %s: %u: %.400s", |
log("Received disconnect from %s: %d: %.400s", get_remote_ipaddr(), |
get_remote_ipaddr(), reason, msg); |
reason, msg); |
xfree(msg); |
xfree(msg); |
fatal_cleanup(); |
fatal_cleanup(); |
break; |
break; |
case SSH2_MSG_UNIMPLEMENTED: |
|
seqnr = packet_get_int(); |
|
debug("Received SSH2_MSG_UNIMPLEMENTED for %u", |
|
seqnr); |
|
break; |
|
default: |
default: |
return type; |
return type; |
break; |
break; |
} |
} |
} else { |
} else { |
type = packet_read_poll1(); |
type = packet_read_poll1(payload_len_ptr); |
switch (type) { |
switch(type) { |
case SSH_MSG_IGNORE: |
case SSH_MSG_IGNORE: |
break; |
break; |
case SSH_MSG_DEBUG: |
case SSH_MSG_DEBUG: |
|
|
break; |
break; |
case SSH_MSG_DISCONNECT: |
case SSH_MSG_DISCONNECT: |
msg = packet_get_string(NULL); |
msg = packet_get_string(NULL); |
log("Received disconnect from %s: %.400s", |
log("Received disconnect from %s: %.400s", get_remote_ipaddr(), |
get_remote_ipaddr(), msg); |
msg); |
fatal_cleanup(); |
fatal_cleanup(); |
xfree(msg); |
xfree(msg); |
break; |
break; |
|
|
} |
} |
} |
} |
|
|
int |
|
packet_read_poll(void) |
|
{ |
|
return packet_read_poll_seqnr(NULL); |
|
} |
|
|
|
/* |
/* |
* Buffers the given amount of input characters. This is intended to be used |
* Buffers the given amount of input characters. This is intended to be used |
* together with packet_read_poll. |
* together with packet_read_poll. |
|
|
/* Returns a character from the packet. */ |
/* Returns a character from the packet. */ |
|
|
u_int |
u_int |
packet_get_char(void) |
packet_get_char() |
{ |
{ |
char ch; |
char ch; |
|
|
buffer_get(&incoming_packet, &ch, 1); |
buffer_get(&incoming_packet, &ch, 1); |
return (u_char) ch; |
return (u_char) ch; |
} |
} |
|
|
/* Returns an integer from the packet data. */ |
/* Returns an integer from the packet data. */ |
|
|
u_int |
u_int |
packet_get_int(void) |
packet_get_int() |
{ |
{ |
return buffer_get_int(&incoming_packet); |
return buffer_get_int(&incoming_packet); |
} |
} |
|
|
*/ |
*/ |
|
|
void |
void |
packet_get_bignum(BIGNUM * value) |
packet_get_bignum(BIGNUM * value, int *length_ptr) |
{ |
{ |
buffer_get_bignum(&incoming_packet, value); |
*length_ptr = buffer_get_bignum(&incoming_packet, value); |
} |
} |
|
|
void |
void |
packet_get_bignum2(BIGNUM * value) |
packet_get_bignum2(BIGNUM * value, int *length_ptr) |
{ |
{ |
buffer_get_bignum2(&incoming_packet, value); |
*length_ptr = buffer_get_bignum2(&incoming_packet, value); |
} |
} |
|
|
void * |
char * |
packet_get_raw(int *length_ptr) |
packet_get_raw(int *length_ptr) |
{ |
{ |
int bytes = buffer_len(&incoming_packet); |
int bytes = buffer_len(&incoming_packet); |
|
|
if (length_ptr != NULL) |
if (length_ptr != NULL) |
*length_ptr = bytes; |
*length_ptr = bytes; |
return buffer_ptr(&incoming_packet); |
return buffer_ptr(&incoming_packet); |
|
|
* integer into which the length of the string is stored. |
* integer into which the length of the string is stored. |
*/ |
*/ |
|
|
void * |
char * |
packet_get_string(u_int *length_ptr) |
packet_get_string(u_int *length_ptr) |
{ |
{ |
return buffer_get_string(&incoming_packet, length_ptr); |
return buffer_get_string(&incoming_packet, length_ptr); |
|
|
char buf[1024]; |
char buf[1024]; |
va_list args; |
va_list args; |
static int disconnecting = 0; |
static int disconnecting = 0; |
|
|
if (disconnecting) /* Guard against recursive invocations. */ |
if (disconnecting) /* Guard against recursive invocations. */ |
fatal("packet_disconnect called recursively."); |
fatal("packet_disconnect called recursively."); |
disconnecting = 1; |
disconnecting = 1; |
|
|
/* Checks if there is any buffered output, and tries to write some of the output. */ |
/* Checks if there is any buffered output, and tries to write some of the output. */ |
|
|
void |
void |
packet_write_poll(void) |
packet_write_poll() |
{ |
{ |
int len = buffer_len(&output); |
int len = buffer_len(&output); |
|
|
if (len > 0) { |
if (len > 0) { |
len = write(connection_out, buffer_ptr(&output), len); |
len = write(connection_out, buffer_ptr(&output), len); |
if (len <= 0) { |
if (len <= 0) { |
|
|
else |
else |
fatal("Write failed: %.100s", strerror(errno)); |
fatal("Write failed: %.100s", strerror(errno)); |
} |
} |
|
debug("packet_write_poll: sent %d bytes", len); |
buffer_consume(&output, len); |
buffer_consume(&output, len); |
} |
} |
} |
} |
|
|
*/ |
*/ |
|
|
void |
void |
packet_write_wait(void) |
packet_write_wait() |
{ |
{ |
fd_set *setp; |
fd_set *setp; |
|
|
|
|
/* Returns true if there is buffered data to write to the connection. */ |
/* Returns true if there is buffered data to write to the connection. */ |
|
|
int |
int |
packet_have_data_to_write(void) |
packet_have_data_to_write() |
{ |
{ |
return buffer_len(&output) != 0; |
return buffer_len(&output) != 0; |
} |
} |
|
|
/* Returns true if there is not too much data to write to the connection. */ |
/* Returns true if there is not too much data to write to the connection. */ |
|
|
int |
int |
packet_not_very_much_data_to_write(void) |
packet_not_very_much_data_to_write() |
{ |
{ |
if (interactive_mode) |
if (interactive_mode) |
return buffer_len(&output) < 16384; |
return buffer_len(&output) < 16384; |
|
|
static int called = 0; |
static int called = 0; |
int lowdelay = IPTOS_LOWDELAY; |
int lowdelay = IPTOS_LOWDELAY; |
int throughput = IPTOS_THROUGHPUT; |
int throughput = IPTOS_THROUGHPUT; |
|
int on = 1; |
|
|
if (called) |
if (called) |
return; |
return; |
|
|
*/ |
*/ |
if (packet_connection_is_ipv4()) { |
if (packet_connection_is_ipv4()) { |
if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, |
if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, |
&lowdelay, sizeof(lowdelay)) < 0) |
(void *) &lowdelay, sizeof(lowdelay)) < 0) |
error("setsockopt IPTOS_LOWDELAY: %.100s", |
error("setsockopt IPTOS_LOWDELAY: %.100s", |
strerror(errno)); |
strerror(errno)); |
} |
} |
set_nodelay(connection_in); |
if (setsockopt(connection_in, IPPROTO_TCP, TCP_NODELAY, (void *) &on, |
|
sizeof(on)) < 0) |
|
error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); |
} else if (packet_connection_is_ipv4()) { |
} else if (packet_connection_is_ipv4()) { |
/* |
/* |
* Set IP options for a non-interactive connection. Use |
* Set IP options for a non-interactive connection. Use |
* IPTOS_THROUGHPUT. |
* IPTOS_THROUGHPUT. |
*/ |
*/ |
if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, &throughput, |
if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *) &throughput, |
sizeof(throughput)) < 0) |
sizeof(throughput)) < 0) |
error("setsockopt IPTOS_THROUGHPUT: %.100s", strerror(errno)); |
error("setsockopt IPTOS_THROUGHPUT: %.100s", strerror(errno)); |
} |
} |
|
|
/* Returns true if the current connection is interactive. */ |
/* Returns true if the current connection is interactive. */ |
|
|
int |
int |
packet_is_interactive(void) |
packet_is_interactive() |
{ |
{ |
return interactive_mode; |
return interactive_mode; |
} |
} |
|
|
packet_set_maxsize(int s) |
packet_set_maxsize(int s) |
{ |
{ |
static int called = 0; |
static int called = 0; |
|
|
if (called) { |
if (called) { |
log("packet_set_maxsize: called twice: old %d new %d", |
log("packet_set_maxsize: called twice: old %d new %d", |
max_packet_size, s); |
max_packet_size, s); |
|
|
|
|
packet_start(compat20 ? SSH2_MSG_IGNORE : SSH_MSG_IGNORE); |
packet_start(compat20 ? SSH2_MSG_IGNORE : SSH_MSG_IGNORE); |
packet_put_int(nbytes); |
packet_put_int(nbytes); |
for (i = 0; i < nbytes; i++) { |
for(i = 0; i < nbytes; i++) { |
if (i % 4 == 0) |
if (i % 4 == 0) |
rand = arc4random(); |
rand = arc4random(); |
packet_put_char(rand & 0xff); |
packet_put_char(rand & 0xff); |