2 * Part of Very Secure FTPd
7 * Code to handle FTP data connections. This includes both PORT (server
8 * connects) and PASV (client connects) modes of data transfer. This
9 * includes sends and receives, files and directories.
12 #include "ftpdataio.h"
25 #include "sysdeputil.h"
27 #include "oneprocess.h"
28 #include "twoprocess.h"
31 #include "readwrite.h"
34 static void init_data_sock_params(struct vsf_session
* p_sess
, int sock_fd
);
35 static filesize_t
calc_num_send(int file_fd
, filesize_t init_offset
);
36 static struct vsf_transfer_ret
do_file_send_sendfile(
37 struct vsf_session
* p_sess
, int net_fd
, int file_fd
,
38 filesize_t curr_file_offset
, filesize_t bytes_to_send
);
39 static struct vsf_transfer_ret
do_file_send_rwloop(
40 struct vsf_session
* p_sess
, int file_fd
, int is_ascii
);
41 static struct vsf_transfer_ret
do_file_recv(
42 struct vsf_session
* p_sess
, int file_fd
, int is_ascii
);
43 static void handle_sigalrm(void* p_private
);
44 static void start_data_alarm(struct vsf_session
* p_sess
);
45 static void handle_io(int retval
, int fd
, void* p_private
);
46 static int transfer_dir_internal(
47 struct vsf_session
* p_sess
, int is_control
, struct vsf_sysutil_dir
* p_dir
,
48 const struct mystr
* p_base_dir_str
, const struct mystr
* p_option_str
,
49 const struct mystr
* p_filter_str
, int is_verbose
);
50 static int write_dir_list(struct vsf_session
* p_sess
,
51 struct mystr_list
* p_dir_list
,
52 enum EVSFRWTarget target
);
53 static unsigned int get_chunk_size();
56 vsf_ftpdataio_dispose_transfer_fd(struct vsf_session
* p_sess
)
60 if (p_sess
->data_fd
== -1)
62 bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd");
64 vsf_sysutil_uninstall_io_handler();
65 if (p_sess
->data_use_ssl
&& p_sess
->ssl_slave_active
)
68 start_data_alarm(p_sess
);
69 priv_sock_send_cmd(p_sess
->ssl_consumer_fd
, PRIV_SOCK_DO_SSL_CLOSE
);
70 result
= priv_sock_get_result(p_sess
->ssl_consumer_fd
);
71 if (result
!= PRIV_SOCK_RESULT_OK
)
76 else if (p_sess
->p_data_ssl
)
78 start_data_alarm(p_sess
);
79 dispose_ret
= ssl_data_close(p_sess
);
81 if (!p_sess
->abor_received
&& !p_sess
->data_timeout
&& dispose_ret
== 1)
83 /* If we didn't get a failure, linger on the close() in order to get more
84 * accurate transfer times.
86 start_data_alarm(p_sess
);
87 vsf_sysutil_activate_linger(p_sess
->data_fd
);
89 /* This close() blocks because we set SO_LINGER */
90 retval
= vsf_sysutil_close_failok(p_sess
->data_fd
);
91 if (vsf_sysutil_retval_is_error(retval
))
93 /* Do it again without blocking. */
94 vsf_sysutil_deactivate_linger_failok(p_sess
->data_fd
);
95 (void) vsf_sysutil_close_failok(p_sess
->data_fd
);
98 if (tunable_data_connection_timeout
> 0)
100 vsf_sysutil_clear_alarm();
102 if (p_sess
->abor_received
|| p_sess
->data_timeout
)
110 vsf_ftpdataio_get_pasv_fd(struct vsf_session
* p_sess
)
113 if (tunable_one_process_model
)
115 remote_fd
= vsf_one_process_get_pasv_fd(p_sess
);
119 remote_fd
= vsf_two_process_get_pasv_fd(p_sess
);
121 /* Yes, yes, hardcoded bad I know. */
124 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
,
125 "Failed to establish connection.");
128 else if (remote_fd
== -2)
130 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
, "Security: Bad IP connecting.");
133 init_data_sock_params(p_sess
, remote_fd
);
138 vsf_ftpdataio_get_port_fd(struct vsf_session
* p_sess
)
141 if (tunable_one_process_model
|| tunable_port_promiscuous
)
143 remote_fd
= vsf_one_process_get_priv_data_sock(p_sess
);
147 remote_fd
= vsf_two_process_get_priv_data_sock(p_sess
);
149 if (vsf_sysutil_retval_is_error(remote_fd
))
151 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
,
152 "Failed to establish connection.");
155 init_data_sock_params(p_sess
, remote_fd
);
160 vsf_ftpdataio_post_mark_connect(struct vsf_session
* p_sess
)
163 if (!p_sess
->data_use_ssl
)
167 if (!p_sess
->ssl_slave_active
)
169 ret
= ssl_accept(p_sess
, p_sess
->data_fd
);
174 priv_sock_send_cmd(p_sess
->ssl_consumer_fd
, PRIV_SOCK_DO_SSL_HANDSHAKE
);
175 priv_sock_send_fd(p_sess
->ssl_consumer_fd
, p_sess
->data_fd
);
176 sock_ret
= priv_sock_get_result(p_sess
->ssl_consumer_fd
);
177 if (sock_ret
== PRIV_SOCK_RESULT_OK
)
184 if (tunable_require_ssl_reuse
)
186 vsf_cmdio_write_exit(p_sess
, FTP_DATATLSBAD
,
187 "SSL connection failed: session reuse required", 1);
189 vsf_cmdio_write(p_sess
, FTP_DATATLSBAD
, "SSL connection failed");
196 handle_sigalrm(void* p_private
)
198 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
199 if (!p_sess
->data_progress
)
201 p_sess
->data_timeout
= 1;
202 vsf_sysutil_shutdown_failok(p_sess
->data_fd
);
203 vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD
);
204 vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD
);
208 p_sess
->data_progress
= 0;
209 start_data_alarm(p_sess
);
214 start_data_alarm(struct vsf_session
* p_sess
)
216 if (tunable_data_connection_timeout
> 0)
218 vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM
,
222 vsf_sysutil_set_alarm(tunable_data_connection_timeout
);
224 else if (tunable_idle_session_timeout
> 0)
226 vsf_sysutil_clear_alarm();
231 init_data_sock_params(struct vsf_session
* p_sess
, int sock_fd
)
233 if (p_sess
->data_fd
!= -1)
235 bug("data descriptor still present in init_data_sock_params");
237 p_sess
->data_fd
= sock_fd
;
238 p_sess
->data_progress
= 0;
239 vsf_sysutil_activate_keepalive(sock_fd
);
240 /* And in the vague hope it might help... */
241 vsf_sysutil_set_iptos_throughput(sock_fd
);
242 /* Start the timeout monitor */
243 vsf_sysutil_install_io_handler(handle_io
, p_sess
);
244 start_data_alarm(p_sess
);
248 handle_io(int retval
, int fd
, void* p_private
)
252 unsigned int bw_rate
;
256 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
257 if (p_sess
->data_fd
!= fd
|| vsf_sysutil_retval_is_error(retval
) ||
262 /* Note that the session hasn't stalled, i.e. don't time it out */
263 p_sess
->data_progress
= 1;
264 /* Apply bandwidth quotas via a little pause, if necessary */
265 if (p_sess
->bw_rate_max
== 0)
269 /* Calculate bandwidth rate */
270 curr_sec
= vsf_sysutil_get_time_sec();
271 curr_usec
= vsf_sysutil_get_time_usec();
272 elapsed
= (double) (curr_sec
- p_sess
->bw_send_start_sec
);
273 elapsed
+= (double) (curr_usec
- p_sess
->bw_send_start_usec
) /
275 if (elapsed
<= (double) 0)
277 elapsed
= (double) 0.01;
279 bw_rate
= (unsigned int) ((double) retval
/ elapsed
);
280 if (bw_rate
<= p_sess
->bw_rate_max
)
282 p_sess
->bw_send_start_sec
= curr_sec
;
283 p_sess
->bw_send_start_usec
= curr_usec
;
286 /* Tut! Rate exceeded, calculate a pause to bring things back into line */
287 rate_ratio
= (double) bw_rate
/ (double) p_sess
->bw_rate_max
;
288 pause_time
= (rate_ratio
- (double) 1) * elapsed
;
289 vsf_sysutil_sleep(pause_time
);
290 p_sess
->bw_send_start_sec
= vsf_sysutil_get_time_sec();
291 p_sess
->bw_send_start_usec
= vsf_sysutil_get_time_usec();
295 vsf_ftpdataio_transfer_dir(struct vsf_session
* p_sess
, int is_control
,
296 struct vsf_sysutil_dir
* p_dir
,
297 const struct mystr
* p_base_dir_str
,
298 const struct mystr
* p_option_str
,
299 const struct mystr
* p_filter_str
,
302 return transfer_dir_internal(p_sess
, is_control
, p_dir
, p_base_dir_str
,
303 p_option_str
, p_filter_str
, is_verbose
);
307 transfer_dir_internal(struct vsf_session
* p_sess
, int is_control
,
308 struct vsf_sysutil_dir
* p_dir
,
309 const struct mystr
* p_base_dir_str
,
310 const struct mystr
* p_option_str
,
311 const struct mystr
* p_filter_str
,
314 struct mystr_list dir_list
= INIT_STRLIST
;
315 struct mystr_list subdir_list
= INIT_STRLIST
;
316 struct mystr dir_prefix_str
= INIT_MYSTR
;
317 struct mystr_list
* p_subdir_list
= 0;
318 struct str_locate_result loc_result
= str_locate_char(p_option_str
, 'R');
320 enum EVSFRWTarget target
= kVSFRWData
;
323 target
= kVSFRWControl
;
325 if (loc_result
.found
&& tunable_ls_recurse_enable
)
327 p_subdir_list
= &subdir_list
;
329 vsf_ls_populate_dir_list(&dir_list
, p_subdir_list
, p_dir
, p_base_dir_str
,
330 p_option_str
, p_filter_str
, is_verbose
);
334 str_copy(&dir_prefix_str
, p_base_dir_str
);
335 str_append_text(&dir_prefix_str
, ":\r\n");
336 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
344 failed
= write_dir_list(p_sess
, &dir_list
, target
);
346 /* Recurse into the subdirectories if required... */
349 struct mystr sub_str
= INIT_MYSTR
;
350 unsigned int num_subdirs
= str_list_get_length(&subdir_list
);
351 unsigned int subdir_index
;
352 for (subdir_index
= 0; subdir_index
< num_subdirs
; subdir_index
++)
355 struct vsf_sysutil_dir
* p_subdir
;
356 const struct mystr
* p_subdir_str
=
357 str_list_get_pstr(&subdir_list
, subdir_index
);
358 if (str_equal_text(p_subdir_str
, ".") ||
359 str_equal_text(p_subdir_str
, ".."))
363 str_copy(&sub_str
, p_base_dir_str
);
364 str_append_char(&sub_str
, '/');
365 str_append_str(&sub_str
, p_subdir_str
);
366 p_subdir
= str_opendir(&sub_str
);
369 /* Unreadable, gone missing, etc. - no matter */
372 str_alloc_text(&dir_prefix_str
, "\r\n");
373 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
377 vsf_sysutil_closedir(p_subdir
);
380 retval
= transfer_dir_internal(p_sess
, is_control
, p_subdir
, &sub_str
,
381 p_option_str
, p_filter_str
, is_verbose
);
382 vsf_sysutil_closedir(p_subdir
);
391 str_list_free(&dir_list
);
392 str_list_free(&subdir_list
);
393 str_free(&dir_prefix_str
);
404 /* XXX - really, this should be refactored into a "buffered writer" object */
406 write_dir_list(struct vsf_session
* p_sess
, struct mystr_list
* p_dir_list
,
407 enum EVSFRWTarget target
)
409 /* This function writes out a list of strings to the client, over the
410 * data socket. We now coalesce the strings into fewer write() syscalls,
411 * which saved 33% CPU time writing a large directory.
414 unsigned int dir_index_max
= str_list_get_length(p_dir_list
);
415 unsigned int dir_index
;
416 struct mystr buf_str
= INIT_MYSTR
;
417 str_reserve(&buf_str
, VSFTP_DIR_BUFSIZE
);
418 for (dir_index
= 0; dir_index
< dir_index_max
; dir_index
++)
420 str_append_str(&buf_str
, str_list_get_pstr(p_dir_list
, dir_index
));
421 if (dir_index
== dir_index_max
- 1 ||
422 str_getlen(&buf_str
) +
423 str_getlen(str_list_get_pstr(p_dir_list
, dir_index
+ 1)) >
426 /* Writeout needed - we're either at the end, or we filled the buffer */
427 int writeret
= ftp_write_str(p_sess
, &buf_str
, target
);
440 struct vsf_transfer_ret
441 vsf_ftpdataio_transfer_file(struct vsf_session
* p_sess
, int remote_fd
,
442 int file_fd
, int is_recv
, int is_ascii
)
446 if (is_ascii
|| p_sess
->data_use_ssl
)
448 return do_file_send_rwloop(p_sess
, file_fd
, is_ascii
);
452 filesize_t curr_offset
= vsf_sysutil_get_file_offset(file_fd
);
453 filesize_t num_send
= calc_num_send(file_fd
, curr_offset
);
454 return do_file_send_sendfile(
455 p_sess
, remote_fd
, file_fd
, curr_offset
, num_send
);
460 return do_file_recv(p_sess
, file_fd
, is_ascii
);
464 static struct vsf_transfer_ret
465 do_file_send_rwloop(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
467 static char* p_readbuf
;
468 static char* p_asciibuf
;
469 struct vsf_transfer_ret ret_struct
= { 0, 0 };
470 unsigned int chunk_size
= get_chunk_size();
471 char* p_writefrom_buf
;
475 vsf_secbuf_alloc(&p_readbuf
, VSFTP_DATA_BUFSIZE
);
481 /* NOTE!! * 2 factor because we can double the data by doing our ASCII
484 vsf_secbuf_alloc(&p_asciibuf
, VSFTP_DATA_BUFSIZE
* 2);
486 p_writefrom_buf
= p_asciibuf
;
490 p_writefrom_buf
= p_readbuf
;
494 unsigned int num_to_write
;
495 int retval
= vsf_sysutil_read(file_fd
, p_readbuf
, chunk_size
);
496 if (vsf_sysutil_retval_is_error(retval
))
498 ret_struct
.retval
= -1;
501 else if (retval
== 0)
508 struct bin_to_ascii_ret ret
=
509 vsf_ascii_bin_to_ascii(p_readbuf
,
511 (unsigned int) retval
,
513 num_to_write
= ret
.stored
;
514 prev_cr
= ret
.last_was_cr
;
518 num_to_write
= (unsigned int) retval
;
520 retval
= ftp_write_data(p_sess
, p_writefrom_buf
, num_to_write
);
521 if (!vsf_sysutil_retval_is_error(retval
))
523 ret_struct
.transferred
+= (unsigned int) retval
;
525 if (vsf_sysutil_retval_is_error(retval
) ||
526 (unsigned int) retval
!= num_to_write
)
528 ret_struct
.retval
= -2;
534 static struct vsf_transfer_ret
535 do_file_send_sendfile(struct vsf_session
* p_sess
, int net_fd
, int file_fd
,
536 filesize_t curr_file_offset
, filesize_t bytes_to_send
)
539 unsigned int chunk_size
= 0;
540 struct vsf_transfer_ret ret_struct
= { 0, 0 };
541 filesize_t init_file_offset
= curr_file_offset
;
542 filesize_t bytes_sent
;
543 if (p_sess
->bw_rate_max
)
545 chunk_size
= get_chunk_size();
547 /* Just because I can ;-) */
548 retval
= vsf_sysutil_sendfile(net_fd
, file_fd
, &curr_file_offset
,
549 bytes_to_send
, chunk_size
);
550 bytes_sent
= curr_file_offset
- init_file_offset
;
551 ret_struct
.transferred
= bytes_sent
;
552 if (vsf_sysutil_retval_is_error(retval
))
554 ret_struct
.retval
= -2;
557 else if (bytes_sent
!= bytes_to_send
)
559 ret_struct
.retval
= -2;
566 calc_num_send(int file_fd
, filesize_t init_offset
)
568 static struct vsf_sysutil_statbuf
* s_p_statbuf
;
569 filesize_t bytes_to_send
;
570 /* Work out how many bytes to send based on file size minus current offset */
571 vsf_sysutil_fstat(file_fd
, &s_p_statbuf
);
572 bytes_to_send
= vsf_sysutil_statbuf_get_size(s_p_statbuf
);
573 if (init_offset
< 0 || bytes_to_send
< 0)
575 die("calc_num_send: negative file offset or send count");
577 /* Don't underflow if some bonehead sets a REST greater than the file size */
578 if (init_offset
> bytes_to_send
)
584 bytes_to_send
-= init_offset
;
586 return bytes_to_send
;
589 static struct vsf_transfer_ret
590 do_file_recv(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
592 static char* p_recvbuf
;
593 unsigned int num_to_write
;
594 struct vsf_transfer_ret ret_struct
= { 0, 0 };
595 unsigned int chunk_size
= get_chunk_size();
599 /* Now that we do ASCII conversion properly, the plus one is to cater for
600 * the fact we may need to stick a '\r' at the front of the buffer if the
601 * last buffer fragment eneded in a '\r' and the current buffer fragment
602 * does not start with a '\n'.
604 vsf_secbuf_alloc(&p_recvbuf
, VSFTP_DATA_BUFSIZE
+ 1);
608 const char* p_writebuf
= p_recvbuf
+ 1;
609 int retval
= ftp_read_data(p_sess
, p_recvbuf
+ 1, chunk_size
);
610 if (vsf_sysutil_retval_is_error(retval
))
612 ret_struct
.retval
= -2;
615 else if (retval
== 0 && !prev_cr
)
617 /* Transfer done, nifty */
620 num_to_write
= (unsigned int) retval
;
621 ret_struct
.transferred
+= num_to_write
;
624 /* Handle ASCII conversion if we have to. Note that using the same
625 * buffer for source and destination is safe, because the ASCII ->
626 * binary transform only ever results in a smaller file.
628 struct ascii_to_bin_ret ret
=
629 vsf_ascii_ascii_to_bin(p_recvbuf
, num_to_write
, prev_cr
);
630 num_to_write
= ret
.stored
;
631 prev_cr
= ret
.last_was_cr
;
632 p_writebuf
= ret
.p_buf
;
634 retval
= vsf_sysutil_write_loop(file_fd
, p_writebuf
, num_to_write
);
635 if (vsf_sysutil_retval_is_error(retval
) ||
636 (unsigned int) retval
!= num_to_write
)
638 ret_struct
.retval
= -1;
647 unsigned int ret
= VSFTP_DATA_BUFSIZE
;
648 if (tunable_trans_chunk_size
< VSFTP_DATA_BUFSIZE
&&
649 tunable_trans_chunk_size
> 0)
651 ret
= tunable_trans_chunk_size
;