updates.
[silc.git] / apps / silcd / server_util.c
index 7e83c5fc1947e8662d67a7441daf4d18f6a5d14f..2fe705f32b5c10064f8f541f15207b2a56552e49 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2001 Pekka Riikonen
+  Copyright (C) 1997 - 2002 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
@@ -21,6 +21,8 @@
 #include "serverincludes.h"
 #include "server_internal.h"
 
+extern char *server_version;
+
 /* Removes the client from channels and possibly removes the channels
    as well.  After removing those channels that exist, their channel
    keys are regnerated. This is called only by the function
@@ -46,7 +48,7 @@ static void silc_server_remove_clients_channels(SilcServer server,
   /* Remove the client from all channels. The client is removed from
      the channels' user list. */
   silc_hash_table_list(client->channels, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&chl)) {
     channel = chl->channel;
 
     /* Remove channel from client's channel list */
@@ -101,7 +103,7 @@ static void silc_server_remove_clients_channels(SilcServer server,
        channel->disabled = TRUE;
 
        silc_hash_table_list(channel->user_list, &htl2);
-       while (silc_hash_table_get(&htl2, NULL, (void *)&chl2)) {
+       while (silc_hash_table_get(&htl2, NULL, (void **)&chl2)) {
          silc_hash_table_del(chl2->client->channels, channel);
          silc_hash_table_del(channel->user_list, chl2->client);
          channel->user_count--;
@@ -216,7 +218,7 @@ bool silc_server_remove_clients_by_server(SilcServer server,
 
        /* Update statistics */
        server->stat.clients--;
-       if (server->server_type == SILC_ROUTER)
+       if (server->stat.cell_clients)
          server->stat.cell_clients--;
        SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
        SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
@@ -280,7 +282,7 @@ bool silc_server_remove_clients_by_server(SilcServer server,
 
        /* Update statistics */
        server->stat.clients--;
-       if (server->server_type == SILC_ROUTER)
+       if (server->stat.cell_clients)
          server->stat.cell_clients--;
        SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
        SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
@@ -343,7 +345,7 @@ bool silc_server_remove_clients_by_server(SilcServer server,
      this server's client(s) on the channel. As they left the channel we
      must re-generate the channel key. */
   silc_hash_table_list(channels, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&channel)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&channel)) {
     if (!silc_server_create_channel_key(server, channel, 0)) {
       silc_hash_table_list_reset(&htl);
       silc_hash_table_free(channels);
@@ -706,7 +708,7 @@ bool silc_server_channel_has_global(SilcChannelEntry channel)
   SilcHashTableList htl;
 
   silc_hash_table_list(channel->user_list, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&chl)) {
     if (chl->client->router) {
       silc_hash_table_list_reset(&htl);
       return TRUE;
@@ -726,7 +728,7 @@ bool silc_server_channel_has_local(SilcChannelEntry channel)
   SilcHashTableList htl;
 
   silc_hash_table_list(channel->user_list, &htl);
-  while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
+  while (silc_hash_table_get(&htl, NULL, (void **)&chl)) {
     if (!chl->client->router) {
       silc_hash_table_list_reset(&htl);
       return TRUE;
@@ -743,12 +745,14 @@ bool silc_server_channel_has_local(SilcChannelEntry channel)
    `client' which is faster than checking the user list from `channel'. */
 
 bool silc_server_client_on_channel(SilcClientEntry client,
-                                  SilcChannelEntry channel)
+                                  SilcChannelEntry channel,
+                                  SilcChannelClientEntry *chl)
 {
   if (!client || !channel)
     return FALSE;
 
-  return silc_hash_table_find(client->channels, channel, NULL, NULL);
+  return silc_hash_table_find(client->channels, channel, NULL, 
+                             (void **)chl);
 }
 
 /* Checks string for bad characters and returns TRUE if they are found. */
@@ -793,14 +797,464 @@ char *silc_server_name_modify_bad(const char *name, SilcUInt32 name_len)
 /* Find number of sockets by IP address indicated by `ip'. Returns 0 if
    socket connections with the IP address does not exist. */
 
-SilcUInt32 silc_server_num_sockets_by_ip(SilcServer server, const char *ip)
+SilcUInt32 silc_server_num_sockets_by_ip(SilcServer server, const char *ip,
+                                        SilcSocketType type)
+{
+  int i, count;
+
+  for (i = 0, count = 0; i < server->config->param.connections_max; i++) {
+    if (server->sockets[i] && !strcmp(server->sockets[i]->ip, ip) &&
+       server->sockets[i]->type == type)
+      count++;
+  }
+
+  return count;
+}
+
+/* Find number of sockets by IP address indicated by remote host, indicatd
+   by `ip' or `hostname', `port', and `type'.  Returns 0 if socket connections
+   does not exist. If `ip' is provided then `hostname' is ignored. */
+
+SilcUInt32 silc_server_num_sockets_by_remote(SilcServer server, 
+                                            const char *ip,
+                                            const char *hostname,
+                                            SilcUInt16 port,
+                                            SilcSocketType type)
 {
   int i, count;
 
+  if (!ip && !hostname)
+    return 0;
+
   for (i = 0, count = 0; i < server->config->param.connections_max; i++) {
-    if (server->sockets[i] && !strcmp(server->sockets[i]->ip, ip))
+    if (server->sockets[i] && 
+       ((ip && !strcmp(server->sockets[i]->ip, ip)) ||
+        (hostname && !strcmp(server->sockets[i]->hostname, hostname))) &&
+       server->sockets[i]->port == port &&
+       server->sockets[i]->type == type)
       count++;
   }
 
   return count;
 }
+
+/* Finds locally cached public key by the public key received in the SKE. 
+   If we have it locally cached then we trust it and will use it in the
+   authentication protocol.  Returns the locally cached public key or NULL
+   if we do not find the public key.  */
+
+SilcPublicKey silc_server_find_public_key(SilcServer server, 
+                                         SilcHashTable local_public_keys,
+                                         SilcPublicKey remote_public_key)
+{
+  SilcPublicKey cached_key;
+
+  SILC_LOG_DEBUG(("Find remote public key (%d keys in local cache)",
+                 silc_hash_table_count(local_public_keys)));
+
+  if (!silc_hash_table_find_ext(local_public_keys, remote_public_key,
+                               (void **)&cached_key, NULL, 
+                               silc_hash_public_key, NULL,
+                               silc_hash_public_key_compare, NULL)) {
+    SILC_LOG_ERROR(("Public key not found"));
+    return NULL;
+  }
+
+  SILC_LOG_DEBUG(("Found public key"));
+
+  return cached_key;
+}
+
+/* This returns the first public key from the table of public keys.  This
+   is used only in cases where single public key exists in the table and
+   we want to get a pointer to it.  For public key tables that has multiple
+   keys in it the silc_server_find_public_key must be used. */
+
+SilcPublicKey silc_server_get_public_key(SilcServer server,
+                                        SilcHashTable local_public_keys)
+{
+  SilcPublicKey cached_key;
+  SilcHashTableList htl;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  assert(silc_hash_table_count(local_public_keys) < 2);
+
+  silc_hash_table_list(local_public_keys, &htl);
+  if (!silc_hash_table_get(&htl, NULL, (void **)&cached_key))
+    return NULL;
+  silc_hash_table_list_reset(&htl);
+
+  return cached_key;
+}
+
+/* Check whether the connection `sock' is allowed to connect to us.  This
+   checks for example whether there is too much connections for this host,
+   and required version for the host etc. */
+
+bool silc_server_connection_allowed(SilcServer server, 
+                                   SilcSocketConnection sock,
+                                   SilcSocketType type,
+                                   SilcServerConfigConnParams *global,
+                                   SilcServerConfigConnParams *params,
+                                   SilcSKE ske)
+{
+  SilcUInt32 conn_number = (type == SILC_SOCKET_TYPE_CLIENT ?
+                           server->stat.my_clients :
+                           type == SILC_SOCKET_TYPE_SERVER ?
+                           server->stat.my_servers :
+                           server->stat.my_routers);
+  SilcUInt32 num_sockets, max_hosts, max_per_host;
+  SilcUInt32 r_protocol_version, l_protocol_version;
+  SilcUInt32 r_software_version, l_software_version;
+  char *r_vendor_version = NULL, *l_vendor_version;
+
+  /* Check version */
+
+  l_protocol_version = 
+    silc_version_to_num(params && params->version_protocol ? 
+                       params->version_protocol : 
+                       global->version_protocol);
+  l_software_version = 
+    silc_version_to_num(params && params->version_software ? 
+                       params->version_software : 
+                       global->version_software);
+  l_vendor_version = (params && params->version_software_vendor ? 
+                     params->version_software_vendor : 
+                     global->version_software_vendor);
+  
+  if (ske && silc_ske_parse_version(ske, &r_protocol_version, NULL,
+                                   &r_software_version, NULL,
+                                   &r_vendor_version)) {
+    sock->version = r_protocol_version;
+
+    /* Match protocol version */
+    if (l_protocol_version && r_protocol_version &&
+       r_protocol_version < l_protocol_version) {
+      SILC_LOG_INFO(("Connection %s (%s) is too old version",
+                    sock->hostname, sock->ip));
+      silc_server_disconnect_remote(server, sock, 
+                                   "Server closed connection: "
+                                   "You support too old protocol version");
+      return FALSE;
+    }
+
+    /* Math software version */
+    if (l_software_version && r_software_version &&
+       r_software_version < l_software_version) {
+      SILC_LOG_INFO(("Connection %s (%s) is too old version",
+                    sock->hostname, sock->ip));
+      silc_server_disconnect_remote(server, sock, 
+                                   "Server closed connection: "
+                                   "You support too old software version");
+      return FALSE;
+    }
+
+    /* Regex match vendor version */
+    if (l_vendor_version && r_vendor_version && 
+       !silc_string_match(l_vendor_version, r_vendor_version)) {
+      SILC_LOG_INFO(("Connection %s (%s) is unsupported version",
+                    sock->hostname, sock->ip));
+      silc_server_disconnect_remote(server, sock, 
+                                   "Server closed connection: "
+                                   "Your software is not supported");
+      return FALSE;
+    }
+  }
+  silc_free(r_vendor_version);
+
+  /* Check for maximum connections limit */
+
+  num_sockets = silc_server_num_sockets_by_ip(server, sock->ip, type);
+  max_hosts = (params ? params->connections_max : global->connections_max);
+  max_per_host = (params ? params->connections_max_per_host :
+                 global->connections_max_per_host);
+
+  if (max_hosts && conn_number >= max_hosts) {
+    SILC_LOG_INFO(("Server is full, closing %s (%s) connection",
+                  sock->hostname, sock->ip));
+    silc_server_disconnect_remote(server, sock, 
+                                 "Server closed connection: "
+                                 "Server is full, try again later");
+    return FALSE;
+  }
+
+  if (num_sockets >= max_per_host) {
+    SILC_LOG_INFO(("Too many connections from %s (%s), closing connection",
+                  sock->hostname, sock->ip));
+    silc_server_disconnect_remote(server, sock, 
+                                 "Server closed connection: "
+                                 "Too many connections from your host");
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+/* Checks that client has rights to add or remove channel modes. If any
+   of the checks fails FALSE is returned. */
+
+bool silc_server_check_cmode_rights(SilcServer server,
+                                   SilcChannelEntry channel,
+                                   SilcChannelClientEntry client,
+                                   SilcUInt32 mode)
+{
+  bool is_op = client->mode & SILC_CHANNEL_UMODE_CHANOP;
+  bool is_fo = client->mode & SILC_CHANNEL_UMODE_CHANFO;
+
+  /* Check whether has rights to change anything */
+  if (!is_op && !is_fo)
+    return FALSE;
+
+  /* Check whether has rights to change everything */
+  if (is_op && is_fo)
+    return TRUE;
+
+  /* We know that client is channel operator, check that they are not
+     changing anything that requires channel founder rights. Rest of the
+     modes are available automatically for channel operator. */
+
+  if (mode & SILC_CHANNEL_MODE_PRIVKEY) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_PRIVKEY))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PRIVKEY) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_PASSPHRASE))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_PASSPHRASE) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+
+  if (mode & SILC_CHANNEL_MODE_CIPHER) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_CIPHER))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_CIPHER) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_FOUNDER_AUTH) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_SILENCE_USERS) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_SILENCE_USERS))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_SILENCE_USERS) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  if (mode & SILC_CHANNEL_MODE_SILENCE_OPERS) {
+    if (!(channel->mode & SILC_CHANNEL_MODE_SILENCE_OPERS))
+      if (is_op && !is_fo)
+       return FALSE;
+  } else {
+    if (channel->mode & SILC_CHANNEL_MODE_SILENCE_OPERS) {
+      if (is_op && !is_fo)
+       return FALSE;
+    }
+  }
+  
+  return TRUE;
+}
+
+/* Check that the client has rights to change its user mode.  Returns
+   FALSE if setting some mode is not allowed. */
+
+bool silc_server_check_umode_rights(SilcServer server,
+                                   SilcClientEntry client,
+                                   SilcUInt32 mode)
+{
+  bool server_op = FALSE, router_op = FALSE;
+
+  if (mode & SILC_UMODE_SERVER_OPERATOR) {
+    /* Cannot set server operator mode (must use OPER command) */
+    if (!(client->mode & SILC_UMODE_SERVER_OPERATOR))
+      return FALSE;
+  } else {
+    /* Remove the server operator rights */
+    if (client->mode & SILC_UMODE_SERVER_OPERATOR)
+      server_op = TRUE;
+  }
+
+  if (mode & SILC_UMODE_ROUTER_OPERATOR) {
+    /* Cannot set router operator mode (must use SILCOPER command) */
+    if (!(client->mode & SILC_UMODE_ROUTER_OPERATOR))
+      return FALSE;
+  } else {
+    /* Remove the router operator rights */
+    if (client->mode & SILC_UMODE_ROUTER_OPERATOR)
+      router_op = TRUE;
+  }
+
+  if (server_op)
+    SILC_UMODE_STATS_UPDATE(server, SILC_UMODE_SERVER_OPERATOR);
+  if (router_op)
+    SILC_UMODE_STATS_UPDATE(router, SILC_UMODE_ROUTER_OPERATOR);
+
+  return TRUE;
+}
+
+/* This function is used to send the notify packets and motd to the
+   incoming client connection. */
+
+void silc_server_send_connect_notifys(SilcServer server,
+                                     SilcSocketConnection sock,
+                                     SilcClientEntry client)
+{
+  SilcIDListData idata = (SilcIDListData)client;
+
+  /* Send some nice info to the client */
+  SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                         ("Welcome to the SILC Network %s",
+                          client->username));
+  SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                         ("Your host is %s, running version %s",
+                          server->server_name, server_version));
+
+  if (server->stat.clients && server->stat.servers + 1)
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("There are %d clients on %d servers in SILC "
+                            "Network", server->stat.clients,
+                            server->stat.servers + 1));
+  if (server->stat.cell_clients && server->stat.cell_servers + 1)
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("There are %d clients on %d server in our cell",
+                            server->stat.cell_clients,
+                            server->stat.cell_servers + 1));
+  if (server->server_type == SILC_ROUTER) {
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("I have %d clients, %d channels, %d servers and "
+                            "%d routers",
+                            server->stat.my_clients, 
+                            server->stat.my_channels,
+                            server->stat.my_servers,
+                            server->stat.my_routers));
+  } else {
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("I have %d clients and %d channels formed",
+                            server->stat.my_clients,
+                            server->stat.my_channels));
+  }
+
+  if (server->stat.server_ops || server->stat.router_ops)
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("There are %d server operators and %d router "
+                            "operators online",
+                            server->stat.server_ops,
+                            server->stat.router_ops));
+  if (server->stat.my_router_ops + server->stat.my_server_ops)
+    SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                           ("I have %d operators online",
+                            server->stat.my_router_ops +
+                            server->stat.my_server_ops));
+
+  SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                         ("Your connection is secured with %s cipher, "
+                          "key length %d bits",
+                          idata->send_key->cipher->name,
+                          idata->send_key->cipher->key_len));
+  SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+                         ("Your current nickname is %s",
+                          client->nickname));
+
+  /* Send motd */
+  silc_server_send_motd(server, sock);
+}
+
+/* Kill the client indicated by `remote_client' sending KILLED notify
+   to the client, to all channels client has joined and to primary
+   router if needed.  The killed client is also removed from all channels. */
+
+void silc_server_kill_client(SilcServer server,
+                            SilcClientEntry remote_client,
+                            const char *comment,
+                            void *killer_id,
+                            SilcIdType killer_id_type)
+{
+  SilcBuffer killed, killer;
+
+  /* Send the KILL notify packets. First send it to the channel, then
+     to our primary router and then directly to the client who is being
+     killed right now. */
+
+  killed = silc_id_payload_encode(remote_client->id, SILC_ID_CLIENT);
+  killer = silc_id_payload_encode(killer_id, killer_id_type);
+
+  /* Send KILLED notify to the channels. It is not sent to the client
+     as it will be sent differently destined directly to the client and not
+     to the channel. */
+  silc_server_send_notify_on_channels(server, remote_client, 
+                                     remote_client, SILC_NOTIFY_TYPE_KILLED,
+                                     3, killed->data, killed->len,
+                                     comment, comment ? strlen(comment) : 0,
+                                     killer->data, killer->len);
+
+  /* Send KILLED notify to primary route */
+  if (!server->standalone)
+    silc_server_send_notify_killed(server, server->router->connection, TRUE,
+                                  remote_client->id, comment, 
+                                  killer_id, killer_id_type);
+
+  /* Send KILLED notify to the client directly */
+  if (remote_client->connection || remote_client->router)
+    silc_server_send_notify_killed(server, remote_client->connection ? 
+                                  remote_client->connection : 
+                                  remote_client->router->connection, FALSE,
+                                  remote_client->id, comment, 
+                                  killer_id, killer_id_type);
+
+  /* Remove the client from all channels. This generates new keys to the
+     channels as well. */
+  silc_server_remove_from_channels(server, NULL, remote_client, FALSE, 
+                                  NULL, TRUE);
+
+  /* Remove the client entry, If it is locally connected then we will also
+     disconnect the client here */
+  if (remote_client->connection) {
+    /* Remove locally conneted client */
+    SilcSocketConnection sock = remote_client->connection;
+    silc_server_free_client_data(server, sock, remote_client, FALSE, NULL);
+    silc_server_close_connection(server, sock);
+  } else {
+    /* Update statistics */
+    server->stat.clients--;
+    server->stat.my_clients--;
+    if (server->stat.cell_clients)
+      server->stat.cell_clients--;
+    SILC_OPER_STATS_UPDATE(remote_client, server, SILC_UMODE_SERVER_OPERATOR);
+    SILC_OPER_STATS_UPDATE(remote_client, router, SILC_UMODE_ROUTER_OPERATOR);
+
+    /* Remove remote client */
+    if (!silc_idlist_del_client(server->global_list, remote_client))
+      silc_idlist_del_client(server->local_list, remote_client);  
+}
+
+  silc_buffer_free(killer);
+  silc_buffer_free(killed);
+}