updates.
[silc.git] / apps / silc / client_ops.c
1 /*
2
3   client_ops.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 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
21 #include "clientincludes.h"
22
23 /* Prints a message with three star (*) sign before the actual message
24    on the current output window. This is used to print command outputs
25    and error messages. */
26
27 void silc_say(SilcClient client, SilcClientConnection conn, 
28               char *msg, ...)
29 {
30   va_list vp;
31   char message[2048];
32   SilcClientInternal app = (SilcClientInternal)client->application;
33
34   memset(message, 0, sizeof(message));
35   strncat(message, "\n***  ", 5);
36
37   va_start(vp, msg);
38   vsprintf(message + 5, msg, vp);
39   va_end(vp);
40   
41   /* Print the message */
42   silc_print_to_window(app->screen->output_win[0], message);
43 }
44
45 /* Message for a channel. The `sender' is the nickname of the sender 
46    received in the packet. The `channel_name' is the name of the channel. */
47
48 void silc_channel_message(SilcClient client, SilcClientConnection conn,
49                           char *sender, char *channel_name, char *msg)
50 {
51   /* Message from client */
52   if (conn && !strcmp(conn->current_channel->channel_name, channel_name))
53     silc_print(client, "<%s> %s", sender, msg);
54   else
55     silc_print(client, "<%s:%s> %s", sender, channel_name, msg);
56 }
57
58 /* Private message to the client. The `sender' is the nickname of the
59    sender received in the packet. */
60
61 void silc_private_message(SilcClient client, SilcClientConnection conn,
62                           char *sender, char *msg)
63 {
64   silc_print(client, "*%s* %s", sender, msg);
65 }
66
67
68 /* Notify message to the client. The notify arguments are sent in the
69    same order as servers sends them. The arguments are same as received
70    from the server except for ID's.  If ID is received application receives
71    the corresponding entry to the ID. For example, if Client ID is received
72    application receives SilcClientEntry.  Also, if the notify type is
73    for channel the channel entry is sent to application (even if server
74    does not send it). */
75
76 void silc_notify(SilcClient client, SilcClientConnection conn, 
77                  SilcNotifyType type, ...)
78 {
79   SilcClientInternal app = (SilcClientInternal)client->application;
80   va_list vp;
81   char message[4096];
82   SilcClientEntry client_entry, client_entry2;
83   SilcChannelEntry channel_entry;
84   char *tmp;
85   unsigned int tmp_int;
86
87   va_start(vp, type);
88
89   memset(message, 0, sizeof(message));
90
91   /* Get arguments (defined by protocol in silc-pp-01 -draft) */
92   switch(type) {
93   case SILC_NOTIFY_TYPE_NONE:
94     tmp = va_arg(vp, char *);
95     if (!tmp)
96       return;
97     strcpy(message, tmp);
98     break;
99
100   case SILC_NOTIFY_TYPE_INVITE:
101     client_entry = va_arg(vp, SilcClientEntry);
102     channel_entry = va_arg(vp, SilcChannelEntry);
103     snprintf(message, sizeof(message), "%s invites you to channel %s", 
104              client_entry->nickname, channel_entry->channel_name);
105     break;
106
107   case SILC_NOTIFY_TYPE_JOIN:
108     client_entry = va_arg(vp, SilcClientEntry);
109     channel_entry = va_arg(vp, SilcChannelEntry);
110     snprintf(message, sizeof(message), "%s (%s) has joined channel %s", 
111              client_entry->nickname, client_entry->username, 
112              channel_entry->channel_name);
113     break;
114
115   case SILC_NOTIFY_TYPE_LEAVE:
116     client_entry = va_arg(vp, SilcClientEntry);
117     channel_entry = va_arg(vp, SilcChannelEntry);
118     if (client_entry->server)
119       snprintf(message, sizeof(message), "%s@%s has left channel %s", 
120                client_entry->nickname, client_entry->server, 
121                channel_entry->channel_name);
122     else
123       snprintf(message, sizeof(message), "%s has left channel %s", 
124                client_entry->nickname, channel_entry->channel_name);
125     break;
126
127   case SILC_NOTIFY_TYPE_SIGNOFF:
128     client_entry = va_arg(vp, SilcClientEntry);
129     if (client_entry->server)
130       snprintf(message, sizeof(message), "Signoff: %s@%s", 
131                client_entry->nickname, client_entry->server);
132     else
133       snprintf(message, sizeof(message), "Signoff: %s", 
134                client_entry->nickname);
135     break;
136
137   case SILC_NOTIFY_TYPE_TOPIC_SET:
138     client_entry = va_arg(vp, SilcClientEntry);
139     tmp = va_arg(vp, char *);
140     channel_entry = va_arg(vp, SilcChannelEntry);
141     if (client_entry->server)
142       snprintf(message, sizeof(message), "%s@%s set topic on %s: %s", 
143                client_entry->nickname, client_entry->server,
144                channel_entry->channel_name, tmp);
145     else
146       snprintf(message, sizeof(message), "%s set topic on %s: %s", 
147                client_entry->nickname, channel_entry->channel_name, tmp);
148     break;
149
150   case SILC_NOTIFY_TYPE_NICK_CHANGE:
151     client_entry = va_arg(vp, SilcClientEntry);
152     client_entry2 = va_arg(vp, SilcClientEntry);
153     if (client_entry->server && client_entry2->server)
154       snprintf(message, sizeof(message), "%s@%s is known as %s@%s", 
155                client_entry->nickname, client_entry->server,
156                client_entry2->nickname, client_entry2->server);
157     else
158       snprintf(message, sizeof(message), "%s is known as %s", 
159                client_entry->nickname, client_entry2->nickname);
160     break;
161
162   case SILC_NOTIFY_TYPE_CMODE_CHANGE:
163     client_entry = va_arg(vp, SilcClientEntry);
164     tmp = silc_client_chmode(va_arg(vp, unsigned int));
165     channel_entry = va_arg(vp, SilcChannelEntry);
166     if (tmp)
167       snprintf(message, sizeof(message), "%s changed channel mode to +%s", 
168                client_entry->nickname, tmp);
169     else
170       snprintf(message, sizeof(message), "%s removed all channel modes", 
171                client_entry->nickname);
172     if (app->screen->bottom_line->channel_mode)
173       silc_free(app->screen->bottom_line->channel_mode);
174     app->screen->bottom_line->channel_mode = tmp;
175     silc_screen_print_bottom_line(app->screen, 0);
176     break;
177
178   case SILC_NOTIFY_TYPE_CUMODE_CHANGE:
179     client_entry = va_arg(vp, SilcClientEntry);
180     tmp_int = va_arg(vp, unsigned int);
181     tmp = silc_client_chumode(tmp_int);
182     client_entry2 = va_arg(vp, SilcClientEntry);
183     channel_entry = va_arg(vp, SilcChannelEntry);
184     if (tmp)
185       snprintf(message, sizeof(message), "%s changed %s mode to +%s", 
186                client_entry->nickname, client_entry2->nickname, tmp);
187     else
188       snprintf(message, sizeof(message), "%s removed %s modes", 
189                client_entry->nickname, client_entry2->nickname);
190     if (client_entry2 == conn->local_entry) {
191       if (app->screen->bottom_line->mode)
192         silc_free(app->screen->bottom_line->mode);
193       app->screen->bottom_line->mode = silc_client_chumode_char(tmp_int);
194       silc_screen_print_bottom_line(app->screen, 0);
195     }
196     silc_free(tmp);
197     break;
198
199   case SILC_NOTIFY_TYPE_MOTD:
200     {
201       char line[256];
202       int i;
203       tmp = va_arg(vp, unsigned char *);
204
205       i = 0;
206       while(tmp[i] != 0) {
207         if (tmp[i++] == '\n') {
208           memset(line, 0, sizeof(line));
209           strncat(line, tmp, i - 1);
210           tmp += i;
211           
212           silc_say(client, conn, "%s", line);
213           
214           if (!strlen(tmp))
215             break;
216           i = 0;
217         }
218       }
219     }
220     return;
221
222   default:
223     break;
224   }
225
226   silc_print(client, "*** %s", message);
227 }
228
229 /* Command handler. This function is called always in the command function.
230    If error occurs it will be called as well. `conn' is the associated
231    client connection. `cmd_context' is the command context that was
232    originally sent to the command. `success' is FALSE if error occured
233    during command. `command' is the command being processed. It must be
234    noted that this is not reply from server. This is merely called just
235    after application has called the command. Just to tell application
236    that the command really was processed. */
237
238 void silc_command(SilcClient client, SilcClientConnection conn, 
239                   SilcClientCommandContext cmd_context, int success,
240                   SilcCommand command)
241 {
242   SilcClientInternal app = (SilcClientInternal)client->application;
243
244   if (!success)
245     return;
246
247   switch(command)
248     {
249     case SILC_COMMAND_QUIT:
250       app->screen->bottom_line->channel = NULL;
251       silc_screen_print_bottom_line(app->screen, 0);
252       break;
253
254     case SILC_COMMAND_LEAVE:
255 #if 0
256       if (!strncmp(conn->current_channel->channel_name, name, strlen(name))) {
257         app->screen->bottom_line->channel = NULL;
258         silc_screen_print_bottom_line(app->screen, 0);
259       }
260 #endif
261       break;
262
263     }
264 }
265
266 /* Command reply handler. This function is called always in the command reply
267    function. If error occurs it will be called as well. Normal scenario
268    is that it will be called after the received command data has been parsed
269    and processed. The function is used to pass the received command data to
270    the application. 
271
272    `conn' is the associated client connection. `cmd_payload' is the command
273    payload data received from server and it can be ignored. It is provided
274    if the application would like to re-parse the received command data,
275    however, it must be noted that the data is parsed already by the library
276    thus the payload can be ignored. `success' is FALSE if error occured.
277    In this case arguments are not sent to the application. `command' is the
278    command reply being processed. The function has variable argument list
279    and each command defines the number and type of arguments it passes to the
280    application (on error they are not sent). */
281
282 void silc_command_reply(SilcClient client, SilcClientConnection conn,
283                         SilcCommandPayload cmd_payload, int success,
284                         SilcCommand command, SilcCommandStatus status, ...)
285 {
286   SilcClientInternal app = (SilcClientInternal)client->application;
287   SilcChannelUser chu;
288   va_list vp;
289
290   if (!success)
291     return;
292
293   va_start(vp, status);
294
295   switch(command)
296     {
297
298     case SILC_COMMAND_JOIN:
299       {
300         unsigned int mode;
301
302         app->screen->bottom_line->channel = va_arg(vp, char *);
303         (void)va_arg(vp, void *);
304         mode = va_arg(vp, unsigned int);
305         app->screen->bottom_line->channel_mode = silc_client_chmode(mode);
306         silc_screen_print_bottom_line(app->screen, 0);
307       }
308       break;
309
310     case SILC_COMMAND_NICK:
311       {
312         SilcClientEntry entry;
313
314         entry = va_arg(vp, SilcClientEntry);
315         silc_say(client, conn, "Your current nickname is %s", entry->nickname);
316         app->screen->bottom_line->nickname = entry->nickname;
317         silc_screen_print_bottom_line(app->screen, 0);
318       }
319       break;
320
321     case SILC_COMMAND_USERS:
322       silc_list_start(conn->current_channel->clients);
323       while ((chu = silc_list_get(conn->current_channel->clients)) 
324              != SILC_LIST_END) {
325         if (chu->client == conn->local_entry) {
326           if (app->screen->bottom_line->mode)
327             silc_free(app->screen->bottom_line->mode);
328           app->screen->bottom_line->mode = 
329             silc_client_chumode_char(chu->mode);
330           silc_screen_print_bottom_line(app->screen, 0);
331           break;
332         }
333       break;
334       }
335     }
336 }
337
338 /* Called to indicate that connection was either successfully established
339    or connecting failed.  This is also the first time application receives
340    the SilcClientConnection objecet which it should save somewhere. */
341
342 void silc_connect(SilcClient client, SilcClientConnection conn, int success)
343 {
344   SilcClientInternal app = (SilcClientInternal)client->application;
345
346   if (success) {
347     app->screen->bottom_line->connection = conn->remote_host;
348     silc_screen_print_bottom_line(app->screen, 0);
349     app->conn = conn;
350   }
351 }
352
353 /* Called to indicate that connection was disconnected to the server. */
354
355 void silc_disconnect(SilcClient client, SilcClientConnection conn)
356 {
357   SilcClientInternal app = (SilcClientInternal)client->application;
358
359   app->screen->bottom_line->connection = NULL;
360   silc_screen_print_bottom_line(app->screen, 0);
361   app->conn = NULL;
362 }
363
364 /* Asks passphrase from user on the input line. */
365
366 unsigned char *silc_ask_passphrase(SilcClient client, 
367                                    SilcClientConnection conn)
368 {
369   SilcClientInternal app = (SilcClientInternal)conn->client->application;
370   char pass1[256], pass2[256];
371   char *ret;
372   int try = 3;
373
374   while(try) {
375
376     /* Print prompt */
377     wattroff(app->screen->input_win, A_INVIS);
378     silc_screen_input_print_prompt(app->screen, "Passphrase: ");
379     wattron(app->screen->input_win, A_INVIS);
380     
381     /* Get string */
382     memset(pass1, 0, sizeof(pass1));
383     wgetnstr(app->screen->input_win, pass1, sizeof(pass1));
384     
385     /* Print retype prompt */
386     wattroff(app->screen->input_win, A_INVIS);
387     silc_screen_input_print_prompt(app->screen, "Retype passphrase: ");
388     wattron(app->screen->input_win, A_INVIS);
389     
390     /* Get string */
391     memset(pass2, 0, sizeof(pass2));
392     wgetnstr(app->screen->input_win, pass2, sizeof(pass2));
393
394     if (!strncmp(pass1, pass2, strlen(pass2)))
395       break;
396
397     try--;
398   }
399
400   ret = silc_calloc(strlen(pass1), sizeof(char));
401   memcpy(ret, pass1, strlen(pass1));
402
403   memset(pass1, 0, sizeof(pass1));
404   memset(pass2, 0, sizeof(pass2));
405
406   wattroff(app->screen->input_win, A_INVIS);
407   silc_screen_input_reset(app->screen);
408
409   return ret;
410 }
411
412 /* Verifies received public key. If user decides to trust the key it is
413    saved as trusted server key for later use. If user does not trust the
414    key this returns FALSE. */
415
416 int silc_verify_server_key(SilcClient client,
417                            SilcClientConnection conn, 
418                            unsigned char *pk, unsigned int pk_len,
419                            SilcSKEPKType pk_type)
420 {
421   SilcSocketConnection sock = conn->sock;
422   char filename[256];
423   char file[256];
424   char *hostname, *fingerprint;
425   struct passwd *pw;
426   struct stat st;
427
428   hostname = sock->hostname ? sock->hostname : sock->ip;
429
430   if (pk_type != SILC_SKE_PK_TYPE_SILC) {
431     silc_say(client, conn, "We don't support server %s key type", hostname);
432     return FALSE;
433   }
434
435   pw = getpwuid(getuid());
436   if (!pw)
437     return FALSE;
438
439   memset(filename, 0, sizeof(filename));
440   memset(file, 0, sizeof(file));
441   snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
442            sock->port);
443   snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s", 
444            pw->pw_dir, file);
445
446   /* Check wheter this key already exists */
447   if (stat(filename, &st) < 0) {
448
449     fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
450     silc_say(client, conn, "Received server %s public key", hostname);
451     silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
452     silc_say(client, conn, "%s", fingerprint);
453     silc_free(fingerprint);
454
455     /* Ask user to verify the key and save it */
456     if (silc_client_ask_yes_no(client, 
457        "Would you like to accept the key (y/n)? "))
458       {
459         /* Save the key for future checking */
460         silc_pkcs_save_public_key_data(filename, pk, pk_len, 
461                                        SILC_PKCS_FILE_PEM);
462         return TRUE;
463       }
464   } else {
465     /* The key already exists, verify it. */
466     SilcPublicKey public_key;
467     unsigned char *encpk;
468     unsigned int encpk_len;
469
470     /* Load the key file */
471     if (!silc_pkcs_load_public_key(filename, &public_key, 
472                                    SILC_PKCS_FILE_PEM))
473       if (!silc_pkcs_load_public_key(filename, &public_key, 
474                                      SILC_PKCS_FILE_BIN)) {
475         fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
476         silc_say(client, conn, "Received server %s public key", hostname);
477         silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
478         silc_say(client, conn, "%s", fingerprint);
479         silc_free(fingerprint);
480         silc_say(client, conn, "Could not load your local copy of the server %s key",
481                  hostname);
482         if (silc_client_ask_yes_no(client, 
483            "Would you like to accept the key anyway (y/n)? "))
484           {
485             /* Save the key for future checking */
486             unlink(filename);
487             silc_pkcs_save_public_key_data(filename, pk, pk_len,
488                                            SILC_PKCS_FILE_PEM);
489             return TRUE;
490           }
491         
492         return FALSE;
493       }
494   
495     /* Encode the key data */
496     encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
497     if (!encpk) {
498       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
499       silc_say(client, conn, "Received server %s public key", hostname);
500       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
501       silc_say(client, conn, "%s", fingerprint);
502       silc_free(fingerprint);
503       silc_say(client, conn, "Your local copy of the server %s key is malformed",
504                hostname);
505       if (silc_client_ask_yes_no(client, 
506          "Would you like to accept the key anyway (y/n)? "))
507         {
508           /* Save the key for future checking */
509           unlink(filename);
510           silc_pkcs_save_public_key_data(filename, pk, pk_len,
511                                          SILC_PKCS_FILE_PEM);
512           return TRUE;
513         }
514
515       return FALSE;
516     }
517
518     if (memcmp(encpk, pk, encpk_len)) {
519       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
520       silc_say(client, conn, "Received server %s public key", hostname);
521       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
522       silc_say(client, conn, "%s", fingerprint);
523       silc_free(fingerprint);
524       silc_say(client, conn, "Server %s key does not match with your local copy",
525                hostname);
526       silc_say(client, conn, "It is possible that the key has expired or changed");
527       silc_say(client, conn, "It is also possible that some one is performing "
528                        "man-in-the-middle attack");
529       
530       /* Ask user to verify the key and save it */
531       if (silc_client_ask_yes_no(client, 
532          "Would you like to accept the key anyway (y/n)? "))
533         {
534           /* Save the key for future checking */
535           unlink(filename);
536           silc_pkcs_save_public_key_data(filename, pk, pk_len,
537                                          SILC_PKCS_FILE_PEM);
538           return TRUE;
539         }
540
541       silc_say(client, conn, "Will not accept server %s key", hostname);
542       return FALSE;
543     }
544
545     /* Local copy matched */
546     return TRUE;
547   }
548
549   silc_say(client, conn, "Will not accept server %s key", hostname);
550   return FALSE;
551 }
552
553 /* Find authentication method and authentication data by hostname and
554    port. The hostname may be IP address as well. The found authentication
555    method and authentication data is returned to `auth_meth', `auth_data'
556    and `auth_data_len'. The function returns TRUE if authentication method
557    is found and FALSE if not. `conn' may be NULL. */
558
559 int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
560                          char *hostname, unsigned short port,
561                          SilcProtocolAuthMeth *auth_meth,
562                          unsigned char **auth_data,
563                          unsigned int *auth_data_len)
564 {
565   SilcClientInternal app = (SilcClientInternal)client->application;
566
567   if (app->config->conns) {
568     SilcClientConfigSectionConnection *conn = NULL;
569
570     /* Check if we find a match from user configured connections */
571     conn = silc_client_config_find_connection(app->config,
572                                               hostname,
573                                               port);
574     if (conn) {
575       /* Match found. Use the configured authentication method */
576       *auth_meth = conn->auth_meth;
577
578       if (conn->auth_data) {
579         *auth_data = strdup(conn->auth_data);
580         *auth_data_len = strlen(conn->auth_data);
581       }
582
583       return TRUE;
584     }
585   }
586
587   return FALSE;
588 }
589
590 /* Notifies application that failure packet was received.  This is called
591    if there is some protocol active in the client.  The `protocol' is the
592    protocol context.  The `failure' is opaque pointer to the failure
593    indication.  Note, that the `failure' is protocol dependant and application
594    must explicitly cast it to correct type.  Usually `failure' is 32 bit
595    failure type (see protocol specs for all protocol failure types). */
596
597 void silc_failure(SilcClient client, SilcClientConnection conn, 
598                   SilcProtocol protocol, void *failure)
599 {
600   SilcClientInternal app = (SilcClientInternal)client->application;
601
602 }
603
604 /* SILC client operations */
605 SilcClientOperations ops = {
606   say:                  silc_say,
607   channel_message:      silc_channel_message,
608   private_message:      silc_private_message,
609   notify:               silc_notify,
610   command:              silc_command,
611   command_reply:        silc_command_reply,
612   connect:              silc_connect,
613   disconnect:           silc_disconnect,
614   get_auth_method:      silc_get_auth_method,
615   verify_server_key:    silc_verify_server_key,
616   ask_passphrase:       silc_ask_passphrase,
617   failure:              silc_failure,
618 };