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