+ payload = silc_notify_payload_parse(buffer);
+ type = silc_notify_get_type(payload);
+ args = silc_notify_get_args(payload);
+ if (!args)
+ goto out;
+
+ switch(type) {
+ case SILC_NOTIFY_TYPE_NONE:
+ /* Notify application */
+ client->ops->notify(client, conn, type,
+ silc_argument_get_arg_type(args, 1, NULL));
+ break;
+
+ case SILC_NOTIFY_TYPE_INVITE:
+ /*
+ * Someone invited me to a channel. Find Client and Channel entries
+ * for the application.
+ */
+
+ /* 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);
+
+ /* Find Client entry and if not found query it */
+ client_entry = silc_idlist_get_client_by_id(client, conn, client_id, TRUE);
+ if (!client_entry) {
+ SilcPacketContext *p = silc_packet_context_dup(packet);
+ p->context = (void *)client;
+ p->sock = sock;
+ silc_client_command_pending(SILC_COMMAND_WHOIS,
+ silc_client_notify_by_server_pending, p);
+ goto out;
+ }
+
+ /* Get Channel ID */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ channel_id = silc_id_payload_parse_id(tmp, tmp_len);
+
+ /* XXX Will ALWAYS fail because currently we don't have way to resolve
+ channel information for channel that we're not joined to. */
+ /* XXX ways to fix: use (extended) LIST command, or define the channel
+ name to the notfy type when name resolving is not mandatory. */
+ /* Find channel entry */
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ goto out;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Notify application */
+ client->ops->notify(client, conn, type, client_entry, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_JOIN:
+ /*
+ * Someone has joined to a channel. Get their ID and nickname and
+ * cache them for later use.
+ */
+
+ /* 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);
+
+ /* Find Client entry and if not found query it */
+ client_entry = silc_idlist_get_client_by_id(client, conn, client_id, TRUE);
+ if (!client_entry) {
+ SilcPacketContext *p = silc_packet_context_dup(packet);
+ p->context = (void *)client;
+ p->sock = sock;
+ silc_client_command_pending(SILC_COMMAND_WHOIS,
+ silc_client_notify_by_server_pending, p);
+ goto out;
+ }
+
+ /* Get channel entry */
+ channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ break;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Add client to channel */
+ for (i = 0; i < channel->clients_count; i++) {
+ if (channel->clients[i].client == NULL) {
+ channel->clients[channel->clients_count].client = client_entry;
+ channel->clients_count++;
+ break;
+ }
+ }
+
+ if (i == channel->clients_count) {
+ channel->clients = silc_realloc(channel->clients,
+ sizeof(*channel->clients) *
+ (channel->clients_count + 1));
+ channel->clients[channel->clients_count].client = client_entry;
+ channel->clients[channel->clients_count].mode = 0;
+ channel->clients_count++;
+ }
+
+ /* XXX add support for multiple same nicks on same channel. Check
+ for them here */
+
+ /* Notify application. The channel entry is sent last as this notify
+ is for channel but application don't know it from the arguments
+ sent by server. */
+ client->ops->notify(client, conn, type, client_entry, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_LEAVE:
+ /*
+ * Someone has left a channel. We will remove it from the channel but
+ * we'll keep it in the cache in case we'll need it later.
+ */
+
+ /* 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);
+
+ /* Find Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Get channel entry */
+ channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ break;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Remove client from channel */
+ for (i = 0; i < channel->clients_count; i++) {
+ if (channel->clients[i].client == client_entry) {
+ channel->clients[i].client = NULL;
+ channel->clients_count--;
+ break;
+ }
+ }
+
+ /* Notify application. The channel entry is sent last as this notify
+ is for channel but application don't know it from the arguments
+ sent by server. */
+ client->ops->notify(client, conn, type, client_entry, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_SIGNOFF:
+ /*
+ * Someone left SILC. We'll remove it from all channels and from cache.
+ */
+
+ /* 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);
+
+ /* Find Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Remove from all channels */
+ silc_client_remove_from_channels(client, conn, client_entry);
+
+ /* Remove from cache */
+ silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT,
+ client_entry->id);
+
+ /* Notify application */
+ client->ops->notify(client, conn, type, client_entry);
+
+ /* Free data */
+ if (client_entry->nickname)
+ silc_free(client_entry->nickname);
+ if (client_entry->server)
+ silc_free(client_entry->server);
+ if (client_entry->id)
+ silc_free(client_entry->id);
+ if (client_entry->send_key)
+ silc_cipher_free(client_entry->send_key);
+ if (client_entry->receive_key)
+ silc_cipher_free(client_entry->receive_key);
+ break;
+
+ case SILC_NOTIFY_TYPE_TOPIC_SET:
+ /*
+ * Someone set the topic on a channel.
+ */
+
+ /* 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);
+
+ /* Find Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Get topic */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ /* Get channel entry */
+ channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ break;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Notify application. The channel entry is sent last as this notify
+ is for channel but application don't know it from the arguments
+ sent by server. */
+ client->ops->notify(client, conn, type, client_entry, tmp, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_NICK_CHANGE:
+ /*
+ * Someone changed their nickname. If we don't have entry for the new
+ * ID we will query it and return here after it's done. After we've
+ * returned we fetch the old entry and free it and notify the
+ * application.
+ */
+
+ /* Get new Client ID */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ client_id = silc_id_payload_parse_id(tmp, tmp_len);
+
+ /* Ignore my ID */
+ if (!SILC_ID_CLIENT_COMPARE(client_id, conn->local_id))
+ break;
+
+ /* Find Client entry and if not found query it */
+ client_entry2 =
+ silc_idlist_get_client_by_id(client, conn, client_id, TRUE);
+ if (!client_entry2) {
+ SilcPacketContext *p = silc_packet_context_dup(packet);
+ p->context = (void *)client;
+ p->sock = sock;
+ silc_client_command_pending(SILC_COMMAND_WHOIS,
+ silc_client_notify_by_server_pending, p);
+ goto out;
+ }
+
+ /* Get old 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);
+
+ /* Find old Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Remove the old from cache */
+ silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT,
+ client_entry->id);
+
+ /* Replace old ID entry with new one on all channels. */
+ silc_client_replace_from_channels(client, conn, client_entry,
+ client_entry2);
+
+ /* Notify application */
+ client->ops->notify(client, conn, type, client_entry, client_entry2);
+
+ /* Free data */
+ if (client_entry->nickname)
+ silc_free(client_entry->nickname);
+ if (client_entry->server)
+ silc_free(client_entry->server);
+ if (client_entry->id)
+ silc_free(client_entry->id);
+ if (client_entry->send_key)
+ silc_cipher_free(client_entry->send_key);
+ if (client_entry->receive_key)
+ silc_cipher_free(client_entry->receive_key);
+ break;
+
+ case SILC_NOTIFY_TYPE_CMODE_CHANGE:
+ /*
+ * Someone changed a channel mode
+ */
+
+ /* 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);
+
+ /* Find Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Get the mode */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ SILC_GET32_MSB(mode, tmp);
+
+ /* Get channel entry */
+ channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ break;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Save the new mode */
+ channel->mode = mode;
+
+ /* Notify application. The channel entry is sent last as this notify
+ is for channel but application don't know it from the arguments
+ sent by server. */
+ client->ops->notify(client, conn, type, client_entry, mode, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
+ /*
+ * Someone changed user's mode on a channel
+ */
+
+ /* 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);
+
+ /* Find Client entry */
+ client_entry =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry)
+ goto out;
+
+ /* Get the mode */
+ tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ SILC_GET32_MSB(mode, tmp);
+
+ /* Get target Client ID */
+ tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ silc_free(client_id);
+ client_id = silc_id_payload_parse_id(tmp, tmp_len);
+
+ /* Find target Client entry */
+ client_entry2 =
+ silc_idlist_get_client_by_id(client, conn, client_id, FALSE);
+ if (!client_entry2)
+ goto out;
+
+ /* Get channel entry */
+ channel_id = silc_id_str2id(packet->dst_id, SILC_ID_CHANNEL);
+ if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
+ SILC_ID_CHANNEL, &id_cache))
+ break;
+
+ channel = (SilcChannelEntry)id_cache->context;
+
+ /* Save the mode */
+ for (i = 0; i < channel->clients_count; i++) {
+ if (channel->clients[i].client == client_entry2) {
+ channel->clients[i].mode = mode;
+ break;
+ }
+ }
+
+ /* Notify application. The channel entry is sent last as this notify
+ is for channel but application don't know it from the arguments
+ sent by server. */
+ client->ops->notify(client, conn, type, client_entry, mode,
+ client_entry2, channel);
+ break;
+
+ case SILC_NOTIFY_TYPE_MOTD:
+ /*
+ * Received Message of the day
+ */
+
+ /* Get motd */
+ tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
+ if (!tmp)
+ goto out;
+
+ /* Notify application */
+ client->ops->notify(client, conn, type, tmp);
+ break;
+
+ default:
+ break;
+ }
+
+ out:
+ silc_notify_payload_free(payload);
+ if (client_id)
+ silc_free(client_id);
+ if (channel_id)
+ silc_free(channel_id);