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