updates.
[silc.git] / lib / silcclient / client_notify.c
1 /*
2
3   client_notify.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1997 - 2001 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21 /* This file includes the Notify packet handling. Notify packets are
22    important packets sent by the server. They tell different things to the
23    client such as nick changes, mode changes etc. */
24
25 #include "clientlibincludes.h"
26 #include "client_internal.h"
27
28 /* Called when notify is received and some async operation (such as command)
29    is required before processing the notify message. This calls again the
30    silc_client_notify_by_server and reprocesses the original notify packet. */
31
32 static void silc_client_notify_by_server_pending(void *context)
33 {
34   SilcPacketContext *p = (SilcPacketContext *)context;
35   silc_client_notify_by_server(p->context, p->sock, p);
36 }
37
38 /* Destructor for the pending command callback */
39
40 static void silc_client_notify_by_server_destructor(void *context)
41 {
42   silc_packet_context_free((SilcPacketContext *)context);
43 }
44
45 /* Resolve client information from server by Client ID. */
46
47 static void silc_client_notify_by_server_resolve(SilcClient client,
48                                                  SilcClientConnection conn,
49                                                  SilcPacketContext *packet,
50                                                  SilcClientID *client_id)
51 {
52   SilcPacketContext *p = silc_packet_context_dup(packet);
53   SilcBuffer idp = silc_id_payload_encode(client_id, SILC_ID_CLIENT);
54
55   p->context = (void *)client;
56   p->sock = conn->sock;
57
58   silc_client_send_command(client, conn, SILC_COMMAND_WHOIS, ++conn->cmd_ident,
59                            1, 3, idp->data, idp->len);
60   silc_client_command_pending(conn, SILC_COMMAND_WHOIS, conn->cmd_ident,
61                               silc_client_notify_by_server_destructor,
62                               silc_client_notify_by_server_pending, p);
63   silc_buffer_free(idp);
64 }
65
66 /* Received notify message from server */
67
68 void silc_client_notify_by_server(SilcClient client,
69                                   SilcSocketConnection sock,
70                                   SilcPacketContext *packet)
71 {
72   SilcBuffer buffer = packet->buffer;
73   SilcClientConnection conn = (SilcClientConnection)sock->user_data;
74   SilcNotifyPayload payload;
75   SilcNotifyType type;
76   SilcArgumentPayload args;
77
78   SilcClientID *client_id = NULL;
79   SilcChannelID *channel_id = NULL;
80   SilcClientEntry client_entry;
81   SilcClientEntry client_entry2;
82   SilcChannelEntry channel;
83   SilcChannelUser chu;
84   SilcIDCacheEntry id_cache = NULL;
85   unsigned char *tmp;
86   unsigned int tmp_len, mode;
87
88   payload = silc_notify_payload_parse(buffer);
89   if (!payload)
90     goto out;
91
92   type = silc_notify_get_type(payload);
93   args = silc_notify_get_args(payload);
94   if (!args)
95     goto out;
96
97   switch(type) {
98   case SILC_NOTIFY_TYPE_NONE:
99     /* Notify application */
100     client->ops->notify(client, conn, type, 
101                         silc_argument_get_arg_type(args, 1, NULL));
102     break;
103
104   case SILC_NOTIFY_TYPE_INVITE:
105     /* 
106      * Someone invited me to a channel. Find Client and Channel entries
107      * for the application.
108      */
109     
110     /* Get Client ID */
111     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
112     if (!tmp)
113       goto out;
114
115     client_id = silc_id_payload_parse_id(tmp, tmp_len);
116     if (!client_id)
117       goto out;
118
119     /* Find Client entry and if not found query it */
120     client_entry = silc_client_get_client_by_id(client, conn, client_id);
121     if (!client_entry) {
122       silc_client_notify_by_server_resolve(client, conn, packet, client_id);
123       goto out;
124     }
125
126     /* Get Channel ID */
127     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
128     if (!tmp)
129       goto out;
130
131     channel_id = silc_id_payload_parse_id(tmp, tmp_len);
132     if (!channel_id)
133       goto out;
134
135     /* XXX Will ALWAYS fail because currently we don't have way to resolve
136        channel information for channel that we're not joined to. */
137     /* XXX ways to fix: use (extended) LIST command, or define the channel
138        name to the notfy type when name resolving is not mandatory. */
139     /* Find channel entry */
140     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
141                                      SILC_ID_CHANNEL, &id_cache))
142       goto out;
143
144     channel = (SilcChannelEntry)id_cache->context;
145
146     /* Notify application */
147     client->ops->notify(client, conn, type, client_entry, channel);
148     break;
149
150   case SILC_NOTIFY_TYPE_JOIN:
151     /*
152      * Someone has joined to a channel. Get their ID and nickname and
153      * cache them for later use.
154      */
155
156     /* Get Client ID */
157     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
158     if (!tmp)
159       goto out;
160
161     client_id = silc_id_payload_parse_id(tmp, tmp_len);
162     if (!client_id)
163       goto out;
164
165     /* Find Client entry and if not found query it */
166     client_entry = silc_client_get_client_by_id(client, conn, client_id);
167     if (!client_entry) {
168       silc_client_notify_by_server_resolve(client, conn, packet, client_id);
169       goto out;
170     }
171
172     /* If nickname or username hasn't been resolved, do so */
173     if (!client_entry->nickname || !client_entry->username) {
174       silc_client_notify_by_server_resolve(client, conn, packet, client_id);
175       goto out;
176     }
177
178     /* Get Channel ID */
179     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
180     if (!tmp)
181       goto out;
182
183     channel_id = silc_id_payload_parse_id(tmp, tmp_len);
184     if (!channel_id)
185       goto out;
186
187     /* Get channel entry */
188     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
189                                      SILC_ID_CHANNEL, &id_cache))
190       break;
191
192     channel = (SilcChannelEntry)id_cache->context;
193
194     /* Add client to channel */
195     if (client_entry != conn->local_entry) {
196       chu = silc_calloc(1, sizeof(*chu));
197       chu->client = client_entry;
198       silc_list_add(channel->clients, chu);
199     }
200
201     /* XXX add support for multiple same nicks on same channel. Check
202        for them here */
203
204     /* Notify application. The channel entry is sent last as this notify
205        is for channel but application don't know it from the arguments
206        sent by server. */
207     client->ops->notify(client, conn, type, client_entry, channel);
208     break;
209
210   case SILC_NOTIFY_TYPE_LEAVE:
211     /*
212      * Someone has left a channel. We will remove it from the channel but
213      * we'll keep it in the cache in case we'll need it later.
214      */
215     
216     /* Get Client ID */
217     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
218     if (!tmp)
219       goto out;
220
221     client_id = silc_id_payload_parse_id(tmp, tmp_len);
222     if (!client_id)
223       goto out;
224
225     /* Find Client entry */
226     client_entry = 
227       silc_client_get_client_by_id(client, conn, client_id);
228     if (!client_entry)
229       goto out;
230
231     /* Get channel entry */
232     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
233                                 SILC_ID_CHANNEL);
234     if (!channel_id)
235       goto out;
236     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
237                                      SILC_ID_CHANNEL, &id_cache))
238       break;
239
240     channel = (SilcChannelEntry)id_cache->context;
241
242     /* Remove client from channel */
243     silc_list_start(channel->clients);
244     while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
245       if (chu->client == client_entry) {
246         silc_list_del(channel->clients, chu);
247         silc_free(chu);
248         break;
249       }
250     }
251
252     /* Notify application. The channel entry is sent last as this notify
253        is for channel but application don't know it from the arguments
254        sent by server. */
255     client->ops->notify(client, conn, type, client_entry, channel);
256     break;
257
258   case SILC_NOTIFY_TYPE_SIGNOFF:
259     /*
260      * Someone left SILC. We'll remove it from all channels and from cache.
261      */
262
263     /* Get Client ID */
264     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
265     if (!tmp)
266       goto out;
267
268     client_id = silc_id_payload_parse_id(tmp, tmp_len);
269     if (!client_id)
270       goto out;
271
272     /* Find Client entry */
273     client_entry = 
274       silc_client_get_client_by_id(client, conn, client_id);
275     if (!client_entry)
276       goto out;
277
278     /* Remove from all channels */
279     silc_client_remove_from_channels(client, conn, client_entry);
280
281     /* Remove from cache */
282     silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, 
283                            client_entry->id);
284
285     /* Get signoff message */
286     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
287     if (tmp_len > 128)
288       tmp = NULL;
289
290     /* Notify application */
291     client->ops->notify(client, conn, type, client_entry, tmp);
292
293     /* Free data */
294     if (client_entry->nickname)
295       silc_free(client_entry->nickname);
296     if (client_entry->server)
297       silc_free(client_entry->server);
298     if (client_entry->id)
299       silc_free(client_entry->id);
300     if (client_entry->send_key)
301       silc_cipher_free(client_entry->send_key);
302     if (client_entry->receive_key)
303       silc_cipher_free(client_entry->receive_key);
304     break;
305
306   case SILC_NOTIFY_TYPE_TOPIC_SET:
307     /*
308      * Someone set the topic on a channel.
309      */
310
311     /* Get Client ID */
312     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
313     if (!tmp)
314       goto out;
315
316     client_id = silc_id_payload_parse_id(tmp, tmp_len);
317     if (!client_id)
318       goto out;
319
320     /* Find Client entry */
321     client_entry = 
322       silc_client_get_client_by_id(client, conn, client_id);
323     if (!client_entry)
324       goto out;
325
326     /* Get topic */
327     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
328     if (!tmp)
329       goto out;
330
331     /* Get channel entry */
332     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
333                                 SILC_ID_CHANNEL);
334     if (!channel_id)
335       goto out;
336     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
337                                      SILC_ID_CHANNEL, &id_cache))
338       break;
339
340     channel = (SilcChannelEntry)id_cache->context;
341
342     /* Notify application. The channel entry is sent last as this notify
343        is for channel but application don't know it from the arguments
344        sent by server. */
345     client->ops->notify(client, conn, type, client_entry, tmp, channel);
346     break;
347
348   case SILC_NOTIFY_TYPE_NICK_CHANGE:
349     /*
350      * Someone changed their nickname. If we don't have entry for the new
351      * ID we will query it and return here after it's done. After we've
352      * returned we fetch the old entry and free it and notify the 
353      * application.
354      */
355
356     /* Get old Client ID */
357     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
358     if (!tmp)
359       goto out;
360
361     client_id = silc_id_payload_parse_id(tmp, tmp_len);
362     if (!client_id)
363       goto out;
364
365     /* Ignore my ID */
366     if (!SILC_ID_CLIENT_COMPARE(client_id, conn->local_id))
367       break;
368
369     /* Find old Client entry */
370     client_entry = silc_client_get_client_by_id(client, conn, client_id);
371     if (!client_entry)
372       goto out;
373     silc_free(client_id);
374
375     /* Get new Client ID */
376     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
377     if (!tmp)
378       goto out;
379
380     client_id = silc_id_payload_parse_id(tmp, tmp_len);
381     if (!client_id)
382       goto out;
383
384     /* Find Client entry and if not found resolve it */
385     client_entry2 = silc_client_get_client_by_id(client, conn, client_id);
386     if (!client_entry2) {
387       silc_client_notify_by_server_resolve(client, conn, packet, client_id);
388       goto out;
389     }
390
391     /* Remove the old from cache */
392     silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, 
393                            client_entry->id);
394
395     /* Replace old ID entry with new one on all channels. */
396     silc_client_replace_from_channels(client, conn, client_entry,
397                                       client_entry2);
398
399     /* Notify application */
400     client->ops->notify(client, conn, type, client_entry, client_entry2);
401
402     /* Free data */
403     if (client_entry->nickname)
404       silc_free(client_entry->nickname);
405     if (client_entry->server)
406       silc_free(client_entry->server);
407     if (client_entry->id)
408       silc_free(client_entry->id);
409     if (client_entry->send_key)
410       silc_cipher_free(client_entry->send_key);
411     if (client_entry->receive_key)
412       silc_cipher_free(client_entry->receive_key);
413     break;
414
415   case SILC_NOTIFY_TYPE_CMODE_CHANGE:
416     /*
417      * Someone changed a channel mode
418      */
419
420     /* Get Client ID */
421     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
422     if (!tmp)
423       goto out;
424
425     client_id = silc_id_payload_parse_id(tmp, tmp_len);
426     if (!client_id)
427       goto out;
428
429     /* Find Client entry */
430     client_entry = 
431       silc_client_get_client_by_id(client, conn, client_id);
432     if (!client_entry)
433       goto out;
434
435     /* Get the mode */
436     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
437     if (!tmp)
438       goto out;
439
440     SILC_GET32_MSB(mode, tmp);
441
442     /* Get channel entry */
443     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
444                                 SILC_ID_CHANNEL);
445     if (!channel_id)
446       goto out;
447     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
448                                      SILC_ID_CHANNEL, &id_cache))
449       break;
450
451     channel = (SilcChannelEntry)id_cache->context;
452
453     /* Save the new mode */
454     channel->mode = mode;
455
456     /* Notify application. The channel entry is sent last as this notify
457        is for channel but application don't know it from the arguments
458        sent by server. */
459     client->ops->notify(client, conn, type, client_entry, mode, channel);
460     break;
461
462   case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
463     /*
464      * Someone changed user's mode on a channel
465      */
466
467     /* Get Client ID */
468     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
469     if (!tmp)
470       goto out;
471
472     client_id = silc_id_payload_parse_id(tmp, tmp_len);
473     if (!client_id)
474       goto out;
475
476     /* Find Client entry */
477     client_entry = 
478       silc_client_get_client_by_id(client, conn, client_id);
479     if (!client_entry)
480       goto out;
481
482     /* Get the mode */
483     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
484     if (!tmp)
485       goto out;
486
487     SILC_GET32_MSB(mode, tmp);
488
489     /* Get target Client ID */
490     tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
491     if (!tmp)
492       goto out;
493
494     silc_free(client_id);
495     client_id = silc_id_payload_parse_id(tmp, tmp_len);
496     if (!client_id)
497       goto out;
498
499     /* Find target Client entry */
500     client_entry2 = 
501       silc_client_get_client_by_id(client, conn, client_id);
502     if (!client_entry2)
503       goto out;
504
505     /* Get channel entry */
506     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
507                                 SILC_ID_CHANNEL);
508     if (!channel_id)
509       goto out;
510     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
511                                      SILC_ID_CHANNEL, &id_cache))
512       break;
513
514     channel = (SilcChannelEntry)id_cache->context;
515
516     /* Save the mode */
517     silc_list_start(channel->clients);
518     while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
519       if (chu->client == client_entry) {
520         chu->mode = mode;
521         break;
522       }
523     }
524
525     /* Notify application. The channel entry is sent last as this notify
526        is for channel but application don't know it from the arguments
527        sent by server. */
528     client->ops->notify(client, conn, type, client_entry, mode, 
529                         client_entry2, channel);
530     break;
531
532   case SILC_NOTIFY_TYPE_MOTD:
533     /*
534      * Received Message of the day
535      */
536
537     /* Get motd */
538     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
539     if (!tmp)
540       goto out;
541     
542     /* Notify application */
543     client->ops->notify(client, conn, type, tmp);
544     break;
545
546   case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
547     /*
548      * Router has enforced a new ID to a channel. Let's change the old
549      * ID to the one provided here.
550      */
551
552     /* Get the old ID */
553     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
554     if (!tmp)
555       goto out;
556     channel_id = silc_id_payload_parse_id(tmp, tmp_len);
557     if (!channel_id)
558       goto out;
559     
560     /* Get the channel entry */
561     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
562                                      SILC_ID_CHANNEL, &id_cache))
563       break;
564
565     channel = (SilcChannelEntry)id_cache->context;
566
567     /* Free the old ID */
568     silc_free(channel_id);
569     silc_free(channel->id);
570
571     /* Get the new ID */
572     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
573     if (!tmp)
574       goto out;
575     channel->id = silc_id_payload_parse_id(tmp, tmp_len);
576     if (!channel->id)
577       goto out;
578
579     id_cache->id = (void *)channel->id;
580
581     /* Notify application */
582     client->ops->notify(client, conn, type, channel, channel);
583     break;
584
585   case SILC_NOTIFY_TYPE_KICKED:
586     /*
587      * A client (maybe me) was kicked from a channel
588      */
589
590     /* Get Client ID */
591     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
592     if (!tmp)
593       goto out;
594
595     client_id = silc_id_payload_parse_id(tmp, tmp_len);
596     if (!client_id)
597       goto out;
598
599     /* Find Client entry */
600     client_entry = 
601       silc_client_get_client_by_id(client, conn, client_id);
602     if (!client_entry)
603       goto out;
604
605     /* Get channel entry */
606     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
607                                 SILC_ID_CHANNEL);
608     if (!channel_id)
609       goto out;
610     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
611                                      SILC_ID_CHANNEL, &id_cache))
612       break;
613
614     channel = (SilcChannelEntry)id_cache->context;
615
616     /* Get comment */
617     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
618
619     /* Notify application. The channel entry is sent last as this notify
620        is for channel but application don't know it from the arguments
621        sent by server. */
622     client->ops->notify(client, conn, type, client_entry, tmp, channel);
623
624     /* If I was kicked from channel, remove the channel */
625     if (client_entry == conn->local_entry) {
626       if (conn->current_channel == channel)
627         conn->current_channel = NULL;
628       silc_idcache_del_by_id(conn->channel_cache, 
629                              SILC_ID_CHANNEL, channel->id);
630       silc_free(channel->channel_name);
631       silc_free(channel->id);
632       silc_free(channel->key);
633       silc_cipher_free(channel->channel_key);
634       silc_free(channel);
635     }
636     break;
637     
638   default:
639     break;
640   }
641
642  out:
643   silc_notify_payload_free(payload);
644   if (client_id)
645     silc_free(client_id);
646   if (channel_id)
647     silc_free(channel_id);
648 }