Added SILC Server library.
[silc.git] / lib / silcclient / client_resume.c
1 /*
2
3   client_resume.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2002, 2004 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; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22 #include "silcclient.h"
23 #include "client_internal.h"
24
25 SILC_CLIENT_CMD_REPLY_FUNC(resume);
26 SILC_CLIENT_CMD_FUNC(resume_identify);
27 SILC_CLIENT_CMD_FUNC(resume_cmode);
28 SILC_CLIENT_CMD_FUNC(resume_users);
29
30 #define RESUME_CALL_COMPLETION(client, session, s)                      \
31 do {                                                                    \
32   SILC_LOG_DEBUG(("Calling completion"));                               \
33   session->success = s;                                                 \
34   silc_schedule_task_add(client->schedule, 0,                           \
35                          silc_client_resume_call_completion, session,   \
36                          0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);   \
37 } while(0)
38
39 /* Generates the session detachment data. This data can be used later
40    to resume back to the server. */
41
42 SilcBuffer silc_client_get_detach_data(SilcClient client,
43                                        SilcClientConnection conn)
44 {
45   SilcBuffer detach;
46   SilcHashTableList htl;
47   SilcChannelUser chu;
48   int ch_count;
49
50   SILC_LOG_DEBUG(("Creating detachment data"));
51
52   ch_count = silc_hash_table_count(conn->local_entry->channels);
53
54   /* Save the nickname, Client ID and user mode in SILC network */
55   detach = silc_buffer_alloc_size(2 + strlen(conn->nickname) +
56                                   2 + conn->local_id_data_len + 4 + 4);
57   silc_buffer_format(detach,
58                      SILC_STR_UI_SHORT(strlen(conn->nickname)),
59                      SILC_STR_UI_XNSTRING(conn->nickname,
60                                           strlen(conn->nickname)),
61                      SILC_STR_UI_SHORT(conn->local_id_data_len),
62                      SILC_STR_UI_XNSTRING(conn->local_id_data,
63                                           conn->local_id_data_len),
64                      SILC_STR_UI_INT(conn->local_entry->mode),
65                      SILC_STR_UI_INT(ch_count),
66                      SILC_STR_END);
67
68   /* Save all joined channels */
69   silc_hash_table_list(conn->local_entry->channels, &htl);
70   while (silc_hash_table_get(&htl, NULL, (void *)&chu)) {
71     unsigned char *chid = silc_id_id2str(chu->channel->id, SILC_ID_CHANNEL);
72     SilcUInt16 chid_len = silc_id_get_len(chu->channel->id, SILC_ID_CHANNEL);
73
74     detach = silc_buffer_realloc(detach, detach->truelen + 2 +
75                                  strlen(chu->channel->channel_name) +
76                                  2 + chid_len + 4);
77     silc_buffer_pull(detach, detach->len);
78     silc_buffer_pull_tail(detach, 2 + strlen(chu->channel->channel_name) +
79                           2 + chid_len + 4);
80     silc_buffer_format(detach,
81                        SILC_STR_UI_SHORT(strlen(chu->channel->channel_name)),
82                        SILC_STR_UI_XNSTRING(chu->channel->channel_name,
83                                            strlen(chu->channel->channel_name)),
84                        SILC_STR_UI_SHORT(chid_len),
85                        SILC_STR_UI_XNSTRING(chid, chid_len),
86                        SILC_STR_UI_INT(chu->channel->mode),
87                        SILC_STR_END);
88     silc_free(chid);
89   }
90   silc_hash_table_list_reset(&htl);
91
92   silc_buffer_push(detach, detach->data - detach->head);
93
94   SILC_LOG_HEXDUMP(("Detach data"), detach->data, detach->len);
95
96   return detach;
97 }
98
99 /* Processes the detachment data. This creates channels and other
100    stuff according the data found in the the connection parameters.
101    This doesn't actually resolve any detailed information from the
102    server.  To do that call silc_client_resume_session function.
103    This returns the old detached session client ID. */
104
105 SilcBool silc_client_process_detach_data(SilcClient client,
106                                      SilcClientConnection conn,
107                                      unsigned char **old_id,
108                                      SilcUInt16 *old_id_len)
109 {
110   SilcBufferStruct detach;
111   SilcUInt32 ch_count;
112   int i, len;
113   char *newnick;
114
115   SILC_LOG_DEBUG(("Start"));
116
117   silc_buffer_set(&detach, conn->internal->params.detach_data,
118                   conn->internal->params.detach_data_len);
119
120   SILC_LOG_HEXDUMP(("Detach data"), detach.data, detach.len);
121
122   *old_id = NULL;
123   *old_id_len = 0;
124
125   /* Take the old client ID from the detachment data */
126   len = silc_buffer_unformat(&detach,
127                              SILC_STR_UI16_NSTRING_ALLOC(&newnick,
128                                                          NULL),
129                              SILC_STR_UI16_NSTRING_ALLOC(old_id, old_id_len),
130                              SILC_STR_UI_INT(NULL),
131                              SILC_STR_UI_INT(&ch_count),
132                              SILC_STR_END);
133   if (len == -1)
134     return FALSE;
135   if (!newnick || !(*old_id) || !(*old_id_len))
136     return FALSE;
137
138   silc_free(conn->nickname);
139   conn->nickname = newnick;
140
141   silc_buffer_pull(&detach, len);
142
143   for (i = 0; i < ch_count; i++) {
144     char *channel;
145     unsigned char *chid;
146     SilcUInt16 chid_len;
147     SilcUInt32 ch_mode;
148     SilcChannelID *channel_id;
149     SilcChannelEntry channel_entry;
150
151     len = silc_buffer_unformat(&detach,
152                                SILC_STR_UI16_NSTRING_ALLOC(&channel, NULL),
153                                SILC_STR_UI16_NSTRING(&chid, &chid_len),
154                                SILC_STR_UI_INT(&ch_mode),
155                                SILC_STR_END);
156     if (len == -1)
157       return FALSE;
158
159     /* Add new channel */
160     channel_id = silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL);
161     channel_entry = silc_client_get_channel_by_id(client, conn, channel_id);
162     if (!channel_entry) {
163       channel_entry = silc_client_add_channel(client, conn, channel, ch_mode,
164                                               channel_id);
165     } else {
166       silc_free(channel);
167       silc_free(channel_id);
168     }
169
170     silc_buffer_pull(&detach, len);
171   }
172   silc_buffer_push(&detach, detach.data - detach.head);
173
174   return TRUE;
175 }
176
177
178 /* Resume session context */
179 typedef struct {
180   SilcClient client;
181   SilcClientConnection conn;
182   SilcClientResumeSessionCallback callback;
183   void *context;
184   SilcUInt32 channel_count;
185   SilcUInt32 *cmd_idents;
186   SilcUInt32 cmd_idents_count;
187   SilcBool success;
188 } *SilcClientResumeSession;
189
190 /* Generic command reply callback. */
191
192 SILC_CLIENT_CMD_REPLY_FUNC(resume)
193 {
194   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
195   SILC_LOG_DEBUG(("Start"));
196   SILC_CLIENT_PENDING_EXEC(cmd, silc_command_get(cmd->payload));
197 }
198
199 /* Special command reply callback for IDENTIFY callbacks.  This calls
200    the pending callback for every returned command entry. */
201
202 SILC_CLIENT_CMD_REPLY_FUNC(resume_special)
203 {
204   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
205   int i;
206
207   SILC_LOG_DEBUG(("Start"));
208   for (i = 0; i < cmd->callbacks_count; i++)
209     if (cmd->callbacks[i].callback)
210       (*cmd->callbacks[i].callback)(cmd->callbacks[i].context, cmd);
211 }
212
213 /* Completion calling callback */
214
215 SILC_TASK_CALLBACK(silc_client_resume_call_completion)
216 {
217   SilcClientResumeSession session = context;
218   int i;
219
220   SILC_LOG_DEBUG(("Session completed"));
221
222   for (i = 0; i < session->cmd_idents_count; i++)
223     silc_client_command_pending_del(session->conn, SILC_COMMAND_IDENTIFY,
224                                     session->cmd_idents[i]);
225   silc_free(session->cmd_idents);
226
227   session->callback(session->client, session->conn, session->success,
228                     session->context);
229
230   memset(session, 'F', sizeof(*session));
231   silc_free(session);
232 }
233
234 /* This function is used to perform the resuming procedure after the
235    client has connected to the server properly and has received the
236    Client ID for the resumed session.  This resolves all channels
237    that the resumed client is joined, joined users, users modes
238    and channel modes.  The `callback' is called after this procedure
239    is completed. */
240
241 void silc_client_resume_session(SilcClient client,
242                                 SilcClientConnection conn,
243                                 SilcClientResumeSessionCallback callback,
244                                 void *context)
245 {
246   SilcClientResumeSession session;
247   SilcIDCacheList list;
248   SilcIDCacheEntry entry;
249   SilcChannelEntry channel;
250   SilcBuffer tmp;
251   int i;
252   SilcBool ret;
253
254   SILC_LOG_DEBUG(("Resuming detached session"));
255
256   session = silc_calloc(1, sizeof(*session));
257   if (!session) {
258     callback(client, conn, FALSE, context);
259     return;
260   }
261   session->client = client;
262   session->conn = conn;
263   session->callback = callback;
264   session->context = context;
265
266   /* First, send UMODE commandto get our own user mode in the network */
267   SILC_LOG_DEBUG(("Sending UMODE"));
268   tmp = silc_id_payload_encode(conn->local_entry->id, SILC_ID_CLIENT);
269   silc_client_command_send(client, conn, SILC_COMMAND_UMODE,
270                            conn->cmd_ident, 1, 1, tmp->data, tmp->len);
271   silc_buffer_free(tmp);
272
273   /* Second, send IDENTIFY command of all channels we know about.  These
274      are the channels we've joined to according our detachment data. */
275   if (silc_idcache_get_all(conn->internal->channel_cache, &list)) {
276     unsigned char **res_argv = NULL;
277     SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
278
279     session->channel_count = silc_idcache_list_count(list);
280
281     ret = silc_idcache_list_first(list, &entry);
282     while (ret) {
283       channel = entry->context;
284       tmp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
285       res_argv = silc_realloc(res_argv, sizeof(*res_argv) * (res_argc + 1));
286       res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
287                                    (res_argc + 1));
288       res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
289                                     (res_argc + 1));
290       res_argv[res_argc] = silc_memdup(tmp->data, tmp->len);
291       res_argv_lens[res_argc] = tmp->len;
292       res_argv_types[res_argc] = res_argc + 5;
293       res_argc++;
294       silc_buffer_free(tmp);
295       ret = silc_idcache_list_next(list, &entry);
296     }
297     silc_idcache_list_free(list);
298
299     if (res_argc) {
300       /* Send the IDENTIFY command */
301       SILC_LOG_DEBUG(("Sending IDENTIFY"));
302       silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
303                                    silc_client_command_reply_resume_special,
304                                    0, ++conn->cmd_ident);
305       silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY,
306                                   conn->cmd_ident,
307                                   silc_client_command_resume_identify,
308                                   session);
309
310       tmp = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
311                                         res_argc, res_argv, res_argv_lens,
312                                         res_argv_types, conn->cmd_ident);
313       silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND,
314                               NULL, 0, NULL, NULL, tmp->data, tmp->len, TRUE);
315
316       session->cmd_idents = silc_realloc(session->cmd_idents,
317                                          sizeof(*session->cmd_idents) *
318                                          (session->cmd_idents_count + 1));
319       session->cmd_idents[session->cmd_idents_count] = conn->cmd_ident;
320       session->cmd_idents_count++;
321
322       for (i = 0; i < res_argc; i++)
323         silc_free(res_argv[i]);
324       silc_free(res_argv);
325       silc_free(res_argv_lens);
326       silc_free(res_argv_types);
327       silc_buffer_free(tmp);
328     }
329   }
330
331   if (!session->channel_count)
332     RESUME_CALL_COMPLETION(client, session, TRUE);
333
334   /* Now, we wait for replies to come back and then continue with USERS,
335      CMODE and TOPIC commands. */
336 }
337
338 /* Received identify reply for a channel entry */
339
340 SILC_CLIENT_CMD_FUNC(resume_identify)
341 {
342   SilcClientResumeSession session = context;
343   SilcClientCommandReplyContext cmd = context2;
344   SilcClient client = session->client;
345   SilcClientConnection conn = session->conn;
346   unsigned char *tmp;
347   SilcUInt32 tmp_len;
348   SilcChannelEntry channel = NULL;
349   SilcChannelID *channel_id;
350   SilcIDPayload idp;
351   SilcIdType id_type;
352
353   SILC_LOG_DEBUG(("Start"));
354
355   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
356   if (!tmp)
357     goto err;
358
359   if (cmd->error != SILC_STATUS_OK) {
360     /* Delete unknown channel from our cache */
361     if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID) {
362       channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
363       if (channel_id) {
364         channel = silc_client_get_channel_by_id(client, conn, channel_id);
365         if (channel)
366           silc_client_del_channel(client, conn, channel);
367         silc_free(channel_id);
368       }
369     }
370     goto err;
371   }
372
373   idp = silc_id_payload_parse(tmp, tmp_len);
374   if (!idp) {
375     return;
376   }
377   id_type = silc_id_payload_get_type(idp);
378
379   switch (id_type) {
380   case SILC_ID_CHANNEL:
381     channel_id = silc_id_payload_get_id(idp);
382     channel = silc_client_get_channel_by_id(client, conn, channel_id);
383     silc_free(channel_id);
384     break;
385   default:
386     silc_id_payload_free(idp);
387     goto err;
388     break;
389   }
390
391   /* Now, send CMODE command for this channel.  We send only this one
392      because this will return also error if we are not currently joined
393      on this channel, plus we get the channel mode.  USERS and TOPIC
394      commands are called after this returns. */
395   if (channel) {
396     SILC_LOG_DEBUG(("Sending CMODE"));
397     silc_client_command_register(client, SILC_COMMAND_CMODE, NULL, NULL,
398                                  silc_client_command_reply_resume, 0,
399                                  ++conn->cmd_ident);
400     silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
401                              conn->cmd_ident, 1, 1, tmp, tmp_len);
402     silc_client_command_pending(conn, SILC_COMMAND_CMODE, conn->cmd_ident,
403                                 silc_client_command_resume_cmode, session);
404   }
405
406   silc_id_payload_free(idp);
407
408   if (cmd->status != SILC_STATUS_OK &&
409       cmd->status != SILC_STATUS_LIST_END)
410     return;
411
412   /* Unregister this command reply */
413   silc_client_command_unregister(client, SILC_COMMAND_IDENTIFY, NULL,
414                                  silc_client_command_reply_resume,
415                                  cmd->ident);
416   return;
417
418  err:
419   session->channel_count--;
420   if (!session->channel_count)
421     RESUME_CALL_COMPLETION(client, session, FALSE);
422 }
423
424 /* Received cmode to channel entry */
425
426 SILC_CLIENT_CMD_FUNC(resume_cmode)
427 {
428   SilcClientResumeSession session = context;
429   SilcClientCommandReplyContext cmd = context2;
430   SilcClient client = session->client;
431   SilcClientConnection conn = session->conn;
432   unsigned char *tmp;
433   SilcChannelID *channel_id;
434   SilcChannelEntry channel;
435   SilcUInt32 len;
436
437   SILC_LOG_DEBUG(("Start"));
438
439   /* Unregister this command reply */
440   silc_client_command_unregister(client, SILC_COMMAND_CMODE, NULL,
441                                  silc_client_command_reply_resume,
442                                  cmd->ident);
443
444   if (cmd->error != SILC_STATUS_OK)
445     goto err;
446
447   /* Take Channel ID */
448   tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
449   if (!tmp)
450     goto err;
451   channel_id = silc_id_payload_parse_id(tmp, len, NULL);
452   if (!channel_id)
453     goto err;
454
455   /* Get the channel entry */
456   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
457   if (channel) {
458
459     /* Get channel mode */
460     tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
461     if (tmp)
462       SILC_GET32_MSB(channel->mode, tmp);
463
464     tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
465
466     /* And now, we will send USERS to get users on the channel */
467     SILC_LOG_DEBUG(("Sending USERS"));
468     silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
469                                  silc_client_command_reply_users_i, 0,
470                                  ++conn->cmd_ident);
471     silc_client_command_send(client, conn, SILC_COMMAND_USERS,
472                              conn->cmd_ident, 1, 1, tmp, len);
473     silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
474                                 silc_client_command_resume_users, session);
475   }
476
477   silc_free(channel_id);
478   return;
479
480  err:
481   session->channel_count--;
482   if (!session->channel_count)
483     RESUME_CALL_COMPLETION(client, session, FALSE);
484 }
485
486 /* Received users reply to a channel entry */
487
488 SILC_CLIENT_CMD_FUNC(resume_users)
489 {
490   SilcClientResumeSession session = context;
491   SilcClientCommandReplyContext cmd = context2;
492   SilcClient client = session->client;
493   SilcClientConnection conn = session->conn;
494   SilcBufferStruct client_id_list, client_mode_list;
495   unsigned char *tmp;
496   SilcUInt32 tmp_len, list_count;
497   SilcChannelEntry channel;
498   SilcChannelID *channel_id = NULL;
499
500   SILC_LOG_DEBUG(("Start"));
501
502   /* Unregister this command reply */
503   silc_client_command_unregister(client, SILC_COMMAND_USERS, NULL,
504                                  silc_client_command_reply_users_i,
505                                  cmd->ident);
506
507   if (cmd->error != SILC_STATUS_OK)
508     goto err;
509
510   /* Get channel ID */
511   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
512   if (!tmp) {
513     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
514     goto err;
515   }
516   channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
517   if (!channel_id) {
518     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
519     goto err;
520   }
521
522   /* Get the list count */
523   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
524   if (!tmp) {
525     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
526     goto err;
527   }
528   SILC_GET32_MSB(list_count, tmp);
529
530   /* Get Client ID list */
531   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
532   if (!tmp) {
533     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
534     goto err;
535   }
536   silc_buffer_set(&client_id_list, tmp, tmp_len);
537
538   /* Get client mode list */
539   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
540   if (!tmp) {
541     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
542     goto err;
543   }
544   silc_buffer_set(&client_mode_list, tmp, tmp_len);
545
546   /* Get channel entry */
547   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
548   if (!channel)
549     goto err;
550
551   /* Send fake JOIN command reply to application */
552   client->internal->ops->command_reply(client, conn, cmd->payload, TRUE,
553                                        SILC_COMMAND_JOIN, cmd->status,
554                                        channel->channel_name, channel,
555                                        channel->mode, 0,
556                                        NULL, NULL, NULL, NULL,
557                                        channel->hmac, list_count,
558                                        &client_id_list, client_mode_list);
559
560   /* Send TOPIC for this channel to get the topic */
561   SILC_LOG_DEBUG(("Sending TOPIC"));
562   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
563   silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
564                            ++conn->cmd_ident, 1, 1, tmp, tmp_len);
565
566   /* Call the completion callback after we've got reply to all of
567      our channels */
568   session->channel_count--;
569   if (!session->channel_count)
570     RESUME_CALL_COMPLETION(client, session, TRUE);
571
572   silc_free(channel_id);
573   return;
574
575  err:
576   silc_free(channel_id);
577   session->channel_count--;
578   if (!session->channel_count)
579     RESUME_CALL_COMPLETION(client, session, FALSE);
580 }