Added support for computing message payload MAC in the new way
[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, 2006 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 /* Generic command reply callback. */
40
41 SILC_CLIENT_CMD_REPLY_FUNC(resume)
42 {
43   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
44   SILC_LOG_DEBUG(("Start"));
45   SILC_CLIENT_PENDING_EXEC(cmd, silc_command_get(cmd->payload));
46 }
47
48 /* Special command reply callback for IDENTIFY callbacks.  This calls
49    the pending callback for every returned command entry. */
50
51 SILC_CLIENT_CMD_REPLY_FUNC(resume_special)
52 {
53   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
54   int i;
55
56   SILC_LOG_DEBUG(("Start"));
57   for (i = 0; i < cmd->callbacks_count; i++)
58     if (cmd->callbacks[i].callback)
59       (*cmd->callbacks[i].callback)(cmd->callbacks[i].context, cmd);
60 }
61
62 /* Completion calling callback */
63
64 SILC_TASK_CALLBACK(silc_client_resume_call_completion)
65 {
66   SilcClientResumeSession session = context;
67   int i;
68
69   SILC_LOG_DEBUG(("Session completed"));
70
71   for (i = 0; i < session->cmd_idents_count; i++)
72     silc_client_command_pending_del(session->conn, SILC_COMMAND_IDENTIFY,
73                                     session->cmd_idents[i]);
74   silc_free(session->cmd_idents);
75
76   session->callback(session->client, session->conn, session->success,
77                     session->context);
78
79   memset(session, 'F', sizeof(*session));
80   silc_free(session);
81 }
82
83 /* This function is used to perform the resuming procedure after the
84    client has connected to the server properly and has received the
85    Client ID for the resumed session.  This resolves all channels
86    that the resumed client is joined, joined users, users modes
87    and channel modes.  The `callback' is called after this procedure
88    is completed. */
89
90 void silc_client_resume_session(SilcClient client,
91                                 SilcClientConnection conn,
92                                 SilcClientResumeSessionCallback callback,
93                                 void *context)
94 {
95   SilcClientResumeSession session;
96   SilcIDCacheList list;
97   SilcIDCacheEntry entry;
98   SilcChannelEntry channel;
99   SilcBuffer tmp;
100   int i;
101   SilcBool ret;
102
103   SILC_LOG_DEBUG(("Resuming detached session"));
104
105   session = silc_calloc(1, sizeof(*session));
106   if (!session) {
107     callback(client, conn, FALSE, context);
108     return;
109   }
110   session->client = client;
111   session->conn = conn;
112   session->callback = callback;
113   session->context = context;
114
115   /* First, send UMODE commandto get our own user mode in the network */
116   SILC_LOG_DEBUG(("Sending UMODE"));
117   tmp = silc_id_payload_encode(conn->local_entry->id, SILC_ID_CLIENT);
118   silc_client_command_send(client, conn, SILC_COMMAND_UMODE,
119                            conn->cmd_ident, 1, 1, tmp->data, tmp->len);
120   silc_buffer_free(tmp);
121
122   /* Second, send IDENTIFY command of all channels we know about.  These
123      are the channels we've joined to according our detachment data. */
124   if (silc_idcache_get_all(conn->internal->channel_cache, &list)) {
125     unsigned char **res_argv = NULL;
126     SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
127
128     session->channel_count = silc_idcache_list_count(list);
129
130     ret = silc_idcache_list_first(list, &entry);
131     while (ret) {
132       channel = entry->context;
133       tmp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
134       res_argv = silc_realloc(res_argv, sizeof(*res_argv) * (res_argc + 1));
135       res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
136                                    (res_argc + 1));
137       res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
138                                     (res_argc + 1));
139       res_argv[res_argc] = silc_memdup(tmp->data, tmp->len);
140       res_argv_lens[res_argc] = tmp->len;
141       res_argv_types[res_argc] = res_argc + 5;
142       res_argc++;
143       silc_buffer_free(tmp);
144       ret = silc_idcache_list_next(list, &entry);
145     }
146     silc_idcache_list_free(list);
147
148     if (res_argc) {
149       /* Send the IDENTIFY command */
150       SILC_LOG_DEBUG(("Sending IDENTIFY"));
151       silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
152                                    silc_client_command_reply_resume_special,
153                                    0, ++conn->cmd_ident);
154       silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY,
155                                   conn->cmd_ident,
156                                   silc_client_command_resume_identify,
157                                   session);
158
159       tmp = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
160                                         res_argc, res_argv, res_argv_lens,
161                                         res_argv_types, conn->cmd_ident);
162       silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND,
163                               NULL, 0, NULL, NULL, tmp->data, tmp->len, TRUE);
164
165       session->cmd_idents = silc_realloc(session->cmd_idents,
166                                          sizeof(*session->cmd_idents) *
167                                          (session->cmd_idents_count + 1));
168       session->cmd_idents[session->cmd_idents_count] = conn->cmd_ident;
169       session->cmd_idents_count++;
170
171       for (i = 0; i < res_argc; i++)
172         silc_free(res_argv[i]);
173       silc_free(res_argv);
174       silc_free(res_argv_lens);
175       silc_free(res_argv_types);
176       silc_buffer_free(tmp);
177     }
178   }
179
180   if (!session->channel_count)
181     RESUME_CALL_COMPLETION(client, session, TRUE);
182
183   /* Now, we wait for replies to come back and then continue with USERS,
184      CMODE and TOPIC commands. */
185 }
186
187 /* Received identify reply for a channel entry */
188
189 SILC_CLIENT_CMD_FUNC(resume_identify)
190 {
191   SilcClientResumeSession session = context;
192   SilcClientCommandReplyContext cmd = context2;
193   SilcClient client = session->client;
194   SilcClientConnection conn = session->conn;
195   unsigned char *tmp;
196   SilcUInt32 tmp_len;
197   SilcChannelEntry channel = NULL;
198   SilcChannelID *channel_id;
199   SilcIDPayload idp;
200   SilcIdType id_type;
201
202   SILC_LOG_DEBUG(("Start"));
203
204   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
205   if (!tmp)
206     goto err;
207
208   if (cmd->error != SILC_STATUS_OK) {
209     /* Delete unknown channel from our cache */
210     if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID) {
211       channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
212       if (channel_id) {
213         channel = silc_client_get_channel_by_id(client, conn, channel_id);
214         if (channel)
215           silc_client_del_channel(client, conn, channel);
216         silc_free(channel_id);
217       }
218     }
219     goto err;
220   }
221
222   idp = silc_id_payload_parse(tmp, tmp_len);
223   if (!idp) {
224     return;
225   }
226   id_type = silc_id_payload_get_type(idp);
227
228   switch (id_type) {
229   case SILC_ID_CHANNEL:
230     channel_id = silc_id_payload_get_id(idp);
231     channel = silc_client_get_channel_by_id(client, conn, channel_id);
232     silc_free(channel_id);
233     break;
234   default:
235     silc_id_payload_free(idp);
236     goto err;
237     break;
238   }
239
240   /* Now, send CMODE command for this channel.  We send only this one
241      because this will return also error if we are not currently joined
242      on this channel, plus we get the channel mode.  USERS and TOPIC
243      commands are called after this returns. */
244   if (channel) {
245     SILC_LOG_DEBUG(("Sending CMODE"));
246     silc_client_command_register(client, SILC_COMMAND_CMODE, NULL, NULL,
247                                  silc_client_command_reply_resume, 0,
248                                  ++conn->cmd_ident);
249     silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
250                              conn->cmd_ident, 1, 1, tmp, tmp_len);
251     silc_client_command_pending(conn, SILC_COMMAND_CMODE, conn->cmd_ident,
252                                 silc_client_command_resume_cmode, session);
253   }
254
255   silc_id_payload_free(idp);
256
257   if (cmd->status != SILC_STATUS_OK &&
258       cmd->status != SILC_STATUS_LIST_END)
259     return;
260
261   /* Unregister this command reply */
262   silc_client_command_unregister(client, SILC_COMMAND_IDENTIFY, NULL,
263                                  silc_client_command_reply_resume,
264                                  cmd->ident);
265   return;
266
267  err:
268   session->channel_count--;
269   if (!session->channel_count)
270     RESUME_CALL_COMPLETION(client, session, FALSE);
271 }
272
273 /* Received cmode to channel entry */
274
275 SILC_CLIENT_CMD_FUNC(resume_cmode)
276 {
277   SilcClientResumeSession session = context;
278   SilcClientCommandReplyContext cmd = context2;
279   SilcClient client = session->client;
280   SilcClientConnection conn = session->conn;
281   unsigned char *tmp;
282   SilcChannelID *channel_id;
283   SilcChannelEntry channel;
284   SilcUInt32 len;
285
286   SILC_LOG_DEBUG(("Start"));
287
288   /* Unregister this command reply */
289   silc_client_command_unregister(client, SILC_COMMAND_CMODE, NULL,
290                                  silc_client_command_reply_resume,
291                                  cmd->ident);
292
293   if (cmd->error != SILC_STATUS_OK)
294     goto err;
295
296   /* Take Channel ID */
297   tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
298   if (!tmp)
299     goto err;
300   channel_id = silc_id_payload_parse_id(tmp, len, NULL);
301   if (!channel_id)
302     goto err;
303
304   /* Get the channel entry */
305   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
306   if (channel) {
307
308     /* Get channel mode */
309     tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
310     if (tmp)
311       SILC_GET32_MSB(channel->mode, tmp);
312
313     tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
314
315     /* And now, we will send USERS to get users on the channel */
316     SILC_LOG_DEBUG(("Sending USERS"));
317     silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
318                                  silc_client_command_reply_users_i, 0,
319                                  ++conn->cmd_ident);
320     silc_client_command_send(client, conn, SILC_COMMAND_USERS,
321                              conn->cmd_ident, 1, 1, tmp, len);
322     silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
323                                 silc_client_command_resume_users, session);
324   }
325
326   silc_free(channel_id);
327   return;
328
329  err:
330   session->channel_count--;
331   if (!session->channel_count)
332     RESUME_CALL_COMPLETION(client, session, FALSE);
333 }
334
335 /* Received users reply to a channel entry */
336
337 SILC_CLIENT_CMD_FUNC(resume_users)
338 {
339   SilcClientResumeSession session = context;
340   SilcClientCommandReplyContext cmd = context2;
341   SilcClient client = session->client;
342   SilcClientConnection conn = session->conn;
343   SilcBufferStruct client_id_list, client_mode_list;
344   unsigned char *tmp;
345   SilcUInt32 tmp_len, list_count;
346   SilcChannelEntry channel;
347   SilcChannelID *channel_id = NULL;
348
349   SILC_LOG_DEBUG(("Start"));
350
351   /* Unregister this command reply */
352   silc_client_command_unregister(client, SILC_COMMAND_USERS, NULL,
353                                  silc_client_command_reply_users_i,
354                                  cmd->ident);
355
356   if (cmd->error != SILC_STATUS_OK)
357     goto err;
358
359   /* Get channel ID */
360   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
361   if (!tmp) {
362     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
363     goto err;
364   }
365   channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
366   if (!channel_id) {
367     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
368     goto err;
369   }
370
371   /* Get the list count */
372   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
373   if (!tmp) {
374     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
375     goto err;
376   }
377   SILC_GET32_MSB(list_count, tmp);
378
379   /* Get Client ID list */
380   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
381   if (!tmp) {
382     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
383     goto err;
384   }
385   silc_buffer_set(&client_id_list, tmp, tmp_len);
386
387   /* Get client mode list */
388   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
389   if (!tmp) {
390     COMMAND_REPLY_ERROR(SILC_STATUS_ERR_NOT_ENOUGH_PARAMS);
391     goto err;
392   }
393   silc_buffer_set(&client_mode_list, tmp, tmp_len);
394
395   /* Get channel entry */
396   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
397   if (!channel)
398     goto err;
399
400   /* Send fake JOIN command reply to application */
401   client->internal->ops->command_reply(client, conn, cmd->payload, TRUE,
402                                        SILC_COMMAND_JOIN, cmd->status,
403                                        channel->channel_name, channel,
404                                        channel->mode, 0,
405                                        NULL, NULL, NULL, NULL,
406                                        channel->hmac, list_count,
407                                        &client_id_list, client_mode_list);
408
409   /* Send TOPIC for this channel to get the topic */
410   SILC_LOG_DEBUG(("Sending TOPIC"));
411   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
412   silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
413                            ++conn->cmd_ident, 1, 1, tmp, tmp_len);
414
415   /* Call the completion callback after we've got reply to all of
416      our channels */
417   session->channel_count--;
418   if (!session->channel_count)
419     RESUME_CALL_COMPLETION(client, session, TRUE);
420
421   silc_free(channel_id);
422   return;
423
424  err:
425   silc_free(channel_id);
426   session->channel_count--;
427   if (!session->channel_count)
428     RESUME_CALL_COMPLETION(client, session, FALSE);
429 }