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