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