version 1.102, 2002/12/10 19:47:14 |
version 1.102.2.2, 2004/03/04 18:18:16 |
|
|
#include "includes.h" |
#include "includes.h" |
RCSID("$OpenBSD$"); |
RCSID("$OpenBSD$"); |
|
|
|
#include <sys/queue.h> |
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "buffer.h" |
#include "buffer.h" |
#include "packet.h" |
#include "packet.h" |
|
|
static int packet_compression = 0; |
static int packet_compression = 0; |
|
|
/* default maximum packet size */ |
/* default maximum packet size */ |
int max_packet_size = 32768; |
u_int max_packet_size = 32768; |
|
|
/* Flag indicating whether this module has been initialized. */ |
/* Flag indicating whether this module has been initialized. */ |
static int initialized = 0; |
static int initialized = 0; |
|
|
|
|
/* 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 struct packet_state { |
static u_int32_t send_seqnr = 0; |
u_int32_t seqnr; |
|
u_int32_t packets; |
|
u_int64_t blocks; |
|
} p_read, p_send; |
|
|
|
static u_int64_t max_blocks_in, max_blocks_out; |
|
static u_int32_t rekey_limit; |
|
|
/* Session key for protocol v1 */ |
/* Session key for protocol v1 */ |
static u_char ssh1_key[SSH_SESSION_KEY_LENGTH]; |
static u_char ssh1_key[SSH_SESSION_KEY_LENGTH]; |
static u_int ssh1_keylen; |
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; |
|
|
|
struct packet { |
|
TAILQ_ENTRY(packet) next; |
|
u_char type; |
|
Buffer payload; |
|
}; |
|
TAILQ_HEAD(, packet) outgoing; |
|
|
/* |
/* |
* Sets the descriptors used for communication. Disables encryption until |
* Sets the descriptors used for communication. Disables encryption until |
* packet_set_encryption_key is called. |
* packet_set_encryption_key is called. |
|
|
buffer_init(&output); |
buffer_init(&output); |
buffer_init(&outgoing_packet); |
buffer_init(&outgoing_packet); |
buffer_init(&incoming_packet); |
buffer_init(&incoming_packet); |
|
TAILQ_INIT(&outgoing); |
} |
} |
/* Kludge: arrange the close function to be called from fatal(). */ |
|
fatal_add_cleanup((void (*) (void *)) packet_close, NULL); |
|
} |
} |
|
|
/* Returns 1 if remote host is connected via socket, 0 if not. */ |
/* Returns 1 if remote host is connected via socket, 0 if not. */ |
|
|
cipher_set_keyiv(cc, dat); |
cipher_set_keyiv(cc, dat); |
} |
} |
int |
int |
packet_get_ssh1_cipher() |
packet_get_ssh1_cipher(void) |
{ |
{ |
return (cipher_get_number(receive_context.cipher)); |
return (cipher_get_number(receive_context.cipher)); |
} |
} |
|
|
|
void |
u_int32_t |
packet_get_state(int mode, u_int32_t *seqnr, u_int64_t *blocks, u_int32_t *packets) |
packet_get_seqnr(int mode) |
|
{ |
{ |
return (mode == MODE_IN ? read_seqnr : send_seqnr); |
struct packet_state *state; |
|
|
|
state = (mode == MODE_IN) ? &p_read : &p_send; |
|
*seqnr = state->seqnr; |
|
*blocks = state->blocks; |
|
*packets = state->packets; |
} |
} |
|
|
void |
void |
packet_set_seqnr(int mode, u_int32_t seqnr) |
packet_set_state(int mode, u_int32_t seqnr, u_int64_t blocks, u_int32_t packets) |
{ |
{ |
if (mode == MODE_IN) |
struct packet_state *state; |
read_seqnr = seqnr; |
|
else if (mode == MODE_OUT) |
state = (mode == MODE_IN) ? &p_read : &p_send; |
send_seqnr = seqnr; |
state->seqnr = seqnr; |
else |
state->blocks = blocks; |
fatal("packet_set_seqnr: bad mode %d", mode); |
state->packets = packets; |
} |
} |
|
|
/* returns 1 if connection is via ipv4 */ |
/* returns 1 if connection is via ipv4 */ |
|
|
Mac *mac; |
Mac *mac; |
Comp *comp; |
Comp *comp; |
CipherContext *cc; |
CipherContext *cc; |
|
u_int64_t *max_blocks; |
int encrypt; |
int encrypt; |
|
|
debug2("set_newkeys: mode %d", mode); |
debug2("set_newkeys: mode %d", mode); |
|
|
if (mode == MODE_OUT) { |
if (mode == MODE_OUT) { |
cc = &send_context; |
cc = &send_context; |
encrypt = CIPHER_ENCRYPT; |
encrypt = CIPHER_ENCRYPT; |
|
p_send.packets = p_send.blocks = 0; |
|
max_blocks = &max_blocks_out; |
} else { |
} else { |
cc = &receive_context; |
cc = &receive_context; |
encrypt = CIPHER_DECRYPT; |
encrypt = CIPHER_DECRYPT; |
|
p_read.packets = p_read.blocks = 0; |
|
max_blocks = &max_blocks_in; |
} |
} |
if (newkeys[mode] != NULL) { |
if (newkeys[mode] != NULL) { |
debug("set_newkeys: rekeying"); |
debug("set_newkeys: rekeying"); |
|
|
buffer_compress_init_recv(); |
buffer_compress_init_recv(); |
comp->enabled = 1; |
comp->enabled = 1; |
} |
} |
|
/* |
|
* The 2^(blocksize*2) limit is too expensive for 3DES, |
|
* blowfish, etc, so enforce a 1GB limit for small blocksizes. |
|
*/ |
|
if (enc->block_size >= 16) |
|
*max_blocks = (u_int64_t)1 << (enc->block_size*2); |
|
else |
|
*max_blocks = ((u_int64_t)1 << 30) / enc->block_size; |
|
if (rekey_limit) |
|
*max_blocks = MIN(*max_blocks, rekey_limit / enc->block_size); |
} |
} |
|
|
/* |
/* |
* Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) |
* Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) |
*/ |
*/ |
static void |
static void |
packet_send2(void) |
packet_send2_wrapped(void) |
{ |
{ |
u_char type, *cp, *macbuf = NULL; |
u_char type, *cp, *macbuf = NULL; |
u_char padlen, pad; |
u_char padlen, pad; |
|
|
|
|
/* 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, p_send.seqnr, |
buffer_ptr(&outgoing_packet), |
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", p_send.seqnr)); |
} |
} |
/* encrypt packet and append to output buffer. */ |
/* encrypt packet and append to output buffer. */ |
cp = buffer_append_space(&output, buffer_len(&outgoing_packet)); |
cp = buffer_append_space(&output, buffer_len(&outgoing_packet)); |
|
|
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 (++p_send.seqnr == 0) |
log("outgoing seqnr wraps around"); |
logit("outgoing seqnr wraps around"); |
|
if (++p_send.packets == 0) |
|
if (!(datafellows & SSH_BUG_NOREKEY)) |
|
fatal("XXX too many packets with same key"); |
|
p_send.blocks += (packet_length + 4) / block_size; |
buffer_clear(&outgoing_packet); |
buffer_clear(&outgoing_packet); |
|
|
if (type == SSH2_MSG_NEWKEYS) |
if (type == SSH2_MSG_NEWKEYS) |
set_newkeys(MODE_OUT); |
set_newkeys(MODE_OUT); |
} |
} |
|
|
|
static void |
|
packet_send2(void) |
|
{ |
|
static int rekeying = 0; |
|
struct packet *p; |
|
u_char type, *cp; |
|
|
|
cp = buffer_ptr(&outgoing_packet); |
|
type = cp[5]; |
|
|
|
/* during rekeying we can only send key exchange messages */ |
|
if (rekeying) { |
|
if (!((type >= SSH2_MSG_TRANSPORT_MIN) && |
|
(type <= SSH2_MSG_TRANSPORT_MAX))) { |
|
debug("enqueue packet: %u", type); |
|
p = xmalloc(sizeof(*p)); |
|
p->type = type; |
|
memcpy(&p->payload, &outgoing_packet, sizeof(Buffer)); |
|
buffer_init(&outgoing_packet); |
|
TAILQ_INSERT_TAIL(&outgoing, p, next); |
|
return; |
|
} |
|
} |
|
|
|
/* rekeying starts with sending KEXINIT */ |
|
if (type == SSH2_MSG_KEXINIT) |
|
rekeying = 1; |
|
|
|
packet_send2_wrapped(); |
|
|
|
/* after a NEWKEYS message we can send the complete queue */ |
|
if (type == SSH2_MSG_NEWKEYS) { |
|
rekeying = 0; |
|
while ((p = TAILQ_FIRST(&outgoing))) { |
|
type = p->type; |
|
debug("dequeue packet: %u", type); |
|
buffer_free(&outgoing_packet); |
|
memcpy(&outgoing_packet, &p->payload, |
|
sizeof(Buffer)); |
|
TAILQ_REMOVE(&outgoing, p, next); |
|
xfree(p); |
|
packet_send2_wrapped(); |
|
} |
|
} |
|
} |
|
|
void |
void |
packet_send(void) |
packet_send(void) |
{ |
{ |
|
|
/* Read data from the socket. */ |
/* Read data from the socket. */ |
len = read(connection_in, buf, sizeof(buf)); |
len = read(connection_in, buf, sizeof(buf)); |
if (len == 0) { |
if (len == 0) { |
log("Connection closed by %.200s", get_remote_ipaddr()); |
logit("Connection closed by %.200s", get_remote_ipaddr()); |
fatal_cleanup(); |
cleanup_exit(255); |
} |
} |
if (len < 0) |
if (len < 0) |
fatal("Read from socket failed: %.100s", strerror(errno)); |
fatal("Read from socket failed: %.100s", strerror(errno)); |
|
|
cp = buffer_ptr(&incoming_packet); |
cp = buffer_ptr(&incoming_packet); |
packet_length = GET_32BIT(cp); |
packet_length = GET_32BIT(cp); |
if (packet_length < 1 + 4 || packet_length > 256 * 1024) { |
if (packet_length < 1 + 4 || packet_length > 256 * 1024) { |
|
#ifdef PACKET_DEBUG |
buffer_dump(&incoming_packet); |
buffer_dump(&incoming_packet); |
|
#endif |
packet_disconnect("Bad packet length %u.", packet_length); |
packet_disconnect("Bad packet length %u.", packet_length); |
} |
} |
DBG(debug("input: packet len %u", packet_length+4)); |
DBG(debug("input: packet len %u", packet_length+4)); |
|
|
* 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, p_read.seqnr, |
buffer_ptr(&incoming_packet), |
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", p_read.seqnr)); |
buffer_consume(&input, mac->mac_len); |
buffer_consume(&input, mac->mac_len); |
} |
} |
if (seqnr_p != NULL) |
if (seqnr_p != NULL) |
*seqnr_p = read_seqnr; |
*seqnr_p = p_read.seqnr; |
if (++read_seqnr == 0) |
if (++p_read.seqnr == 0) |
log("incoming seqnr wraps around"); |
logit("incoming seqnr wraps around"); |
|
if (++p_read.packets == 0) |
|
if (!(datafellows & SSH_BUG_NOREKEY)) |
|
fatal("XXX too many packets with same key"); |
|
p_read.blocks += (packet_length + 4) / block_size; |
|
|
/* get padlen */ |
/* get padlen */ |
cp = buffer_ptr(&incoming_packet); |
cp = buffer_ptr(&incoming_packet); |
|
|
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", |
logit("Received disconnect from %s: %u: %.400s", |
get_remote_ipaddr(), reason, msg); |
get_remote_ipaddr(), reason, msg); |
xfree(msg); |
xfree(msg); |
fatal_cleanup(); |
cleanup_exit(255); |
break; |
break; |
case SSH2_MSG_UNIMPLEMENTED: |
case SSH2_MSG_UNIMPLEMENTED: |
seqnr = packet_get_int(); |
seqnr = packet_get_int(); |
|
|
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", |
logit("Received disconnect from %s: %.400s", |
get_remote_ipaddr(), msg); |
get_remote_ipaddr(), msg); |
fatal_cleanup(); |
cleanup_exit(255); |
xfree(msg); |
xfree(msg); |
break; |
break; |
default: |
default: |
|
|
va_end(args); |
va_end(args); |
|
|
/* Display the error locally */ |
/* Display the error locally */ |
log("Disconnecting: %.100s", buf); |
logit("Disconnecting: %.100s", buf); |
|
|
/* Send the disconnect message to the other side, and wait for it to get sent. */ |
/* Send the disconnect message to the other side, and wait for it to get sent. */ |
if (compat20) { |
if (compat20) { |
|
|
|
|
/* Close the connection. */ |
/* Close the connection. */ |
packet_close(); |
packet_close(); |
|
cleanup_exit(255); |
fatal_cleanup(); |
|
} |
} |
|
|
/* 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. */ |
|
|
return interactive_mode; |
return interactive_mode; |
} |
} |
|
|
int |
u_int |
packet_set_maxsize(int s) |
packet_set_maxsize(u_int s) |
{ |
{ |
static int called = 0; |
static int called = 0; |
|
|
if (called) { |
if (called) { |
log("packet_set_maxsize: called twice: old %d new %d", |
logit("packet_set_maxsize: called twice: old %d new %d", |
max_packet_size, s); |
max_packet_size, s); |
return -1; |
return -1; |
} |
} |
if (s < 4 * 1024 || s > 1024 * 1024) { |
if (s < 4 * 1024 || s > 1024 * 1024) { |
log("packet_set_maxsize: bad size %d", s); |
logit("packet_set_maxsize: bad size %d", s); |
return -1; |
return -1; |
} |
} |
called = 1; |
called = 1; |
|
|
packet_put_char(rand & 0xff); |
packet_put_char(rand & 0xff); |
rand >>= 8; |
rand >>= 8; |
} |
} |
|
} |
|
|
|
#define MAX_PACKETS (1<<31) |
|
int |
|
packet_need_rekeying(void) |
|
{ |
|
if (datafellows & SSH_BUG_NOREKEY) |
|
return 0; |
|
return |
|
(p_send.packets > MAX_PACKETS) || |
|
(p_read.packets > MAX_PACKETS) || |
|
(max_blocks_out && (p_send.blocks > max_blocks_out)) || |
|
(max_blocks_in && (p_read.blocks > max_blocks_in)); |
|
} |
|
|
|
void |
|
packet_set_rekey_limit(u_int32_t bytes) |
|
{ |
|
rekey_limit = bytes; |
} |
} |