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