update.
[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 new Client ID */
357     tmp = silc_argument_get_arg_type(args, 2, &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 Client entry and if not found query it */
370     client_entry2 = 
371       silc_client_get_client_by_id(client, conn, client_id);
372     if (!client_entry2) {
373       silc_client_notify_by_server_resolve(client, conn, packet, client_id);
374       goto out;
375     }
376     silc_free(client_id);
377
378     /* Get old Client ID */
379     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
380     if (!tmp)
381       goto out;
382
383     client_id = silc_id_payload_parse_id(tmp, tmp_len);
384     if (!client_id)
385       goto out;
386
387     /* Find old Client entry */
388     client_entry = 
389       silc_client_get_client_by_id(client, conn, client_id);
390     if (!client_entry)
391       goto out;
392
393     /* Remove the old from cache */
394     silc_idcache_del_by_id(conn->client_cache, SILC_ID_CLIENT, 
395                            client_entry->id);
396
397     /* Replace old ID entry with new one on all channels. */
398     silc_client_replace_from_channels(client, conn, client_entry,
399                                       client_entry2);
400
401     /* Notify application */
402     client->ops->notify(client, conn, type, client_entry, client_entry2);
403
404     /* Free data */
405     if (client_entry->nickname)
406       silc_free(client_entry->nickname);
407     if (client_entry->server)
408       silc_free(client_entry->server);
409     if (client_entry->id)
410       silc_free(client_entry->id);
411     if (client_entry->send_key)
412       silc_cipher_free(client_entry->send_key);
413     if (client_entry->receive_key)
414       silc_cipher_free(client_entry->receive_key);
415     break;
416
417   case SILC_NOTIFY_TYPE_CMODE_CHANGE:
418     /*
419      * Someone changed a channel mode
420      */
421
422     /* Get Client ID */
423     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
424     if (!tmp)
425       goto out;
426
427     client_id = silc_id_payload_parse_id(tmp, tmp_len);
428     if (!client_id)
429       goto out;
430
431     /* Find Client entry */
432     client_entry = 
433       silc_client_get_client_by_id(client, conn, client_id);
434     if (!client_entry)
435       goto out;
436
437     /* Get the mode */
438     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
439     if (!tmp)
440       goto out;
441
442     SILC_GET32_MSB(mode, tmp);
443
444     /* Get channel entry */
445     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
446                                 SILC_ID_CHANNEL);
447     if (!channel_id)
448       goto out;
449     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
450                                      SILC_ID_CHANNEL, &id_cache))
451       break;
452
453     channel = (SilcChannelEntry)id_cache->context;
454
455     /* Save the new mode */
456     channel->mode = mode;
457
458     /* Notify application. The channel entry is sent last as this notify
459        is for channel but application don't know it from the arguments
460        sent by server. */
461     client->ops->notify(client, conn, type, client_entry, mode, channel);
462     break;
463
464   case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
465     /*
466      * Someone changed user's mode on a channel
467      */
468
469     /* Get Client ID */
470     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
471     if (!tmp)
472       goto out;
473
474     client_id = silc_id_payload_parse_id(tmp, tmp_len);
475     if (!client_id)
476       goto out;
477
478     /* Find Client entry */
479     client_entry = 
480       silc_client_get_client_by_id(client, conn, client_id);
481     if (!client_entry)
482       goto out;
483
484     /* Get the mode */
485     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
486     if (!tmp)
487       goto out;
488
489     SILC_GET32_MSB(mode, tmp);
490
491     /* Get target Client ID */
492     tmp = silc_argument_get_arg_type(args, 3, &tmp_len);
493     if (!tmp)
494       goto out;
495
496     silc_free(client_id);
497     client_id = silc_id_payload_parse_id(tmp, tmp_len);
498     if (!client_id)
499       goto out;
500
501     /* Find target Client entry */
502     client_entry2 = 
503       silc_client_get_client_by_id(client, conn, client_id);
504     if (!client_entry2)
505       goto out;
506
507     /* Get channel entry */
508     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
509                                 SILC_ID_CHANNEL);
510     if (!channel_id)
511       goto out;
512     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
513                                      SILC_ID_CHANNEL, &id_cache))
514       break;
515
516     channel = (SilcChannelEntry)id_cache->context;
517
518     /* Save the mode */
519     silc_list_start(channel->clients);
520     while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
521       if (chu->client == client_entry) {
522         chu->mode = mode;
523         break;
524       }
525     }
526
527     /* Notify application. The channel entry is sent last as this notify
528        is for channel but application don't know it from the arguments
529        sent by server. */
530     client->ops->notify(client, conn, type, client_entry, mode, 
531                         client_entry2, channel);
532     break;
533
534   case SILC_NOTIFY_TYPE_MOTD:
535     /*
536      * Received Message of the day
537      */
538
539     /* Get motd */
540     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
541     if (!tmp)
542       goto out;
543     
544     /* Notify application */
545     client->ops->notify(client, conn, type, tmp);
546     break;
547
548   case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
549     /*
550      * Router has enforced a new ID to a channel. Let's change the old
551      * ID to the one provided here.
552      */
553
554     /* Get the old ID */
555     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
556     if (!tmp)
557       goto out;
558     channel_id = silc_id_payload_parse_id(tmp, tmp_len);
559     if (!channel_id)
560       goto out;
561     
562     /* Get the channel entry */
563     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
564                                      SILC_ID_CHANNEL, &id_cache))
565       break;
566
567     channel = (SilcChannelEntry)id_cache->context;
568
569     /* Free the old ID */
570     silc_free(channel_id);
571     silc_free(channel->id);
572
573     /* Get the new ID */
574     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
575     if (!tmp)
576       goto out;
577     channel->id = silc_id_payload_parse_id(tmp, tmp_len);
578     if (!channel->id)
579       goto out;
580
581     id_cache->id = (void *)channel->id;
582
583     /* Notify application */
584     client->ops->notify(client, conn, type, channel, channel);
585     break;
586
587   case SILC_NOTIFY_TYPE_KICKED:
588     /*
589      * A client (maybe me) was kicked from a channel
590      */
591
592     /* Get Client ID */
593     tmp = silc_argument_get_arg_type(args, 1, &tmp_len);
594     if (!tmp)
595       goto out;
596
597     client_id = silc_id_payload_parse_id(tmp, tmp_len);
598     if (!client_id)
599       goto out;
600
601     /* Find Client entry */
602     client_entry = 
603       silc_client_get_client_by_id(client, conn, client_id);
604     if (!client_entry)
605       goto out;
606
607     /* Get channel entry */
608     channel_id = silc_id_str2id(packet->dst_id, packet->dst_id_len,
609                                 SILC_ID_CHANNEL);
610     if (!channel_id)
611       goto out;
612     if (!silc_idcache_find_by_id_one(conn->channel_cache, (void *)channel_id,
613                                      SILC_ID_CHANNEL, &id_cache))
614       break;
615
616     channel = (SilcChannelEntry)id_cache->context;
617
618     /* Get comment */
619     tmp = silc_argument_get_arg_type(args, 2, &tmp_len);
620
621     /* Notify application. The channel entry is sent last as this notify
622        is for channel but application don't know it from the arguments
623        sent by server. */
624     client->ops->notify(client, conn, type, client_entry, tmp, channel);
625
626     /* If I was kicked from channel, remove the channel */
627     if (client_entry == conn->local_entry) {
628       if (conn->current_channel == channel)
629         conn->current_channel = NULL;
630       silc_idcache_del_by_id(conn->channel_cache, 
631                              SILC_ID_CHANNEL, channel->id);
632       silc_free(channel->channel_name);
633       silc_free(channel->id);
634       silc_free(channel->key);
635       silc_cipher_free(channel->channel_key);
636       silc_free(channel);
637     }
638     break;
639     
640   default:
641     break;
642   }
643
644  out:
645   silc_notify_payload_free(payload);
646   if (client_id)
647     silc_free(client_id);
648   if (channel_id)
649     silc_free(channel_id);
650 }