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_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_WHOIS);
235
236  out:
237   silc_server_command_reply_free(cmd);
238 }
239
240 /* Caches the received IDENTIFY information. */
241
242 static char
243 silc_server_command_reply_identify_save(SilcServerCommandReplyContext cmd)
244 {
245   SilcServer server = cmd->server;
246   int len, id_len;
247   unsigned char *id_data;
248   char *nickname, *username;
249   SilcClientID *client_id;
250   SilcClientEntry client;
251   SilcIDCacheEntry cache = NULL;
252   char global = FALSE;
253   char *nick = NULL;
254
255   id_data = silc_argument_get_arg_type(cmd->args, 2, &id_len);
256   nickname = silc_argument_get_arg_type(cmd->args, 3, &len);
257   username = silc_argument_get_arg_type(cmd->args, 4, &len);
258   if (!id_data)
259     return FALSE;
260
261   client_id = silc_id_payload_parse_id(id_data, id_len);
262   if (!client_id)
263     return FALSE;
264
265   /* Check if we have this client cached already. */
266
267   client = silc_idlist_find_client_by_id(server->local_list, client_id,
268                                          &cache);
269   if (!client) {
270     client = silc_idlist_find_client_by_id(server->global_list, 
271                                            client_id, &cache);
272     global = TRUE;
273   }
274
275   if (!client) {
276     /* If router did not find such Client ID in its lists then this must
277        be bogus client or some router in the net is buggy. */
278     if (server->server_type == SILC_ROUTER)
279       return FALSE;
280
281     /* Take hostname out of nick string if it includes it. */
282     if (nickname) {
283       if (strchr(nickname, '@')) {
284         int len = strcspn(nickname, "@");
285         nick = silc_calloc(len + 1, sizeof(char));
286         memcpy(nick, nickname, len);
287       } else {
288         nick = strdup(nickname);
289       }
290     }
291
292     /* We don't have that client anywhere, add it. The client is added
293        to global list since server didn't have it in the lists so it must be 
294        global. */
295     silc_idlist_add_client(server->global_list, nick,
296                            username ? strdup(username) : NULL, NULL,
297                            client_id, cmd->sock->user_data, NULL);
298   } else {
299     /* We have the client already, update the data */
300
301     SILC_LOG_DEBUG(("Updating client data"));
302
303     /* Take hostname out of nick string if it includes it. */
304     if (nickname) {
305       if (strchr(nickname, '@')) {
306         int len = strcspn(nickname, "@");
307         nick = silc_calloc(len + 1, sizeof(char));
308         memcpy(nick, nickname, len);
309       } else {
310         nick = strdup(nickname);
311       }
312     }
313
314     if (nickname && client->nickname)
315       silc_free(client->nickname);
316
317     if (nickname)
318       client->nickname = nick;
319
320     if (username && client->username) {
321       silc_free(client->username);
322       client->username = strdup(username);
323     }
324
325     if (nickname && cache) {
326       cache->data = nick;
327       silc_idcache_sort_by_data(global ? server->global_list->clients : 
328                                 server->local_list->clients);
329     }
330
331     silc_free(client_id);
332   }
333
334   return TRUE;
335 }
336
337 /* Received reply for forwarded IDENTIFY command. We have received the
338    requested identify information now and we will cache it. After this we
339    will call the pending command so that the requestee gets the information
340    after all. */
341
342 SILC_SERVER_CMD_REPLY_FUNC(identify)
343 {
344   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
345   SilcCommandStatus status;
346
347   COMMAND_CHECK_STATUS_LIST;
348
349   if (!silc_server_command_reply_identify_save(cmd))
350     goto out;
351
352   /* Execute any pending commands */
353   SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_IDENTIFY);
354
355  out:
356   silc_server_command_reply_free(cmd);
357 }
358
359 /* Received reply for forwarded JOIN command. Router has created or joined
360    the client to the channel. We save some channel information locally
361    for future use. */
362
363 SILC_SERVER_CMD_REPLY_FUNC(join)
364 {
365   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
366   SilcServer server = cmd->server;
367   SilcCommandStatus status;
368   SilcChannelID *id;
369   SilcChannelEntry entry;
370   unsigned int id_len, len;
371   unsigned char *id_string;
372   char *channel_name, *tmp;
373   unsigned int mode, created;
374   SilcBuffer keyp;
375
376   COMMAND_CHECK_STATUS;
377
378   /* Get channel name */
379   channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL);
380   if (!channel_name)
381     goto out;
382
383   /* Get channel ID */
384   id_string = silc_argument_get_arg_type(cmd->args, 3, &id_len);
385   if (!id_string)
386     goto out;
387
388   /* Get mode mask */
389   tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
390   if (!tmp)
391     goto out;
392   SILC_GET32_MSB(mode, tmp);
393
394   /* Get created boolean value */
395   tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
396   if (!tmp)
397     goto out;
398   SILC_GET32_MSB(created, tmp);
399   if (created != 0 && created != 1)
400     goto out;
401
402   /* Get channel key */
403   tmp = silc_argument_get_arg_type(cmd->args, 6, &len);
404   if (!tmp)
405     goto out;
406   keyp = silc_buffer_alloc(len);
407   silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp));
408   silc_buffer_put(keyp, tmp, len);
409
410   id = silc_id_payload_parse_id(id_string, id_len);
411   if (!id)
412     goto out;
413
414   /* See whether we already have the channel. */
415   entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
416   if (!entry) {
417     /* Add new channel */
418
419     SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", 
420                     (created == 0 ? "existing" : "created"), channel_name,
421                     silc_id_render(id, SILC_ID_CHANNEL)));
422
423     /* Add the channel to our local list. */
424     entry = silc_idlist_add_channel(server->local_list, strdup(channel_name), 
425                                     SILC_CHANNEL_MODE_NONE, id, 
426                                     server->router, NULL);
427     if (!entry) {
428       silc_free(id);
429       goto out;
430     }
431   } else {
432     silc_free(id);
433   }
434
435   /* If channel was not created we know there is global users on the 
436      channel. */
437   entry->global_users = (created == 0 ? TRUE : FALSE);
438
439   /* If channel was just created the mask must be zero */
440   if (!entry->global_users && mode) {
441     SILC_LOG_DEBUG(("Buggy router `%s' sent non-zero mode mask for "
442                     "new channel, forcing it to zero", cmd->sock->hostname));
443     mode = 0;
444   }
445
446   /* Save channel mode */
447   entry->mode = mode;
448
449   /* Save channel key */
450   silc_server_save_channel_key(server, keyp, entry);
451   silc_buffer_free(keyp);
452
453   /* Execute any pending commands */
454   SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_JOIN);
455
456  out:
457   silc_server_command_reply_free(cmd);
458 }
459
460 SILC_SERVER_CMD_REPLY_FUNC(users)
461 {
462   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
463   SilcServer server = cmd->server;
464   SilcCommandStatus status;
465   SilcChannelEntry channel;
466   SilcChannelID *channel_id = NULL;
467   SilcBuffer client_id_list;
468   SilcBuffer client_mode_list;
469   unsigned char *tmp;
470   unsigned int tmp_len;
471   unsigned int list_count, i;
472
473   COMMAND_CHECK_STATUS;
474
475   /* Get channel ID */
476   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
477   if (!tmp)
478     goto out;
479   channel_id = silc_id_payload_parse_id(tmp, tmp_len);
480   if (!channel_id)
481     goto out;
482
483   /* Get the list count */
484   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
485   if (!tmp)
486     goto out;
487   SILC_GET32_MSB(list_count, tmp);
488
489   /* Get Client ID list */
490   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
491   if (!tmp)
492     goto out;
493
494   client_id_list = silc_buffer_alloc(tmp_len);
495   silc_buffer_pull_tail(client_id_list, tmp_len);
496   silc_buffer_put(client_id_list, tmp, tmp_len);
497
498   /* Get client mode list */
499   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
500   if (!tmp)
501     goto out;
502
503   client_mode_list = silc_buffer_alloc(tmp_len);
504   silc_buffer_pull_tail(client_mode_list, tmp_len);
505   silc_buffer_put(client_mode_list, tmp, tmp_len);
506
507   /* Get channel entry */
508   channel = silc_idlist_find_channel_by_id(server->local_list, 
509                                            channel_id, NULL);
510   if (!channel) {
511     channel = silc_idlist_find_channel_by_id(server->global_list, 
512                                              channel_id, NULL);
513     if (!channel)
514       goto out;
515   }
516
517   /* Cache the received Client ID's and modes. This cache expires
518      whenever server sends notify message to channel. It means two things;
519      some user has joined or leaved the channel. XXX! */
520   for (i = 0; i < list_count; i++) {
521     unsigned short idp_len;
522     unsigned int mode;
523     SilcClientID *client_id;
524     SilcClientEntry client;
525
526     /* Client ID */
527     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
528     idp_len += 4;
529     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
530     if (!client_id)
531       continue;
532     silc_buffer_pull(client_id_list, idp_len);
533     
534     /* Mode */
535     SILC_GET32_MSB(mode, client_mode_list->data);
536     silc_buffer_pull(client_mode_list, 4);
537
538     /* Check if we have this client cached already. */
539     client = silc_idlist_find_client_by_id(server->local_list, client_id,
540                                            NULL);
541     if (!client)
542       client = silc_idlist_find_client_by_id(server->global_list, 
543                                              client_id, NULL);
544     if (!client) {
545       /* If router did not find such Client ID in its lists then this must
546          be bogus client or some router in the net is buggy. */
547       if (server->server_type == SILC_ROUTER)
548         goto out;
549
550       /* We don't have that client anywhere, add it. The client is added
551          to global list since server didn't have it in the lists so it must be 
552          global. */
553       client = silc_idlist_add_client(server->global_list, NULL, NULL, 
554                                       NULL, client_id, cmd->sock->user_data, 
555                                       NULL);
556       if (!client) {
557         silc_free(client_id);
558         continue;
559       }
560     } else {
561       /* We have the client already. */
562       silc_free(client_id);
563     }
564
565     if (!silc_server_client_on_channel(client, channel)) {
566       /* Client was not on the channel, add it. */
567       SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl));
568       chl->client = client;
569       chl->mode = mode;
570       chl->channel = channel;
571       silc_list_add(channel->user_list, chl);
572       silc_list_add(client->channels, chl);
573     }
574   }
575
576   silc_buffer_free(client_id_list);
577   silc_buffer_free(client_mode_list);
578
579   /* Execute any pending commands */
580   SILC_SERVER_COMMAND_EXEC_PENDING(cmd, SILC_COMMAND_USERS);
581
582  out:
583   if (channel_id)
584     silc_free(channel_id);
585   silc_server_command_reply_free(cmd);
586 }