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