updates.
[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     goto out;
488   }
489
490   /* Find channel entry */
491   channel_entry = silc_idlist_get_channel(client, conn, cmd->argv[2]);
492   if (!channel_entry) {
493     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
494     COMMAND_ERROR;
495     goto out;
496   }
497
498   /* Send command */
499   clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
500   chidp = silc_id_payload_encode(channel_entry->id, SILC_ID_CHANNEL);
501   buffer = silc_command_payload_encode_va(SILC_COMMAND_INVITE, 0, 2,
502                                           1, clidp->data, clidp->len,
503                                           2, chidp->data, chidp->len);
504   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
505                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
506   silc_buffer_free(buffer);
507   silc_buffer_free(clidp);
508   silc_buffer_free(chidp);
509
510   cmd->client->ops->say(cmd->client, conn, 
511                         "Inviting %s to channel %s", cmd->argv[1], 
512                         cmd->argv[2]);
513
514   /* Notify application */
515   COMMAND;
516
517  out:
518   silc_client_command_free(cmd);
519 }
520
521 /* Command QUIT. Closes connection with current server. */
522  
523 SILC_CLIENT_CMD_FUNC(quit)
524 {
525   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
526   SilcBuffer buffer;
527
528   if (!cmd->conn) {
529     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
530     COMMAND_ERROR;
531     goto out;
532   }
533
534   buffer = silc_command_payload_encode(SILC_COMMAND_QUIT, cmd->argc - 1, 
535                                        ++cmd->argv, ++cmd->argv_lens,
536                                        ++cmd->argv_types, 0);
537   silc_client_packet_send(cmd->client, cmd->conn->sock, SILC_PACKET_COMMAND, 
538                           NULL, 0, NULL, NULL, 
539                           buffer->data, buffer->len, TRUE);
540   silc_buffer_free(buffer);
541   cmd->argv--;
542   cmd->argv_lens--;
543   cmd->argv_types--;
544
545   /* Close connection */
546   cmd->client->ops->disconnect(cmd->client, cmd->conn);
547   silc_client_close_connection(cmd->client, cmd->conn->sock);
548
549   /* Notify application */
550   COMMAND;
551
552  out:
553   silc_client_command_free(cmd);
554 }
555
556 SILC_CLIENT_CMD_FUNC(kill)
557 {
558 }
559
560 /* Command INFO. Request information about specific server. If specific
561    server is not provided the current server is used. */
562
563 SILC_CLIENT_CMD_FUNC(info)
564 {
565   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
566   SilcClientConnection conn = cmd->conn;
567   SilcBuffer buffer;
568   char *name;
569
570   if (!cmd->conn) {
571     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
572     COMMAND_ERROR;
573     goto out;
574   }
575
576   if (cmd->argc < 2)
577     name = strdup(conn->remote_host);
578   else
579     name = strdup(cmd->argv[1]);
580
581   /* Send the command */
582   buffer = silc_command_payload_encode_va(SILC_COMMAND_INFO, 0, 1, 
583                                           1, name, strlen(name));
584   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
585                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
586   silc_buffer_free(buffer);
587
588   /* Notify application */
589   COMMAND;
590
591  out:
592   silc_client_command_free(cmd);
593 }
594
595 SILC_CLIENT_CMD_FUNC(connect)
596 {
597 }
598
599 /* Command PING. Sends ping to server. This is used to test the 
600    communication channel. */
601
602 SILC_CLIENT_CMD_FUNC(ping)
603 {
604   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
605   SilcClientConnection conn = cmd->conn;
606   SilcBuffer buffer;
607   void *id;
608   int i;
609   char *name = NULL;
610
611   if (!cmd->conn) {
612     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
613     COMMAND_ERROR;
614     goto out;
615   }
616
617   if (cmd->argc == 1 || !strcmp(cmd->argv[1], conn->remote_host))
618     name = strdup(conn->remote_host);
619
620   /* Send the command */
621   buffer = silc_command_payload_encode_va(SILC_COMMAND_PING, 0, 1, 
622                                           1, conn->remote_id_data, 
623                                           SILC_ID_SERVER_LEN);
624   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
625                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
626   silc_buffer_free(buffer);
627
628   id = silc_id_str2id(conn->remote_id_data, conn->remote_id_data_len,
629                       SILC_ID_SERVER);
630   if (!id) {
631     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
632     COMMAND_ERROR;
633     goto out;
634   }
635
636   /* Start counting time */
637   for (i = 0; i < conn->ping_count; i++) {
638     if (conn->ping[i].dest_id == NULL) {
639       conn->ping[i].start_time = time(NULL);
640       conn->ping[i].dest_id = id;
641       conn->ping[i].dest_name = name;
642       conn->ping_count++;
643       break;
644     }
645   }
646   if (i >= conn->ping_count) {
647     i = conn->ping_count;
648     conn->ping = silc_realloc(conn->ping, sizeof(*conn->ping) * (i + 1));
649     conn->ping[i].start_time = time(NULL);
650     conn->ping[i].dest_id = id;
651     conn->ping[i].dest_name = name;
652     conn->ping_count++;
653   }
654   
655   /* Notify application */
656   COMMAND;
657
658  out:
659   silc_client_command_free(cmd);
660 }
661
662 SILC_CLIENT_CMD_FUNC(oper)
663 {
664 }
665
666 SILC_CLIENT_CMD_FUNC(trace)
667 {
668 }
669
670 SILC_CLIENT_CMD_FUNC(notice)
671 {
672 }
673
674 /* Command JOIN. Joins to a channel. */
675
676 SILC_CLIENT_CMD_FUNC(join)
677 {
678   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
679   SilcClientConnection conn = cmd->conn;
680   SilcIDCacheEntry id_cache = NULL;
681   SilcBuffer buffer, idp;
682
683   if (!cmd->conn) {
684     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
685     COMMAND_ERROR;
686     goto out;
687   }
688
689   if (cmd->argc < 2) {
690     /* Show channels currently joined to */
691
692     goto out;
693   }
694
695   /* See if we have joined to the requested channel already */
696   if (silc_idcache_find_by_data_one(conn->channel_cache, cmd->argv[1],
697                                     &id_cache)) {
698     cmd->client->ops->say(cmd->client, conn, 
699                           "You are talking to channel %s", cmd->argv[1]);
700     conn->current_channel = (SilcChannelEntry)id_cache->context;
701 #if 0
702     cmd->client->screen->bottom_line->channel = cmd->argv[1];
703     silc_screen_print_bottom_line(cmd->client->screen, 0);
704 #endif
705     goto out;
706   }
707
708   idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
709
710   /* Send JOIN command to the server */
711   if (cmd->argc == 2)
712     buffer = 
713       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 2,
714                                      1, cmd->argv[1], cmd->argv_lens[1],
715                                      2, idp->data, idp->len);
716   else if (cmd->argc == 3)
717     /* XXX Buggy */
718     buffer = 
719       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 3,
720                                      1, cmd->argv[1], cmd->argv_lens[1],
721                                      2, idp->data, idp->len,
722                                      3, cmd->argv[2], cmd->argv_lens[2]);
723   else
724     buffer = 
725       silc_command_payload_encode_va(SILC_COMMAND_JOIN, 0, 4,
726                                      1, cmd->argv[1], cmd->argv_lens[1],
727                                      2, idp->data, idp->len,
728                                      3, cmd->argv[2], cmd->argv_lens[2],
729                                      4, cmd->argv[3], cmd->argv_lens[3]);
730
731   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
732                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
733   silc_buffer_free(buffer);
734   silc_buffer_free(idp);
735
736   /* Notify application */
737   COMMAND;
738
739  out:
740   silc_client_command_free(cmd);
741 }
742
743 /* MOTD command. Requests motd from server. */
744
745 SILC_CLIENT_CMD_FUNC(motd)
746 {
747   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
748   SilcClientConnection conn = cmd->conn;
749   SilcBuffer buffer;
750
751   if (!cmd->conn) {
752     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
753     COMMAND_ERROR;
754     goto out;
755   }
756
757   if (cmd->argc < 1 || cmd->argc > 1) {
758     cmd->client->ops->say(cmd->client, conn,
759                           "Usage: /MOTD");
760     COMMAND_ERROR;
761     goto out;
762   }
763
764   /* Send TOPIC command to the server */
765   buffer = silc_command_payload_encode_va(SILC_COMMAND_MOTD, 0, 1, 
766                                           2, conn->remote_host, 
767                                           strlen(conn->remote_host));
768   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
769                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
770   silc_buffer_free(buffer);
771
772   /* Notify application */
773   COMMAND;
774
775  out:
776   silc_client_command_free(cmd);
777 }
778
779 /* UMODE. Set user mode in SILC. */
780
781 SILC_CLIENT_CMD_FUNC(umode)
782 {
783
784 }
785
786 /* CMODE command. Sets channel mode. Modes that does not require any arguments
787    can be set several at once. Those modes that require argument must be set
788    separately (unless set with modes that does not require arguments). */
789
790 SILC_CLIENT_CMD_FUNC(cmode)
791 {
792   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
793   SilcClientConnection conn = cmd->conn;
794   SilcChannelEntry channel;
795   SilcBuffer buffer, chidp;
796   unsigned char *name, *cp, modebuf[4], tmp[4], *arg = NULL;
797   unsigned int mode, add, type, len, arg_len = 0;
798   int i;
799
800   if (!cmd->conn) {
801     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
802     COMMAND_ERROR;
803     goto out;
804   }
805
806   if (cmd->argc < 3) {
807     cmd->client->ops->say(cmd->client, conn, 
808                   "Usage: /CMODE <channel> +|-<modes> [{ <arguments>}]");
809     COMMAND_ERROR;
810     goto out;
811   }
812
813   if (cmd->argv[1][0] == '*') {
814     if (!conn->current_channel) {
815       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
816       COMMAND_ERROR;
817       goto out;
818     }
819
820     channel = conn->current_channel;
821   } else {
822     name = cmd->argv[1];
823
824     channel = silc_idlist_get_channel(cmd->client, conn, name);
825     if (!channel) {
826       cmd->client->ops->say(cmd->client, conn, "You are on that channel");
827       COMMAND_ERROR;
828       goto out;
829     }
830   }
831
832   mode = channel->mode;
833
834   /* Are we adding or removing mode */
835   if (cmd->argv[2][0] == '-')
836     add = FALSE;
837   else
838     add = TRUE;
839
840   /* Argument type to be sent to server */
841   type = 0;
842
843   /* Parse mode */
844   cp = cmd->argv[2] + 1;
845   len = strlen(cp);
846   for (i = 0; i < len; i++) {
847     switch(cp[i]) {
848     case 'p':
849       if (add)
850         mode |= SILC_CHANNEL_MODE_PRIVATE;
851       else
852         mode &= ~SILC_CHANNEL_MODE_PRIVATE;
853       break;
854     case 's':
855       if (add)
856         mode |= SILC_CHANNEL_MODE_SECRET;
857       else
858         mode &= ~SILC_CHANNEL_MODE_SECRET;
859       break;
860     case 'k':
861       if (add)
862         mode |= SILC_CHANNEL_MODE_PRIVKEY;
863       else
864         mode &= ~SILC_CHANNEL_MODE_PRIVKEY;
865       break;
866     case 'i':
867       if (add)
868         mode |= SILC_CHANNEL_MODE_INVITE;
869       else
870         mode &= ~SILC_CHANNEL_MODE_INVITE;
871       break;
872     case 't':
873       if (add)
874         mode |= SILC_CHANNEL_MODE_TOPIC;
875       else
876         mode &= ~SILC_CHANNEL_MODE_TOPIC;
877       break;
878     case 'l':
879       if (add) {
880         int ll;
881         mode |= SILC_CHANNEL_MODE_ULIMIT;
882         type = 3;
883         ll = atoi(cmd->argv[3]);
884         SILC_PUT32_MSB(ll, tmp);
885         arg = tmp;
886         arg_len = 4;
887       } else {
888         mode &= ~SILC_CHANNEL_MODE_ULIMIT;
889       }
890       break;
891     case 'a':
892       if (add) {
893         mode |= SILC_CHANNEL_MODE_PASSPHRASE;
894         type = 4;
895         arg = cmd->argv[3];
896         arg_len = cmd->argv_lens[3];
897       } else {
898         mode &= ~SILC_CHANNEL_MODE_PASSPHRASE;
899       }
900       break;
901     case 'b':
902       if (add) {
903         mode |= SILC_CHANNEL_MODE_BAN;
904         type = 5;
905         arg = cmd->argv[3];
906         arg_len = cmd->argv_lens[3];
907       } else {
908         mode &= ~SILC_CHANNEL_MODE_BAN;
909       }
910       break;
911     case 'I':
912       if (add) {
913         mode |= SILC_CHANNEL_MODE_INVITE_LIST;
914         type = 6;
915         arg = cmd->argv[3];
916         arg_len = cmd->argv_lens[3];
917       } else {
918         mode &= ~SILC_CHANNEL_MODE_INVITE_LIST;
919       }
920       break;
921     case 'c':
922       if (add) {
923         mode |= SILC_CHANNEL_MODE_CIPHER;
924         type = 8;
925         arg = cmd->argv[3];
926         arg_len = cmd->argv_lens[3];
927       } else {
928         mode &= ~SILC_CHANNEL_MODE_CIPHER;
929       }
930       break;
931     default:
932       COMMAND_ERROR;
933       goto out;
934       break;
935     }
936   }
937
938   if (type && cmd->argc < 3) {
939     COMMAND_ERROR;
940     goto out;
941   }
942
943   chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
944   SILC_PUT32_MSB(mode, modebuf);
945
946   /* Send the command packet. We support sending only one mode at once
947      that requires an argument. */
948   if (type && arg) {
949     buffer = 
950       silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 3, 
951                                      1, chidp->data, chidp->len, 
952                                      2, modebuf, sizeof(modebuf),
953                                      type, arg, arg_len);
954   } else {
955     buffer = 
956       silc_command_payload_encode_va(SILC_COMMAND_CMODE, 0, 2, 
957                                      1, chidp->data, chidp->len, 
958                                      2, modebuf, sizeof(modebuf));
959   }
960
961   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
962                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
963   silc_buffer_free(buffer);
964   silc_buffer_free(chidp);
965
966   /* Notify application */
967   COMMAND;
968
969  out:
970   silc_client_command_free(cmd);
971 }
972
973 /* CUMODE command. Changes client's mode on a channel. */
974
975 SILC_CLIENT_CMD_FUNC(cumode)
976 {
977   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
978   SilcClientConnection conn = cmd->conn;
979   SilcChannelEntry channel;
980   SilcChannelUser chu;
981   SilcClientEntry client_entry;
982   SilcBuffer buffer, clidp, chidp;
983   unsigned char *name, *cp, modebuf[4];
984   unsigned int mode = 0, add, len;
985   char *nickname = NULL, *server = NULL;
986   unsigned int num = 0;
987   int i;
988
989   if (!cmd->conn) {
990     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
991     COMMAND_ERROR;
992     goto out;
993   }
994
995   if (cmd->argc < 4) {
996     cmd->client->ops->say(cmd->client, conn, 
997                   "Usage: /CUMODE <channel> +|-<modes> <nickname>[@<server>]");
998     COMMAND_ERROR;
999     goto out;
1000   }
1001
1002   if (cmd->argv[1][0] == '*') {
1003     if (!conn->current_channel) {
1004       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1005       COMMAND_ERROR;
1006       goto out;
1007     }
1008
1009     channel = conn->current_channel;
1010   } else {
1011     name = cmd->argv[1];
1012
1013     channel = silc_idlist_get_channel(cmd->client, conn, name);
1014     if (!channel) {
1015       cmd->client->ops->say(cmd->client, conn, "You are on that channel");
1016       COMMAND_ERROR;
1017       goto out;
1018     }
1019   }
1020
1021   /* Parse the typed nickname. */
1022   if (!silc_parse_nickname(cmd->argv[3], &nickname, &server, &num)) {
1023     cmd->client->ops->say(cmd->client, conn, "Bad nickname");
1024     COMMAND_ERROR;
1025     goto out;
1026   }
1027
1028   /* Find client entry */
1029   client_entry = silc_idlist_get_client(cmd->client, conn, 
1030                                         nickname, server, num);
1031   if (!client_entry) {
1032     /* Client entry not found, it was requested thus mark this to be
1033        pending command. */
1034     silc_client_command_pending(conn, SILC_COMMAND_CUMODE, 0,  
1035                                 silc_client_command_destructor,
1036                                 silc_client_command_cumode, 
1037                                 silc_client_command_dup(cmd));
1038     goto out;
1039   }
1040   
1041   while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
1042     if (chu->client == client_entry) {
1043       chu->mode = mode;
1044       break;
1045     }
1046   }
1047
1048   /* Are we adding or removing mode */
1049   if (cmd->argv[2][0] == '-')
1050     add = FALSE;
1051   else
1052     add = TRUE;
1053
1054   /* Parse mode */
1055   cp = cmd->argv[2] + 1;
1056   len = strlen(cp);
1057   for (i = 0; i < len; i++) {
1058     switch(cp[i]) {
1059     case 'a':
1060       if (add) {
1061         mode |= SILC_CHANNEL_UMODE_CHANFO;
1062         mode |= SILC_CHANNEL_UMODE_CHANOP;
1063       } else {
1064         mode = SILC_CHANNEL_UMODE_NONE;
1065       }
1066       break;
1067     case 'f':
1068       if (add)
1069         mode |= SILC_CHANNEL_UMODE_CHANFO;
1070       else
1071         mode &= ~SILC_CHANNEL_UMODE_CHANFO;
1072       break;
1073     case 'o':
1074       if (add)
1075         mode |= SILC_CHANNEL_UMODE_CHANOP;
1076       else
1077         mode &= ~SILC_CHANNEL_UMODE_CHANOP;
1078       break;
1079     default:
1080       COMMAND_ERROR;
1081       goto out;
1082       break;
1083     }
1084   }
1085
1086   chidp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
1087   SILC_PUT32_MSB(mode, modebuf);
1088   clidp = silc_id_payload_encode(client_entry->id, SILC_ID_CLIENT);
1089
1090   /* Send the command packet. We support sending only one mode at once
1091      that requires an argument. */
1092   buffer = silc_command_payload_encode_va(SILC_COMMAND_CUMODE, 0, 3, 
1093                                           1, chidp->data, chidp->len, 
1094                                           2, modebuf, 4,
1095                                           3, clidp->data, clidp->len);
1096
1097   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
1098                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
1099   silc_buffer_free(buffer);
1100   silc_buffer_free(chidp);
1101   silc_buffer_free(clidp);
1102   
1103   /* Notify application */
1104   COMMAND;
1105
1106  out:
1107   silc_client_command_free(cmd);
1108 }
1109
1110 /* KICK command. Kicks a client out of channel. */
1111
1112 SILC_CLIENT_CMD_FUNC(kick)
1113 {
1114   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1115   SilcClientConnection conn = cmd->conn;
1116
1117 }
1118
1119 SILC_CLIENT_CMD_FUNC(restart)
1120 {
1121 }
1122  
1123 SILC_CLIENT_CMD_FUNC(close)
1124 {
1125 }
1126  
1127 SILC_CLIENT_CMD_FUNC(die)
1128 {
1129 }
1130  
1131 SILC_CLIENT_CMD_FUNC(silcoper)
1132 {
1133 }
1134
1135 /* LEAVE command. Leaves a channel. Client removes itself from a channel. */
1136
1137 SILC_CLIENT_CMD_FUNC(leave)
1138 {
1139   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1140   SilcClientConnection conn = cmd->conn;
1141   SilcIDCacheEntry id_cache = NULL;
1142   SilcChannelEntry channel;
1143   SilcBuffer buffer, idp;
1144   char *name;
1145
1146   if (!cmd->conn) {
1147     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
1148     COMMAND_ERROR;
1149     goto out;
1150   }
1151
1152   if (cmd->argc != 2) {
1153     cmd->client->ops->say(cmd->client, conn, "Usage: /LEAVE <channel>");
1154     COMMAND_ERROR;
1155     goto out;
1156   }
1157
1158   if (cmd->argv[1][0] == '*') {
1159     if (!conn->current_channel) {
1160       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1161       COMMAND_ERROR;
1162       goto out;
1163     }
1164     name = conn->current_channel->channel_name;
1165   } else {
1166     name = cmd->argv[1];
1167   }
1168
1169   if (!conn->current_channel) {
1170     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1171     COMMAND_ERROR;
1172     goto out;
1173   }
1174
1175   /* Get the Channel ID of the channel */
1176   if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
1177     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1178     COMMAND_ERROR;
1179     goto out;
1180   }
1181
1182   channel = (SilcChannelEntry)id_cache->context;
1183
1184   /* Send LEAVE command to the server */
1185   idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
1186   buffer = silc_command_payload_encode_va(SILC_COMMAND_LEAVE, 0, 1, 
1187                                           1, idp->data, idp->len);
1188   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, NULL, 
1189                           0, NULL, NULL, buffer->data, buffer->len, TRUE);
1190   silc_buffer_free(buffer);
1191   silc_buffer_free(idp);
1192
1193   /* We won't talk anymore on this channel */
1194   cmd->client->ops->say(cmd->client, conn, "You have left channel %s", name);
1195
1196   conn->current_channel = NULL;
1197
1198   silc_idcache_del_by_id(conn->channel_cache, SILC_ID_CHANNEL, channel->id);
1199   silc_free(channel->channel_name);
1200   silc_free(channel->id);
1201   silc_free(channel->key);
1202   silc_cipher_free(channel->channel_key);
1203   silc_free(channel);
1204
1205   /* Notify application */
1206   COMMAND;
1207
1208  out:
1209   silc_client_command_free(cmd);
1210 }
1211
1212 /* Command USERS. Requests the USERS of the clients joined on requested
1213    channel. */
1214
1215 SILC_CLIENT_CMD_FUNC(users)
1216 {
1217   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
1218   SilcClientConnection conn = cmd->conn;
1219   SilcIDCacheEntry id_cache = NULL;
1220   SilcChannelEntry channel;
1221   SilcBuffer buffer, idp;
1222   char *name;
1223
1224   if (!cmd->conn) {
1225     SILC_NOT_CONNECTED(cmd->client, cmd->conn);
1226     COMMAND_ERROR;
1227     goto out;
1228   }
1229
1230   if (cmd->argc != 2) {
1231     cmd->client->ops->say(cmd->client, conn, "Usage: /USERS <channel>");
1232     COMMAND_ERROR;
1233     goto out;
1234   }
1235
1236   if (cmd->argv[1][0] == '*') {
1237     if (!conn->current_channel) {
1238       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
1239       COMMAND_ERROR;
1240       goto out;
1241     }
1242     name = conn->current_channel->channel_name;
1243   } else {
1244     name = cmd->argv[1];
1245   }
1246
1247   if (!conn->current_channel) {
1248     cmd->client->ops->say(cmd->client, conn, "You are not on that channel");
1249     COMMAND_ERROR;
1250     goto out;
1251   }
1252
1253   /* Get the Channel ID of the channel */
1254   if (!silc_idcache_find_by_data_one(conn->channel_cache, name, &id_cache)) {
1255     /* XXX should resolve the channel ID; LIST command */
1256     cmd->client->ops->say(cmd->client, conn, 
1257                           "You are not on that channel", name);
1258     COMMAND_ERROR;
1259     goto out;
1260   }
1261
1262   channel = (SilcChannelEntry)id_cache->context;
1263
1264   if (!cmd->pending) {
1265     /* Send USERS command to the server */
1266     idp = silc_id_payload_encode(id_cache->id, SILC_ID_CHANNEL);
1267     buffer = silc_command_payload_encode_va(SILC_COMMAND_USERS, 0, 1, 
1268                                             1, idp->data, idp->len);
1269     silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, 
1270                             NULL, 0, NULL, NULL, buffer->data, 
1271                             buffer->len, TRUE);
1272     silc_buffer_free(buffer);
1273     silc_buffer_free(idp);
1274
1275     /* Register pending callback which will recall this command callback with
1276        same context and reprocesses the command. When reprocessing we actually
1277        display the information on the screen. */
1278     silc_client_command_pending(conn, SILC_COMMAND_USERS, 0, 
1279                                 silc_client_command_destructor,
1280                                 silc_client_command_users, 
1281                                 silc_client_command_dup(cmd));
1282     cmd->pending = TRUE;
1283     goto out;
1284   }
1285
1286   if (cmd->pending) {
1287     /* Pending command. Now we've resolved the information from server and
1288        we are ready to display the information on screen. */
1289     int i;
1290     SilcChannelUser chu;
1291
1292     cmd->client->ops->say(cmd->client, conn, "Users on %s", 
1293                           channel->channel_name);
1294
1295     silc_list_start(channel->clients);
1296     while ((chu = silc_list_get(channel->clients)) != SILC_LIST_END) {
1297       SilcClientEntry e = chu->client;
1298       char *m, tmp[80], line[80], len1;
1299
1300       memset(line, 0, sizeof(line));
1301       memset(tmp, 0, sizeof(tmp));
1302       m = silc_client_chumode_char(chu->mode);
1303
1304       strcat(line, " ");
1305       strcat(line, e->nickname);
1306       strcat(line, e->server ? "@" : "");
1307
1308       len1 = 0;
1309       if (e->server)
1310         len1 = strlen(e->server);
1311       strncat(line, e->server ? e->server : "", len1 > 30 ? 30 : len1);
1312
1313       len1 = strlen(line);
1314       if (len1 >= 30) {
1315         memset(&line[29], 0, len1 - 29);
1316       } else {
1317         for (i = 0; i < 30 - len1 - 1; i++)
1318           strcat(line, " ");
1319       }
1320
1321       strcat(line, "  H");
1322       strcat(tmp, m ? m : "");
1323       strcat(line, tmp);
1324
1325       if (strlen(tmp) < 5)
1326         for (i = 0; i < 5 - strlen(tmp); i++)
1327           strcat(line, " ");
1328
1329       strcat(line, e->username ? e->username : "");
1330
1331       cmd->client->ops->say(cmd->client, conn, "%s", line);
1332
1333       if (m)
1334         silc_free(m);
1335     }
1336   }
1337
1338   /* Notify application */
1339   COMMAND;
1340
1341  out:
1342   silc_client_command_free(cmd);
1343 }