| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #include "ftpdataio.h" |
| #include "session.h" |
| #include "ftpcmdio.h" |
| #include "ftpcodes.h" |
| #include "utility.h" |
| #include "tunables.h" |
| #include "defs.h" |
| #include "str.h" |
| #include "strlist.h" |
| #include "sysutil.h" |
| #include "logging.h" |
| #include "secbuf.h" |
| #include "sysstr.h" |
| #include "sysdeputil.h" |
| #include "ascii.h" |
| #include "oneprocess.h" |
| #include "twoprocess.h" |
| #include "ls.h" |
| #include "ssl.h" |
| #include "readwrite.h" |
| #include "privsock.h" |
| |
| static void init_data_sock_params(struct vsf_session* p_sess, int sock_fd); |
| static filesize_t calc_num_send(int file_fd, filesize_t init_offset); |
| static struct vsf_transfer_ret do_file_send_sendfile( |
| struct vsf_session* p_sess, int net_fd, int file_fd, |
| filesize_t curr_file_offset, filesize_t bytes_to_send); |
| static struct vsf_transfer_ret do_file_send_rwloop( |
| struct vsf_session* p_sess, int file_fd, int is_ascii); |
| static struct vsf_transfer_ret do_file_recv( |
| struct vsf_session* p_sess, int file_fd, int is_ascii); |
| static void handle_sigalrm(void* p_private); |
| static void start_data_alarm(struct vsf_session* p_sess); |
| static void handle_io(int retval, int fd, void* p_private); |
| static int transfer_dir_internal( |
| struct vsf_session* p_sess, int is_control, struct vsf_sysutil_dir* p_dir, |
| const struct mystr* p_base_dir_str, const struct mystr* p_option_str, |
| const struct mystr* p_filter_str, int is_verbose); |
| static int write_dir_list(struct vsf_session* p_sess, |
| struct mystr_list* p_dir_list, |
| enum EVSFRWTarget target); |
| static unsigned int get_chunk_size(); |
| |
| int |
| vsf_ftpdataio_dispose_transfer_fd(struct vsf_session* p_sess) |
| { |
| int dispose_ret = 1; |
| int retval; |
| if (p_sess->data_fd == -1) |
| { |
| bug("no data descriptor in vsf_ftpdataio_dispose_transfer_fd"); |
| } |
| vsf_sysutil_uninstall_io_handler(); |
| if (p_sess->data_use_ssl && p_sess->ssl_slave_active) |
| { |
| char result; |
| start_data_alarm(p_sess); |
| priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_CLOSE); |
| result = priv_sock_get_result(p_sess->ssl_consumer_fd); |
| if (result != PRIV_SOCK_RESULT_OK) |
| { |
| dispose_ret = 0; |
| } |
| } |
| else if (p_sess->p_data_ssl) |
| { |
| start_data_alarm(p_sess); |
| dispose_ret = ssl_data_close(p_sess); |
| } |
| if (!p_sess->abor_received && !p_sess->data_timeout && dispose_ret == 1) |
| { |
| |
| |
| |
| start_data_alarm(p_sess); |
| vsf_sysutil_activate_linger(p_sess->data_fd); |
| } |
| |
| retval = vsf_sysutil_close_failok(p_sess->data_fd); |
| if (vsf_sysutil_retval_is_error(retval)) |
| { |
| |
| vsf_sysutil_deactivate_linger_failok(p_sess->data_fd); |
| (void) vsf_sysutil_close_failok(p_sess->data_fd); |
| } |
| p_sess->data_fd = -1; |
| if (tunable_data_connection_timeout > 0) |
| { |
| vsf_sysutil_clear_alarm(); |
| } |
| if (p_sess->abor_received || p_sess->data_timeout) |
| { |
| dispose_ret = 0; |
| } |
| return dispose_ret; |
| } |
| |
| int |
| vsf_ftpdataio_get_pasv_fd(struct vsf_session* p_sess) |
| { |
| int remote_fd; |
| if (tunable_one_process_model) |
| { |
| remote_fd = vsf_one_process_get_pasv_fd(p_sess); |
| } |
| else |
| { |
| remote_fd = vsf_two_process_get_pasv_fd(p_sess); |
| } |
| |
| if (remote_fd == -1) |
| { |
| vsf_cmdio_write(p_sess, FTP_BADSENDCONN, |
| "Failed to establish connection."); |
| return remote_fd; |
| } |
| else if (remote_fd == -2) |
| { |
| vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Security: Bad IP connecting."); |
| return -1; |
| } |
| init_data_sock_params(p_sess, remote_fd); |
| return remote_fd; |
| } |
| |
| int |
| vsf_ftpdataio_get_port_fd(struct vsf_session* p_sess) |
| { |
| int remote_fd; |
| if (tunable_one_process_model || tunable_port_promiscuous) |
| { |
| remote_fd = vsf_one_process_get_priv_data_sock(p_sess); |
| } |
| else |
| { |
| remote_fd = vsf_two_process_get_priv_data_sock(p_sess); |
| } |
| if (vsf_sysutil_retval_is_error(remote_fd)) |
| { |
| vsf_cmdio_write(p_sess, FTP_BADSENDCONN, |
| "Failed to establish connection."); |
| return -1; |
| } |
| init_data_sock_params(p_sess, remote_fd); |
| return remote_fd; |
| } |
| |
| int |
| vsf_ftpdataio_post_mark_connect(struct vsf_session* p_sess) |
| { |
| int ret = 0; |
| if (!p_sess->data_use_ssl) |
| { |
| return 1; |
| } |
| if (!p_sess->ssl_slave_active) |
| { |
| ret = ssl_accept(p_sess, p_sess->data_fd); |
| } |
| else |
| { |
| int sock_ret; |
| priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_HANDSHAKE); |
| priv_sock_send_fd(p_sess->ssl_consumer_fd, p_sess->data_fd); |
| sock_ret = priv_sock_get_result(p_sess->ssl_consumer_fd); |
| if (sock_ret == PRIV_SOCK_RESULT_OK) |
| { |
| ret = 1; |
| } |
| } |
| if (ret != 1) |
| { |
| if (tunable_require_ssl_reuse) |
| { |
| vsf_cmdio_write_exit(p_sess, FTP_DATATLSBAD, |
| "SSL connection failed: session reuse required", 1); |
| } else { |
| vsf_cmdio_write(p_sess, FTP_DATATLSBAD, "SSL connection failed"); |
| } |
| } |
| return ret; |
| } |
| |
| static void |
| handle_sigalrm(void* p_private) |
| { |
| struct vsf_session* p_sess = (struct vsf_session*) p_private; |
| if (!p_sess->data_progress) |
| { |
| p_sess->data_timeout = 1; |
| vsf_sysutil_shutdown_failok(p_sess->data_fd); |
| vsf_sysutil_shutdown_read_failok(VSFTP_COMMAND_FD); |
| vsf_sysutil_activate_noblock(VSFTP_COMMAND_FD); |
| } |
| else |
| { |
| p_sess->data_progress = 0; |
| start_data_alarm(p_sess); |
| } |
| } |
| |
| void |
| start_data_alarm(struct vsf_session* p_sess) |
| { |
| if (tunable_data_connection_timeout > 0) |
| { |
| vsf_sysutil_install_sighandler(kVSFSysUtilSigALRM, |
| handle_sigalrm, |
| p_sess, |
| 1); |
| vsf_sysutil_set_alarm(tunable_data_connection_timeout); |
| } |
| else if (tunable_idle_session_timeout > 0) |
| { |
| vsf_sysutil_clear_alarm(); |
| } |
| } |
| |
| static void |
| init_data_sock_params(struct vsf_session* p_sess, int sock_fd) |
| { |
| if (p_sess->data_fd != -1) |
| { |
| bug("data descriptor still present in init_data_sock_params"); |
| } |
| p_sess->data_fd = sock_fd; |
| p_sess->data_progress = 0; |
| vsf_sysutil_activate_keepalive(sock_fd); |
| |
| vsf_sysutil_set_iptos_throughput(sock_fd); |
| |
| vsf_sysutil_install_io_handler(handle_io, p_sess); |
| start_data_alarm(p_sess); |
| if(tunable_delete_failed_uploads) |
| { |
| vsf_sysutil_rcvtimeo(sock_fd); |
| } |
| } |
| |
| static void |
| handle_io(int retval, int fd, void* p_private) |
| { |
| long curr_sec; |
| long curr_usec; |
| unsigned long bw_rate; |
| double elapsed; |
| double pause_time; |
| double rate_ratio; |
| struct vsf_session* p_sess = (struct vsf_session*) p_private; |
| if (p_sess->data_fd != fd || vsf_sysutil_retval_is_error(retval) || |
| retval == 0) |
| { |
| return; |
| } |
| |
| p_sess->data_progress = 1; |
| |
| if (p_sess->bw_rate_max == 0) |
| { |
| return; |
| } |
| |
| curr_sec = vsf_sysutil_get_time_sec(); |
| curr_usec = vsf_sysutil_get_time_usec(); |
| elapsed = (double) (curr_sec - p_sess->bw_send_start_sec); |
| elapsed += (double) (curr_usec - p_sess->bw_send_start_usec) / |
| (double) 1000000; |
| if (elapsed <= (double) 0) |
| { |
| elapsed = (double) 0.01; |
| } |
| p_sess->bw_retval += retval; |
| bw_rate = (unsigned long) ((double) p_sess->bw_retval / elapsed); |
| if (bw_rate <= p_sess->bw_rate_max || p_sess->bw_retval < (unsigned long)(10*retval)) |
| { |
| return; |
| } |
| |
| rate_ratio = (double) bw_rate / (double) p_sess->bw_rate_max; |
| pause_time = (rate_ratio - (double) 1) * elapsed; |
| vsf_sysutil_sleep(pause_time); |
| } |
| |
| int |
| vsf_ftpdataio_transfer_dir(struct vsf_session* p_sess, int is_control, |
| struct vsf_sysutil_dir* p_dir, |
| const struct mystr* p_base_dir_str, |
| const struct mystr* p_option_str, |
| const struct mystr* p_filter_str, |
| int is_verbose) |
| { |
| return transfer_dir_internal(p_sess, is_control, p_dir, p_base_dir_str, |
| p_option_str, p_filter_str, is_verbose); |
| } |
| |
| static int |
| transfer_dir_internal(struct vsf_session* p_sess, int is_control, |
| struct vsf_sysutil_dir* p_dir, |
| const struct mystr* p_base_dir_str, |
| const struct mystr* p_option_str, |
| const struct mystr* p_filter_str, |
| int is_verbose) |
| { |
| struct mystr_list dir_list = INIT_STRLIST; |
| struct mystr_list subdir_list = INIT_STRLIST; |
| struct mystr dir_prefix_str = INIT_MYSTR; |
| struct mystr_list* p_subdir_list = 0; |
| struct str_locate_result loc_result = str_locate_char(p_option_str, 'R'); |
| int failed = 0; |
| enum EVSFRWTarget target = kVSFRWData; |
| if (is_control) |
| { |
| target = kVSFRWControl; |
| } |
| if (loc_result.found && tunable_ls_recurse_enable) |
| { |
| p_subdir_list = &subdir_list; |
| } |
| vsf_ls_populate_dir_list(&dir_list, p_subdir_list, p_dir, p_base_dir_str, |
| p_option_str, p_filter_str, is_verbose); |
| if (p_subdir_list) |
| { |
| int retval; |
| str_copy(&dir_prefix_str, p_base_dir_str); |
| str_append_text(&dir_prefix_str, ":\r\n"); |
| retval = ftp_write_str(p_sess, &dir_prefix_str, target); |
| if (retval != 0) |
| { |
| failed = 1; |
| } |
| } |
| if (!failed) |
| { |
| failed = write_dir_list(p_sess, &dir_list, target); |
| } |
| |
| if (!failed) |
| { |
| struct mystr sub_str = INIT_MYSTR; |
| unsigned int num_subdirs = str_list_get_length(&subdir_list); |
| unsigned int subdir_index; |
| for (subdir_index = 0; subdir_index < num_subdirs; subdir_index++) |
| { |
| int retval; |
| struct vsf_sysutil_dir* p_subdir; |
| const struct mystr* p_subdir_str = |
| str_list_get_pstr(&subdir_list, subdir_index); |
| if (str_equal_text(p_subdir_str, ".") || |
| str_equal_text(p_subdir_str, "..")) |
| { |
| continue; |
| } |
| str_copy(&sub_str, p_base_dir_str); |
| str_append_char(&sub_str, '/'); |
| str_append_str(&sub_str, p_subdir_str); |
| p_subdir = str_opendir(&sub_str); |
| if (p_subdir == 0) |
| { |
| |
| continue; |
| } |
| str_alloc_text(&dir_prefix_str, "\r\n"); |
| retval = ftp_write_str(p_sess, &dir_prefix_str, target); |
| if (retval != 0) |
| { |
| failed = 1; |
| vsf_sysutil_closedir(p_subdir); |
| break; |
| } |
| retval = transfer_dir_internal(p_sess, is_control, p_subdir, &sub_str, |
| p_option_str, p_filter_str, is_verbose); |
| vsf_sysutil_closedir(p_subdir); |
| if (retval != 0) |
| { |
| failed = 1; |
| break; |
| } |
| } |
| str_free(&sub_str); |
| } |
| str_list_free(&dir_list); |
| str_list_free(&subdir_list); |
| str_free(&dir_prefix_str); |
| if (!failed) |
| { |
| return 0; |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| |
| |
| static int |
| write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list, |
| enum EVSFRWTarget target) |
| { |
| |
| |
| |
| |
| int retval = 0; |
| unsigned int dir_index_max = str_list_get_length(p_dir_list); |
| unsigned int dir_index; |
| struct mystr buf_str = INIT_MYSTR; |
| str_reserve(&buf_str, VSFTP_DIR_BUFSIZE); |
| for (dir_index = 0; dir_index < dir_index_max; dir_index++) |
| { |
| str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index)); |
| if (dir_index == dir_index_max - 1 || |
| str_getlen(&buf_str) + |
| str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) > |
| VSFTP_DIR_BUFSIZE) |
| { |
| |
| int writeret = ftp_write_str(p_sess, &buf_str, target); |
| if (writeret != 0) |
| { |
| retval = 1; |
| break; |
| } |
| str_empty(&buf_str); |
| } |
| } |
| str_free(&buf_str); |
| return retval; |
| } |
| |
| struct vsf_transfer_ret |
| vsf_ftpdataio_transfer_file(struct vsf_session* p_sess, int remote_fd, |
| int file_fd, int is_recv, int is_ascii) |
| { |
| p_sess->bw_send_start_sec = vsf_sysutil_get_time_sec(); |
| p_sess->bw_send_start_usec = vsf_sysutil_get_time_usec(); |
| p_sess->bw_retval = 0; |
| if (!is_recv) |
| { |
| if (is_ascii || p_sess->data_use_ssl) |
| { |
| return do_file_send_rwloop(p_sess, file_fd, is_ascii); |
| } |
| else |
| { |
| filesize_t curr_offset = vsf_sysutil_get_file_offset(file_fd); |
| filesize_t num_send = calc_num_send(file_fd, curr_offset); |
| return do_file_send_sendfile( |
| p_sess, remote_fd, file_fd, curr_offset, num_send); |
| } |
| } |
| else |
| { |
| return do_file_recv(p_sess, file_fd, is_ascii); |
| } |
| } |
| |
| static struct vsf_transfer_ret |
| do_file_send_rwloop(struct vsf_session* p_sess, int file_fd, int is_ascii) |
| { |
| static char* p_readbuf; |
| static char* p_asciibuf; |
| struct vsf_transfer_ret ret_struct = { 0, 0 }; |
| unsigned int chunk_size = get_chunk_size(); |
| char* p_writefrom_buf; |
| int prev_cr = 0; |
| if (p_readbuf == 0) |
| { |
| vsf_secbuf_alloc(&p_readbuf, VSFTP_DATA_BUFSIZE); |
| } |
| if (is_ascii) |
| { |
| if (p_asciibuf == 0) |
| { |
| |
| |
| |
| vsf_secbuf_alloc(&p_asciibuf, VSFTP_DATA_BUFSIZE * 2); |
| } |
| p_writefrom_buf = p_asciibuf; |
| } |
| else |
| { |
| p_writefrom_buf = p_readbuf; |
| } |
| while (1) |
| { |
| unsigned int num_to_write; |
| int retval = vsf_sysutil_read(file_fd, p_readbuf, chunk_size); |
| if (vsf_sysutil_retval_is_error(retval)) |
| { |
| ret_struct.retval = -1; |
| return ret_struct; |
| } |
| else if (retval == 0) |
| { |
| |
| return ret_struct; |
| } |
| if (is_ascii) |
| { |
| struct bin_to_ascii_ret ret = |
| vsf_ascii_bin_to_ascii(p_readbuf, |
| p_asciibuf, |
| (unsigned int) retval, |
| prev_cr); |
| num_to_write = ret.stored; |
| prev_cr = ret.last_was_cr; |
| } |
| else |
| { |
| num_to_write = (unsigned int) retval; |
| } |
| retval = ftp_write_data(p_sess, p_writefrom_buf, num_to_write); |
| if (!vsf_sysutil_retval_is_error(retval)) |
| { |
| ret_struct.transferred += (unsigned int) retval; |
| } |
| if (vsf_sysutil_retval_is_error(retval) || |
| (unsigned int) retval != num_to_write) |
| { |
| ret_struct.retval = -2; |
| return ret_struct; |
| } |
| } |
| } |
| |
| static struct vsf_transfer_ret |
| do_file_send_sendfile(struct vsf_session* p_sess, int net_fd, int file_fd, |
| filesize_t curr_file_offset, filesize_t bytes_to_send) |
| { |
| int retval; |
| unsigned int chunk_size = 0; |
| struct vsf_transfer_ret ret_struct = { 0, 0 }; |
| filesize_t init_file_offset = curr_file_offset; |
| filesize_t bytes_sent; |
| if (p_sess->bw_rate_max) |
| { |
| chunk_size = get_chunk_size(); |
| } |
| |
| retval = vsf_sysutil_sendfile(net_fd, file_fd, &curr_file_offset, |
| bytes_to_send, chunk_size); |
| bytes_sent = curr_file_offset - init_file_offset; |
| ret_struct.transferred = bytes_sent; |
| if (vsf_sysutil_retval_is_error(retval)) |
| { |
| ret_struct.retval = -2; |
| return ret_struct; |
| } |
| else if (bytes_sent != bytes_to_send) |
| { |
| ret_struct.retval = -2; |
| return ret_struct; |
| } |
| return ret_struct; |
| } |
| |
| static filesize_t |
| calc_num_send(int file_fd, filesize_t init_offset) |
| { |
| static struct vsf_sysutil_statbuf* s_p_statbuf; |
| filesize_t bytes_to_send; |
| |
| vsf_sysutil_fstat(file_fd, &s_p_statbuf); |
| bytes_to_send = vsf_sysutil_statbuf_get_size(s_p_statbuf); |
| if (init_offset < 0 || bytes_to_send < 0) |
| { |
| die("calc_num_send: negative file offset or send count"); |
| } |
| |
| if (init_offset > bytes_to_send) |
| { |
| bytes_to_send = 0; |
| } |
| else |
| { |
| bytes_to_send -= init_offset; |
| } |
| return bytes_to_send; |
| } |
| |
| static struct vsf_transfer_ret |
| do_file_recv(struct vsf_session* p_sess, int file_fd, int is_ascii) |
| { |
| static char* p_recvbuf; |
| unsigned int num_to_write; |
| struct vsf_transfer_ret ret_struct = { 0, 0 }; |
| unsigned int chunk_size = get_chunk_size(); |
| int prev_cr = 0; |
| if (p_recvbuf == 0) |
| { |
| |
| |
| |
| |
| |
| vsf_secbuf_alloc(&p_recvbuf, VSFTP_DATA_BUFSIZE + 1); |
| } |
| while (1) |
| { |
| const char* p_writebuf = p_recvbuf + 1; |
| int retval = ftp_read_data(p_sess, p_recvbuf + 1, chunk_size); |
| if (vsf_sysutil_retval_is_error(retval)) |
| { |
| ret_struct.retval = -2; |
| return ret_struct; |
| } |
| else if (retval == 0 && !prev_cr) |
| { |
| |
| if (tunable_delete_failed_uploads && |
| !is_ascii && p_sess->upload_size > 0 && |
| p_sess->upload_size != ret_struct.transferred) |
| ret_struct.retval = -2; |
| return ret_struct; |
| } |
| num_to_write = (unsigned int) retval; |
| ret_struct.transferred += num_to_write; |
| if (is_ascii) |
| { |
| |
| |
| |
| |
| struct ascii_to_bin_ret ret = |
| vsf_ascii_ascii_to_bin(p_recvbuf, num_to_write, prev_cr); |
| num_to_write = ret.stored; |
| prev_cr = ret.last_was_cr; |
| p_writebuf = ret.p_buf; |
| } |
| retval = vsf_sysutil_write_loop(file_fd, p_writebuf, num_to_write); |
| if (vsf_sysutil_retval_is_error(retval) || |
| (unsigned int) retval != num_to_write) |
| { |
| ret_struct.retval = -1; |
| return ret_struct; |
| } |
| } |
| } |
| |
| static unsigned int |
| get_chunk_size() |
| { |
| unsigned int ret = VSFTP_DATA_BUFSIZE; |
| if (tunable_trans_chunk_size < VSFTP_DATA_BUFSIZE && |
| tunable_trans_chunk_size > 0) |
| { |
| ret = tunable_trans_chunk_size; |
| if (ret < 4096) |
| { |
| ret = 4096; |
| } |
| } |
| return ret; |
| } |
| |