/*
- silcpacket.c
+ silcpacket.c
- Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
+ Author: Pekka Riikonen <priikone@silcnet.org>
- Copyright (C) 1997 - 2000 Pekka Riikonen
+ Copyright (C) 1997 - 2001 Pekka Riikonen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
+ the Free Software Foundation; version 2 of the License.
+
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
******************************************************************************/
-/* Writes data from encrypted buffer to the socket connection. If the
- data cannot be written at once, it will be written later with a timeout.
- The data is written from the data section of the buffer, not from head
- or tail section. This automatically pulls the data section towards end
- after writing the data. */
-
-int silc_packet_write(int sock, SilcBuffer src)
-{
- int ret = 0;
-
- SILC_LOG_DEBUG(("Writing data to socket %d", sock));
-
- if (src->len > 0) {
- ret = write(sock, src->data, src->len);
- if (ret < 0) {
- if (errno == EAGAIN) {
- SILC_LOG_DEBUG(("Could not write immediately, will do it later"));
- return -2;
- }
- SILC_LOG_ERROR(("Cannot write to socket: %s", strerror(errno)));
- return -1;
- }
-
- silc_buffer_pull(src, ret);
- }
-
- SILC_LOG_DEBUG(("Wrote data %d bytes", ret));
-
- return ret;
-}
-
/* Actually sends the packet. This flushes the connections outgoing data
buffer. If data is sent directly to the network this returns the bytes
written, if error occured this returns -1 and if the data could not
later time. If `force_send' is TRUE this attempts to write the data
directly to the network, if FALSE, this returns -2. */
-int silc_packet_send(SilcSocketConnection sock, int force_send)
+int silc_packet_send(SilcSocketConnection sock, bool force_send)
{
+ SILC_LOG_DEBUG(("Sending packet to %s:%d [%s]", sock->hostname,
+ sock->port,
+ (sock->type == SILC_SOCKET_TYPE_UNKNOWN ? "Unknown" :
+ sock->type == SILC_SOCKET_TYPE_CLIENT ? "Client" :
+ sock->type == SILC_SOCKET_TYPE_SERVER ? "Server" :
+ "Router")));
+
/* Send now if forced to do so */
if (force_send == TRUE) {
int ret;
SILC_LOG_DEBUG(("Forcing packet send, packet sent immediately"));
/* Write to network */
- ret = silc_packet_write(sock->sock, sock->outbuf);
+ ret = silc_socket_write(sock);
if (ret == -1) {
SILC_LOG_ERROR(("Error sending packet, dropped"));
cannot be used. */
void silc_packet_encrypt(SilcCipher cipher, SilcHmac hmac,
- SilcBuffer buffer, unsigned int len)
+ SilcBuffer buffer, uint32 len)
{
unsigned char mac[32];
+ uint32 mac_len;
/* Compute HMAC. This assumes that HMAC is created from the entire
data area thus this uses the length found in buffer, not the length
sent as argument. */
if (hmac) {
- silc_hmac_make(hmac, buffer->data, buffer->len, mac);
- silc_buffer_put_tail(buffer, mac, hmac->hash->hash->hash_len);
+ silc_hmac_init(hmac);
+ silc_hmac_update(hmac, buffer->data, buffer->len);
+ silc_hmac_final(hmac, mac, &mac_len);
+ silc_buffer_put_tail(buffer, mac, mac_len);
memset(mac, 0, sizeof(mac));
}
/* Pull the HMAC into the visible data area in the buffer */
if (hmac)
- silc_buffer_pull_tail(buffer, hmac->hash->hash->hash_len);
+ silc_buffer_pull_tail(buffer, mac_len);
}
/* Assembles a new packet to be ready for send out. The buffer sent as
Packet construct is as follows (* = won't be encrypted):
- x bytes SILC Header
+ n bytes SILC Header
2 bytes Payload length (*)
1 byte Flags
1 byte Packet type
- 1 byte Source ID Type
2 bytes Source ID Length
- x bytes Source ID
- 1 byte Destination ID Type
2 bytes Destination ID Length
- x bytes Destination ID
+ 1 byte Source ID Type
+ n bytes Source ID
+ 1 byte Destination ID Type
+ n bytes Destination ID
1 - 16 bytes Padding
- x bytes Data payload
+ n bytes Data payload
All fields in the packet will be authenticated by MAC. The MAC is
- not computed here, it must be computed differently before encrypting
+ not computed here, it must be computed separately before encrypting
the packet.
*/
/* Get random padding */
#if 1
- for (i = 0; i < ctx->padlen; i++) tmppad[i] = silc_rng_get_byte(ctx->rng);
+ for (i = 0; i < ctx->padlen; i++) tmppad[i] = silc_rng_global_get_byte();
#else
/* XXX: For testing - to be removed */
memset(tmppad, 65, sizeof(tmppad));
outgoing buffer in SilcSocketConnection object. */
void silc_packet_send_prepare(SilcSocketConnection sock,
- unsigned int header_len,
- unsigned int padlen,
- unsigned int data_len)
+ uint32 header_len,
+ uint32 padlen,
+ uint32 data_len)
{
int totlen, oldlen;
/* Allocate new buffer. This is done only once per connection. */
SILC_LOG_DEBUG(("Allocating outgoing data buffer"));
- sock->outbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE);
+ if (totlen > SILC_PACKET_DEFAULT_SIZE)
+ sock->outbuf = silc_buffer_alloc(totlen);
+ else
+ sock->outbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE);
silc_buffer_pull_tail(sock->outbuf, totlen);
silc_buffer_pull(sock->outbuf, header_len + padlen);
} else {
/* There is some pending data in the buffer. */
/* Allocate more space if needed */
- if ((sock->outbuf->end - sock->outbuf->tail) < data_len) {
+ if ((sock->outbuf->end - sock->outbuf->tail) <
+ (totlen + 20)) {
SILC_LOG_DEBUG(("Reallocating outgoing data buffer"));
sock->outbuf = silc_buffer_realloc(sock->outbuf,
- sock->outbuf->truelen + totlen);
+ sock->outbuf->truelen +
+ (totlen * 2));
}
oldlen = sock->outbuf->len;
} else {
/* Buffer is free for use */
silc_buffer_clear(sock->outbuf);
+
+ /* Allocate more space if needed */
+ if ((sock->outbuf->end - sock->outbuf->tail) < (totlen + 20)) {
+ SILC_LOG_DEBUG(("Reallocating outgoing data buffer"));
+ sock->outbuf = silc_buffer_realloc(sock->outbuf,
+ sock->outbuf->truelen +
+ (totlen * 2));
+ }
+
silc_buffer_pull_tail(sock->outbuf, totlen);
silc_buffer_pull(sock->outbuf, header_len + padlen);
}
******************************************************************************/
-/* Reads data from the socket connection into the incoming data buffer.
- However, this does not parse the packet, it only reads some amount from
- the network. If there are more data available that can be read at a time
- the rest of the data will be read later with a timeout and only after
- that the packet is ready to be parsed.
-
- The destination buffer sent as argument must be initialized before
- calling this function, and, the data section and the start of the tail
- section must be same. Ie. we add the read data to the tail section of
- the buffer hence the data section is the start of the buffer.
-
- This returns amount of bytes read or -1 on error or -2 on case where
- all of the data could not be read at once. */
-
-int silc_packet_read(int sock, SilcBuffer dest)
-{
- int len = 0;
- unsigned char buf[SILC_PACKET_READ_SIZE];
-
- SILC_LOG_DEBUG(("Reading data from socket %d", sock));
-
- /* Read the data from the socket. */
- len = read(sock, buf, sizeof(buf));
- if (len < 0) {
- if (errno == EAGAIN || errno == EINTR) {
- SILC_LOG_DEBUG(("Could not read immediately, will do it later"));
- return -2;
- }
- SILC_LOG_ERROR(("Cannot read from socket: %d", strerror(errno)));
- return -1;
- }
-
- if (!len)
- return 0;
-
- /* Insert the data to the buffer. If the data doesn't fit to the
- buffer space is allocated for the buffer. */
- /* XXX: This may actually be bad thing as if there is pending data in
- the buffer they will be lost! */
- if (dest) {
-
- /* If the data doesn't fit we just have to allocate a whole new
- data area */
- if (dest->truelen <= len) {
-
- /* Free the old buffer */
- memset(dest->head, 'F', dest->truelen);
- silc_free(dest->head);
-
- /* Allocate new data area */
- len += SILC_PACKET_DEFAULT_SIZE;
- dest->data = silc_calloc(len, sizeof(char));
- dest->truelen = len;
- dest->len = 0;
- dest->head = dest->data;
- dest->data = dest->data;
- dest->tail = dest->data;
- dest->end = dest->data + dest->truelen;
- len -= SILC_PACKET_DEFAULT_SIZE;
- }
-
- silc_buffer_put_tail(dest, buf, len);
- silc_buffer_pull_tail(dest, len);
- }
-
- SILC_LOG_DEBUG(("Read %d bytes", len));
-
- return len;
-}
-
/* Processes the received data. This checks the received data and
calls parser callback that handles the actual packet decryption
and parsing. If more than one packet was received this calls the
return;
if (hmac)
- mac_len = hmac->hash->hash->hash_len;
+ mac_len = silc_hmac_len(hmac);
/* Parse the packets from the data */
count = 0;
if (packetlen < SILC_PACKET_MIN_LEN) {
SILC_LOG_DEBUG(("Received invalid packet, dropped"));
+ silc_buffer_clear(sock->inbuf);
return;
}
parse_ctx->packet = silc_packet_context_alloc();
parse_ctx->packet->buffer = silc_buffer_alloc(paddedlen + mac_len);
parse_ctx->sock = sock;
- parse_ctx->cipher = cipher;
- parse_ctx->hmac = hmac;
parse_ctx->context = context;
silc_buffer_pull_tail(parse_ctx->packet->buffer,
silc_buffer_pull(sock->inbuf, mac_len);
}
+ SILC_LOG_DEBUG(("Clearing inbound buffer"));
silc_buffer_clear(sock->inbuf);
}
{
int ret;
- /* Allocate the incoming data buffer if not done already. */
- if (!sock->inbuf)
- sock->inbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE);
-
- /* Read some data from connection */
- ret = silc_packet_read(sock->sock, sock->inbuf);
+ SILC_LOG_DEBUG(("Receiving packet from %s:%d [%s]", sock->hostname,
+ sock->port,
+ (sock->type == SILC_SOCKET_TYPE_UNKNOWN ? "Unknown" :
+ sock->type == SILC_SOCKET_TYPE_CLIENT ? "Client" :
+ sock->type == SILC_SOCKET_TYPE_SERVER ? "Server" :
+ "Router")));
- /* Error */
- if (ret == -1) {
- SILC_LOG_ERROR(("Error reading packet, dropped"));
- }
+ /* Read some data from connection */
+ ret = silc_socket_read(sock);
return ret;
}
/* Check MAC */
if (hmac) {
unsigned char mac[32];
+ uint32 mac_len;
SILC_LOG_DEBUG(("Verifying MAC"));
/* Compute HMAC of packet */
memset(mac, 0, sizeof(mac));
- silc_hmac_make(hmac, buffer->data, buffer->len, mac);
+ silc_hmac_init(hmac);
+ silc_hmac_update(hmac, buffer->data, buffer->len);
+ silc_hmac_final(hmac, mac, &mac_len);
/* Compare the HMAC's (buffer->tail has the packet's HMAC) */
- if (memcmp(mac, buffer->tail, hmac->hash->hash->hash_len)) {
- SILC_LOG_DEBUG(("MAC failed"));
+ if (memcmp(mac, buffer->tail, mac_len)) {
+ SILC_LOG_ERROR(("MAC failed"));
return FALSE;
}
/* Pull MAC from packet before decryption */
if (hmac) {
- if ((buffer->len - hmac->hash->hash->hash_len) > SILC_PACKET_MIN_LEN) {
- silc_buffer_push_tail(buffer, hmac->hash->hash->hash_len);
+ if ((buffer->len - silc_hmac_len(hmac)) > SILC_PACKET_MIN_LEN) {
+ silc_buffer_push_tail(buffer, silc_hmac_len(hmac));
} else {
SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped"));
return FALSE;
{
/* Decrypt rest of the header plus padding */
if (cipher) {
- unsigned short truelen, len1, len2, padlen;
+ uint16 truelen, len1, len2, padlen;
/* Pull MAC from packet before decryption */
if (hmac) {
- if ((buffer->len - hmac->hash->hash->hash_len) > SILC_PACKET_MIN_LEN) {
- silc_buffer_push_tail(buffer, hmac->hash->hash->hash_len);
+ if ((buffer->len - silc_hmac_len(hmac)) > SILC_PACKET_MIN_LEN) {
+ silc_buffer_push_tail(buffer, silc_hmac_len(hmac));
} else {
SILC_LOG_DEBUG(("Bad MAC length in packet, packet dropped"));
return FALSE;
len1 = (truelen + padlen) - (SILC_PACKET_MIN_HEADER_LEN - 2);
silc_buffer_pull(buffer, SILC_PACKET_MIN_HEADER_LEN - 2);
+ if (len1 - 2 > buffer->len) {
+ SILC_LOG_DEBUG(("Garbage in header of packet, bad packet length, "
+ "packet dropped"));
+ return FALSE;
+ }
+
cipher->cipher->decrypt(cipher->context, buffer->data + 2,
buffer->data + 2, len1 - 2,
cipher->iv);
the HMAC of the packet. If any other special or customized decryption
processing is required this function cannot be used. This returns
-1 on error, 0 when packet is normal packet and 1 when the packet
- is special and requires special processing. */
+ is special and requires special processing.
+
+ The `check_packet' is a callback funtion that this function will
+ call. The callback relates to the checking whether the packet is
+ normal packet or special packet and how it should be processed. If
+ the callback return TRUE the packet is normal and FALSE if the packet
+ is special and requires special procesing. */
int silc_packet_decrypt(SilcCipher cipher, SilcHmac hmac,
- SilcBuffer buffer, SilcPacketContext *packet)
+ SilcBuffer buffer, SilcPacketContext *packet,
+ SilcPacketCheckDecrypt check_packet,
+ void *context)
{
+ int check;
/* Decrypt start of the packet header */
if (cipher)
- cipher->cipher->decrypt(cipher->context, buffer->data + 2,
- buffer->data + 2, SILC_PACKET_MIN_HEADER_LEN - 2,
- cipher->iv);
+ silc_cipher_decrypt(cipher, buffer->data + 2, buffer->data + 2,
+ SILC_PACKET_MIN_HEADER_LEN - 2, cipher->iv);
+
+ /* Do packet checking, whether the packet is normal or special */
+ check = check_packet((SilcPacketType)buffer->data[3], buffer,
+ packet, context);
/* If the packet type is not any special type lets decrypt rest
of the packet here. */
- if ((buffer->data[3] == SILC_PACKET_PRIVATE_MESSAGE &&
- !(buffer->data[2] & SILC_PACKET_FLAG_PRIVMSG_KEY)) ||
- buffer->data[3] != SILC_PACKET_CHANNEL_MESSAGE) {
-
+ if (check == TRUE) {
/* Normal packet, decrypt rest of the packet */
if (!silc_packet_decrypt_rest(cipher, hmac, buffer))
return -1;
/* Check MAC */
if (!silc_packet_check_mac(hmac, buffer))
- return FALSE;
+ return -1;
return 0;
} else {
/* Check MAC */
if (!silc_packet_check_mac(hmac, buffer))
- return FALSE;
+ return -1;
return 1;
}
/* Allocate packet context */
-SilcPacketContext *silc_packet_context_alloc()
+SilcPacketContext *silc_packet_context_alloc(void)
{
SilcPacketContext *ctx = silc_calloc(1, sizeof(*ctx));
ctx->users++;
SilcPacketContext *silc_packet_context_dup(SilcPacketContext *ctx)
{
ctx->users++;
- SILC_LOG_DEBUG(("Packet context %p rfcnt %d->%d", ctx, ctx->users - 1,
+ SILC_LOG_DEBUG(("Packet context %p refcnt %d->%d", ctx, ctx->users - 1,
ctx->users));
return ctx;
}
void silc_packet_context_free(SilcPacketContext *ctx)
{
ctx->users--;
- SILC_LOG_DEBUG(("Packet context %p rfcnt %d->%d", ctx, ctx->users + 1,
+ SILC_LOG_DEBUG(("Packet context %p refcnt %d->%d", ctx, ctx->users + 1,
ctx->users));
if (ctx->users < 1)
{