static void handle_mkd(struct vsf_session* p_sess) { int retval; vsf_log_start_entry(p_sess, kVSFLogEntryMkdir); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); /* NOTE! Actual permissions will be governed by the tunable umask */ retval = str_mkdir(&p_sess->ftp_arg_str, 0777); if (retval != 0) { vsf_log_do_log(p_sess, 0); vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Create directory operation failed."); return; } vsf_log_do_log(p_sess, 1); { static struct mystr s_mkd_res; static struct mystr s_tmp_str; str_copy(&s_tmp_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&s_tmp_str); /* Double up double quotes */ str_replace_text(&s_tmp_str, "\"", "\"\""); /* Build result string */ str_alloc_text(&s_mkd_res, "\""); str_append_str(&s_mkd_res, &s_tmp_str); str_append_text(&s_mkd_res, "\" created"); vsf_cmdio_write_str(p_sess, FTP_MKDIROK, &s_mkd_res); } }
static void handle_mdtm(struct vsf_session* p_sess) { static struct vsf_sysutil_statbuf* s_p_statbuf; int retval; if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file modification time."); } else { static struct mystr s_mdtm_res_str; str_alloc_text(&s_mdtm_res_str, vsf_sysutil_statbuf_get_numeric_date( s_p_statbuf, tunable_use_localtime)); vsf_cmdio_write_str(p_sess, FTP_MDTMOK, &s_mdtm_res_str); } }
static void handle_size(struct vsf_session* p_sess) { /* Note - in ASCII mode, are supposed to return the size after taking into * account ASCII linefeed conversions. At least this is what wu-ftpd does in * version 2.6.1. Proftpd-1.2.0pre fails to do this. * I will not do it because it is a potential I/O DoS. */ static struct vsf_sysutil_statbuf* s_p_statbuf; int retval; if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } retval = str_stat(&p_sess->ftp_arg_str, &s_p_statbuf); if (retval != 0 || !vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Could not get file size."); } else { static struct mystr s_size_res_str; str_alloc_filesize_t(&s_size_res_str, vsf_sysutil_statbuf_get_size(s_p_statbuf)); vsf_cmdio_write_str(p_sess, FTP_SIZEOK, &s_size_res_str); } }
void handle_auth(struct vsf_session* p_sess) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "TLS") || str_equal_text(&p_sess->ftp_arg_str, "TLS-C") || str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { vsf_cmdio_write(p_sess, FTP_AUTHOK, "Proceed with negotiation."); if (!ssl_session_init(p_sess)) { struct mystr err_str = INIT_MYSTR; str_alloc_text(&err_str, "Negotiation failed: "); str_append_text(&err_str, get_ssl_error()); vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str); vsf_sysutil_exit(0); } p_sess->control_use_ssl = 1; if (str_equal_text(&p_sess->ftp_arg_str, "SSL") || str_equal_text(&p_sess->ftp_arg_str, "TLS-P")) { p_sess->data_use_ssl = 1; } } else { vsf_cmdio_write(p_sess, FTP_BADAUTH, "Unknown AUTH type."); } }
static void handle_pwd(struct vsf_session* p_sess) { static struct mystr s_cwd_buf_mangle_str; static struct mystr s_pwd_res_str; str_getcwd(&s_cwd_buf_mangle_str); /* Double up any double-quotes in the pathname! */ str_replace_text(&s_cwd_buf_mangle_str, "\"", "\"\""); /* Enclose pathname in quotes */ str_alloc_text(&s_pwd_res_str, "\""); str_append_str(&s_pwd_res_str, &s_cwd_buf_mangle_str); str_append_text(&s_pwd_res_str, "\""); vsf_cmdio_write_str(p_sess, FTP_PWDOK, &s_pwd_res_str); }
void ssl_control_handshake(struct vsf_session* p_sess) { if (!ssl_session_init(p_sess)) { struct mystr err_str = INIT_MYSTR; str_alloc_text(&err_str, "Negotiation failed: "); /* Technically, we shouldn't leak such detailed error messages. */ str_append_text(&err_str, get_ssl_error()); vsf_cmdio_write_str(p_sess, FTP_TLS_FAIL, &err_str); vsf_sysutil_exit(0); } p_sess->control_use_ssl = 1; }
static void handle_rest(struct vsf_session* p_sess) { static struct mystr s_rest_str; filesize_t val = str_a_to_filesize_t(&p_sess->ftp_arg_str); if (val < 0) { val = 0; } p_sess->restart_pos = val; str_alloc_text(&s_rest_str, "Restart position accepted ("); str_append_filesize_t(&s_rest_str, val); str_append_text(&s_rest_str, ")."); vsf_cmdio_write_str(p_sess, FTP_RESTOK, &s_rest_str); }
static void handle_site_umask(struct vsf_session* p_sess, struct mystr* p_arg_str) { static struct mystr s_umask_resp_str; if (str_isempty(p_arg_str)) { /* Empty arg => report current umask */ str_alloc_text(&s_umask_resp_str, "Your current UMASK is "); str_append_text(&s_umask_resp_str, vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); } else { /* Set current umask */ unsigned int new_umask = str_octal_to_uint(p_arg_str); vsf_sysutil_set_umask(new_umask); str_alloc_text(&s_umask_resp_str, "UMASK set to "); str_append_text(&s_umask_resp_str, vsf_sysutil_uint_to_octal(vsf_sysutil_get_umask())); } vsf_cmdio_write_str(p_sess, FTP_UMASKOK, &s_umask_resp_str); }
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) { static struct mystr s_err_msg; str_alloc_text(&s_err_msg, "SSL connection failed"); if (tunable_require_ssl_reuse) { str_append_text(&s_err_msg, "; session reuse required"); str_append_text( &s_err_msg, ": see require_ssl_reuse option in vsftpd.conf man page"); } vsf_cmdio_write_str(p_sess, FTP_DATATLSBAD, &s_err_msg); } return ret; }
static void handle_retr(struct vsf_session* p_sess) { static struct mystr s_mark_str; static struct vsf_sysutil_statbuf* s_p_statbuf; struct vsf_transfer_ret trans_ret; int retval; int remote_fd; int opened_file; int is_ascii = 0; filesize_t offset = p_sess->restart_pos; p_sess->restart_pos = 0; if (!pasv_active(p_sess) && !port_active(p_sess)) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first."); return; } if (p_sess->is_ascii && offset != 0) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "No support for resume of ASCII transfer."); return; } opened_file = str_open(&p_sess->ftp_arg_str, kVSFSysStrOpenReadOnly); if (vsf_sysutil_retval_is_error(opened_file)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); return; } vsf_sysutil_fstat(opened_file, &s_p_statbuf); /* No games please */ if (!vsf_sysutil_statbuf_is_regfile(s_p_statbuf)) { /* Note - pretend open failed */ vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); goto file_close_out; } /* Optionally, we'll be paranoid and only serve publicly readable stuff */ if (p_sess->is_anonymous && tunable_anon_world_readable_only && !vsf_sysutil_statbuf_is_readable_other(s_p_statbuf)) { vsf_cmdio_write(p_sess, FTP_FILEFAIL, "Failed to open file."); goto file_close_out; } /* Set the download offset (from REST) if any */ if (offset != 0) { vsf_sysutil_lseek_to(opened_file, offset); } remote_fd = get_remote_transfer_fd(p_sess); if (vsf_sysutil_retval_is_error(remote_fd)) { goto port_pasv_cleanup_out; } vsf_log_start_entry(p_sess, kVSFLogEntryDownload); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); str_alloc_text(&s_mark_str, "Opening "); if (tunable_ascii_download_enable && p_sess->is_ascii) { str_append_text(&s_mark_str, "ASCII"); is_ascii = 1; } else { str_append_text(&s_mark_str, "BINARY"); } str_append_text(&s_mark_str, " mode data connection for "); str_append_str(&s_mark_str, &p_sess->ftp_arg_str); str_append_text(&s_mark_str, " ("); str_append_filesize_t(&s_mark_str, vsf_sysutil_statbuf_get_size(s_p_statbuf)); str_append_text(&s_mark_str, " bytes)."); vsf_cmdio_write_str(p_sess, FTP_DATACONN, &s_mark_str); trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, opened_file, 0, is_ascii); p_sess->transfer_size = trans_ret.transferred; retval = dispose_remote_transfer_fd(p_sess); /* Log _after_ the blocking dispose call, so we get transfer times right */ if (trans_ret.retval == 0 && retval == 0) { vsf_log_do_log(p_sess, 1); } else { vsf_log_do_log(p_sess, 0); } port_pasv_cleanup_out: port_cleanup(p_sess); pasv_cleanup(p_sess); file_close_out: vsf_sysutil_close(opened_file); }
static void handle_pasv(struct vsf_session* p_sess) { static struct mystr s_pasv_res_str; static struct vsf_sysutil_sockaddr* s_p_sockaddr; struct vsf_sysutil_ipv4port listen_port; struct vsf_sysutil_ipv4addr listen_ipaddr; int bind_retries = 10; pasv_cleanup(p_sess); port_cleanup(p_sess); p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock(); while (--bind_retries) { int retval; unsigned short the_port; double scaled_port; /* IPPORT_RESERVED */ unsigned short min_port = 1024; unsigned short max_port = 65535; if (tunable_pasv_min_port > min_port && tunable_pasv_min_port < max_port) { min_port = tunable_pasv_min_port; } if (tunable_pasv_max_port > min_port && tunable_pasv_max_port < max_port) { max_port = tunable_pasv_max_port; } the_port = vsf_sysutil_get_random_byte(); the_port <<= 8; the_port |= vsf_sysutil_get_random_byte(); scaled_port = (double) min_port; scaled_port += ((double) the_port / (double) 65535) * ((double) max_port - min_port); the_port = (unsigned short) scaled_port; vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr); vsf_sysutil_sockaddr_set_port(s_p_sockaddr, vsf_sysutil_ipv4port_from_int(the_port)); /* Bind to same address we got the incoming connect on */ vsf_sysutil_sockaddr_set_ipaddr(s_p_sockaddr, vsf_sysutil_sockaddr_get_ipaddr(p_sess->p_local_addr)); retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr); if (!vsf_sysutil_retval_is_error(retval)) { break; } if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE) { continue; } die("vsf_sysutil_bind"); } if (!bind_retries) { die("vsf_sysutil_bind"); } vsf_sysutil_listen(p_sess->pasv_listen_fd, 1); /* Get the address of the bound socket, for the port */ vsf_sysutil_getsockname(p_sess->pasv_listen_fd, &s_p_sockaddr); if (tunable_pasv_address != 0) { /* Report passive address as specified in configuration */ if (vsf_sysutil_inet_aton(tunable_pasv_address, &listen_ipaddr) == 0) { die("invalid pasv_address"); } } else { /* Use address of bound socket for passive address */ listen_ipaddr = vsf_sysutil_sockaddr_get_ipaddr(s_p_sockaddr); } listen_port = vsf_sysutil_sockaddr_get_port(s_p_sockaddr); str_alloc_text(&s_pasv_res_str, "Entering Passive Mode ("); str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[0]); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[1]); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[2]); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, listen_ipaddr.data[3]); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, listen_port.data[0]); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, listen_port.data[1]); str_append_text(&s_pasv_res_str, ")"); vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str); }
static void handle_upload_common(struct vsf_session* p_sess, int is_append, int is_unique) { static struct mystr s_filename; struct mystr* p_filename; struct vsf_transfer_ret trans_ret; int new_file_fd; int remote_fd; int retval; filesize_t offset = p_sess->restart_pos; p_sess->restart_pos = 0; if (!pasv_active(p_sess) && !port_active(p_sess)) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first."); return; } p_filename = &p_sess->ftp_arg_str; if (is_unique) { get_unique_filename(&s_filename, p_filename); p_filename = &s_filename; } if (!vsf_access_check_file(p_filename)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } /* NOTE - actual file permissions will be governed by the tunable umask */ /* XXX - do we care about race between create and chown() of anonymous * upload? */ if (is_unique || (p_sess->is_anonymous && !tunable_anon_other_write_enable)) { new_file_fd = str_create(p_filename); } else { /* For non-anonymous, allow open() to overwrite or append existing files */ if (!is_append && offset == 0) { new_file_fd = str_create_overwrite(p_filename); } else { new_file_fd = str_create_append(p_filename); } } if (vsf_sysutil_retval_is_error(new_file_fd)) { vsf_cmdio_write(p_sess, FTP_UPLOADFAIL, "Could not create file."); return; } /* Are we required to chown() this file for security? */ if (p_sess->is_anonymous && tunable_chown_uploads) { vsf_sysutil_fchmod(new_file_fd, 0600); if (tunable_one_process_model) { vsf_one_process_chown_upload(p_sess, new_file_fd); } else { vsf_two_process_chown_upload(p_sess, new_file_fd); } } if (!is_append && offset != 0) { /* XXX - warning, allows seek past end of file! Check for seek > size? */ vsf_sysutil_lseek_to(new_file_fd, offset); } remote_fd = get_remote_transfer_fd(p_sess); if (vsf_sysutil_retval_is_error(remote_fd)) { goto port_pasv_cleanup_out; } if (is_unique) { struct mystr resp_str = INIT_MYSTR; str_alloc_text(&resp_str, "FILE: "); str_append_str(&resp_str, p_filename); vsf_cmdio_write_str(p_sess, FTP_DATACONN, &resp_str); str_free(&resp_str); } else { vsf_cmdio_write(p_sess, FTP_DATACONN, "Ok to send data."); } vsf_log_start_entry(p_sess, kVSFLogEntryUpload); str_copy(&p_sess->log_str, &p_sess->ftp_arg_str); prepend_path_to_filename(&p_sess->log_str); if (tunable_ascii_upload_enable && p_sess->is_ascii) { trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, new_file_fd, 1, 1); } else { trans_ret = vsf_ftpdataio_transfer_file(p_sess, remote_fd, new_file_fd, 1, 0); } p_sess->transfer_size = trans_ret.transferred; /* XXX - handle failure, delete file? */ retval = dispose_remote_transfer_fd(p_sess); /* Log _after_ the blocking dispose call, so we get transfer times right */ if (trans_ret.retval == 0 && retval == 0) { vsf_log_do_log(p_sess, 1); } else { vsf_log_do_log(p_sess, 0); } port_pasv_cleanup_out: port_cleanup(p_sess); pasv_cleanup(p_sess); vsf_sysutil_close(new_file_fd); }
static void handle_pasv(struct vsf_session* p_sess, int is_epsv) { static struct mystr s_pasv_res_str; static struct vsf_sysutil_sockaddr* s_p_sockaddr; int bind_retries = 10; unsigned short the_port = 0; int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr); if (is_epsv && !str_isempty(&p_sess->ftp_arg_str)) { int argval; str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "ALL")) { p_sess->epsv_all = 1; vsf_cmdio_write(p_sess, FTP_EPSVALLOK, "EPSV ALL ok."); return; } argval = vsf_sysutil_atoi(str_getbuf(&p_sess->ftp_arg_str)); if (!is_ipv6 || argval != 2) { vsf_cmdio_write(p_sess, FTP_EPSVBAD, "Bad network protocol."); return; } } pasv_cleanup(p_sess); port_cleanup(p_sess); if (is_epsv && is_ipv6) { p_sess->pasv_listen_fd = vsf_sysutil_get_ipv6_sock(); } else { p_sess->pasv_listen_fd = vsf_sysutil_get_ipv4_sock(); } vsf_sysutil_activate_reuseaddr(p_sess->pasv_listen_fd); while (--bind_retries) { int retval; double scaled_port; /* IPPORT_RESERVED */ unsigned short min_port = 1024; unsigned short max_port = 65535; if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port) { min_port = tunable_pasv_min_port; } if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port) { max_port = tunable_pasv_max_port; } the_port = vsf_sysutil_get_random_byte(); the_port <<= 8; the_port |= vsf_sysutil_get_random_byte(); scaled_port = (double) min_port; scaled_port += ((double) the_port / (double) 65536) * ((double) max_port - min_port + 1); the_port = (unsigned short) scaled_port; vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr); vsf_sysutil_sockaddr_set_port(s_p_sockaddr, the_port); retval = vsf_sysutil_bind(p_sess->pasv_listen_fd, s_p_sockaddr); if (!vsf_sysutil_retval_is_error(retval)) { break; } if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE) { continue; } die("vsf_sysutil_bind"); } if (!bind_retries) { die("vsf_sysutil_bind"); } vsf_sysutil_listen(p_sess->pasv_listen_fd, 1); if (is_epsv) { str_alloc_text(&s_pasv_res_str, "Entering Extended Passive Mode (|||"); str_append_ulong(&s_pasv_res_str, (unsigned long) the_port); str_append_text(&s_pasv_res_str, "|)"); vsf_cmdio_write_str(p_sess, FTP_EPSVOK, &s_pasv_res_str); return; } if (tunable_pasv_address != 0) { /* Report passive address as specified in configuration */ if (vsf_sysutil_inet_aton(tunable_pasv_address, s_p_sockaddr) == 0) { die("invalid pasv_address"); } } str_alloc_text(&s_pasv_res_str, "Entering Passive Mode ("); if (!is_ipv6) { str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntop(s_p_sockaddr)); } else { const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr); if (p_v4addr) { str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr)); } } str_replace_char(&s_pasv_res_str, '.', ','); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, the_port >> 8); str_append_text(&s_pasv_res_str, ","); str_append_ulong(&s_pasv_res_str, the_port & 255); str_append_text(&s_pasv_res_str, ")"); vsf_cmdio_write_str(p_sess, FTP_PASVOK, &s_pasv_res_str); }