updates.
[silc.git] / apps / silcd / command_reply.c
1 /*
2
3   command_reply.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
22 #include "serverincludes.h"
23 #include "server_internal.h"
24 #include "command_reply.h"
25
26 #define COMMAND_CHECK_STATUS                                              \
27 do {                                                                      \
28   SILC_LOG_DEBUG(("Start"));                                              \
29   SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \
30   if (status != SILC_STATUS_OK) {                                         \
31     silc_server_command_reply_free(cmd);                                  \
32     return;                                                               \
33   }                                                                       \
34 } while(0)
35
36 #define COMMAND_CHECK_STATUS_LIST                                         \
37 do {                                                                      \
38   SILC_LOG_DEBUG(("Start"));                                              \
39   SILC_GET16_MSB(status, silc_argument_get_arg_type(cmd->args, 1, NULL)); \
40   if (status != SILC_STATUS_OK &&                                         \
41       status != SILC_STATUS_LIST_START &&                                 \
42       status != SILC_STATUS_LIST_ITEM &&                                  \
43       status != SILC_STATUS_LIST_END) {                                   \
44     silc_server_command_reply_free(cmd);                                  \
45     return;                                                               \
46   }                                                                       \
47 } while(0)
48
49 /* Server command reply list. Not all commands have reply function as
50    they are never sent by server. More maybe added later if need appears. */
51 SilcServerCommandReply silc_command_reply_list[] =
52 {
53   SILC_SERVER_CMD_REPLY(join, JOIN),
54   SILC_SERVER_CMD_REPLY(whois, WHOIS),
55   SILC_SERVER_CMD_REPLY(identify, IDENTIFY),
56   SILC_SERVER_CMD_REPLY(users, USERS),
57
58   { NULL, 0 },
59 };
60
61 /* Process received command reply. */
62
63 void silc_server_command_reply_process(SilcServer server,
64                                        SilcSocketConnection sock,
65                                        SilcBuffer buffer)
66 {
67   SilcServerCommandReply *cmd;
68   SilcServerCommandReplyContext ctx;
69   SilcCommandPayload payload;
70   SilcCommand command;
71   unsigned short ident;
72
73   SILC_LOG_DEBUG(("Start"));
74
75   /* Get command reply payload from packet */
76   payload = silc_command_payload_parse(buffer);
77   if (!payload) {
78     /* Silently ignore bad reply packet */
79     SILC_LOG_DEBUG(("Bad command reply packet"));
80     return;
81   }
82   
83   /* Allocate command reply context. This must be free'd by the
84      command reply routine receiving it. */
85   ctx = silc_calloc(1, sizeof(*ctx));
86   ctx->server = server;
87   ctx->sock = sock;
88   ctx->payload = payload;
89   ctx->args = silc_command_get_args(ctx->payload);
90   ident = silc_command_get_ident(ctx->payload);
91       
92   /* Check for pending commands and mark to be exeucted */
93   silc_server_command_pending_check(server, ctx, 
94                                     silc_command_get(ctx->payload), ident);
95
96   /* Execute command reply */
97   command = silc_command_get(ctx->payload);
98   for (cmd = silc_command_reply_list; cmd->cb; cmd++)
99     if (cmd->cmd == command)
100       break;
101
102   if (cmd == NULL || !cmd->cb) {
103     silc_free(ctx);
104     return;
105   }
106
107   cmd->cb(ctx);
108 }
109
110 /* Free command reply context and its internals. */
111
112 void silc_server_command_reply_free(SilcServerCommandReplyContext cmd)
113 {
114   if (cmd) {
115     silc_command_free_payload(cmd->payload);
116     silc_free(cmd);
117   }
118 }
119
120 /* Caches the received WHOIS information. If we are normal server currently
121    we cache global information only for short period of time.  */
122 /* XXX cache expirying not implemented yet! */
123
124 static char
125 silc_server_command_reply_whois_save(SilcServerCommandReplyContext cmd)
126 {
127   SilcServer server = cmd->server;
128   int len, id_len;
129   unsigned char *id_data;
130   char *nickname, *username, *realname;
131   SilcClientID *client_id;
132   SilcClientEntry client;
133   SilcIDCacheEntry cache = NULL;
134   char global = FALSE;
135   char *nick;
136
137   id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
138   nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
139   username = silc_argument_get_arg_type(cmd->args, 4, &len);
140   realname = silc_argument_get_arg_type(cmd->args, 5, &len);
141   if (!id_data || !nickname || !username || !realname) 
142     return FALSE;
143
144   client_id = silc_id_payload_parse_id(id_data, id_len);
145   if (!client_id)
146     return FALSE;
147
148   /* Check if we have this client cached already. */
149
150   client = silc_idlist_find_client_by_id(server->local_list, client_id,
151                                          &cache);
152   if (!client) {
153     client = silc_idlist_find_client_by_id(server->global_list, 
154                                            client_id, &cache);
155     global = TRUE;
156   }
157
158   if (!client) {
159     /* If router did not find such Client ID in its lists then this must
160        be bogus client or some router in the net is buggy. */
161     if (server->server_type == SILC_ROUTER)
162       return FALSE;
163
164     /* Take hostname out of nick string if it includes it. */
165     if (strchr(nickname, '@')) {
166       int len = strcspn(nickname, "@");
167       nick = silc_calloc(len + 1, sizeof(char));
168       memcpy(nick, nickname, len);
169     } else {
170       nick = strdup(nickname);
171     }
172
173     /* We don't have that client anywhere, add it. The client is added
174        to global list since server didn't have it in the lists so it must be 
175        global. */
176     silc_idlist_add_client(server->global_list, nick,
177                            strdup(username), 
178                            strdup(realname), client_id, 
179                            cmd->sock->user_data, NULL);
180   } else {
181     /* We have the client already, update the data */
182
183     SILC_LOG_DEBUG(("Updating client data"));
184
185     /* Take hostname out of nick string if it includes it. */
186     if (strchr(nickname, '@')) {
187       int len = strcspn(nickname, "@");
188       nick = silc_calloc(len + 1, sizeof(char));
189       memcpy(nick, nickname, len);
190     } else {
191       nick = strdup(nickname);
192     }
193
194     if (client->nickname)
195       silc_free(client->nickname);
196     if (client->username)
197       silc_free(client->username);
198     if (client->userinfo)
199       silc_free(client->userinfo);
200     
201     client->nickname = nick;
202     client->username = strdup(username);
203     client->userinfo = strdup(realname);
204
205     if (cache) {
206       cache->data = nick;
207       silc_idcache_sort_by_data(global ? server->global_list->clients : 
208                                 server->local_list->clients);
209     }
210
211     silc_free(client_id);
212   }
213
214   return TRUE;
215 }
216
217 /* Reiceved reply for WHOIS command. We sent the whois request to our
218    primary router, if we are normal server, and thus has now received reply
219    to the command. We will figure out what client originally sent us the
220    command and will send the reply to it.  If we are router we will figure
221    out who server sent us the command and send reply to that one. */
222
223 SILC_SERVER_CMD_REPLY_FUNC(whois)
224 {
225   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
226   SilcCommandStatus status;
227
228   COMMAND_CHECK_STATUS_LIST;
229
230   if (!silc_server_command_reply_whois_save(cmd))
231     goto out;
232
233   /* Execute any pending commands */
234   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_WHOIS);
235
236  out:
237   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_WHOIS);
238   silc_server_command_reply_free(cmd);
239 }
240
241 /* Caches the received IDENTIFY information. */
242
243 static char
244 silc_server_command_reply_identify_save(SilcServerCommandReplyContext cmd)
245 {
246   SilcServer server = cmd->server;
247   int len, id_len;
248   unsigned char *id_data;
249   char *nickname, *username;
250   SilcClientID *client_id;
251   SilcClientEntry client;
252   SilcIDCacheEntry cache = NULL;
253   char global = FALSE;
254   char *nick = NULL;
255
256   id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
257   nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
258   username = silc_argument_get_arg_type(cmd->args, 4, &len);
259   if (!id_data)
260     return FALSE;
261
262   client_id = silc_id_payload_parse_id(id_data, id_len);
263   if (!client_id)
264     return FALSE;
265
266   /* Check if we have this client cached already. */
267
268   client = silc_idlist_find_client_by_id(server->local_list, client_id,
269                                          &cache);
270   if (!client) {
271     client = silc_idlist_find_client_by_id(server->global_list, 
272                                            client_id, &cache);
273     global = TRUE;
274   }
275
276   if (!client) {
277     /* If router did not find such Client ID in its lists then this must
278        be bogus client or some router in the net is buggy. */
279     if (server->server_type == SILC_ROUTER)
280       return FALSE;
281
282     /* Take hostname out of nick string if it includes it. */
283     if (nickname) {
284       if (strchr(nickname, '@')) {
285         int len = strcspn(nickname, "@");
286         nick = silc_calloc(len + 1, sizeof(char));
287         memcpy(nick, nickname, len);
288       } else {
289         nick = strdup(nickname);
290       }
291     }
292
293     /* We don't have that client anywhere, add it. The client is added
294        to global list since server didn't have it in the lists so it must be 
295        global. */
296     silc_idlist_add_client(server->global_list, nick,
297                            username ? strdup(username) : NULL, NULL,
298                            client_id, cmd->sock->user_data, NULL);
299   } else {
300     /* We have the client already, update the data */
301
302     SILC_LOG_DEBUG(("Updating client data"));
303
304     /* Take hostname out of nick string if it includes it. */
305     if (nickname) {
306       if (strchr(nickname, '@')) {
307         int len = strcspn(nickname, "@");
308         nick = silc_calloc(len + 1, sizeof(char));
309         memcpy(nick, nickname, len);
310       } else {
311         nick = strdup(nickname);
312       }
313     }
314
315     if (nickname && client->nickname)
316       silc_free(client->nickname);
317
318     if (nickname)
319       client->nickname = nick;
320
321     if (username && client->username) {
322       silc_free(client->username);
323       client->username = strdup(username);
324     }
325
326     if (nickname && cache) {
327       cache->data = nick;
328       silc_idcache_sort_by_data(global ? server->global_list->clients : 
329                                 server->local_list->clients);
330     }
331
332     silc_free(client_id);
333   }
334
335   return TRUE;
336 }
337
338 /* Received reply for forwarded IDENTIFY command. We have received the
339    requested identify information now and we will cache it. After this we
340    will call the pending command so that the requestee gets the information
341    after all. */
342
343 SILC_SERVER_CMD_REPLY_FUNC(identify)
344 {
345   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
346   SilcCommandStatus status;
347
348   COMMAND_CHECK_STATUS_LIST;
349
350   if (!silc_server_command_reply_identify_save(cmd))
351     goto out;
352
353   /* Execute any pending commands */
354   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_IDENTIFY);
355
356  out:
357   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_IDENTIFY);
358   silc_server_command_reply_free(cmd);
359 }
360
361 /* Received reply for forwarded JOIN command. Router has created or joined
362    the client to the channel. We save some channel information locally
363    for future use. */
364
365 SILC_SERVER_CMD_REPLY_FUNC(join)
366 {
367   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
368   SilcServer server = cmd->server;
369   SilcCommandStatus status;
370   SilcChannelID *id;
371   SilcChannelEntry entry;
372   unsigned int id_len, len;
373   unsigned char *id_string;
374   char *channel_name, *tmp;
375   unsigned int mode, created;
376   SilcBuffer keyp;
377
378   COMMAND_CHECK_STATUS;
379
380   /* Get channel name */
381   channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL);
382   if (!channel_name)
383     goto out;
384
385   /* Get channel ID */
386   id_string = silc_argument_get_arg_type(cmd->args, 3, &id_len);
387   if (!id_string)
388     goto out;
389
390   /* Get mode mask */
391   tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
392   if (!tmp)
393     goto out;
394   SILC_GET32_MSB(mode, tmp);
395
396   /* Get created boolean value */
397   tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
398   if (!tmp)
399     goto out;
400   SILC_GET32_MSB(created, tmp);
401   if (created != 0 && created != 1)
402     goto out;
403
404   /* Get channel key */
405   tmp = silc_argument_get_arg_type(cmd->args, 6, &len);
406   if (!tmp)
407     goto out;
408   keyp = silc_buffer_alloc(len);
409   silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp));
410   silc_buffer_put(keyp, tmp, len);
411
412   id = silc_id_payload_parse_id(id_string, id_len);
413   if (!id)
414     goto out;
415
416   /* See whether we already have the channel. */
417   entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
418   if (!entry) {
419     /* Add new channel */
420
421     SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", 
422                     (created == 0 ? "existing" : "created"), channel_name,
423                     silc_id_render(id, SILC_ID_CHANNEL)));
424
425     /* Add the channel to our local list. */
426     entry = silc_idlist_add_channel(server->local_list, strdup(channel_name), 
427                                     SILC_CHANNEL_MODE_NONE, id, 
428                                     server->router, NULL);
429     if (!entry) {
430       silc_free(id);
431       goto out;
432     }
433   } else {
434     silc_free(id);
435   }
436
437   /* If channel was not created we know there is global users on the 
438      channel. */
439   entry->global_users = (created == 0 ? TRUE : FALSE);
440
441   /* If channel was just created the mask must be zero */
442   if (!entry->global_users && mode) {
443     SILC_LOG_DEBUG(("Buggy router `%s' sent non-zero mode mask for "
444                     "new channel, forcing it to zero", cmd->sock->hostname));
445     mode = 0;
446   }
447
448   /* Save channel mode */
449   entry->mode = mode;
450
451   /* Save channel key */
452   silc_server_save_channel_key(server, keyp, entry);
453   silc_buffer_free(keyp);
454
455   /* Execute any pending commands */
456   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_JOIN);
457
458  out:
459   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN);
460   silc_server_command_reply_free(cmd);
461 }
462
463 SILC_SERVER_CMD_REPLY_FUNC(users)
464 {
465   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
466   SilcServer server = cmd->server;
467   SilcCommandStatus status;
468   SilcChannelEntry channel;
469   SilcChannelID *channel_id = NULL;
470   SilcBuffer client_id_list;
471   SilcBuffer client_mode_list;
472   unsigned char *tmp;
473   unsigned int tmp_len;
474   unsigned int list_count, i;
475
476   COMMAND_CHECK_STATUS;
477
478   /* Get channel ID */
479   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
480   if (!tmp)
481     goto out;
482   channel_id = silc_id_payload_parse_id(tmp, tmp_len);
483   if (!channel_id)
484     goto out;
485
486   /* Get the list count */
487   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
488   if (!tmp)
489     goto out;
490   SILC_GET32_MSB(list_count, tmp);
491
492   /* Get Client ID list */
493   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
494   if (!tmp)
495     goto out;
496
497   client_id_list = silc_buffer_alloc(tmp_len);
498   silc_buffer_pull_tail(client_id_list, tmp_len);
499   silc_buffer_put(client_id_list, tmp, tmp_len);
500
501   /* Get client mode list */
502   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
503   if (!tmp)
504     goto out;
505
506   client_mode_list = silc_buffer_alloc(tmp_len);
507   silc_buffer_pull_tail(client_mode_list, tmp_len);
508   silc_buffer_put(client_mode_list, tmp, tmp_len);
509
510   /* Get channel entry */
511   channel = silc_idlist_find_channel_by_id(server->local_list, 
512                                            channel_id, NULL);
513   if (!channel) {
514     channel = silc_idlist_find_channel_by_id(server->global_list, 
515                                              channel_id, NULL);
516     if (!channel)
517       goto out;
518   }
519
520   /* Cache the received Client ID's and modes. This cache expires
521      whenever server sends notify message to channel. It means two things;
522      some user has joined or leaved the channel. XXX! */
523   for (i = 0; i < list_count; i++) {
524     unsigned short idp_len;
525     unsigned int mode;
526     SilcClientID *client_id;
527     SilcClientEntry client;
528
529     /* Client ID */
530     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
531     idp_len += 4;
532     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
533     if (!client_id)
534       continue;
535     silc_buffer_pull(client_id_list, idp_len);
536     
537     /* Mode */
538     SILC_GET32_MSB(mode, client_mode_list->data);
539     silc_buffer_pull(client_mode_list, 4);
540
541     /* Check if we have this client cached already. */
542     client = silc_idlist_find_client_by_id(server->local_list, client_id,
543                                            NULL);
544     if (!client)
545       client = silc_idlist_find_client_by_id(server->global_list, 
546                                              client_id, NULL);
547     if (!client) {
548       /* If router did not find such Client ID in its lists then this must
549          be bogus client or some router in the net is buggy. */
550       if (server->server_type == SILC_ROUTER)
551         goto out;
552
553       /* We don't have that client anywhere, add it. The client is added
554          to global list since server didn't have it in the lists so it must be 
555          global. */
556       client = silc_idlist_add_client(server->global_list, NULL, NULL, 
557                                       NULL, client_id, cmd->sock->user_data, 
558                                       NULL);
559       if (!client) {
560         silc_free(client_id);
561         continue;
562       }
563     } else {
564       /* We have the client already. */
565       silc_free(client_id);
566     }
567
568     if (!silc_server_client_on_channel(client, channel)) {
569       /* Client was not on the channel, add it. */
570       SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl));
571       chl->client = client;
572       chl->mode = mode;
573       chl->channel = channel;
574       silc_list_add(channel->user_list, chl);
575       silc_list_add(client->channels, chl);
576     }
577   }
578
579   silc_buffer_free(client_id_list);
580   silc_buffer_free(client_mode_list);
581
582   /* Execute any pending commands */
583   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_USERS);
584
585  out:
586   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS);
587   if (channel_id)
588     silc_free(channel_id);
589   silc_server_command_reply_free(cmd);
590 }