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