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