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 = 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   SilcHmac hmac = NULL;
380   unsigned int id_len, len;
381   unsigned char *id_string;
382   char *channel_name, *tmp;
383   unsigned int mode, created;
384   SilcBuffer keyp;
385
386   COMMAND_CHECK_STATUS;
387
388   /* Get channel name */
389   channel_name = silc_argument_get_arg_type(cmd->args, 2, NULL);
390   if (!channel_name)
391     goto out;
392
393   /* Get channel ID */
394   id_string = silc_argument_get_arg_type(cmd->args, 3, &id_len);
395   if (!id_string)
396     goto out;
397
398   /* Get mode mask */
399   tmp = silc_argument_get_arg_type(cmd->args, 4, NULL);
400   if (!tmp)
401     goto out;
402   SILC_GET32_MSB(mode, tmp);
403
404   /* Get created boolean value */
405   tmp = silc_argument_get_arg_type(cmd->args, 5, NULL);
406   if (!tmp)
407     goto out;
408   SILC_GET32_MSB(created, tmp);
409   if (created != 0 && created != 1)
410     goto out;
411
412   /* Get channel key */
413   tmp = silc_argument_get_arg_type(cmd->args, 6, &len);
414   if (!tmp)
415     goto out;
416   keyp = silc_buffer_alloc(len);
417   silc_buffer_pull_tail(keyp, SILC_BUFFER_END(keyp));
418   silc_buffer_put(keyp, tmp, len);
419
420   id = silc_id_payload_parse_id(id_string, id_len);
421   if (!id)
422     goto out;
423
424   /* Get hmac */
425   tmp = silc_argument_get_arg_type(cmd->args, 10, NULL);
426   if (tmp) {
427     if (!silc_hmac_alloc(tmp, NULL, &hmac))
428       goto out;
429   }
430
431   /* See whether we already have the channel. */
432   entry = silc_idlist_find_channel_by_id(server->local_list, id, NULL);
433   if (!entry) {
434     /* Add new channel */
435
436     SILC_LOG_DEBUG(("Adding new [%s] channel %s id(%s)", 
437                     (created == 0 ? "existing" : "created"), channel_name,
438                     silc_id_render(id, SILC_ID_CHANNEL)));
439
440     /* Add the channel to our local list. */
441     entry = silc_idlist_add_channel(server->local_list, strdup(channel_name), 
442                                     SILC_CHANNEL_MODE_NONE, id, 
443                                     server->router, NULL, hmac);
444     if (!entry) {
445       silc_free(id);
446       goto out;
447     }
448   } else {
449     silc_free(id);
450   }
451
452   /* If channel was not created we know there is global users on the 
453      channel. */
454   entry->global_users = (created == 0 ? TRUE : FALSE);
455
456   /* If channel was just created the mask must be zero */
457   if (!entry->global_users && mode) {
458     SILC_LOG_DEBUG(("Buggy router `%s' sent non-zero mode mask for "
459                     "new channel, forcing it to zero", cmd->sock->hostname));
460     mode = 0;
461   }
462
463   /* Save channel mode */
464   entry->mode = mode;
465
466   /* Save channel key */
467   silc_server_save_channel_key(server, keyp, entry);
468   silc_buffer_free(keyp);
469
470   /* Execute any pending commands */
471   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_JOIN);
472
473  out:
474   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_JOIN);
475   silc_server_command_reply_free(cmd);
476 }
477
478 SILC_SERVER_CMD_REPLY_FUNC(users)
479 {
480   SilcServerCommandReplyContext cmd = (SilcServerCommandReplyContext)context;
481   SilcServer server = cmd->server;
482   SilcCommandStatus status;
483   SilcChannelEntry channel;
484   SilcChannelID *channel_id = NULL;
485   SilcBuffer client_id_list;
486   SilcBuffer client_mode_list;
487   unsigned char *tmp;
488   unsigned int tmp_len;
489   unsigned int list_count, i;
490
491   COMMAND_CHECK_STATUS;
492
493   /* Get channel ID */
494   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
495   if (!tmp)
496     goto out;
497   channel_id = silc_id_payload_parse_id(tmp, tmp_len);
498   if (!channel_id)
499     goto out;
500
501   /* Get the list count */
502   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
503   if (!tmp)
504     goto out;
505   SILC_GET32_MSB(list_count, tmp);
506
507   /* Get Client ID list */
508   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
509   if (!tmp)
510     goto out;
511
512   client_id_list = silc_buffer_alloc(tmp_len);
513   silc_buffer_pull_tail(client_id_list, tmp_len);
514   silc_buffer_put(client_id_list, tmp, tmp_len);
515
516   /* Get client mode list */
517   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
518   if (!tmp)
519     goto out;
520
521   client_mode_list = silc_buffer_alloc(tmp_len);
522   silc_buffer_pull_tail(client_mode_list, tmp_len);
523   silc_buffer_put(client_mode_list, tmp, tmp_len);
524
525   /* Get channel entry */
526   channel = silc_idlist_find_channel_by_id(server->local_list, 
527                                            channel_id, NULL);
528   if (!channel) {
529     channel = silc_idlist_find_channel_by_id(server->global_list, 
530                                              channel_id, NULL);
531     if (!channel)
532       goto out;
533   }
534
535   /* Cache the received Client ID's and modes. This cache expires
536      whenever server sends notify message to channel. It means two things;
537      some user has joined or leaved the channel. XXX! */
538   for (i = 0; i < list_count; i++) {
539     unsigned short idp_len;
540     unsigned int mode;
541     SilcClientID *client_id;
542     SilcClientEntry client;
543
544     /* Client ID */
545     SILC_GET16_MSB(idp_len, client_id_list->data + 2);
546     idp_len += 4;
547     client_id = silc_id_payload_parse_id(client_id_list->data, idp_len);
548     if (!client_id)
549       continue;
550     silc_buffer_pull(client_id_list, idp_len);
551     
552     /* Mode */
553     SILC_GET32_MSB(mode, client_mode_list->data);
554     silc_buffer_pull(client_mode_list, 4);
555
556     /* Check if we have this client cached already. */
557     client = silc_idlist_find_client_by_id(server->local_list, client_id,
558                                            NULL);
559     if (!client)
560       client = silc_idlist_find_client_by_id(server->global_list, 
561                                              client_id, NULL);
562     if (!client) {
563       /* If router did not find such Client ID in its lists then this must
564          be bogus client or some router in the net is buggy. */
565       if (server->server_type == SILC_ROUTER)
566         goto out;
567
568       /* We don't have that client anywhere, add it. The client is added
569          to global list since server didn't have it in the lists so it must be 
570          global. */
571       client = silc_idlist_add_client(server->global_list, NULL, NULL, 
572                                       NULL, client_id, cmd->sock->user_data, 
573                                       NULL);
574       if (!client) {
575         silc_free(client_id);
576         continue;
577       }
578     } else {
579       /* We have the client already. */
580       silc_free(client_id);
581     }
582
583     if (!silc_server_client_on_channel(client, channel)) {
584       /* Client was not on the channel, add it. */
585       SilcChannelClientEntry chl = silc_calloc(1, sizeof(*chl));
586       chl->client = client;
587       chl->mode = mode;
588       chl->channel = channel;
589       silc_list_add(channel->user_list, chl);
590       silc_list_add(client->channels, chl);
591     }
592   }
593
594   silc_buffer_free(client_id_list);
595   silc_buffer_free(client_mode_list);
596
597   /* Execute any pending commands */
598   SILC_SERVER_PENDING_EXEC(cmd, SILC_COMMAND_USERS);
599
600  out:
601   SILC_SERVER_PENDING_DESTRUCTOR(cmd, SILC_COMMAND_USERS);
602   if (channel_id)
603     silc_free(channel_id);
604   silc_server_command_reply_free(cmd);
605 }