update.
[silc.git] / lib / silcclient / command.c
1 /*
2
3   command.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1997 - 2000 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; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "clientlibincludes.h"
23
24 /* Client command list. */
25 SilcClientCommand silc_command_list[] =
26 {
27   SILC_CLIENT_CMD(whois, WHOIS, "WHOIS", SILC_CF_LAG | SILC_CF_REG, 3),
28   SILC_CLIENT_CMD(whowas, WHOWAS, "WHOWAS", SILC_CF_LAG | SILC_CF_REG, 3),
29   SILC_CLIENT_CMD(identify, IDENTIFY, "IDENTIFY", 
30                   SILC_CF_LAG | SILC_CF_REG, 3),
31   SILC_CLIENT_CMD(nick, NICK, "NICK", SILC_CF_LAG | SILC_CF_REG, 2),
32   SILC_CLIENT_CMD(list, LIST, "LIST", SILC_CF_LAG | SILC_CF_REG, 2),
33   SILC_CLIENT_CMD(topic, TOPIC, "TOPIC", SILC_CF_LAG | SILC_CF_REG, 3),
34   SILC_CLIENT_CMD(invite, INVITE, "INVITE", SILC_CF_LAG | SILC_CF_REG, 3),
35   SILC_CLIENT_CMD(quit, QUIT, "QUIT", SILC_CF_LAG | SILC_CF_REG, 1),
36   SILC_CLIENT_CMD(kill, KILL, "KILL", 
37                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
38   SILC_CLIENT_CMD(info, INFO, "INFO", SILC_CF_LAG | SILC_CF_REG, 2),
39   SILC_CLIENT_CMD(connect, CONNECT, "CONNECT",
40                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
41   SILC_CLIENT_CMD(ping, PING, "PING", SILC_CF_LAG | SILC_CF_REG, 2),
42   SILC_CLIENT_CMD(oper, OPER, "OPER",
43                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
44   SILC_CLIENT_CMD(join, JOIN, "JOIN", SILC_CF_LAG | SILC_CF_REG, 4),
45   SILC_CLIENT_CMD(motd, MOTD, "MOTD", SILC_CF_LAG | SILC_CF_REG, 2),
46   SILC_CLIENT_CMD(umode, UMODE, "UMODE", SILC_CF_LAG | SILC_CF_REG, 2),
47   SILC_CLIENT_CMD(cmode, CMODE, "CMODE", SILC_CF_LAG | SILC_CF_REG, 4),
48   SILC_CLIENT_CMD(cumode, CUMODE, "CUMODE", SILC_CF_LAG | SILC_CF_REG, 5),
49   SILC_CLIENT_CMD(kick, KICK, "KICK", SILC_CF_LAG | SILC_CF_REG, 2),
50   SILC_CLIENT_CMD(restart, RESTART, "RESTART",
51                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
52   SILC_CLIENT_CMD(close, CLOSE, "CLOSE",
53                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
54   SILC_CLIENT_CMD(die, DIE, "DIE",
55                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_OPER, 2),
56   SILC_CLIENT_CMD(silcoper, SILCOPER, "SILOPER",
57                   SILC_CF_LAG | SILC_CF_REG | SILC_CF_SILC_OPER, 2),
58   SILC_CLIENT_CMD(leave, LEAVE, "LEAVE", SILC_CF_LAG | SILC_CF_REG, 2),
59   SILC_CLIENT_CMD(users, USERS, "USERS", SILC_CF_LAG | SILC_CF_REG, 2),
60
61   { NULL, 0, NULL, 0, 0 },
62 };
63
64 #define SILC_NOT_CONNECTED(x, c) \
65   x->ops->say((x), (c), \
66            "You are not connected to a server, use /SERVER to connect");
67
68 /* Command operation that is called at the end of all commands. 
69    Usage: COMMAND; */
70 #define COMMAND cmd->client->ops->command(cmd->client, cmd->conn, \
71   cmd, TRUE, cmd->command->cmd)
72
73 /* Error to application. Usage: COMMAND_ERROR; */
74 #define COMMAND_ERROR cmd->client->ops->command(cmd->client, cmd->conn, \
75   cmd, FALSE, cmd->command->cmd)
76
77 /* Generic function to send any command. The arguments must be sent already
78    encoded into correct form in correct order. */
79
80 void silc_client_send_command(SilcClient client, SilcClientConnection conn,
81                               SilcCommand command, unsigned short ident,
82                               unsigned int argc, ...)
83 {
84   SilcBuffer packet;
85   va_list ap;
86
87   va_start(ap, argc);
88
89   packet = silc_command_payload_encode_vap(command, ident, argc, ap);
90   silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
91                           NULL, 0, NULL, NULL, packet->data, 
92                           packet->len, TRUE);
93   silc_buffer_free(packet);
94 }
95
96 /* Finds and returns a pointer to the command list. Return NULL if the
97    command is not found. */
98
99 SilcClientCommand *silc_client_command_find(const char *name)
100 {
101   SilcClientCommand *cmd;
102
103   for (cmd = silc_command_list; cmd->name; cmd++) {
104     if (!strcmp(cmd->name, name))
105       return cmd;
106   }
107
108   return NULL;
109 }
110
111 /* Add new pending command to be executed when reply to a command has been
112    received.  The `reply_cmd' is the command that will call the `callback'
113    with `context' when reply has been received.  If `ident is non-zero
114    the `callback' will be executed when received reply with command 
115    identifier `ident'. */
116
117 void silc_client_command_pending(SilcClientConnection conn,
118                                  SilcCommand reply_cmd,
119                                  unsigned short ident,
120                                  SilcClientPendingDestructor destructor,
121                                  SilcCommandCb callback,
122                                  void *context)
123 {
124   SilcClientCommandPending *reply;
125
126   reply = silc_calloc(1, sizeof(*reply));
127   reply->reply_cmd = reply_cmd;
128   reply->ident = ident;
129   reply->context = context;
130   reply->callback = callback;
131   reply->destructor = destructor;
132   silc_dlist_add(conn->pending_commands, reply);
133 }
134
135 /* Deletes pending command by reply command type. */
136
137 void silc_client_command_pending_del(SilcClientConnection conn,
138                                      SilcCommand reply_cmd,
139                                      unsigned short ident)
140 {
141   SilcClientCommandPending *r;
142
143   silc_dlist_start(conn->pending_commands);
144   while ((r = silc_dlist_get(conn->pending_commands)) != SILC_LIST_END) {
145     if (r->reply_cmd == reply_cmd && r->ident == ident) {
146       silc_dlist_del(conn->pending_commands, r);
147       break;
148     }
149   }
150 }
151
152 /* Checks for pending commands and marks callbacks to be called from
153    the command reply function. Returns TRUE if there were pending command. */
154
155 int silc_client_command_pending_check(SilcClientConnection conn,
156                                       SilcClientCommandReplyContext ctx,
157                                       SilcCommand command, 
158                                       unsigned short ident)
159 {
160   SilcClientCommandPending *r;
161
162   silc_dlist_start(conn->pending_commands);
163   while ((r = silc_dlist_get(conn->pending_commands)) != SILC_LIST_END) {
164     if (r->reply_cmd == command && r->ident == ident) {
165       ctx->context = r->context;
166       ctx->callback = r->callback;
167       ctx->destructor = r->destructor;
168       ctx->ident = ident;
169       return TRUE;
170     }
171   }
172
173   return FALSE;
174 }
175
176 /* Allocate Command Context */
177
178 SilcClientCommandContext silc_client_command_alloc()
179 {
180   SilcClientCommandContext ctx = silc_calloc(1, sizeof(*ctx));
181   ctx->users++;
182   return ctx;
183 }
184
185 /* Free command context and its internals */
186
187 void silc_client_command_free(SilcClientCommandContext ctx)
188 {
189   ctx->users--;
190   SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users + 1,
191                   ctx->users));
192   if (ctx->users < 1) {
193     int i;
194
195     for (i = 0; i < ctx->argc; i++)
196       silc_free(ctx->argv[i]);
197     silc_free(ctx);
198   }
199 }
200
201 /* Duplicate Command Context by adding reference counter. The context won't
202    be free'd untill it hits zero. */
203
204 SilcClientCommandContext 
205 silc_client_command_dup(SilcClientCommandContext ctx)
206 {
207   ctx->users++;
208   SILC_LOG_DEBUG(("Command context %p refcnt %d->%d", ctx, ctx->users - 1,
209                   ctx->users));
210   return ctx;
211 }
212
213 /* Pending command destructor. */
214
215 static void silc_client_command_destructor(void *context)
216 {
217   silc_client_command_free((SilcClientCommandContext)context);
218 }
219
220 /* Command WHOIS. This command is used to query information about 
221    specific user. */
222
223 SILC_CLIENT_CMD_FUNC(whois)
224 {
225   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
226   SilcClientConnection conn = cmd->conn;
227   SilcBuffer buffer;
228
229   if (!cmd->conn) {
230     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
231     COMMAND_ERROR;
232     goto out;
233   }
234
235   if (cmd->argc < 2 || cmd->argc > 3) {
236     cmd->client->ops->say(cmd->client, conn, 
237              "Usage: /WHOIS <nickname>[@<server>] [<count>]");
238     COMMAND_ERROR;
239     goto out;
240   }
241
242   buffer = silc_command_payload_encode(SILC_COMMAND_WHOIS,
243                                        cmd->argc - 1, ++cmd->argv,
244                                        ++cmd->argv_lens, ++cmd->argv_types,
245                                        0);
246   silc_client_packet_send(cmd->client, cmd->conn->sock,
247                           SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
248                           buffer->data, buffer->len, TRUE);
249   silc_buffer_free(buffer);
250   cmd->argv--;
251   cmd->argv_lens--;
252   cmd->argv_types--;
253
254   /* Notify application */
255   COMMAND;
256
257  out:
258   silc_client_command_free(cmd);
259 }
260
261 SILC_CLIENT_CMD_FUNC(whowas)
262 {
263 }
264
265 /* Command IDENTIFY. This command is used to query information about 
266    specific user, especially ID's. */
267
268 SILC_CLIENT_CMD_FUNC(identify)
269 {
270   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
271   SilcClientConnection conn = cmd->conn;
272   SilcBuffer buffer;
273
274   if (!cmd->conn) {
275     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
276     COMMAND_ERROR;
277     goto out;
278   }
279
280   if (cmd->argc < 2 || cmd->argc > 3) {
281     cmd->client->ops->say(cmd->client, conn,
282              "Usage: /IDENTIFY <nickname>[@<server>] [<count>]");
283     COMMAND_ERROR;
284     goto out;
285   }
286
287   buffer = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
288                                        cmd->argc - 1, ++cmd->argv,
289                                        ++cmd->argv_lens, ++cmd->argv_types,
290                                        0);
291   silc_client_packet_send(cmd->client, cmd->conn->sock,
292                           SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
293                           buffer->data, buffer->len, TRUE);
294   silc_buffer_free(buffer);
295   cmd->argv--;
296   cmd->argv_lens--;
297   cmd->argv_types--;
298
299   /* Notify application */
300   COMMAND;
301
302  out:
303   silc_client_command_free(cmd);
304 }
305
306 /* Command NICK. Shows current nickname/sets new nickname on current
307    window. */
308
309 SILC_CLIENT_CMD_FUNC(nick)
310 {
311   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
312   SilcClientConnection conn = cmd->conn;
313   SilcBuffer buffer;
314
315   if (!cmd->conn) {
316     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
317     COMMAND_ERROR;
318     goto out;
319   }
320
321   if (!strcmp(conn->nickname, cmd->argv[1]))
322     goto out;
323
324   /* Show current nickname */
325   if (cmd->argc < 2) {
326     if (cmd->conn) {
327       cmd->client->ops->say(cmd->client, conn, 
328                             "Your nickname is %s on server %s", 
329                             conn->nickname, conn->remote_host);
330     } else {
331       cmd->client->ops->say(cmd->client, conn, 
332                             "Your nickname is %s", conn->nickname);
333     }
334
335     /* XXX Notify application */
336     COMMAND;
337     goto out;
338   }
339
340   /* Set new nickname */
341   buffer = silc_command_payload_encode(SILC_COMMAND_NICK,
342                                        cmd->argc - 1, ++cmd->argv,
343                                        ++cmd->argv_lens, ++cmd->argv_types,
344                                        0);
345   silc_client_packet_send(cmd->client, cmd->conn->sock,
346                           SILC_PACKET_COMMAND, NULL, 0, NULL, NULL,
347                           buffer->data, buffer->len, TRUE);
348   silc_buffer_free(buffer);
349   cmd->argv--;
350   cmd->argv_lens--;
351   cmd->argv_types--;
352   if (conn->nickname)
353     silc_free(conn->nickname);
354   conn->nickname = strdup(cmd->argv[1]);
355
356   /* Notify application */
357   COMMAND;
358
359  out:
360   silc_client_command_free(cmd);
361 }
362
363 SILC_CLIENT_CMD_FUNC(list)
364 {
365 }
366
367 /* Command TOPIC. Sets/shows topic on a channel. */
368
369 SILC_CLIENT_CMD_FUNC(topic)
370 {
371   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
372   SilcClientConnection conn = cmd->conn;
373   SilcIDCacheEntry id_cache = NULL;
374   SilcChannelEntry channel;
375   SilcBuffer buffer, idp;
376   char *name;
377
378   if (!cmd->conn) {
379     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
380     COMMAND_ERROR;
381     goto out;
382   }
383
384   if (cmd->argc < 2 || cmd->argc > 3) {
385     cmd->client->ops->say(cmd->client, conn,
386                           "Usage: /TOPIC <channel> [<topic>]");
387     COMMAND_ERROR;
388     goto out;
389   }
390
391   if (cmd->argv[1][0] == '*') {
392     if (!conn->current_channel) {
393       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
394       COMMAND_ERROR;
395       goto out;
396     }
397     name = conn->current_channel->channel_name;
398   } else {
399     name = cmd->argv[1];
400   }
401
402   if (!conn->current_channel) {
403     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
404     COMMAND_ERROR;
405     goto out;
406   }
407
408   /* Get the Channel ID of the channel */
409   if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
410     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
411     COMMAND_ERROR;
412     goto out;
413   }
414
415   channel = (SilcChannelEntry)id_cache->context;
416
417   /* Send TOPIC command to the server */
418   idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
419   if (cmd->argc > 2)
420     buffer = silc_command_payload_encode_va(SILC_COMMAND_TOPIC, 0, 2, 
421                                             1, idp->data, idp->len,
422                                             2, cmd->argv[2], 
423                                             strlen(cmd->argv[2]));
424   else
425     buffer = silc_command_payload_encode_va(SILC_COMMAND_TOPIC, 1, 
426                                             1, idp->data, idp->len,
427                                             0);
428   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
429                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
430   silc_buffer_free(buffer);
431   silc_buffer_free(idp);
432
433   /* Notify application */
434   COMMAND;
435
436  out:
437   silc_client_command_free(cmd);
438 }
439
440 /* Command INVITE. Invites specific client to join a channel. */
441
442 SILC_CLIENT_CMD_FUNC(invite)
443 {
444   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
445   SilcClient client = cmd->client;
446   SilcClientConnection conn = cmd->conn;
447   SilcClientEntry client_entry;
448   SilcChannelEntry channel_entry;
449   SilcBuffer buffer, clidp, chidp;
450   unsigned int num = 0;
451   char *nickname = NULL, *server = NULL;
452
453   if (!cmd->conn) {
454     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
455     COMMAND_ERROR;
456     goto out;
457   }
458
459   if (cmd->argc != 3) {
460     cmd->client->ops->say(cmd->client, conn,
461                           "Usage: /INVITE <nickname>[@<server>] <channel>");
462     COMMAND_ERROR;
463     goto out;
464   }
465
466   /* Parse the typed nickname. */
467   if (!silc_parse_nickname(cmd->argv[1], &nickname, &server, &num)) {
468     cmd->client->ops->say(cmd->client, conn, "Bad nickname");
469     COMMAND_ERROR;
470     goto out;
471   }
472
473   /* Find client entry */
474   client_entry = silc_idlist_get_client(client, conn, nickname, server, num);
475   if (!client_entry) {
476     if (nickname)
477       silc_free(nickname);
478     if (server)
479       silc_free(server);
480
481     /* Client entry not found, it was requested thus mark this to be
482        pending command. */
483     silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 0,
484                                 silc_client_command_destructor,
485                                 silc_client_command_invite, 
486                                 silc_client_command_dup(cmd));
487     cmd->pending = 1;
488     return;
489   }
490
491   /* Find channel entry */
492   channel_entry = silc_idlist_get_channel(client, conn, cmd->argv[2]);
493   if (!channel_entry) {
494     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
495     COMMAND_ERROR;
496     goto out;
497   }
498
499   /* Send command */
500   clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
501   chidp = silc_id_payload_encode(channel_entry->id, SILC_ID_CHANNEL);
502   buffer = silc_command_payload_encode_va(SILC_COMMAND_INVITE, 0, 2,
503                                           1, clidp->data, clidp->len,
504                                           2, chidp->data, chidp->len);
505   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
506                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
507   silc_buffer_free(buffer);
508   silc_buffer_free(clidp);
509   silc_buffer_free(chidp);
510
511   cmd->client->ops->say(cmd->client, conn, 
512                         "Inviting %s to channel %s", cmd->argv[1], 
513                         cmd->argv[2]);
514
515   /* Notify application */
516   COMMAND;
517
518  out:
519   silc_client_command_free(cmd);
520 }
521
522 /* Command QUIT. Closes connection with current server. */
523  
524 SILC_CLIENT_CMD_FUNC(quit)
525 {
526   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
527   SilcBuffer buffer;
528
529   if (!cmd->conn) {
530     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
531     COMMAND_ERROR;
532     goto out;
533   }
534
535   buffer = silc_command_payload_encode(SILC_COMMAND_QUIT, cmd->argc - 1, 
536                                        ++cmd->argv, ++cmd->argv_lens,
537                                        ++cmd->argv_types, 0);
538   silc_client_packet_send(cmd->client, cmd->conn->sock, SILC_PACKET_COMMAND, 
539                           NULL, 0, NULL, NULL, 
540                           buffer->data, buffer->len, TRUE);
541   silc_buffer_free(buffer);
542   cmd->argv--;
543   cmd->argv_lens--;
544   cmd->argv_types--;
545
546   /* Close connection */
547   cmd->client->ops->disconnect(cmd->client, cmd->conn);
548   silc_client_close_connection(cmd->client, cmd->conn->sock);
549
550   /* Notify application */
551   COMMAND;
552
553  out:
554   silc_client_command_free(cmd);
555 }
556
557 SILC_CLIENT_CMD_FUNC(kill)
558 {
559 }
560
561 /* Command INFO. Request information about specific server. If specific
562    server is not provided the current server is used. */
563
564 SILC_CLIENT_CMD_FUNC(info)
565 {
566   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
567   SilcClientConnection conn = cmd->conn;
568   SilcBuffer buffer;
569   char *name;
570
571   if (!cmd->conn) {
572     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
573     COMMAND_ERROR;
574     goto out;
575   }
576
577   if (cmd->argc < 2)
578     name = strdup(conn->remote_host);
579   else
580     name = strdup(cmd->argv[1]);
581
582   /* Send the command */
583   buffer = silc_command_payload_encode_va(SILC_COMMAND_INFO, 0, 1, 
584                                           1, name, strlen(name));
585   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
586                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
587   silc_buffer_free(buffer);
588
589   /* Notify application */
590   COMMAND;
591
592  out:
593   silc_client_command_free(cmd);
594 }
595
596 SILC_CLIENT_CMD_FUNC(connect)
597 {
598 }
599
600 /* Command PING. Sends ping to server. This is used to test the 
601    communication channel. */
602
603 SILC_CLIENT_CMD_FUNC(ping)
604 {
605   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
606   SilcClientConnection conn = cmd->conn;
607   SilcBuffer buffer;
608   void *id;
609   int i;
610   char *name = NULL;
611
612   if (!cmd->conn) {
613     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
614     COMMAND_ERROR;
615     goto out;
616   }
617
618   if (cmd->argc == 1 || !strcmp(cmd->argv[1], conn->remote_host))
619     name = strdup(conn->remote_host);
620
621   /* Send the command */
622   buffer = silc_command_payload_encode_va(SILC_COMMAND_PING, 0, 1, 
623                                           1, conn->remote_id_data, 
624                                           SILC_ID_SERVER_LEN);
625   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
626                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
627   silc_buffer_free(buffer);
628
629   id = silc_id_str2id(conn->remote_id_data, conn->remote_id_data_len,
630                       SILC_ID_SERVER);
631   if (!id) {
632     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
633     COMMAND_ERROR;
634     goto out;
635   }
636
637   /* Start counting time */
638   for (i = 0; i < conn->ping_count; i++) {
639     if (conn->ping[i].dest_id == NULL) {
640       conn->ping[i].start_time = time(NULL);
641       conn->ping[i].dest_id = id;
642       conn->ping[i].dest_name = name;
643       conn->ping_count++;
644       break;
645     }
646   }
647   if (i >= conn->ping_count) {
648     i = conn->ping_count;
649     conn->ping = silc_realloc(conn->ping, sizeof(*conn->ping) * (i + 1));
650     conn->ping[i].start_time = time(NULL);
651     conn->ping[i].dest_id = id;
652     conn->ping[i].dest_name = name;
653     conn->ping_count++;
654   }
655   
656   /* Notify application */
657   COMMAND;
658
659  out:
660   silc_client_command_free(cmd);
661 }
662
663 SILC_CLIENT_CMD_FUNC(oper)
664 {
665 }
666
667 SILC_CLIENT_CMD_FUNC(trace)
668 {
669 }
670
671 SILC_CLIENT_CMD_FUNC(notice)
672 {
673 }
674
675 /* Command JOIN. Joins to a channel. */
676
677 SILC_CLIENT_CMD_FUNC(join)
678 {
679   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
680   SilcClientConnection conn = cmd->conn;
681   SilcIDCacheEntry id_cache = NULL;
682   SilcBuffer buffer, idp;
683
684   if (!cmd->conn) {
685     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
686     COMMAND_ERROR;
687     goto out;
688   }
689
690   if (cmd->argc < 2) {
691     /* Show channels currently joined to */
692
693     goto out;
694   }
695
696   /* See if we have joined to the requested channel already */
697   if (silc_idcache_find_by_data_one(conn->channel_cache, cmd->argv[1],
698                                     &id_cache)) {
699     cmd->client->ops->say(cmd->client, conn, 
700                           "You are talking to channel %s", cmd->argv[1]);
701     conn->current_channel = (SilcChannelEntry)id_cache->context;
702 #if 0
703     cmd->client->screen->bottom_line->channel = cmd->argv[1];
704     silc_screen_print_bottom_line(cmd->client->screen, 0);
705 #endif
706     goto out;
707   }
708
709   idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
710
711   /* Send JOIN command to the server */
712   if (cmd->argc == 2)
713     buffer = 
714       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 2,
715                                      1, cmd->argv[1], cmd->argv_lens[1],
716                                      2, idp->data, idp->len);
717   else if (cmd->argc == 3)
718     /* XXX Buggy */
719     buffer = 
720       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 3,
721                                      1, cmd->argv[1], cmd->argv_lens[1],
722                                      2, idp->data, idp->len,
723                                      3, cmd->argv[2], cmd->argv_lens[2]);
724   else
725     buffer = 
726       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 4,
727                                      1, cmd->argv[1], cmd->argv_lens[1],
728                                      2, idp->data, idp->len,
729                                      3, cmd->argv[2], cmd->argv_lens[2],
730                                      4, cmd->argv[3], cmd->argv_lens[3]);
731
732   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
733                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
734   silc_buffer_free(buffer);
735   silc_buffer_free(idp);
736
737   /* Notify application */
738   COMMAND;
739
740  out:
741   silc_client_command_free(cmd);
742 }
743
744 /* MOTD command. Requests motd from server. */
745
746 SILC_CLIENT_CMD_FUNC(motd)
747 {
748   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
749   SilcClientConnection conn = cmd->conn;
750   SilcBuffer buffer;
751
752   if (!cmd->conn) {
753     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
754     COMMAND_ERROR;
755     goto out;
756   }
757
758   if (cmd->argc < 1 || cmd->argc > 1) {
759     cmd->client->ops->say(cmd->client, conn,
760                           "Usage: /MOTD");
761     COMMAND_ERROR;
762     goto out;
763   }
764
765   /* Send TOPIC command to the server */
766   buffer = silc_command_payload_encode_va(SILC_COMMAND_MOTD, 0, 1, 
767                                           2, conn->remote_host, 
768                                           strlen(conn->remote_host));
769   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
770                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
771   silc_buffer_free(buffer);
772
773   /* Notify application */
774   COMMAND;
775
776  out:
777   silc_client_command_free(cmd);
778 }
779
780 /* UMODE. Set user mode in SILC. */
781
782 SILC_CLIENT_CMD_FUNC(umode)
783 {
784
785 }
786
787 /* CMODE command. Sets channel mode. Modes that does not require any arguments
788    can be set several at once. Those modes that require argument must be set
789    separately (unless set with modes that does not require arguments). */
790
791 SILC_CLIENT_CMD_FUNC(cmode)
792 {
793   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
794   SilcClientConnection conn = cmd->conn;
795   SilcChannelEntry channel;
796   SilcBuffer buffer, chidp;
797   unsigned char *name, *cp, modebuf[4], tmp[4], *arg = NULL;
798   unsigned int mode, add, type, len, arg_len = 0;
799   int i;
800
801   if (!cmd->conn) {
802     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
803     COMMAND_ERROR;
804     goto out;
805   }
806
807   if (cmd->argc < 3) {
808     cmd->client->ops->say(cmd->client, conn, 
809                   "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
810     COMMAND_ERROR;
811     goto out;
812   }
813
814   if (cmd->argv[1][0] == '*') {
815     if (!conn->current_channel) {
816       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
817       COMMAND_ERROR;
818       goto out;
819     }
820
821     channel = conn->current_channel;
822   } else {
823     name = cmd->argv[1];
824
825     channel = silc_idlist_get_channel(cmd->client, conn, name);
826     if (!channel) {
827       cmd->client->ops->say(cmd->client, conn, "You are on that channel");
828       COMMAND_ERROR;
829       goto out;
830     }
831   }
832
833   mode = channel->mode;
834
835   /* Are we adding or removing mode */
836   if (cmd->argv[2][0] == '-')
837     add = FALSE;
838   else
839     add = TRUE;
840
841   /* Argument type to be sent to server */
842   type = 0;
843
844   /* Parse mode */
845   cp = cmd->argv[2] + 1;
846   len = strlen(cp);
847   for (i = 0; i < len; i++) {
848     switch(cp[i]) {
849     case 'p':
850       if (add)
851         mode |= SILC_CHANNEL_MODE_PRIVATE;
852       else
853         mode &= ~SILC_CHANNEL_MODE_PRIVATE;
854       break;
855     case 's':
856       if (add)
857         mode |= SILC_CHANNEL_MODE_SECRET;
858       else
859         mode &= ~SILC_CHANNEL_MODE_SECRET;
860       break;
861     case 'k':
862       if (add)
863         mode |= SILC_CHANNEL_MODE_PRIVKEY;
864       else
865         mode &= ~SILC_CHANNEL_MODE_PRIVKEY;
866       break;
867     case 'i':
868       if (add)
869         mode |= SILC_CHANNEL_MODE_INVITE;
870       else
871         mode &= ~SILC_CHANNEL_MODE_INVITE;
872       break;
873     case 't':
874       if (add)
875         mode |= SILC_CHANNEL_MODE_TOPIC;
876       else
877         mode &= ~SILC_CHANNEL_MODE_TOPIC;
878       break;
879     case 'l':
880       if (add) {
881         int ll;
882         mode |= SILC_CHANNEL_MODE_ULIMIT;
883         type = 3;
884         ll = atoi(cmd->argv[3]);
885         SILC_PUT32_MSB(ll, tmp);
886         arg = tmp;
887         arg_len = 4;
888       } else {
889         mode &= ~SILC_CHANNEL_MODE_ULIMIT;
890       }
891       break;
892     case 'a':
893       if (add) {
894         mode |= SILC_CHANNEL_MODE_PASSPHRASE;
895         type = 4;
896         arg = cmd->argv[3];
897         arg_len = cmd->argv_lens[3];
898       } else {
899         mode &= ~SILC_CHANNEL_MODE_PASSPHRASE;
900       }
901       break;
902     case 'b':
903       if (add) {
904         mode |= SILC_CHANNEL_MODE_BAN;
905         type = 5;
906         arg = cmd->argv[3];
907         arg_len = cmd->argv_lens[3];
908       } else {
909         mode &= ~SILC_CHANNEL_MODE_BAN;
910       }
911       break;
912     case 'I':
913       if (add) {
914         mode |= SILC_CHANNEL_MODE_INVITE_LIST;
915         type = 6;
916         arg = cmd->argv[3];
917         arg_len = cmd->argv_lens[3];
918       } else {
919         mode &= ~SILC_CHANNEL_MODE_INVITE_LIST;
920       }
921       break;
922     case 'c':
923       if (add) {
924         mode |= SILC_CHANNEL_MODE_CIPHER;
925         type = 8;
926         arg = cmd->argv[3];
927         arg_len = cmd->argv_lens[3];
928       } else {
929         mode &= ~SILC_CHANNEL_MODE_CIPHER;
930       }
931       break;
932     default:
933       COMMAND_ERROR;
934       goto out;
935       break;
936     }
937   }
938
939   if (type && cmd->argc < 3) {
940     COMMAND_ERROR;
941     goto out;
942   }
943
944   chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
945   SILC_PUT32_MSB(mode, modebuf);
946
947   /* Send the command packet. We support sending only one mode at once
948      that requires an argument. */
949   if (type && arg) {
950     buffer = 
951       silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 3, 
952                                      1, chidp->data, chidp->len, 
953                                      2, modebuf, sizeof(modebuf),
954                                      type, arg, arg_len);
955   } else {
956     buffer = 
957       silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 2, 
958                                      1, chidp->data, chidp->len, 
959                                      2, modebuf, sizeof(modebuf));
960   }
961
962   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
963                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
964   silc_buffer_free(buffer);
965   silc_buffer_free(chidp);
966
967   /* Notify application */
968   COMMAND;
969
970  out:
971   silc_client_command_free(cmd);
972 }
973
974 /* CUMODE command. Changes client's mode on a channel. */
975
976 SILC_CLIENT_CMD_FUNC(cumode)
977 {
978   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
979   SilcClientConnection conn = cmd->conn;
980   SilcChannelEntry channel;
981   SilcChannelUser chu;
982   SilcClientEntry client_entry;
983   SilcBuffer buffer, clidp, chidp;
984   unsigned char *name, *cp, modebuf[4];
985   unsigned int mode = 0, add, len;
986   char *nickname = NULL, *server = NULL;
987   unsigned int num = 0;
988   int i;
989
990   if (!cmd->conn) {
991     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
992     COMMAND_ERROR;
993     goto out;
994   }
995
996   if (cmd->argc < 4) {
997     cmd->client->ops->say(cmd->client, conn, 
998                   "Usage: /CUMODE <channel> +|-<modes> <nickname>[@<server>]");
999     COMMAND_ERROR;
1000     goto out;
1001   }
1002
1003   if (cmd->argv[1][0] == '*') {
1004     if (!conn->current_channel) {
1005       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1006       COMMAND_ERROR;
1007       goto out;
1008     }
1009
1010     channel = conn->current_channel;
1011   } else {
1012     name = cmd->argv[1];
1013
1014     channel = silc_idlist_get_channel(cmd->client, conn, name);
1015     if (!channel) {
1016       cmd->client->ops->say(cmd->client, conn, "You are on that channel");
1017       COMMAND_ERROR;
1018       goto out;
1019     }
1020   }
1021
1022   /* Parse the typed nickname. */
1023   if (!silc_parse_nickname(cmd->argv[3], &nickname, &server, &num)) {
1024     cmd->client->ops->say(cmd->client, conn, "Bad nickname");
1025     COMMAND_ERROR;
1026     goto out;
1027   }
1028
1029   /* Find client entry */
1030   client_entry = silc_idlist_get_client(cmd->client, conn, 
1031                                         nickname, server, num);
1032   if (!client_entry) {
1033     /* Client entry not found, it was requested thus mark this to be
1034        pending command. */
1035     silc_client_command_pending(conn, SILC_COMMAND_CUMODE, 0,  
1036                                 silc_client_command_destructor,
1037                                 silc_client_command_cumode, 
1038                                 silc_client_command_dup(cmd));
1039     cmd->pending = 1;
1040     return;
1041   }
1042   
1043   while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
1044     if (chu->client == client_entry) {
1045       chu->mode = mode;
1046       break;
1047     }
1048   }
1049
1050   /* Are we adding or removing mode */
1051   if (cmd->argv[2][0] == '-')
1052     add = FALSE;
1053   else
1054     add = TRUE;
1055
1056   /* Parse mode */
1057   cp = cmd->argv[2] + 1;
1058   len = strlen(cp);
1059   for (i = 0; i < len; i++) {
1060     switch(cp[i]) {
1061     case 'a':
1062       if (add) {
1063         mode |= SILC_CHANNEL_UMODE_CHANFO;
1064         mode |= SILC_CHANNEL_UMODE_CHANOP;
1065       } else {
1066         mode = SILC_CHANNEL_UMODE_NONE;
1067       }
1068       break;
1069     case 'f':
1070       if (add)
1071         mode |= SILC_CHANNEL_UMODE_CHANFO;
1072       else
1073         mode &= ~SILC_CHANNEL_UMODE_CHANFO;
1074       break;
1075     case 'o':
1076       if (add)
1077         mode |= SILC_CHANNEL_UMODE_CHANOP;
1078       else
1079         mode &= ~SILC_CHANNEL_UMODE_CHANOP;
1080       break;
1081     default:
1082       COMMAND_ERROR;
1083       goto out;
1084       break;
1085     }
1086   }
1087
1088   chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
1089   SILC_PUT32_MSB(mode, modebuf);
1090   clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
1091
1092   /* Send the command packet. We support sending only one mode at once
1093      that requires an argument. */
1094   buffer = silc_command_payload_encode_va(SILC_COMMAND_CUMODE, 0, 3, 
1095                                           1, chidp->data, chidp->len, 
1096                                           2, modebuf, 4,
1097                                           3, clidp->data, clidp->len);
1098
1099   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
1100                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
1101   silc_buffer_free(buffer);
1102   silc_buffer_free(chidp);
1103   silc_buffer_free(clidp);
1104   
1105   /* Notify application */
1106   COMMAND;
1107
1108  out:
1109   silc_client_command_free(cmd);
1110 }
1111
1112 /* KICK command. Kicks a client out of channel. */
1113
1114 SILC_CLIENT_CMD_FUNC(kick)
1115 {
1116   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1117   SilcClientConnection conn = cmd->conn;
1118
1119 }
1120
1121 SILC_CLIENT_CMD_FUNC(restart)
1122 {
1123 }
1124  
1125 SILC_CLIENT_CMD_FUNC(close)
1126 {
1127 }
1128  
1129 SILC_CLIENT_CMD_FUNC(die)
1130 {
1131 }
1132  
1133 SILC_CLIENT_CMD_FUNC(silcoper)
1134 {
1135 }
1136
1137 /* LEAVE command. Leaves a channel. Client removes itself from a channel. */
1138
1139 SILC_CLIENT_CMD_FUNC(leave)
1140 {
1141   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1142   SilcClientConnection conn = cmd->conn;
1143   SilcIDCacheEntry id_cache = NULL;
1144   SilcChannelEntry channel;
1145   SilcBuffer buffer, idp;
1146   char *name;
1147
1148   if (!cmd->conn) {
1149     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
1150     COMMAND_ERROR;
1151     goto out;
1152   }
1153
1154   if (cmd->argc != 2) {
1155     cmd->client->ops->say(cmd->client, conn, "Usage: /LEAVE <channel>");
1156     COMMAND_ERROR;
1157     goto out;
1158   }
1159
1160   if (cmd->argv[1][0] == '*') {
1161     if (!conn->current_channel) {
1162       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1163       COMMAND_ERROR;
1164       goto out;
1165     }
1166     name = conn->current_channel->channel_name;
1167   } else {
1168     name = cmd->argv[1];
1169   }
1170
1171   if (!conn->current_channel) {
1172     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1173     COMMAND_ERROR;
1174     goto out;
1175   }
1176
1177   /* Get the Channel ID of the channel */
1178   if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
1179     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1180     COMMAND_ERROR;
1181     goto out;
1182   }
1183
1184   channel = (SilcChannelEntry)id_cache->context;
1185
1186   /* Send LEAVE command to the server */
1187   idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
1188   buffer = silc_command_payload_encode_va(SILC_COMMAND_LEAVE, 0, 1, 
1189                                           1, idp->data, idp->len);
1190   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
1191                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
1192   silc_buffer_free(buffer);
1193   silc_buffer_free(idp);
1194
1195   /* We won't talk anymore on this channel */
1196   cmd->client->ops->say(cmd->client, conn, "You have left channel %s", name);
1197
1198   conn->current_channel = NULL;
1199
1200   silc_idcache_del_by_id(conn->channel_cache, SILC_ID_CHANNEL, channel->id);
1201   silc_free(channel->channel_name);
1202   silc_free(channel->id);
1203   silc_free(channel->key);
1204   silc_cipher_free(channel->channel_key);
1205   silc_free(channel);
1206
1207   /* Notify application */
1208   COMMAND;
1209
1210  out:
1211   silc_client_command_free(cmd);
1212 }
1213
1214 /* Command USERS. Requests the USERS of the clients joined on requested
1215    channel. */
1216
1217 SILC_CLIENT_CMD_FUNC(users)
1218 {
1219   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1220   SilcClientConnection conn = cmd->conn;
1221   SilcIDCacheEntry id_cache = NULL;
1222   SilcChannelEntry channel;
1223   SilcBuffer buffer, idp;
1224   char *name, *line = NULL;
1225   unsigned int line_len = 0;
1226
1227   if (!cmd->conn) {
1228     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
1229     COMMAND_ERROR;
1230     goto out;
1231   }
1232
1233   if (cmd->argc != 2) {
1234     cmd->client->ops->say(cmd->client, conn, "Usage: /USERS <channel>");
1235     COMMAND_ERROR;
1236     goto out;
1237   }
1238
1239   if (cmd->argv[1][0] == '*') {
1240     if (!conn->current_channel) {
1241       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1242       COMMAND_ERROR;
1243       goto out;
1244     }
1245     name = conn->current_channel->channel_name;
1246   } else {
1247     name = cmd->argv[1];
1248   }
1249
1250   if (!conn->current_channel) {
1251     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1252     COMMAND_ERROR;
1253     goto out;
1254   }
1255
1256   /* Get the Channel ID of the channel */
1257   if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
1258     /* XXX should resolve the channel ID; LIST command */
1259     cmd->client->ops->say(cmd->client, conn, 
1260                           "You are not on that channel", name);
1261     COMMAND_ERROR;
1262     goto out;
1263   }
1264
1265   channel = (SilcChannelEntry)id_cache->context;
1266
1267   if (!cmd->pending) {
1268     /* Send USERS command to the server */
1269     idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
1270     buffer = silc_command_payload_encode_va(SILC_COMMAND_USERS, 0, 1, 
1271                                             1, idp->data, idp->len);
1272     silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, 
1273                             NULL, 0, NULL, NULL, buffer->data, 
1274                             buffer->len, TRUE);
1275     silc_buffer_free(buffer);
1276     silc_buffer_free(idp);
1277
1278     /* Register pending callback which will recall this command callback with
1279        same context and reprocesses the command. When reprocessing we actually
1280        display the information on the screen. */
1281     silc_client_command_pending(conn, SILC_COMMAND_USERS, 0, 
1282                                 silc_client_command_destructor,
1283                                 silc_client_command_users, 
1284                                 silc_client_command_dup(cmd));
1285     cmd->pending = TRUE;
1286     return;
1287   }
1288
1289   if (cmd->pending) {
1290     /* Pending command. Now we've resolved the information from server and
1291        we are ready to display the information on screen. */
1292     int i;
1293     SilcChannelUser chu;
1294
1295     cmd->client->ops->say(cmd->client, conn, "Users on %s", 
1296                           channel->channel_name);
1297
1298     line = silc_calloc(4096, sizeof(*line));
1299     line_len = 4096;
1300     silc_list_start(channel->clients);
1301     while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
1302       SilcClientEntry e = chu->client;
1303       char *m, tmp[80], len1;
1304
1305       memset(line, 0, sizeof(line_len));
1306
1307       if (strlen(e->nickname) + strlen(e->server) + 100 > line_len) {
1308         silc_free(line);
1309         line_len += strlen(e->nickname) + strlen(e->server) + 100;
1310         line = silc_calloc(line_len, sizeof(*line));
1311       }
1312
1313       memset(tmp, 0, sizeof(tmp));
1314       m = silc_client_chumode_char(chu->mode);
1315
1316       strncat(line, " ", 1);
1317       strncat(line, e->nickname, strlen(e->nickname));
1318       strncat(line, e->server ? "@" : "", 1);
1319
1320       len1 = 0;
1321       if (e->server)
1322         len1 = strlen(e->server);
1323       strncat(line, e->server ? e->server : "", len1 > 30 ? 30 : len1);
1324
1325       len1 = strlen(line);
1326       if (len1 >= 30) {
1327         memset(&line[29], 0, len1 - 29);
1328       } else {
1329         for (i = 0; i < 30 - len1 - 1; i++)
1330           strcat(line, " ");
1331       }
1332
1333       strncat(line, "  H", 3);
1334       strcat(tmp, m ? m : "");
1335       strncat(line, tmp, strlen(tmp));
1336
1337       if (strlen(tmp) < 5)
1338         for (i = 0; i < 5 - strlen(tmp); i++)
1339           strcat(line, " ");
1340
1341       strcat(line, e->username ? e->username : "");
1342
1343       cmd->client->ops->say(cmd->client, conn, "%s", line);
1344
1345       if (m)
1346         silc_free(m);
1347     }
1348   }
1349
1350   if (line)
1351     silc_free(line);
1352
1353   /* Notify application */
1354   COMMAND;
1355
1356  out:
1357   silc_client_command_free(cmd);
1358 }