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