Moved silc_client_ch[u]mode[_char] to client library from silc/.
[silc.git] / apps / silc / silc.c
1 /*
2
3   silc.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 "version.h"
24
25 /* Static function prototypes */
26 static int silc_client_bad_keys(unsigned char key);
27 static void silc_client_clear_input(SilcClientInternal app);
28 static void silc_client_process_message(SilcClientInternal app);
29 static char *silc_client_parse_command(unsigned char *buffer);
30
31 void silc_client_create_main_window(SilcClientInternal app);
32
33 /* Static task callback prototypes */
34 SILC_TASK_CALLBACK(silc_client_update_clock);
35 SILC_TASK_CALLBACK(silc_client_run_commands);
36 SILC_TASK_CALLBACK(silc_client_process_key_press);
37
38 /* Long command line options */
39 static struct option long_opts[] = 
40 {
41   /* Generic options */
42   { "server", 1, NULL, 's' },
43   { "port", 1, NULL, 'p' },
44   { "nickname", 1, NULL, 'n' },
45   { "channel", 1, NULL, 'c' },
46   { "cipher", 1, NULL, 'r' },
47   { "public-key", 1, NULL, 'b' },
48   { "private-key", 1, NULL, 'k' },
49   { "config-file", 1, NULL, 'f' },
50   { "no-silcrc", 0, NULL, 'q' },
51   { "debug", 0, NULL, 'd' },
52   { "help", 0, NULL, 'h' },
53   { "version", 0, NULL, 'V' },
54   { "list-ciphers", 0, NULL, 1 },
55   { "list-hash-funcs", 0, NULL, 2 },
56   { "list-pkcs", 0, NULL, 3 },
57
58   /* Key management options */
59   { "create-key-pair", 0, NULL, 'C' },
60   { "pkcs", 1, NULL, 10 },
61   { "bits", 1, NULL, 11 },
62
63   { NULL, 0, NULL, 0 }
64 };
65
66 /* Command line option variables */
67 static char *opt_server = NULL;
68 static int opt_port = 0;
69 static char *opt_nickname = NULL;
70 static char *opt_channel = NULL;
71 static char *opt_cipher = NULL;
72 static char *opt_public_key = NULL;
73 static char *opt_private_key = NULL;
74 static char *opt_config_file = NULL;
75 static int opt_no_silcrc = FALSE;
76
77 static int opt_create_keypair = FALSE;
78 static char *opt_pkcs = NULL;
79 static int opt_bits = 0;
80
81 /* SILC Client operations */
82 extern SilcClientOperations ops;
83
84 /* Prints out the usage of silc client */
85
86 void usage()
87 {
88   printf("\
89 Usage: silc [options]\n\
90 \n\
91   Generic Options:\n\
92   -s, --server=HOST            Open connection to server HOST\n\
93   -p, --port=PORT              Set PORT as default port to connect\n\
94   -n, --nickname=STRING        Set default nickname on startup\n\
95   -c, --channel=STRING         Join channel on startup\n\
96   -r, --cipher=CIPHER          Use CIPHER as default cipher in SILC\n\
97   -b, --public-key=FILE        Public key used in SILC\n\
98   -k, --private-key=FILE       Private key used in SILC\n\
99   -f, --config-file=FILE       Alternate configuration file\n\
100   -q, --no-silcrc              Don't load ~/.silcrc on startup\n\
101   -d, --debug                  Enable debugging\n\
102   -h, --help                   Display this help message\n\
103   -V, --version                Display version\n\
104       --list-ciphers           List supported ciphers\n\
105       --list-hash-funcs        List supported hash functions\n\
106       --list-pkcs              List supported PKCS's\n\
107 \n\
108   Key Management Options:\n\
109   -C, --create-key-pair        Create new public key pair\n\
110       --pkcs=PKCS              Set the PKCS of the public key pair\n\
111       --bits=VALUE             Set length of the public key pair\n\
112 \n");
113 }
114
115 int main(int argc, char **argv)
116 {
117   int opt, option_index = 1;
118   int ret;
119   SilcClient silc = NULL;
120   SilcClientInternal app = NULL;
121
122   silc_debug = FALSE;
123
124   if (argc > 1) 
125     {
126       while ((opt = 
127               getopt_long(argc, argv,
128                           "s:p:n:c:b:k:f:qdhVC",
129                           long_opts, &option_index)) != EOF)
130         {
131           switch(opt) 
132             {
133               /* 
134                * Generic options
135                */
136             case 's':
137               if (optarg)
138                 opt_server = strdup(optarg);
139               break;
140             case 'p':
141               if (optarg)
142                 opt_port = atoi(optarg);
143               break;
144             case 'n':
145               if (optarg)
146                 opt_nickname = strdup(optarg);
147               break;
148             case 'c':
149               if (optarg)
150                 opt_channel = strdup(optarg);
151               break;
152             case 'r':
153               if (optarg)
154                 opt_cipher = strdup(optarg);
155               break;
156             case 'b':
157               if (optarg)
158                 opt_public_key = strdup(optarg);
159               break;
160             case 'k':
161               if (optarg)
162                 opt_private_key = strdup(optarg);
163               break;
164             case 'f':
165               if (optarg)
166                 opt_config_file = strdup(optarg);
167               break;
168             case 'q':
169               opt_no_silcrc = TRUE;
170               break;
171             case 'd':
172               silc_debug = TRUE;
173               break;
174             case 'h':
175               usage();
176               exit(0);
177               break;
178             case 'V':
179               printf("\
180 SILC Secure Internet Live Conferencing, version %s\n", 
181                      silc_version);
182               printf("\
183 (c) 1997 - 2000 Pekka Riikonen <priikone@poseidon.pspt.fi>\n");
184               exit(0);
185               break;
186             case 1:
187               silc_client_list_ciphers();
188               exit(0);
189               break;
190             case 2:
191               silc_client_list_hash_funcs();
192               exit(0);
193               break;
194             case 3:
195               silc_client_list_pkcs();
196               exit(0);
197               break;
198
199               /*
200                * Key management options
201                */
202             case 'C':
203               opt_create_keypair = TRUE;
204               break;
205             case 10:
206               if (optarg)
207                 opt_pkcs = strdup(optarg);
208               break;
209             case 11:
210               if (optarg)
211                 opt_bits = atoi(optarg);
212               break;
213
214             default:
215               exit(0);
216               break;
217             }
218         }
219     }
220
221   /* Init signals */
222   signal(SIGHUP, SIG_DFL);
223   signal(SIGTERM, SIG_DFL);
224   signal(SIGPIPE, SIG_IGN);
225   signal(SIGCHLD, SIG_DFL);
226   signal(SIGALRM, SIG_IGN);
227   signal(SIGQUIT, SIG_IGN);
228   signal(SIGSEGV, SIG_DFL);
229   signal(SIGBUS, SIG_DFL);
230   signal(SIGFPE, SIG_DFL);
231   //  signal(SIGINT, SIG_IGN);
232   
233 #ifdef SOCKS
234   /* Init SOCKS */
235   SOCKSinit(argv[0]);
236 #endif
237
238   if (opt_create_keypair == TRUE) {
239     /* Create new key pair and exit */
240     silc_client_create_key_pair(opt_pkcs, opt_bits, 
241                                 NULL, NULL, NULL, NULL, NULL);
242     exit(0);
243   }
244
245   /* Default configuration file */
246   if (!opt_config_file)
247     opt_config_file = strdup(SILC_CLIENT_CONFIG_FILE);
248
249   /* Allocate internal application context */
250   app = silc_calloc(1, sizeof(*app));
251
252   /* Allocate new client */
253   app->client = silc = silc_client_alloc(&ops, app);
254   if (!silc)
255     goto fail;
256
257   /* Read global configuration file. */
258   app->config = silc_client_config_alloc(opt_config_file);
259   if (app->config == NULL)
260     goto fail;
261
262   /* XXX Read local configuration file */
263
264   /* Check ~/.silc directory and public and private keys */
265   if (silc_client_check_silc_dir() == FALSE)
266     goto fail;
267
268   /* Get user information */
269   silc->username = silc_get_username();
270   silc->hostname = silc_net_localhost();
271   silc->realname = silc_get_real_name();
272
273   /* Register all configured ciphers, PKCS and hash functions. */
274   app->config->client = (void *)app;
275   silc_client_config_register_ciphers(app->config);
276   silc_client_config_register_pkcs(app->config);
277   silc_client_config_register_hashfuncs(app->config);
278
279   /* Load public and private key */
280   if (silc_client_load_keys(silc) == FALSE)
281     goto fail;
282
283   /* Initialize the client. This initializes the client library and
284      sets everything ready for silc_client_run. */
285   ret = silc_client_init(silc);
286   if (ret == FALSE)
287     goto fail;
288
289   /* Register the main task that is used in client. This receives
290      the key pressings. */
291   silc_task_register(silc->io_queue, fileno(stdin), 
292                      silc_client_process_key_press,
293                      (void *)silc, 0, 0, 
294                      SILC_TASK_FD,
295                      SILC_TASK_PRI_NORMAL);
296
297   /* Register timeout task that updates clock every minute. */
298   silc_task_register(silc->timeout_queue, 0,
299                      silc_client_update_clock,
300                      (void *)silc, 
301                      silc_client_time_til_next_min(), 0,
302                      SILC_TASK_TIMEOUT,
303                      SILC_TASK_PRI_LOW);
304
305   if (app->config->commands) {
306     /* Run user configured commands with timeout */
307     silc_task_register(silc->timeout_queue, 0,
308                        silc_client_run_commands,
309                        (void *)silc, 0, 1,
310                        SILC_TASK_TIMEOUT,
311                        SILC_TASK_PRI_LOW);
312   }
313
314   /* Allocate the input buffer used to save typed characters */
315   app->input_buffer = silc_buffer_alloc(SILC_SCREEN_INPUT_WIN_SIZE);
316   silc_buffer_pull_tail(app->input_buffer, 
317                         SILC_BUFFER_END(app->input_buffer));
318
319   /* Initialize the screen */
320   silc_client_create_main_window(app);
321   silc_screen_print_coordinates(app->screen, 0);
322
323   /* Run the client. When this returns the application will be
324      terminated. */
325   silc_client_run(silc);
326
327   /* Stop the client. This probably has been done already but it
328      doesn't hurt to do it here again. */
329   silc_client_stop(silc);
330   silc_client_free(silc);
331   
332   exit(0);
333
334  fail:
335   if (opt_config_file)
336     silc_free(opt_config_file);
337   if (app->config)
338     silc_client_config_free(app->config);
339   if (silc)
340     silc_client_free(silc);
341   exit(1);
342 }
343
344 /* Creates the main window used in SILC client. This is called always
345    at the initialization of the client. If user wants to create more
346    than one windows a new windows are always created by calling 
347    silc_client_add_window. */
348
349 void silc_client_create_main_window(SilcClientInternal app)
350 {
351   void *screen;
352
353   SILC_LOG_DEBUG(("Creating main window"));
354
355   app->screen = silc_screen_init();
356   app->screen->input_buffer = app->input_buffer->data;
357   app->screen->u_stat_line.program_name = silc_name;
358   app->screen->u_stat_line.program_version = silc_version;
359
360   /* Create the actual screen */
361   screen = (void *)silc_screen_create_output_window(app->screen);
362   silc_screen_create_input_window(app->screen);
363   silc_screen_init_upper_status_line(app->screen);
364   silc_screen_init_output_status_line(app->screen);
365
366   app->screen->bottom_line->nickname = silc_get_username();
367   silc_screen_print_bottom_line(app->screen, 0);
368 }
369
370 /* The main task on SILC client. This processes the key pressings user
371    has made. */
372
373 SILC_TASK_CALLBACK(silc_client_process_key_press)
374 {
375   SilcClient client = (SilcClient)context;
376   SilcClientInternal app = (SilcClientInternal)client->application;
377   int c;
378
379   /* There is data pending in stdin, this gets it directly */
380   c = wgetch(app->screen->input_win);
381   if (silc_client_bad_keys(c))
382     return;
383
384   SILC_LOG_DEBUG(("Pressed key: %d", c));
385
386   switch(c) {
387     /* 
388      * Special character handling
389      */
390   case KEY_UP: 
391   case KEY_DOWN:
392     break;
393   case KEY_RIGHT:
394     /* Right arrow */
395     SILC_LOG_DEBUG(("RIGHT"));
396     silc_screen_input_cursor_right(app->screen);
397     break;
398   case KEY_LEFT:
399     /* Left arrow */
400     SILC_LOG_DEBUG(("LEFT"));
401     silc_screen_input_cursor_left(app->screen);
402     break;
403   case KEY_BACKSPACE:
404   case KEY_DC:
405   case '\177':
406   case '\b':
407     /* Backspace */
408     silc_screen_input_backspace(app->screen);
409     break;
410   case '\011':
411     /* Tabulator */
412     break;
413   case KEY_IC:
414     /* Insert switch. Turns on/off insert on input window */
415     silc_screen_input_insert(app->screen);
416     break;
417   case CTRL('j'):
418   case '\r':
419     /* Enter, Return. User pressed enter we are ready to
420        process the message. */
421     silc_client_process_message(app);
422     break;
423   case CTRL('l'):
424     /* Refresh screen, Ctrl^l */
425     silc_screen_refresh_all(app->screen);
426     break;
427   case CTRL('a'):
428   case KEY_HOME:
429 #ifdef KEY_BEG
430   case KEY_BEG:
431 #endif
432     /* Beginning, Home */
433     silc_screen_input_cursor_home(app->screen);
434     break;
435   case CTRL('e'):
436 #ifdef KEY_END
437   case KEY_END:
438 #endif
439   case KEY_LL:
440     /* End */
441     silc_screen_input_cursor_end(app->screen);
442     break;
443   case CTRL('g'):
444     /* Bell, Ctrl^g */
445     beep();
446     break;
447   case KEY_DL:
448   case CTRL('u'):
449     /* Delete line */
450     silc_client_clear_input(app);
451     break;
452   default:
453     /* 
454      * Other characters 
455      */
456     if (c < 32) {
457       /* Control codes are printed as reversed */
458       c = (c & 127) | 64;
459       wattron(app->screen->input_win, A_REVERSE);
460       silc_screen_input_print(app->screen, c);
461       wattroff(app->screen->input_win, A_REVERSE);
462     } else  {
463       /* Normal character */
464       silc_screen_input_print(app->screen, c);
465     }
466   }
467
468   silc_screen_print_coordinates(app->screen, 0);
469   silc_screen_refresh_win(app->screen->input_win);
470 }
471
472 static int silc_client_bad_keys(unsigned char key)
473 {
474   /* these are explained in curses.h */
475   switch(key) {
476   case KEY_SF:
477   case KEY_SR:
478   case KEY_NPAGE:
479   case KEY_PPAGE:
480   case KEY_PRINT:
481   case KEY_A1:
482   case KEY_A3:
483   case KEY_B2:
484   case KEY_C1:
485   case KEY_C3:
486 #ifdef KEY_UNDO
487   case KEY_UNDO:
488 #endif
489 #ifdef KEY_EXIT
490   case KEY_EXIT:
491 #endif
492   case '\v':           /* VT */
493   case '\E':           /* we ignore ESC */
494     return TRUE;
495   default: 
496     return FALSE; 
497   }
498 }
499
500 /* Clears input buffer */
501
502 static void silc_client_clear_input(SilcClientInternal app)
503 {
504   silc_buffer_clear(app->input_buffer);
505   silc_buffer_pull_tail(app->input_buffer,
506                         SILC_BUFFER_END(app->input_buffer));
507   silc_screen_input_reset(app->screen);
508 }
509
510 /* Processes messages user has typed on the screen. This either sends
511    a packet out to network or if command were written executes it. */
512
513 static void silc_client_process_message(SilcClientInternal app)
514 {
515   unsigned char *data;
516   unsigned int len;
517
518   SILC_LOG_DEBUG(("Start"));
519
520   data = app->input_buffer->data;
521   len = strlen(data);
522
523   if (data[0] == '/' && data[1] != ' ') {
524     /* Command */
525     unsigned int argc = 0;
526     unsigned char **argv, *tmpcmd;
527     unsigned int *argv_lens, *argv_types;
528     SilcClientCommand *cmd;
529     SilcClientCommandContext ctx;
530
531     /* Get the command */
532     tmpcmd = silc_client_parse_command(data);
533     cmd = silc_client_local_command_find(tmpcmd);
534     if (!cmd && (cmd = silc_client_command_find(tmpcmd)) == NULL) {
535       silc_say(app->client, app->current_win, "Invalid command: %s", tmpcmd);
536       silc_free(tmpcmd);
537       goto out;
538     }
539
540     /* Now parse all arguments */
541     silc_parse_command_line(data + 1, &argv, &argv_lens, 
542                             &argv_types, &argc, cmd->max_args);
543     silc_free(tmpcmd);
544
545     SILC_LOG_DEBUG(("Executing command: %s", cmd->name));
546
547     /* Allocate command context. This and its internals must be free'd 
548        by the command routine receiving it. */
549     ctx = silc_calloc(1, sizeof(*ctx));
550     ctx->client = app->client;
551     ctx->conn = app->conn;
552     ctx->command = cmd;
553     ctx->argc = argc;
554     ctx->argv = argv;
555     ctx->argv_lens = argv_lens;
556     ctx->argv_types = argv_types;
557
558     /* Execute command */
559     (*cmd->cb)(ctx);
560
561   } else {
562     /* Normal message to a channel */
563     if (len && app->conn->current_channel &&
564         app->conn->current_channel->on_channel == TRUE) {
565       silc_print(app->client, "> %s", data);
566       silc_client_packet_send_to_channel(app->client, 
567                                          app->conn->sock,
568                                          app->conn->current_channel,
569                                          data, strlen(data), TRUE);
570     }
571   }
572
573  out:
574   /* Clear the input buffer */
575   silc_client_clear_input(app);
576 }
577
578 /* Returns the command fetched from user typed command line */
579
580 static char *silc_client_parse_command(unsigned char *buffer)
581 {
582   char *ret;
583   const char *cp = buffer;
584   int len;
585
586   len = strcspn(cp, " ");
587   ret = silc_to_upper((char *)++cp);
588   ret[len - 1] = 0;
589
590   return ret;
591 }
592
593 /* Updates clock on the screen every minute. */
594
595 SILC_TASK_CALLBACK(silc_client_update_clock)
596 {
597   SilcClient client = (SilcClient)context;
598   SilcClientInternal app = (SilcClientInternal)client->application;
599
600   /* Update the clock on the screen */
601   silc_screen_print_clock(app->screen);
602
603   /* Re-register this same task */
604   silc_task_register(qptr, 0, silc_client_update_clock, context, 
605                      silc_client_time_til_next_min(), 0,
606                      SILC_TASK_TIMEOUT,
607                      SILC_TASK_PRI_LOW);
608
609   silc_screen_refresh_win(app->screen->input_win);
610 }
611
612 /* Runs commands user configured in configuration file. This is
613    called when initializing client. */
614
615 SILC_TASK_CALLBACK(silc_client_run_commands)
616 {
617   SilcClient client = (SilcClient)context;
618   SilcClientInternal app = (SilcClientInternal)client->application;
619   SilcClientConfigSectionCommand *cs;
620
621   SILC_LOG_DEBUG(("Start"));
622
623   cs = app->config->commands;
624   while(cs) {
625     unsigned int argc = 0;
626     unsigned char **argv, *tmpcmd;
627     unsigned int *argv_lens, *argv_types;
628     SilcClientCommand *cmd;
629     SilcClientCommandContext ctx;
630
631     /* Get the command */
632     tmpcmd = silc_client_parse_command(cs->command);
633     cmd = silc_client_local_command_find(tmpcmd);
634     if (!cmd && (cmd = silc_client_command_find(tmpcmd)) == NULL) {
635       silc_say(client, app->conn, "Invalid command: %s", tmpcmd);
636       silc_free(tmpcmd);
637       continue;
638     }
639     
640     /* Now parse all arguments */
641     silc_parse_command_line(cs->command + 1, &argv, &argv_lens, 
642                             &argv_types, &argc, cmd->max_args);
643     silc_free(tmpcmd);
644
645     SILC_LOG_DEBUG(("Executing command: %s", cmd->name));
646
647     /* Allocate command context. This and its internals must be free'd 
648        by the command routine receiving it. */
649     ctx = silc_calloc(1, sizeof(*ctx));
650     ctx->client = client;
651     ctx->conn = app->conn;
652     ctx->command = cmd;
653     ctx->argc = argc;
654     ctx->argv = argv;
655     ctx->argv_lens = argv_lens;
656     ctx->argv_types = argv_types;
657
658     /* Execute command */
659     (*cmd->cb)(ctx);
660
661     cs = cs->next;
662   }
663 }