+
+/* 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,
+ SilcSocketType type)
+{
+ int i, count;
+
+ for (i = 0, count = 0; i < server->config->param.connections_max; i++) {
+ if (server->sockets[i] && !SILC_IS_LISTENER(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] && !SILC_IS_LISTENER(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;
+
+ SILC_LOG_DEBUG(("Checking whether connection is allowed"));
+
+ /* 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,
+ SILC_STATUS_ERR_BAD_VERSION,
+ "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,
+ SILC_STATUS_ERR_BAD_VERSION,
+ "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,
+ SILC_STATUS_ERR_BAD_VERSION,
+ "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,
+ SILC_STATUS_ERR_RESOURCE_LIMIT,
+ "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,
+ SILC_STATUS_ERR_RESOURCE_LIMIT,
+ "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;
+
+ /* Founder implies operator */
+ if (is_fo)
+ is_op = 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 (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;
+
+ SILC_LOG_DEBUG(("Send welcome notifys"));
+
+ /* 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->server_type == SILC_ROUTER) {
+ SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+ ("There are %d clients, %d servers and %d "
+ "routers in SILC Network",
+ server->stat.clients, server->stat.servers + 1,
+ server->stat.routers));
+ } else {
+ if (server->stat.clients && server->stat.servers + 1)
+ SILC_SERVER_SEND_NOTIFY(server, sock, SILC_NOTIFY_TYPE_NONE,
+ ("There are %d clients, %d servers and %d "
+ "routers in SILC Network",
+ server->stat.clients, server->stat.servers + 1,
+ (server->standalone ? 0 :
+ !server->stat.routers ? 1 :
+ server->stat.routers)));
+ }
+
+ 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",
+ silc_cipher_get_name(idata->send_key),
+ silc_cipher_get_key_len(idata->send_key)));
+ 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;
+
+ SILC_LOG_DEBUG(("Killing client %s",
+ silc_id_render(remote_client->id, SILC_ID_CLIENT)));
+
+ /* 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 */
+ silc_server_send_notify_killed(server, SILC_PRIMARY_ROUTE(server),
+ SILC_BROADCAST(server), 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, 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--;
+ 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);
+
+ if (SILC_IS_LOCAL(remote_client)) {
+ server->stat.my_clients--;
+ silc_schedule_task_del_by_context(server->schedule, remote_client);
+ silc_idlist_del_data(remote_client);
+ }
+
+ /* Remove remote client */
+ silc_idlist_del_data(remote_client);
+ if (!silc_idlist_del_client(server->global_list, remote_client)) {
+ /* Remove this client from watcher list if it is */
+ silc_server_del_from_watcher_list(server, remote_client);
+ silc_idlist_del_client(server->local_list, remote_client);
+ }
+ }
+
+ silc_buffer_free(killer);
+ silc_buffer_free(killed);
+}
+
+typedef struct {
+ SilcServer server;
+ SilcClientEntry client;
+ SilcNotifyType notify;
+ const char *new_nick;
+} WatcherNotifyContext;
+
+static void
+silc_server_check_watcher_list_foreach(void *key, void *context,
+ void *user_context)
+{
+ WatcherNotifyContext *notify = user_context;
+ SilcClientEntry entry = context;
+ SilcSocketConnection sock;
+
+ if (!context)
+ return;
+
+ if (entry == notify->client)
+ return;
+
+ sock = silc_server_get_client_route(notify->server, NULL, 0, entry->id,
+ NULL, NULL);
+ if (sock) {
+ SILC_LOG_DEBUG(("Sending WATCH notify to %s",
+ silc_id_render(entry->id, SILC_ID_CLIENT)));
+
+ /* Send the WATCH notify */
+ silc_server_send_notify_watch(notify->server, sock, entry,
+ notify->client,
+ notify->new_nick ? notify->new_nick :
+ (const char *)notify->client->nickname,
+ notify->notify);
+ }
+}
+
+/* This function checks whether the `client' nickname is being watched
+ by someone, and notifies the watcher of the notify change of notify
+ type indicated by `notify'. */
+
+bool silc_server_check_watcher_list(SilcServer server,
+ SilcClientEntry client,
+ const char *new_nick,
+ SilcNotifyType notify)
+{
+ unsigned char hash[16];
+ WatcherNotifyContext n;
+
+ SILC_LOG_DEBUG(("Checking watcher list %s",
+ client->nickname ? client->nickname : (unsigned char *)""));
+
+ /* If the watching is rejected by the client do nothing */
+ if (client->mode & SILC_UMODE_REJECT_WATCHING)
+ return FALSE;
+
+ /* Make hash from the nick, or take it from Client ID */
+ if (client->nickname) {
+ char nick[128 + 1];
+ memset(nick, 0, sizeof(nick));
+ silc_to_lower(client->nickname, nick, sizeof(nick) - 1);
+ silc_hash_make(server->md5hash, nick, strlen(nick), hash);
+ } else {
+ memset(hash, 0, sizeof(hash));
+ memcpy(hash, client->id->hash, sizeof(client->id->hash));
+ }
+
+ n.server = server;
+ n.client = client;
+ n.new_nick = new_nick;
+ n.notify = notify;
+
+ /* Send notify to all watchers */
+ silc_hash_table_find_foreach(server->watcher_list, hash,
+ silc_server_check_watcher_list_foreach, &n);
+
+ return TRUE;
+}
+
+/* Remove the `client' from watcher list. After calling this the `client'
+ is not watching any nicknames. */
+
+bool silc_server_del_from_watcher_list(SilcServer server,
+ SilcClientEntry client)
+{
+ SilcHashTableList htl;
+ void *key;
+ SilcClientEntry entry;
+ bool found = FALSE;
+
+ silc_hash_table_list(server->watcher_list, &htl);
+ while (silc_hash_table_get(&htl, &key, (void **)&entry)) {
+ if (entry == client) {
+ silc_hash_table_del_by_context(server->watcher_list, key, client);
+
+ if (client->id)
+ SILC_LOG_DEBUG(("Removing %s from WATCH list",
+ silc_id_render(client->id, SILC_ID_CLIENT)));
+
+ /* Now check whether there still exists entries with this key, if not
+ then free the key to not leak memory. */
+ if (!silc_hash_table_find(server->watcher_list, key, NULL, NULL))
+ silc_free(key);
+
+ found = TRUE;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ return found;
+}
+
+/* Force the client indicated by `chl' to change the channel user mode
+ on channel indicated by `channel' to `forced_mode'. */
+
+bool silc_server_force_cumode_change(SilcServer server,
+ SilcSocketConnection sock,
+ SilcChannelEntry channel,
+ SilcChannelClientEntry chl,
+ SilcUInt32 forced_mode)
+{
+ SilcBuffer idp1, idp2;
+ unsigned char cumode[4];
+
+ SILC_LOG_DEBUG(("Enforcing sender to change mode"));
+
+ if (sock)
+ silc_server_send_notify_cumode(server, sock, FALSE, channel, forced_mode,
+ server->id, SILC_ID_SERVER,
+ chl->client->id, NULL);
+
+ idp1 = silc_id_payload_encode(server->id, SILC_ID_SERVER);
+ idp2 = silc_id_payload_encode(chl->client->id, SILC_ID_CLIENT);
+ SILC_PUT32_MSB(forced_mode, cumode);
+ silc_server_send_notify_to_channel(server, sock, channel, FALSE,
+ SILC_NOTIFY_TYPE_CUMODE_CHANGE,
+ 3, idp1->data, idp1->len,
+ cumode, sizeof(cumode),
+ idp2->data, idp2->len);
+ silc_buffer_free(idp1);
+ silc_buffer_free(idp2);
+
+ return TRUE;
+}
+
+/* Find active socket connection by the IP address and port indicated by
+ `ip' and `port', and socket connection type of `type'. */
+
+SilcSocketConnection
+silc_server_find_socket_by_host(SilcServer server,
+ SilcSocketType type,
+ const char *ip, SilcUInt16 port)
+{
+ int i;
+
+ for (i = 0; i < server->config->param.connections_max; i++) {
+ if (!server->sockets[i])
+ continue;
+ if (!strcmp(server->sockets[i]->ip, ip) &&
+ (!port || server->sockets[i]->port == port) &&
+ server->sockets[i]->type == type)
+ return server->sockets[i];
+ }
+
+ return NULL;
+}
+
+/* This function can be used to match the invite and ban lists. */
+
+bool silc_server_inviteban_match(SilcServer server, SilcHashTable list,
+ SilcUInt8 type, void *check)
+{
+ unsigned char *tmp = NULL;
+ SilcUInt32 len = 0, t;
+ SilcHashTableList htl;
+ SilcBuffer entry, idp = NULL;
+ bool ret = FALSE;
+
+ if (type < 1 || type > 3 || !check)
+ return FALSE;
+
+ if (type == 1) {
+ tmp = strdup((char *)check);
+ if (!tmp)
+ return FALSE;
+ }
+ if (type == 2) {
+ tmp = silc_pkcs_public_key_encode(check, &len);
+ if (!tmp)
+ return FALSE;
+ }
+ if (type == 3) {
+ idp = silc_id_payload_encode(check, SILC_ID_CLIENT);
+ if (!idp)
+ return FALSE;
+ tmp = idp->data;
+ len = idp->len;
+ }
+
+ /* Compare the list */
+ silc_hash_table_list(list, &htl);
+ while (silc_hash_table_get(&htl, (void **)&t, (void **)&entry)) {
+ if (type == t) {
+ if (type == 1) {
+ if (silc_string_match((char *)entry, tmp)) {
+ ret = TRUE;
+ break;
+ }
+ } else if (!memcmp(entry->data, tmp, len)) {
+ ret = TRUE;
+ break;
+ }
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ if (!idp)
+ silc_free(tmp);
+ silc_buffer_free(idp);
+ return ret;
+}
+
+static void silc_server_inviteban_dummy_dest(void *key, void *context,
+ void *user_context)
+{
+ /* Nothing */
+}
+
+/* Process invite or ban information */
+
+void silc_server_inviteban_process(SilcServer server, SilcHashTable list,
+ SilcUInt8 action, SilcArgumentPayload args)
+{
+ unsigned char *tmp;
+ SilcUInt32 type, len;
+ SilcBuffer tmp2;
+ SilcHashTableList htl;
+
+ SILC_LOG_DEBUG(("Processing invite/ban for %s action",
+ action == 0x00 ? "ADD" : "DEL"));
+
+ /* Add the information to invite list */
+ if (action == 0x00) {
+ /* Traverse all arguments and add to the hash table according to
+ their type. */
+ tmp = silc_argument_get_first_arg(args, &type, &len);
+ while (tmp) {
+ if (type == 1) {
+ /* Invite string. Get the old invite string from hash table
+ and append this at the end of the existing one. */
+ char *string = NULL;
+ silc_hash_table_find(list, (void *)1,
+ NULL, (void **)&string);
+ silc_hash_table_del_ext(list, (void *)1, NULL, NULL, NULL, NULL,
+ silc_server_inviteban_dummy_dest, NULL);
+ if (!string)
+ string = silc_calloc(len + 2, sizeof(*string));
+ else
+ string = silc_realloc(string, sizeof(*string) *
+ (strlen(string) + len + 2));
+ memset(string + strlen(string), 0, len + 2);
+ if (tmp[len - 1] == ',')
+ tmp[len - 1] = '\0';
+ strncat(string, tmp, len);
+ strncat(string, ",", 1);
+
+ /* Add new invite string to invite list */
+ silc_hash_table_add(list, (void *)1, string);
+
+ } else if (type == 2) {
+ /* Public key. Check first if the public key is already on the
+ list and ignore it if it is, otherwise, add it to hash table. */
+
+ /* Check if the public key is in the list already */
+ silc_hash_table_list(list, &htl);
+ while (silc_hash_table_get(&htl, (void **)&type, (void **)&tmp2)) {
+ if (type == 2 && !memcmp(tmp2->data, tmp, len)) {
+ tmp = NULL;
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ /* Add new public key to invite list */
+ if (tmp) {
+ tmp2 = silc_buffer_alloc_size(len);
+ silc_buffer_put(tmp2, tmp, len);
+ silc_hash_table_add(list, (void *)2, tmp2);
+ }
+
+ } else if (type == 3) {
+ /* Client ID */
+
+ /* Check if the ID is in the list already */
+ silc_hash_table_list(list, &htl);
+ while (silc_hash_table_get(&htl, (void **)&type, (void **)&tmp2)) {
+ if (type == 3 && !memcmp(tmp2->data, tmp, len)) {
+ tmp = NULL;
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ /* Add new Client ID to invite list */
+ if (tmp) {
+ tmp2 = silc_buffer_alloc_size(len);
+ silc_buffer_put(tmp2, tmp, len);
+ silc_hash_table_add(list, (void *)3, tmp2);
+ }
+ }
+
+ tmp = silc_argument_get_next_arg(args, &type, &len);
+ }
+ }
+
+ /* Delete information to invite list */
+ if (action == 0x01 && list) {
+ /* Now delete the arguments from invite list */
+ tmp = silc_argument_get_first_arg(args, &type, &len);
+ while (tmp) {
+ if (type == 1) {
+ /* Invite string. Get the old string from hash table and delete
+ the requested string. */
+ char *string = NULL, *start, *end, *n;
+
+ if (silc_hash_table_find(list, (void *)1, NULL, (void **)&string)) {
+ if (!strncmp(string, tmp, strlen(string) - 1)) {
+ silc_hash_table_del(list, (void *)1);
+ string = NULL;
+ } else {
+ start = strstr(string, tmp);
+ if (start && strlen(start) >= len) {
+ end = start + len;
+ n = silc_calloc(strlen(string) - len, sizeof(*n));
+ strncat(n, string, start - string);
+ strncat(n, end + 1, ((string + strlen(string)) - end) - 1);
+ silc_hash_table_del(list, (void *)1);
+ string = n;
+ }
+ }
+
+ /* Add new invite string to invite list */
+ if (string)
+ silc_hash_table_add(list, (void *)1, string);
+ }
+
+ } else if (type == 2) {
+ /* Public key. */
+
+ /* Delete from the invite list */
+ silc_hash_table_list(list, &htl);
+ while (silc_hash_table_get(&htl, (void **)&type, (void **)&tmp2)) {
+ if (type == 2 && !memcmp(tmp2->data, tmp, len)) {
+ silc_hash_table_del_by_context(list, (void *)2, tmp2);
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ } else if (type == 3) {
+ /* Client ID */
+
+ /* Delete from the invite list */
+ silc_hash_table_list(list, &htl);
+ while (silc_hash_table_get(&htl, (void **)&type, (void **)&tmp2)) {
+ if (type == 3 && !memcmp(tmp2->data, tmp, len)) {
+ silc_hash_table_del_by_context(list, (void *)3, tmp2);
+ break;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+ }
+
+ tmp = silc_argument_get_next_arg(args, &type, &len);
+ }
+ }
+}
+
+/* Destructor for invite or ban list entrys */
+
+void silc_server_inviteban_destruct(void *key, void *context,
+ void *user_context)
+{
+ switch ((SilcUInt32)key) {
+ case 1:
+ /* Invite string */
+ silc_free(context);
+ break;
+ case 2:
+ case 3:
+ /* Public key/Channel ID SilcBuffer */
+ silc_buffer_free(context);
+ break;
+ default:
+ break;
+ }
+}