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   session->callback(session->client, session->conn, session->success,
212                     session->context);
213
214   for (i = 0; i < session->cmd_idents_count; i++)
215     silc_client_command_pending_del(session->conn, SILC_COMMAND_IDENTIFY, 
216                                     session->cmd_idents[i]);
217   silc_free(session->cmd_idents);
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   /* Now, we wait for replies to come back and then continue with USERS,
321      CMODE and TOPIC commands. */
322 }
323
324 /* Received identify reply for a channel entry */
325
326 SILC_CLIENT_CMD_FUNC(resume_identify)
327 {
328   SilcClientResumeSession session = context;
329   SilcClientCommandReplyContext cmd = context2;
330   SilcClient client = session->client;
331   SilcClientConnection conn = session->conn;
332   unsigned char *tmp;
333   SilcUInt32 tmp_len;
334   SilcChannelEntry channel = NULL;
335   SilcChannelID *channel_id;
336   SilcIDPayload idp;
337   SilcIdType id_type;
338
339   SILC_LOG_DEBUG(("Start"));
340
341   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
342   if (!tmp)
343     goto err;
344
345   if (cmd->error != SILC_STATUS_OK) {
346     /* Delete unknown channel from our cache */
347     if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID) {
348       channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
349       if (channel_id) {
350         channel = silc_client_get_channel_by_id(client, conn, channel_id);
351         if (channel)
352           silc_client_del_channel(client, conn, channel);
353         silc_free(channel_id);
354       }
355     }
356     goto err;
357   }
358
359   idp = silc_id_payload_parse(tmp, tmp_len);
360   if (!idp) {
361     return;
362   }
363   id_type = silc_id_payload_get_type(idp);
364
365   switch (id_type) {
366   case SILC_ID_CHANNEL:
367     channel_id = silc_id_payload_get_id(idp);
368     channel = silc_client_get_channel_by_id(client, conn, channel_id);
369     silc_free(channel_id);
370     break;
371   default:
372     silc_id_payload_free(idp);
373     goto err;
374     break;
375   }
376
377   /* Now, send CMODE command for this channel.  We send only this one
378      because this will return also error if we are not currently joined
379      on this channel, plus we get the channel mode.  USERS and TOPIC
380      commands are called after this returns. */
381   if (channel) {
382     SILC_LOG_DEBUG(("Sending CMODE"));
383     silc_client_command_register(client, SILC_COMMAND_CMODE, NULL, NULL,
384                                  silc_client_command_reply_resume, 0,
385                                  ++conn->cmd_ident);
386     silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
387                              conn->cmd_ident, 1, 1, tmp, tmp_len);
388     silc_client_command_pending(conn, SILC_COMMAND_CMODE, conn->cmd_ident,
389                                 silc_client_command_resume_cmode, session);
390   }
391
392   silc_id_payload_free(idp);
393
394   if (cmd->status != SILC_STATUS_OK &&
395       cmd->status != SILC_STATUS_LIST_END)
396     return;
397
398   /* Unregister this command reply */
399   silc_client_command_unregister(client, SILC_COMMAND_IDENTIFY, NULL, 
400                                  silc_client_command_reply_resume,
401                                  cmd->ident);
402   return;
403
404  err:
405   session->channel_count--;
406   if (!session->channel_count)
407     RESUME_CALL_COMPLETION(client, session, FALSE);
408 }
409
410 /* Received cmode to channel entry */
411
412 SILC_CLIENT_CMD_FUNC(resume_cmode)
413 {
414   SilcClientResumeSession session = context;
415   SilcClientCommandReplyContext cmd = context2;
416   SilcClient client = session->client;
417   SilcClientConnection conn = session->conn;
418   unsigned char *tmp;
419   SilcChannelID *channel_id;
420   SilcChannelEntry channel;
421   SilcUInt32 len;
422
423   SILC_LOG_DEBUG(("Start"));
424
425   /* Unregister this command reply */
426   silc_client_command_unregister(client, SILC_COMMAND_CMODE, NULL, 
427                                  silc_client_command_reply_resume,
428                                  cmd->ident);
429
430   if (cmd->error != SILC_STATUS_OK)
431     goto err;
432
433   /* Take Channel ID */
434   tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
435   if (!tmp)
436     goto err;
437   channel_id = silc_id_payload_parse_id(tmp, len, NULL);
438   if (!channel_id)
439     goto err;
440
441   /* Get the channel entry */
442   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
443   if (channel) {
444
445     /* Get channel mode */
446     tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
447     if (tmp)
448       SILC_GET32_MSB(channel->mode, tmp);
449
450     tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
451
452     /* And now, we will send USERS to get users on the channel */
453     SILC_LOG_DEBUG(("Sending USERS"));
454     silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
455                                  silc_client_command_reply_users_i, 0,
456                                  ++conn->cmd_ident);
457     silc_client_command_send(client, conn, SILC_COMMAND_USERS,
458                              conn->cmd_ident, 1, 1, tmp, len);
459     silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
460                                 silc_client_command_resume_users, session);
461   }
462
463   silc_free(channel_id);
464   return;
465
466  err:
467   session->channel_count--;
468   if (!session->channel_count)
469     RESUME_CALL_COMPLETION(client, session, FALSE);
470 }
471
472 /* Received users reply to a channel entry */
473
474 SILC_CLIENT_CMD_FUNC(resume_users)
475 {
476   SilcClientResumeSession session = context;
477   SilcClientCommandReplyContext cmd = context2;
478   SilcClient client = session->client;
479   SilcClientConnection conn = session->conn;
480   SilcBufferStruct client_id_list, client_mode_list;
481   unsigned char *tmp;
482   SilcUInt32 tmp_len, list_count;
483   SilcChannelEntry channel;
484   SilcChannelID *channel_id = NULL;
485
486   SILC_LOG_DEBUG(("Start"));
487
488   /* Unregister this command reply */
489   silc_client_command_unregister(client, SILC_COMMAND_USERS, NULL, 
490                                  silc_client_command_reply_users_i,
491                                  cmd->ident);
492
493   if (cmd->error != SILC_STATUS_OK)
494     goto err;
495
496   /* Get channel ID */
497   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
498   if (!tmp) {
499     COMMAND_REPLY_ERROR;
500     goto err;
501   }
502   channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
503   if (!channel_id) {
504     COMMAND_REPLY_ERROR;
505     goto err;
506   }
507
508   /* Get the list count */
509   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
510   if (!tmp) {
511     COMMAND_REPLY_ERROR;
512     goto err;
513   }
514   SILC_GET32_MSB(list_count, tmp);
515
516   /* Get Client ID list */
517   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
518   if (!tmp) {
519     COMMAND_REPLY_ERROR;
520     goto err;
521   }
522   silc_buffer_set(&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     COMMAND_REPLY_ERROR;
528     goto err;
529   }
530   silc_buffer_set(&client_mode_list, tmp, tmp_len);
531
532   /* Get channel entry */
533   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
534   if (!channel)
535     goto err;
536
537   /* Send fake JOIN command reply to application */
538   client->internal->ops->command_reply(client, conn, cmd->payload, TRUE,
539                                        SILC_COMMAND_JOIN, cmd->status,
540                                        channel->channel_name, channel,
541                                        channel->mode, 0, 
542                                        NULL, NULL, NULL, NULL, 
543                                        channel->hmac, list_count,
544                                        &client_id_list, client_mode_list);
545
546   /* Send TOPIC for this channel to get the topic */
547   SILC_LOG_DEBUG(("Sending TOPIC"));
548   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
549   silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
550                            ++conn->cmd_ident, 1, 1, tmp, tmp_len);
551
552   /* Call the completion callback after we've got reply to all of
553      our channels */
554   session->channel_count--;
555   if (!session->channel_count)
556     RESUME_CALL_COMPLETION(client, session, TRUE);
557
558   silc_free(channel_id);
559   return;
560
561  err:
562   silc_free(channel_id);
563   session->channel_count--;
564   if (!session->channel_count)
565     RESUME_CALL_COMPLETION(client, session, FALSE);
566 }