Sun Mar 11 15:22:42 CET 2007 Jochen Eisinger <coffee@silcnet.org>
[silc.git] / lib / silcclient / client_ftp.c
1 /*
2
3   client_ftp.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2001 - 2007 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; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silc.h"
22 #include "silcclient.h"
23 #include "client_internal.h"
24
25 /************************** Types and definitions ***************************/
26
27 /* File transmission session */
28 struct SilcClientFtpSessionStruct {
29   SilcClient client;                  /* Client */
30   SilcClientConnection server_conn;   /* Connection to server */
31   SilcClientConnection conn;          /* Connection to remote host */
32   SilcClientEntry client_entry;       /* The client entry */
33   SilcClientListener listener;        /* Listener */
34   SilcAsyncOperation op;              /* Operation for connecting */
35   SilcClientConnectionParams params;  /* Connection params */
36   SilcPublicKey public_key;           /* Public key used in key exchange */
37   SilcPrivateKey private_key;         /* Private key used in key exchange */
38   SilcUInt32 session_id;              /* File transfer ID */
39
40   SilcClientFileMonitor monitor;      /* File transfer monitor callback */
41   void *monitor_context;
42   SilcClientFileAskName ask_name;     /* File name asking callback */
43   void *ask_name_context;
44   char *filepath;                     /* The remote filename */
45   char *path;                         /* User given path to save the file  */
46
47   SilcStream stream;                  /* Wrapped SilcPacketStream */
48   SilcSFTP sftp;                      /* SFTP server/client */
49   SilcSFTPFilesystem fs;              /* SFTP memory file system */
50   SilcSFTPHandle dir_handle;          /* SFTP session directory handle */
51   SilcSFTPHandle read_handle;         /* SFTP session file handles */
52
53   char *hostname;                     /* Remote host */
54   SilcUInt16 port;                    /* Remote port */
55   SilcUInt64 filesize;                /* File size */
56   SilcUInt64 read_offset;             /* Current read offset */
57   int fd;                             /* File descriptor */
58   unsigned int initiator : 1;         /* File sender sets this to TRUE */
59   unsigned int closed    : 1;         /* silc_client_file_close called */
60 };
61
62 /************************* SFTP Server Callbacks ****************************/
63
64 /* SFTP monitor callback for SFTP server. This reports the application
65    how the transmission is going along. This function is for the client
66    who made the file available for download. */
67
68 static void silc_client_ftp_monitor(SilcSFTP sftp,
69                                     SilcSFTPMonitors type,
70                                     const SilcSFTPMonitorData data,
71                                     void *context)
72 {
73   SilcClientFtpSession session = (SilcClientFtpSession)context;
74
75   if (type == SILC_SFTP_MONITOR_READ) {
76     /* Call the monitor for application */
77     if (session->monitor)
78       (*session->monitor)(session->client, session->conn,
79                           SILC_CLIENT_FILE_MONITOR_SEND,
80                           SILC_CLIENT_FILE_OK,
81                           data->offset, session->filesize,
82                           session->client_entry, session->session_id,
83                           session->filepath, session->monitor_context);
84   }
85 }
86
87 /************************* SFTP Client Callbacks ****************************/
88
89 /* Returns the read data. This is the downloader's function (client side)
90    to receive the read data and read more until EOF is received from
91    the other side. This will also monitor the transmission and notify
92    the application. */
93
94 static void silc_client_ftp_data(SilcSFTP sftp,
95                                  SilcSFTPStatus status,
96                                  const unsigned char *data,
97                                  SilcUInt32 data_len,
98                                  void *context)
99 {
100   SilcClientFtpSession session = (SilcClientFtpSession)context;
101
102   SILC_LOG_DEBUG(("Start"));
103
104   if (status == SILC_SFTP_STATUS_EOF) {
105     /* EOF received */
106
107     /* Close the handle */
108     silc_sftp_close(sftp, session->read_handle, NULL, NULL);
109     session->read_handle = NULL;
110
111     /* Close the read file descriptor */
112     silc_file_close(session->fd);
113     return;
114   }
115
116   if (status != SILC_SFTP_STATUS_OK) {
117     /* Call monitor callback */
118     if (session->monitor)
119       (*session->monitor)(session->client, session->conn,
120                           SILC_CLIENT_FILE_MONITOR_ERROR,
121                           (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
122                            SILC_CLIENT_FILE_NO_SUCH_FILE :
123                            status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
124                            SILC_CLIENT_FILE_PERMISSION_DENIED :
125                            SILC_CLIENT_FILE_ERROR), 0, 0,
126                           session->client_entry, session->session_id,
127                           session->filepath, session->monitor_context);
128
129     /* Close the handle */
130     silc_sftp_close(sftp, session->read_handle, NULL, NULL);
131     session->read_handle = NULL;
132
133     /* Close the read file descriptor */
134     silc_file_close(session->fd);
135     return;
136   }
137
138   /* Read more, until EOF is received */
139   session->read_offset += data_len;
140   silc_sftp_read(sftp, session->read_handle, session->read_offset,
141                  SILC_PACKET_MAX_LEN - 1024,
142                  silc_client_ftp_data, session);
143
144   /* Write the read data to the real file */
145   silc_file_write(session->fd, data, data_len);
146
147   /* Call monitor callback */
148   if (session->monitor)
149     (*session->monitor)(session->client, session->conn,
150                         SILC_CLIENT_FILE_MONITOR_RECEIVE,
151                         SILC_CLIENT_FILE_OK,
152                         session->read_offset, session->filesize,
153                         session->client_entry, session->session_id,
154                         session->filepath, session->monitor_context);
155 }
156
157 /* Returns handle for the opened file. This is the downloader's function.
158    This will begin reading the data from the file. */
159
160 static void silc_client_ftp_open_handle(SilcSFTP sftp,
161                                         SilcSFTPStatus status,
162                                         SilcSFTPHandle handle,
163                                         void *context)
164 {
165   SilcClientFtpSession session = (SilcClientFtpSession)context;
166   char path[512];
167
168   SILC_LOG_DEBUG(("Start"));
169
170   if (status != SILC_SFTP_STATUS_OK) {
171     /* Call monitor callback */
172     if (session->monitor)
173       (*session->monitor)(session->client, session->conn,
174                           SILC_CLIENT_FILE_MONITOR_ERROR,
175                           (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
176                            SILC_CLIENT_FILE_NO_SUCH_FILE :
177                            status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
178                            SILC_CLIENT_FILE_PERMISSION_DENIED :
179                            SILC_CLIENT_FILE_ERROR), 0, 0,
180                           session->client_entry, session->session_id,
181                           session->filepath, session->monitor_context);
182     return;
183   }
184
185   /* Open the actual local file */
186   memset(path, 0, sizeof(path));
187   silc_snprintf(path, sizeof(path) - 1, "%s%s", session->path ?
188                 session->path : "", session->filepath);
189   session->fd = silc_file_open(path, O_RDWR | O_CREAT | O_EXCL);
190   if (session->fd < 0) {
191     /* Call monitor callback */
192     session->client->internal->ops->say(session->client, session->conn,
193                                         SILC_CLIENT_MESSAGE_ERROR,
194                                         "File `%s' open failed: %s",
195                                         session->filepath,
196                                         strerror(errno));
197
198     if (session->monitor)
199       (*session->monitor)(session->client, session->conn,
200                           SILC_CLIENT_FILE_MONITOR_ERROR,
201                           SILC_CLIENT_FILE_PERMISSION_DENIED, 0, 0,
202                           session->client_entry, session->session_id,
203                           session->filepath, session->monitor_context);
204     return;
205   }
206
207   session->read_handle = handle;
208
209   /* Now, start reading the file */
210   silc_sftp_read(sftp, session->read_handle, session->read_offset,
211                  SILC_PACKET_MAX_LEN - 1024,
212                  silc_client_ftp_data, session);
213
214   /* Call monitor callback */
215   if (session->monitor)
216     (*session->monitor)(session->client, session->conn,
217                         SILC_CLIENT_FILE_MONITOR_RECEIVE,
218                         SILC_CLIENT_FILE_OK,
219                         session->read_offset, session->filesize,
220                         session->client_entry, session->session_id,
221                         session->filepath, session->monitor_context);
222 }
223
224 /* Ask filename completion callback.  Delivers the filepath selected by
225    user. */
226
227 static void silc_client_ftp_ask_name(const char *filepath,
228                                      void *context)
229 {
230   SilcClientFtpSession session = (SilcClientFtpSession)context;
231   SilcSFTPAttributesStruct attr;
232   char *remote_file = NULL;
233
234   SILC_LOG_DEBUG(("Start"));
235
236   if (filepath) {
237     remote_file = session->filepath;
238     session->filepath = NULL;
239     silc_free(session->path);
240     session->path = NULL;
241     session->filepath = strdup(filepath);
242   } else {
243     remote_file = strdup(session->filepath);
244   }
245
246   /* Now open the file */
247   memset(&attr, 0, sizeof(attr));
248   silc_sftp_open(session->sftp, remote_file, SILC_SFTP_FXF_READ, &attr,
249                  silc_client_ftp_open_handle, session);
250
251   /* Close the directory handle */
252   silc_sftp_close(session->sftp, session->dir_handle, NULL, NULL);
253   session->dir_handle = NULL;
254
255   silc_free(remote_file);
256 }
257
258 /* Returns the file name available for download. This is the downloader's
259    function. */
260
261 static void silc_client_ftp_readdir_name(SilcSFTP sftp,
262                                          SilcSFTPStatus status,
263                                          const SilcSFTPName name,
264                                          void *context)
265 {
266   SilcClientFtpSession session = (SilcClientFtpSession)context;
267
268   SILC_LOG_DEBUG(("Start"));
269
270   if (status != SILC_SFTP_STATUS_OK) {
271     /* Call monitor callback */
272     if (session->monitor)
273       (*session->monitor)(session->client, session->conn,
274                           SILC_CLIENT_FILE_MONITOR_ERROR,
275                           (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
276                            SILC_CLIENT_FILE_NO_SUCH_FILE :
277                            status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
278                            SILC_CLIENT_FILE_PERMISSION_DENIED :
279                            SILC_CLIENT_FILE_ERROR), 0, 0,
280                           session->client_entry, session->session_id,
281                           session->filepath, session->monitor_context);
282     return;
283   }
284
285   /* Save the important attributes like filename and file size */
286   session->filepath = strdup(name->filename[0]);
287   session->filesize = name->attrs[0]->size;
288
289   /* If the path was not provided, ask from application where to save the
290      downloaded file. */
291   if (!session->path && session->ask_name) {
292     session->ask_name(session->client, session->conn, session->session_id,
293                       name->filename[0], silc_client_ftp_ask_name, session,
294                       session->ask_name_context);
295     return;
296   }
297
298   /* Start downloading immediately to current directory. */
299   silc_client_ftp_ask_name(NULL, session);
300 }
301
302 /* Returns the file handle after giving opendir command. This is the
303    downloader's function. */
304
305 static void silc_client_ftp_opendir_handle(SilcSFTP sftp,
306                                            SilcSFTPStatus status,
307                                            SilcSFTPHandle handle,
308                                            void *context)
309 {
310   SilcClientFtpSession session = (SilcClientFtpSession)context;
311
312   SILC_LOG_DEBUG(("Start"));
313
314   if (status != SILC_SFTP_STATUS_OK) {
315     /* Call monitor callback */
316     if (session->monitor)
317       (*session->monitor)(session->client, session->conn,
318                           SILC_CLIENT_FILE_MONITOR_ERROR,
319                           (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
320                            SILC_CLIENT_FILE_NO_SUCH_FILE :
321                            status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
322                            SILC_CLIENT_FILE_PERMISSION_DENIED :
323                            SILC_CLIENT_FILE_ERROR), 0, 0,
324                           session->client_entry, session->session_id,
325                           session->filepath, session->monitor_context);
326     return;
327   }
328
329   /* Now, read the directory */
330   silc_sftp_readdir(sftp, handle, silc_client_ftp_readdir_name, session);
331   session->dir_handle = handle;
332 }
333
334 /* SFTP version callback for SFTP client. This is the downloader's function
335    after initializing the SFTP connection to the remote client. This will
336    find out the filename available for download. */
337
338 static void silc_client_ftp_version(SilcSFTP sftp,
339                                     SilcSFTPStatus status,
340                                     SilcSFTPVersion version,
341                                     void *context)
342 {
343   SilcClientFtpSession session = (SilcClientFtpSession)context;
344
345   SILC_LOG_DEBUG(("Start"));
346
347   if (status != SILC_SFTP_STATUS_OK) {
348     /* Call monitor callback */
349     if (session->monitor)
350       (*session->monitor)(session->client, session->conn,
351                           SILC_CLIENT_FILE_MONITOR_ERROR,
352                           (status == SILC_SFTP_STATUS_NO_SUCH_FILE ?
353                            SILC_CLIENT_FILE_NO_SUCH_FILE :
354                            status == SILC_SFTP_STATUS_PERMISSION_DENIED ?
355                            SILC_CLIENT_FILE_PERMISSION_DENIED :
356                            SILC_CLIENT_FILE_ERROR), 0, 0,
357                           session->client_entry, session->session_id,
358                           session->filepath, session->monitor_context);
359     return;
360   }
361
362   /* The SFTP session is open, now retrieve the info about available file. */
363   silc_sftp_opendir(sftp, "", silc_client_ftp_opendir_handle, session);
364 }
365
366 /* SFTP stream error callback */
367
368 static void silc_client_ftp_error(SilcSFTP sftp, SilcSFTPStatus status,
369                                   void *context)
370 {
371
372 }
373
374 /************************ Static utility functions **************************/
375
376 /* Free session resources.   Connection must be closed before getting
377    here. */
378
379 static void silc_client_ftp_session_free(SilcClientFtpSession session)
380 {
381   SILC_LOG_DEBUG(("Free session %d", session->session_id));
382
383   silc_dlist_del(session->client->internal->ftp_sessions, session);
384
385   /* Abort connecting  */
386   if (session->op)
387     silc_async_abort(session->op, NULL, NULL);
388
389   /* Destroy SFTP */
390   if (session->sftp) {
391     if (session->initiator)
392       silc_sftp_server_shutdown(session->sftp);
393     else
394       silc_sftp_client_shutdown(session->sftp);
395   }
396   if (session->fs)
397     silc_sftp_fs_memory_free(session->fs);
398
399   /* Destroy listener */
400   if (session->listener)
401     silc_client_listener_free(session->listener);
402
403   /* Destroy wrapped stream */
404   if (session->stream)
405     silc_stream_destroy(session->stream);
406
407   silc_client_unref_client(session->client, session->server_conn,
408                            session->client_entry);
409   silc_free(session->hostname);
410   silc_free(session->filepath);
411   silc_free(session->path);
412   silc_free(session);
413 }
414
415 /* File transfer session timeout */
416
417 SILC_TASK_CALLBACK(silc_client_ftp_timeout)
418 {
419   SilcClientFtpSession session = context;
420
421   SILC_LOG_DEBUG(("Timeout"));
422
423   /* Close connection (destroyes the session context later).  If it is
424      already closed, destroy the session now. */
425   if (session->conn) {
426     silc_client_close_connection(session->client, session->conn);
427     session->conn = NULL;
428   } else {
429     /* Call monitor callback */
430     if (session->monitor)
431       (*session->monitor)(session->client, session->conn,
432                           SILC_CLIENT_FILE_MONITOR_ERROR,
433                           SILC_CLIENT_FILE_TIMEOUT, 0, 0,
434                           session->client_entry, session->session_id,
435                           session->filepath, session->monitor_context);
436
437     silc_client_ftp_session_free(context);
438   }
439 }
440
441 /* File transfer session closing task callback */
442
443 SILC_TASK_CALLBACK(silc_client_file_close_final)
444 {
445   SilcClientFtpSession session = context;
446
447   /* Close connection (destroyes the session context later).  If it is
448      already closed, destroy the session now. */
449   if (session->conn) {
450     silc_client_close_connection(session->client, session->conn);
451     session->conn = NULL;
452   } else {
453     silc_client_ftp_session_free(context);
454   }
455 }
456
457 /* Client resolving callback.  Continues with the FTP packet processing */
458
459 static void silc_client_ftp_client_resolved(SilcClient client,
460                                             SilcClientConnection conn,
461                                             SilcStatus status,
462                                             SilcDList clients,
463                                             void *context)
464 {
465   SilcFSMThread thread = context;
466   SilcPacket packet = silc_fsm_get_state_context(thread);
467
468   /* If no client found, ignore the packet, a silent error */
469   if (!clients) {
470     silc_packet_free(packet);
471     silc_fsm_finish(thread);
472     return;
473   }
474
475   /* Continue processing the packet */
476   SILC_FSM_CALL_CONTINUE(context);
477 }
478
479 /* FTP packet payload encoder/decoder.  This is called for every FTP packet.
480    We add/remove FTP payload in this function, because SFTP library does not
481    add/remove it. */
482
483 static SilcBool
484 silc_client_ftp_coder(SilcStream stream, SilcStreamStatus status,
485                       SilcBuffer buffer, void *context)
486 {
487   /* Pull FTP type in the payload, revealing SFTP payload */
488   if (status == SILC_STREAM_CAN_READ) {
489     if (silc_buffer_len(buffer) >= 1)
490       silc_buffer_pull(buffer, 1);
491     return TRUE;
492   }
493
494   /* Add FTP type before SFTP data */
495   if (status == SILC_STREAM_CAN_WRITE) {
496     if (silc_buffer_format(buffer,
497                            SILC_STR_UI_CHAR(1),
498                            SILC_STR_END) < 0)
499       return FALSE;
500     return TRUE;
501   }
502
503   return FALSE;
504 }
505
506 /* FTP Connection callback.  The SFTP session is started here. */
507
508 static void
509 silc_client_ftp_connect_completion(SilcClient client,
510                                    SilcClientConnection conn,
511                                    SilcClientConnectionStatus status,
512                                    SilcStatus error,
513                                    const char *message,
514                                    void *context)
515 {
516   SilcClientFtpSession session = context;
517
518   session->conn = conn;
519   session->op = NULL;
520
521   silc_schedule_task_del_by_context(client->schedule, session);
522
523   switch (status) {
524   case SILC_CLIENT_CONN_SUCCESS:
525     SILC_LOG_DEBUG(("Connected, conn %p", conn));
526
527     /* Wrap the connection packet stream */
528     session->stream = silc_packet_stream_wrap(conn->stream, SILC_PACKET_FTP,
529                                               0, FALSE,
530                                               silc_client_ftp_coder, session);
531     if (!session->stream) {
532       /* Call monitor callback */
533       if (session->monitor)
534         (*session->monitor)(session->client, session->conn,
535                             SILC_CLIENT_FILE_MONITOR_ERROR,
536                             SILC_CLIENT_FILE_ERROR, 0, 0,
537                             session->client_entry, session->session_id,
538                             session->filepath, session->monitor_context);
539       silc_client_close_connection(client, conn);
540       session->conn = NULL;
541       return;
542     }
543
544     if (!session->initiator) {
545       /* If we are the SFTP client then start the SFTP session and retrieve
546          the info about the file available for download. */
547       session->sftp = silc_sftp_client_start(session->stream,
548                                              conn->internal->schedule,
549                                              silc_client_ftp_version,
550                                              silc_client_ftp_error, session);
551     } else {
552       /* Start SFTP server */
553       session->sftp = silc_sftp_server_start(session->stream,
554                                              conn->internal->schedule,
555                                              silc_client_ftp_error, session,
556                                              session->fs);
557
558       /* Monitor transmission */
559       silc_sftp_server_set_monitor(session->sftp, SILC_SFTP_MONITOR_READ,
560                                    silc_client_ftp_monitor, session);
561     }
562
563     break;
564
565   case SILC_CLIENT_CONN_DISCONNECTED:
566     SILC_LOG_DEBUG(("Disconnected %p", conn));
567
568     /* Call monitor callback */
569     if (session->monitor)
570       (*session->monitor)(session->client, session->conn,
571                           SILC_CLIENT_FILE_MONITOR_DISCONNECT,
572                           SILC_CLIENT_FILE_ERROR, 0, 0,
573                           session->client_entry, session->session_id,
574                           session->filepath, session->monitor_context);
575
576     /* Connection already closed */
577     session->conn = NULL;
578
579     /* If closed by user, destroy the session now */
580     if (session->closed)
581       silc_client_ftp_session_free(session);
582     break;
583
584   case SILC_CLIENT_CONN_ERROR_TIMEOUT:
585     SILC_LOG_DEBUG(("Connecting timeout"));
586
587     /* Call monitor callback */
588     if (session->monitor)
589       (*session->monitor)(session->client, session->conn,
590                           SILC_CLIENT_FILE_MONITOR_ERROR,
591                           SILC_CLIENT_FILE_TIMEOUT, 0, 0,
592                           session->client_entry, session->session_id,
593                           session->filepath, session->monitor_context);
594
595     /* Connection already closed */
596     session->conn = NULL;
597     break;
598
599   default:
600     SILC_LOG_DEBUG(("Connecting error %d", status));
601
602     /* Call monitor callback */
603     if (session->monitor)
604       (*session->monitor)(session->client, session->conn,
605                           SILC_CLIENT_FILE_MONITOR_ERROR,
606                           status != SILC_CLIENT_CONN_ERROR ?
607                           SILC_CLIENT_FILE_KEY_AGREEMENT_FAILED :
608                           SILC_CLIENT_FILE_CONNECT_FAILED, 0, 0,
609                           session->client_entry, session->session_id,
610                           session->filepath, session->monitor_context);
611
612     /* Connection already closed */
613     session->conn = NULL;
614     break;
615   }
616 }
617
618 /*************************** File Transfer API ******************************/
619
620 /* Free all file transfer sessions. */
621
622 void silc_client_ftp_free_sessions(SilcClient client)
623 {
624   SilcClientFtpSession session;
625
626   if (!client->internal->ftp_sessions)
627     return;
628
629   silc_dlist_start(client->internal->ftp_sessions);
630   while ((session = silc_dlist_get(client->internal->ftp_sessions)))
631     silc_client_ftp_session_free(session);
632   silc_dlist_del(client->internal->ftp_sessions, session);
633 }
634
635 /* Free file transfer session by client entry. */
636
637 void silc_client_ftp_session_free_client(SilcClient client,
638                                          SilcClientEntry client_entry)
639 {
640   SilcClientFtpSession session;
641
642   if (!client->internal->ftp_sessions)
643     return;
644
645   /* Get the session */
646   silc_dlist_start(client->internal->ftp_sessions);
647   while ((session = silc_dlist_get(client->internal->ftp_sessions)))
648     if (session->client_entry == client_entry)
649       silc_client_ftp_session_free(session);
650 }
651
652 /* Sends a file indicated by the `filepath' to the remote client
653    indicated by the `client_entry'.  This will negotiate a secret key
654    with the remote client before actually starting the transmission of
655    the file.  The `monitor' callback will be called to monitor the
656    transmission of the file. */
657
658 SilcClientFileError
659 silc_client_file_send(SilcClient client,
660                       SilcClientConnection conn,
661                       SilcClientEntry client_entry,
662                       SilcClientConnectionParams *params,
663                       SilcPublicKey public_key,
664                       SilcPrivateKey private_key,
665                       SilcClientFileMonitor monitor,
666                       void *monitor_context,
667                       const char *filepath,
668                       SilcUInt32 *session_id)
669 {
670   SilcClientFtpSession session;
671   SilcBuffer keyagr;
672   char *filename, *path;
673   int fd;
674
675   SILC_LOG_DEBUG(("File send request (file: %s)", filepath));
676
677   if (!client || !client_entry || !filepath || !params ||
678       !public_key || !private_key)
679     return SILC_CLIENT_FILE_ERROR;
680
681   /* Check for existing session for `filepath'. */
682   silc_dlist_start(client->internal->ftp_sessions);
683   while ((session = silc_dlist_get(client->internal->ftp_sessions))) {
684     if (session->filepath && !strcmp(session->filepath, filepath) &&
685         session->client_entry == client_entry)
686       return SILC_CLIENT_FILE_ALREADY_STARTED;
687   }
688
689   /* See whether the file exists and can be opened */
690   fd = silc_file_open(filepath, O_RDONLY);
691   if (fd < 0)
692     return SILC_CLIENT_FILE_NO_SUCH_FILE;
693   silc_file_close(fd);
694
695   /* Add new session */
696   session = silc_calloc(1, sizeof(*session));
697   if (!session)
698     return SILC_CLIENT_FILE_ERROR;
699   session->session_id = ++client->internal->next_session_id;
700   session->client = client;
701   session->server_conn = conn;
702   session->initiator = TRUE;
703   session->client_entry = silc_client_ref_client(client, conn, client_entry);
704   session->monitor = monitor;
705   session->monitor_context = monitor_context;
706   session->filepath = strdup(filepath);
707   session->params = *params;
708   session->public_key = public_key;
709   session->private_key = private_key;
710
711   if (silc_asprintf(&path, "file://%s", filepath) < 0) {
712     silc_free(session);
713     return SILC_CLIENT_FILE_NO_MEMORY;
714   }
715
716   /* Allocate memory filesystem and put the file to it */
717   if (strrchr(path, '/'))
718     filename = strrchr(path, '/') + 1;
719   else
720     filename = (char *)path;
721   session->fs = silc_sftp_fs_memory_alloc(SILC_SFTP_FS_PERM_READ |
722                                           SILC_SFTP_FS_PERM_EXEC);
723   silc_sftp_fs_memory_add_file(session->fs, NULL, SILC_SFTP_FS_PERM_READ,
724                                filename, path);
725
726   session->filesize = silc_file_size(filepath);
727
728   /* If local IP is provided, create listener for incoming key exchange */
729   if (params->local_ip || params->bind_ip) {
730     session->listener =
731       silc_client_listener_add(client,
732                                conn->internal->schedule,
733                                params, public_key, private_key,
734                                silc_client_ftp_connect_completion,
735                                session);
736     if (!session->listener) {
737       client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
738                                  "Cannot create listener for file transfer: "
739                                  "%s", strerror(errno));
740       silc_free(session);
741       return SILC_CLIENT_FILE_NO_MEMORY;
742     }
743
744     session->hostname = (params->bind_ip ? strdup(params->bind_ip) :
745                          strdup(params->local_ip));
746     session->port = silc_client_listener_get_local_port(session->listener);
747   }
748
749   SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
750
751   /* Send the key agreement inside FTP packet */
752   keyagr = silc_key_agreement_payload_encode(session->hostname, 0,
753                                              session->port);
754   if (!keyagr) {
755     if (session->listener)
756       silc_client_listener_free(session->listener);
757     silc_free(session);
758     return SILC_CLIENT_FILE_NO_MEMORY;
759   }
760   silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL,
761                           SILC_ID_CLIENT, &client_entry->id, NULL, NULL,
762                           SILC_STR_UI_CHAR(1),
763                           SILC_STR_DATA(silc_buffer_data(keyagr),
764                                         silc_buffer_len(keyagr)),
765                           SILC_STR_END);
766
767   silc_buffer_free(keyagr);
768   silc_free(path);
769
770   silc_dlist_add(client->internal->ftp_sessions, session);
771   if (session_id)
772     *session_id = session->session_id;
773
774   /* Add session request timeout */
775   if (params && params->timeout_secs)
776     silc_schedule_task_add_timeout(client->schedule,
777                                    silc_client_ftp_timeout, session,
778                                    params->timeout_secs, 0);
779
780   return SILC_CLIENT_FILE_OK;
781 }
782
783 /* Receives a file from a client indicated by the `client_entry'.  The
784    `session_id' indicates the file transmission session and it has been
785    received in the `ftp' client operation function.  This will actually
786    perform the key agreement protocol with the remote client before
787    actually starting the file transmission.  The `monitor' callback
788    will be called to monitor the transmission. */
789
790 SilcClientFileError
791 silc_client_file_receive(SilcClient client,
792                          SilcClientConnection conn,
793                          SilcClientConnectionParams *params,
794                          SilcPublicKey public_key,
795                          SilcPrivateKey private_key,
796                          SilcClientFileMonitor monitor,
797                          void *monitor_context,
798                          const char *path,
799                          SilcUInt32 session_id,
800                          SilcClientFileAskName ask_name,
801                          void *ask_name_context)
802 {
803   SilcClientFtpSession session;
804   SilcBuffer keyagr;
805
806   if (!client || !conn)
807     return SILC_CLIENT_FILE_ERROR;
808
809   SILC_LOG_DEBUG(("Start, Session ID: %d", session_id));
810
811   /* Get the session */
812   silc_dlist_start(client->internal->ftp_sessions);
813   while ((session = silc_dlist_get(client->internal->ftp_sessions))
814          != SILC_LIST_END) {
815     if (session->session_id == session_id) {
816       break;
817     }
818   }
819
820   if (session == SILC_LIST_END) {
821     SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
822     return SILC_CLIENT_FILE_UNKNOWN_SESSION;
823   }
824
825   /* See if we have this session running already */
826   if (session->sftp || session->listener) {
827     SILC_LOG_DEBUG(("Session already started"));
828     return SILC_CLIENT_FILE_ALREADY_STARTED;
829   }
830
831   session->monitor = monitor;
832   session->monitor_context = monitor_context;
833   session->ask_name = ask_name;
834   session->ask_name_context = ask_name_context;
835   session->path = path ? strdup(path) : NULL;
836
837   /* If the hostname and port already exists then the remote client did
838      provide the connection point to us and we won't create listener, but
839      create the connection ourselves. */
840   if (session->hostname && session->port) {
841     SILC_LOG_DEBUG(("Connecting to remote client"));
842     /* Connect to the remote client.  Performs key exchange automatically. */
843     session->op =
844       silc_client_connect_to_client(client, params, public_key, private_key,
845                                     session->hostname, session->port,
846                                     silc_client_ftp_connect_completion,
847                                     session);
848     if (!session->op) {
849       silc_free(session);
850       return SILC_CLIENT_FILE_ERROR;
851     }
852   } else {
853     /* Add the listener for the key agreement */
854     SILC_LOG_DEBUG(("Creating listener for file transfer"));
855     if (!params || (!params->local_ip && !params->bind_ip)) {
856       silc_free(session);
857       return SILC_CLIENT_FILE_ERROR;
858     }
859     session->listener =
860       silc_client_listener_add(client, conn->internal->schedule, params,
861                                public_key, private_key,
862                                silc_client_ftp_connect_completion,
863                                session);
864     if (!session->listener) {
865       client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_ERROR,
866                                  "Cannot create listener for file transfer: "
867                                  "%s", strerror(errno));
868       silc_free(session);
869       return SILC_CLIENT_FILE_NO_MEMORY;
870     }
871     session->hostname = (params->bind_ip ? strdup(params->bind_ip) :
872                          strdup(params->local_ip));
873     session->port = silc_client_listener_get_local_port(session->listener);
874
875     /* Send the key agreement inside FTP packet */
876     SILC_LOG_DEBUG(("Sending key agreement for file transfer"));
877     keyagr = silc_key_agreement_payload_encode(session->hostname, 0,
878                                                session->port);
879     if (!keyagr) {
880       silc_client_listener_free(session->listener);
881       silc_free(session);
882       return SILC_CLIENT_FILE_NO_MEMORY;
883     }
884     silc_packet_send_va_ext(conn->stream, SILC_PACKET_FTP, 0, 0, NULL,
885                             SILC_ID_CLIENT, &session->client_entry->id,
886                             NULL, NULL,
887                             SILC_STR_UI_CHAR(1),
888                             SILC_STR_DATA(silc_buffer_data(keyagr),
889                                           silc_buffer_len(keyagr)),
890                             SILC_STR_END);
891     silc_buffer_free(keyagr);
892
893     /* Add session request timeout */
894     if (params && params->timeout_secs)
895       silc_schedule_task_add_timeout(client->schedule,
896                                      silc_client_ftp_timeout, session,
897                                      params->timeout_secs, 0);
898   }
899
900   return SILC_CLIENT_FILE_OK;
901 }
902
903 /* Closes file transmission session indicated by the `session_id'.
904    If file transmission is being conducted it will be aborted
905    automatically. This function is also used to close the session
906    after successful file transmission. This function can be used
907    also to reject incoming file transmission request. */
908
909 SilcClientFileError silc_client_file_close(SilcClient client,
910                                            SilcClientConnection conn,
911                                            SilcUInt32 session_id)
912 {
913   SilcClientFtpSession session;
914
915   if (!client || !conn)
916     return SILC_CLIENT_FILE_ERROR;
917
918   SILC_LOG_DEBUG(("Closing file transer session %d", session_id));
919
920   /* Get the session */
921   silc_dlist_start(client->internal->ftp_sessions);
922   while ((session = silc_dlist_get(client->internal->ftp_sessions))
923          != SILC_LIST_END) {
924     if (session->session_id == session_id)
925       break;
926   }
927
928   if (session == SILC_LIST_END) {
929     SILC_LOG_DEBUG(("Unknown session ID: %d\n", session_id));
930     return SILC_CLIENT_FILE_UNKNOWN_SESSION;
931   }
932
933   if (session->monitor) {
934     (*session->monitor)(session->client, session->conn,
935                         SILC_CLIENT_FILE_MONITOR_CLOSED,
936                         SILC_CLIENT_FILE_OK, 0, 0,
937                         session->client_entry, session->session_id,
938                         session->filepath, session->monitor_context);
939
940     /* No more callbacks to application */
941     session->monitor = NULL;
942   }
943
944   silc_schedule_task_del_by_context(client->schedule, session);
945
946   session->closed = TRUE;
947
948   /* Destroy via timeout */
949   silc_schedule_task_add_timeout(conn->internal->schedule,
950                                  silc_client_file_close_final, session,
951                                  0, 1);
952
953   return SILC_CLIENT_FILE_OK;
954 }
955
956 /************************** FTP Request Processing **************************/
957
958 /* Received file transfer packet.  Only file transfer requests get here.
959    The actual file transfer is handled by the SFTP library when we give it
960    the packet stream wrapped into SilcStream context. */
961
962 SILC_FSM_STATE(silc_client_ftp)
963 {
964   SilcClientConnection conn = fsm_context;
965   SilcClient client = conn->client;
966   SilcPacket packet = state_context;
967   SilcClientFtpSession session;
968   SilcClientID remote_id;
969   SilcClientEntry remote_client;
970   SilcKeyAgreementPayload payload = NULL;
971   char *hostname;
972   SilcUInt16 port;
973
974   SILC_LOG_DEBUG(("Process file transfer packet"));
975
976   if (silc_buffer_len(&packet->buffer) < 1)
977     goto out;
978
979   /* We support file transfer type number 1 (== SFTP) */
980   if (packet->buffer.data[0] != 0x01) {
981     SILC_LOG_DEBUG(("Unsupported file transfer type %d",
982                     packet->buffer.data[0]));
983     goto out;
984   }
985
986   if (!silc_id_str2id(packet->src_id, packet->src_id_len,
987                       SILC_ID_CLIENT, &remote_id, sizeof(remote_id))) {
988     SILC_LOG_DEBUG(("Invalid client ID"));
989     goto out;
990   }
991
992   /* Check whether we know this client already */
993   remote_client = silc_client_get_client_by_id(client, conn, &remote_id);
994   if (!remote_client || !remote_client->internal.valid) {
995     /** Resolve client info */
996     silc_client_unref_client(client, conn, remote_client);
997     SILC_FSM_CALL(silc_client_get_client_by_id_resolve(
998                                          client, conn, &remote_id, NULL,
999                                          silc_client_ftp_client_resolved,
1000                                          fsm));
1001     /* NOT REACHED */
1002   }
1003
1004   /* Get session */
1005   silc_dlist_start(client->internal->ftp_sessions);
1006   while ((session = silc_dlist_get(client->internal->ftp_sessions))) {
1007     if (session->client_entry == remote_client &&
1008         (!session->initiator || !session->listener))
1009       break;
1010   }
1011
1012   /* Parse the key agreement payload */
1013   payload =
1014     silc_key_agreement_payload_parse(silc_buffer_data(&packet->buffer) + 1,
1015                                      silc_buffer_len(&packet->buffer) - 1);
1016   if (!payload) {
1017     SILC_LOG_DEBUG(("Invalid key agreement payload"));
1018     goto out;
1019   }
1020
1021   hostname = silc_key_agreement_get_hostname(payload);
1022   port = silc_key_agreement_get_port(payload);
1023   if (!hostname || !port) {
1024     hostname = NULL;
1025     port = 0;
1026   }
1027
1028   /* If session doesn't exist, we create new one.  If session exists, but
1029      we are responder it means that the remote sent another request and user
1030      hasn't even accepted the first one yet.  We assume this session is new
1031      session as well. */
1032   if (!session || !hostname || !session->initiator) {
1033     /* New file transfer session */
1034     SILC_LOG_DEBUG(("New file transfer session %d",
1035                     client->internal->next_session_id + 1));
1036
1037     session = silc_calloc(1, sizeof(*session));
1038     if (!session)
1039       goto out;
1040     session->session_id = ++client->internal->next_session_id;
1041     session->client = client;
1042     session->server_conn = conn;
1043     session->client_entry = silc_client_ref_client(client, conn,
1044                                                    remote_client);
1045     if (hostname && port) {
1046       session->hostname = strdup(hostname);
1047       session->port = port;
1048     }
1049     silc_dlist_add(client->internal->ftp_sessions, session);
1050
1051     /* Notify application of incoming FTP request */
1052     client->internal->ops->ftp(client, conn, remote_client,
1053                                session->session_id, hostname, port);
1054     goto out;
1055   }
1056
1057   /* Session exists, continue with key agreement protocol. */
1058   SILC_LOG_DEBUG(("Session %d exists, connecting to remote client",
1059                   session->session_id));
1060
1061   session->hostname = strdup(hostname);
1062   session->port = port;
1063
1064   /* Connect to the remote client.  Performs key exchange automatically. */
1065   session->op =
1066     silc_client_connect_to_client(client, &session->params,
1067                                   session->public_key, session->private_key,
1068                                   session->hostname, session->port,
1069                                   silc_client_ftp_connect_completion,
1070                                   session);
1071   if (!session->op) {
1072     /* Call monitor callback */
1073     if (session->monitor)
1074       (*session->monitor)(session->client, session->conn,
1075                           SILC_CLIENT_FILE_MONITOR_ERROR,
1076                           SILC_CLIENT_FILE_ERROR, 0, 0,
1077                           session->client_entry, session->session_id,
1078                           session->filepath, session->monitor_context);
1079   }
1080
1081  out:
1082   if (payload)
1083     silc_key_agreement_payload_free(payload);
1084   silc_packet_free(packet);
1085   return SILC_FSM_FINISH;
1086 }