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