updates. New data types.
[silc.git] / apps / silc / local_command.c
1 /*
2
3   local_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 "clientincludes.h"
23 #include "client_internal.h"
24
25 /* Local commands. */
26 SilcClientCommand silc_local_command_list[] =
27 {
28   SILC_CLIENT_LCMD(help, HELP, "HELP", 0, 2),
29   SILC_CLIENT_LCMD(clear, CLEAR, "CLEAR", 0, 1),
30   SILC_CLIENT_LCMD(version, VERSION, "VERSION", 0, 1),
31   SILC_CLIENT_LCMD(server, SERVER, "SERVER", 0, 2),
32   SILC_CLIENT_LCMD(msg, MSG, "MSG", 0, 3),
33   SILC_CLIENT_LCMD(away, AWAY, "AWAY", 0, 2),
34   SILC_CLIENT_LCMD(key, KEY, "KEY", 0, 7),
35   SILC_CLIENT_LCMD(me, ME, "ME", 0, 3),
36   SILC_CLIENT_LCMD(notice, NOTICE, "NOTICE", 0, 3),
37
38   { NULL, 0, NULL, 0, 0 },
39 };
40
41 /* Finds and returns a pointer to the command list. Return NULL if the
42    command is not found. */
43
44 SilcClientCommand *silc_client_local_command_find(const char *name)
45 {
46   SilcClientCommand *cmd;
47
48   for (cmd = silc_local_command_list; cmd->name; cmd++) {
49     if (!strcmp(cmd->name, name))
50       return cmd;
51   }
52
53   return NULL;
54 }
55
56 /* HELP command. This is local command and shows help on SILC */
57
58 SILC_CLIENT_LCMD_FUNC(help)
59 {
60
61 }
62
63 /* CLEAR command. This is local command and clears current output window */
64
65 SILC_CLIENT_LCMD_FUNC(clear)
66 {
67   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
68
69   silc_client_command_free(cmd);
70 }
71
72 /* VERSION command. This is local command and shows version of the client */
73
74 SILC_CLIENT_LCMD_FUNC(version)
75 {
76   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
77   SilcClient client = cmd->client;
78   extern char *silc_version;
79   extern char *silc_name;
80   extern char *silc_fullname;
81
82   silc_say(client, cmd->conn,
83            "%s (%s) version %s", silc_name, silc_fullname,
84            silc_version);
85
86   silc_client_command_free(cmd);
87 }
88
89 /* Command MSG. Sends private message to user or list of users. Note that
90    private messages are not really commands, they are message packets,
91    however, on user interface it is convenient to show them as commands
92    as that is the common way of sending private messages (like in IRC). */
93 /* XXX supports only one destination */
94
95 SILC_CLIENT_LCMD_FUNC(msg)
96 {
97   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
98   SilcClientConnection conn = cmd->conn;
99   SilcClient client = cmd->client;
100   SilcClientEntry client_entry = NULL;
101   uint32 num = 0;
102   char *nickname = NULL, *server = NULL;
103
104   if (!cmd->conn) {
105     silc_say(client, conn,
106              "You are not connected to a server, use /SERVER to connect");
107     goto out;
108   }
109
110   if (cmd->argc < 3) {
111     silc_say(client, conn, "Usage: /MSG <nickname> <message>");
112     goto out;
113   }
114
115   /* Parse the typed nickname. */
116   if (!silc_parse_nickname(cmd->argv[1], &nickname, &server, &num)) {
117     silc_say(client, conn, "Bad nickname");
118     goto out;
119   }
120
121   /* Find client entry */
122   client_entry = silc_idlist_get_client(client, conn, nickname, server, num,
123                                         TRUE);
124   if (!client_entry) {
125     /* Client entry not found, it was requested thus mark this to be
126        pending command. */
127     silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, conn->cmd_ident, 
128                                 NULL, silc_client_local_command_msg, context);
129     return;
130   }
131
132   /* Display the message for our eyes. */
133   silc_print(client, "-> *%s* %s", cmd->argv[1], cmd->argv[2]);
134
135   /* Send the private message */
136   silc_client_send_private_message(client, conn, client_entry, 0,
137                                    cmd->argv[2], cmd->argv_lens[2],
138                                    TRUE);
139
140  out:
141   silc_client_command_free(cmd);
142 }
143
144
145 /* Command SERVER. Connects to remote SILC server. This is local command. */
146
147 SILC_CLIENT_LCMD_FUNC(server)
148 {
149   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
150   SilcClient client = cmd->client;
151   SilcClientConnection conn = cmd->conn;
152   int i = 0, len, port;
153   char *hostname;
154
155   if (cmd->argc < 2) {
156     /* Show current servers */
157
158     if (!cmd->conn) {
159       silc_say(client, conn, "You are not connected to any server");
160       silc_say(client, conn, "Usage: /SERVER [<server>[:<port>]]");
161       goto out;
162     }
163
164     silc_say(client, conn, "Current server: %s on %d %s", 
165              conn->remote_host, conn->remote_port,
166              conn->remote_info ? conn->remote_info : "");
167     
168     silc_say(client, conn, "Server list:");
169     for (i = 0; i < client->conns_count; i++) {
170       silc_say(client, conn, " [%d] %s on %d %s", i + 1,
171                client->conns[i]->remote_host, 
172                client->conns[i]->remote_port,
173                client->conns[i]->remote_info ? 
174                client->conns[i]->remote_info : "");
175     }
176
177     goto out;
178   }
179
180   /* See if port is included and then extract it */
181   if (strchr(cmd->argv[1], ':')) {
182     len = strcspn(cmd->argv[1], ":");
183     hostname = silc_calloc(len + 1, sizeof(char));
184     memcpy(hostname, cmd->argv[1], len);
185     port = atoi(cmd->argv[1] + 1 + len);
186   } else {
187     hostname = cmd->argv[1];
188     port = 706;
189   }
190
191 #if 0
192   if (conn && conn->remote_host) {
193     if (!strcmp(hostname, conn->remote_host) && port == conn->remote_port) {
194       silc_say(client, conn, "You are already connected to that server");
195       goto out;
196     }
197
198     /* Close connection */
199     cmd->client->ops->disconnect(cmd->client, cmd->conn);
200     silc_client_close_connection(cmd->client, cmd->conn->sock);
201   }
202 #endif
203
204   /* Connect asynchronously to not to block user interface */
205   silc_client_connect_to_server(cmd->client, port, hostname, NULL);
206
207  out:
208   silc_client_command_free(cmd);
209 }
210
211 /* Local command AWAY. Client replies with away message to whomever sends
212    private message to the client if the away message is set. If this is
213    given without arguments the away message is removed. */
214
215 SILC_CLIENT_LCMD_FUNC(away)
216 {
217   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
218   SilcClientConnection conn = cmd->conn;
219   SilcClient client = cmd->client;
220   SilcClientInternal app = (SilcClientInternal)client->application;
221   unsigned char modebuf[4];
222   SilcBuffer idp, buffer;
223
224   if (!cmd->conn) {
225     silc_say(client, conn,
226              "You are not connected to a server, use /SERVER to connect");
227     goto out;
228   }
229
230   if (cmd->argc == 1) {
231     conn->local_entry->mode &= ~SILC_UMODE_GONE;
232
233     if (conn->away) {
234       silc_free(conn->away->away);
235       silc_free(conn->away);
236       conn->away = NULL;
237       app->screen->bottom_line->away = FALSE;
238
239       silc_say(client, conn, "Away message removed");
240       silc_screen_print_bottom_line(app->screen, 0);
241     }
242   } else {
243     conn->local_entry->mode |= SILC_UMODE_GONE;
244   
245     if (conn->away)
246       silc_free(conn->away->away);
247     else
248       conn->away = silc_calloc(1, sizeof(*conn->away));
249     
250     app->screen->bottom_line->away = TRUE;
251     conn->away->away = strdup(cmd->argv[1]);
252
253     silc_say(client, conn, "Away message set: %s", conn->away->away);
254     silc_screen_print_bottom_line(app->screen, 0);
255   }
256
257   /* Send the UMODE command to se myself as gone */
258   idp = silc_id_payload_encode(conn->local_id, SILC_ID_CLIENT);
259   SILC_PUT32_MSB(conn->local_entry->mode, modebuf);
260   buffer = silc_command_payload_encode_va(SILC_COMMAND_UMODE, 
261                                           ++conn->cmd_ident, 2, 
262                                           1, idp->data, idp->len, 
263                                           2, modebuf, sizeof(modebuf));
264   silc_client_packet_send(cmd->client, conn->sock, SILC_PACKET_COMMAND, 
265                           NULL, 0, NULL, NULL, buffer->data, 
266                           buffer->len, TRUE);
267   silc_buffer_free(buffer);
268   silc_buffer_free(idp);
269
270  out:
271   silc_client_command_free(cmd);
272 }
273
274 typedef struct {
275   int type;                     /* 1 = msg, 2 = channel */
276 } *KeyInternal;
277
278 static SilcSKEKeyMaterial *curr_key = NULL;
279
280 /* Key agreement callback that is called after the key agreement protocol
281    has been performed. This is called also if error occured during the
282    key agreement protocol. The `key' is the allocated key material and
283    the caller is responsible of freeing it. The `key' is NULL if error
284    has occured. The application can freely use the `key' to whatever
285    purpose it needs. See lib/silcske/silcske.h for the definition of
286    the SilcSKEKeyMaterial structure. */
287
288 static void keyagr_completion(SilcClient client,
289                               SilcClientConnection conn,
290                               SilcClientEntry client_entry,
291                               SilcKeyAgreementStatus status,
292                               SilcSKEKeyMaterial *key,
293                               void *context)
294 {
295   KeyInternal i = (KeyInternal)context;
296
297   curr_key = NULL;
298
299   switch(status) {
300   case SILC_KEY_AGREEMENT_OK:
301     silc_say(client, conn, "Key agreement compeleted successfully with %s",
302              client_entry->nickname);;
303
304     if (i->type == 1) {
305       if (!silc_client_ask_yes_no(client, 
306          "Would you like to use the key with private messages (y/n)? ")) {
307         silc_say(client, conn, "You can set the key material into use later by giving /KEY msg set command");
308         curr_key = key;
309         break;
310       }
311       
312       /* Set the private key for this client */
313       silc_client_del_private_message_key(client, conn, client_entry);
314       silc_client_add_private_message_key_ske(client, conn, client_entry,
315                                               NULL, key);
316       silc_say(client, conn, "The private messages with the %s are now protected with the private key", client_entry->nickname);
317       silc_ske_free_key_material(key);
318     }
319     
320     break;
321     
322   case SILC_KEY_AGREEMENT_ERROR:
323     silc_say(client, conn, "Error occured during key agreement with %s",
324              client_entry->nickname);
325     break;
326     
327   case SILC_KEY_AGREEMENT_FAILURE:
328     silc_say(client, conn, "The key agreement failed with %s",
329              client_entry->nickname);
330     break;
331     
332   case SILC_KEY_AGREEMENT_TIMEOUT:
333     silc_say(client, conn, "Timeout during key agreement. The key agreement was not performed with %s",
334              client_entry->nickname);
335     break;
336     
337   default:
338     break;
339   } 
340
341   if (i)
342     silc_free(i);
343 }
344
345 /* Local command KEY. This command is used to set and unset private
346    keys for channels, set and unset private keys for private messages
347    with remote clients and to send key agreement requests and
348    negotiate the key agreement protocol with remote client.  The
349    key agreement is supported only to negotiate private message keys,
350    it currently cannot be used to negotiate private keys for channels,
351    as it is not convenient for that purpose. */
352
353 SILC_CLIENT_LCMD_FUNC(key)
354 {
355   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
356   SilcClientConnection conn = cmd->conn;
357   SilcClient client = cmd->client;
358   SilcClientEntry client_entry = NULL;
359   SilcChannelEntry channel_entry = NULL;
360   uint32 num = 0;
361   char *nickname = NULL, *server = NULL;
362   int command = 0, port = 0, type = 0;
363   char *hostname = NULL;
364   KeyInternal internal = NULL;
365
366   if (!cmd->conn) {
367     silc_say(client, conn,
368              "You are not connected to a server, use /SERVER to connect");
369     goto out;
370   }
371
372   if (cmd->argc < 4) {
373     silc_say(client, conn, "Usage: /KEY msg|channel <nickname|channel> "
374              "set|unset|agreement|negotiate [<arguments>]");
375     goto out;
376   }
377
378   /* Get type */
379   if (!strcasecmp(cmd->argv[1], "msg"))
380     type = 1;
381   if (!strcasecmp(cmd->argv[1], "channel"))
382     type = 2;
383
384   if (type == 0) {
385     silc_say(client, conn, "Usage: /KEY msg|channel <nickname|channel> "
386              "set|unset|agreement|negotiate [<arguments>]");
387     goto out;
388   }
389
390   if (type == 1) {
391     if (cmd->argv[2][0] == '*') {
392       nickname = "*";
393     } else {
394       /* Parse the typed nickname. */
395       if (!silc_parse_nickname(cmd->argv[2], &nickname, &server, &num)) {
396         silc_say(client, conn, "Bad nickname");
397         goto out;
398       }
399       
400       /* Find client entry */
401       client_entry = silc_idlist_get_client(client, conn, nickname, 
402                                             server, num, TRUE);
403       if (!client_entry) {
404         /* Client entry not found, it was requested thus mark this to be
405            pending command. */
406         silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
407                                     conn->cmd_ident, 
408                                     NULL, silc_client_local_command_key, 
409                                     context);
410         return;
411       }
412     }
413   }
414
415   if (type == 2) {
416     /* Get channel entry */
417     char *name;
418
419     if (cmd->argv[2][0] == '*') {
420       if (!conn->current_channel) {
421         cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
422         goto out;
423       }
424       name = conn->current_channel->channel_name;
425     } else {
426       name = cmd->argv[2];
427     }
428
429     channel_entry = silc_client_get_channel(client, conn, name);
430     if (!channel_entry) {
431       silc_say(client, conn, "You are not on that channel");
432       goto out;
433     }
434   }
435
436   /* Set command */
437   if (!strcasecmp(cmd->argv[3], "set")) {
438     command = 1;
439
440     if (cmd->argc == 4) {
441       if (curr_key && type == 1 && client_entry) {
442         silc_client_del_private_message_key(client, conn, client_entry);
443         silc_client_add_private_message_key_ske(client, conn, client_entry,
444                                                 NULL, curr_key);
445         goto out;
446       }
447     }
448
449     if (cmd->argc >= 5) {
450       if (type == 1 && client_entry) {
451         /* Set private message key */
452         
453         silc_client_del_private_message_key(client, conn, client_entry);
454
455         if (cmd->argc >= 6)
456           silc_client_add_private_message_key(client, conn, client_entry,
457                                               cmd->argv[5], cmd->argv[4],
458                                               cmd->argv_lens[4],
459                                               (cmd->argv[4][0] == '*' ?
460                                                TRUE : FALSE));
461         else
462           silc_client_add_private_message_key(client, conn, client_entry,
463                                               NULL, cmd->argv[4],
464                                               cmd->argv_lens[4],
465                                               (cmd->argv[4][0] == '*' ?
466                                                TRUE : FALSE));
467
468         /* Send the key to the remote client so that it starts using it
469            too. */
470         silc_client_send_private_message_key(client, conn, client_entry, TRUE);
471       } else if (type == 2) {
472         /* Set private channel key */
473         char *cipher = NULL, *hmac = NULL;
474
475         if (!(channel_entry->mode & SILC_CHANNEL_MODE_PRIVKEY)) {
476           silc_say(client, conn, 
477                    "Private key mode is not set on this channel");
478           goto out;
479         }
480
481         if (cmd->argc >= 6)
482           cipher = cmd->argv[5];
483         if (cmd->argc >= 7)
484           hmac = cmd->argv[6];
485
486         if (!silc_client_add_channel_private_key(client, conn, channel_entry,
487                                                  cipher, hmac,
488                                                  cmd->argv[4],
489                                                  cmd->argv_lens[4])) {
490           silc_say(client, conn, "Could not add channel private key");
491           goto out;
492         }
493       }
494     }
495
496     goto out;
497   }
498   
499   /* Unset command */
500   if (!strcasecmp(cmd->argv[3], "unset")) {
501     command = 2;
502
503     if (type == 1 && client_entry) {
504       /* Unset private message key */
505       silc_client_del_private_message_key(client, conn, client_entry);
506     } else if (type == 2) {
507       /* Unset channel key(s) */
508       SilcChannelPrivateKey *keys;
509       uint32 keys_count;
510       int number;
511
512       if (cmd->argc == 4)
513         silc_client_del_channel_private_keys(client, conn, channel_entry);
514
515       if (cmd->argc > 4) {
516         number = atoi(cmd->argv[4]);
517         keys = silc_client_list_channel_private_keys(client, conn, 
518                                                      channel_entry,
519                                                      &keys_count);
520         if (!keys)
521           goto out;
522
523         if (!number || number > keys_count) {
524           silc_client_free_channel_private_keys(keys, keys_count);
525           goto out;
526         }
527
528         silc_client_del_channel_private_key(client, conn, channel_entry,
529                                             keys[number - 1]);
530         silc_client_free_channel_private_keys(keys, keys_count);
531       }
532
533       goto out;
534     }
535   }
536
537   /* List command */
538   if (!strcasecmp(cmd->argv[3], "list")) {
539     command = 3;
540
541     if (type == 1) {
542       SilcPrivateMessageKeys keys;
543       uint32 keys_count;
544       int k, i, len;
545       char buf[1024];
546
547       keys = silc_client_list_private_message_keys(client, conn, 
548                                                    &keys_count);
549       if (!keys)
550         goto out;
551
552       /* list the private message key(s) */
553       if (nickname[0] == '*') {
554         silc_say(client, conn, "Private message keys");
555         silc_say(client, conn, 
556                  "  Client                         Cipher         Key");
557         for (k = 0; k < keys_count; k++) {
558           memset(buf, 0, sizeof(buf));
559           strncat(buf, "  ", 2);
560           len = strlen(keys[k].client_entry->nickname);
561           strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len);
562           if (len < 30)
563             for (i = 0; i < 30 - len; i++)
564               strcat(buf, " ");
565           strcat(buf, " ");
566           
567           len = strlen(keys[k].cipher);
568           strncat(buf, keys[k].cipher, len > 14 ? 14 : len);
569           if (len < 14)
570             for (i = 0; i < 14 - len; i++)
571               strcat(buf, " ");
572           strcat(buf, " ");
573
574           if (keys[k].key)
575             strcat(buf, "<hidden>");
576           else
577             strcat(buf, "*generated*");
578
579           silc_say(client, conn, "%s", buf);
580         }
581       } else {
582         silc_say(client, conn, "Private message key", 
583                  client_entry->nickname);
584         silc_say(client, conn, 
585                  "  Client                         Cipher         Key");
586         for (k = 0; k < keys_count; k++) {
587           if (keys[k].client_entry != client_entry)
588             continue;
589
590           memset(buf, 0, sizeof(buf));
591           strncat(buf, "  ", 2);
592           len = strlen(keys[k].client_entry->nickname);
593           strncat(buf, keys[k].client_entry->nickname, len > 30 ? 30 : len);
594           if (len < 30)
595             for (i = 0; i < 30 - len; i++)
596               strcat(buf, " ");
597           strcat(buf, " ");
598           
599           len = strlen(keys[k].cipher);
600           strncat(buf, keys[k].cipher, len > 14 ? 14 : len);
601           if (len < 14)
602             for (i = 0; i < 14 - len; i++)
603               strcat(buf, " ");
604           strcat(buf, " ");
605
606           if (keys[k].key)
607             strcat(buf, "<hidden>");
608           else
609             strcat(buf, "*generated*");
610
611           silc_say(client, conn, "%s", buf);
612         }
613       }
614
615       silc_client_free_private_message_keys(keys, keys_count);
616     } else if (type == 2) {
617       SilcChannelPrivateKey *keys;
618       uint32 keys_count;
619       int k, i, len;
620       char buf[1024];
621
622       keys = silc_client_list_channel_private_keys(client, conn, channel_entry,
623                                                    &keys_count);
624       if (!keys)
625         goto out;
626
627       silc_say(client, conn, "Channel %s private keys", 
628                channel_entry->channel_name);
629       silc_say(client, conn, 
630                "  Cipher           Hmac             Key");
631       for (k = 0; k < keys_count; k++) {
632         memset(buf, 0, sizeof(buf));
633         strncat(buf, "  ", 2);
634
635         len = strlen(keys[k]->cipher->cipher->name);
636         strncat(buf, keys[k]->cipher->cipher->name, len > 16 ? 16 : len);
637         if (len < 16)
638           for (i = 0; i < 16 - len; i++)
639             strcat(buf, " ");
640         strcat(buf, " ");
641         
642         len = strlen(keys[k]->hmac->hmac->name);
643         strncat(buf, keys[k]->hmac->hmac->name, len > 16 ? 16 : len);
644         if (len < 16)
645           for (i = 0; i < 16 - len; i++)
646             strcat(buf, " ");
647         strcat(buf, " ");
648         
649         strcat(buf, "<hidden>");
650
651         silc_say(client, conn, "%s", buf);
652       }
653       
654       silc_client_free_channel_private_keys(keys, keys_count);
655     }
656
657     goto out;
658   }
659
660   /* Send command is used to send key agreement */
661   if (!strcasecmp(cmd->argv[3], "agreement")) {
662     command = 4;
663
664     if (cmd->argc >= 5)
665       hostname = cmd->argv[4];
666     if (cmd->argc >= 6)
667       port = atoi(cmd->argv[5]);
668
669     internal = silc_calloc(1, sizeof(*internal));
670     internal->type = type;
671   }
672
673   /* Start command is used to start key agreement (after receiving the
674      key_agreement client operation). */
675   if (!strcasecmp(cmd->argv[3], "negotiate")) {
676     command = 5;
677
678     if (cmd->argc >= 5)
679       hostname = cmd->argv[4];
680     if (cmd->argc >= 6)
681       port = atoi(cmd->argv[5]);
682
683     internal = silc_calloc(1, sizeof(*internal));
684     internal->type = type;
685   }
686
687   if (command == 0) {
688     silc_say(client, conn, "Usage: /KEY msg|channel <nickname|channel> "
689              "set|unset|agreement|negotiate [<arguments>]");
690     goto out;
691   }
692
693   if (command == 4 && client_entry) {
694     silc_say(client, conn, "Sending key agreement to %s", cmd->argv[2]);
695     silc_client_send_key_agreement(client, conn, client_entry, hostname, 
696                                    port, 120, keyagr_completion, internal);
697     goto out;
698   }
699
700   if (command == 5 && client_entry) {
701     silc_say(client, conn, "Starting key agreement with %s", cmd->argv[2]);
702     silc_client_perform_key_agreement(client, conn, client_entry, hostname, 
703                                       port, keyagr_completion, internal);
704     goto out;
705   }
706
707  out:
708   if (nickname)
709     silc_free(nickname);
710   if (server)
711     silc_free(server);
712   silc_client_command_free(cmd);
713 }
714
715 /* Sends an action to the channel.  Equals CTCP's ACTION (IRC's /ME) 
716    command. */
717
718 SILC_CLIENT_LCMD_FUNC(me)
719 {
720   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
721   SilcClientConnection conn = cmd->conn;
722   SilcClient client = cmd->client;
723   SilcChannelEntry channel_entry;
724   char *name;
725
726   if (!cmd->conn) {
727     silc_say(client, conn,
728              "You are not connected to a server, use /SERVER to connect");
729     goto out;
730   }
731
732   if (cmd->argc < 3) {
733     silc_say(client, conn, "Usage: /ME <channel> <action message>");
734     goto out;
735   }
736
737   if (cmd->argv[1][0] == '*') {
738     if (!conn->current_channel) {
739       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
740       goto out;
741     }
742     name = conn->current_channel->channel_name;
743   } else {
744     name = cmd->argv[1];
745   }
746
747   channel_entry = silc_client_get_channel(client, conn, name);
748   if (!channel_entry) {
749     silc_say(client, conn, "You are not on that channel");
750     goto out;
751   }
752
753   /* Send the action message */
754   silc_client_send_channel_message(client, conn, channel_entry, NULL,
755                                    SILC_MESSAGE_FLAG_ACTION, 
756                                    cmd->argv[2], cmd->argv_lens[2], TRUE);
757
758   silc_print(client, "* %s %s", conn->nickname, cmd->argv[2]);
759
760  out:
761   silc_client_command_free(cmd);
762 }
763
764 /* Sends an notice to the channel.  */
765
766 SILC_CLIENT_LCMD_FUNC(notice)
767 {
768   SilcClientCommandContext cmd = (SilcClientCommandContext)context;
769   SilcClientConnection conn = cmd->conn;
770   SilcClient client = cmd->client;
771   SilcChannelEntry channel_entry;
772   char *name;
773
774   if (!cmd->conn) {
775     silc_say(client, conn,
776              "You are not connected to a server, use /SERVER to connect");
777     goto out;
778   }
779
780   if (cmd->argc < 3) {
781     silc_say(client, conn, "Usage: /NOTICE <channel> <message>");
782     goto out;
783   }
784
785   if (cmd->argv[1][0] == '*') {
786     if (!conn->current_channel) {
787       cmd->client->ops->say(cmd->client, conn, "You are not on any channel");
788       goto out;
789     }
790     name = conn->current_channel->channel_name;
791   } else {
792     name = cmd->argv[1];
793   }
794
795   channel_entry = silc_client_get_channel(client, conn, name);
796   if (!channel_entry) {
797     silc_say(client, conn, "You are not on that channel");
798     goto out;
799   }
800
801   /* Send the action message */
802   silc_client_send_channel_message(client, conn, channel_entry, NULL,
803                                    SILC_MESSAGE_FLAG_NOTICE, 
804                                    cmd->argv[2], cmd->argv_lens[2], TRUE);
805
806   silc_print(client, "- %s %s", conn->nickname, cmd->argv[2]);
807
808  out:
809   silc_client_command_free(cmd);
810 }