Code auditing weekend results and fixes committing.
[silc.git] / apps / silcd / server.c
index 047cca050b305d3cd994213bf0416aae16227ad8..26b214fd3aef7adf1900caa2cd8f0204d25d7be6 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
 
-  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
@@ -71,7 +71,9 @@ int silc_server_alloc(SilcServer *new_server)
 void silc_server_free(SilcServer server)
 {
   if (server) {
+#ifdef SILC_SIM
     SilcSimContext *sim;
+#endif
 
     if (server->local_list)
       silc_free(server->local_list);
@@ -450,7 +452,7 @@ SILC_TASK_CALLBACK(silc_server_connect_router)
      protocol. */
   silc_socket_alloc(sock, SILC_SOCKET_TYPE_UNKNOWN, NULL, &newsocket);
   server->sockets[sock] = newsocket;
-  newsocket->hostname = sconn->remote_host;
+  newsocket->hostname = strdup(sconn->remote_host);
   newsocket->port = sconn->remote_port;
   sconn->sock = newsocket;
 
@@ -770,9 +772,12 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection)
 
   SILC_LOG_DEBUG(("Accepting new connection"));
 
+  server->stat.conn_attempts++;
+
   sock = silc_net_accept_connection(server->sock);
   if (sock < 0) {
     SILC_LOG_ERROR(("Could not accept new connection: %s", strerror(errno)));
+    server->stat.conn_failures++;
     return;
   }
 
@@ -783,6 +788,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection)
       /*silc_server_send_notify("Server is full, trying to redirect..."); */
     } else {
       SILC_LOG_ERROR(("Refusing connection, server is full"));
+      server->stat.conn_failures++;
     }
     return;
   }
@@ -804,6 +810,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection)
   if ((server->params->require_reverse_mapping && !newsocket->hostname) ||
       !newsocket->ip) {
     SILC_LOG_ERROR(("IP/DNS lookup failed"));
+    server->stat.conn_failures++;
     return;
   }
   if (!newsocket->hostname)
@@ -824,6 +831,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection)
      protocol but will not start it yet. The connector will be the
      initiator of the protocol thus we will wait for initiation from 
      there before we start the protocol. */
+  server->stat.auth_attempts++;
   silc_protocol_alloc(SILC_PROTOCOL_SERVER_KEY_EXCHANGE, 
                      &newsocket->protocol, proto_ctx, 
                      silc_server_accept_new_connection_second);
@@ -879,6 +887,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_second)
       sock->protocol = NULL;
     silc_server_disconnect_remote(server, sock, "Server closed connection: "
                                  "Key exchange failed");
+    server->stat.auth_failures++;
     return;
   }
 
@@ -947,6 +956,7 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
       sock->protocol = NULL;
     silc_server_disconnect_remote(server, sock, "Server closed connection: "
                                  "Authentication failed");
+    server->stat.auth_failures++;
     return;
   }
 
@@ -971,6 +981,12 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
        break;
       }
 
+      /* Statistics */
+      server->stat.my_clients++;
+      server->stat.clients++;
+      if (server->server_type == SILC_ROUTER)
+       server->stat.cell_clients++;
+
       id_entry = (void *)client;
       break;
     }
@@ -1002,6 +1018,13 @@ SILC_TASK_CALLBACK(silc_server_accept_new_connection_final)
        break;
       }
 
+      /* Statistics */
+      if (sock->type == SILC_SOCKET_TYPE_SERVER)
+       server->stat.my_servers++;
+      else
+       server->stat.my_routers++;
+      server->stat.servers++;
+
       id_entry = (void *)new_server;
       
       /* There is connection to other server now, if it is router then
@@ -1056,11 +1079,15 @@ SILC_TASK_CALLBACK(silc_server_packet_process)
   SilcHmac hmac = NULL;
   int ret;
 
+  if (!sock)
+    return;
+
   SILC_LOG_DEBUG(("Processing packet"));
 
   /* Packet sending */
+
   if (type == SILC_TASK_WRITE) {
-    SILC_LOG_DEBUG(("Writing data to connection"));
+    server->stat.packets_sent++;
 
     if (sock->outbuf->data - sock->outbuf->head)
       silc_buffer_push(sock->outbuf, sock->outbuf->data - sock->outbuf->head);
@@ -1071,6 +1098,9 @@ SILC_TASK_CALLBACK(silc_server_packet_process)
        it later. */
     if (ret == -2)
       return;
+
+    if (ret == -1)
+      return;
     
     /* The packet has been sent and now it is time to set the connection
        back to only for input. When there is again some outgoing data 
@@ -1084,7 +1114,6 @@ SILC_TASK_CALLBACK(silc_server_packet_process)
   }
 
   /* Packet receiving */
-  SILC_LOG_DEBUG(("Reading data from connection"));
 
   /* Read some data from connection */
   ret = silc_packet_receive(sock);
@@ -1106,6 +1135,16 @@ SILC_TASK_CALLBACK(silc_server_packet_process)
       
     SILC_LOG_DEBUG(("Premature EOF from connection %d", sock->sock));
 
+    /* If the closed connection was our primary router connection the
+       start re-connecting phase. */
+    if (!server->standalone && server->server_type == SILC_SERVER && 
+       sock == server->router->connection)
+      silc_task_register(server->timeout_queue, 0, 
+                        silc_server_connect_to_router,
+                        context, 0, 500000,
+                        SILC_TASK_TIMEOUT,
+                        SILC_TASK_PRI_NORMAL);
+
     if (sock->user_data)
       silc_server_free_sock_user_data(server, sock);
     silc_server_close_connection(server, sock);
@@ -1119,6 +1158,8 @@ SILC_TASK_CALLBACK(silc_server_packet_process)
     return;
   }
 
+  server->stat.packets_received++;
+
   /* Get keys and stuff from ID entry */
   idata = (SilcIDListData)sock->user_data;
   if (idata) {
@@ -1166,12 +1207,15 @@ SILC_TASK_CALLBACK(silc_server_packet_parse_real)
   if (server->server_type == SILC_ROUTER) {
     /* Route the packet if it is not destined to us. Other ID types but
        server are handled separately after processing them. */
-    if (packet->dst_id_type == SILC_ID_SERVER &&
+    if (packet->dst_id_type == SILC_ID_SERVER && 
        sock->type != SILC_SOCKET_TYPE_CLIENT &&
        SILC_ID_SERVER_COMPARE(packet->dst_id, server->id_string)) {
       
       /* Route the packet to fastest route for the destination ID */
-      void *id = silc_id_str2id(packet->dst_id, packet->dst_id_type);
+      void *id = silc_id_str2id(packet->dst_id, packet->dst_id_len, 
+                               packet->dst_id_type);
+      if (!id)
+       goto out;
       silc_server_packet_route(server,
                               silc_server_route_get(server, id,
                                                     packet->dst_id_type),
@@ -1294,7 +1338,7 @@ void silc_server_packet_parse_type(SilcServer server,
   case SILC_PACKET_CHANNEL_MESSAGE:
     /*
      * Received channel message. Channel messages are special packets
-     * (although probably most common ones) hence they are handled
+     * (although probably most common ones) thus they are handled
      * specially.
      */
     SILC_LOG_DEBUG(("Channel Message packet"));
@@ -1317,7 +1361,8 @@ void silc_server_packet_parse_type(SilcServer server,
      */
   case SILC_PACKET_COMMAND:
     /*
-     * Recived command. Allocate command context and execute the command.
+     * Recived command. Processes the command request and allocates the
+     * command context and calls the command.
      */
     SILC_LOG_DEBUG(("Command packet"));
     silc_server_command_process(server, sock, packet);
@@ -1325,10 +1370,9 @@ void silc_server_packet_parse_type(SilcServer server,
 
   case SILC_PACKET_COMMAND_REPLY:
     /*
-     * Received command reply packet. Servers never send commands thus
-     * they don't receive command reply packets either, except in cases
-     * where server has forwarded command packet coming from client. 
-     * This must be the case here or we will ignore the packet.
+     * Received command reply packet. Received command reply to command. It
+     * may be reply to command sent by us or reply to command sent by client
+     * that we've routed further.
      */
     SILC_LOG_DEBUG(("Command Reply packet"));
     silc_server_command_reply(server, sock, packet);
@@ -1348,7 +1392,7 @@ void silc_server_packet_parse_type(SilcServer server,
 
   case SILC_PACKET_PRIVATE_MESSAGE_KEY:
     /*
-     * XXX
+     * Private message key packet.
      */
     break;
 
@@ -1389,7 +1433,10 @@ void silc_server_packet_parse_type(SilcServer server,
 
       proto_ctx->packet = silc_packet_context_dup(packet);
       proto_ctx->dest_id_type = packet->src_id_type;
-      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
+      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+                                         packet->src_id_type);
+      if (!proto_ctx->dest_id)
+       break;
 
       /* Let the protocol handle the packet */
       sock->protocol->execute(server->timeout_queue, 0, 
@@ -1414,7 +1461,10 @@ void silc_server_packet_parse_type(SilcServer server,
 
       proto_ctx->packet = silc_packet_context_dup(packet);
       proto_ctx->dest_id_type = packet->src_id_type;
-      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_type);
+      proto_ctx->dest_id = silc_id_str2id(packet->src_id, packet->src_id_len,
+                                         packet->src_id_type);
+      if (!proto_ctx->dest_id)
+       break;
 
       /* Let the protocol handle the packet */
       sock->protocol->execute(server->timeout_queue, 0, 
@@ -1427,9 +1477,12 @@ void silc_server_packet_parse_type(SilcServer server,
     break;
 
   case SILC_PACKET_CONNECTION_AUTH_REQUEST:
-    /* If we receive this packet we will send to the other end information
-       about our mandatory authentication method for the connection. 
-       This packet maybe received at any time. */
+    /*
+     * Connection authentication request packet. When we receive this packet
+     * we will send to the other end information about our mandatory
+     * authentication method for the connection. This packet maybe received
+     * at any time. 
+     */
     SILC_LOG_DEBUG(("Connection authentication request packet"));
     break;
 
@@ -1540,6 +1593,7 @@ void silc_server_packet_parse_type(SilcServer server,
      * Received remove ID Packet. 
      */
     SILC_LOG_DEBUG(("Remove ID packet"));
+    silc_server_remove_id(server, sock, packet);
     break;
 
   case SILC_PACKET_REMOVE_CHANNEL_USER:
@@ -1551,6 +1605,14 @@ void silc_server_packet_parse_type(SilcServer server,
     silc_server_remove_channel_user(server, sock, packet);
     break;
 
+  case SILC_PACKET_SET_MODE:
+    /*
+     * Received packet to set the mode of channel or client's channel mode.
+     */
+    SILC_LOG_DEBUG(("Set Mode packet"));
+    silc_server_set_mode(server, sock, packet);
+    break;
+
   default:
     SILC_LOG_ERROR(("Incorrect packet type %d, packet dropped", type));
     break;
@@ -1563,7 +1625,6 @@ void silc_server_packet_parse_type(SilcServer server,
 void silc_server_close_connection(SilcServer server,
                                  SilcSocketConnection sock)
 {
-
   SILC_LOG_DEBUG(("Closing connection %d", sock->sock));
 
   /* We won't listen for this connection anymore */
@@ -1589,6 +1650,9 @@ void silc_server_disconnect_remote(SilcServer server,
   va_list ap;
   unsigned char buf[4096];
 
+  if (!sock)
+    return;
+
   memset(buf, 0, sizeof(buf));
   va_start(ap, fmt);
   vsprintf(buf, fmt, ap);
@@ -1606,9 +1670,9 @@ void silc_server_disconnect_remote(SilcServer server,
   silc_server_close_connection(server, sock);
 }
 
-/* Free's user_data pointer from socket connection object. As this 
-   pointer maybe anything we wil switch here to find the correct
-   data type and free it the way it needs to be free'd. */
+/* Free's user_data pointer from socket connection object. This also sends
+   appropriate notify packets to the network to inform about leaving
+   entities. */
 
 void silc_server_free_sock_user_data(SilcServer server, 
                                     SilcSocketConnection sock)
@@ -1625,18 +1689,43 @@ void silc_server_free_sock_user_data(SilcServer server,
 
       /* XXX must take some info to history before freeing */
 
+      /* Send REMOVE_ID packet to routers. */
+      if (!server->standalone && server->router)
+       silc_server_send_remove_id(server, server->router->connection,
+                                  server->server_type == SILC_SERVER ?
+                                  FALSE : TRUE, user_data->id, 
+                                  SILC_ID_CLIENT_LEN, SILC_ID_CLIENT);
+
       /* Free the client entry and everything in it */
       silc_idlist_del_data(user_data);
       silc_idlist_del_client(server->local_list, user_data);
+      server->stat.my_clients--;
+      server->stat.clients--;
+      if (server->server_type == SILC_ROUTER)
+       server->stat.cell_clients--;
       break;
     }
   case SILC_SOCKET_TYPE_SERVER:
   case SILC_SOCKET_TYPE_ROUTER:
     {
+      SilcServerEntry user_data = (SilcServerEntry)sock->user_data;
+
+      /* Send REMOVE_ID packet to routers. */
+      if (!server->standalone && server->router)
+       silc_server_send_remove_id(server, server->router->connection,
+                                  server->server_type == SILC_SERVER ?
+                                  FALSE : TRUE, user_data->id, 
+                                  SILC_ID_CLIENT_LEN, SILC_ID_CLIENT);
 
+      /* Free the server entry */
+      silc_idlist_del_data(user_data);
+      silc_idlist_del_server(server->local_list, user_data);
+      server->stat.my_servers--;
+      server->stat.servers--;
+      if (server->server_type == SILC_ROUTER)
+       server->stat.cell_servers--;
       break;
     }
-    break;
   default:
     {
       SilcUnknownEntry user_data = (SilcUnknownEntry)sock->user_data;
@@ -1650,9 +1739,41 @@ void silc_server_free_sock_user_data(SilcServer server,
   sock->user_data = NULL;
 }
 
-/* Removes client from all channels it has joined. This is used when
-   client connection is disconnected. If the client on a channel
-   is last, the channel is removed as well. */
+/* Checks whether given channel has global users.  If it does this returns
+   TRUE and FALSE if there is only locally connected clients on the channel. */
+
+int silc_server_channel_has_global(SilcChannelEntry channel)
+{
+  SilcChannelClientEntry chl;
+
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    if (chl->client->router)
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+/* Checks whether given channel has locally connected users.  If it does this
+   returns TRUE and FALSE if there is not one locally connected client. */
+
+int silc_server_channel_has_local(SilcChannelEntry channel)
+{
+  SilcChannelClientEntry chl;
+
+  silc_list_start(channel->user_list);
+  while ((chl = silc_list_get(channel->user_list)) != SILC_LIST_END) {
+    if (!chl->client->router)
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+/* Removes client from all channels it has joined. This is used when client
+   connection is disconnected. If the client on a channel is last, the
+   channel is removed as well. This sends the SIGNOFF notify types. */
 
 void silc_server_remove_from_channels(SilcServer server, 
                                      SilcSocketConnection sock,
@@ -1688,21 +1809,23 @@ void silc_server_remove_from_channels(SilcServer server,
         channel globally from SILC network, in this case we will
         notify that this client has left the channel. */
       if (channel->global_users)
-       silc_server_send_notify_to_channel(server, channel, TRUE,
+       silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                           SILC_NOTIFY_TYPE_SIGNOFF, 1,
                                           clidp->data, clidp->len);
       
       silc_idlist_del_channel(server->local_list, channel);
+      server->stat.my_channels--;
       continue;
     }
 
     /* Remove from list */
     silc_list_del(channel->user_list, chl);
     silc_free(chl);
+    server->stat.my_chanclients--;
 
     /* Send notify to channel about client leaving SILC and thus
        the entire channel. */
-    silc_server_send_notify_to_channel(server, channel, TRUE,
+    silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                       SILC_NOTIFY_TYPE_SIGNOFF, 1,
                                       clidp->data, clidp->len);
     silc_buffer_free(chidp);
@@ -1746,25 +1869,42 @@ int silc_server_remove_from_one_channel(SilcServer server,
     /* If this client is last one on the channel the channel
        is removed all together. */
     if (silc_list_count(channel->user_list) < 2) {
-      /* Notify about leaving client if this channel has global users,
-        ie. the channel is not created locally. */
+      /* Notify about leaving client if this channel has global users. */
       if (notify && channel->global_users)
-       silc_server_send_notify_to_channel(server, channel, TRUE,
+       silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                           SILC_NOTIFY_TYPE_LEAVE, 1,
                                           clidp->data, clidp->len);
       
       silc_idlist_del_channel(server->local_list, channel);
       silc_buffer_free(clidp);
+      server->stat.my_channels--;
       return FALSE;
     }
 
     /* Remove from list */
     silc_list_del(channel->user_list, chl);
     silc_free(chl);
+    server->stat.my_chanclients--;
+
+    /* If there is no global users on the channel anymore mark the channel
+       as local channel. */
+    if (server->server_type == SILC_SERVER &&
+       !silc_server_channel_has_global(channel))
+      channel->global_users = FALSE;
+
+    /* If tehre is not at least one local user on the channel then we don't
+       need the channel entry anymore, we can remove it safely. */
+    if (server->server_type == SILC_SERVER &&
+       !silc_server_channel_has_local(channel)) {
+      silc_idlist_del_channel(server->local_list, channel);
+      silc_buffer_free(clidp);
+      server->stat.my_channels--;
+      return FALSE;
+    }
 
     /* Send notify to channel about client leaving the channel */
     if (notify)
-      silc_server_send_notify_to_channel(server, channel, TRUE,
+      silc_server_send_notify_to_channel(server, NULL, channel, FALSE,
                                         SILC_NOTIFY_TYPE_LEAVE, 1,
                                         clidp->data, clidp->len);
     break;
@@ -1801,10 +1941,16 @@ int silc_server_client_on_channel(SilcClientEntry client,
 
 SILC_TASK_CALLBACK(silc_server_timeout_remote)
 {
-  SilcServerConnection sconn = (SilcServerConnection)context;
-  SilcSocketConnection sock = sconn->server->sockets[fd];
+  SilcServer server = (SilcServer)context;
+  SilcSocketConnection sock = server->sockets[fd];
+
+  if (!sock)
+    return;
 
-  silc_server_disconnect_remote(sconn->server, sock, 
+  if (sock->user_data)
+    silc_server_free_sock_user_data(server, sock);
+
+  silc_server_disconnect_remote(server, sock, 
                                "Server closed connection: "
                                "Connection timeout");
 }
@@ -1853,6 +1999,8 @@ SilcChannelEntry silc_server_create_new_channel(SilcServer server,
                                 channel_name, entry->id, SILC_ID_CHANNEL_LEN);
   }
 
+  server->stat.my_channels++;
+
   return entry;
 }
 
@@ -1868,12 +2016,15 @@ void silc_server_create_channel_key(SilcServer server,
   unsigned char channel_key[32];
   unsigned int len;
 
+  if (!channel->channel_key)
+    silc_cipher_alloc("twofish", &channel->channel_key);
+
   if (key_len)
     len = key_len;
   else if (channel->key_len)
     len = channel->key_len / 8;
   else
-    len = 32;
+    len = sizeof(channel_key);
 
   /* Create channel key */
   for (i = 0; i < len; i++) channel_key[i] = silc_rng_get_byte(server->rng);
@@ -1894,3 +2045,84 @@ void silc_server_create_channel_key(SilcServer server,
   memcpy(channel->key, channel_key, len);
   memset(channel_key, 0, sizeof(channel_key));
 }
+
+/* Saves the channel key found in the encoded `key_payload' buffer. This 
+   function is used when we receive Channel Key Payload and also when we're
+   processing JOIN command reply. Returns entry to the channel. */
+
+SilcChannelEntry silc_server_save_channel_key(SilcServer server,
+                                             SilcBuffer key_payload,
+                                             SilcChannelEntry channel)
+{
+  SilcChannelKeyPayload payload = NULL;
+  SilcChannelID *id = NULL;
+  unsigned char *tmp;
+  unsigned int tmp_len;
+  char *cipher;
+
+  /* Decode channel key payload */
+  payload = silc_channel_key_payload_parse(key_payload);
+  if (!payload) {
+    SILC_LOG_ERROR(("Bad channel key payload, dropped"));
+    channel = NULL;
+    goto out;
+  }
+
+  /* Get the channel entry */
+  if (!channel) {
+
+    /* Get channel ID */
+    tmp = silc_channel_key_get_id(payload, &tmp_len);
+    id = silc_id_str2id(tmp, tmp_len, SILC_ID_CHANNEL);
+    if (!id) {
+      channel = NULL;
+      goto out;
+    }
+
+    channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
+    if (!channel) {
+      SILC_LOG_ERROR(("Received key for non-existent channel"));
+      goto out;
+    }
+  }
+
+  tmp = silc_channel_key_get_key(payload, &tmp_len);
+  if (!tmp) {
+    channel = NULL;
+    goto out;
+  }
+
+  cipher = silc_channel_key_get_cipher(payload, NULL);;
+  if (!cipher) {
+    channel = NULL;
+    goto out;
+  }
+
+  /* Remove old key if exists */
+  if (channel->key) {
+    memset(channel->key, 0, channel->key_len / 8);
+    silc_free(channel->key);
+    silc_cipher_free(channel->channel_key);
+  }
+
+  /* Create new cipher */
+  if (!silc_cipher_alloc(cipher, &channel->channel_key)) {
+    channel = NULL;
+    goto out;
+  }
+
+  /* Save the key */
+  channel->key_len = tmp_len * 8;
+  channel->key = silc_calloc(tmp_len, sizeof(unsigned char));
+  memcpy(channel->key, tmp, tmp_len);
+  channel->channel_key->cipher->set_key(channel->channel_key->context, 
+                                       tmp, tmp_len);
+
+ out:
+  if (id)
+    silc_free(id);
+  if (payload)
+    silc_channel_key_payload_free(payload);
+
+  return channel;
+}