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