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