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   case SILC_NOTIFY_TYPE_CHANNEL_CHANGE:
223     break;
224
225   default:
226     break;
227   }
228
229   silc_print(client, "*** %s", message);
230 }
231
232 /* Command handler. This function is called always in the command function.
233    If error occurs it will be called as well. `conn' is the associated
234    client connection. `cmd_context' is the command context that was
235    originally sent to the command. `success' is FALSE if error occured
236    during command. `command' is the command being processed. It must be
237    noted that this is not reply from server. This is merely called just
238    after application has called the command. Just to tell application
239    that the command really was processed. */
240
241 void silc_command(SilcClient client, SilcClientConnection conn, 
242                   SilcClientCommandContext cmd_context, int success,
243                   SilcCommand command)
244 {
245   SilcClientInternal app = (SilcClientInternal)client->application;
246
247   if (!success)
248     return;
249
250   switch(command)
251     {
252     case SILC_COMMAND_QUIT:
253       app->screen->bottom_line->channel = NULL;
254       silc_screen_print_bottom_line(app->screen, 0);
255       break;
256
257     case SILC_COMMAND_LEAVE:
258 #if 0
259       if (!strncmp(conn->current_channel->channel_name, name, strlen(name))) {
260         app->screen->bottom_line->channel = NULL;
261         silc_screen_print_bottom_line(app->screen, 0);
262       }
263 #endif
264       break;
265
266     }
267 }
268
269 /* Command reply handler. This function is called always in the command reply
270    function. If error occurs it will be called as well. Normal scenario
271    is that it will be called after the received command data has been parsed
272    and processed. The function is used to pass the received command data to
273    the application. 
274
275    `conn' is the associated client connection. `cmd_payload' is the command
276    payload data received from server and it can be ignored. It is provided
277    if the application would like to re-parse the received command data,
278    however, it must be noted that the data is parsed already by the library
279    thus the payload can be ignored. `success' is FALSE if error occured.
280    In this case arguments are not sent to the application. `command' is the
281    command reply being processed. The function has variable argument list
282    and each command defines the number and type of arguments it passes to the
283    application (on error they are not sent). */
284
285 void silc_command_reply(SilcClient client, SilcClientConnection conn,
286                         SilcCommandPayload cmd_payload, int success,
287                         SilcCommand command, SilcCommandStatus status, ...)
288 {
289   SilcClientInternal app = (SilcClientInternal)client->application;
290   SilcChannelUser chu;
291   va_list vp;
292
293   if (!success)
294     return;
295
296   va_start(vp, status);
297
298   switch(command)
299     {
300
301     case SILC_COMMAND_JOIN:
302       {
303         unsigned int mode;
304
305         app->screen->bottom_line->channel = va_arg(vp, char *);
306         (void)va_arg(vp, void *);
307         mode = va_arg(vp, unsigned int);
308         app->screen->bottom_line->channel_mode = silc_client_chmode(mode);
309         silc_screen_print_bottom_line(app->screen, 0);
310       }
311       break;
312
313     case SILC_COMMAND_NICK:
314       {
315         SilcClientEntry entry;
316
317         entry = va_arg(vp, SilcClientEntry);
318         silc_say(client, conn, "Your current nickname is %s", entry->nickname);
319         app->screen->bottom_line->nickname = entry->nickname;
320         silc_screen_print_bottom_line(app->screen, 0);
321       }
322       break;
323
324     case SILC_COMMAND_USERS:
325       silc_list_start(conn->current_channel->clients);
326       while ((chu = silc_list_get(conn->current_channel->clients)) 
327              != SILC_LIST_END) {
328         if (chu->client == conn->local_entry) {
329           if (app->screen->bottom_line->mode)
330             silc_free(app->screen->bottom_line->mode);
331           app->screen->bottom_line->mode = 
332             silc_client_chumode_char(chu->mode);
333           silc_screen_print_bottom_line(app->screen, 0);
334           break;
335         }
336       break;
337       }
338     }
339 }
340
341 /* Called to indicate that connection was either successfully established
342    or connecting failed.  This is also the first time application receives
343    the SilcClientConnection objecet which it should save somewhere. */
344
345 void silc_connect(SilcClient client, SilcClientConnection conn, int success)
346 {
347   SilcClientInternal app = (SilcClientInternal)client->application;
348
349   if (success) {
350     app->screen->bottom_line->connection = conn->remote_host;
351     silc_screen_print_bottom_line(app->screen, 0);
352     app->conn = conn;
353   }
354 }
355
356 /* Called to indicate that connection was disconnected to the server. */
357
358 void silc_disconnect(SilcClient client, SilcClientConnection conn)
359 {
360   SilcClientInternal app = (SilcClientInternal)client->application;
361
362   app->screen->bottom_line->connection = NULL;
363   silc_screen_print_bottom_line(app->screen, 0);
364   app->conn = NULL;
365 }
366
367 /* Asks passphrase from user on the input line. */
368
369 unsigned char *silc_ask_passphrase(SilcClient client, 
370                                    SilcClientConnection conn)
371 {
372   SilcClientInternal app = (SilcClientInternal)conn->client->application;
373   char pass1[256], pass2[256];
374   char *ret;
375   int try = 3;
376
377   while(try) {
378
379     /* Print prompt */
380     wattroff(app->screen->input_win, A_INVIS);
381     silc_screen_input_print_prompt(app->screen, "Passphrase: ");
382     wattron(app->screen->input_win, A_INVIS);
383     
384     /* Get string */
385     memset(pass1, 0, sizeof(pass1));
386     wgetnstr(app->screen->input_win, pass1, sizeof(pass1));
387     
388     /* Print retype prompt */
389     wattroff(app->screen->input_win, A_INVIS);
390     silc_screen_input_print_prompt(app->screen, "Retype passphrase: ");
391     wattron(app->screen->input_win, A_INVIS);
392     
393     /* Get string */
394     memset(pass2, 0, sizeof(pass2));
395     wgetnstr(app->screen->input_win, pass2, sizeof(pass2));
396
397     if (!strncmp(pass1, pass2, strlen(pass2)))
398       break;
399
400     try--;
401   }
402
403   ret = silc_calloc(strlen(pass1), sizeof(char));
404   memcpy(ret, pass1, strlen(pass1));
405
406   memset(pass1, 0, sizeof(pass1));
407   memset(pass2, 0, sizeof(pass2));
408
409   wattroff(app->screen->input_win, A_INVIS);
410   silc_screen_input_reset(app->screen);
411
412   return ret;
413 }
414
415 /* Verifies received public key. If user decides to trust the key it is
416    saved as trusted server key for later use. If user does not trust the
417    key this returns FALSE. */
418
419 int silc_verify_server_key(SilcClient client,
420                            SilcClientConnection conn, 
421                            unsigned char *pk, unsigned int pk_len,
422                            SilcSKEPKType pk_type)
423 {
424   SilcSocketConnection sock = conn->sock;
425   char filename[256];
426   char file[256];
427   char *hostname, *fingerprint;
428   struct passwd *pw;
429   struct stat st;
430
431   hostname = sock->hostname ? sock->hostname : sock->ip;
432
433   if (pk_type != SILC_SKE_PK_TYPE_SILC) {
434     silc_say(client, conn, "We don't support server %s key type", hostname);
435     return FALSE;
436   }
437
438   pw = getpwuid(getuid());
439   if (!pw)
440     return FALSE;
441
442   memset(filename, 0, sizeof(filename));
443   memset(file, 0, sizeof(file));
444   snprintf(file, sizeof(file) - 1, "serverkey_%s_%d.pub", hostname,
445            sock->port);
446   snprintf(filename, sizeof(filename) - 1, "%s/.silc/serverkeys/%s", 
447            pw->pw_dir, file);
448
449   /* Check wheter this key already exists */
450   if (stat(filename, &st) < 0) {
451
452     fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
453     silc_say(client, conn, "Received server %s public key", hostname);
454     silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
455     silc_say(client, conn, "%s", fingerprint);
456     silc_free(fingerprint);
457
458     /* Ask user to verify the key and save it */
459     if (silc_client_ask_yes_no(client, 
460        "Would you like to accept the key (y/n)? "))
461       {
462         /* Save the key for future checking */
463         silc_pkcs_save_public_key_data(filename, pk, pk_len, 
464                                        SILC_PKCS_FILE_PEM);
465         return TRUE;
466       }
467   } else {
468     /* The key already exists, verify it. */
469     SilcPublicKey public_key;
470     unsigned char *encpk;
471     unsigned int encpk_len;
472
473     /* Load the key file */
474     if (!silc_pkcs_load_public_key(filename, &public_key, 
475                                    SILC_PKCS_FILE_PEM))
476       if (!silc_pkcs_load_public_key(filename, &public_key, 
477                                      SILC_PKCS_FILE_BIN)) {
478         fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
479         silc_say(client, conn, "Received server %s public key", hostname);
480         silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
481         silc_say(client, conn, "%s", fingerprint);
482         silc_free(fingerprint);
483         silc_say(client, conn, "Could not load your local copy of the server %s key",
484                  hostname);
485         if (silc_client_ask_yes_no(client, 
486            "Would you like to accept the key anyway (y/n)? "))
487           {
488             /* Save the key for future checking */
489             unlink(filename);
490             silc_pkcs_save_public_key_data(filename, pk, pk_len,
491                                            SILC_PKCS_FILE_PEM);
492             return TRUE;
493           }
494         
495         return FALSE;
496       }
497   
498     /* Encode the key data */
499     encpk = silc_pkcs_public_key_encode(public_key, &encpk_len);
500     if (!encpk) {
501       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
502       silc_say(client, conn, "Received server %s public key", hostname);
503       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
504       silc_say(client, conn, "%s", fingerprint);
505       silc_free(fingerprint);
506       silc_say(client, conn, "Your local copy of the server %s key is malformed",
507                hostname);
508       if (silc_client_ask_yes_no(client, 
509          "Would you like to accept the key anyway (y/n)? "))
510         {
511           /* Save the key for future checking */
512           unlink(filename);
513           silc_pkcs_save_public_key_data(filename, pk, pk_len,
514                                          SILC_PKCS_FILE_PEM);
515           return TRUE;
516         }
517
518       return FALSE;
519     }
520
521     if (memcmp(encpk, pk, encpk_len)) {
522       fingerprint = silc_hash_fingerprint(NULL, pk, pk_len);
523       silc_say(client, conn, "Received server %s public key", hostname);
524       silc_say(client, conn, "Fingerprint for the server %s key is", hostname);
525       silc_say(client, conn, "%s", fingerprint);
526       silc_free(fingerprint);
527       silc_say(client, conn, "Server %s key does not match with your local copy",
528                hostname);
529       silc_say(client, conn, "It is possible that the key has expired or changed");
530       silc_say(client, conn, "It is also possible that some one is performing "
531                        "man-in-the-middle attack");
532       
533       /* Ask user to verify the key and save it */
534       if (silc_client_ask_yes_no(client, 
535          "Would you like to accept the key anyway (y/n)? "))
536         {
537           /* Save the key for future checking */
538           unlink(filename);
539           silc_pkcs_save_public_key_data(filename, pk, pk_len,
540                                          SILC_PKCS_FILE_PEM);
541           return TRUE;
542         }
543
544       silc_say(client, conn, "Will not accept server %s key", hostname);
545       return FALSE;
546     }
547
548     /* Local copy matched */
549     return TRUE;
550   }
551
552   silc_say(client, conn, "Will not accept server %s key", hostname);
553   return FALSE;
554 }
555
556 /* Find authentication method and authentication data by hostname and
557    port. The hostname may be IP address as well. The found authentication
558    method and authentication data is returned to `auth_meth', `auth_data'
559    and `auth_data_len'. The function returns TRUE if authentication method
560    is found and FALSE if not. `conn' may be NULL. */
561
562 int silc_get_auth_method(SilcClient client, SilcClientConnection conn,
563                          char *hostname, unsigned short port,
564                          SilcProtocolAuthMeth *auth_meth,
565                          unsigned char **auth_data,
566                          unsigned int *auth_data_len)
567 {
568   SilcClientInternal app = (SilcClientInternal)client->application;
569
570   if (app->config->conns) {
571     SilcClientConfigSectionConnection *conn = NULL;
572
573     /* Check if we find a match from user configured connections */
574     conn = silc_client_config_find_connection(app->config,
575                                               hostname,
576                                               port);
577     if (conn) {
578       /* Match found. Use the configured authentication method */
579       *auth_meth = conn->auth_meth;
580
581       if (conn->auth_data) {
582         *auth_data = strdup(conn->auth_data);
583         *auth_data_len = strlen(conn->auth_data);
584       }
585
586       return TRUE;
587     }
588   }
589
590   return FALSE;
591 }
592
593 /* Notifies application that failure packet was received.  This is called
594    if there is some protocol active in the client.  The `protocol' is the
595    protocol context.  The `failure' is opaque pointer to the failure
596    indication.  Note, that the `failure' is protocol dependant and application
597    must explicitly cast it to correct type.  Usually `failure' is 32 bit
598    failure type (see protocol specs for all protocol failure types). */
599
600 void silc_failure(SilcClient client, SilcClientConnection conn, 
601                   SilcProtocol protocol, void *failure)
602 {
603   SilcClientInternal app = (SilcClientInternal)client->application;
604
605 }
606
607 /* SILC client operations */
608 SilcClientOperations ops = {
609   say:                  silc_say,
610   channel_message:      silc_channel_message,
611   private_message:      silc_private_message,
612   notify:               silc_notify,
613   command:              silc_command,
614   command_reply:        silc_command_reply,
615   connect:              silc_connect,
616   disconnect:           silc_disconnect,
617   get_auth_method:      silc_get_auth_method,
618   verify_server_key:    silc_verify_server_key,
619   ask_passphrase:       silc_ask_passphrase,
620   failure:              silc_failure,
621 };