+ /* Get entry to the channel user list */
+ silc_hash_table_list(channel->user_list, &htl);
+ while (silc_hash_table_get(&htl, NULL, (void *)&chl)) {
+ /* If the mode is channel founder and we already find a client
+ to have that mode on the channel we will enforce the sender
+ to change the channel founder mode away. There can be only one
+ channel founder on the channel. */
+ if (server->server_type == SILC_ROUTER &&
+ mode & SILC_CHANNEL_UMODE_CHANFO &&
+ chl->mode & SILC_CHANNEL_UMODE_CHANFO) {
+ SilcBuffer idp;
+ unsigned char cumode[4];
+
+ if (chl->client == client && chl->mode == mode) {
+ notify_sent = TRUE;
+ break;
+ }
+
+ mode &= ~SILC_CHANNEL_UMODE_CHANFO;
+ silc_server_send_notify_cumode(server, sock, FALSE, channel, mode,
+ client->id, SILC_ID_CLIENT,
+ client->id);
+
+ idp = silc_id_payload_encode(client->id, SILC_ID_CLIENT);
+ SILC_PUT32_MSB(mode, cumode);
+ silc_server_send_notify_to_channel(server, sock, channel, FALSE,
+ SILC_NOTIFY_TYPE_CUMODE_CHANGE,
+ 3, idp->data, idp->len,
+ cumode, 4,
+ idp->data, idp->len);
+ silc_buffer_free(idp);
+ notify_sent = TRUE;
+
+ /* Force the mode change if we alredy set the mode */
+ if (chl2) {
+ chl2->mode = mode;
+ silc_free(channel_id);
+ silc_hash_table_list_reset(&htl);
+ goto out;
+ }
+ }
+
+ if (chl->client == client) {
+ if (chl->mode == mode) {
+ notify_sent = TRUE;
+ break;
+ }
+
+ SILC_LOG_DEBUG(("Changing the channel user mode"));
+
+ /* Change the mode */
+ chl->mode = mode;
+ if (!(mode & SILC_CHANNEL_UMODE_CHANFO))
+ break;
+
+ chl2 = chl;
+ }
+ }
+ silc_hash_table_list_reset(&htl);
+
+ /* Send the same notify to the channel */
+ if (!notify_sent)
+ silc_server_packet_send_to_channel(server, sock, channel,
+ packet->type,
+ FALSE, packet->buffer->data,
+ packet->buffer->len, FALSE);
+
+ silc_free(channel_id);
+ break;
+ }
+
+ case SILC_NOTIFY_TYPE_INVITE:
+
+ if (packet->dst_id_type == SILC_ID_CLIENT)
+ goto out;
+
+ SILC_LOG_DEBUG(("INVITE notify"));
+
+ /* Get Channel ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!channel_id)
+ goto out;
+
+ /* Get channel entry */
+ channel = silc_idlist_find_channel_by_id(server->global_list,
+ channel_id, NULL);
+ if (!channel) {
+ channel = silc_idlist_find_channel_by_id(server->local_list,
+ channel_id, NULL);
+ if (!channel) {
+ silc_free(channel_id);
+ goto out;
+ }
+ }
+ silc_free(channel_id);
+
+ /* Get the added invite */
+ tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+ if (tmp) {
+ if (!channel->invite_list)
+ channel->invite_list = silc_calloc(tmp_len + 2,
+ sizeof(*channel->invite_list));
+ else
+ channel->invite_list = silc_realloc(channel->invite_list,
+ sizeof(*channel->invite_list) *
+ (tmp_len +
+ strlen(channel->invite_list) +
+ 2));
+ if (tmp[tmp_len - 1] == ',')
+ tmp[tmp_len - 1] = '\0';
+
+ strncat(channel->invite_list, tmp, tmp_len);
+ strncat(channel->invite_list, ",", 1);
+ }
+
+ /* Get the deleted invite */
+ tmp = silc_argument_get_arg_type(args, 4, &tmp_len);
+ if (tmp && channel->invite_list) {
+ char *start, *end, *n;
+
+ if (!strncmp(channel->invite_list, tmp,
+ strlen(channel->invite_list) - 1)) {
+ silc_free(channel->invite_list);
+ channel->invite_list = NULL;
+ } else {
+ start = strstr(channel->invite_list, tmp);
+ if (start && strlen(start) >= tmp_len) {
+ end = start + tmp_len;
+ n = silc_calloc(strlen(channel->invite_list) - tmp_len, sizeof(*n));
+ strncat(n, channel->invite_list, start - channel->invite_list);
+ strncat(n, end + 1, ((channel->invite_list +
+ strlen(channel->invite_list)) - end) - 1);
+ silc_free(channel->invite_list);
+ channel->invite_list = n;
+ }
+ }
+ }
+
+ break;
+
+ case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
+ /*
+ * Distribute to the local clients on the channel and change the
+ * channel ID.
+ */
+
+ SILC_LOG_DEBUG(("CHANNEL CHANGE"));
+
+ if (sock->type != SILC_SOCKET_TYPE_ROUTER)
+ break;
+
+ /* Get the old Channel ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!channel_id)
+ goto out;
+
+ /* Get the channel entry */
+ channel = silc_idlist_find_channel_by_id(server->local_list,
+ channel_id, NULL);
+ if (!channel) {
+ channel = silc_idlist_find_channel_by_id(server->global_list,
+ channel_id, NULL);
+ if (!channel) {
+ silc_free(channel_id);
+ goto out;
+ }
+ }
+
+ /* Send the notify to the channel */
+ silc_server_packet_send_to_channel(server, sock, channel, packet->type,
+ FALSE, packet->buffer->data,
+ packet->buffer->len, FALSE);
+
+ /* Get the new Channel ID */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+ channel_id2 = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!channel_id2)
+ goto out;
+
+ SILC_LOG_DEBUG(("Old Channel ID id(%s)",
+ silc_id_render(channel_id, SILC_ID_CHANNEL)));
+ SILC_LOG_DEBUG(("New Channel ID id(%s)",
+ silc_id_render(channel_id2, SILC_ID_CHANNEL)));
+
+ /* Replace the Channel ID */
+ if (!silc_idlist_replace_channel_id(server->local_list, channel_id,
+ channel_id2))
+ if (!silc_idlist_replace_channel_id(server->global_list, channel_id,
+ channel_id2)) {
+ silc_free(channel_id2);
+ channel_id2 = NULL;
+ }
+
+ if (channel_id2) {
+ SilcBuffer users = NULL, users_modes = NULL;
+
+ /* Re-announce this channel which ID was changed. */
+ silc_server_send_new_channel(server, sock, FALSE, channel->channel_name,
+ channel->id,
+ silc_id_get_len(channel->id,
+ SILC_ID_CHANNEL),
+ channel->mode);
+
+ /* Re-announce our clients on the channel as the ID has changed now */
+ silc_server_announce_get_channel_users(server, channel, &users,
+ &users_modes);
+ if (users) {
+ silc_buffer_push(users, users->data - users->head);
+ silc_server_packet_send(server, sock,
+ SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST,
+ users->data, users->len, FALSE);
+ silc_buffer_free(users);
+ }
+ if (users_modes) {
+ silc_buffer_push(users_modes, users_modes->data - users_modes->head);
+ silc_server_packet_send_dest(server, sock,
+ SILC_PACKET_NOTIFY, SILC_PACKET_FLAG_LIST,
+ channel->id, SILC_ID_CHANNEL,
+ users_modes->data,
+ users_modes->len, FALSE);
+ silc_buffer_free(users_modes);
+ }
+
+ /* Re-announce channel's topic */
+ if (channel->topic) {
+ silc_server_send_notify_topic_set(server, sock,
+ server->server_type == SILC_ROUTER ?
+ TRUE : FALSE, channel,
+ channel->id, SILC_ID_CHANNEL,
+ channel->topic);
+ }
+ }
+
+ silc_free(channel_id);
+
+ break;
+
+ case SILC_NOTIFY_TYPE_SERVER_SIGNOFF:
+ /*
+ * Remove the server entry and all clients that this server owns.
+ */
+
+ SILC_LOG_DEBUG(("SERVER SIGNOFF notify"));
+
+ /* Get Server ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ server_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!server_id)
+ goto out;
+
+ /* Get server entry */
+ server_entry = silc_idlist_find_server_by_id(server->global_list,
+ server_id, TRUE, NULL);
+ local = TRUE;
+ if (!server_entry) {
+ server_entry = silc_idlist_find_server_by_id(server->local_list,
+ server_id, TRUE, NULL);
+ local = TRUE;
+ if (!server_entry) {
+ /* If we are normal server then we might not have the server. Check
+ whether router was kind enough to send the list of all clients
+ that actually was to be removed. Remove them if the list is
+ available. */
+ if (server->server_type != SILC_ROUTER &&
+ silc_argument_get_arg_num(args) > 1) {
+ int i;
+
+ for (i = 1; i < silc_argument_get_arg_num(args); i++) {
+ /* Get Client ID */
+ tmp = silc_argument_get_arg_type(args, i + 1, &tmp_len);
+ if (!tmp)
+ continue;
+ client_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!client_id)
+ continue;
+
+ /* Get client entry */
+ client = silc_idlist_find_client_by_id(server->global_list,
+ client_id, TRUE, &cache);
+ local = TRUE;
+ if (!client) {
+ client = silc_idlist_find_client_by_id(server->local_list,
+ client_id, TRUE, &cache);
+ local = FALSE;
+ if (!client) {
+ silc_free(client_id);
+ continue;
+ }
+ }
+ silc_free(client_id);
+
+ /* Update statistics */
+ server->stat.clients--;
+ if (server->server_type == SILC_ROUTER)
+ server->stat.cell_clients--;
+ SILC_OPER_STATS_UPDATE(client, server, SILC_UMODE_SERVER_OPERATOR);
+ SILC_OPER_STATS_UPDATE(client, router, SILC_UMODE_ROUTER_OPERATOR);
+
+ /* Remove the client from all channels. */
+ silc_server_remove_from_channels(server, NULL, client,
+ TRUE, NULL, FALSE);
+
+ /* Remove the client */
+ silc_idlist_del_client(local ? server->local_list :
+ server->global_list, client);
+ }
+ }
+
+ silc_free(server_id);
+ goto out;
+ }
+ }
+ silc_free(server_id);
+
+ /* Free all client entries that this server owns as they will
+ become invalid now as well. */
+ silc_server_remove_clients_by_server(server, server_entry, TRUE);
+
+ /* Remove the server entry */
+ silc_idlist_del_server(local ? server->local_list :
+ server->global_list, server_entry);
+
+ /* XXX update statistics */
+
+ break;
+
+ case SILC_NOTIFY_TYPE_KICKED:
+ /*
+ * Distribute the notify to local clients on the channel
+ */
+
+ SILC_LOG_DEBUG(("KICKED notify"));
+
+ if (!channel_id) {
+ channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
+ packet->dst_id_type);
+ if (!channel_id)
+ goto out;
+ }
+
+ /* Get channel entry */
+ channel = silc_idlist_find_channel_by_id(server->global_list,
+ channel_id, NULL);
+ if (!channel) {
+ channel = silc_idlist_find_channel_by_id(server->local_list,
+ channel_id, NULL);
+ if (!channel) {
+ silc_free(channel_id);
+ goto out;
+ }
+ }
+ silc_free(channel_id);
+
+ /* Get client ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ client_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!client_id)
+ goto out;
+
+ /* If the the client is not in local list we check global list */
+ client = silc_idlist_find_client_by_id(server->global_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ client = silc_idlist_find_client_by_id(server->local_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ silc_free(client_id);
+ goto out;
+ }
+ }
+
+ /* Send to channel */
+ silc_server_packet_send_to_channel(server, sock, channel, packet->type,
+ FALSE, packet->buffer->data,
+ packet->buffer->len, FALSE);
+
+ /* Remove the client from channel */
+ silc_server_remove_from_one_channel(server, sock, channel, client, FALSE);
+
+ break;
+
+ case SILC_NOTIFY_TYPE_KILLED:
+ {
+ /*
+ * Distribute the notify to local clients on channels
+ */
+ unsigned char *id;
+ uint32 id_len;
+
+ SILC_LOG_DEBUG(("KILLED notify"));
+
+ /* Get client ID */
+ id = silc_argument_get_arg_type(args, 1, &id_len);
+ if (!id)
+ goto out;
+ client_id = silc_id_payload_parse_id(id, id_len);
+ if (!client_id)
+ goto out;
+
+ /* If the the client is not in local list we check global list */
+ client = silc_idlist_find_client_by_id(server->global_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ client = silc_idlist_find_client_by_id(server->local_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ silc_free(client_id);
+ goto out;
+ }
+ }
+ silc_free(client_id);
+
+ /* If the client is one of ours, then close the connection to the
+ client now. This removes the client from all channels as well. */
+ if (packet->dst_id_type == SILC_ID_CLIENT && client->connection) {
+ sock = client->connection;
+ silc_server_free_client_data(server, NULL, client, FALSE, NULL);
+ silc_server_close_connection(server, sock);
+ break;
+ }
+
+ /* Get comment */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (tmp_len > 128)
+ tmp = NULL;
+
+ /* Send the notify to local clients on the channels except to the
+ client who is killed. */
+ silc_server_send_notify_on_channels(server, client, client,
+ SILC_NOTIFY_TYPE_KILLED,
+ tmp ? 2 : 1,
+ id, id_len,
+ tmp, tmp_len);
+
+ /* Remove the client from all channels */
+ silc_server_remove_from_channels(server, NULL, client, FALSE, NULL,
+ FALSE);
+
+ break;
+ }
+
+ case SILC_NOTIFY_TYPE_UMODE_CHANGE:
+ /*
+ * Save the mode of the client.
+ */
+
+ SILC_LOG_DEBUG(("UMODE_CHANGE notify"));
+
+ /* Get client ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ client_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!client_id)
+ goto out;
+
+ /* Get client entry */
+ client = silc_idlist_find_client_by_id(server->global_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ client = silc_idlist_find_client_by_id(server->local_list,
+ client_id, TRUE, NULL);
+ if (!client) {
+ silc_free(client_id);
+ goto out;
+ }
+ }
+ silc_free(client_id);
+
+ /* Get the mode */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+ SILC_GET32_MSB(mode, tmp);
+
+#define SILC_UMODE_STATS_UPDATE(oper, mod) \
+do { \
+ if (client->mode & (mod)) { \
+ if (!(mode & (mod))) { \
+ if (client->connection) \
+ server->stat.my_ ## oper ## _ops--; \
+ if (server->server_type == SILC_ROUTER) \
+ server->stat. oper ## _ops--; \
+ } \
+ } else { \
+ if (mode & (mod)) { \
+ if (client->connection) \
+ server->stat.my_ ## oper ## _ops++; \
+ if (server->server_type == SILC_ROUTER) \
+ server->stat. oper ## _ops++; \
+ } \
+ } \
+} while(0)
+
+ /* Update statistics */
+ SILC_UMODE_STATS_UPDATE(server, SILC_UMODE_SERVER_OPERATOR);
+ SILC_UMODE_STATS_UPDATE(router, SILC_UMODE_ROUTER_OPERATOR);
+
+ /* Save the mode */
+ client->mode = mode;
+
+ break;
+
+ case SILC_NOTIFY_TYPE_BAN:
+ /*
+ * Save the ban
+ */
+
+ SILC_LOG_DEBUG(("BAN notify"));
+
+ /* Get Channel ID */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+ channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+ if (!channel_id)
+ goto out;
+
+ /* Get channel entry */
+ channel = silc_idlist_find_channel_by_id(server->global_list,
+ channel_id, NULL);
+ if (!channel) {
+ channel = silc_idlist_find_channel_by_id(server->local_list,
+ channel_id, NULL);
+ if (!channel) {
+ silc_free(channel_id);
+ goto out;
+ }
+ }
+ silc_free(channel_id);
+
+ /* Get the new ban and add it to the ban list */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (tmp) {
+ if (!channel->ban_list)
+ channel->ban_list = silc_calloc(tmp_len + 2,
+ sizeof(*channel->ban_list));
+ else
+ channel->ban_list = silc_realloc(channel->ban_list,
+ sizeof(*channel->ban_list) *
+ (tmp_len +
+ strlen(channel->ban_list) + 2));
+ strncat(channel->ban_list, tmp, tmp_len);
+ strncat(channel->ban_list, ",", 1);
+ }
+
+ /* Get the ban to be removed and remove it from the list */
+ tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+ if (tmp && channel->ban_list) {
+ char *start, *end, *n;
+
+ if (!strncmp(channel->ban_list, tmp, strlen(channel->ban_list) - 1)) {
+ silc_free(channel->ban_list);
+ channel->ban_list = NULL;
+ } else {
+ start = strstr(channel->ban_list, tmp);
+ if (start && strlen(start) >= tmp_len) {
+ end = start + tmp_len;
+ n = silc_calloc(strlen(channel->ban_list) - tmp_len, sizeof(*n));
+ strncat(n, channel->ban_list, start - channel->ban_list);
+ strncat(n, end + 1, ((channel->ban_list +
+ strlen(channel->ban_list)) - end) - 1);
+ silc_free(channel->ban_list);
+ channel->ban_list = n;
+ }
+ }
+ }
+ break;
+
+ /* Ignore rest of the notify types for now */
+ case SILC_NOTIFY_TYPE_NONE:
+ case SILC_NOTIFY_TYPE_MOTD:
+ break;
+ default:
+ break;
+ }
+
+ out:
+ silc_notify_payload_free(payload);
+}
+
+void silc_server_notify_list(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcPacketContext *new;
+ SilcBuffer buffer;
+ uint16 len;
+
+ SILC_LOG_DEBUG(("Processing Notify List"));
+
+ if (sock->type == SILC_SOCKET_TYPE_CLIENT ||
+ packet->src_id_type != SILC_ID_SERVER)
+ return;
+
+ /* Make copy of the original packet context, except for the actual
+ data buffer, which we will here now fetch from the original buffer. */
+ new = silc_packet_context_alloc();
+ new->type = SILC_PACKET_NOTIFY;
+ new->flags = packet->flags;
+ new->src_id = packet->src_id;
+ new->src_id_len = packet->src_id_len;
+ new->src_id_type = packet->src_id_type;
+ new->dst_id = packet->dst_id;
+ new->dst_id_len = packet->dst_id_len;
+ new->dst_id_type = packet->dst_id_type;
+
+ buffer = silc_buffer_alloc(1024);
+ new->buffer = buffer;
+
+ while (packet->buffer->len) {
+ SILC_GET16_MSB(len, packet->buffer->data + 2);
+ if (len > packet->buffer->len)
+ break;
+
+ if (len > buffer->truelen) {
+ silc_buffer_free(buffer);
+ buffer = silc_buffer_alloc(1024 + len);
+ }
+
+ silc_buffer_pull_tail(buffer, len);
+ silc_buffer_put(buffer, packet->buffer->data, len);
+
+ /* Process the Notify */
+ silc_server_notify(server, sock, new);
+
+ silc_buffer_push_tail(buffer, len);
+ silc_buffer_pull(packet->buffer, len);
+ }
+
+ silc_buffer_free(buffer);
+ silc_free(new);
+}
+
+/* Received private message. This resolves the destination of the message
+ and sends the packet. This is used by both server and router. If the
+ destination is our locally connected client this sends the packet to
+ the client. This may also send the message for further routing if
+ the destination is not in our server (or router). */
+
+void silc_server_private_message(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcSocketConnection dst_sock;
+ SilcIDListData idata;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (packet->src_id_type != SILC_ID_CLIENT ||
+ packet->dst_id_type != SILC_ID_CLIENT || !packet->dst_id)
+ return;
+
+ /* Get the route to the client */
+ dst_sock = silc_server_get_client_route(server, packet->dst_id,
+ packet->dst_id_len, NULL, &idata);
+ if (!dst_sock) {
+ /* Send IDENTIFY command reply with error status to indicate that
+ such destination ID does not exist or is invalid */
+ SilcBuffer idp = silc_id_payload_encode_data(packet->dst_id,
+ packet->dst_id_len,
+ packet->dst_id_type);
+ if (!idp)
+ return;
+
+ if (packet->src_id_type == SILC_ID_CLIENT) {
+ SilcClientID *client_id = silc_id_str2id(packet->src_id,
+ packet->src_id_len,
+ packet->src_id_type);
+ silc_server_send_dest_command_reply(server, sock,
+ client_id, SILC_ID_CLIENT,
+ SILC_COMMAND_IDENTIFY,
+ SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+ 0, 1, 2, idp->data, idp->len);
+ silc_free(client_id);
+ } else {
+ silc_server_send_command_reply(server, sock, SILC_COMMAND_IDENTIFY,
+ SILC_STATUS_ERR_NO_SUCH_CLIENT_ID,
+ 0, 1, 2, idp->data, idp->len);
+ }
+
+ silc_buffer_free(idp);
+ return;
+ }
+
+ /* Send the private message */
+ silc_server_send_private_message(server, dst_sock, idata->send_key,
+ idata->hmac_send, idata->psn_send++,
+ packet);
+}
+
+/* Received private message key packet.. This packet is never for us. It is to
+ the client in the packet's destination ID. Sending of this sort of packet
+ equals sending private message, ie. it is sent point to point from
+ one client to another. */
+
+void silc_server_private_message_key(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcSocketConnection dst_sock;
+ SilcIDListData idata;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ if (packet->src_id_type != SILC_ID_CLIENT ||
+ packet->dst_id_type != SILC_ID_CLIENT)
+ return;
+
+ if (!packet->dst_id)
+ return;
+
+ /* Get the route to the client */
+ dst_sock = silc_server_get_client_route(server, packet->dst_id,
+ packet->dst_id_len, NULL, &idata);
+ if (!dst_sock)
+ return;
+
+ /* Relay the packet */
+ silc_server_relay_packet(server, dst_sock, idata->send_key,
+ idata->hmac_send, idata->psn_send++, packet, FALSE);
+}
+
+/* Processes incoming command reply packet. The command reply packet may
+ be destined to one of our clients or it may directly for us. We will
+ call the command reply routine after processing the packet. */
+
+void silc_server_command_reply(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcBuffer buffer = packet->buffer;
+ SilcClientEntry client = NULL;
+ SilcSocketConnection dst_sock;
+ SilcIDListData idata;
+ SilcClientID *id = NULL;
+
+ SILC_LOG_DEBUG(("Start"));
+
+ /* Source must be server or router */
+ if (packet->src_id_type != SILC_ID_SERVER &&
+ sock->type != SILC_SOCKET_TYPE_ROUTER)
+ return;
+
+ if (packet->dst_id_type == SILC_ID_CHANNEL)
+ return;
+
+ if (packet->dst_id_type == SILC_ID_CLIENT) {
+ /* Destination must be one of ours */
+ id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CLIENT);
+ if (!id)
+ return;
+ client = silc_idlist_find_client_by_id(server->local_list, id, TRUE, NULL);
+ if (!client) {
+ SILC_LOG_ERROR(("Cannot process command reply to unknown client"));
+ silc_free(id);
+ return;
+ }
+ }
+
+ if (packet->dst_id_type == SILC_ID_SERVER) {
+ /* For now this must be for us */
+ if (memcmp(packet->dst_id, server->id_string, server->id_string_len)) {
+ SILC_LOG_ERROR(("Cannot process command reply to unknown server"));
+ return;
+ }
+ }
+
+ /* Execute command reply locally for the command */
+ silc_server_command_reply_process(server, sock, buffer);
+
+ if (packet->dst_id_type == SILC_ID_CLIENT && client && id) {
+ /* Relay the packet to the client */
+
+ dst_sock = (SilcSocketConnection)client->connection;
+ silc_buffer_push(buffer, SILC_PACKET_HEADER_LEN + packet->src_id_len
+ + packet->dst_id_len + packet->padlen);
+
+ silc_packet_send_prepare(dst_sock, 0, 0, buffer->len);
+ silc_buffer_put(dst_sock->outbuf, buffer->data, buffer->len);
+
+ idata = (SilcIDListData)client;
+
+ /* Encrypt packet */
+ silc_packet_encrypt(idata->send_key, idata->hmac_send, idata->psn_send++,
+ dst_sock->outbuf, buffer->len);
+
+ /* Send the packet */
+ silc_server_packet_send_real(server, dst_sock, TRUE);
+
+ silc_free(id);
+ }
+}
+
+/* Process received channel message. The message can be originated from
+ client or server. */
+
+void silc_server_channel_message(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcChannelEntry channel = NULL;
+ SilcChannelID *id = NULL;
+ void *sender = NULL;
+ void *sender_entry = NULL;
+ bool local = TRUE;
+
+ SILC_LOG_DEBUG(("Processing channel message"));
+
+ /* Sanity checks */
+ if (packet->dst_id_type != SILC_ID_CHANNEL) {
+ SILC_LOG_DEBUG(("Received bad message for channel, dropped"));
+ goto out;
+ }
+
+ /* Find channel entry */
+ id = silc_id_str2id(packet->dst_id, packet->dst_id_len, SILC_ID_CHANNEL);
+ if (!id)
+ goto out;
+ channel = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
+ if (!channel) {
+ channel = silc_idlist_find_channel_by_id(server->global_list, id, NULL);
+ if (!channel) {
+ SILC_LOG_DEBUG(("Could not find channel"));
+ goto out;
+ }
+ }
+
+ /* See that this client is on the channel. If the original sender is
+ not client (as it can be server as well) we don't do the check. */
+ sender = silc_id_str2id(packet->src_id, packet->src_id_len,
+ packet->src_id_type);
+ if (!sender)
+ goto out;
+ if (packet->src_id_type == SILC_ID_CLIENT) {
+ sender_entry = silc_idlist_find_client_by_id(server->local_list,
+ sender, TRUE, NULL);
+ if (!sender_entry) {
+ local = FALSE;
+ sender_entry = silc_idlist_find_client_by_id(server->global_list,
+ sender, TRUE, NULL);
+ }
+ if (!sender_entry || !silc_server_client_on_channel(sender_entry,
+ channel)) {
+ SILC_LOG_DEBUG(("Client not on channel"));
+ goto out;
+ }
+
+ /* If the packet is coming from router, but the client entry is
+ local entry to us then some router is rerouting this to us and it is
+ not allowed. */
+ if (server->server_type == SILC_ROUTER &&
+ sock->type == SILC_SOCKET_TYPE_ROUTER && local) {
+ SILC_LOG_DEBUG(("Channel message rerouted to the sender, drop it"));
+ goto out;
+ }
+ }
+
+ /* Distribute the packet to our local clients. This will send the
+ packet for further routing as well, if needed. */
+ silc_server_packet_relay_to_channel(server, sock, channel, sender,
+ packet->src_id_type, sender_entry,
+ packet->buffer->data,
+ packet->buffer->len, FALSE);
+
+ out:
+ if (sender)
+ silc_free(sender);
+ if (id)
+ silc_free(id);
+}
+
+/* Received channel key packet. We distribute the key to all of our locally
+ connected clients on the channel. */
+
+void silc_server_channel_key(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcBuffer buffer = packet->buffer;
+ SilcChannelEntry channel;
+
+ if (packet->src_id_type != SILC_ID_SERVER ||
+ (server->server_type == SILC_ROUTER &&
+ sock->type == SILC_SOCKET_TYPE_ROUTER))
+ return;
+
+ /* Save the channel key */
+ channel = silc_server_save_channel_key(server, buffer, NULL);
+ if (!channel)
+ return;
+
+ /* Distribute the key to everybody who is on the channel. If we are router
+ we will also send it to locally connected servers. */
+ silc_server_send_channel_key(server, sock, channel, FALSE);
+
+ if (server->server_type != SILC_BACKUP_ROUTER) {
+ /* Distribute to local cell backup routers. */
+ silc_server_backup_send(server, (SilcServerEntry)sock->user_data,
+ SILC_PACKET_CHANNEL_KEY, 0,
+ buffer->data, buffer->len, FALSE, TRUE);
+ }
+}
+
+/* Received New Client packet and processes it. Creates Client ID for the
+ client. Client becomes registered after calling this functions. */
+
+SilcClientEntry silc_server_new_client(SilcServer server,
+ SilcSocketConnection sock,
+ SilcPacketContext *packet)
+{
+ SilcBuffer buffer = packet->buffer;
+ SilcClientEntry client;
+ SilcClientID *client_id;
+ SilcBuffer reply;
+ SilcIDListData idata;
+ char *username = NULL, *realname = NULL, *id_string;
+ uint16 username_len;
+ uint32 id_len;
+ int ret;
+ char *hostname, *nickname;
+ int nickfail = 0;
+
+ SILC_LOG_DEBUG(("Creating new client"));
+
+ if (sock->type != SILC_SOCKET_TYPE_CLIENT)
+ return NULL;
+
+ /* Take client entry */
+ client = (SilcClientEntry)sock->user_data;
+ idata = (SilcIDListData)client;
+
+ /* Remove the old cache entry. */
+ if (!silc_idcache_del_by_context(server->local_list->clients, client)) {
+ SILC_LOG_ERROR(("Lost client's cache entry - bad thing"));
+ silc_server_disconnect_remote(server, sock, "Server closed connection: "
+ "Unknown client");
+ return NULL;
+ }
+
+ /* Parse incoming packet */
+ ret = silc_buffer_unformat(buffer,
+ SILC_STR_UI16_NSTRING_ALLOC(&username,
+ &username_len),
+ SILC_STR_UI16_STRING_ALLOC(&realname),
+ SILC_STR_END);
+ if (ret == -1) {
+ silc_free(username);
+ silc_free(realname);
+ silc_server_disconnect_remote(server, sock, "Server closed connection: "
+ "Incomplete client information");
+ return NULL;
+ }
+
+ if (!username) {
+ silc_free(username);
+ silc_free(realname);
+ silc_server_disconnect_remote(server, sock, "Server closed connection: "
+ "Incomplete client information");
+ return NULL;
+ }
+
+ if (username_len > 128)
+ username[128] = '\0';
+
+ /* Check for bad characters for nickname, and modify the nickname if
+ it includes those. */
+ if (silc_server_name_bad_chars(username, username_len)) {
+ nickname = silc_server_name_modify_bad(username, username_len);
+ } else {
+ nickname = strdup(username);
+ }
+
+ /* Make sanity checks for the hostname of the client. If the hostname
+ is provided in the `username' check that it is the same than the
+ resolved hostname, or if not resolved the hostname that appears in
+ the client's public key. If the hostname is not present then put
+ it from the resolved name or from the public key. */
+ if (strchr(username, '@')) {
+ SilcPublicKeyIdentifier pident;
+ int tlen = strcspn(username, "@");
+ char *phostname = NULL;
+
+ hostname = silc_calloc((strlen(username) - tlen) + 1, sizeof(char));
+ memcpy(hostname, username + tlen + 1, strlen(username) - tlen - 1);
+
+ if (strcmp(sock->hostname, sock->ip) &&
+ strcmp(sock->hostname, hostname)) {
+ silc_free(username);
+ silc_free(hostname);
+ silc_free(realname);
+ silc_server_disconnect_remote(server, sock,
+ "Server closed connection: "
+ "Incomplete client information");
+ return NULL;
+ }
+
+ pident = silc_pkcs_decode_identifier(client->data.public_key->identifier);
+ if (pident) {
+ phostname = strdup(pident->host);
+ silc_pkcs_free_identifier(pident);
+ }
+
+ if (!strcmp(sock->hostname, sock->ip) &&
+ phostname && strcmp(phostname, hostname)) {
+ silc_free(username);
+ silc_free(hostname);
+ silc_free(phostname);
+ silc_free(realname);
+ silc_server_disconnect_remote(server, sock,
+ "Server closed connection: "
+ "Incomplete client information");
+ return NULL;
+ }
+
+ silc_free(phostname);
+ } else {
+ /* The hostname is not present, add it. */
+ char *newusername;
+ /* XXX For now we cannot take the host name from the public key since
+ they are not trusted or we cannot verify them as trusted. Just take
+ what the resolved name or address is. */
+#if 0
+ if (strcmp(sock->hostname, sock->ip)) {
+#endif
+ newusername = silc_calloc(strlen(username) +
+ strlen(sock->hostname) + 2,
+ sizeof(*newusername));
+ strncat(newusername, username, strlen(username));
+ strncat(newusername, "@", 1);
+ strncat(newusername, sock->hostname, strlen(sock->hostname));
+ silc_free(username);
+ username = newusername;
+#if 0
+ } else {
+ SilcPublicKeyIdentifier pident =
+ silc_pkcs_decode_identifier(client->data.public_key->identifier);
+
+ if (pident) {
+ newusername = silc_calloc(strlen(username) +
+ strlen(pident->host) + 2,
+ sizeof(*newusername));
+ strncat(newusername, username, strlen(username));
+ strncat(newusername, "@", 1);
+ strncat(newusername, pident->host, strlen(pident->host));
+ silc_free(username);
+ username = newusername;
+ silc_pkcs_free_identifier(pident);
+ }
+ }
+#endif
+ }
+
+ /* Create Client ID */
+ while (!silc_id_create_client_id(server, server->id, server->rng,
+ server->md5hash, nickname, &client_id)) {
+ nickfail++;
+ snprintf(&nickname[strlen(nickname) - 1], 1, "%d", nickfail);
+ }
+
+ /* Update client entry */
+ idata->status |= SILC_IDLIST_STATUS_REGISTERED;
+ client->nickname = nickname;
+ client->username = username;
+ client->userinfo = realname ? realname : strdup(" ");
+ client->id = client_id;
+ id_len = silc_id_get_len(client_id, SILC_ID_CLIENT);
+
+ /* Add the client again to the ID cache */
+ silc_idcache_add(server->local_list->clients, client->nickname,
+ client_id, client, 0, NULL);
+
+ /* Notify our router about new client on the SILC network */
+ if (!server->standalone)
+ silc_server_send_new_id(server, (SilcSocketConnection)
+ server->router->connection,
+ server->server_type == SILC_ROUTER ? TRUE : FALSE,
+ client->id, SILC_ID_CLIENT, id_len);
+
+ /* Send the new client ID to the client. */
+ id_string = silc_id_id2str(client->id, SILC_ID_CLIENT);
+ reply = silc_buffer_alloc(2 + 2 + id_len);
+ silc_buffer_pull_tail(reply, SILC_BUFFER_END(reply));
+ silc_buffer_format(reply,
+ SILC_STR_UI_SHORT(SILC_ID_CLIENT),
+ SILC_STR_UI_SHORT(id_len),
+ SILC_STR_UI_XNSTRING(id_string, id_len),
+ SILC_STR_END);
+ silc_server_packet_send(server, sock, SILC_PACKET_NEW_ID, 0,
+ reply->data, reply->len, FALSE);
+ silc_free(id_string);
+ silc_buffer_free(reply);
+
+ /* Send some nice info to the client */