From d4a8b7e34dd619a4debf9a206c81db26d1402ea6 Mon Sep 17 00:00:00 2001 From: Damien Miller Date: Wed, 27 Oct 1999 13:42:43 +1000 Subject: Initial revision --- packet.c | 762 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 762 insertions(+) create mode 100644 packet.c (limited to 'packet.c') diff --git a/packet.c b/packet.c new file mode 100644 index 000000000..7e74c73b3 --- /dev/null +++ b/packet.c @@ -0,0 +1,762 @@ +/* + +packet.c + +Author: Tatu Ylonen + +Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + All rights reserved + +Created: Sat Mar 18 02:40:40 1995 ylo + +This file contains code implementing the packet protocol and communication +with the other side. This same code is used both on client and server side. + +*/ + +#include "includes.h" +RCSID("$Id: packet.c,v 1.1 1999/10/27 03:42:44 damien Exp $"); + +#include "xmalloc.h" +#include "buffer.h" +#include "packet.h" +#include "bufaux.h" +#include "ssh.h" +#include "crc32.h" +#include "cipher.h" +#include "getput.h" + +#include "compress.h" +#include "deattack.h" + +/* This variable contains the file descriptors used for communicating with + the other side. connection_in is used for reading; connection_out + for writing. These can be the same descriptor, in which case it is + assumed to be a socket. */ +static int connection_in = -1; +static int connection_out = -1; + +/* Cipher type. This value is only used to determine whether to pad the + packets with zeroes or random data. */ +static int cipher_type = SSH_CIPHER_NONE; + +/* Protocol flags for the remote side. */ +static unsigned int remote_protocol_flags = 0; + +/* Encryption context for receiving data. This is only used for decryption. */ +static CipherContext receive_context; +/* Encryption coontext for sending data. This is only used for encryption. */ +static CipherContext send_context; + +/* Buffer for raw input data from the socket. */ +static Buffer input; + +/* Buffer for raw output data going to the socket. */ +static Buffer output; + +/* Buffer for the partial outgoing packet being constructed. */ +static Buffer outgoing_packet; + +/* Buffer for the incoming packet currently being processed. */ +static Buffer incoming_packet; + +/* Scratch buffer for packet compression/decompression. */ +static Buffer compression_buffer; + +/* Flag indicating whether packet compression/decompression is enabled. */ +static int packet_compression = 0; + +/* Flag indicating whether this module has been initialized. */ +static int initialized = 0; + +/* Set to true if the connection is interactive. */ +static int interactive_mode = 0; + +/* Sets the descriptors used for communication. Disables encryption until + packet_set_encryption_key is called. */ + +void +packet_set_connection(int fd_in, int fd_out) +{ + connection_in = fd_in; + connection_out = fd_out; + cipher_type = SSH_CIPHER_NONE; + cipher_set_key(&send_context, SSH_CIPHER_NONE, (unsigned char *)"", 0, 1); + cipher_set_key(&receive_context, SSH_CIPHER_NONE, (unsigned char *)"", 0, 0); + if (!initialized) + { + initialized = 1; + buffer_init(&input); + buffer_init(&output); + buffer_init(&outgoing_packet); + buffer_init(&incoming_packet); + } + + /* Kludge: arrange the close function to be called from fatal(). */ + fatal_add_cleanup((void (*)(void *))packet_close, NULL); +} + +/* Sets the connection into non-blocking mode. */ + +void +packet_set_nonblocking() +{ + /* Set the socket into non-blocking mode. */ + if (fcntl(connection_in, F_SETFL, O_NONBLOCK) < 0) + error("fcntl O_NONBLOCK: %.100s", strerror(errno)); + + if (connection_out != connection_in) + { + if (fcntl(connection_out, F_SETFL, O_NONBLOCK) < 0) + error("fcntl O_NONBLOCK: %.100s", strerror(errno)); + } +} + +/* Returns the socket used for reading. */ + +int +packet_get_connection_in() +{ + return connection_in; +} + +/* Returns the descriptor used for writing. */ + +int +packet_get_connection_out() +{ + return connection_out; +} + +/* Closes the connection and clears and frees internal data structures. */ + +void +packet_close() +{ + if (!initialized) + return; + initialized = 0; + if (connection_in == connection_out) + { + shutdown(connection_out, SHUT_RDWR); + close(connection_out); + } + else + { + close(connection_in); + close(connection_out); + } + buffer_free(&input); + buffer_free(&output); + buffer_free(&outgoing_packet); + buffer_free(&incoming_packet); + if (packet_compression) + { + buffer_free(&compression_buffer); + buffer_compress_uninit(); + } +} + +/* Sets remote side protocol flags. */ + +void +packet_set_protocol_flags(unsigned int protocol_flags) +{ + remote_protocol_flags = protocol_flags; + channel_set_options((protocol_flags & SSH_PROTOFLAG_HOST_IN_FWD_OPEN) != 0); +} + +/* Returns the remote protocol flags set earlier by the above function. */ + +unsigned int +packet_get_protocol_flags() +{ + return remote_protocol_flags; +} + +/* Starts packet compression from the next packet on in both directions. + Level is compression level 1 (fastest) - 9 (slow, best) as in gzip. */ + +void +packet_start_compression(int level) +{ + if (packet_compression) + fatal("Compression already enabled."); + packet_compression = 1; + buffer_init(&compression_buffer); + buffer_compress_init(level); +} + +/* Encrypts the given number of bytes, copying from src to dest. + bytes is known to be a multiple of 8. */ + +void +packet_encrypt(CipherContext *cc, void *dest, void *src, + unsigned int bytes) +{ + assert((bytes % 8) == 0); + cipher_encrypt(cc, dest, src, bytes); +} + +/* Decrypts the given number of bytes, copying from src to dest. + bytes is known to be a multiple of 8. */ + +void +packet_decrypt(CipherContext *cc, void *dest, void *src, + unsigned int bytes) +{ + int i; + + assert((bytes % 8) == 0); + + /* + Cryptographic attack detector for ssh - Modifications for packet.c + (C)1998 CORE-SDI, Buenos Aires Argentina + Ariel Futoransky(futo@core-sdi.com) + */ + switch (cc->type) + { + case SSH_CIPHER_NONE: + i = DEATTACK_OK; + break; + default: + i = detect_attack(src, bytes, NULL); + break; + } + + if (i == DEATTACK_DETECTED) + packet_disconnect("crc32 compensation attack: network attack detected"); + + cipher_decrypt(cc, dest, src, bytes); +} + +/* 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. */ + +void +packet_set_encryption_key(const unsigned char *key, unsigned int keylen, + int cipher, int is_client) +{ + cipher_type = cipher; + if (cipher == SSH_CIPHER_RC4) + { + if (is_client) + { /* In client: use first half for receiving, second for sending. */ + cipher_set_key(&receive_context, cipher, key, keylen / 2, 0); + cipher_set_key(&send_context, cipher, key + keylen / 2, + keylen / 2, 1); + } + else + { /* In server: use first half for sending, second for receiving. */ + cipher_set_key(&receive_context, cipher, key + keylen / 2, + keylen / 2, 0); + cipher_set_key(&send_context, cipher, key, keylen / 2, 1); + } + } + else + { + /* All other ciphers use the same key in both directions for now. */ + cipher_set_key(&receive_context, cipher, key, keylen, 0); + cipher_set_key(&send_context, cipher, key, keylen, 1); + } +} + +/* Starts constructing a packet to send. */ + +void +packet_start(int type) +{ + char buf[9]; + + buffer_clear(&outgoing_packet); + memset(buf, 0, 8); + buf[8] = type; + buffer_append(&outgoing_packet, buf, 9); +} + +/* Appends a character to the packet data. */ + +void +packet_put_char(int value) +{ + char ch = value; + buffer_append(&outgoing_packet, &ch, 1); +} + +/* Appends an integer to the packet data. */ + +void +packet_put_int(unsigned int value) +{ + buffer_put_int(&outgoing_packet, value); +} + +/* Appends a string to packet data. */ + +void +packet_put_string(const char *buf, unsigned int len) +{ + buffer_put_string(&outgoing_packet, buf, len); +} + +/* Appends an arbitrary precision integer to packet data. */ + +void +packet_put_bignum(BIGNUM *value) +{ + buffer_put_bignum(&outgoing_packet, value); +} + +/* Finalizes and sends the packet. If the encryption key has been set, + encrypts the packet before sending. */ + +void +packet_send() +{ + char buf[8], *cp; + int i, padding, len; + unsigned int checksum; + u_int32_t rand = 0; + + /* If using packet compression, compress the payload of the outgoing + packet. */ + if (packet_compression) + { + buffer_clear(&compression_buffer); + buffer_consume(&outgoing_packet, 8); /* Skip padding. */ + buffer_append(&compression_buffer, "\0\0\0\0\0\0\0\0", 8); /* padding */ + buffer_compress(&outgoing_packet, &compression_buffer); + buffer_clear(&outgoing_packet); + buffer_append(&outgoing_packet, buffer_ptr(&compression_buffer), + buffer_len(&compression_buffer)); + } + + /* Compute packet length without padding (add checksum, remove padding). */ + len = buffer_len(&outgoing_packet) + 4 - 8; + + /* Insert padding. */ + padding = 8 - len % 8; + if (cipher_type != SSH_CIPHER_NONE) + { + cp = buffer_ptr(&outgoing_packet); + for (i = 0; i < padding; i++) { + if (i % 4 == 0) + rand = arc4random(); + cp[7 - i] = rand & 0xff; + rand >>= 8; + } + } + buffer_consume(&outgoing_packet, 8 - padding); + + /* Add check bytes. */ + checksum = crc32((unsigned char *)buffer_ptr(&outgoing_packet), + buffer_len(&outgoing_packet)); + PUT_32BIT(buf, checksum); + buffer_append(&outgoing_packet, buf, 4); + +#ifdef PACKET_DEBUG + fprintf(stderr, "packet_send plain: "); + buffer_dump(&outgoing_packet); +#endif + + /* Append to output. */ + PUT_32BIT(buf, len); + buffer_append(&output, buf, 4); + buffer_append_space(&output, &cp, buffer_len(&outgoing_packet)); + packet_encrypt(&send_context, cp, buffer_ptr(&outgoing_packet), + buffer_len(&outgoing_packet)); + +#ifdef PACKET_DEBUG + fprintf(stderr, "encrypted: "); buffer_dump(&output); +#endif + + buffer_clear(&outgoing_packet); + + /* 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 called. */ +} + +/* 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 be used during the interactive session. */ + +int +packet_read(int *payload_len_ptr) +{ + int type, len; + fd_set set; + char buf[8192]; + + /* Since we are blocking, ensure that all written packets have been sent. */ + packet_write_wait(); + + /* Stay in the loop until we have received a complete packet. */ + for (;;) + { + /* Try to read a packet from the buffer. */ + type = packet_read_poll(payload_len_ptr); + if (type == SSH_SMSG_SUCCESS + || type == SSH_SMSG_FAILURE + || type == SSH_CMSG_EOF + || type == SSH_CMSG_EXIT_CONFIRMATION) + packet_integrity_check(*payload_len_ptr, 0, type); + /* If we got a packet, return it. */ + if (type != SSH_MSG_NONE) + return type; + /* Otherwise, wait for some data to arrive, add it to the buffer, + and try again. */ + FD_ZERO(&set); + FD_SET(connection_in, &set); + /* Wait for some data to arrive. */ + select(connection_in + 1, &set, NULL, NULL, NULL); + /* Read data from the socket. */ + len = read(connection_in, buf, sizeof(buf)); + if (len == 0) + fatal("Connection closed by remote host."); + if (len < 0) + fatal("Read from socket failed: %.100s", strerror(errno)); + /* Append it to the buffer. */ + packet_process_incoming(buf, len); + } + /*NOTREACHED*/ +} + +/* Waits until a packet has been received, verifies that its type matches + that given, and gives a fatal error and exits if there is a mismatch. */ + +void +packet_read_expect(int *payload_len_ptr, int expected_type) +{ + int type; + + type = packet_read(payload_len_ptr); + if (type != expected_type) + packet_disconnect("Protocol error: expected packet type %d, got %d", + expected_type, type); +} + +/* Checks if a full packet is available in the data received so far via + packet_process_incoming. If so, reads the packet; otherwise returns + SSH_MSG_NONE. This does not wait for data from the connection. + + SSH_MSG_DISCONNECT is handled specially here. Also, + SSH_MSG_IGNORE messages are skipped by this function and are never returned + to higher levels. + + The returned payload_len does include space consumed by: + Packet length + Padding + Packet type + Check bytes + + + */ + +int +packet_read_poll(int *payload_len_ptr) +{ + unsigned int len, padded_len; + unsigned char *ucp; + char buf[8], *cp; + unsigned int checksum, stored_checksum; + + restart: + + /* Check if input size is less than minimum packet size. */ + if (buffer_len(&input) < 4 + 8) + return SSH_MSG_NONE; + /* Get length of incoming packet. */ + ucp = (unsigned char *)buffer_ptr(&input); + len = GET_32BIT(ucp); + if (len < 1 + 2 + 2 || len > 256*1024) + packet_disconnect("Bad packet length %d.", len); + padded_len = (len + 8) & ~7; + + /* Check if the packet has been entirely received. */ + if (buffer_len(&input) < 4 + padded_len) + return SSH_MSG_NONE; + + /* The entire packet is in buffer. */ + + /* Consume packet length. */ + buffer_consume(&input, 4); + + /* Copy data to incoming_packet. */ + buffer_clear(&incoming_packet); + buffer_append_space(&incoming_packet, &cp, padded_len); + packet_decrypt(&receive_context, cp, buffer_ptr(&input), padded_len); + buffer_consume(&input, padded_len); + +#ifdef PACKET_DEBUG + fprintf(stderr, "read_poll plain: "); buffer_dump(&incoming_packet); +#endif + + /* Compute packet checksum. */ + checksum = crc32((unsigned char *)buffer_ptr(&incoming_packet), + buffer_len(&incoming_packet) - 4); + + /* Skip padding. */ + buffer_consume(&incoming_packet, 8 - len % 8); + + /* Test check bytes. */ + assert(len == buffer_len(&incoming_packet)); + ucp = (unsigned char *)buffer_ptr(&incoming_packet) + len - 4; + stored_checksum = GET_32BIT(ucp); + if (checksum != stored_checksum) + packet_disconnect("Corrupted check bytes on input."); + buffer_consume_end(&incoming_packet, 4); + + /* If using packet compression, decompress the packet. */ + if (packet_compression) + { + buffer_clear(&compression_buffer); + buffer_uncompress(&incoming_packet, &compression_buffer); + buffer_clear(&incoming_packet); + buffer_append(&incoming_packet, buffer_ptr(&compression_buffer), + buffer_len(&compression_buffer)); + } + + /* Get packet type. */ + buffer_get(&incoming_packet, &buf[0], 1); + + /* Return length of payload (without type field). */ + *payload_len_ptr = buffer_len(&incoming_packet); + + /* Handle disconnect message. */ + if ((unsigned char)buf[0] == SSH_MSG_DISCONNECT) + fatal("%.900s", packet_get_string(NULL)); + + /* Ignore ignore messages. */ + if ((unsigned char)buf[0] == SSH_MSG_IGNORE) + goto restart; + + /* Send debug messages as debugging output. */ + if ((unsigned char)buf[0] == SSH_MSG_DEBUG) + { + debug("Remote: %.900s", packet_get_string(NULL)); + goto restart; + } + + /* Return type. */ + return (unsigned char)buf[0]; +} + +/* Buffers the given amount of input characters. This is intended to be + used together with packet_read_poll. */ + +void +packet_process_incoming(const char *buf, unsigned int len) +{ + buffer_append(&input, buf, len); +} + +/* Returns a character from the packet. */ + +unsigned int +packet_get_char() +{ + char ch; + buffer_get(&incoming_packet, &ch, 1); + return (unsigned char)ch; +} + +/* Returns an integer from the packet data. */ + +unsigned int +packet_get_int() +{ + return buffer_get_int(&incoming_packet); +} + +/* Returns an arbitrary precision integer from the packet data. The integer + must have been initialized before this call. */ + +void +packet_get_bignum(BIGNUM *value, int *length_ptr) +{ + *length_ptr = buffer_get_bignum(&incoming_packet, value); +} + +/* 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. */ + +char +*packet_get_string(unsigned int *length_ptr) +{ + return buffer_get_string(&incoming_packet, length_ptr); +} + +/* 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 message is printed immediately, but only if the client is being + executed in verbose mode. These messages are primarily intended to + ease debugging authentication problems. The length of the formatted + message must not exceed 1024 bytes. This will automatically call + packet_write_wait. */ + +void +packet_send_debug(const char *fmt, ...) +{ + char buf[1024]; + va_list args; + + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + packet_start(SSH_MSG_DEBUG); + packet_put_string(buf, strlen(buf)); + packet_send(); + packet_write_wait(); +} + +/* Logs the error plus constructs and sends a disconnect + packet, closes the connection, and exits. This function never returns. + The error message should not contain a newline. The length of the + formatted message must not exceed 1024 bytes. */ + +void +packet_disconnect(const char *fmt, ...) +{ + char buf[1024]; + va_list args; + static int disconnecting = 0; + if (disconnecting) /* Guard against recursive invocations. */ + fatal("packet_disconnect called recursively."); + disconnecting = 1; + + /* Format the message. Note that the caller must make sure the message + is of limited size. */ + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + + /* Send the disconnect message to the other side, and wait for it to get + sent. */ + packet_start(SSH_MSG_DISCONNECT); + packet_put_string(buf, strlen(buf)); + packet_send(); + packet_write_wait(); + + /* Stop listening for connections. */ + channel_stop_listening(); + + /* Close the connection. */ + packet_close(); + + /* Display the error locally and exit. */ + fatal("Local: %.100s", buf); +} + +/* Checks if there is any buffered output, and tries to write some of the + output. */ + +void +packet_write_poll() +{ + int len = buffer_len(&output); + if (len > 0) + { + len = write(connection_out, buffer_ptr(&output), len); + if (len <= 0) { + if (errno == EAGAIN) + return; + else + fatal("Write failed: %.100s", strerror(errno)); + } + buffer_consume(&output, len); + } +} + +/* Calls packet_write_poll repeatedly until all pending output data has + been written. */ + +void +packet_write_wait() +{ + packet_write_poll(); + while (packet_have_data_to_write()) + { + fd_set set; + FD_ZERO(&set); + FD_SET(connection_out, &set); + select(connection_out + 1, NULL, &set, NULL, NULL); + packet_write_poll(); + } +} + +/* Returns true if there is buffered data to write to the connection. */ + +int +packet_have_data_to_write() +{ + return buffer_len(&output) != 0; +} + +/* Returns true if there is not too much data to write to the connection. */ + +int +packet_not_very_much_data_to_write() +{ + if (interactive_mode) + return buffer_len(&output) < 16384; + else + return buffer_len(&output) < 128*1024; +} + +/* Informs that the current session is interactive. Sets IP flags for that. */ + +void +packet_set_interactive(int interactive, int keepalives) +{ + int on = 1; + + /* Record that we are in interactive mode. */ + interactive_mode = interactive; + + /* Only set socket options if using a socket (as indicated by the descriptors + being the same). */ + if (connection_in != connection_out) + return; + + if (keepalives) + { + /* Set keepalives if requested. */ + if (setsockopt(connection_in, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, + sizeof(on)) < 0) + error("setsockopt SO_KEEPALIVE: %.100s", strerror(errno)); + } + + if (interactive) + { + /* Set IP options for an interactive connection. Use IPTOS_LOWDELAY + and TCP_NODELAY. */ + int lowdelay = IPTOS_LOWDELAY; + if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *)&lowdelay, + sizeof(lowdelay)) < 0) + error("setsockopt IPTOS_LOWDELAY: %.100s", strerror(errno)); + if (setsockopt(connection_in, IPPROTO_TCP, TCP_NODELAY, (void *)&on, + sizeof(on)) < 0) + error("setsockopt TCP_NODELAY: %.100s", strerror(errno)); + } + else + { + /* Set IP options for a non-interactive connection. Use + IPTOS_THROUGHPUT. */ + int throughput = IPTOS_THROUGHPUT; + if (setsockopt(connection_in, IPPROTO_IP, IP_TOS, (void *)&throughput, + sizeof(throughput)) < 0) + error("setsockopt IPTOS_THROUGHPUT: %.100s", strerror(errno)); + } +} + +/* Returns true if the current connection is interactive. */ + +int +packet_is_interactive() +{ + return interactive_mode; +} -- cgit v1.2.3