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