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