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 static struct mystr s_err_msg
;
185 str_alloc_text(&s_err_msg
, "SSL connection failed");
186 if (tunable_require_ssl_reuse
)
188 str_append_text(&s_err_msg
, "; session reuse required");
190 &s_err_msg
, ": see require_ssl_reuse option in vsftpd.conf man page");
192 vsf_cmdio_write_str(p_sess
, FTP_DATATLSBAD
, &s_err_msg
);
198 handle_sigalrm(void* p_private
)
200 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
201 if (!p_sess
->data_progress
)
203 p_sess
->data_timeout
= 1;
204 vsf_sysutil_shutdown_failok(p_sess
->data_fd
);
205 vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD
);
206 vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD
);
210 p_sess
->data_progress
= 0;
211 start_data_alarm(p_sess
);
216 start_data_alarm(struct vsf_session
* p_sess
)
218 if (tunable_data_connection_timeout
> 0)
220 vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM
,
224 vsf_sysutil_set_alarm(tunable_data_connection_timeout
);
226 else if (tunable_idle_session_timeout
> 0)
228 vsf_sysutil_clear_alarm();
233 init_data_sock_params(struct vsf_session
* p_sess
, int sock_fd
)
235 if (p_sess
->data_fd
!= -1)
237 bug("data descriptor still present in init_data_sock_params");
239 p_sess
->data_fd
= sock_fd
;
240 p_sess
->data_progress
= 0;
241 vsf_sysutil_activate_keepalive(sock_fd
);
242 /* And in the vague hope it might help... */
243 vsf_sysutil_set_iptos_throughput(sock_fd
);
244 /* Start the timeout monitor */
245 vsf_sysutil_install_io_handler(handle_io
, p_sess
);
246 start_data_alarm(p_sess
);
250 handle_io(int retval
, int fd
, void* p_private
)
254 unsigned int bw_rate
;
258 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
259 if (p_sess
->data_fd
!= fd
|| vsf_sysutil_retval_is_error(retval
) ||
264 /* Note that the session hasn't stalled, i.e. don't time it out */
265 p_sess
->data_progress
= 1;
266 /* Apply bandwidth quotas via a little pause, if necessary */
267 if (p_sess
->bw_rate_max
== 0)
271 /* Calculate bandwidth rate */
272 curr_sec
= vsf_sysutil_get_time_sec();
273 curr_usec
= vsf_sysutil_get_time_usec();
274 elapsed
= (double) (curr_sec
- p_sess
->bw_send_start_sec
);
275 elapsed
+= (double) (curr_usec
- p_sess
->bw_send_start_usec
) /
277 if (elapsed
<= (double) 0)
279 elapsed
= (double) 0.01;
281 bw_rate
= (unsigned int) ((double) retval
/ elapsed
);
282 if (bw_rate
<= p_sess
->bw_rate_max
)
284 p_sess
->bw_send_start_sec
= curr_sec
;
285 p_sess
->bw_send_start_usec
= curr_usec
;
288 /* Tut! Rate exceeded, calculate a pause to bring things back into line */
289 rate_ratio
= (double) bw_rate
/ (double) p_sess
->bw_rate_max
;
290 pause_time
= (rate_ratio
- (double) 1) * elapsed
;
291 vsf_sysutil_sleep(pause_time
);
292 p_sess
->bw_send_start_sec
= vsf_sysutil_get_time_sec();
293 p_sess
->bw_send_start_usec
= vsf_sysutil_get_time_usec();
297 vsf_ftpdataio_transfer_dir(struct vsf_session
* p_sess
, int is_control
,
298 struct vsf_sysutil_dir
* p_dir
,
299 const struct mystr
* p_base_dir_str
,
300 const struct mystr
* p_option_str
,
301 const struct mystr
* p_filter_str
,
304 return transfer_dir_internal(p_sess
, is_control
, p_dir
, p_base_dir_str
,
305 p_option_str
, p_filter_str
, is_verbose
);
309 transfer_dir_internal(struct vsf_session
* p_sess
, int is_control
,
310 struct vsf_sysutil_dir
* p_dir
,
311 const struct mystr
* p_base_dir_str
,
312 const struct mystr
* p_option_str
,
313 const struct mystr
* p_filter_str
,
316 struct mystr_list dir_list
= INIT_STRLIST
;
317 struct mystr_list subdir_list
= INIT_STRLIST
;
318 struct mystr dir_prefix_str
= INIT_MYSTR
;
319 struct mystr_list
* p_subdir_list
= 0;
320 struct str_locate_result loc_result
= str_locate_char(p_option_str
, 'R');
322 enum EVSFRWTarget target
= kVSFRWData
;
325 target
= kVSFRWControl
;
327 if (loc_result
.found
&& tunable_ls_recurse_enable
)
329 p_subdir_list
= &subdir_list
;
331 vsf_ls_populate_dir_list(&dir_list
, p_subdir_list
, p_dir
, p_base_dir_str
,
332 p_option_str
, p_filter_str
, is_verbose
);
336 str_copy(&dir_prefix_str
, p_base_dir_str
);
337 str_append_text(&dir_prefix_str
, ":\r\n");
338 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
346 failed
= write_dir_list(p_sess
, &dir_list
, target
);
348 /* Recurse into the subdirectories if required... */
351 struct mystr sub_str
= INIT_MYSTR
;
352 unsigned int num_subdirs
= str_list_get_length(&subdir_list
);
353 unsigned int subdir_index
;
354 for (subdir_index
= 0; subdir_index
< num_subdirs
; subdir_index
++)
357 struct vsf_sysutil_dir
* p_subdir
;
358 const struct mystr
* p_subdir_str
=
359 str_list_get_pstr(&subdir_list
, subdir_index
);
360 if (str_equal_text(p_subdir_str
, ".") ||
361 str_equal_text(p_subdir_str
, ".."))
365 str_copy(&sub_str
, p_base_dir_str
);
366 str_append_char(&sub_str
, '/');
367 str_append_str(&sub_str
, p_subdir_str
);
368 p_subdir
= str_opendir(&sub_str
);
371 /* Unreadable, gone missing, etc. - no matter */
374 str_alloc_text(&dir_prefix_str
, "\r\n");
375 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
379 vsf_sysutil_closedir(p_subdir
);
382 retval
= transfer_dir_internal(p_sess
, is_control
, p_subdir
, &sub_str
,
383 p_option_str
, p_filter_str
, is_verbose
);
384 vsf_sysutil_closedir(p_subdir
);
393 str_list_free(&dir_list
);
394 str_list_free(&subdir_list
);
395 str_free(&dir_prefix_str
);
406 /* XXX - really, this should be refactored into a "buffered writer" object */
408 write_dir_list(struct vsf_session
* p_sess
, struct mystr_list
* p_dir_list
,
409 enum EVSFRWTarget target
)
411 /* This function writes out a list of strings to the client, over the
412 * data socket. We now coalesce the strings into fewer write() syscalls,
413 * which saved 33% CPU time writing a large directory.
416 unsigned int dir_index_max
= str_list_get_length(p_dir_list
);
417 unsigned int dir_index
;
418 struct mystr buf_str
= INIT_MYSTR
;
419 str_reserve(&buf_str
, VSFTP_DIR_BUFSIZE
);
420 for (dir_index
= 0; dir_index
< dir_index_max
; dir_index
++)
422 str_append_str(&buf_str
, str_list_get_pstr(p_dir_list
, dir_index
));
423 if (dir_index
== dir_index_max
- 1 ||
424 str_getlen(&buf_str
) +
425 str_getlen(str_list_get_pstr(p_dir_list
, dir_index
+ 1)) >
428 /* Writeout needed - we're either at the end, or we filled the buffer */
429 int writeret
= ftp_write_str(p_sess
, &buf_str
, target
);
442 struct vsf_transfer_ret
443 vsf_ftpdataio_transfer_file(struct vsf_session
* p_sess
, int remote_fd
,
444 int file_fd
, int is_recv
, int is_ascii
)
448 if (is_ascii
|| p_sess
->data_use_ssl
)
450 return do_file_send_rwloop(p_sess
, file_fd
, is_ascii
);
454 filesize_t curr_offset
= vsf_sysutil_get_file_offset(file_fd
);
455 filesize_t num_send
= calc_num_send(file_fd
, curr_offset
);
456 return do_file_send_sendfile(
457 p_sess
, remote_fd
, file_fd
, curr_offset
, num_send
);
462 return do_file_recv(p_sess
, file_fd
, is_ascii
);
466 static struct vsf_transfer_ret
467 do_file_send_rwloop(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
469 static char* p_readbuf
;
470 static char* p_asciibuf
;
471 struct vsf_transfer_ret ret_struct
= { 0, 0 };
472 unsigned int chunk_size
= get_chunk_size();
473 char* p_writefrom_buf
;
477 vsf_secbuf_alloc(&p_readbuf
, VSFTP_DATA_BUFSIZE
);
483 /* NOTE!! * 2 factor because we can double the data by doing our ASCII
486 vsf_secbuf_alloc(&p_asciibuf
, VSFTP_DATA_BUFSIZE
* 2);
488 p_writefrom_buf
= p_asciibuf
;
492 p_writefrom_buf
= p_readbuf
;
496 unsigned int num_to_write
;
497 int retval
= vsf_sysutil_read(file_fd
, p_readbuf
, chunk_size
);
498 if (vsf_sysutil_retval_is_error(retval
))
500 ret_struct
.retval
= -1;
503 else if (retval
== 0)
510 struct bin_to_ascii_ret ret
=
511 vsf_ascii_bin_to_ascii(p_readbuf
,
513 (unsigned int) retval
,
515 num_to_write
= ret
.stored
;
516 prev_cr
= ret
.last_was_cr
;
520 num_to_write
= (unsigned int) retval
;
522 retval
= ftp_write_data(p_sess
, p_writefrom_buf
, num_to_write
);
523 if (!vsf_sysutil_retval_is_error(retval
))
525 ret_struct
.transferred
+= (unsigned int) retval
;
527 if (vsf_sysutil_retval_is_error(retval
) ||
528 (unsigned int) retval
!= num_to_write
)
530 ret_struct
.retval
= -2;
536 static struct vsf_transfer_ret
537 do_file_send_sendfile(struct vsf_session
* p_sess
, int net_fd
, int file_fd
,
538 filesize_t curr_file_offset
, filesize_t bytes_to_send
)
541 unsigned int chunk_size
= 0;
542 struct vsf_transfer_ret ret_struct
= { 0, 0 };
543 filesize_t init_file_offset
= curr_file_offset
;
544 filesize_t bytes_sent
;
545 if (p_sess
->bw_rate_max
)
547 chunk_size
= get_chunk_size();
549 /* Just because I can ;-) */
550 retval
= vsf_sysutil_sendfile(net_fd
, file_fd
, &curr_file_offset
,
551 bytes_to_send
, chunk_size
);
552 bytes_sent
= curr_file_offset
- init_file_offset
;
553 ret_struct
.transferred
= bytes_sent
;
554 if (vsf_sysutil_retval_is_error(retval
))
556 ret_struct
.retval
= -2;
559 else if (bytes_sent
!= bytes_to_send
)
561 ret_struct
.retval
= -2;
568 calc_num_send(int file_fd
, filesize_t init_offset
)
570 static struct vsf_sysutil_statbuf
* s_p_statbuf
;
571 filesize_t bytes_to_send
;
572 /* Work out how many bytes to send based on file size minus current offset */
573 vsf_sysutil_fstat(file_fd
, &s_p_statbuf
);
574 bytes_to_send
= vsf_sysutil_statbuf_get_size(s_p_statbuf
);
575 if (init_offset
< 0 || bytes_to_send
< 0)
577 die("calc_num_send: negative file offset or send count");
579 /* Don't underflow if some bonehead sets a REST greater than the file size */
580 if (init_offset
> bytes_to_send
)
586 bytes_to_send
-= init_offset
;
588 return bytes_to_send
;
591 static struct vsf_transfer_ret
592 do_file_recv(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
594 static char* p_recvbuf
;
595 unsigned int num_to_write
;
596 struct vsf_transfer_ret ret_struct
= { 0, 0 };
597 unsigned int chunk_size
= get_chunk_size();
601 /* Now that we do ASCII conversion properly, the plus one is to cater for
602 * the fact we may need to stick a '\r' at the front of the buffer if the
603 * last buffer fragment eneded in a '\r' and the current buffer fragment
604 * does not start with a '\n'.
606 vsf_secbuf_alloc(&p_recvbuf
, VSFTP_DATA_BUFSIZE
+ 1);
610 const char* p_writebuf
= p_recvbuf
+ 1;
611 int retval
= ftp_read_data(p_sess
, p_recvbuf
+ 1, chunk_size
);
612 if (vsf_sysutil_retval_is_error(retval
))
614 ret_struct
.retval
= -2;
617 else if (retval
== 0 && !prev_cr
)
619 /* Transfer done, nifty */
622 num_to_write
= (unsigned int) retval
;
623 ret_struct
.transferred
+= num_to_write
;
626 /* Handle ASCII conversion if we have to. Note that using the same
627 * buffer for source and destination is safe, because the ASCII ->
628 * binary transform only ever results in a smaller file.
630 struct ascii_to_bin_ret ret
=
631 vsf_ascii_ascii_to_bin(p_recvbuf
, num_to_write
, prev_cr
);
632 num_to_write
= ret
.stored
;
633 prev_cr
= ret
.last_was_cr
;
634 p_writebuf
= ret
.p_buf
;
636 retval
= vsf_sysutil_write_loop(file_fd
, p_writebuf
, num_to_write
);
637 if (vsf_sysutil_retval_is_error(retval
) ||
638 (unsigned int) retval
!= num_to_write
)
640 ret_struct
.retval
= -1;
649 unsigned int ret
= VSFTP_DATA_BUFSIZE
;
650 if (tunable_trans_chunk_size
< VSFTP_DATA_BUFSIZE
&&
651 tunable_trans_chunk_size
> 0)
653 ret
= tunable_trans_chunk_size
;