4df32f6168c55a0dd89b7baa3a516862ee434384
[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 (!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_payload' is the Notify
69    Payload received from server.  Client library may parse it to cache
70    some data received from the payload but it is the application's 
71    responsiblity to retrieve the message and arguments from the payload.
72    The message in the payload sent by server is implementation specific
73    thus it is recommended that application will generate its own message. */
74 /* XXX should generate own messages based on notify type. */
75
76 void silc_notify(SilcClient client, SilcClientConnection conn, 
77                  SilcNotifyPayload notify_payload)
78 {
79   SilcNotifyType type;
80   SilcArgumentPayload args;
81   char message[4096];
82   char *msg;
83
84   type = silc_notify_get_type(notify_payload);
85   msg = silc_notify_get_message(notify_payload);
86   args = silc_notify_get_args(notify_payload);
87
88   memset(message, 0, sizeof(message));
89
90   /* Get arguments (defined by protocol in silc-pp-01 -draft) */
91   switch(type) {
92   case SILC_NOTIFY_TYPE_NONE:
93     strncat(message, msg, strlen(msg));
94     break;
95   case SILC_NOTIFY_TYPE_INVITE:
96     snprintf(message, sizeof(message), msg, 
97              silc_argument_get_arg_type(args, 1, NULL),
98              silc_argument_get_arg_type(args, 2, NULL));
99     break;
100   case SILC_NOTIFY_TYPE_JOIN:
101     snprintf(message, sizeof(message), msg, 
102              silc_argument_get_arg_type(args, 2, NULL),
103              silc_argument_get_arg_type(args, 3, NULL),
104              silc_argument_get_arg_type(args, 4, NULL),
105              silc_argument_get_arg_type(args, 6, NULL));
106     break;
107   case SILC_NOTIFY_TYPE_LEAVE:
108     snprintf(message, sizeof(message), msg, 
109              silc_argument_get_arg_type(args, 1, NULL),
110              silc_argument_get_arg_type(args, 2, NULL),
111              silc_argument_get_arg_type(args, 4, NULL));
112     break;
113   case SILC_NOTIFY_TYPE_SIGNOFF:
114     snprintf(message, sizeof(message), msg, 
115              silc_argument_get_arg_type(args, 1, NULL),
116              silc_argument_get_arg_type(args, 2, NULL));
117     break;
118   case SILC_NOTIFY_TYPE_TOPIC_SET:
119     snprintf(message, sizeof(message), msg, 
120              silc_argument_get_arg_type(args, 3, NULL),
121              silc_argument_get_arg_type(args, 4, NULL),
122              silc_argument_get_arg_type(args, 2, NULL));
123     break;
124   default:
125     break;
126   }
127
128   silc_print(client, "*** %s", message);
129 }
130
131 /* Command handler. This function is called always in the command function.
132    If error occurs it will be called as well. `conn' is the associated
133    client connection. `cmd_context' is the command context that was
134    originally sent to the command. `success' is FALSE if error occured
135    during command. `command' is the command being processed. It must be
136    noted that this is not reply from server. This is merely called just
137    after application has called the command. Just to tell application
138    that the command really was processed. */
139
140 void silc_command(SilcClient client, SilcClientConnection conn, 
141                   SilcClientCommandContext cmd_context, int success,
142                   SilcCommand command)
143 {
144   SilcClientInternal app = (SilcClientInternal)client->application;
145
146   if (!success)
147     return;
148
149   switch(command)
150     {
151     case SILC_COMMAND_QUIT:
152       app->screen->bottom_line->channel = NULL;
153       silc_screen_print_bottom_line(app->screen, 0);
154       break;
155
156     case SILC_COMMAND_LEAVE:
157 #if 0
158       if (!strncmp(conn->current_channel->channel_name, name, strlen(name))) {
159         app->screen->bottom_line->channel = NULL;
160         silc_screen_print_bottom_line(app->screen, 0);
161       }
162 #endif
163       break;
164
165     }
166 }
167
168 /* Command reply handler. This function is called always in the command reply
169    function. If error occurs it will be called as well. Normal scenario
170    is that it will be called after the received command data has been parsed
171    and processed. The function is used to pass the received command data to
172    the application. 
173
174    `conn' is the associated client connection. `cmd_payload' is the command
175    payload data received from server and it can be ignored. It is provided
176    if the application would like to re-parse the received command data,
177    however, it must be noted that the data is parsed already by the library
178    thus the payload can be ignored. `success' is FALSE if error occured.
179    In this case arguments are not sent to the application. `command' is the
180    command reply being processed. The function has variable argument list
181    and each command defines the number and type of arguments it passes to the
182    application (on error they are not sent). */
183
184 void silc_command_reply(SilcClient client, SilcClientConnection conn,
185                         SilcCommandPayload cmd_payload, int success,
186                         SilcCommandStatus status, SilcCommand command, ...)
187 {
188   SilcClientInternal app = (SilcClientInternal)client->application;
189   va_list vp;
190
191   if (!success)
192     return;
193
194   va_start(vp, command);
195
196   switch(command)
197     {
198
199     case SILC_COMMAND_JOIN:
200       app->screen->bottom_line->channel = va_arg(vp, char *);
201       silc_screen_print_bottom_line(app->screen, 0);
202       break;
203
204     case SILC_COMMAND_NICK:
205       app->screen->bottom_line->nickname = va_arg(vp, char *);
206       silc_screen_print_bottom_line(app->screen, 0);
207       break;
208
209     }
210 }
211
212 /* Called to indicate that connection was either successfully established
213    or connecting failed.  This is also the first time application receives
214    the SilcClientConnection objecet which it should save somewhere. */
215
216 void silc_connect(SilcClient client, SilcClientConnection conn, int success)
217 {
218   SilcClientInternal app = (SilcClientInternal)client->application;
219
220   if (success) {
221     app->screen->bottom_line->connection = conn->remote_host;
222     silc_screen_print_bottom_line(app->screen, 0);
223     app->conn = conn;
224   }
225 }
226
227 /* Called to indicate that connection was disconnected to the server. */
228
229 void silc_disconnect(SilcClient client, SilcClientConnection conn)
230 {
231   SilcClientInternal app = (SilcClientInternal)client->application;
232
233   app->screen->bottom_line->connection = NULL;
234   silc_screen_print_bottom_line(app->screen, 0);
235 }
236
237 /* Asks passphrase from user on the input line. */
238
239 unsigned char *silc_ask_passphrase(SilcClient client, 
240                                    SilcClientConnection conn)
241 {
242   SilcClientInternal app = (SilcClientInternal)conn->client->application;
243   char pass1[256], pass2[256];
244   char *ret;
245   int try = 3;
246
247   while(try) {
248
249     /* Print prompt */
250     wattroff(app->screen->input_win, A_INVIS);
251     silc_screen_input_print_prompt(app->screen, "Passphrase: ");
252     wattron(app->screen->input_win, A_INVIS);
253     
254     /* Get string */
255     memset(pass1, 0, sizeof(pass1));
256     wgetnstr(app->screen->input_win, pass1, sizeof(pass1));
257     
258     /* Print retype prompt */
259     wattroff(app->screen->input_win, A_INVIS);
260     silc_screen_input_print_prompt(app->screen, "Retype passphrase: ");
261     wattron(app->screen->input_win, A_INVIS);
262     
263     /* Get string */
264     memset(pass2, 0, sizeof(pass2));
265     wgetnstr(app->screen->input_win, pass2, sizeof(pass2));
266
267     if (!strncmp(pass1, pass2, strlen(pass2)))
268       break;
269
270     try--;
271   }
272
273   ret = silc_calloc(strlen(pass1), sizeof(char));
274   memcpy(ret, pass1, strlen(pass1));
275
276   memset(pass1, 0, sizeof(pass1));
277   memset(pass2, 0, sizeof(pass2));
278
279   wattroff(app->screen->input_win, A_INVIS);
280   silc_screen_input_reset(app->screen);
281
282   return ret;
283 }
284
285 /* Verifies received public key. If user decides to trust the key it is
286    saved as trusted server key for later use. If user does not trust the
287    key this returns FALSE. */
288
289 int silc_verify_server_key(SilcClient client,
290                            SilcClientConnection conn, 
291                            unsigned char *pk, unsigned int pk_len,
292                            SilcSKEPKType pk_type)
293 {
294   SilcSocketConnection sock = conn->sock;
295   char filename[256];
296   char file[256];
297   char *hostname, *fingerprint;
298   struct passwd *pw;
299   struct stat st;
300
301   hostname = sock->hostname ? sock->hostname : sock->ip;
302
303   if (pk_type != SILC_SKE_PK_TYPE_SILC) {
304     silc_say(client, conn, "We don't support server %s key type", hostname);
305     return FALSE;
306   }
307
308   pw = getpwuid(getuid());
309   if (!pw)
310     return FALSE;
311
312   memset(filename, 0, sizeof(filename));
313   memset(file, 0, sizeof(file));
314   snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
315            sock->port);
316   snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s", 
317            pw->pw_dir, file);
318
319   /* Check wheter this key already exists */
320   if (stat(filename, &st) < 0) {
321
322     fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
323     silc_say(client, conn, "Received server %s public key", hostname);
324     silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
325     silc_say(client, conn, "%s", fingerprint);
326     silc_free(fingerprint);
327
328     /* Ask user to verify the key and save it */
329     if (silc_client_ask_yes_no(client, 
330        "Would you like to accept the key (y/n)? "))
331       {
332         /* Save the key for future checking */
333         silc_pkcs_save_public_key_data(filename, pk, pk_len, 
334                                        SILC_PKCS_FILE_PEM);
335         return TRUE;
336       }
337   } else {
338     /* The key already exists, verify it. */
339     SilcPublicKey public_key;
340     unsigned char *encpk;
341     unsigned int encpk_len;
342
343     /* Load the key file */
344     if (!silc_pkcs_load_public_key(filename, &public_key, 
345                                    SILC_PKCS_FILE_PEM))
346       if (!silc_pkcs_load_public_key(filename, &public_key, 
347                                      SILC_PKCS_FILE_BIN)) {
348         fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
349         silc_say(client, conn, "Received server %s public key", hostname);
350         silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
351         silc_say(client, conn, "%s", fingerprint);
352         silc_free(fingerprint);
353         silc_say(client, conn, "Could not load your local copy of the server %s key",
354                  hostname);
355         if (silc_client_ask_yes_no(client, 
356            "Would you like to accept the key anyway (y/n)? "))
357           {
358             /* Save the key for future checking */
359             unlink(filename);
360             silc_pkcs_save_public_key_data(filename, pk, pk_len,
361                                            SILC_PKCS_FILE_PEM);
362             return TRUE;
363           }
364         
365         return FALSE;
366       }
367   
368     /* Encode the key data */
369     encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
370     if (!encpk) {
371       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
372       silc_say(client, conn, "Received server %s public key", hostname);
373       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
374       silc_say(client, conn, "%s", fingerprint);
375       silc_free(fingerprint);
376       silc_say(client, conn, "Your local copy of the server %s key is malformed",
377                hostname);
378       if (silc_client_ask_yes_no(client, 
379          "Would you like to accept the key anyway (y/n)? "))
380         {
381           /* Save the key for future checking */
382           unlink(filename);
383           silc_pkcs_save_public_key_data(filename, pk, pk_len,
384                                          SILC_PKCS_FILE_PEM);
385           return TRUE;
386         }
387
388       return FALSE;
389     }
390
391     if (memcmp(encpk, pk, encpk_len)) {
392       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
393       silc_say(client, conn, "Received server %s public key", hostname);
394       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
395       silc_say(client, conn, "%s", fingerprint);
396       silc_free(fingerprint);
397       silc_say(client, conn, "Server %s key does not match with your local copy",
398                hostname);
399       silc_say(client, conn, "It is possible that the key has expired or changed");
400       silc_say(client, conn, "It is also possible that some one is performing "
401                        "man-in-the-middle attack");
402       
403       /* Ask user to verify the key and save it */
404       if (silc_client_ask_yes_no(client, 
405          "Would you like to accept the key anyway (y/n)? "))
406         {
407           /* Save the key for future checking */
408           unlink(filename);
409           silc_pkcs_save_public_key_data(filename, pk, pk_len,
410                                          SILC_PKCS_FILE_PEM);
411           return TRUE;
412         }
413
414       silc_say(client, conn, "Will not accept server %s key", hostname);
415       return FALSE;
416     }
417
418     /* Local copy matched */
419     return TRUE;
420   }
421
422   silc_say(client, conn, "Will not accept server %s key", hostname);
423   return FALSE;
424 }
425
426 /* Find authentication method and authentication data by hostname and
427    port. The hostname may be IP address as well. The found authentication
428    method and authentication data is returned to `auth_meth', `auth_data'
429    and `auth_data_len'. The function returns TRUE if authentication method
430    is found and FALSE if not. `conn' may be NULL. */
431
432 int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
433                          char *hostname, unsigned short port,
434                          SilcProtocolAuthMeth *auth_meth,
435                          unsigned char **auth_data,
436                          unsigned int *auth_data_len)
437 {
438   SilcClientInternal app = (SilcClientInternal)client->application;
439
440   if (app->config->conns) {
441     SilcClientConfigSectionConnection *conn = NULL;
442
443     /* Check if we find a match from user configured connections */
444     conn = silc_client_config_find_connection(app->config,
445                                               hostname,
446                                               port);
447     if (conn) {
448       /* Match found. Use the configured authentication method */
449       *auth_meth = conn->auth_meth;
450
451       if (conn->auth_data) {
452         *auth_data = strdup(conn->auth_data);
453         *auth_data_len = strlen(conn->auth_data);
454       }
455
456       return TRUE;
457     }
458   }
459
460   return FALSE;
461 }
462
463 /* SILC client operations */
464 SilcClientOperations ops = {
465   say:                  silc_say,
466   channel_message:      silc_channel_message,
467   private_message:      silc_private_message,
468   notify:               silc_notify,
469   command:              silc_command,
470   command_reply:        silc_command_reply,
471   connect:              silc_connect,
472   disconnect:           silc_disconnect,
473   get_auth_method:      silc_get_auth_method,
474   verify_server_key:    silc_verify_server_key,
475   ask_passphrase:       silc_ask_passphrase,
476 };