version 1.200, 2015/01/13 19:31:40 |
version 1.201, 2015/01/19 19:52:16 |
|
|
#include <signal.h> |
#include <signal.h> |
#include <time.h> |
#include <time.h> |
|
|
|
#include <zlib.h> |
|
|
|
#include "buffer.h" /* typedefs XXX */ |
|
#include "key.h" /* typedefs XXX */ |
|
|
#include "xmalloc.h" |
#include "xmalloc.h" |
#include "buffer.h" |
|
#include "packet.h" |
|
#include "crc32.h" |
#include "crc32.h" |
#include "compress.h" |
|
#include "deattack.h" |
#include "deattack.h" |
#include "compat.h" |
#include "compat.h" |
#include "ssh1.h" |
#include "ssh1.h" |
#include "ssh2.h" |
#include "ssh2.h" |
#include "cipher.h" |
#include "cipher.h" |
#include "key.h" |
#include "sshkey.h" |
#include "kex.h" |
#include "kex.h" |
#include "digest.h" |
#include "digest.h" |
#include "mac.h" |
#include "mac.h" |
|
|
#include "misc.h" |
#include "misc.h" |
#include "channels.h" |
#include "channels.h" |
#include "ssh.h" |
#include "ssh.h" |
#include "ssherr.h" |
#include "packet.h" |
#include "roaming.h" |
#include "roaming.h" |
|
#include "ssherr.h" |
|
#include "sshbuf.h" |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
#define DBG(x) x |
#define DBG(x) x |
|
|
struct packet { |
struct packet { |
TAILQ_ENTRY(packet) next; |
TAILQ_ENTRY(packet) next; |
u_char type; |
u_char type; |
Buffer payload; |
struct sshbuf *payload; |
}; |
}; |
|
|
struct session_state { |
struct session_state { |
|
|
u_int remote_protocol_flags; |
u_int remote_protocol_flags; |
|
|
/* Encryption context for receiving data. Only used for decryption. */ |
/* Encryption context for receiving data. Only used for decryption. */ |
CipherContext receive_context; |
struct sshcipher_ctx receive_context; |
|
|
/* Encryption context for sending data. Only used for encryption. */ |
/* Encryption context for sending data. Only used for encryption. */ |
CipherContext send_context; |
struct sshcipher_ctx send_context; |
|
|
/* Buffer for raw input data from the socket. */ |
/* Buffer for raw input data from the socket. */ |
Buffer input; |
struct sshbuf *input; |
|
|
/* Buffer for raw output data going to the socket. */ |
/* Buffer for raw output data going to the socket. */ |
Buffer output; |
struct sshbuf *output; |
|
|
/* Buffer for the partial outgoing packet being constructed. */ |
/* Buffer for the partial outgoing packet being constructed. */ |
Buffer outgoing_packet; |
struct sshbuf *outgoing_packet; |
|
|
/* Buffer for the incoming packet currently being processed. */ |
/* Buffer for the incoming packet currently being processed. */ |
Buffer incoming_packet; |
struct sshbuf *incoming_packet; |
|
|
/* Scratch buffer for packet compression/decompression. */ |
/* Scratch buffer for packet compression/decompression. */ |
Buffer compression_buffer; |
struct sshbuf *compression_buffer; |
int compression_buffer_ready; |
|
|
|
|
/* Incoming/outgoing compression dictionaries */ |
|
z_stream compression_in_stream; |
|
z_stream compression_out_stream; |
|
int compression_in_started; |
|
int compression_out_started; |
|
int compression_in_failures; |
|
int compression_out_failures; |
|
|
/* |
/* |
* Flag indicating whether packet compression/decompression is |
* Flag indicating whether packet compression/decompression is |
* enabled. |
* enabled. |
|
|
int packet_timeout_ms; |
int packet_timeout_ms; |
|
|
/* Session key information for Encryption and MAC */ |
/* Session key information for Encryption and MAC */ |
Newkeys *newkeys[MODE_MAX]; |
struct newkeys *newkeys[MODE_MAX]; |
struct packet_state p_read, p_send; |
struct packet_state p_read, p_send; |
|
|
/* Volume-based rekeying */ |
/* Volume-based rekeying */ |
|
|
|
|
/* XXX discard incoming data after MAC error */ |
/* XXX discard incoming data after MAC error */ |
u_int packet_discard; |
u_int packet_discard; |
Mac *packet_discard_mac; |
struct sshmac *packet_discard_mac; |
|
|
/* Used in packet_read_poll2() */ |
/* Used in packet_read_poll2() */ |
u_int packlen; |
u_int packlen; |
|
|
/* Used in packet_set_maxsize */ |
/* Used in packet_set_maxsize */ |
int set_maxsize_called; |
int set_maxsize_called; |
|
|
|
/* One-off warning about weak ciphers */ |
|
int cipher_warning_done; |
|
|
|
/* SSH1 CRC compensation attack detector */ |
|
struct deattack_ctx deattack; |
|
|
TAILQ_HEAD(, packet) outgoing; |
TAILQ_HEAD(, packet) outgoing; |
}; |
}; |
|
|
static struct session_state *active_state, *backup_state; |
struct ssh * |
|
ssh_alloc_session_state(void) |
static struct session_state * |
|
alloc_session_state(void) |
|
{ |
{ |
struct session_state *s = xcalloc(1, sizeof(*s)); |
struct ssh *ssh = NULL; |
|
struct session_state *state = NULL; |
|
|
s->connection_in = -1; |
if ((ssh = calloc(1, sizeof(*ssh))) == NULL || |
s->connection_out = -1; |
(state = calloc(1, sizeof(*state))) == NULL || |
s->max_packet_size = 32768; |
(state->input = sshbuf_new()) == NULL || |
s->packet_timeout_ms = -1; |
(state->output = sshbuf_new()) == NULL || |
return s; |
(state->outgoing_packet = sshbuf_new()) == NULL || |
|
(state->incoming_packet = sshbuf_new()) == NULL) |
|
goto fail; |
|
TAILQ_INIT(&state->outgoing); |
|
state->connection_in = -1; |
|
state->connection_out = -1; |
|
state->max_packet_size = 32768; |
|
state->packet_timeout_ms = -1; |
|
state->p_send.packets = state->p_read.packets = 0; |
|
state->initialized = 1; |
|
/* |
|
* ssh_packet_send2() needs to queue packets until |
|
* we've done the initial key exchange. |
|
*/ |
|
state->rekeying = 1; |
|
ssh->state = state; |
|
return ssh; |
|
fail: |
|
if (state) { |
|
sshbuf_free(state->input); |
|
sshbuf_free(state->output); |
|
sshbuf_free(state->incoming_packet); |
|
sshbuf_free(state->outgoing_packet); |
|
free(state); |
|
} |
|
free(ssh); |
|
return NULL; |
} |
} |
|
|
/* |
/* |
* 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. |
*/ |
*/ |
void |
struct ssh * |
packet_set_connection(int fd_in, int fd_out) |
ssh_packet_set_connection(struct ssh *ssh, int fd_in, int fd_out) |
{ |
{ |
const Cipher *none = cipher_by_name("none"); |
struct session_state *state; |
|
const struct sshcipher *none = cipher_by_name("none"); |
int r; |
int r; |
|
|
if (none == NULL) |
if (none == NULL) |
fatal("packet_set_connection: cannot load cipher 'none'"); |
fatal("%s: cannot load cipher 'none'", __func__); |
if (active_state == NULL) |
if (ssh == NULL) |
active_state = alloc_session_state(); |
ssh = ssh_alloc_session_state(); |
active_state->connection_in = fd_in; |
if (ssh == NULL) |
active_state->connection_out = fd_out; |
fatal("%s: cound not allocate state", __func__); |
if ((r = cipher_init(&active_state->send_context, none, |
state = ssh->state; |
|
state->connection_in = fd_in; |
|
state->connection_out = fd_out; |
|
if ((r = cipher_init(&state->send_context, none, |
(const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT)) != 0 || |
(const u_char *)"", 0, NULL, 0, CIPHER_ENCRYPT)) != 0 || |
(r = cipher_init(&active_state->receive_context, none, |
(r = cipher_init(&state->receive_context, none, |
(const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT)) != 0) |
(const u_char *)"", 0, NULL, 0, CIPHER_DECRYPT)) != 0) |
fatal("%s: cipher_init: %s", __func__, ssh_err(r)); |
fatal("%s: cipher_init failed: %s", __func__, ssh_err(r)); |
active_state->newkeys[MODE_IN] = active_state->newkeys[MODE_OUT] = NULL; |
state->newkeys[MODE_IN] = state->newkeys[MODE_OUT] = NULL; |
if (!active_state->initialized) { |
deattack_init(&state->deattack); |
active_state->initialized = 1; |
return ssh; |
buffer_init(&active_state->input); |
|
buffer_init(&active_state->output); |
|
buffer_init(&active_state->outgoing_packet); |
|
buffer_init(&active_state->incoming_packet); |
|
TAILQ_INIT(&active_state->outgoing); |
|
active_state->p_send.packets = active_state->p_read.packets = 0; |
|
} |
|
} |
} |
|
|
void |
void |
packet_set_timeout(int timeout, int count) |
ssh_packet_set_timeout(struct ssh *ssh, int timeout, int count) |
{ |
{ |
|
struct session_state *state = ssh->state; |
|
|
if (timeout <= 0 || count <= 0) { |
if (timeout <= 0 || count <= 0) { |
active_state->packet_timeout_ms = -1; |
state->packet_timeout_ms = -1; |
return; |
return; |
} |
} |
if ((INT_MAX / 1000) / count < timeout) |
if ((INT_MAX / 1000) / count < timeout) |
active_state->packet_timeout_ms = INT_MAX; |
state->packet_timeout_ms = INT_MAX; |
else |
else |
active_state->packet_timeout_ms = timeout * count * 1000; |
state->packet_timeout_ms = timeout * count * 1000; |
} |
} |
|
|
static void |
int |
packet_stop_discard(void) |
ssh_packet_stop_discard(struct ssh *ssh) |
{ |
{ |
if (active_state->packet_discard_mac) { |
struct session_state *state = ssh->state; |
|
int r; |
|
|
|
if (state->packet_discard_mac) { |
char buf[1024]; |
char buf[1024]; |
|
|
memset(buf, 'a', sizeof(buf)); |
memset(buf, 'a', sizeof(buf)); |
while (buffer_len(&active_state->incoming_packet) < |
while (sshbuf_len(state->incoming_packet) < |
PACKET_MAX_SIZE) |
PACKET_MAX_SIZE) |
buffer_append(&active_state->incoming_packet, buf, |
if ((r = sshbuf_put(state->incoming_packet, buf, |
sizeof(buf)); |
sizeof(buf))) != 0) |
(void) mac_compute(active_state->packet_discard_mac, |
return r; |
active_state->p_read.seqnr, |
(void) mac_compute(state->packet_discard_mac, |
buffer_ptr(&active_state->incoming_packet), |
state->p_read.seqnr, |
PACKET_MAX_SIZE, NULL, 0); |
sshbuf_ptr(state->incoming_packet), PACKET_MAX_SIZE, |
|
NULL, 0); |
} |
} |
logit("Finished discarding for %.200s", get_remote_ipaddr()); |
logit("Finished discarding for %.200s", ssh_remote_ipaddr(ssh)); |
cleanup_exit(255); |
return SSH_ERR_MAC_INVALID; |
} |
} |
|
|
static void |
static int |
packet_start_discard(Enc *enc, Mac *mac, u_int packet_length, u_int discard) |
ssh_packet_start_discard(struct ssh *ssh, struct sshenc *enc, |
|
struct sshmac *mac, u_int packet_length, u_int discard) |
{ |
{ |
if (enc == NULL || !cipher_is_cbc(enc->cipher) || (mac && mac->etm)) |
struct session_state *state = ssh->state; |
packet_disconnect("Packet corrupt"); |
int r; |
|
|
|
if (enc == NULL || !cipher_is_cbc(enc->cipher) || (mac && mac->etm)) { |
|
if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) |
|
return r; |
|
return SSH_ERR_MAC_INVALID; |
|
} |
if (packet_length != PACKET_MAX_SIZE && mac && mac->enabled) |
if (packet_length != PACKET_MAX_SIZE && mac && mac->enabled) |
active_state->packet_discard_mac = mac; |
state->packet_discard_mac = mac; |
if (buffer_len(&active_state->input) >= discard) |
if (sshbuf_len(state->input) >= discard && |
packet_stop_discard(); |
(r = ssh_packet_stop_discard(ssh)) != 0) |
active_state->packet_discard = discard - |
return r; |
buffer_len(&active_state->input); |
state->packet_discard = discard - sshbuf_len(state->input); |
|
return 0; |
} |
} |
|
|
/* 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) |
ssh_packet_connection_is_on_socket(struct ssh *ssh) |
{ |
{ |
|
struct session_state *state = ssh->state; |
struct sockaddr_storage from, to; |
struct sockaddr_storage from, to; |
socklen_t fromlen, tolen; |
socklen_t fromlen, tolen; |
|
|
/* filedescriptors in and out are the same, so it's a socket */ |
/* filedescriptors in and out are the same, so it's a socket */ |
if (active_state->connection_in == active_state->connection_out) |
if (state->connection_in == state->connection_out) |
return 1; |
return 1; |
fromlen = sizeof(from); |
fromlen = sizeof(from); |
memset(&from, 0, sizeof(from)); |
memset(&from, 0, sizeof(from)); |
if (getpeername(active_state->connection_in, (struct sockaddr *)&from, |
if (getpeername(state->connection_in, (struct sockaddr *)&from, |
&fromlen) < 0) |
&fromlen) < 0) |
return 0; |
return 0; |
tolen = sizeof(to); |
tolen = sizeof(to); |
memset(&to, 0, sizeof(to)); |
memset(&to, 0, sizeof(to)); |
if (getpeername(active_state->connection_out, (struct sockaddr *)&to, |
if (getpeername(state->connection_out, (struct sockaddr *)&to, |
&tolen) < 0) |
&tolen) < 0) |
return 0; |
return 0; |
if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) |
if (fromlen != tolen || memcmp(&from, &to, fromlen) != 0) |
|
|
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 |
void |
packet_get_keyiv(int mode, u_char *iv, u_int len) |
ssh_packet_get_bytes(struct ssh *ssh, u_int64_t *ibytes, u_int64_t *obytes) |
{ |
{ |
CipherContext *cc; |
if (ibytes) |
int r; |
*ibytes = ssh->state->p_read.bytes; |
|
if (obytes) |
if (mode == MODE_OUT) |
*obytes = ssh->state->p_send.bytes; |
cc = &active_state->send_context; |
|
else |
|
cc = &active_state->receive_context; |
|
|
|
if ((r = cipher_get_keyiv(cc, iv, len)) != 0) |
|
fatal("%s: cipher_get_keyiv: %s", __func__, ssh_err(r)); |
|
} |
} |
|
|
int |
int |
packet_get_keycontext(int mode, u_char *dat) |
ssh_packet_connection_af(struct ssh *ssh) |
{ |
{ |
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &active_state->send_context; |
|
else |
|
cc = &active_state->receive_context; |
|
|
|
return (cipher_get_keycontext(cc, dat)); |
|
} |
|
|
|
void |
|
packet_set_keycontext(int mode, u_char *dat) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &active_state->send_context; |
|
else |
|
cc = &active_state->receive_context; |
|
|
|
cipher_set_keycontext(cc, dat); |
|
} |
|
|
|
int |
|
packet_get_keyiv_len(int mode) |
|
{ |
|
CipherContext *cc; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &active_state->send_context; |
|
else |
|
cc = &active_state->receive_context; |
|
|
|
return (cipher_get_keyiv_len(cc)); |
|
} |
|
|
|
void |
|
packet_set_iv(int mode, u_char *dat) |
|
{ |
|
CipherContext *cc; |
|
int r; |
|
|
|
if (mode == MODE_OUT) |
|
cc = &active_state->send_context; |
|
else |
|
cc = &active_state->receive_context; |
|
|
|
if ((r = cipher_set_keyiv(cc, dat)) != 0) |
|
fatal("%s: cipher_set_keyiv: %s", __func__, ssh_err(r)); |
|
} |
|
|
|
int |
|
packet_get_ssh1_cipher(void) |
|
{ |
|
return (cipher_get_number(active_state->receive_context.cipher)); |
|
} |
|
|
|
void |
|
packet_get_state(int mode, u_int32_t *seqnr, u_int64_t *blocks, |
|
u_int32_t *packets, u_int64_t *bytes) |
|
{ |
|
struct packet_state *state; |
|
|
|
state = (mode == MODE_IN) ? |
|
&active_state->p_read : &active_state->p_send; |
|
if (seqnr) |
|
*seqnr = state->seqnr; |
|
if (blocks) |
|
*blocks = state->blocks; |
|
if (packets) |
|
*packets = state->packets; |
|
if (bytes) |
|
*bytes = state->bytes; |
|
} |
|
|
|
void |
|
packet_set_state(int mode, u_int32_t seqnr, u_int64_t blocks, u_int32_t packets, |
|
u_int64_t bytes) |
|
{ |
|
struct packet_state *state; |
|
|
|
state = (mode == MODE_IN) ? |
|
&active_state->p_read : &active_state->p_send; |
|
state->seqnr = seqnr; |
|
state->blocks = blocks; |
|
state->packets = packets; |
|
state->bytes = bytes; |
|
} |
|
|
|
static int |
|
packet_connection_af(void) |
|
{ |
|
struct sockaddr_storage to; |
struct sockaddr_storage to; |
socklen_t tolen = sizeof(to); |
socklen_t tolen = sizeof(to); |
|
|
memset(&to, 0, sizeof(to)); |
memset(&to, 0, sizeof(to)); |
if (getsockname(active_state->connection_out, (struct sockaddr *)&to, |
if (getsockname(ssh->state->connection_out, (struct sockaddr *)&to, |
&tolen) < 0) |
&tolen) < 0) |
return 0; |
return 0; |
return to.ss_family; |
return to.ss_family; |
|
|
/* Sets the connection into non-blocking mode. */ |
/* Sets the connection into non-blocking mode. */ |
|
|
void |
void |
packet_set_nonblocking(void) |
ssh_packet_set_nonblocking(struct ssh *ssh) |
{ |
{ |
/* Set the socket into non-blocking mode. */ |
/* Set the socket into non-blocking mode. */ |
set_nonblock(active_state->connection_in); |
set_nonblock(ssh->state->connection_in); |
|
|
if (active_state->connection_out != active_state->connection_in) |
if (ssh->state->connection_out != ssh->state->connection_in) |
set_nonblock(active_state->connection_out); |
set_nonblock(ssh->state->connection_out); |
} |
} |
|
|
/* Returns the socket used for reading. */ |
/* Returns the socket used for reading. */ |
|
|
int |
int |
packet_get_connection_in(void) |
ssh_packet_get_connection_in(struct ssh *ssh) |
{ |
{ |
return active_state->connection_in; |
return ssh->state->connection_in; |
} |
} |
|
|
/* Returns the descriptor used for writing. */ |
/* Returns the descriptor used for writing. */ |
|
|
int |
int |
packet_get_connection_out(void) |
ssh_packet_get_connection_out(struct ssh *ssh) |
{ |
{ |
return active_state->connection_out; |
return ssh->state->connection_out; |
} |
} |
|
|
|
/* |
|
* Returns the IP-address of the remote host as a string. The returned |
|
* string must not be freed. |
|
*/ |
|
|
|
const char * |
|
ssh_remote_ipaddr(struct ssh *ssh) |
|
{ |
|
/* Check whether we have cached the ipaddr. */ |
|
if (ssh->remote_ipaddr == NULL) |
|
ssh->remote_ipaddr = ssh_packet_connection_is_on_socket(ssh) ? |
|
get_peer_ipaddr(ssh->state->connection_in) : |
|
strdup("UNKNOWN"); |
|
if (ssh->remote_ipaddr == NULL) |
|
return "UNKNOWN"; |
|
return ssh->remote_ipaddr; |
|
} |
|
|
/* 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) |
ssh_packet_close(struct ssh *ssh) |
{ |
{ |
if (!active_state->initialized) |
struct session_state *state = ssh->state; |
|
int r; |
|
u_int mode; |
|
|
|
if (!state->initialized) |
return; |
return; |
active_state->initialized = 0; |
state->initialized = 0; |
if (active_state->connection_in == active_state->connection_out) { |
if (state->connection_in == state->connection_out) { |
shutdown(active_state->connection_out, SHUT_RDWR); |
shutdown(state->connection_out, SHUT_RDWR); |
close(active_state->connection_out); |
close(state->connection_out); |
} else { |
} else { |
close(active_state->connection_in); |
close(state->connection_in); |
close(active_state->connection_out); |
close(state->connection_out); |
} |
} |
buffer_free(&active_state->input); |
sshbuf_free(state->input); |
buffer_free(&active_state->output); |
sshbuf_free(state->output); |
buffer_free(&active_state->outgoing_packet); |
sshbuf_free(state->outgoing_packet); |
buffer_free(&active_state->incoming_packet); |
sshbuf_free(state->incoming_packet); |
if (active_state->compression_buffer_ready) { |
for (mode = 0; mode < MODE_MAX; mode++) |
buffer_free(&active_state->compression_buffer); |
kex_free_newkeys(state->newkeys[mode]); |
buffer_compress_uninit(); |
if (state->compression_buffer) { |
|
sshbuf_free(state->compression_buffer); |
|
if (state->compression_out_started) { |
|
z_streamp stream = &state->compression_out_stream; |
|
debug("compress outgoing: " |
|
"raw data %llu, compressed %llu, factor %.2f", |
|
(unsigned long long)stream->total_in, |
|
(unsigned long long)stream->total_out, |
|
stream->total_in == 0 ? 0.0 : |
|
(double) stream->total_out / stream->total_in); |
|
if (state->compression_out_failures == 0) |
|
deflateEnd(stream); |
|
} |
|
if (state->compression_in_started) { |
|
z_streamp stream = &state->compression_out_stream; |
|
debug("compress incoming: " |
|
"raw data %llu, compressed %llu, factor %.2f", |
|
(unsigned long long)stream->total_out, |
|
(unsigned long long)stream->total_in, |
|
stream->total_out == 0 ? 0.0 : |
|
(double) stream->total_in / stream->total_out); |
|
if (state->compression_in_failures == 0) |
|
inflateEnd(stream); |
|
} |
} |
} |
cipher_cleanup(&active_state->send_context); |
if ((r = cipher_cleanup(&state->send_context)) != 0) |
cipher_cleanup(&active_state->receive_context); |
error("%s: cipher_cleanup failed: %s", __func__, ssh_err(r)); |
|
if ((r = cipher_cleanup(&state->receive_context)) != 0) |
|
error("%s: cipher_cleanup failed: %s", __func__, ssh_err(r)); |
|
if (ssh->remote_ipaddr) { |
|
free(ssh->remote_ipaddr); |
|
ssh->remote_ipaddr = NULL; |
|
} |
|
free(ssh->state); |
|
ssh->state = NULL; |
} |
} |
|
|
/* Sets remote side protocol flags. */ |
/* Sets remote side protocol flags. */ |
|
|
void |
void |
packet_set_protocol_flags(u_int protocol_flags) |
ssh_packet_set_protocol_flags(struct ssh *ssh, u_int protocol_flags) |
{ |
{ |
active_state->remote_protocol_flags = protocol_flags; |
ssh->state->remote_protocol_flags = 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) |
ssh_packet_get_protocol_flags(struct ssh *ssh) |
{ |
{ |
return active_state->remote_protocol_flags; |
return ssh->state->remote_protocol_flags; |
} |
} |
|
|
/* |
/* |
|
|
* Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. |
* Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. |
*/ |
*/ |
|
|
static void |
static int |
packet_init_compression(void) |
ssh_packet_init_compression(struct ssh *ssh) |
{ |
{ |
if (active_state->compression_buffer_ready == 1) |
if (!ssh->state->compression_buffer && |
return; |
((ssh->state->compression_buffer = sshbuf_new()) == NULL)) |
active_state->compression_buffer_ready = 1; |
return SSH_ERR_ALLOC_FAIL; |
buffer_init(&active_state->compression_buffer); |
return 0; |
} |
} |
|
|
void |
static int |
packet_start_compression(int level) |
start_compression_out(struct ssh *ssh, int level) |
{ |
{ |
if (active_state->packet_compression && !compat20) |
if (level < 1 || level > 9) |
fatal("Compression already enabled."); |
return SSH_ERR_INVALID_ARGUMENT; |
active_state->packet_compression = 1; |
debug("Enabling compression at level %d.", level); |
packet_init_compression(); |
if (ssh->state->compression_out_started == 1) |
buffer_compress_init_send(level); |
deflateEnd(&ssh->state->compression_out_stream); |
buffer_compress_init_recv(); |
switch (deflateInit(&ssh->state->compression_out_stream, level)) { |
|
case Z_OK: |
|
ssh->state->compression_out_started = 1; |
|
break; |
|
case Z_MEM_ERROR: |
|
return SSH_ERR_ALLOC_FAIL; |
|
default: |
|
return SSH_ERR_INTERNAL_ERROR; |
|
} |
|
return 0; |
} |
} |
|
|
/* |
static int |
* Causes any further packets to be encrypted using the given key. The same |
start_compression_in(struct ssh *ssh) |
* key is used for both sending and reception. However, both directions are |
{ |
* encrypted independently of each other. |
if (ssh->state->compression_in_started == 1) |
*/ |
inflateEnd(&ssh->state->compression_in_stream); |
|
switch (inflateInit(&ssh->state->compression_in_stream)) { |
|
case Z_OK: |
|
ssh->state->compression_in_started = 1; |
|
break; |
|
case Z_MEM_ERROR: |
|
return SSH_ERR_ALLOC_FAIL; |
|
default: |
|
return SSH_ERR_INTERNAL_ERROR; |
|
} |
|
return 0; |
|
} |
|
|
void |
int |
packet_set_encryption_key(const u_char *key, u_int keylen, int number) |
ssh_packet_start_compression(struct ssh *ssh, int level) |
{ |
{ |
const Cipher *cipher = cipher_by_number(number); |
|
int r; |
int r; |
|
|
if (cipher == NULL) |
if (ssh->state->packet_compression && !compat20) |
fatal("packet_set_encryption_key: unknown cipher number %d", number); |
return SSH_ERR_INTERNAL_ERROR; |
if (keylen < 20) |
ssh->state->packet_compression = 1; |
fatal("packet_set_encryption_key: keylen too small: %d", keylen); |
if ((r = ssh_packet_init_compression(ssh)) != 0 || |
if (keylen > SSH_SESSION_KEY_LENGTH) |
(r = start_compression_in(ssh)) != 0 || |
fatal("packet_set_encryption_key: keylen too big: %d", keylen); |
(r = start_compression_out(ssh, level)) != 0) |
memcpy(active_state->ssh1_key, key, keylen); |
return r; |
active_state->ssh1_keylen = keylen; |
return 0; |
if ((r = cipher_init(&active_state->send_context, cipher, |
|
key, keylen, NULL, 0, CIPHER_ENCRYPT)) != 0 || |
|
(r = cipher_init(&active_state->receive_context, cipher, |
|
key, keylen, NULL, 0, CIPHER_DECRYPT)) != 0) |
|
fatal("%s: cipher_init: %s", __func__, ssh_err(r)); |
|
} |
} |
|
|
u_int |
/* XXX remove need for separate compression buffer */ |
packet_get_encryption_key(u_char *key) |
static int |
|
compress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) |
{ |
{ |
if (key == NULL) |
u_char buf[4096]; |
return (active_state->ssh1_keylen); |
int r, status; |
memcpy(key, active_state->ssh1_key, active_state->ssh1_keylen); |
|
return (active_state->ssh1_keylen); |
|
} |
|
|
|
/* Start constructing a packet to send. */ |
if (ssh->state->compression_out_started != 1) |
void |
return SSH_ERR_INTERNAL_ERROR; |
packet_start(u_char type) |
|
{ |
|
u_char buf[9]; |
|
int len; |
|
|
|
DBG(debug("packet_start[%d]", type)); |
/* This case is not handled below. */ |
len = compat20 ? 6 : 9; |
if (sshbuf_len(in) == 0) |
memset(buf, 0, len - 1); |
return 0; |
buf[len - 1] = type; |
|
buffer_clear(&active_state->outgoing_packet); |
|
buffer_append(&active_state->outgoing_packet, buf, len); |
|
} |
|
|
|
/* Append payload. */ |
/* Input is the contents of the input buffer. */ |
void |
if ((ssh->state->compression_out_stream.next_in = |
packet_put_char(int value) |
sshbuf_mutable_ptr(in)) == NULL) |
{ |
return SSH_ERR_INTERNAL_ERROR; |
char ch = value; |
ssh->state->compression_out_stream.avail_in = sshbuf_len(in); |
|
|
buffer_append(&active_state->outgoing_packet, &ch, 1); |
/* Loop compressing until deflate() returns with avail_out != 0. */ |
} |
do { |
|
/* Set up fixed-size output buffer. */ |
|
ssh->state->compression_out_stream.next_out = buf; |
|
ssh->state->compression_out_stream.avail_out = sizeof(buf); |
|
|
void |
/* Compress as much data into the buffer as possible. */ |
packet_put_int(u_int value) |
status = deflate(&ssh->state->compression_out_stream, |
{ |
Z_PARTIAL_FLUSH); |
buffer_put_int(&active_state->outgoing_packet, value); |
switch (status) { |
|
case Z_MEM_ERROR: |
|
return SSH_ERR_ALLOC_FAIL; |
|
case Z_OK: |
|
/* Append compressed data to output_buffer. */ |
|
if ((r = sshbuf_put(out, buf, sizeof(buf) - |
|
ssh->state->compression_out_stream.avail_out)) != 0) |
|
return r; |
|
break; |
|
case Z_STREAM_ERROR: |
|
default: |
|
ssh->state->compression_out_failures++; |
|
return SSH_ERR_INVALID_FORMAT; |
|
} |
|
} while (ssh->state->compression_out_stream.avail_out == 0); |
|
return 0; |
} |
} |
|
|
void |
static int |
packet_put_int64(u_int64_t value) |
uncompress_buffer(struct ssh *ssh, struct sshbuf *in, struct sshbuf *out) |
{ |
{ |
buffer_put_int64(&active_state->outgoing_packet, value); |
u_char buf[4096]; |
|
int r, status; |
|
|
|
if (ssh->state->compression_in_started != 1) |
|
return SSH_ERR_INTERNAL_ERROR; |
|
|
|
if ((ssh->state->compression_in_stream.next_in = |
|
sshbuf_mutable_ptr(in)) == NULL) |
|
return SSH_ERR_INTERNAL_ERROR; |
|
ssh->state->compression_in_stream.avail_in = sshbuf_len(in); |
|
|
|
for (;;) { |
|
/* Set up fixed-size output buffer. */ |
|
ssh->state->compression_in_stream.next_out = buf; |
|
ssh->state->compression_in_stream.avail_out = sizeof(buf); |
|
|
|
status = inflate(&ssh->state->compression_in_stream, |
|
Z_PARTIAL_FLUSH); |
|
switch (status) { |
|
case Z_OK: |
|
if ((r = sshbuf_put(out, buf, sizeof(buf) - |
|
ssh->state->compression_in_stream.avail_out)) != 0) |
|
return r; |
|
break; |
|
case Z_BUF_ERROR: |
|
/* |
|
* Comments in zlib.h say that we should keep calling |
|
* inflate() until we get an error. This appears to |
|
* be the error that we get. |
|
*/ |
|
return 0; |
|
case Z_DATA_ERROR: |
|
return SSH_ERR_INVALID_FORMAT; |
|
case Z_MEM_ERROR: |
|
return SSH_ERR_ALLOC_FAIL; |
|
case Z_STREAM_ERROR: |
|
default: |
|
ssh->state->compression_in_failures++; |
|
return SSH_ERR_INTERNAL_ERROR; |
|
} |
|
} |
|
/* NOTREACHED */ |
} |
} |
|
|
void |
/* Serialise compression state into a blob for privsep */ |
packet_put_string(const void *buf, u_int len) |
static int |
|
ssh_packet_get_compress_state(struct sshbuf *m, struct ssh *ssh) |
{ |
{ |
buffer_put_string(&active_state->outgoing_packet, buf, len); |
struct session_state *state = ssh->state; |
|
struct sshbuf *b; |
|
int r; |
|
|
|
if ((b = sshbuf_new()) == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
|
if (state->compression_in_started) { |
|
if ((r = sshbuf_put_string(b, &state->compression_in_stream, |
|
sizeof(state->compression_in_stream))) != 0) |
|
goto out; |
|
} else if ((r = sshbuf_put_string(b, NULL, 0)) != 0) |
|
goto out; |
|
if (state->compression_out_started) { |
|
if ((r = sshbuf_put_string(b, &state->compression_out_stream, |
|
sizeof(state->compression_out_stream))) != 0) |
|
goto out; |
|
} else if ((r = sshbuf_put_string(b, NULL, 0)) != 0) |
|
goto out; |
|
r = sshbuf_put_stringb(m, b); |
|
out: |
|
sshbuf_free(b); |
|
return r; |
} |
} |
|
|
void |
/* Deserialise compression state from a blob for privsep */ |
packet_put_cstring(const char *str) |
static int |
|
ssh_packet_set_compress_state(struct ssh *ssh, struct sshbuf *m) |
{ |
{ |
buffer_put_cstring(&active_state->outgoing_packet, str); |
struct session_state *state = ssh->state; |
|
struct sshbuf *b = NULL; |
|
int r; |
|
const u_char *inblob, *outblob; |
|
size_t inl, outl; |
|
|
|
if ((r = sshbuf_froms(m, &b)) != 0) |
|
goto out; |
|
if ((r = sshbuf_get_string_direct(b, &inblob, &inl)) != 0 || |
|
(r = sshbuf_get_string_direct(b, &outblob, &outl)) != 0) |
|
goto out; |
|
if (inl == 0) |
|
state->compression_in_started = 0; |
|
else if (inl != sizeof(state->compression_in_stream)) { |
|
r = SSH_ERR_INTERNAL_ERROR; |
|
goto out; |
|
} else { |
|
state->compression_in_started = 1; |
|
memcpy(&state->compression_in_stream, inblob, inl); |
|
} |
|
if (outl == 0) |
|
state->compression_out_started = 0; |
|
else if (outl != sizeof(state->compression_out_stream)) { |
|
r = SSH_ERR_INTERNAL_ERROR; |
|
goto out; |
|
} else { |
|
state->compression_out_started = 1; |
|
memcpy(&state->compression_out_stream, outblob, outl); |
|
} |
|
r = 0; |
|
out: |
|
sshbuf_free(b); |
|
return r; |
} |
} |
|
|
void |
void |
packet_put_raw(const void *buf, u_int len) |
ssh_packet_set_compress_hooks(struct ssh *ssh, void *ctx, |
|
void *(*allocfunc)(void *, u_int, u_int), |
|
void (*freefunc)(void *, void *)) |
{ |
{ |
buffer_append(&active_state->outgoing_packet, buf, len); |
ssh->state->compression_out_stream.zalloc = (alloc_func)allocfunc; |
|
ssh->state->compression_out_stream.zfree = (free_func)freefunc; |
|
ssh->state->compression_out_stream.opaque = ctx; |
|
ssh->state->compression_in_stream.zalloc = (alloc_func)allocfunc; |
|
ssh->state->compression_in_stream.zfree = (free_func)freefunc; |
|
ssh->state->compression_in_stream.opaque = ctx; |
} |
} |
|
|
|
/* |
|
* Causes any further packets to be encrypted using the given key. The same |
|
* key is used for both sending and reception. However, both directions are |
|
* encrypted independently of each other. |
|
*/ |
|
|
#ifdef WITH_OPENSSL |
#ifdef WITH_OPENSSL |
void |
void |
packet_put_bignum(BIGNUM * value) |
ssh_packet_set_encryption_key(struct ssh *ssh, const u_char *key, u_int keylen, int number) |
{ |
{ |
buffer_put_bignum(&active_state->outgoing_packet, value); |
struct session_state *state = ssh->state; |
} |
const struct sshcipher *cipher = cipher_by_number(number); |
|
int r; |
|
const char *wmsg; |
|
|
void |
if (cipher == NULL) |
packet_put_bignum2(BIGNUM * value) |
fatal("%s: unknown cipher number %d", __func__, number); |
{ |
if (keylen < 20) |
buffer_put_bignum2(&active_state->outgoing_packet, value); |
fatal("%s: keylen too small: %d", __func__, keylen); |
|
if (keylen > SSH_SESSION_KEY_LENGTH) |
|
fatal("%s: keylen too big: %d", __func__, keylen); |
|
memcpy(state->ssh1_key, key, keylen); |
|
state->ssh1_keylen = keylen; |
|
if ((r = cipher_init(&state->send_context, cipher, key, keylen, |
|
NULL, 0, CIPHER_ENCRYPT)) != 0 || |
|
(r = cipher_init(&state->receive_context, cipher, key, keylen, |
|
NULL, 0, CIPHER_DECRYPT) != 0)) |
|
fatal("%s: cipher_init failed: %s", __func__, ssh_err(r)); |
|
if (!state->cipher_warning_done && |
|
((wmsg = cipher_warning_message(&state->send_context)) != NULL || |
|
(wmsg = cipher_warning_message(&state->send_context)) != NULL)) { |
|
error("Warning: %s", wmsg); |
|
state->cipher_warning_done = 1; |
|
} |
} |
} |
|
|
void |
|
packet_put_ecpoint(const EC_GROUP *curve, const EC_POINT *point) |
|
{ |
|
buffer_put_ecpoint(&active_state->outgoing_packet, curve, point); |
|
} |
|
#endif |
#endif |
|
|
/* |
/* |
|
|
* encrypts the packet before sending. |
* encrypts the packet before sending. |
*/ |
*/ |
|
|
static void |
int |
packet_send1(void) |
ssh_packet_send1(struct ssh *ssh) |
{ |
{ |
|
struct session_state *state = ssh->state; |
u_char buf[8], *cp; |
u_char buf[8], *cp; |
int i, padding, len; |
int r, padding, len; |
u_int checksum; |
u_int checksum; |
u_int32_t rnd = 0; |
|
|
|
/* |
/* |
* If using packet compression, compress the payload of the outgoing |
* If using packet compression, compress the payload of the outgoing |
* packet. |
* packet. |
*/ |
*/ |
if (active_state->packet_compression) { |
if (state->packet_compression) { |
buffer_clear(&active_state->compression_buffer); |
sshbuf_reset(state->compression_buffer); |
/* Skip padding. */ |
/* Skip padding. */ |
buffer_consume(&active_state->outgoing_packet, 8); |
if ((r = sshbuf_consume(state->outgoing_packet, 8)) != 0) |
|
goto out; |
/* padding */ |
/* padding */ |
buffer_append(&active_state->compression_buffer, |
if ((r = sshbuf_put(state->compression_buffer, |
"\0\0\0\0\0\0\0\0", 8); |
"\0\0\0\0\0\0\0\0", 8)) != 0) |
buffer_compress(&active_state->outgoing_packet, |
goto out; |
&active_state->compression_buffer); |
if ((r = compress_buffer(ssh, state->outgoing_packet, |
buffer_clear(&active_state->outgoing_packet); |
state->compression_buffer)) != 0) |
buffer_append(&active_state->outgoing_packet, |
goto out; |
buffer_ptr(&active_state->compression_buffer), |
sshbuf_reset(state->outgoing_packet); |
buffer_len(&active_state->compression_buffer)); |
if ((r = sshbuf_putb(state->outgoing_packet, |
|
state->compression_buffer)) != 0) |
|
goto out; |
} |
} |
/* Compute packet length without padding (add checksum, remove padding). */ |
/* Compute packet length without padding (add checksum, remove padding). */ |
len = buffer_len(&active_state->outgoing_packet) + 4 - 8; |
len = sshbuf_len(state->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 (!active_state->send_context.plaintext) { |
if (!state->send_context.plaintext) { |
cp = buffer_ptr(&active_state->outgoing_packet); |
cp = sshbuf_mutable_ptr(state->outgoing_packet); |
for (i = 0; i < padding; i++) { |
if (cp == NULL) { |
if (i % 4 == 0) |
r = SSH_ERR_INTERNAL_ERROR; |
rnd = arc4random(); |
goto out; |
cp[7 - i] = rnd & 0xff; |
|
rnd >>= 8; |
|
} |
} |
|
arc4random_buf(cp + 8 - padding, padding); |
} |
} |
buffer_consume(&active_state->outgoing_packet, 8 - padding); |
if ((r = sshbuf_consume(state->outgoing_packet, 8 - padding)) != 0) |
|
goto out; |
|
|
/* Add check bytes. */ |
/* Add check bytes. */ |
checksum = ssh_crc32(buffer_ptr(&active_state->outgoing_packet), |
checksum = ssh_crc32(sshbuf_ptr(state->outgoing_packet), |
buffer_len(&active_state->outgoing_packet)); |
sshbuf_len(state->outgoing_packet)); |
put_u32(buf, checksum); |
POKE_U32(buf, checksum); |
buffer_append(&active_state->outgoing_packet, buf, 4); |
if ((r = sshbuf_put(state->outgoing_packet, buf, 4)) != 0) |
|
goto out; |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "packet_send plain: "); |
fprintf(stderr, "packet_send plain: "); |
buffer_dump(&active_state->outgoing_packet); |
sshbuf_dump(state->outgoing_packet, stderr); |
#endif |
#endif |
|
|
/* Append to output. */ |
/* Append to output. */ |
put_u32(buf, len); |
POKE_U32(buf, len); |
buffer_append(&active_state->output, buf, 4); |
if ((r = sshbuf_put(state->output, buf, 4)) != 0) |
cp = buffer_append_space(&active_state->output, |
goto out; |
buffer_len(&active_state->outgoing_packet)); |
if ((r = sshbuf_reserve(state->output, |
if (cipher_crypt(&active_state->send_context, 0, cp, |
sshbuf_len(state->outgoing_packet), &cp)) != 0) |
buffer_ptr(&active_state->outgoing_packet), |
goto out; |
buffer_len(&active_state->outgoing_packet), 0, 0) != 0) |
if ((r = cipher_crypt(&state->send_context, 0, cp, |
fatal("%s: cipher_crypt failed", __func__); |
sshbuf_ptr(state->outgoing_packet), |
|
sshbuf_len(state->outgoing_packet), 0, 0)) != 0) |
|
goto out; |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "encrypted: "); |
fprintf(stderr, "encrypted: "); |
buffer_dump(&active_state->output); |
sshbuf_dump(state->output, stderr); |
#endif |
#endif |
active_state->p_send.packets++; |
state->p_send.packets++; |
active_state->p_send.bytes += len + |
state->p_send.bytes += len + |
buffer_len(&active_state->outgoing_packet); |
sshbuf_len(state->outgoing_packet); |
buffer_clear(&active_state->outgoing_packet); |
sshbuf_reset(state->outgoing_packet); |
|
|
/* |
/* |
* Note that the packet is now only buffered in output. It won't be |
* Note that the packet is now only buffered in output. It won't be |
* actually sent until packet_write_wait or packet_write_poll is |
* actually sent until packet_write_wait or packet_write_poll is |
* called. |
* called. |
*/ |
*/ |
|
r = 0; |
|
out: |
|
return r; |
} |
} |
|
|
void |
int |
set_newkeys(int mode) |
ssh_set_newkeys(struct ssh *ssh, int mode) |
{ |
{ |
Enc *enc; |
struct session_state *state = ssh->state; |
Mac *mac; |
struct sshenc *enc; |
Comp *comp; |
struct sshmac *mac; |
CipherContext *cc; |
struct sshcomp *comp; |
|
struct sshcipher_ctx *cc; |
u_int64_t *max_blocks; |
u_int64_t *max_blocks; |
|
const char *wmsg; |
int r, crypt_type; |
int r, crypt_type; |
|
|
debug2("set_newkeys: mode %d", mode); |
debug2("set_newkeys: mode %d", mode); |
|
|
if (mode == MODE_OUT) { |
if (mode == MODE_OUT) { |
cc = &active_state->send_context; |
cc = &state->send_context; |
crypt_type = CIPHER_ENCRYPT; |
crypt_type = CIPHER_ENCRYPT; |
active_state->p_send.packets = active_state->p_send.blocks = 0; |
state->p_send.packets = state->p_send.blocks = 0; |
max_blocks = &active_state->max_blocks_out; |
max_blocks = &state->max_blocks_out; |
} else { |
} else { |
cc = &active_state->receive_context; |
cc = &state->receive_context; |
crypt_type = CIPHER_DECRYPT; |
crypt_type = CIPHER_DECRYPT; |
active_state->p_read.packets = active_state->p_read.blocks = 0; |
state->p_read.packets = state->p_read.blocks = 0; |
max_blocks = &active_state->max_blocks_in; |
max_blocks = &state->max_blocks_in; |
} |
} |
if (active_state->newkeys[mode] != NULL) { |
if (state->newkeys[mode] != NULL) { |
debug("set_newkeys: rekeying"); |
debug("set_newkeys: rekeying"); |
cipher_cleanup(cc); |
if ((r = cipher_cleanup(cc)) != 0) |
enc = &active_state->newkeys[mode]->enc; |
return r; |
mac = &active_state->newkeys[mode]->mac; |
enc = &state->newkeys[mode]->enc; |
comp = &active_state->newkeys[mode]->comp; |
mac = &state->newkeys[mode]->mac; |
|
comp = &state->newkeys[mode]->comp; |
mac_clear(mac); |
mac_clear(mac); |
explicit_bzero(enc->iv, enc->iv_len); |
explicit_bzero(enc->iv, enc->iv_len); |
explicit_bzero(enc->key, enc->key_len); |
explicit_bzero(enc->key, enc->key_len); |
|
|
free(mac->name); |
free(mac->name); |
free(mac->key); |
free(mac->key); |
free(comp->name); |
free(comp->name); |
free(active_state->newkeys[mode]); |
free(state->newkeys[mode]); |
} |
} |
active_state->newkeys[mode] = kex_get_newkeys(mode); |
/* move newkeys from kex to state */ |
if (active_state->newkeys[mode] == NULL) |
if ((state->newkeys[mode] = ssh->kex->newkeys[mode]) == NULL) |
fatal("newkeys: no keys for mode %d", mode); |
return SSH_ERR_INTERNAL_ERROR; |
enc = &active_state->newkeys[mode]->enc; |
ssh->kex->newkeys[mode] = NULL; |
mac = &active_state->newkeys[mode]->mac; |
enc = &state->newkeys[mode]->enc; |
comp = &active_state->newkeys[mode]->comp; |
mac = &state->newkeys[mode]->mac; |
if (cipher_authlen(enc->cipher) == 0 && mac_init(mac) == 0) |
comp = &state->newkeys[mode]->comp; |
mac->enabled = 1; |
if (cipher_authlen(enc->cipher) == 0) { |
|
if ((r = mac_init(mac)) != 0) |
|
return r; |
|
} |
|
mac->enabled = 1; |
DBG(debug("cipher_init_context: %d", mode)); |
DBG(debug("cipher_init_context: %d", mode)); |
if ((r = cipher_init(cc, enc->cipher, enc->key, enc->key_len, |
if ((r = cipher_init(cc, enc->cipher, enc->key, enc->key_len, |
enc->iv, enc->iv_len, crypt_type)) != 0) |
enc->iv, enc->iv_len, crypt_type)) != 0) |
fatal("%s: cipher_init: %s", __func__, ssh_err(r)); |
return r; |
|
if (!state->cipher_warning_done && |
|
(wmsg = cipher_warning_message(cc)) != NULL) { |
|
error("Warning: %s", wmsg); |
|
state->cipher_warning_done = 1; |
|
} |
/* Deleting the keys does not gain extra security */ |
/* Deleting the keys does not gain extra security */ |
/* explicit_bzero(enc->iv, enc->block_size); |
/* explicit_bzero(enc->iv, enc->block_size); |
explicit_bzero(enc->key, enc->key_len); |
explicit_bzero(enc->key, enc->key_len); |
explicit_bzero(mac->key, mac->key_len); */ |
explicit_bzero(mac->key, mac->key_len); */ |
if ((comp->type == COMP_ZLIB || |
if ((comp->type == COMP_ZLIB || |
(comp->type == COMP_DELAYED && |
(comp->type == COMP_DELAYED && |
active_state->after_authentication)) && comp->enabled == 0) { |
state->after_authentication)) && comp->enabled == 0) { |
packet_init_compression(); |
if ((r = ssh_packet_init_compression(ssh)) < 0) |
if (mode == MODE_OUT) |
return r; |
buffer_compress_init_send(6); |
if (mode == MODE_OUT) { |
else |
if ((r = start_compression_out(ssh, 6)) != 0) |
buffer_compress_init_recv(); |
return r; |
|
} else { |
|
if ((r = start_compression_in(ssh)) != 0) |
|
return r; |
|
} |
comp->enabled = 1; |
comp->enabled = 1; |
} |
} |
/* |
/* |
|
|
*max_blocks = (u_int64_t)1 << (enc->block_size*2); |
*max_blocks = (u_int64_t)1 << (enc->block_size*2); |
else |
else |
*max_blocks = ((u_int64_t)1 << 30) / enc->block_size; |
*max_blocks = ((u_int64_t)1 << 30) / enc->block_size; |
if (active_state->rekey_limit) |
if (state->rekey_limit) |
*max_blocks = MIN(*max_blocks, |
*max_blocks = MIN(*max_blocks, |
active_state->rekey_limit / enc->block_size); |
state->rekey_limit / enc->block_size); |
|
return 0; |
} |
} |
|
|
/* |
/* |
|
|
* This happens on the server side after a SSH2_MSG_USERAUTH_SUCCESS is sent, |
* This happens on the server side after a SSH2_MSG_USERAUTH_SUCCESS is sent, |
* and on the client side after a SSH2_MSG_USERAUTH_SUCCESS is received. |
* and on the client side after a SSH2_MSG_USERAUTH_SUCCESS is received. |
*/ |
*/ |
static void |
static int |
packet_enable_delayed_compress(void) |
ssh_packet_enable_delayed_compress(struct ssh *ssh) |
{ |
{ |
Comp *comp = NULL; |
struct session_state *state = ssh->state; |
int mode; |
struct sshcomp *comp = NULL; |
|
int r, mode; |
|
|
/* |
/* |
* Remember that we are past the authentication step, so rekeying |
* Remember that we are past the authentication step, so rekeying |
* with COMP_DELAYED will turn on compression immediately. |
* with COMP_DELAYED will turn on compression immediately. |
*/ |
*/ |
active_state->after_authentication = 1; |
state->after_authentication = 1; |
for (mode = 0; mode < MODE_MAX; mode++) { |
for (mode = 0; mode < MODE_MAX; mode++) { |
/* protocol error: USERAUTH_SUCCESS received before NEWKEYS */ |
/* protocol error: USERAUTH_SUCCESS received before NEWKEYS */ |
if (active_state->newkeys[mode] == NULL) |
if (state->newkeys[mode] == NULL) |
continue; |
continue; |
comp = &active_state->newkeys[mode]->comp; |
comp = &state->newkeys[mode]->comp; |
if (comp && !comp->enabled && comp->type == COMP_DELAYED) { |
if (comp && !comp->enabled && comp->type == COMP_DELAYED) { |
packet_init_compression(); |
if ((r = ssh_packet_init_compression(ssh)) != 0) |
if (mode == MODE_OUT) |
return r; |
buffer_compress_init_send(6); |
if (mode == MODE_OUT) { |
else |
if ((r = start_compression_out(ssh, 6)) != 0) |
buffer_compress_init_recv(); |
return r; |
|
} else { |
|
if ((r = start_compression_in(ssh)) != 0) |
|
return r; |
|
} |
comp->enabled = 1; |
comp->enabled = 1; |
} |
} |
} |
} |
|
return 0; |
} |
} |
|
|
/* |
/* |
* Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) |
* Finalize packet in SSH2 format (compress, mac, encrypt, enqueue) |
*/ |
*/ |
static void |
int |
packet_send2_wrapped(void) |
ssh_packet_send2_wrapped(struct ssh *ssh) |
{ |
{ |
|
struct session_state *state = ssh->state; |
u_char type, *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; |
u_char type, *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; |
u_char padlen, pad = 0; |
u_char padlen, pad = 0; |
u_int i, len, authlen = 0, aadlen = 0; |
u_int authlen = 0, aadlen = 0; |
u_int32_t rnd = 0; |
u_int len; |
Enc *enc = NULL; |
struct sshenc *enc = NULL; |
Mac *mac = NULL; |
struct sshmac *mac = NULL; |
Comp *comp = NULL; |
struct sshcomp *comp = NULL; |
int block_size; |
int r, block_size; |
int r; |
|
|
|
if (active_state->newkeys[MODE_OUT] != NULL) { |
if (state->newkeys[MODE_OUT] != NULL) { |
enc = &active_state->newkeys[MODE_OUT]->enc; |
enc = &state->newkeys[MODE_OUT]->enc; |
mac = &active_state->newkeys[MODE_OUT]->mac; |
mac = &state->newkeys[MODE_OUT]->mac; |
comp = &active_state->newkeys[MODE_OUT]->comp; |
comp = &state->newkeys[MODE_OUT]->comp; |
/* disable mac for authenticated encryption */ |
/* disable mac for authenticated encryption */ |
if ((authlen = cipher_authlen(enc->cipher)) != 0) |
if ((authlen = cipher_authlen(enc->cipher)) != 0) |
mac = NULL; |
mac = NULL; |
|
|
block_size = enc ? enc->block_size : 8; |
block_size = enc ? enc->block_size : 8; |
aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; |
aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; |
|
|
cp = buffer_ptr(&active_state->outgoing_packet); |
type = (sshbuf_ptr(state->outgoing_packet))[5]; |
type = cp[5]; |
|
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "plain: "); |
fprintf(stderr, "plain: "); |
buffer_dump(&active_state->outgoing_packet); |
sshbuf_dump(state->outgoing_packet, stderr); |
#endif |
#endif |
|
|
if (comp && comp->enabled) { |
if (comp && comp->enabled) { |
len = buffer_len(&active_state->outgoing_packet); |
len = sshbuf_len(state->outgoing_packet); |
/* skip header, compress only payload */ |
/* skip header, compress only payload */ |
buffer_consume(&active_state->outgoing_packet, 5); |
if ((r = sshbuf_consume(state->outgoing_packet, 5)) != 0) |
buffer_clear(&active_state->compression_buffer); |
goto out; |
buffer_compress(&active_state->outgoing_packet, |
sshbuf_reset(state->compression_buffer); |
&active_state->compression_buffer); |
if ((r = compress_buffer(ssh, state->outgoing_packet, |
buffer_clear(&active_state->outgoing_packet); |
state->compression_buffer)) != 0) |
buffer_append(&active_state->outgoing_packet, "\0\0\0\0\0", 5); |
goto out; |
buffer_append(&active_state->outgoing_packet, |
sshbuf_reset(state->outgoing_packet); |
buffer_ptr(&active_state->compression_buffer), |
if ((r = sshbuf_put(state->outgoing_packet, |
buffer_len(&active_state->compression_buffer)); |
"\0\0\0\0\0", 5)) != 0 || |
DBG(debug("compression: raw %d compressed %d", len, |
(r = sshbuf_putb(state->outgoing_packet, |
buffer_len(&active_state->outgoing_packet))); |
state->compression_buffer)) != 0) |
|
goto out; |
|
DBG(debug("compression: raw %d compressed %zd", len, |
|
sshbuf_len(state->outgoing_packet))); |
} |
} |
|
|
/* sizeof (packet_len + pad_len + payload) */ |
/* sizeof (packet_len + pad_len + payload) */ |
len = buffer_len(&active_state->outgoing_packet); |
len = sshbuf_len(state->outgoing_packet); |
|
|
/* |
/* |
* calc size of padding, alloc space, get random data, |
* calc size of padding, alloc space, get random data, |
|
|
padlen = block_size - (len % block_size); |
padlen = block_size - (len % block_size); |
if (padlen < 4) |
if (padlen < 4) |
padlen += block_size; |
padlen += block_size; |
if (active_state->extra_pad) { |
if (state->extra_pad) { |
/* will wrap if extra_pad+padlen > 255 */ |
/* will wrap if extra_pad+padlen > 255 */ |
active_state->extra_pad = |
state->extra_pad = |
roundup(active_state->extra_pad, block_size); |
roundup(state->extra_pad, block_size); |
pad = active_state->extra_pad - |
pad = state->extra_pad - |
((len + padlen) % active_state->extra_pad); |
((len + padlen) % state->extra_pad); |
DBG(debug3("%s: adding %d (len %d padlen %d extra_pad %d)", |
DBG(debug3("%s: adding %d (len %d padlen %d extra_pad %d)", |
__func__, pad, len, padlen, active_state->extra_pad)); |
__func__, pad, len, padlen, state->extra_pad)); |
padlen += pad; |
padlen += pad; |
active_state->extra_pad = 0; |
state->extra_pad = 0; |
} |
} |
cp = buffer_append_space(&active_state->outgoing_packet, padlen); |
if ((r = sshbuf_reserve(state->outgoing_packet, padlen, &cp)) != 0) |
if (enc && !active_state->send_context.plaintext) { |
goto out; |
|
if (enc && !state->send_context.plaintext) { |
/* random padding */ |
/* random padding */ |
for (i = 0; i < padlen; i++) { |
arc4random_buf(cp, padlen); |
if (i % 4 == 0) |
|
rnd = arc4random(); |
|
cp[i] = rnd & 0xff; |
|
rnd >>= 8; |
|
} |
|
} else { |
} else { |
/* clear padding */ |
/* clear padding */ |
explicit_bzero(cp, padlen); |
explicit_bzero(cp, padlen); |
} |
} |
/* sizeof (packet_len + pad_len + payload + padding) */ |
/* sizeof (packet_len + pad_len + payload + padding) */ |
len = buffer_len(&active_state->outgoing_packet); |
len = sshbuf_len(state->outgoing_packet); |
cp = buffer_ptr(&active_state->outgoing_packet); |
cp = sshbuf_mutable_ptr(state->outgoing_packet); |
|
if (cp == NULL) { |
|
r = SSH_ERR_INTERNAL_ERROR; |
|
goto out; |
|
} |
/* packet_length includes payload, padding and padding length field */ |
/* packet_length includes payload, padding and padding length field */ |
put_u32(cp, len - 4); |
POKE_U32(cp, len - 4); |
cp[4] = padlen; |
cp[4] = padlen; |
DBG(debug("send: len %d (includes padlen %d, aadlen %d)", |
DBG(debug("send: len %d (includes padlen %d, aadlen %d)", |
len, padlen, aadlen)); |
len, padlen, aadlen)); |
|
|
/* compute MAC over seqnr and packet(length fields, payload, padding) */ |
/* compute MAC over seqnr and packet(length fields, payload, padding) */ |
if (mac && mac->enabled && !mac->etm) { |
if (mac && mac->enabled && !mac->etm) { |
if ((r = mac_compute(mac, active_state->p_send.seqnr, |
if ((r = mac_compute(mac, state->p_send.seqnr, |
buffer_ptr(&active_state->outgoing_packet), len, |
sshbuf_ptr(state->outgoing_packet), len, |
macbuf, sizeof(macbuf))) != 0) |
macbuf, sizeof(macbuf))) != 0) |
fatal("%s: mac_compute: %s", __func__, ssh_err(r)); |
goto out; |
DBG(debug("done calc MAC out #%d", active_state->p_send.seqnr)); |
DBG(debug("done calc MAC out #%d", state->p_send.seqnr)); |
} |
} |
/* encrypt packet and append to output buffer. */ |
/* encrypt packet and append to output buffer. */ |
cp = buffer_append_space(&active_state->output, len + authlen); |
if ((r = sshbuf_reserve(state->output, |
if (cipher_crypt(&active_state->send_context, active_state->p_send.seqnr, |
sshbuf_len(state->outgoing_packet) + authlen, &cp)) != 0) |
cp, buffer_ptr(&active_state->outgoing_packet), |
goto out; |
len - aadlen, aadlen, authlen) != 0) |
if ((r = cipher_crypt(&state->send_context, state->p_send.seqnr, cp, |
fatal("%s: cipher_crypt failed", __func__); |
sshbuf_ptr(state->outgoing_packet), |
|
len - aadlen, aadlen, authlen)) != 0) |
|
goto out; |
/* append unencrypted MAC */ |
/* append unencrypted MAC */ |
if (mac && mac->enabled) { |
if (mac && mac->enabled) { |
if (mac->etm) { |
if (mac->etm) { |
/* EtM: compute mac over aadlen + cipher text */ |
/* EtM: compute mac over aadlen + cipher text */ |
if ((r = mac_compute(mac, |
if ((r = mac_compute(mac, state->p_send.seqnr, |
active_state->p_send.seqnr, cp, len, |
cp, len, macbuf, sizeof(macbuf))) != 0) |
macbuf, sizeof(macbuf))) != 0) |
goto out; |
fatal("%s: mac_compute: %s", __func__, ssh_err(r)); |
|
DBG(debug("done calc MAC(EtM) out #%d", |
DBG(debug("done calc MAC(EtM) out #%d", |
active_state->p_send.seqnr)); |
state->p_send.seqnr)); |
} |
} |
buffer_append(&active_state->output, macbuf, mac->mac_len); |
if ((r = sshbuf_put(state->output, macbuf, mac->mac_len)) != 0) |
|
goto out; |
} |
} |
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "encrypted: "); |
fprintf(stderr, "encrypted: "); |
buffer_dump(&active_state->output); |
sshbuf_dump(state->output, stderr); |
#endif |
#endif |
/* increment sequence number for outgoing packets */ |
/* increment sequence number for outgoing packets */ |
if (++active_state->p_send.seqnr == 0) |
if (++state->p_send.seqnr == 0) |
logit("outgoing seqnr wraps around"); |
logit("outgoing seqnr wraps around"); |
if (++active_state->p_send.packets == 0) |
if (++state->p_send.packets == 0) |
if (!(datafellows & SSH_BUG_NOREKEY)) |
if (!(ssh->compat & SSH_BUG_NOREKEY)) |
fatal("XXX too many packets with same key"); |
return SSH_ERR_NEED_REKEY; |
active_state->p_send.blocks += len / block_size; |
state->p_send.blocks += len / block_size; |
active_state->p_send.bytes += len; |
state->p_send.bytes += len; |
buffer_clear(&active_state->outgoing_packet); |
sshbuf_reset(state->outgoing_packet); |
|
|
if (type == SSH2_MSG_NEWKEYS) |
if (type == SSH2_MSG_NEWKEYS) |
set_newkeys(MODE_OUT); |
r = ssh_set_newkeys(ssh, MODE_OUT); |
else if (type == SSH2_MSG_USERAUTH_SUCCESS && active_state->server_side) |
else if (type == SSH2_MSG_USERAUTH_SUCCESS && state->server_side) |
packet_enable_delayed_compress(); |
r = ssh_packet_enable_delayed_compress(ssh); |
|
else |
|
r = 0; |
|
out: |
|
return r; |
} |
} |
|
|
static void |
int |
packet_send2(void) |
ssh_packet_send2(struct ssh *ssh) |
{ |
{ |
|
struct session_state *state = ssh->state; |
struct packet *p; |
struct packet *p; |
u_char type, *cp; |
u_char type; |
|
int r; |
|
|
cp = buffer_ptr(&active_state->outgoing_packet); |
type = sshbuf_ptr(state->outgoing_packet)[5]; |
type = cp[5]; |
|
|
|
/* during rekeying we can only send key exchange messages */ |
/* during rekeying we can only send key exchange messages */ |
if (active_state->rekeying) { |
if (state->rekeying) { |
if ((type < SSH2_MSG_TRANSPORT_MIN) || |
if ((type < SSH2_MSG_TRANSPORT_MIN) || |
(type > SSH2_MSG_TRANSPORT_MAX) || |
(type > SSH2_MSG_TRANSPORT_MAX) || |
(type == SSH2_MSG_SERVICE_REQUEST) || |
(type == SSH2_MSG_SERVICE_REQUEST) || |
(type == SSH2_MSG_SERVICE_ACCEPT)) { |
(type == SSH2_MSG_SERVICE_ACCEPT)) { |
debug("enqueue packet: %u", type); |
debug("enqueue packet: %u", type); |
p = xcalloc(1, sizeof(*p)); |
p = calloc(1, sizeof(*p)); |
|
if (p == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
p->type = type; |
p->type = type; |
memcpy(&p->payload, &active_state->outgoing_packet, |
p->payload = state->outgoing_packet; |
sizeof(Buffer)); |
TAILQ_INSERT_TAIL(&state->outgoing, p, next); |
buffer_init(&active_state->outgoing_packet); |
state->outgoing_packet = sshbuf_new(); |
TAILQ_INSERT_TAIL(&active_state->outgoing, p, next); |
if (state->outgoing_packet == NULL) |
return; |
return SSH_ERR_ALLOC_FAIL; |
|
return 0; |
} |
} |
} |
} |
|
|
/* rekeying starts with sending KEXINIT */ |
/* rekeying starts with sending KEXINIT */ |
if (type == SSH2_MSG_KEXINIT) |
if (type == SSH2_MSG_KEXINIT) |
active_state->rekeying = 1; |
state->rekeying = 1; |
|
|
packet_send2_wrapped(); |
if ((r = ssh_packet_send2_wrapped(ssh)) != 0) |
|
return r; |
|
|
/* after a NEWKEYS message we can send the complete queue */ |
/* after a NEWKEYS message we can send the complete queue */ |
if (type == SSH2_MSG_NEWKEYS) { |
if (type == SSH2_MSG_NEWKEYS) { |
active_state->rekeying = 0; |
state->rekeying = 0; |
active_state->rekey_time = monotime(); |
state->rekey_time = monotime(); |
while ((p = TAILQ_FIRST(&active_state->outgoing))) { |
while ((p = TAILQ_FIRST(&state->outgoing))) { |
type = p->type; |
type = p->type; |
debug("dequeue packet: %u", type); |
debug("dequeue packet: %u", type); |
buffer_free(&active_state->outgoing_packet); |
sshbuf_free(state->outgoing_packet); |
memcpy(&active_state->outgoing_packet, &p->payload, |
state->outgoing_packet = p->payload; |
sizeof(Buffer)); |
TAILQ_REMOVE(&state->outgoing, p, next); |
TAILQ_REMOVE(&active_state->outgoing, p, next); |
|
free(p); |
free(p); |
packet_send2_wrapped(); |
if ((r = ssh_packet_send2_wrapped(ssh)) != 0) |
|
return r; |
} |
} |
} |
} |
|
return 0; |
} |
} |
|
|
void |
|
packet_send(void) |
|
{ |
|
if (compat20) |
|
packet_send2(); |
|
else |
|
packet_send1(); |
|
DBG(debug("packet_send done")); |
|
} |
|
|
|
/* |
/* |
* Waits until a packet has been received, and returns its type. Note that |
* Waits until a packet has been received, and returns its type. Note that |
* no other data is processed until this returns, so this function should not |
* no other data is processed until this returns, so this function should not |
|
|
*/ |
*/ |
|
|
int |
int |
packet_read_seqnr(u_int32_t *seqnr_p) |
ssh_packet_read_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) |
{ |
{ |
int type, len, ret, cont, ms_remain = 0; |
struct session_state *state = ssh->state; |
|
int len, r, ms_remain, cont; |
fd_set *setp; |
fd_set *setp; |
char buf[8192]; |
char buf[8192]; |
struct timeval timeout, start, *timeoutp = NULL; |
struct timeval timeout, start, *timeoutp = NULL; |
|
|
DBG(debug("packet_read()")); |
DBG(debug("packet_read()")); |
|
|
setp = (fd_set *)xcalloc(howmany(active_state->connection_in + 1, |
setp = (fd_set *)calloc(howmany(state->connection_in + 1, |
NFDBITS), sizeof(fd_mask)); |
NFDBITS), sizeof(fd_mask)); |
|
if (setp == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
|
|
/* Since we are blocking, ensure that all written packets have been sent. */ |
/* Since we are blocking, ensure that all written packets have been sent. */ |
packet_write_wait(); |
ssh_packet_write_wait(ssh); |
|
|
/* 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); |
r = ssh_packet_read_poll_seqnr(ssh, typep, seqnr_p); |
|
if (r != 0) |
|
break; |
if (!compat20 && ( |
if (!compat20 && ( |
type == SSH_SMSG_SUCCESS |
*typep == SSH_SMSG_SUCCESS |
|| type == SSH_SMSG_FAILURE |
|| *typep == SSH_SMSG_FAILURE |
|| type == SSH_CMSG_EOF |
|| *typep == SSH_CMSG_EOF |
|| type == SSH_CMSG_EXIT_CONFIRMATION)) |
|| *typep == SSH_CMSG_EXIT_CONFIRMATION)) |
packet_check_eom(); |
if ((r = sshpkt_get_end(ssh)) != 0) |
|
break; |
/* If we got a packet, return it. */ |
/* If we got a packet, return it. */ |
if (type != SSH_MSG_NONE) { |
if (*typep != SSH_MSG_NONE) |
free(setp); |
break; |
return type; |
|
} |
|
/* |
/* |
* Otherwise, wait for some data to arrive, add it to the |
* Otherwise, wait for some data to arrive, add it to the |
* buffer, and try again. |
* buffer, and try again. |
*/ |
*/ |
memset(setp, 0, howmany(active_state->connection_in + 1, |
memset(setp, 0, howmany(state->connection_in + 1, |
NFDBITS) * sizeof(fd_mask)); |
NFDBITS) * sizeof(fd_mask)); |
FD_SET(active_state->connection_in, setp); |
FD_SET(state->connection_in, setp); |
|
|
if (active_state->packet_timeout_ms > 0) { |
if (state->packet_timeout_ms > 0) { |
ms_remain = active_state->packet_timeout_ms; |
ms_remain = state->packet_timeout_ms; |
timeoutp = &timeout; |
timeoutp = &timeout; |
} |
} |
/* Wait for some data to arrive. */ |
/* Wait for some data to arrive. */ |
for (;;) { |
for (;;) { |
if (active_state->packet_timeout_ms != -1) { |
if (state->packet_timeout_ms != -1) { |
ms_to_timeval(&timeout, ms_remain); |
ms_to_timeval(&timeout, ms_remain); |
gettimeofday(&start, NULL); |
gettimeofday(&start, NULL); |
} |
} |
if ((ret = select(active_state->connection_in + 1, setp, |
if ((r = select(state->connection_in + 1, setp, |
NULL, NULL, timeoutp)) >= 0) |
NULL, NULL, timeoutp)) >= 0) |
break; |
break; |
if (errno != EAGAIN && errno != EINTR) |
if (errno != EAGAIN && errno != EINTR) |
break; |
break; |
if (active_state->packet_timeout_ms == -1) |
if (state->packet_timeout_ms == -1) |
continue; |
continue; |
ms_subtract_diff(&start, &ms_remain); |
ms_subtract_diff(&start, &ms_remain); |
if (ms_remain <= 0) { |
if (ms_remain <= 0) { |
ret = 0; |
r = 0; |
break; |
break; |
} |
} |
} |
} |
if (ret == 0) { |
if (r == 0) { |
logit("Connection to %.200s timed out while " |
logit("Connection to %.200s timed out while " |
"waiting to read", get_remote_ipaddr()); |
"waiting to read", ssh_remote_ipaddr(ssh)); |
cleanup_exit(255); |
cleanup_exit(255); |
} |
} |
/* Read data from the socket. */ |
/* Read data from the socket. */ |
do { |
do { |
cont = 0; |
cont = 0; |
len = roaming_read(active_state->connection_in, buf, |
len = roaming_read(state->connection_in, buf, |
sizeof(buf), &cont); |
sizeof(buf), &cont); |
} while (len == 0 && cont); |
} while (len == 0 && cont); |
if (len == 0) { |
if (len == 0) { |
logit("Connection closed by %.200s", get_remote_ipaddr()); |
logit("Connection closed by %.200s", |
|
ssh_remote_ipaddr(ssh)); |
cleanup_exit(255); |
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)); |
/* Append it to the buffer. */ |
/* Append it to the buffer. */ |
packet_process_incoming(buf, len); |
ssh_packet_process_incoming(ssh, buf, len); |
} |
} |
/* NOTREACHED */ |
free(setp); |
|
return r; |
} |
} |
|
|
int |
int |
packet_read(void) |
ssh_packet_read(struct ssh *ssh) |
{ |
{ |
return packet_read_seqnr(NULL); |
u_char type; |
|
int r; |
|
|
|
if ((r = ssh_packet_read_seqnr(ssh, &type, NULL)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
|
return type; |
} |
} |
|
|
/* |
/* |
|
|
*/ |
*/ |
|
|
void |
void |
packet_read_expect(int expected_type) |
ssh_packet_read_expect(struct ssh *ssh, int expected_type) |
{ |
{ |
int type; |
int type; |
|
|
type = packet_read(); |
type = ssh_packet_read(ssh); |
if (type != expected_type) |
if (type != expected_type) |
packet_disconnect("Protocol error: expected packet type %d, got %d", |
ssh_packet_disconnect(ssh, |
|
"Protocol error: expected packet type %d, got %d", |
expected_type, type); |
expected_type, type); |
} |
} |
|
|
|
|
* to higher levels. |
* to higher levels. |
*/ |
*/ |
|
|
static int |
int |
packet_read_poll1(void) |
ssh_packet_read_poll1(struct ssh *ssh, u_char *typep) |
{ |
{ |
|
struct session_state *state = ssh->state; |
u_int len, padded_len; |
u_int len, padded_len; |
u_char *cp, type; |
const u_char *cp; |
|
u_char *p; |
u_int checksum, stored_checksum; |
u_int checksum, stored_checksum; |
|
int r; |
|
|
|
*typep = SSH_MSG_NONE; |
|
|
/* Check if input size is less than minimum packet size. */ |
/* Check if input size is less than minimum packet size. */ |
if (buffer_len(&active_state->input) < 4 + 8) |
if (sshbuf_len(state->input) < 4 + 8) |
return SSH_MSG_NONE; |
return 0; |
/* Get length of incoming packet. */ |
/* Get length of incoming packet. */ |
cp = buffer_ptr(&active_state->input); |
len = PEEK_U32(sshbuf_ptr(state->input)); |
len = get_u32(cp); |
|
if (len < 1 + 2 + 2 || len > 256 * 1024) |
if (len < 1 + 2 + 2 || len > 256 * 1024) |
packet_disconnect("Bad packet length %u.", len); |
ssh_packet_disconnect(ssh, "Bad packet length %u.", |
|
len); |
padded_len = (len + 8) & ~7; |
padded_len = (len + 8) & ~7; |
|
|
/* Check if the packet has been entirely received. */ |
/* Check if the packet has been entirely received. */ |
if (buffer_len(&active_state->input) < 4 + padded_len) |
if (sshbuf_len(state->input) < 4 + padded_len) |
return SSH_MSG_NONE; |
return 0; |
|
|
/* The entire packet is in buffer. */ |
/* The entire packet is in buffer. */ |
|
|
/* Consume packet length. */ |
/* Consume packet length. */ |
buffer_consume(&active_state->input, 4); |
if ((r = sshbuf_consume(state->input, 4)) != 0) |
|
goto out; |
|
|
/* |
/* |
* Cryptographic attack detector for ssh |
* Cryptographic attack detector for ssh |
* (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 (!active_state->receive_context.plaintext) { |
if (!state->receive_context.plaintext) { |
switch (detect_attack(buffer_ptr(&active_state->input), |
switch (detect_attack(&state->deattack, |
padded_len)) { |
sshbuf_ptr(state->input), padded_len)) { |
|
case DEATTACK_OK: |
|
break; |
case DEATTACK_DETECTED: |
case DEATTACK_DETECTED: |
packet_disconnect("crc32 compensation attack: " |
ssh_packet_disconnect(ssh, |
"network attack detected"); |
"crc32 compensation attack: network attack detected" |
|
); |
case DEATTACK_DOS_DETECTED: |
case DEATTACK_DOS_DETECTED: |
packet_disconnect("deattack denial of " |
ssh_packet_disconnect(ssh, |
"service detected"); |
"deattack denial of service detected"); |
|
default: |
|
ssh_packet_disconnect(ssh, "deattack error"); |
} |
} |
} |
} |
|
|
/* Decrypt data to incoming_packet. */ |
/* Decrypt data to incoming_packet. */ |
buffer_clear(&active_state->incoming_packet); |
sshbuf_reset(state->incoming_packet); |
cp = buffer_append_space(&active_state->incoming_packet, padded_len); |
if ((r = sshbuf_reserve(state->incoming_packet, padded_len, &p)) != 0) |
if (cipher_crypt(&active_state->receive_context, 0, cp, |
goto out; |
buffer_ptr(&active_state->input), padded_len, 0, 0) != 0) |
if ((r = cipher_crypt(&state->receive_context, 0, p, |
fatal("%s: cipher_crypt failed", __func__); |
sshbuf_ptr(state->input), padded_len, 0, 0)) != 0) |
|
goto out; |
|
|
buffer_consume(&active_state->input, padded_len); |
if ((r = sshbuf_consume(state->input, padded_len)) != 0) |
|
goto out; |
|
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "read_poll plain: "); |
fprintf(stderr, "read_poll plain: "); |
buffer_dump(&active_state->incoming_packet); |
sshbuf_dump(state->incoming_packet, stderr); |
#endif |
#endif |
|
|
/* Compute packet checksum. */ |
/* Compute packet checksum. */ |
checksum = ssh_crc32(buffer_ptr(&active_state->incoming_packet), |
checksum = ssh_crc32(sshbuf_ptr(state->incoming_packet), |
buffer_len(&active_state->incoming_packet) - 4); |
sshbuf_len(state->incoming_packet) - 4); |
|
|
/* Skip padding. */ |
/* Skip padding. */ |
buffer_consume(&active_state->incoming_packet, 8 - len % 8); |
if ((r = sshbuf_consume(state->incoming_packet, 8 - len % 8)) != 0) |
|
goto out; |
|
|
/* Test check bytes. */ |
/* Test check bytes. */ |
if (len != buffer_len(&active_state->incoming_packet)) |
if (len != sshbuf_len(state->incoming_packet)) |
packet_disconnect("packet_read_poll1: len %d != buffer_len %d.", |
ssh_packet_disconnect(ssh, |
len, buffer_len(&active_state->incoming_packet)); |
"packet_read_poll1: len %d != sshbuf_len %zd.", |
|
len, sshbuf_len(state->incoming_packet)); |
|
|
cp = (u_char *)buffer_ptr(&active_state->incoming_packet) + len - 4; |
cp = sshbuf_ptr(state->incoming_packet) + len - 4; |
stored_checksum = get_u32(cp); |
stored_checksum = PEEK_U32(cp); |
if (checksum != stored_checksum) |
if (checksum != stored_checksum) |
packet_disconnect("Corrupted check bytes on input."); |
ssh_packet_disconnect(ssh, |
buffer_consume_end(&active_state->incoming_packet, 4); |
"Corrupted check bytes on input."); |
|
if ((r = sshbuf_consume_end(state->incoming_packet, 4)) < 0) |
|
goto out; |
|
|
if (active_state->packet_compression) { |
if (state->packet_compression) { |
buffer_clear(&active_state->compression_buffer); |
sshbuf_reset(state->compression_buffer); |
buffer_uncompress(&active_state->incoming_packet, |
if ((r = uncompress_buffer(ssh, state->incoming_packet, |
&active_state->compression_buffer); |
state->compression_buffer)) != 0) |
buffer_clear(&active_state->incoming_packet); |
goto out; |
buffer_append(&active_state->incoming_packet, |
sshbuf_reset(state->incoming_packet); |
buffer_ptr(&active_state->compression_buffer), |
if ((r = sshbuf_putb(state->incoming_packet, |
buffer_len(&active_state->compression_buffer)); |
state->compression_buffer)) != 0) |
|
goto out; |
} |
} |
active_state->p_read.packets++; |
state->p_read.packets++; |
active_state->p_read.bytes += padded_len + 4; |
state->p_read.bytes += padded_len + 4; |
type = buffer_get_char(&active_state->incoming_packet); |
if ((r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) |
if (type < SSH_MSG_MIN || type > SSH_MSG_MAX) |
goto out; |
packet_disconnect("Invalid ssh1 packet type: %d", type); |
if (*typep < SSH_MSG_MIN || *typep > SSH_MSG_MAX) |
return type; |
ssh_packet_disconnect(ssh, |
|
"Invalid ssh1 packet type: %d", *typep); |
|
r = 0; |
|
out: |
|
return r; |
} |
} |
|
|
static int |
int |
packet_read_poll2(u_int32_t *seqnr_p) |
ssh_packet_read_poll2(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) |
{ |
{ |
|
struct session_state *state = ssh->state; |
u_int padlen, need; |
u_int padlen, need; |
u_char type, *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; |
u_char *cp, macbuf[SSH_DIGEST_MAX_LENGTH]; |
u_int maclen, authlen = 0, aadlen = 0, block_size; |
u_int maclen, aadlen = 0, authlen = 0, block_size; |
|
struct sshenc *enc = NULL; |
|
struct sshmac *mac = NULL; |
|
struct sshcomp *comp = NULL; |
int r; |
int r; |
Enc *enc = NULL; |
|
Mac *mac = NULL; |
|
Comp *comp = NULL; |
|
|
|
if (active_state->packet_discard) |
*typep = SSH_MSG_NONE; |
return SSH_MSG_NONE; |
|
|
|
if (active_state->newkeys[MODE_IN] != NULL) { |
if (state->packet_discard) |
enc = &active_state->newkeys[MODE_IN]->enc; |
return 0; |
mac = &active_state->newkeys[MODE_IN]->mac; |
|
comp = &active_state->newkeys[MODE_IN]->comp; |
if (state->newkeys[MODE_IN] != NULL) { |
|
enc = &state->newkeys[MODE_IN]->enc; |
|
mac = &state->newkeys[MODE_IN]->mac; |
|
comp = &state->newkeys[MODE_IN]->comp; |
/* disable mac for authenticated encryption */ |
/* disable mac for authenticated encryption */ |
if ((authlen = cipher_authlen(enc->cipher)) != 0) |
if ((authlen = cipher_authlen(enc->cipher)) != 0) |
mac = NULL; |
mac = NULL; |
|
|
block_size = enc ? enc->block_size : 8; |
block_size = enc ? enc->block_size : 8; |
aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; |
aadlen = (mac && mac->enabled && mac->etm) || authlen ? 4 : 0; |
|
|
if (aadlen && active_state->packlen == 0) { |
if (aadlen && state->packlen == 0) { |
if (cipher_get_length(&active_state->receive_context, |
if (cipher_get_length(&state->receive_context, |
&active_state->packlen, |
&state->packlen, state->p_read.seqnr, |
active_state->p_read.seqnr, |
sshbuf_ptr(state->input), sshbuf_len(state->input)) != 0) |
buffer_ptr(&active_state->input), |
return 0; |
buffer_len(&active_state->input)) != 0) |
if (state->packlen < 1 + 4 || |
return SSH_MSG_NONE; |
state->packlen > PACKET_MAX_SIZE) { |
if (active_state->packlen < 1 + 4 || |
|
active_state->packlen > PACKET_MAX_SIZE) { |
|
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
buffer_dump(&active_state->input); |
sshbuf_dump(state->input, stderr); |
#endif |
#endif |
logit("Bad packet length %u.", active_state->packlen); |
logit("Bad packet length %u.", state->packlen); |
packet_disconnect("Packet corrupt"); |
if ((r = sshpkt_disconnect(ssh, "Packet corrupt")) != 0) |
|
return r; |
} |
} |
buffer_clear(&active_state->incoming_packet); |
sshbuf_reset(state->incoming_packet); |
} else if (active_state->packlen == 0) { |
} else if (state->packlen == 0) { |
/* |
/* |
* check if input size is less than the cipher block size, |
* check if input size is less than the cipher block size, |
* decrypt first block and extract length of incoming packet |
* decrypt first block and extract length of incoming packet |
*/ |
*/ |
if (buffer_len(&active_state->input) < block_size) |
if (sshbuf_len(state->input) < block_size) |
return SSH_MSG_NONE; |
return 0; |
buffer_clear(&active_state->incoming_packet); |
sshbuf_reset(state->incoming_packet); |
cp = buffer_append_space(&active_state->incoming_packet, |
if ((r = sshbuf_reserve(state->incoming_packet, block_size, |
block_size); |
&cp)) != 0) |
if (cipher_crypt(&active_state->receive_context, |
goto out; |
active_state->p_read.seqnr, cp, |
if ((r = cipher_crypt(&state->receive_context, |
buffer_ptr(&active_state->input), block_size, 0, 0) != 0) |
state->p_send.seqnr, cp, sshbuf_ptr(state->input), |
fatal("Decryption integrity check failed"); |
block_size, 0, 0)) != 0) |
cp = buffer_ptr(&active_state->incoming_packet); |
goto out; |
active_state->packlen = get_u32(cp); |
state->packlen = PEEK_U32(sshbuf_ptr(state->incoming_packet)); |
if (active_state->packlen < 1 + 4 || |
if (state->packlen < 1 + 4 || |
active_state->packlen > PACKET_MAX_SIZE) { |
state->packlen > PACKET_MAX_SIZE) { |
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
buffer_dump(&active_state->incoming_packet); |
fprintf(stderr, "input: \n"); |
|
sshbuf_dump(state->input, stderr); |
|
fprintf(stderr, "incoming_packet: \n"); |
|
sshbuf_dump(state->incoming_packet, stderr); |
#endif |
#endif |
logit("Bad packet length %u.", active_state->packlen); |
logit("Bad packet length %u.", state->packlen); |
packet_start_discard(enc, mac, active_state->packlen, |
return ssh_packet_start_discard(ssh, enc, mac, |
PACKET_MAX_SIZE); |
state->packlen, PACKET_MAX_SIZE); |
return SSH_MSG_NONE; |
|
} |
} |
buffer_consume(&active_state->input, block_size); |
if ((r = sshbuf_consume(state->input, block_size)) != 0) |
|
goto out; |
} |
} |
DBG(debug("input: packet len %u", active_state->packlen+4)); |
DBG(debug("input: packet len %u", state->packlen+4)); |
|
|
if (aadlen) { |
if (aadlen) { |
/* only the payload is encrypted */ |
/* only the payload is encrypted */ |
need = active_state->packlen; |
need = state->packlen; |
} else { |
} else { |
/* |
/* |
* the payload size and the payload are encrypted, but we |
* the payload size and the payload are encrypted, but we |
* have a partial packet of block_size bytes |
* have a partial packet of block_size bytes |
*/ |
*/ |
need = 4 + active_state->packlen - block_size; |
need = 4 + state->packlen - block_size; |
} |
} |
DBG(debug("partial packet: block %d, need %d, maclen %d, authlen %d," |
DBG(debug("partial packet: block %d, need %d, maclen %d, authlen %d," |
" aadlen %d", block_size, need, maclen, authlen, aadlen)); |
" aadlen %d", block_size, need, maclen, authlen, aadlen)); |
if (need % block_size != 0) { |
if (need % block_size != 0) { |
logit("padding error: need %d block %d mod %d", |
logit("padding error: need %d block %d mod %d", |
need, block_size, need % block_size); |
need, block_size, need % block_size); |
packet_start_discard(enc, mac, active_state->packlen, |
return ssh_packet_start_discard(ssh, enc, mac, |
PACKET_MAX_SIZE - block_size); |
state->packlen, PACKET_MAX_SIZE - block_size); |
return SSH_MSG_NONE; |
|
} |
} |
/* |
/* |
* check if the entire packet has been received and |
* check if the entire packet has been received and |
|
|
* 'authlen' bytes of authentication tag or |
* 'authlen' bytes of authentication tag or |
* 'maclen' bytes of message authentication code. |
* 'maclen' bytes of message authentication code. |
*/ |
*/ |
if (buffer_len(&active_state->input) < aadlen + need + authlen + maclen) |
if (sshbuf_len(state->input) < aadlen + need + authlen + maclen) |
return SSH_MSG_NONE; |
return 0; |
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "read_poll enc/full: "); |
fprintf(stderr, "read_poll enc/full: "); |
buffer_dump(&active_state->input); |
sshbuf_dump(state->input, stderr); |
#endif |
#endif |
/* EtM: compute mac over encrypted input */ |
/* EtM: compute mac over encrypted input */ |
if (mac && mac->enabled && mac->etm) |
if (mac && mac->enabled && mac->etm) { |
if ((r = mac_compute(mac, active_state->p_read.seqnr, |
if ((r = mac_compute(mac, state->p_read.seqnr, |
buffer_ptr(&active_state->input), aadlen + need, |
sshbuf_ptr(state->input), aadlen + need, |
macbuf, sizeof(macbuf))) != 0) |
macbuf, sizeof(macbuf))) != 0) |
fatal("%s: mac_compute: %s", __func__, ssh_err(r)); |
goto out; |
cp = buffer_append_space(&active_state->incoming_packet, aadlen + need); |
} |
if (cipher_crypt(&active_state->receive_context, |
if ((r = sshbuf_reserve(state->incoming_packet, aadlen + need, |
active_state->p_read.seqnr, cp, |
&cp)) != 0) |
buffer_ptr(&active_state->input), need, aadlen, authlen) != 0) |
goto out; |
fatal("Decryption integrity check failed"); |
if ((r = cipher_crypt(&state->receive_context, state->p_read.seqnr, cp, |
buffer_consume(&active_state->input, aadlen + need + authlen); |
sshbuf_ptr(state->input), need, aadlen, authlen)) != 0) |
|
goto out; |
|
if ((r = sshbuf_consume(state->input, aadlen + need + authlen)) != 0) |
|
goto out; |
/* |
/* |
* 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) { |
if (!mac->etm) |
if (!mac->etm) |
if ((r = mac_compute(mac, active_state->p_read.seqnr, |
if ((r = mac_compute(mac, state->p_read.seqnr, |
buffer_ptr(&active_state->incoming_packet), |
sshbuf_ptr(state->incoming_packet), |
buffer_len(&active_state->incoming_packet), |
sshbuf_len(state->incoming_packet), |
macbuf, sizeof(macbuf))) != 0) |
macbuf, sizeof(macbuf))) != 0) |
fatal("%s: mac_compute: %s", __func__, ssh_err(r)); |
goto out; |
if (timingsafe_bcmp(macbuf, buffer_ptr(&active_state->input), |
if (timingsafe_bcmp(macbuf, sshbuf_ptr(state->input), |
mac->mac_len) != 0) { |
mac->mac_len) != 0) { |
logit("Corrupted MAC on input."); |
logit("Corrupted MAC on input."); |
if (need > PACKET_MAX_SIZE) |
if (need > PACKET_MAX_SIZE) |
fatal("internal error need %d", need); |
return SSH_ERR_INTERNAL_ERROR; |
packet_start_discard(enc, mac, active_state->packlen, |
return ssh_packet_start_discard(ssh, enc, mac, |
PACKET_MAX_SIZE - need); |
state->packlen, PACKET_MAX_SIZE - need); |
return SSH_MSG_NONE; |
|
} |
} |
|
|
DBG(debug("MAC #%d ok", active_state->p_read.seqnr)); |
DBG(debug("MAC #%d ok", state->p_read.seqnr)); |
buffer_consume(&active_state->input, mac->mac_len); |
if ((r = sshbuf_consume(state->input, mac->mac_len)) != 0) |
|
goto out; |
} |
} |
/* XXX now it's safe to use fatal/packet_disconnect */ |
/* XXX now it's safe to use fatal/packet_disconnect */ |
if (seqnr_p != NULL) |
if (seqnr_p != NULL) |
*seqnr_p = active_state->p_read.seqnr; |
*seqnr_p = state->p_read.seqnr; |
if (++active_state->p_read.seqnr == 0) |
if (++state->p_read.seqnr == 0) |
logit("incoming seqnr wraps around"); |
logit("incoming seqnr wraps around"); |
if (++active_state->p_read.packets == 0) |
if (++state->p_read.packets == 0) |
if (!(datafellows & SSH_BUG_NOREKEY)) |
if (!(ssh->compat & SSH_BUG_NOREKEY)) |
fatal("XXX too many packets with same key"); |
return SSH_ERR_NEED_REKEY; |
active_state->p_read.blocks += (active_state->packlen + 4) / block_size; |
state->p_read.blocks += (state->packlen + 4) / block_size; |
active_state->p_read.bytes += active_state->packlen + 4; |
state->p_read.bytes += state->packlen + 4; |
|
|
/* get padlen */ |
/* get padlen */ |
cp = buffer_ptr(&active_state->incoming_packet); |
padlen = sshbuf_ptr(state->incoming_packet)[4]; |
padlen = cp[4]; |
|
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); |
ssh_packet_disconnect(ssh, |
|
"Corrupted padlen %d on input.", padlen); |
|
|
/* skip packet size + padlen, discard padding */ |
/* skip packet size + padlen, discard padding */ |
buffer_consume(&active_state->incoming_packet, 4 + 1); |
if ((r = sshbuf_consume(state->incoming_packet, 4 + 1)) != 0 || |
buffer_consume_end(&active_state->incoming_packet, padlen); |
((r = sshbuf_consume_end(state->incoming_packet, padlen)) != 0)) |
|
goto out; |
|
|
DBG(debug("input: len before de-compress %d", |
DBG(debug("input: len before de-compress %zd", |
buffer_len(&active_state->incoming_packet))); |
sshbuf_len(state->incoming_packet))); |
if (comp && comp->enabled) { |
if (comp && comp->enabled) { |
buffer_clear(&active_state->compression_buffer); |
sshbuf_reset(state->compression_buffer); |
buffer_uncompress(&active_state->incoming_packet, |
if ((r = uncompress_buffer(ssh, state->incoming_packet, |
&active_state->compression_buffer); |
state->compression_buffer)) != 0) |
buffer_clear(&active_state->incoming_packet); |
goto out; |
buffer_append(&active_state->incoming_packet, |
sshbuf_reset(state->incoming_packet); |
buffer_ptr(&active_state->compression_buffer), |
if ((r = sshbuf_putb(state->incoming_packet, |
buffer_len(&active_state->compression_buffer)); |
state->compression_buffer)) != 0) |
DBG(debug("input: len after de-compress %d", |
goto out; |
buffer_len(&active_state->incoming_packet))); |
DBG(debug("input: len after de-compress %zd", |
|
sshbuf_len(state->incoming_packet))); |
} |
} |
/* |
/* |
* get packet type, implies consume. |
* get packet type, implies consume. |
* return length of payload (without type field) |
* return length of payload (without type field) |
*/ |
*/ |
type = buffer_get_char(&active_state->incoming_packet); |
if ((r = sshbuf_get_u8(state->incoming_packet, typep)) != 0) |
if (type < SSH2_MSG_MIN || type >= SSH2_MSG_LOCAL_MIN) |
goto out; |
packet_disconnect("Invalid ssh2 packet type: %d", type); |
if (*typep < SSH2_MSG_MIN || *typep >= SSH2_MSG_LOCAL_MIN) |
if (type == SSH2_MSG_NEWKEYS) |
ssh_packet_disconnect(ssh, |
set_newkeys(MODE_IN); |
"Invalid ssh2 packet type: %d", *typep); |
else if (type == SSH2_MSG_USERAUTH_SUCCESS && |
if (*typep == SSH2_MSG_NEWKEYS) |
!active_state->server_side) |
r = ssh_set_newkeys(ssh, MODE_IN); |
packet_enable_delayed_compress(); |
else if (*typep == SSH2_MSG_USERAUTH_SUCCESS && !state->server_side) |
|
r = ssh_packet_enable_delayed_compress(ssh); |
|
else |
|
r = 0; |
#ifdef PACKET_DEBUG |
#ifdef PACKET_DEBUG |
fprintf(stderr, "read/plain[%d]:\r\n", type); |
fprintf(stderr, "read/plain[%d]:\r\n", *typep); |
buffer_dump(&active_state->incoming_packet); |
sshbuf_dump(state->incoming_packet, stderr); |
#endif |
#endif |
/* reset for next packet */ |
/* reset for next packet */ |
active_state->packlen = 0; |
state->packlen = 0; |
return type; |
out: |
|
return r; |
} |
} |
|
|
int |
int |
packet_read_poll_seqnr(u_int32_t *seqnr_p) |
ssh_packet_read_poll_seqnr(struct ssh *ssh, u_char *typep, u_int32_t *seqnr_p) |
{ |
{ |
|
struct session_state *state = ssh->state; |
u_int reason, seqnr; |
u_int reason, seqnr; |
u_char type; |
int r; |
char *msg; |
u_char *msg; |
|
|
for (;;) { |
for (;;) { |
|
msg = NULL; |
if (compat20) { |
if (compat20) { |
type = packet_read_poll2(seqnr_p); |
r = ssh_packet_read_poll2(ssh, typep, seqnr_p); |
if (type) { |
if (r != 0) |
active_state->keep_alive_timeouts = 0; |
return r; |
DBG(debug("received packet type %d", type)); |
if (*typep) { |
|
state->keep_alive_timeouts = 0; |
|
DBG(debug("received packet type %d", *typep)); |
} |
} |
switch (type) { |
switch (*typep) { |
case SSH2_MSG_IGNORE: |
case SSH2_MSG_IGNORE: |
debug3("Received SSH2_MSG_IGNORE"); |
debug3("Received SSH2_MSG_IGNORE"); |
break; |
break; |
case SSH2_MSG_DEBUG: |
case SSH2_MSG_DEBUG: |
packet_get_char(); |
if ((r = sshpkt_get_u8(ssh, NULL)) != 0 || |
msg = packet_get_string(NULL); |
(r = sshpkt_get_string(ssh, &msg, NULL)) != 0 || |
|
(r = sshpkt_get_string(ssh, NULL, NULL)) != 0) { |
|
if (msg) |
|
free(msg); |
|
return r; |
|
} |
debug("Remote: %.900s", msg); |
debug("Remote: %.900s", msg); |
free(msg); |
free(msg); |
msg = packet_get_string(NULL); |
|
free(msg); |
|
break; |
break; |
case SSH2_MSG_DISCONNECT: |
case SSH2_MSG_DISCONNECT: |
reason = packet_get_int(); |
if ((r = sshpkt_get_u32(ssh, &reason)) != 0 || |
msg = packet_get_string(NULL); |
(r = sshpkt_get_string(ssh, &msg, NULL)) != 0) |
|
return r; |
/* Ignore normal client exit notifications */ |
/* Ignore normal client exit notifications */ |
do_log2(active_state->server_side && |
do_log2(ssh->state->server_side && |
reason == SSH2_DISCONNECT_BY_APPLICATION ? |
reason == SSH2_DISCONNECT_BY_APPLICATION ? |
SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, |
SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_ERROR, |
"Received disconnect from %s: %u: %.400s", |
"Received disconnect from %s: %u: %.400s", |
get_remote_ipaddr(), reason, msg); |
ssh_remote_ipaddr(ssh), reason, msg); |
free(msg); |
free(msg); |
cleanup_exit(255); |
return SSH_ERR_DISCONNECTED; |
break; |
|
case SSH2_MSG_UNIMPLEMENTED: |
case SSH2_MSG_UNIMPLEMENTED: |
seqnr = packet_get_int(); |
if ((r = sshpkt_get_u32(ssh, &seqnr)) != 0) |
|
return r; |
debug("Received SSH2_MSG_UNIMPLEMENTED for %u", |
debug("Received SSH2_MSG_UNIMPLEMENTED for %u", |
seqnr); |
seqnr); |
break; |
break; |
default: |
default: |
return type; |
return 0; |
} |
} |
} else { |
} else { |
type = packet_read_poll1(); |
r = ssh_packet_read_poll1(ssh, typep); |
switch (type) { |
switch (*typep) { |
case SSH_MSG_NONE: |
case SSH_MSG_NONE: |
return SSH_MSG_NONE; |
return SSH_MSG_NONE; |
case SSH_MSG_IGNORE: |
case SSH_MSG_IGNORE: |
break; |
break; |
case SSH_MSG_DEBUG: |
case SSH_MSG_DEBUG: |
msg = packet_get_string(NULL); |
if ((r = sshpkt_get_string(ssh, &msg, NULL)) != 0) |
|
return r; |
debug("Remote: %.900s", msg); |
debug("Remote: %.900s", msg); |
free(msg); |
free(msg); |
break; |
break; |
case SSH_MSG_DISCONNECT: |
case SSH_MSG_DISCONNECT: |
msg = packet_get_string(NULL); |
if ((r = sshpkt_get_string(ssh, &msg, NULL)) != 0) |
|
return r; |
error("Received disconnect from %s: %.400s", |
error("Received disconnect from %s: %.400s", |
get_remote_ipaddr(), msg); |
ssh_remote_ipaddr(ssh), msg); |
cleanup_exit(255); |
free(msg); |
break; |
return SSH_ERR_DISCONNECTED; |
default: |
default: |
DBG(debug("received packet type %d", type)); |
DBG(debug("received packet type %d", *typep)); |
return type; |
return 0; |
} |
} |
} |
} |
} |
} |
|
|
*/ |
*/ |
|
|
void |
void |
packet_process_incoming(const char *buf, u_int len) |
ssh_packet_process_incoming(struct ssh *ssh, const char *buf, u_int len) |
{ |
{ |
if (active_state->packet_discard) { |
struct session_state *state = ssh->state; |
active_state->keep_alive_timeouts = 0; /* ?? */ |
int r; |
if (len >= active_state->packet_discard) |
|
packet_stop_discard(); |
if (state->packet_discard) { |
active_state->packet_discard -= len; |
state->keep_alive_timeouts = 0; /* ?? */ |
|
if (len >= state->packet_discard) { |
|
if ((r = ssh_packet_stop_discard(ssh)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
|
cleanup_exit(255); |
|
} |
|
state->packet_discard -= len; |
return; |
return; |
} |
} |
buffer_append(&active_state->input, buf, len); |
if ((r = sshbuf_put(ssh->state->input, buf, len)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} |
} |
|
|
/* Returns a character from the packet. */ |
|
|
|
u_int |
|
packet_get_char(void) |
|
{ |
|
char ch; |
|
|
|
buffer_get(&active_state->incoming_packet, &ch, 1); |
|
return (u_char) ch; |
|
} |
|
|
|
/* Returns an integer from the packet data. */ |
|
|
|
u_int |
|
packet_get_int(void) |
|
{ |
|
return buffer_get_int(&active_state->incoming_packet); |
|
} |
|
|
|
/* Returns an 64 bit integer from the packet data. */ |
|
|
|
u_int64_t |
|
packet_get_int64(void) |
|
{ |
|
return buffer_get_int64(&active_state->incoming_packet); |
|
} |
|
|
|
/* |
|
* Returns an arbitrary precision integer from the packet data. The integer |
|
* must have been initialized before this call. |
|
*/ |
|
|
|
#ifdef WITH_OPENSSL |
|
void |
|
packet_get_bignum(BIGNUM * value) |
|
{ |
|
buffer_get_bignum(&active_state->incoming_packet, value); |
|
} |
|
|
|
void |
|
packet_get_bignum2(BIGNUM * value) |
|
{ |
|
buffer_get_bignum2(&active_state->incoming_packet, value); |
|
} |
|
|
|
void |
|
packet_get_ecpoint(const EC_GROUP *curve, EC_POINT *point) |
|
{ |
|
buffer_get_ecpoint(&active_state->incoming_packet, curve, point); |
|
} |
|
#endif |
|
|
|
void * |
|
packet_get_raw(u_int *length_ptr) |
|
{ |
|
u_int bytes = buffer_len(&active_state->incoming_packet); |
|
|
|
if (length_ptr != NULL) |
|
*length_ptr = bytes; |
|
return buffer_ptr(&active_state->incoming_packet); |
|
} |
|
|
|
int |
int |
packet_remaining(void) |
ssh_packet_remaining(struct ssh *ssh) |
{ |
{ |
return buffer_len(&active_state->incoming_packet); |
return sshbuf_len(ssh->state->incoming_packet); |
} |
} |
|
|
/* |
/* |
* Returns a string from the packet data. The string is allocated using |
|
* xmalloc; it is the responsibility of the calling program to free it when |
|
* no longer needed. The length_ptr argument may be NULL, or point to an |
|
* integer into which the length of the string is stored. |
|
*/ |
|
|
|
void * |
|
packet_get_string(u_int *length_ptr) |
|
{ |
|
return buffer_get_string(&active_state->incoming_packet, length_ptr); |
|
} |
|
|
|
const void * |
|
packet_get_string_ptr(u_int *length_ptr) |
|
{ |
|
return buffer_get_string_ptr(&active_state->incoming_packet, length_ptr); |
|
} |
|
|
|
/* Ensures the returned string has no embedded \0 characters in it. */ |
|
char * |
|
packet_get_cstring(u_int *length_ptr) |
|
{ |
|
return buffer_get_cstring(&active_state->incoming_packet, length_ptr); |
|
} |
|
|
|
/* |
|
* Sends a diagnostic message from the server to the client. This message |
* Sends a diagnostic message from the server to the client. This message |
* can be sent at any time (but not while constructing another message). The |
* can be sent at any time (but not while constructing another message). The |
* message is printed immediately, but only if the client is being executed |
* message is printed immediately, but only if the client is being executed |
|
|
*/ |
*/ |
|
|
void |
void |
packet_send_debug(const char *fmt,...) |
ssh_packet_send_debug(struct ssh *ssh, const char *fmt,...) |
{ |
{ |
char buf[1024]; |
char buf[1024]; |
va_list args; |
va_list args; |
|
int r; |
|
|
if (compat20 && (datafellows & SSH_BUG_DEBUG)) |
if (compat20 && (ssh->compat & SSH_BUG_DEBUG)) |
return; |
return; |
|
|
va_start(args, fmt); |
va_start(args, fmt); |
|
|
va_end(args); |
va_end(args); |
|
|
if (compat20) { |
if (compat20) { |
packet_start(SSH2_MSG_DEBUG); |
if ((r = sshpkt_start(ssh, SSH2_MSG_DEBUG)) != 0 || |
packet_put_char(0); /* bool: always display */ |
(r = sshpkt_put_u8(ssh, 0)) != 0 || /* always display */ |
packet_put_cstring(buf); |
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
packet_put_cstring(""); |
(r = sshpkt_put_cstring(ssh, "")) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} else { |
} else { |
packet_start(SSH_MSG_DEBUG); |
if ((r = sshpkt_start(ssh, SSH_MSG_DEBUG)) != 0 || |
packet_put_cstring(buf); |
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} |
} |
packet_send(); |
ssh_packet_write_wait(ssh); |
packet_write_wait(); |
|
} |
} |
|
|
/* |
/* |
|
|
*/ |
*/ |
|
|
void |
void |
packet_disconnect(const char *fmt,...) |
ssh_packet_disconnect(struct ssh *ssh, const char *fmt,...) |
{ |
{ |
char buf[1024]; |
char buf[1024]; |
va_list args; |
va_list args; |
static int disconnecting = 0; |
static int disconnecting = 0; |
|
int r; |
|
|
if (disconnecting) /* Guard against recursive invocations. */ |
if (disconnecting) /* Guard against recursive invocations. */ |
fatal("packet_disconnect called recursively."); |
fatal("packet_disconnect called recursively."); |
|
|
|
|
/* 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) { |
packet_start(SSH2_MSG_DISCONNECT); |
if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || |
packet_put_int(SSH2_DISCONNECT_PROTOCOL_ERROR); |
(r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || |
packet_put_cstring(buf); |
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
packet_put_cstring(""); |
(r = sshpkt_put_cstring(ssh, "")) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} else { |
} else { |
packet_start(SSH_MSG_DISCONNECT); |
if ((r = sshpkt_start(ssh, SSH_MSG_DISCONNECT)) != 0 || |
packet_put_cstring(buf); |
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} |
} |
packet_send(); |
ssh_packet_write_wait(ssh); |
packet_write_wait(); |
|
|
|
/* Stop listening for connections. */ |
|
channel_close_all(); |
|
|
|
/* Close the connection. */ |
/* Close the connection. */ |
packet_close(); |
ssh_packet_close(ssh); |
cleanup_exit(255); |
cleanup_exit(255); |
} |
} |
|
|
/* 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) |
ssh_packet_write_poll(struct ssh *ssh) |
{ |
{ |
int len = buffer_len(&active_state->output); |
struct session_state *state = ssh->state; |
int cont; |
int len = sshbuf_len(state->output); |
|
int cont, r; |
|
|
if (len > 0) { |
if (len > 0) { |
cont = 0; |
cont = 0; |
len = roaming_write(active_state->connection_out, |
len = roaming_write(state->connection_out, |
buffer_ptr(&active_state->output), len, &cont); |
sshbuf_ptr(state->output), len, &cont); |
if (len == -1) { |
if (len == -1) { |
if (errno == EINTR || errno == EAGAIN) |
if (errno == EINTR || errno == EAGAIN) |
return; |
return; |
|
|
} |
} |
if (len == 0 && !cont) |
if (len == 0 && !cont) |
fatal("Write connection closed"); |
fatal("Write connection closed"); |
buffer_consume(&active_state->output, len); |
if ((r = sshbuf_consume(state->output, len)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
} |
} |
} |
} |
|
|
|
|
*/ |
*/ |
|
|
void |
void |
packet_write_wait(void) |
ssh_packet_write_wait(struct ssh *ssh) |
{ |
{ |
fd_set *setp; |
fd_set *setp; |
int ret, ms_remain = 0; |
int ret, ms_remain = 0; |
struct timeval start, timeout, *timeoutp = NULL; |
struct timeval start, timeout, *timeoutp = NULL; |
|
struct session_state *state = ssh->state; |
|
|
setp = (fd_set *)xcalloc(howmany(active_state->connection_out + 1, |
setp = (fd_set *)calloc(howmany(state->connection_out + 1, |
NFDBITS), sizeof(fd_mask)); |
NFDBITS), sizeof(fd_mask)); |
packet_write_poll(); |
if (setp == NULL) |
while (packet_have_data_to_write()) { |
fatal("%s: calloc failed", __func__); |
memset(setp, 0, howmany(active_state->connection_out + 1, |
ssh_packet_write_poll(ssh); |
|
while (ssh_packet_have_data_to_write(ssh)) { |
|
memset(setp, 0, howmany(state->connection_out + 1, |
NFDBITS) * sizeof(fd_mask)); |
NFDBITS) * sizeof(fd_mask)); |
FD_SET(active_state->connection_out, setp); |
FD_SET(state->connection_out, setp); |
|
|
if (active_state->packet_timeout_ms > 0) { |
if (state->packet_timeout_ms > 0) { |
ms_remain = active_state->packet_timeout_ms; |
ms_remain = state->packet_timeout_ms; |
timeoutp = &timeout; |
timeoutp = &timeout; |
} |
} |
for (;;) { |
for (;;) { |
if (active_state->packet_timeout_ms != -1) { |
if (state->packet_timeout_ms != -1) { |
ms_to_timeval(&timeout, ms_remain); |
ms_to_timeval(&timeout, ms_remain); |
gettimeofday(&start, NULL); |
gettimeofday(&start, NULL); |
} |
} |
if ((ret = select(active_state->connection_out + 1, |
if ((ret = select(state->connection_out + 1, |
NULL, setp, NULL, timeoutp)) >= 0) |
NULL, setp, NULL, timeoutp)) >= 0) |
break; |
break; |
if (errno != EAGAIN && errno != EINTR) |
if (errno != EAGAIN && errno != EINTR) |
break; |
break; |
if (active_state->packet_timeout_ms == -1) |
if (state->packet_timeout_ms == -1) |
continue; |
continue; |
ms_subtract_diff(&start, &ms_remain); |
ms_subtract_diff(&start, &ms_remain); |
if (ms_remain <= 0) { |
if (ms_remain <= 0) { |
|
|
} |
} |
if (ret == 0) { |
if (ret == 0) { |
logit("Connection to %.200s timed out while " |
logit("Connection to %.200s timed out while " |
"waiting to write", get_remote_ipaddr()); |
"waiting to write", ssh_remote_ipaddr(ssh)); |
cleanup_exit(255); |
cleanup_exit(255); |
} |
} |
packet_write_poll(); |
ssh_packet_write_poll(ssh); |
} |
} |
free(setp); |
free(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) |
ssh_packet_have_data_to_write(struct ssh *ssh) |
{ |
{ |
return buffer_len(&active_state->output) != 0; |
return sshbuf_len(ssh->state->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) |
ssh_packet_not_very_much_data_to_write(struct ssh *ssh) |
{ |
{ |
if (active_state->interactive_mode) |
if (ssh->state->interactive_mode) |
return buffer_len(&active_state->output) < 16384; |
return sshbuf_len(ssh->state->output) < 16384; |
else |
else |
return buffer_len(&active_state->output) < 128 * 1024; |
return sshbuf_len(ssh->state->output) < 128 * 1024; |
} |
} |
|
|
static void |
void |
packet_set_tos(int tos) |
ssh_packet_set_tos(struct ssh *ssh, int tos) |
{ |
{ |
if (!packet_connection_is_on_socket()) |
if (!ssh_packet_connection_is_on_socket(ssh)) |
return; |
return; |
switch (packet_connection_af()) { |
switch (ssh_packet_connection_af(ssh)) { |
case AF_INET: |
case AF_INET: |
debug3("%s: set IP_TOS 0x%02x", __func__, tos); |
debug3("%s: set IP_TOS 0x%02x", __func__, tos); |
if (setsockopt(active_state->connection_in, |
if (setsockopt(ssh->state->connection_in, |
IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) |
IPPROTO_IP, IP_TOS, &tos, sizeof(tos)) < 0) |
error("setsockopt IP_TOS %d: %.100s:", |
error("setsockopt IP_TOS %d: %.100s:", |
tos, strerror(errno)); |
tos, strerror(errno)); |
break; |
break; |
case AF_INET6: |
case AF_INET6: |
debug3("%s: set IPV6_TCLASS 0x%02x", __func__, tos); |
debug3("%s: set IPV6_TCLASS 0x%02x", __func__, tos); |
if (setsockopt(active_state->connection_in, |
if (setsockopt(ssh->state->connection_in, |
IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) < 0) |
IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)) < 0) |
error("setsockopt IPV6_TCLASS %d: %.100s:", |
error("setsockopt IPV6_TCLASS %d: %.100s:", |
tos, strerror(errno)); |
tos, strerror(errno)); |
|
|
/* Informs that the current session is interactive. Sets IP flags for that. */ |
/* Informs that the current session is interactive. Sets IP flags for that. */ |
|
|
void |
void |
packet_set_interactive(int interactive, int qos_interactive, int qos_bulk) |
ssh_packet_set_interactive(struct ssh *ssh, int interactive, int qos_interactive, int qos_bulk) |
{ |
{ |
if (active_state->set_interactive_called) |
struct session_state *state = ssh->state; |
|
|
|
if (state->set_interactive_called) |
return; |
return; |
active_state->set_interactive_called = 1; |
state->set_interactive_called = 1; |
|
|
/* Record that we are in interactive mode. */ |
/* Record that we are in interactive mode. */ |
active_state->interactive_mode = interactive; |
state->interactive_mode = interactive; |
|
|
/* Only set socket options if using a socket. */ |
/* Only set socket options if using a socket. */ |
if (!packet_connection_is_on_socket()) |
if (!ssh_packet_connection_is_on_socket(ssh)) |
return; |
return; |
set_nodelay(active_state->connection_in); |
set_nodelay(state->connection_in); |
packet_set_tos(interactive ? qos_interactive : qos_bulk); |
ssh_packet_set_tos(ssh, interactive ? qos_interactive : |
|
qos_bulk); |
} |
} |
|
|
/* Returns true if the current connection is interactive. */ |
/* Returns true if the current connection is interactive. */ |
|
|
int |
int |
packet_is_interactive(void) |
ssh_packet_is_interactive(struct ssh *ssh) |
{ |
{ |
return active_state->interactive_mode; |
return ssh->state->interactive_mode; |
} |
} |
|
|
int |
int |
packet_set_maxsize(u_int s) |
ssh_packet_set_maxsize(struct ssh *ssh, u_int s) |
{ |
{ |
if (active_state->set_maxsize_called) { |
struct session_state *state = ssh->state; |
|
|
|
if (state->set_maxsize_called) { |
logit("packet_set_maxsize: called twice: old %d new %d", |
logit("packet_set_maxsize: called twice: old %d new %d", |
active_state->max_packet_size, s); |
state->max_packet_size, s); |
return -1; |
return -1; |
} |
} |
if (s < 4 * 1024 || s > 1024 * 1024) { |
if (s < 4 * 1024 || s > 1024 * 1024) { |
logit("packet_set_maxsize: bad size %d", s); |
logit("packet_set_maxsize: bad size %d", s); |
return -1; |
return -1; |
} |
} |
active_state->set_maxsize_called = 1; |
state->set_maxsize_called = 1; |
debug("packet_set_maxsize: setting to %d", s); |
debug("packet_set_maxsize: setting to %d", s); |
active_state->max_packet_size = s; |
state->max_packet_size = s; |
return s; |
return s; |
} |
} |
|
|
int |
int |
packet_inc_alive_timeouts(void) |
ssh_packet_inc_alive_timeouts(struct ssh *ssh) |
{ |
{ |
return ++active_state->keep_alive_timeouts; |
return ++ssh->state->keep_alive_timeouts; |
} |
} |
|
|
void |
void |
packet_set_alive_timeouts(int ka) |
ssh_packet_set_alive_timeouts(struct ssh *ssh, int ka) |
{ |
{ |
active_state->keep_alive_timeouts = ka; |
ssh->state->keep_alive_timeouts = ka; |
} |
} |
|
|
u_int |
u_int |
packet_get_maxsize(void) |
ssh_packet_get_maxsize(struct ssh *ssh) |
{ |
{ |
return active_state->max_packet_size; |
return ssh->state->max_packet_size; |
} |
} |
|
|
/* roundup current message to pad bytes */ |
|
void |
|
packet_add_padding(u_char pad) |
|
{ |
|
active_state->extra_pad = pad; |
|
} |
|
|
|
/* |
/* |
* 9.2. Ignored Data Message |
* 9.2. Ignored Data Message |
* |
* |
|
|
* protection measure against advanced traffic analysis techniques. |
* protection measure against advanced traffic analysis techniques. |
*/ |
*/ |
void |
void |
packet_send_ignore(int nbytes) |
ssh_packet_send_ignore(struct ssh *ssh, int nbytes) |
{ |
{ |
u_int32_t rnd = 0; |
u_int32_t rnd = 0; |
int i; |
int r, i; |
|
|
packet_start(compat20 ? SSH2_MSG_IGNORE : SSH_MSG_IGNORE); |
if ((r = sshpkt_start(ssh, compat20 ? |
packet_put_int(nbytes); |
SSH2_MSG_IGNORE : SSH_MSG_IGNORE)) != 0 || |
|
(r = sshpkt_put_u32(ssh, nbytes)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
for (i = 0; i < nbytes; i++) { |
for (i = 0; i < nbytes; i++) { |
if (i % 4 == 0) |
if (i % 4 == 0) |
rnd = arc4random(); |
rnd = arc4random(); |
packet_put_char((u_char)rnd & 0xff); |
if ((r = sshpkt_put_u8(ssh, (u_char)rnd & 0xff)) != 0) |
|
fatal("%s: %s", __func__, ssh_err(r)); |
rnd >>= 8; |
rnd >>= 8; |
} |
} |
} |
} |
|
|
#define MAX_PACKETS (1U<<31) |
#define MAX_PACKETS (1U<<31) |
int |
int |
packet_need_rekeying(void) |
ssh_packet_need_rekeying(struct ssh *ssh) |
{ |
{ |
if (datafellows & SSH_BUG_NOREKEY) |
struct session_state *state = ssh->state; |
|
|
|
if (ssh->compat & SSH_BUG_NOREKEY) |
return 0; |
return 0; |
return |
return |
(active_state->p_send.packets > MAX_PACKETS) || |
(state->p_send.packets > MAX_PACKETS) || |
(active_state->p_read.packets > MAX_PACKETS) || |
(state->p_read.packets > MAX_PACKETS) || |
(active_state->max_blocks_out && |
(state->max_blocks_out && |
(active_state->p_send.blocks > active_state->max_blocks_out)) || |
(state->p_send.blocks > state->max_blocks_out)) || |
(active_state->max_blocks_in && |
(state->max_blocks_in && |
(active_state->p_read.blocks > active_state->max_blocks_in)) || |
(state->p_read.blocks > state->max_blocks_in)) || |
(active_state->rekey_interval != 0 && active_state->rekey_time + |
(state->rekey_interval != 0 && state->rekey_time + |
active_state->rekey_interval <= monotime()); |
state->rekey_interval <= monotime()); |
} |
} |
|
|
void |
void |
packet_set_rekey_limits(u_int32_t bytes, time_t seconds) |
ssh_packet_set_rekey_limits(struct ssh *ssh, u_int32_t bytes, time_t seconds) |
{ |
{ |
debug3("rekey after %lld bytes, %d seconds", (long long)bytes, |
debug3("rekey after %lld bytes, %d seconds", (long long)bytes, |
(int)seconds); |
(int)seconds); |
active_state->rekey_limit = bytes; |
ssh->state->rekey_limit = bytes; |
active_state->rekey_interval = seconds; |
ssh->state->rekey_interval = seconds; |
/* |
/* |
* We set the time here so that in post-auth privsep slave we count |
* We set the time here so that in post-auth privsep slave we count |
* from the completion of the authentication. |
* from the completion of the authentication. |
*/ |
*/ |
active_state->rekey_time = monotime(); |
ssh->state->rekey_time = monotime(); |
} |
} |
|
|
time_t |
time_t |
packet_get_rekey_timeout(void) |
ssh_packet_get_rekey_timeout(struct ssh *ssh) |
{ |
{ |
time_t seconds; |
time_t seconds; |
|
|
seconds = active_state->rekey_time + active_state->rekey_interval - |
seconds = ssh->state->rekey_time + ssh->state->rekey_interval - |
monotime(); |
monotime(); |
return (seconds <= 0 ? 1 : seconds); |
return (seconds <= 0 ? 1 : seconds); |
} |
} |
|
|
void |
void |
packet_set_server(void) |
ssh_packet_set_server(struct ssh *ssh) |
{ |
{ |
active_state->server_side = 1; |
ssh->state->server_side = 1; |
} |
} |
|
|
void |
void |
packet_set_authenticated(void) |
ssh_packet_set_authenticated(struct ssh *ssh) |
{ |
{ |
active_state->after_authentication = 1; |
ssh->state->after_authentication = 1; |
} |
} |
|
|
void * |
void * |
packet_get_input(void) |
ssh_packet_get_input(struct ssh *ssh) |
{ |
{ |
return (void *)&active_state->input; |
return (void *)ssh->state->input; |
} |
} |
|
|
void * |
void * |
packet_get_output(void) |
ssh_packet_get_output(struct ssh *ssh) |
{ |
{ |
return (void *)&active_state->output; |
return (void *)ssh->state->output; |
} |
} |
|
|
void * |
/* XXX TODO update roaming to new API (does not work anyway) */ |
packet_get_newkeys(int mode) |
|
{ |
|
return (void *)active_state->newkeys[mode]; |
|
} |
|
|
|
/* |
/* |
* Save the state for the real connection, and use a separate state when |
* Save the state for the real connection, and use a separate state when |
* resuming a suspended connection. |
* resuming a suspended connection. |
*/ |
*/ |
void |
void |
packet_backup_state(void) |
ssh_packet_backup_state(struct ssh *ssh, |
|
struct ssh *backup_state) |
{ |
{ |
struct session_state *tmp; |
struct ssh *tmp; |
|
|
close(active_state->connection_in); |
close(ssh->state->connection_in); |
active_state->connection_in = -1; |
ssh->state->connection_in = -1; |
close(active_state->connection_out); |
close(ssh->state->connection_out); |
active_state->connection_out = -1; |
ssh->state->connection_out = -1; |
if (backup_state) |
if (backup_state) |
tmp = backup_state; |
tmp = backup_state; |
else |
else |
tmp = alloc_session_state(); |
tmp = ssh_alloc_session_state(); |
backup_state = active_state; |
backup_state = ssh; |
active_state = tmp; |
ssh = tmp; |
} |
} |
|
|
|
/* XXX FIXME FIXME FIXME */ |
/* |
/* |
* Swap in the old state when resuming a connecion. |
* Swap in the old state when resuming a connecion. |
*/ |
*/ |
void |
void |
packet_restore_state(void) |
ssh_packet_restore_state(struct ssh *ssh, |
|
struct ssh *backup_state) |
{ |
{ |
struct session_state *tmp; |
struct ssh *tmp; |
void *buf; |
|
u_int len; |
u_int len; |
|
int r; |
|
|
tmp = backup_state; |
tmp = backup_state; |
backup_state = active_state; |
backup_state = ssh; |
active_state = tmp; |
ssh = tmp; |
active_state->connection_in = backup_state->connection_in; |
ssh->state->connection_in = backup_state->state->connection_in; |
backup_state->connection_in = -1; |
backup_state->state->connection_in = -1; |
active_state->connection_out = backup_state->connection_out; |
ssh->state->connection_out = backup_state->state->connection_out; |
backup_state->connection_out = -1; |
backup_state->state->connection_out = -1; |
len = buffer_len(&backup_state->input); |
len = sshbuf_len(backup_state->state->input); |
if (len > 0) { |
if (len > 0) { |
buf = buffer_ptr(&backup_state->input); |
if ((r = sshbuf_putb(ssh->state->input, |
buffer_append(&active_state->input, buf, len); |
backup_state->state->input)) != 0) |
buffer_clear(&backup_state->input); |
fatal("%s: %s", __func__, ssh_err(r)); |
|
sshbuf_reset(backup_state->state->input); |
add_recv_bytes(len); |
add_recv_bytes(len); |
} |
} |
} |
} |
|
|
/* Reset after_authentication and reset compression in post-auth privsep */ |
/* Reset after_authentication and reset compression in post-auth privsep */ |
void |
static int |
packet_set_postauth(void) |
ssh_packet_set_postauth(struct ssh *ssh) |
{ |
{ |
Comp *comp; |
struct sshcomp *comp; |
int mode; |
int r, mode; |
|
|
debug("%s: called", __func__); |
debug("%s: called", __func__); |
/* This was set in net child, but is not visible in user child */ |
/* This was set in net child, but is not visible in user child */ |
active_state->after_authentication = 1; |
ssh->state->after_authentication = 1; |
active_state->rekeying = 0; |
ssh->state->rekeying = 0; |
for (mode = 0; mode < MODE_MAX; mode++) { |
for (mode = 0; mode < MODE_MAX; mode++) { |
if (active_state->newkeys[mode] == NULL) |
if (ssh->state->newkeys[mode] == NULL) |
continue; |
continue; |
comp = &active_state->newkeys[mode]->comp; |
comp = &ssh->state->newkeys[mode]->comp; |
if (comp && comp->enabled) |
if (comp && comp->enabled && |
packet_init_compression(); |
(r = ssh_packet_init_compression(ssh)) != 0) |
|
return r; |
} |
} |
|
return 0; |
|
} |
|
|
|
/* Packet state (de-)serialization for privsep */ |
|
|
|
/* turn kex into a blob for packet state serialization */ |
|
static int |
|
kex_to_blob(struct sshbuf *m, struct kex *kex) |
|
{ |
|
int r; |
|
|
|
if ((r = sshbuf_put_string(m, kex->session_id, |
|
kex->session_id_len)) != 0 || |
|
(r = sshbuf_put_u32(m, kex->we_need)) != 0 || |
|
(r = sshbuf_put_u32(m, kex->hostkey_type)) != 0 || |
|
(r = sshbuf_put_u32(m, kex->kex_type)) != 0 || |
|
(r = sshbuf_put_stringb(m, kex->my)) != 0 || |
|
(r = sshbuf_put_stringb(m, kex->peer)) != 0 || |
|
(r = sshbuf_put_u32(m, kex->flags)) != 0 || |
|
(r = sshbuf_put_cstring(m, kex->client_version_string)) != 0 || |
|
(r = sshbuf_put_cstring(m, kex->server_version_string)) != 0) |
|
return r; |
|
return 0; |
|
} |
|
|
|
/* turn key exchange results into a blob for packet state serialization */ |
|
static int |
|
newkeys_to_blob(struct sshbuf *m, struct ssh *ssh, int mode) |
|
{ |
|
struct sshbuf *b; |
|
struct sshcipher_ctx *cc; |
|
struct sshcomp *comp; |
|
struct sshenc *enc; |
|
struct sshmac *mac; |
|
struct newkeys *newkey; |
|
int r; |
|
|
|
if ((newkey = ssh->state->newkeys[mode]) == NULL) |
|
return SSH_ERR_INTERNAL_ERROR; |
|
enc = &newkey->enc; |
|
mac = &newkey->mac; |
|
comp = &newkey->comp; |
|
cc = (mode == MODE_OUT) ? &ssh->state->send_context : |
|
&ssh->state->receive_context; |
|
if ((r = cipher_get_keyiv(cc, enc->iv, enc->iv_len)) != 0) |
|
return r; |
|
if ((b = sshbuf_new()) == NULL) |
|
return SSH_ERR_ALLOC_FAIL; |
|
/* The cipher struct is constant and shared, you export pointer */ |
|
if ((r = sshbuf_put_cstring(b, enc->name)) != 0 || |
|
(r = sshbuf_put(b, &enc->cipher, sizeof(enc->cipher))) != 0 || |
|
(r = sshbuf_put_u32(b, enc->enabled)) != 0 || |
|
(r = sshbuf_put_u32(b, enc->block_size)) != 0 || |
|
(r = sshbuf_put_string(b, enc->key, enc->key_len)) != 0 || |
|
(r = sshbuf_put_string(b, enc->iv, enc->iv_len)) != 0) |
|
goto out; |
|
if (cipher_authlen(enc->cipher) == 0) { |
|
if ((r = sshbuf_put_cstring(b, mac->name)) != 0 || |
|
(r = sshbuf_put_u32(b, mac->enabled)) != 0 || |
|
(r = sshbuf_put_string(b, mac->key, mac->key_len)) != 0) |
|
goto out; |
|
} |
|
if ((r = sshbuf_put_u32(b, comp->type)) != 0 || |
|
(r = sshbuf_put_u32(b, comp->enabled)) != 0 || |
|
(r = sshbuf_put_cstring(b, comp->name)) != 0) |
|
goto out; |
|
r = sshbuf_put_stringb(m, b); |
|
out: |
|
if (b != NULL) |
|
sshbuf_free(b); |
|
return r; |
|
} |
|
|
|
/* serialize packet state into a blob */ |
|
int |
|
ssh_packet_get_state(struct ssh *ssh, struct sshbuf *m) |
|
{ |
|
struct session_state *state = ssh->state; |
|
u_char *p; |
|
size_t slen, rlen; |
|
int r, ssh1cipher; |
|
|
|
if (!compat20) { |
|
ssh1cipher = cipher_get_number(state->receive_context.cipher); |
|
slen = cipher_get_keyiv_len(&state->send_context); |
|
rlen = cipher_get_keyiv_len(&state->receive_context); |
|
if ((r = sshbuf_put_u32(m, state->remote_protocol_flags)) != 0 || |
|
(r = sshbuf_put_u32(m, ssh1cipher)) != 0 || |
|
(r = sshbuf_put_string(m, state->ssh1_key, state->ssh1_keylen)) != 0 || |
|
(r = sshbuf_put_u32(m, slen)) != 0 || |
|
(r = sshbuf_reserve(m, slen, &p)) != 0 || |
|
(r = cipher_get_keyiv(&state->send_context, p, slen)) != 0 || |
|
(r = sshbuf_put_u32(m, rlen)) != 0 || |
|
(r = sshbuf_reserve(m, rlen, &p)) != 0 || |
|
(r = cipher_get_keyiv(&state->receive_context, p, rlen)) != 0) |
|
return r; |
|
} else { |
|
if ((r = kex_to_blob(m, ssh->kex)) != 0 || |
|
(r = newkeys_to_blob(m, ssh, MODE_OUT)) != 0 || |
|
(r = newkeys_to_blob(m, ssh, MODE_IN)) != 0 || |
|
(r = sshbuf_put_u32(m, state->p_send.seqnr)) != 0 || |
|
(r = sshbuf_put_u64(m, state->p_send.blocks)) != 0 || |
|
(r = sshbuf_put_u32(m, state->p_send.packets)) != 0 || |
|
(r = sshbuf_put_u64(m, state->p_send.bytes)) != 0 || |
|
(r = sshbuf_put_u32(m, state->p_read.seqnr)) != 0 || |
|
(r = sshbuf_put_u64(m, state->p_read.blocks)) != 0 || |
|
(r = sshbuf_put_u32(m, state->p_read.packets)) != 0 || |
|
(r = sshbuf_put_u64(m, state->p_read.bytes)) != 0) |
|
return r; |
|
} |
|
|
|
slen = cipher_get_keycontext(&state->send_context, NULL); |
|
rlen = cipher_get_keycontext(&state->receive_context, NULL); |
|
if ((r = sshbuf_put_u32(m, slen)) != 0 || |
|
(r = sshbuf_reserve(m, slen, &p)) != 0) |
|
return r; |
|
if (cipher_get_keycontext(&state->send_context, p) != (int)slen) |
|
return SSH_ERR_INTERNAL_ERROR; |
|
if ((r = sshbuf_put_u32(m, rlen)) != 0 || |
|
(r = sshbuf_reserve(m, rlen, &p)) != 0) |
|
return r; |
|
if (cipher_get_keycontext(&state->receive_context, p) != (int)rlen) |
|
return SSH_ERR_INTERNAL_ERROR; |
|
|
|
if ((r = ssh_packet_get_compress_state(m, ssh)) != 0 || |
|
(r = sshbuf_put_stringb(m, state->input)) != 0 || |
|
(r = sshbuf_put_stringb(m, state->output)) != 0) |
|
return r; |
|
|
|
if (compat20) { |
|
if ((r = sshbuf_put_u64(m, get_sent_bytes())) != 0 || |
|
(r = sshbuf_put_u64(m, get_recv_bytes())) != 0) |
|
return r; |
|
} |
|
return 0; |
|
} |
|
|
|
/* restore key exchange results from blob for packet state de-serialization */ |
|
static int |
|
newkeys_from_blob(struct sshbuf *m, struct ssh *ssh, int mode) |
|
{ |
|
struct sshbuf *b = NULL; |
|
struct sshcomp *comp; |
|
struct sshenc *enc; |
|
struct sshmac *mac; |
|
struct newkeys *newkey = NULL; |
|
size_t keylen, ivlen, maclen; |
|
int r; |
|
|
|
if ((newkey = calloc(1, sizeof(*newkey))) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto out; |
|
} |
|
if ((r = sshbuf_froms(m, &b)) != 0) |
|
goto out; |
|
#ifdef DEBUG_PK |
|
sshbuf_dump(b, stderr); |
|
#endif |
|
enc = &newkey->enc; |
|
mac = &newkey->mac; |
|
comp = &newkey->comp; |
|
|
|
if ((r = sshbuf_get_cstring(b, &enc->name, NULL)) != 0 || |
|
(r = sshbuf_get(b, &enc->cipher, sizeof(enc->cipher))) != 0 || |
|
(r = sshbuf_get_u32(b, (u_int *)&enc->enabled)) != 0 || |
|
(r = sshbuf_get_u32(b, &enc->block_size)) != 0 || |
|
(r = sshbuf_get_string(b, &enc->key, &keylen)) != 0 || |
|
(r = sshbuf_get_string(b, &enc->iv, &ivlen)) != 0) |
|
goto out; |
|
if (cipher_authlen(enc->cipher) == 0) { |
|
if ((r = sshbuf_get_cstring(b, &mac->name, NULL)) != 0) |
|
goto out; |
|
if ((r = mac_setup(mac, mac->name)) != 0) |
|
goto out; |
|
if ((r = sshbuf_get_u32(b, (u_int *)&mac->enabled)) != 0 || |
|
(r = sshbuf_get_string(b, &mac->key, &maclen)) != 0) |
|
goto out; |
|
if (maclen > mac->key_len) { |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
mac->key_len = maclen; |
|
} |
|
if ((r = sshbuf_get_u32(b, &comp->type)) != 0 || |
|
(r = sshbuf_get_u32(b, (u_int *)&comp->enabled)) != 0 || |
|
(r = sshbuf_get_cstring(b, &comp->name, NULL)) != 0) |
|
goto out; |
|
if (enc->name == NULL || |
|
cipher_by_name(enc->name) != enc->cipher) { |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
if (sshbuf_len(b) != 0) { |
|
r = SSH_ERR_INVALID_FORMAT; |
|
goto out; |
|
} |
|
enc->key_len = keylen; |
|
enc->iv_len = ivlen; |
|
ssh->kex->newkeys[mode] = newkey; |
|
newkey = NULL; |
|
r = 0; |
|
out: |
|
if (newkey != NULL) |
|
free(newkey); |
|
if (b != NULL) |
|
sshbuf_free(b); |
|
return r; |
|
} |
|
|
|
/* restore kex from blob for packet state de-serialization */ |
|
static int |
|
kex_from_blob(struct sshbuf *m, struct kex **kexp) |
|
{ |
|
struct kex *kex; |
|
int r; |
|
|
|
if ((kex = calloc(1, sizeof(struct kex))) == NULL || |
|
(kex->my = sshbuf_new()) == NULL || |
|
(kex->peer = sshbuf_new()) == NULL) { |
|
r = SSH_ERR_ALLOC_FAIL; |
|
goto out; |
|
} |
|
if ((r = sshbuf_get_string(m, &kex->session_id, &kex->session_id_len)) != 0 || |
|
(r = sshbuf_get_u32(m, &kex->we_need)) != 0 || |
|
(r = sshbuf_get_u32(m, (u_int *)&kex->hostkey_type)) != 0 || |
|
(r = sshbuf_get_u32(m, &kex->kex_type)) != 0 || |
|
(r = sshbuf_get_stringb(m, kex->my)) != 0 || |
|
(r = sshbuf_get_stringb(m, kex->peer)) != 0 || |
|
(r = sshbuf_get_u32(m, &kex->flags)) != 0 || |
|
(r = sshbuf_get_cstring(m, &kex->client_version_string, NULL)) != 0 || |
|
(r = sshbuf_get_cstring(m, &kex->server_version_string, NULL)) != 0) |
|
goto out; |
|
kex->server = 1; |
|
kex->done = 1; |
|
r = 0; |
|
out: |
|
if (r != 0 || kexp == NULL) { |
|
if (kex != NULL) { |
|
if (kex->my != NULL) |
|
sshbuf_free(kex->my); |
|
if (kex->peer != NULL) |
|
sshbuf_free(kex->peer); |
|
free(kex); |
|
} |
|
if (kexp != NULL) |
|
*kexp = NULL; |
|
} else { |
|
*kexp = kex; |
|
} |
|
return r; |
|
} |
|
|
|
/* |
|
* Restore packet state from content of blob 'm' (de-serialization). |
|
* Note that 'm' will be partially consumed on parsing or any other errors. |
|
*/ |
|
int |
|
ssh_packet_set_state(struct ssh *ssh, struct sshbuf *m) |
|
{ |
|
struct session_state *state = ssh->state; |
|
const u_char *ssh1key, *ivin, *ivout, *keyin, *keyout, *input, *output; |
|
size_t ssh1keylen, rlen, slen, ilen, olen; |
|
int r; |
|
u_int ssh1cipher = 0; |
|
u_int64_t sent_bytes = 0, recv_bytes = 0; |
|
|
|
if (!compat20) { |
|
if ((r = sshbuf_get_u32(m, &state->remote_protocol_flags)) != 0 || |
|
(r = sshbuf_get_u32(m, &ssh1cipher)) != 0 || |
|
(r = sshbuf_get_string_direct(m, &ssh1key, &ssh1keylen)) != 0 || |
|
(r = sshbuf_get_string_direct(m, &ivout, &slen)) != 0 || |
|
(r = sshbuf_get_string_direct(m, &ivin, &rlen)) != 0) |
|
return r; |
|
if (ssh1cipher > INT_MAX) |
|
return SSH_ERR_KEY_UNKNOWN_CIPHER; |
|
ssh_packet_set_encryption_key(ssh, ssh1key, ssh1keylen, |
|
(int)ssh1cipher); |
|
if (cipher_get_keyiv_len(&state->send_context) != (int)slen || |
|
cipher_get_keyiv_len(&state->receive_context) != (int)rlen) |
|
return SSH_ERR_INVALID_FORMAT; |
|
if ((r = cipher_set_keyiv(&state->send_context, ivout)) != 0 || |
|
(r = cipher_set_keyiv(&state->receive_context, ivin)) != 0) |
|
return r; |
|
} else { |
|
if ((r = kex_from_blob(m, &ssh->kex)) != 0 || |
|
(r = newkeys_from_blob(m, ssh, MODE_OUT)) != 0 || |
|
(r = newkeys_from_blob(m, ssh, MODE_IN)) != 0 || |
|
(r = sshbuf_get_u32(m, &state->p_send.seqnr)) != 0 || |
|
(r = sshbuf_get_u64(m, &state->p_send.blocks)) != 0 || |
|
(r = sshbuf_get_u32(m, &state->p_send.packets)) != 0 || |
|
(r = sshbuf_get_u64(m, &state->p_send.bytes)) != 0 || |
|
(r = sshbuf_get_u32(m, &state->p_read.seqnr)) != 0 || |
|
(r = sshbuf_get_u64(m, &state->p_read.blocks)) != 0 || |
|
(r = sshbuf_get_u32(m, &state->p_read.packets)) != 0 || |
|
(r = sshbuf_get_u64(m, &state->p_read.bytes)) != 0) |
|
return r; |
|
/* XXX ssh_set_newkeys overrides p_read.packets? XXX */ |
|
if ((r = ssh_set_newkeys(ssh, MODE_IN)) != 0 || |
|
(r = ssh_set_newkeys(ssh, MODE_OUT)) != 0) |
|
return r; |
|
} |
|
if ((r = sshbuf_get_string_direct(m, &keyout, &slen)) != 0 || |
|
(r = sshbuf_get_string_direct(m, &keyin, &rlen)) != 0) |
|
return r; |
|
if (cipher_get_keycontext(&state->send_context, NULL) != (int)slen || |
|
cipher_get_keycontext(&state->receive_context, NULL) != (int)rlen) |
|
return SSH_ERR_INVALID_FORMAT; |
|
cipher_set_keycontext(&state->send_context, keyout); |
|
cipher_set_keycontext(&state->receive_context, keyin); |
|
|
|
if ((r = ssh_packet_set_compress_state(ssh, m)) != 0 || |
|
(r = ssh_packet_set_postauth(ssh)) != 0) |
|
return r; |
|
|
|
sshbuf_reset(state->input); |
|
sshbuf_reset(state->output); |
|
if ((r = sshbuf_get_string_direct(m, &input, &ilen)) != 0 || |
|
(r = sshbuf_get_string_direct(m, &output, &olen)) != 0 || |
|
(r = sshbuf_put(state->input, input, ilen)) != 0 || |
|
(r = sshbuf_put(state->output, output, olen)) != 0) |
|
return r; |
|
|
|
if (compat20) { |
|
if ((r = sshbuf_get_u64(m, &sent_bytes)) != 0 || |
|
(r = sshbuf_get_u64(m, &recv_bytes)) != 0) |
|
return r; |
|
roam_set_bytes(sent_bytes, recv_bytes); |
|
} |
|
if (sshbuf_len(m)) |
|
return SSH_ERR_INVALID_FORMAT; |
|
debug3("%s: done", __func__); |
|
return 0; |
|
} |
|
|
|
/* NEW API */ |
|
|
|
/* put data to the outgoing packet */ |
|
|
|
int |
|
sshpkt_put(struct ssh *ssh, const void *v, size_t len) |
|
{ |
|
return sshbuf_put(ssh->state->outgoing_packet, v, len); |
|
} |
|
|
|
int |
|
sshpkt_putb(struct ssh *ssh, const struct sshbuf *b) |
|
{ |
|
return sshbuf_putb(ssh->state->outgoing_packet, b); |
|
} |
|
|
|
int |
|
sshpkt_put_u8(struct ssh *ssh, u_char val) |
|
{ |
|
return sshbuf_put_u8(ssh->state->outgoing_packet, val); |
|
} |
|
|
|
int |
|
sshpkt_put_u32(struct ssh *ssh, u_int32_t val) |
|
{ |
|
return sshbuf_put_u32(ssh->state->outgoing_packet, val); |
|
} |
|
|
|
int |
|
sshpkt_put_u64(struct ssh *ssh, u_int64_t val) |
|
{ |
|
return sshbuf_put_u64(ssh->state->outgoing_packet, val); |
|
} |
|
|
|
int |
|
sshpkt_put_string(struct ssh *ssh, const void *v, size_t len) |
|
{ |
|
return sshbuf_put_string(ssh->state->outgoing_packet, v, len); |
|
} |
|
|
|
int |
|
sshpkt_put_cstring(struct ssh *ssh, const void *v) |
|
{ |
|
return sshbuf_put_cstring(ssh->state->outgoing_packet, v); |
|
} |
|
|
|
int |
|
sshpkt_put_stringb(struct ssh *ssh, const struct sshbuf *v) |
|
{ |
|
return sshbuf_put_stringb(ssh->state->outgoing_packet, v); |
|
} |
|
|
|
int |
|
sshpkt_put_ec(struct ssh *ssh, const EC_POINT *v, const EC_GROUP *g) |
|
{ |
|
return sshbuf_put_ec(ssh->state->outgoing_packet, v, g); |
|
} |
|
|
|
int |
|
sshpkt_put_bignum1(struct ssh *ssh, const BIGNUM *v) |
|
{ |
|
return sshbuf_put_bignum1(ssh->state->outgoing_packet, v); |
|
} |
|
|
|
int |
|
sshpkt_put_bignum2(struct ssh *ssh, const BIGNUM *v) |
|
{ |
|
return sshbuf_put_bignum2(ssh->state->outgoing_packet, v); |
|
} |
|
|
|
/* fetch data from the incoming packet */ |
|
|
|
int |
|
sshpkt_get(struct ssh *ssh, void *valp, size_t len) |
|
{ |
|
return sshbuf_get(ssh->state->incoming_packet, valp, len); |
|
} |
|
|
|
int |
|
sshpkt_get_u8(struct ssh *ssh, u_char *valp) |
|
{ |
|
return sshbuf_get_u8(ssh->state->incoming_packet, valp); |
|
} |
|
|
|
int |
|
sshpkt_get_u32(struct ssh *ssh, u_int32_t *valp) |
|
{ |
|
return sshbuf_get_u32(ssh->state->incoming_packet, valp); |
|
} |
|
|
|
int |
|
sshpkt_get_u64(struct ssh *ssh, u_int64_t *valp) |
|
{ |
|
return sshbuf_get_u64(ssh->state->incoming_packet, valp); |
|
} |
|
|
|
int |
|
sshpkt_get_string(struct ssh *ssh, u_char **valp, size_t *lenp) |
|
{ |
|
return sshbuf_get_string(ssh->state->incoming_packet, valp, lenp); |
|
} |
|
|
|
int |
|
sshpkt_get_string_direct(struct ssh *ssh, const u_char **valp, size_t *lenp) |
|
{ |
|
return sshbuf_get_string_direct(ssh->state->incoming_packet, valp, lenp); |
|
} |
|
|
|
int |
|
sshpkt_get_cstring(struct ssh *ssh, char **valp, size_t *lenp) |
|
{ |
|
return sshbuf_get_cstring(ssh->state->incoming_packet, valp, lenp); |
|
} |
|
|
|
int |
|
sshpkt_get_ec(struct ssh *ssh, EC_POINT *v, const EC_GROUP *g) |
|
{ |
|
return sshbuf_get_ec(ssh->state->incoming_packet, v, g); |
|
} |
|
|
|
int |
|
sshpkt_get_bignum1(struct ssh *ssh, BIGNUM *v) |
|
{ |
|
return sshbuf_get_bignum1(ssh->state->incoming_packet, v); |
|
} |
|
|
|
int |
|
sshpkt_get_bignum2(struct ssh *ssh, BIGNUM *v) |
|
{ |
|
return sshbuf_get_bignum2(ssh->state->incoming_packet, v); |
|
} |
|
|
|
int |
|
sshpkt_get_end(struct ssh *ssh) |
|
{ |
|
if (sshbuf_len(ssh->state->incoming_packet) > 0) |
|
return SSH_ERR_UNEXPECTED_TRAILING_DATA; |
|
return 0; |
|
} |
|
|
|
const u_char * |
|
sshpkt_ptr(struct ssh *ssh, size_t *lenp) |
|
{ |
|
if (lenp != NULL) |
|
*lenp = sshbuf_len(ssh->state->incoming_packet); |
|
return sshbuf_ptr(ssh->state->incoming_packet); |
|
} |
|
|
|
/* start a new packet */ |
|
|
|
int |
|
sshpkt_start(struct ssh *ssh, u_char type) |
|
{ |
|
u_char buf[9]; |
|
int len; |
|
|
|
DBG(debug("packet_start[%d]", type)); |
|
len = compat20 ? 6 : 9; |
|
memset(buf, 0, len - 1); |
|
buf[len - 1] = type; |
|
sshbuf_reset(ssh->state->outgoing_packet); |
|
return sshbuf_put(ssh->state->outgoing_packet, buf, len); |
|
} |
|
|
|
/* send it */ |
|
|
|
int |
|
sshpkt_send(struct ssh *ssh) |
|
{ |
|
if (compat20) |
|
return ssh_packet_send2(ssh); |
|
else |
|
return ssh_packet_send1(ssh); |
|
} |
|
|
|
int |
|
sshpkt_disconnect(struct ssh *ssh, const char *fmt,...) |
|
{ |
|
char buf[1024]; |
|
va_list args; |
|
int r; |
|
|
|
va_start(args, fmt); |
|
vsnprintf(buf, sizeof(buf), fmt, args); |
|
va_end(args); |
|
|
|
if (compat20) { |
|
if ((r = sshpkt_start(ssh, SSH2_MSG_DISCONNECT)) != 0 || |
|
(r = sshpkt_put_u32(ssh, SSH2_DISCONNECT_PROTOCOL_ERROR)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, "")) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
return r; |
|
} else { |
|
if ((r = sshpkt_start(ssh, SSH_MSG_DISCONNECT)) != 0 || |
|
(r = sshpkt_put_cstring(ssh, buf)) != 0 || |
|
(r = sshpkt_send(ssh)) != 0) |
|
return r; |
|
} |
|
return 0; |
|
} |
|
|
|
/* roundup current message to pad bytes */ |
|
int |
|
sshpkt_add_padding(struct ssh *ssh, u_char pad) |
|
{ |
|
ssh->state->extra_pad = pad; |
|
return 0; |
} |
} |