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 /* Reset the data connection alarm so it runs anew with the blocking close */
65 start_data_alarm(p_sess
);
66 vsf_sysutil_uninstall_io_handler();
67 if (p_sess
->data_use_ssl
&& p_sess
->ssl_slave_active
)
70 priv_sock_send_cmd(p_sess
->ssl_consumer_fd
, PRIV_SOCK_DO_SSL_CLOSE
);
71 result
= priv_sock_get_result(p_sess
->ssl_consumer_fd
);
72 if (result
!= PRIV_SOCK_RESULT_OK
)
77 else if (p_sess
->p_data_ssl
)
79 dispose_ret
= ssl_data_close(p_sess
);
81 /* This close() blocks because we set SO_LINGER */
82 retval
= vsf_sysutil_close_failok(p_sess
->data_fd
);
83 if (vsf_sysutil_retval_is_error(retval
))
85 /* Do it again without blocking. */
86 vsf_sysutil_deactivate_linger_failok(p_sess
->data_fd
);
87 (void) vsf_sysutil_close_failok(p_sess
->data_fd
);
89 if (tunable_data_connection_timeout
> 0)
91 vsf_sysutil_clear_alarm();
98 vsf_ftpdataio_get_pasv_fd(struct vsf_session
* p_sess
)
101 if (tunable_one_process_model
)
103 remote_fd
= vsf_one_process_get_pasv_fd(p_sess
);
107 remote_fd
= vsf_two_process_get_pasv_fd(p_sess
);
109 /* Yes, yes, hardcoded bad I know. */
112 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
,
113 "Failed to establish connection.");
116 else if (remote_fd
== -2)
118 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
, "Security: Bad IP connecting.");
121 init_data_sock_params(p_sess
, remote_fd
);
126 vsf_ftpdataio_get_port_fd(struct vsf_session
* p_sess
)
129 if (tunable_one_process_model
|| tunable_port_promiscuous
)
131 remote_fd
= vsf_one_process_get_priv_data_sock(p_sess
);
135 remote_fd
= vsf_two_process_get_priv_data_sock(p_sess
);
137 if (vsf_sysutil_retval_is_error(remote_fd
))
139 vsf_cmdio_write(p_sess
, FTP_BADSENDCONN
,
140 "Failed to establish connection.");
143 init_data_sock_params(p_sess
, remote_fd
);
148 vsf_ftpdataio_post_mark_connect(struct vsf_session
* p_sess
)
151 if (!p_sess
->data_use_ssl
)
155 if (!p_sess
->ssl_slave_active
)
157 ret
= ssl_accept(p_sess
, p_sess
->data_fd
);
162 priv_sock_send_cmd(p_sess
->ssl_consumer_fd
, PRIV_SOCK_DO_SSL_HANDSHAKE
);
163 priv_sock_send_fd(p_sess
->ssl_consumer_fd
, p_sess
->data_fd
);
164 sock_ret
= priv_sock_get_result(p_sess
->ssl_consumer_fd
);
165 if (sock_ret
== PRIV_SOCK_RESULT_OK
)
172 static struct mystr s_err_msg
;
173 str_alloc_text(&s_err_msg
, "SSL connection failed");
174 if (tunable_require_ssl_reuse
)
176 str_append_text(&s_err_msg
, "; session reuse required");
178 &s_err_msg
, ": see require_ssl_reuse option in vsftpd.conf man page");
180 vsf_cmdio_write_str(p_sess
, FTP_DATATLSBAD
, &s_err_msg
);
186 handle_sigalrm(void* p_private
)
188 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
189 if (!p_sess
->data_progress
)
191 vsf_cmdio_write_exit(p_sess
, FTP_DATA_TIMEOUT
,
192 "Data timeout. Reconnect. Sorry.");
194 p_sess
->data_progress
= 0;
195 start_data_alarm(p_sess
);
199 start_data_alarm(struct vsf_session
* p_sess
)
201 if (tunable_data_connection_timeout
> 0)
203 vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM
,
207 vsf_sysutil_set_alarm(tunable_data_connection_timeout
);
209 else if (tunable_idle_session_timeout
> 0)
211 vsf_sysutil_clear_alarm();
216 init_data_sock_params(struct vsf_session
* p_sess
, int sock_fd
)
218 if (p_sess
->data_fd
!= -1)
220 bug("data descriptor still present in init_data_sock_params");
222 p_sess
->data_fd
= sock_fd
;
223 p_sess
->data_progress
= 0;
224 vsf_sysutil_activate_keepalive(sock_fd
);
225 /* And in the vague hope it might help... */
226 vsf_sysutil_set_iptos_throughput(sock_fd
);
227 /* Set up lingering, so that we wait for all data to transfer, and report
228 * more accurate transfer rates.
230 vsf_sysutil_activate_linger(sock_fd
);
231 /* Start the timeout monitor */
232 vsf_sysutil_install_io_handler(handle_io
, p_sess
);
233 start_data_alarm(p_sess
);
237 handle_io(int retval
, int fd
, void* p_private
)
241 unsigned int bw_rate
;
245 struct vsf_session
* p_sess
= (struct vsf_session
*) p_private
;
246 if (p_sess
->data_fd
!= fd
|| vsf_sysutil_retval_is_error(retval
) ||
251 /* Note that the session hasn't stalled, i.e. don't time it out */
252 p_sess
->data_progress
= 1;
253 /* Apply bandwidth quotas via a little pause, if necessary */
254 if (p_sess
->bw_rate_max
== 0)
258 /* Calculate bandwidth rate */
259 curr_sec
= vsf_sysutil_get_time_sec();
260 curr_usec
= vsf_sysutil_get_time_usec();
261 elapsed
= (double) (curr_sec
- p_sess
->bw_send_start_sec
);
262 elapsed
+= (double) (curr_usec
- p_sess
->bw_send_start_usec
) /
264 if (elapsed
<= (double) 0)
266 elapsed
= (double) 0.01;
268 bw_rate
= (unsigned int) ((double) retval
/ elapsed
);
269 if (bw_rate
<= p_sess
->bw_rate_max
)
271 p_sess
->bw_send_start_sec
= curr_sec
;
272 p_sess
->bw_send_start_usec
= curr_usec
;
275 /* Tut! Rate exceeded, calculate a pause to bring things back into line */
276 rate_ratio
= (double) bw_rate
/ (double) p_sess
->bw_rate_max
;
277 pause_time
= (rate_ratio
- (double) 1) * elapsed
;
278 vsf_sysutil_sleep(pause_time
);
279 p_sess
->bw_send_start_sec
= vsf_sysutil_get_time_sec();
280 p_sess
->bw_send_start_usec
= vsf_sysutil_get_time_usec();
284 vsf_ftpdataio_transfer_dir(struct vsf_session
* p_sess
, int is_control
,
285 struct vsf_sysutil_dir
* p_dir
,
286 const struct mystr
* p_base_dir_str
,
287 const struct mystr
* p_option_str
,
288 const struct mystr
* p_filter_str
,
291 return transfer_dir_internal(p_sess
, is_control
, p_dir
, p_base_dir_str
,
292 p_option_str
, p_filter_str
, is_verbose
);
296 transfer_dir_internal(struct vsf_session
* p_sess
, int is_control
,
297 struct vsf_sysutil_dir
* p_dir
,
298 const struct mystr
* p_base_dir_str
,
299 const struct mystr
* p_option_str
,
300 const struct mystr
* p_filter_str
,
303 struct mystr_list dir_list
= INIT_STRLIST
;
304 struct mystr_list subdir_list
= INIT_STRLIST
;
305 struct mystr dir_prefix_str
= INIT_MYSTR
;
306 struct mystr_list
* p_subdir_list
= 0;
307 struct str_locate_result loc_result
= str_locate_char(p_option_str
, 'R');
309 enum EVSFRWTarget target
= kVSFRWData
;
312 target
= kVSFRWControl
;
314 if (loc_result
.found
&& tunable_ls_recurse_enable
)
316 p_subdir_list
= &subdir_list
;
318 vsf_ls_populate_dir_list(&dir_list
, p_subdir_list
, p_dir
, p_base_dir_str
,
319 p_option_str
, p_filter_str
, is_verbose
);
323 str_copy(&dir_prefix_str
, p_base_dir_str
);
324 str_append_text(&dir_prefix_str
, ":\r\n");
325 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
333 failed
= write_dir_list(p_sess
, &dir_list
, target
);
335 /* Recurse into the subdirectories if required... */
338 struct mystr sub_str
= INIT_MYSTR
;
339 unsigned int num_subdirs
= str_list_get_length(&subdir_list
);
340 unsigned int subdir_index
;
341 for (subdir_index
= 0; subdir_index
< num_subdirs
; subdir_index
++)
344 struct vsf_sysutil_dir
* p_subdir
;
345 const struct mystr
* p_subdir_str
=
346 str_list_get_pstr(&subdir_list
, subdir_index
);
347 if (str_equal_text(p_subdir_str
, ".") ||
348 str_equal_text(p_subdir_str
, ".."))
352 str_copy(&sub_str
, p_base_dir_str
);
353 str_append_char(&sub_str
, '/');
354 str_append_str(&sub_str
, p_subdir_str
);
355 p_subdir
= str_opendir(&sub_str
);
358 /* Unreadable, gone missing, etc. - no matter */
361 str_alloc_text(&dir_prefix_str
, "\r\n");
362 retval
= ftp_write_str(p_sess
, &dir_prefix_str
, target
);
366 vsf_sysutil_closedir(p_subdir
);
369 retval
= transfer_dir_internal(p_sess
, is_control
, p_subdir
, &sub_str
,
370 p_option_str
, p_filter_str
, is_verbose
);
371 vsf_sysutil_closedir(p_subdir
);
380 str_list_free(&dir_list
);
381 str_list_free(&subdir_list
);
382 str_free(&dir_prefix_str
);
393 /* XXX - really, this should be refactored into a "buffered writer" object */
395 write_dir_list(struct vsf_session
* p_sess
, struct mystr_list
* p_dir_list
,
396 enum EVSFRWTarget target
)
398 /* This function writes out a list of strings to the client, over the
399 * data socket. We now coalesce the strings into fewer write() syscalls,
400 * which saved 33% CPU time writing a large directory.
403 unsigned int dir_index_max
= str_list_get_length(p_dir_list
);
404 unsigned int dir_index
;
405 struct mystr buf_str
= INIT_MYSTR
;
406 str_reserve(&buf_str
, VSFTP_DIR_BUFSIZE
);
407 for (dir_index
= 0; dir_index
< dir_index_max
; dir_index
++)
409 str_append_str(&buf_str
, str_list_get_pstr(p_dir_list
, dir_index
));
410 if (dir_index
== dir_index_max
- 1 ||
411 str_getlen(&buf_str
) +
412 str_getlen(str_list_get_pstr(p_dir_list
, dir_index
+ 1)) >
415 /* Writeout needed - we're either at the end, or we filled the buffer */
416 int writeret
= ftp_write_str(p_sess
, &buf_str
, target
);
429 struct vsf_transfer_ret
430 vsf_ftpdataio_transfer_file(struct vsf_session
* p_sess
, int remote_fd
,
431 int file_fd
, int is_recv
, int is_ascii
)
435 if (is_ascii
|| p_sess
->data_use_ssl
)
437 return do_file_send_rwloop(p_sess
, file_fd
, is_ascii
);
441 filesize_t curr_offset
= vsf_sysutil_get_file_offset(file_fd
);
442 filesize_t num_send
= calc_num_send(file_fd
, curr_offset
);
443 return do_file_send_sendfile(
444 p_sess
, remote_fd
, file_fd
, curr_offset
, num_send
);
449 return do_file_recv(p_sess
, file_fd
, is_ascii
);
453 static struct vsf_transfer_ret
454 do_file_send_rwloop(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
456 static char* p_readbuf
;
457 static char* p_asciibuf
;
458 struct vsf_transfer_ret ret_struct
= { 0, 0 };
459 unsigned int chunk_size
= get_chunk_size();
460 char* p_writefrom_buf
;
464 vsf_secbuf_alloc(&p_readbuf
, VSFTP_DATA_BUFSIZE
);
470 /* NOTE!! * 2 factor because we can double the data by doing our ASCII
473 vsf_secbuf_alloc(&p_asciibuf
, VSFTP_DATA_BUFSIZE
* 2);
475 p_writefrom_buf
= p_asciibuf
;
479 p_writefrom_buf
= p_readbuf
;
483 unsigned int num_to_write
;
484 int retval
= vsf_sysutil_read(file_fd
, p_readbuf
, chunk_size
);
485 if (vsf_sysutil_retval_is_error(retval
))
487 ret_struct
.retval
= -1;
490 else if (retval
== 0)
497 struct bin_to_ascii_ret ret
=
498 vsf_ascii_bin_to_ascii(p_readbuf
,
500 (unsigned int) retval
,
502 num_to_write
= ret
.stored
;
503 prev_cr
= ret
.last_was_cr
;
507 num_to_write
= (unsigned int) retval
;
509 retval
= ftp_write_data(p_sess
, p_writefrom_buf
, num_to_write
);
510 if (!vsf_sysutil_retval_is_error(retval
))
512 ret_struct
.transferred
+= (unsigned int) retval
;
514 if (vsf_sysutil_retval_is_error(retval
) ||
515 (unsigned int) retval
!= num_to_write
)
517 ret_struct
.retval
= -2;
523 static struct vsf_transfer_ret
524 do_file_send_sendfile(struct vsf_session
* p_sess
, int net_fd
, int file_fd
,
525 filesize_t curr_file_offset
, filesize_t bytes_to_send
)
528 unsigned int chunk_size
= 0;
529 struct vsf_transfer_ret ret_struct
= { 0, 0 };
530 filesize_t init_file_offset
= curr_file_offset
;
531 filesize_t bytes_sent
;
532 if (p_sess
->bw_rate_max
)
534 chunk_size
= get_chunk_size();
536 /* Just because I can ;-) */
537 retval
= vsf_sysutil_sendfile(net_fd
, file_fd
, &curr_file_offset
,
538 bytes_to_send
, chunk_size
);
539 bytes_sent
= curr_file_offset
- init_file_offset
;
540 ret_struct
.transferred
= bytes_sent
;
541 if (vsf_sysutil_retval_is_error(retval
))
543 ret_struct
.retval
= -2;
546 else if (bytes_sent
!= bytes_to_send
)
548 ret_struct
.retval
= -2;
555 calc_num_send(int file_fd
, filesize_t init_offset
)
557 static struct vsf_sysutil_statbuf
* s_p_statbuf
;
558 filesize_t bytes_to_send
;
559 /* Work out how many bytes to send based on file size minus current offset */
560 vsf_sysutil_fstat(file_fd
, &s_p_statbuf
);
561 bytes_to_send
= vsf_sysutil_statbuf_get_size(s_p_statbuf
);
562 if (init_offset
< 0 || bytes_to_send
< 0)
564 die("calc_num_send: negative file offset or send count");
566 /* Don't underflow if some bonehead sets a REST greater than the file size */
567 if (init_offset
> bytes_to_send
)
573 bytes_to_send
-= init_offset
;
575 return bytes_to_send
;
578 static struct vsf_transfer_ret
579 do_file_recv(struct vsf_session
* p_sess
, int file_fd
, int is_ascii
)
581 static char* p_recvbuf
;
582 unsigned int num_to_write
;
583 struct vsf_transfer_ret ret_struct
= { 0, 0 };
584 unsigned int chunk_size
= get_chunk_size();
588 /* Now that we do ASCII conversion properly, the plus one is to cater for
589 * the fact we may need to stick a '\r' at the front of the buffer if the
590 * last buffer fragment eneded in a '\r' and the current buffer fragment
591 * does not start with a '\n'.
593 vsf_secbuf_alloc(&p_recvbuf
, VSFTP_DATA_BUFSIZE
+ 1);
597 const char* p_writebuf
= p_recvbuf
+ 1;
598 int retval
= ftp_read_data(p_sess
, p_recvbuf
+ 1, chunk_size
);
599 if (vsf_sysutil_retval_is_error(retval
))
601 ret_struct
.retval
= -2;
604 else if (retval
== 0 && !prev_cr
)
606 /* Transfer done, nifty */
609 num_to_write
= (unsigned int) retval
;
610 ret_struct
.transferred
+= num_to_write
;
613 /* Handle ASCII conversion if we have to. Note that using the same
614 * buffer for source and destination is safe, because the ASCII ->
615 * binary transform only ever results in a smaller file.
617 struct ascii_to_bin_ret ret
=
618 vsf_ascii_ascii_to_bin(p_recvbuf
, num_to_write
, prev_cr
);
619 num_to_write
= ret
.stored
;
620 prev_cr
= ret
.last_was_cr
;
621 p_writebuf
= ret
.p_buf
;
623 retval
= vsf_sysutil_write_loop(file_fd
, p_writebuf
, num_to_write
);
624 if (vsf_sysutil_retval_is_error(retval
) ||
625 (unsigned int) retval
!= num_to_write
)
627 ret_struct
.retval
= -1;
636 unsigned int ret
= VSFTP_DATA_BUFSIZE
;
637 if (tunable_trans_chunk_size
< VSFTP_DATA_BUFSIZE
&&
638 tunable_trans_chunk_size
> 0)
640 ret
= tunable_trans_chunk_size
;