void vsf_insert_uwtmp(const struct mystr* p_user_str, const struct mystr* p_host_str) { if (sizeof(s_utent.ut_line) < 16) { return; } if (s_uwtmp_inserted) { bug("vsf_insert_uwtmp"); } { struct mystr line_str = INIT_MYSTR; str_alloc_text(&line_str, "vsftpd:"); str_append_ulong(&line_str, vsf_sysutil_getpid()); if (str_getlen(&line_str) >= sizeof(s_utent.ut_line)) { str_free(&line_str); return; } vsf_sysutil_strcpy(s_utent.ut_line, str_getbuf(&line_str), sizeof(s_utent.ut_line)); str_free(&line_str); } s_uwtmp_inserted = 1; s_utent.ut_type = USER_PROCESS; s_utent.ut_pid = vsf_sysutil_getpid(); vsf_sysutil_strcpy(s_utent.ut_user, str_getbuf(p_user_str), sizeof(s_utent.ut_user)); vsf_sysutil_strcpy(s_utent.ut_host, str_getbuf(p_host_str), sizeof(s_utent.ut_host)); s_utent.ut_tv.tv_sec = vsf_sysutil_get_time_sec(); setutxent(); (void) pututxline(&s_utent); endutxent(); updwtmpx(WTMPX_FILE, &s_utent); }
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_mkd(struct vsf_session* p_sess) { int retval; if (!vsf_access_check_file(&p_sess->ftp_arg_str)) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); return; } 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_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); }
int main(int argc, const char* argv[]) { struct vsf_session the_session = { /* Control connection */ 0, 0, 0, 0, 0, /* Data connection */ -1, 0, -1, 0, 0, 0, 0, /* Login */ 1, 0, INIT_MYSTR, INIT_MYSTR, /* Protocol state */ 0, 1, INIT_MYSTR, 0, 0, /* HTTP hacks */ 0, INIT_MYSTR, /* Session state */ 0, /* Userids */ -1, -1, -1, /* Pre-chroot() cache */ INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1, /* Logging */ -1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0, /* Buffers */ INIT_MYSTR, INIT_MYSTR, 0, INIT_MYSTR, /* Parent <-> child comms */ -1, -1, /* Number of clients */ 0, 0, /* Home directory */ INIT_MYSTR, /* Secure connection state */ 0, 0, 0, 0, 0, INIT_MYSTR, 0, -1, -1, /* Login fails */ 0, /* write_enable */ 0 }; int config_loaded = 0; int i; tunables_load_defaults(); /* This might need to open /dev/zero on systems lacking MAP_ANON. Needs * to be done early (i.e. before config file parse, which may use * anonymous pages */ vsf_sysutil_map_anon_pages_init(); /* Argument parsing. Any argument not starting with "-" is a config file, * loaded in the order encountered. -o opt=value options are loading in the * order encountered, including correct ordering with respect intermingled * config files. * If we see -v (version) or an unknown option, parsing bails and exits. */ if (argc == 0) { die("vsftpd: missing argv[0]"); } for (i = 1; i < argc; ++i) { const char* p_arg = argv[i]; if (p_arg[0] != '-') { config_loaded = 1; vsf_parseconf_load_file(p_arg, 1); } else { if (p_arg[1] == 'v') { vsf_exit("vsftpd: version " VSF_VERSION "\n"); } else if (p_arg[1] == 'o') { vsf_parseconf_load_setting(&p_arg[2], 1); } else { die2("unrecognise option: ", p_arg); } } } /* Parse default config file if necessary */ if (!config_loaded) { struct vsf_sysutil_statbuf* p_statbuf = 0; int retval = vsf_sysutil_stat(VSFTP_DEFAULT_CONFIG, &p_statbuf); if (!vsf_sysutil_retval_is_error(retval)) { vsf_parseconf_load_file(VSFTP_DEFAULT_CONFIG, 1); } vsf_sysutil_free(p_statbuf); } /* Resolve pasv_address if required */ if (tunable_pasv_address && tunable_pasv_addr_resolve) { struct vsf_sysutil_sockaddr* p_addr = 0; const char* p_numeric_addr; vsf_sysutil_dns_resolve(&p_addr, tunable_pasv_address); vsf_sysutil_free((char*) tunable_pasv_address); p_numeric_addr = vsf_sysutil_inet_ntop(p_addr); tunable_pasv_address = vsf_sysutil_strdup(p_numeric_addr); vsf_sysutil_free(p_addr); } if (!tunable_run_as_launching_user) { /* Just get out unless we start with requisite privilege */ die_unless_privileged(); } if (tunable_setproctitle_enable) { /* Warning -- warning -- may nuke argv, environ */ vsf_sysutil_setproctitle_init(argc, argv); } /* Initialize the SSL system here if needed - saves the overhead of each * child doing this itself. */ if (tunable_ssl_enable) { ssl_init(&the_session); } if (tunable_listen || tunable_listen_ipv6) { /* Standalone mode */ struct vsf_client_launch ret = vsf_standalone_main(); the_session.num_clients = ret.num_children; the_session.num_this_ip = ret.num_this_ip; } if (tunable_tcp_wrappers) { the_session.tcp_wrapper_ok = vsf_tcp_wrapper_ok(VSFTP_COMMAND_FD); } { const char* p_load_conf = vsf_sysutil_getenv("VSFTPD_LOAD_CONF"); if (p_load_conf) { vsf_parseconf_load_file(p_load_conf, 1); } } /* Sanity checks - exit with a graceful error message if our STDIN is not * a socket. Also check various config options don't collide. */ do_sanity_checks(); /* Initializes session globals - e.g. IP addr's etc. */ session_init(&the_session); /* Set up "environment", e.g. process group etc. */ env_init(); /* Set up resource limits. */ limits_init(); /* Set up logging - must come after global init because we need the remote * address to convert into text */ vsf_log_init(&the_session); str_alloc_text(&the_session.remote_ip_str, vsf_sysutil_inet_ntop(the_session.p_remote_addr)); /* Set up options on the command socket */ vsf_cmdio_sock_setup(); if (tunable_setproctitle_enable) { vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str); vsf_sysutil_setproctitle("connected"); } /* We might chroot() very soon (one process model), so we need to open * any required config files here. */ /* SSL may have been enabled by a per-IP configuration.. */ if (tunable_ssl_enable) { ssl_init(&the_session); ssl_add_entropy(&the_session); } if (tunable_deny_email_enable) { int retval = -1; if (tunable_banned_email_file) { retval = str_fileread(&the_session.banned_email_str, tunable_banned_email_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read anon e-mail list file:", tunable_banned_email_file); } } if (tunable_banner_file) { int retval = str_fileread(&the_session.banner_str, tunable_banner_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read banner file:", tunable_banner_file); } } if (tunable_secure_email_list_enable) { int retval = -1; if (tunable_email_password_file) { retval = str_fileread(&the_session.email_passwords_str, tunable_email_password_file, VSFTP_CONF_FILE_MAX); } if (vsf_sysutil_retval_is_error(retval)) { die2("cannot read email passwords file:", tunable_email_password_file); } } if (tunable_run_as_launching_user) { tunable_one_process_model = 1; if (!vsf_sysutil_running_as_root()) { tunable_connect_from_port_20 = 0; tunable_chown_uploads = 0; } } if (tunable_one_process_model) { vsf_one_process_start(&the_session); } else { vsf_two_process_start(&the_session); } /* NOTREACHED */ bug("should not get here: main"); return 1; }
static void vsf_log_do_log_wuftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded) { long delta_sec; enum EVSFLogEntryType what = (enum EVSFLogEntryType) p_sess->log_type; /* Date - vsf_sysutil_get_current_date updates cached time */ str_alloc_text(p_str, vsf_sysutil_get_current_date()); str_append_char(p_str, ' '); /* Transfer time (in seconds) */ delta_sec = vsf_sysutil_get_cached_time_sec() - p_sess->log_start_sec; if (delta_sec <= 0) { delta_sec = 1; } str_append_ulong(p_str, (unsigned long) delta_sec); str_append_char(p_str, ' '); /* Remote host name */ str_append_str(p_str, &p_sess->remote_ip_str); str_append_char(p_str, ' '); /* Bytes transferred */ str_append_filesize_t(p_str, p_sess->transfer_size); str_append_char(p_str, ' '); /* Filename */ str_append_str(p_str, &p_sess->log_str); str_append_char(p_str, ' '); /* Transfer type (ascii/binary) */ if (p_sess->is_ascii) { str_append_text(p_str, "a "); } else { str_append_text(p_str, "b "); } /* Special action flag - tar, gzip etc. */ str_append_text(p_str, "_ "); /* Direction of transfer */ if (what == kVSFLogEntryUpload) { str_append_text(p_str, "i "); } else { str_append_text(p_str, "o "); } /* Access mode: anonymous/real user, and identity */ if (p_sess->is_anonymous) { str_append_text(p_str, "a "); str_append_str(p_str, &p_sess->anon_pass_str); } else { str_append_text(p_str, "r "); str_append_str(p_str, &p_sess->user_str); } str_append_char(p_str, ' '); /* Service name, authentication method, authentication user id */ str_append_text(p_str, "ftp 0 * "); /* Completion status */ if (succeeded) { str_append_char(p_str, 'c'); } else { str_append_char(p_str, 'i'); } }
int main(int argc, const char* argv[]) { struct vsf_session the_session = { /* Control connection */ 0, 0, 0, /* Data connection */ -1, 0, -1, 0, 0, 0, 0, /* Login */ 1, INIT_MYSTR, INIT_MYSTR, /* Protocol state */ 0, 1, INIT_MYSTR, 0, 0, /* Session state */ 0, /* Userids */ -1, -1, -1, /* Pre-chroot() cache */ INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, INIT_MYSTR, 1, /* Logging */ -1, -1, INIT_MYSTR, 0, 0, 0, INIT_MYSTR, 0, /* Buffers */ INIT_MYSTR, INIT_MYSTR, /* Parent <-> child comms */ -1, -1, /* Number of clients */ 0, 0, /* Home directory */ INIT_MYSTR, /* Secure connection state */ 0, 0, 0, 0, 0, 0, -1, -1 }; int config_specified = 0; const char* p_config_name = VSFTP_DEFAULT_CONFIG; /* Zero or one argument supported. If one argument is passed, it is the * path to the config file */ if (argc > 2) { die("vsftpd: too many arguments (I take an optional config file only)"); } else if (argc == 0) { die("vsftpd: missing argv[0]"); } if (argc == 2) { if (!vsf_sysutil_strcmp(argv[1], "-v")) { vsf_exit("vsftpd: version " VSF_VERSION "\n"); } p_config_name = argv[1]; config_specified = 1; } /* This might need to open /dev/zero on systems lacking MAP_ANON. Needs * to be done early (i.e. before config file parse, which may use * anonymous pages */ vsf_sysutil_map_anon_pages_init(); /* Parse config file if it's there */ { struct vsf_sysutil_statbuf* p_statbuf = 0; int retval = vsf_sysutil_stat(p_config_name, &p_statbuf); if (!vsf_sysutil_retval_is_error(retval)) { vsf_parseconf_load_file(p_config_name, 1); } else if (config_specified) { die2("vsftpd: cannot open config file:", p_config_name); } vsf_sysutil_free(p_statbuf); } if (!tunable_run_as_launching_user) { /* Just get out unless we start with requisite privilege */ die_unless_privileged(); } if (tunable_setproctitle_enable) { /* Warning -- warning -- may nuke argv, environ */ vsf_sysutil_setproctitle_init(argc, argv); } /* Initialize the SSL system here if needed - saves the overhead of each * child doing this itself. */ if (tunable_ssl_enable) { ssl_init(&the_session); } if (tunable_listen || tunable_listen_ipv6) { /* Standalone mode */ struct vsf_client_launch ret = vsf_standalone_main(); the_session.num_clients = ret.num_children; the_session.num_this_ip = ret.num_this_ip; } /* Sanity checks - exit with a graceful error message if our STDIN is not * a socket. Also check various config options don't collide. */ do_sanity_checks(); /* Initializes session globals - e.g. IP addr's etc. */ session_init(&the_session); /* Set up "environment", e.g. process group etc. */ env_init(); /* Set up logging - must come after global init because we need the remote * address to convert into text */ vsf_log_init(&the_session); str_alloc_text(&the_session.remote_ip_str, vsf_sysutil_inet_ntop(the_session.p_remote_addr)); /* Set up options on the command socket */ vsf_cmdio_sock_setup(); if (tunable_setproctitle_enable) { vsf_sysutil_set_proctitle_prefix(&the_session.remote_ip_str); vsf_sysutil_setproctitle("connected"); } /* We might chroot() very soon (one process model), so we need to open * any required config files here. */ if (tunable_tcp_wrappers) { the_session.tcp_wrapper_ok = vsf_tcp_wrapper_ok(VSFTP_COMMAND_FD); } { const char* p_load_conf = vsf_sysutil_getenv("VSFTPD_LOAD_CONF"); if (p_load_conf) { vsf_parseconf_load_file(p_load_conf, 1); } } /* SSL may have been enabled by a per-IP configuration.. */ if (tunable_ssl_enable) { ssl_init(&the_session); } if (tunable_deny_email_enable) { int retval = str_fileread(&the_session.banned_email_str, tunable_banned_email_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die2("cannot open anon e-mail list file:", tunable_banned_email_file); } } if (tunable_banner_file) { int retval = str_fileread(&the_session.banner_str, tunable_banner_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die2("cannot open banner file:", tunable_banner_file); } } if (tunable_secure_email_list_enable) { int retval = str_fileread(&the_session.email_passwords_str, tunable_email_password_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die2("cannot open email passwords file:", tunable_email_password_file); } } /* Special case - can force one process model if we've got a setup * needing _no_ privs */ if (!tunable_local_enable && !tunable_connect_from_port_20 && !tunable_chown_uploads) { tunable_one_process_model = 1; } if (tunable_run_as_launching_user) { tunable_one_process_model = 1; if (!vsf_sysutil_running_as_root()) { tunable_connect_from_port_20 = 0; tunable_chown_uploads = 0; } } if (tunable_one_process_model) { vsf_one_process_start(&the_session); } else { vsf_two_process_start(&the_session); } /* NOTREACHED */ bug("should not get here: main"); return 1; }
void vsf_parseconf_load_setting(const char* p_setting, int errs_fatal) { static struct mystr s_setting_str; static struct mystr s_value_str; while (vsf_sysutil_isspace(*p_setting)) { p_setting++; } str_alloc_text(&s_setting_str, p_setting); str_split_char(&s_setting_str, &s_value_str, '='); /* Is it a string setting? */ { const struct parseconf_str_setting* p_str_setting = parseconf_str_array; while (p_str_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_str_setting->p_setting_name)) { /* Got it */ const char** p_curr_setting = p_str_setting->p_variable; if (*p_curr_setting) { vsf_sysutil_free((char*) *p_curr_setting); } if (str_isempty(&s_value_str)) { *p_curr_setting = 0; } else { *p_curr_setting = str_strdup(&s_value_str); } return; } p_str_setting++; } } if (str_isempty(&s_value_str)) { if (errs_fatal) { die2("missing value in config file for: ", str_getbuf(&s_setting_str)); } else { return; } } /* Is it a boolean value? */ { const struct parseconf_bool_setting* p_bool_setting = parseconf_bool_array; while (p_bool_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_bool_setting->p_setting_name)) { /* Got it */ str_upper(&s_value_str); if (str_equal_text(&s_value_str, "YES") || str_equal_text(&s_value_str, "TRUE") || str_equal_text(&s_value_str, "1")) { *(p_bool_setting->p_variable) = 1; } else if (str_equal_text(&s_value_str, "NO") || str_equal_text(&s_value_str, "FALSE") || str_equal_text(&s_value_str, "0")) { *(p_bool_setting->p_variable) = 0; } else if (errs_fatal) { die2("bad bool value in config file for: ", str_getbuf(&s_setting_str)); } return; } p_bool_setting++; } } /* Is it an unsigned integer setting? */ { const struct parseconf_uint_setting* p_uint_setting = parseconf_uint_array; while (p_uint_setting->p_setting_name != 0) { if (str_equal_text(&s_setting_str, p_uint_setting->p_setting_name)) { /* Got it */ /* If the value starts with 0, assume it's an octal value */ if (!str_isempty(&s_value_str) && str_get_char_at(&s_value_str, 0) == '0') { *(p_uint_setting->p_variable) = str_octal_to_uint(&s_value_str); } else { /* TODO: we could reject negatives instead of converting them? */ *(p_uint_setting->p_variable) = (unsigned int) str_atoi(&s_value_str); } return; } p_uint_setting++; } } if (errs_fatal) { die2("unrecognised variable in config file: ", str_getbuf(&s_setting_str)); } }
void str_alloc_filesize_t(struct mystr* p_str, filesize_t the_filesize) { str_alloc_text(p_str, vsf_sysutil_filesize_t_to_str(the_filesize)); }
static void common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, int do_chroot, int anon) { int was_anon = anon; int newpid; vsf_sysutil_default_sig(kVSFSysUtilSigCHLD); /* Asks the pre-login child to go away (by exiting) */ priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_OK); (void) vsf_sysutil_wait(); /* Handle loading per-user config options */ handle_per_user_config(p_user_str); vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, twoproc_handle_sigchld); newpid = vsf_sysutil_fork(); if (newpid == 0) { struct mystr guest_user_str = INIT_MYSTR; struct mystr chroot_str = INIT_MYSTR; struct mystr chdir_str = INIT_MYSTR; unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS; calculate_chdir_dir(anon, &chroot_str, &chdir_str, p_user_str); if (do_chroot) { secutil_option |= VSF_SECUTIL_OPTION_CHROOT; } /* Child - drop privs and start proper FTP! */ if (tunable_guest_enable && !anon) { /* Remap to the guest user */ str_alloc_text(&guest_user_str, tunable_guest_username); p_user_str = &guest_user_str; /* SECURITY: For now, apply the anonymous restrictions to * guest users */ anon = 1; } if (!anon) { secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID; } vsf_secutil_change_credentials(p_user_str, 0, &chroot_str, 0, secutil_option); if (!str_isempty(&chdir_str)) { (void) str_chdir(&chdir_str); } str_free(&guest_user_str); str_free(&chroot_str); str_free(&chdir_str); /* Guard against the config error of having the anonymous ftp tree owned * by the user we are running as */ if (was_anon && vsf_sysutil_write_access("/")) { die("vsftpd: refusing to run with writable anonymous root"); } p_sess->is_anonymous = anon; process_post_login(p_sess); bug("should not get here: common_do_login"); } /* Parent */ vsf_priv_parent_postlogin(p_sess); bug("should not get here in common_do_login"); }
void vsf_cmdio_get_cmd_and_arg(struct vsf_session* p_sess, struct mystr* p_cmd_str, struct mystr* p_arg_str, int set_alarm) { int ret; /* Prepare an alarm to timeout the session.. */ if (set_alarm) { vsf_cmdio_set_alarm(p_sess); } /* Blocks */ ret = control_getline(p_cmd_str, p_sess); if (p_sess->idle_timeout) { vsf_cmdio_write_exit(p_sess, FTP_IDLE_TIMEOUT, "Timeout.", 1); } if (ret == 0) { /* Remote end hung up without a polite QUIT. The shutdown is to make * sure buggy clients don't ever see an OOPS message. */ vsf_sysutil_shutdown_failok(VSFTP_COMMAND_FD); vsf_sysutil_exit(1); } /* View a single space as a command of " ", which although a useless command, * permits the caller to distinguish input of "" from " ". */ if (str_getlen(p_cmd_str) == 1 && str_get_char_at(p_cmd_str, 0) == ' ') { str_empty(p_arg_str); } else { str_split_char(p_cmd_str, p_arg_str, ' '); } str_upper(p_cmd_str); if (!str_isempty(p_arg_str)) { char *tmp_str; tmp_str = remote2local(str_getbuf(p_arg_str)); if (tmp_str != NULL) { str_empty(p_arg_str); str_append_text(p_arg_str, tmp_str); vsf_sysutil_free(tmp_str); } } if (tunable_log_ftp_protocol) { static struct mystr s_log_str; if (str_equal_text(p_cmd_str, "PASS")) { str_alloc_text(&s_log_str, "PASS <password>"); } else { str_copy(&s_log_str, p_cmd_str); if (!str_isempty(p_arg_str)) { str_append_char(&s_log_str, ' '); str_append_str(&s_log_str, p_arg_str); } } vsf_log_line(p_sess, kVSFLogEntryFTPInput, &s_log_str); } }
static void common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, int do_chroot, int anon) { int was_anon = anon; const struct mystr* p_orig_user_str = p_user_str; int newpid; vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD); /* Tells the pre-login child all is OK (it may exit in response) */ priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); if (!p_sess->control_use_ssl) { (void) vsf_sysutil_wait(); } else { p_sess->ssl_slave_active = 1; } /* Handle loading per-user config options */ handle_per_user_config(p_user_str); /* Set this before we fork */ p_sess->is_anonymous = anon; priv_sock_close(p_sess); priv_sock_init(p_sess); vsf_sysutil_install_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1); if (tunable_isolate_network && !tunable_port_promiscuous) { newpid = vsf_sysutil_fork_newnet(); } else { newpid = vsf_sysutil_fork(); } if (newpid == 0) { struct mystr guest_user_str = INIT_MYSTR; struct mystr chroot_str = INIT_MYSTR; struct mystr chdir_str = INIT_MYSTR; struct mystr userdir_str = INIT_MYSTR; unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS | VSF_SECUTIL_OPTION_NO_PROCS; /* Child - drop privs and start proper FTP! */ /* This PR_SET_PDEATHSIG doesn't work for all possible process tree setups. * The other cases are taken care of by a shutdown() of the command * connection in our SIGTERM handler. */ vsf_set_die_if_parent_dies(); priv_sock_set_child_context(p_sess); if (tunable_guest_enable && !anon) { p_sess->is_guest = 1; /* Remap to the guest user */ str_alloc_text(&guest_user_str, tunable_guest_username); p_user_str = &guest_user_str; if (!tunable_virtual_use_local_privs) { anon = 1; do_chroot = 1; } } if (do_chroot) { secutil_option |= VSF_SECUTIL_OPTION_CHROOT; } if (!anon) { secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID; } calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str, p_user_str, p_orig_user_str); vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str, 0, secutil_option); if (!str_isempty(&chdir_str)) { (void) str_chdir(&chdir_str); } str_free(&guest_user_str); str_free(&chroot_str); str_free(&chdir_str); str_free(&userdir_str); p_sess->is_anonymous = anon; process_post_login(p_sess); bug("should not get here: common_do_login"); } /* Parent */ priv_sock_set_parent_context(p_sess); if (tunable_ssl_enable) { ssl_comm_channel_set_producer_context(p_sess); } vsf_priv_parent_postlogin(p_sess); bug("should not get here in common_do_login"); }
void vsf_secutil_change_credentials(const struct mystr* p_user_str, const struct mystr* p_dir_str, const struct mystr* p_ext_dir_str, unsigned int caps, unsigned int options) { struct vsf_sysutil_user* p_user; if (!vsf_sysutil_running_as_root()) { bug("vsf_secutil_change_credentials: not running as root"); } p_user = str_getpwnam(p_user_str); if (p_user == 0) { die2("cannot locate user entry:", str_getbuf(p_user_str)); } { struct mystr dir_str = INIT_MYSTR; /* Work out where the chroot() jail is */ if (p_dir_str == 0 || str_isempty(p_dir_str)) { str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user)); } else { str_copy(&dir_str, p_dir_str); } /* Sort out supplementary groups before the chroot(). We need to access * /etc/groups */ if (options & VSF_SECUTIL_OPTION_USE_GROUPS) { vsf_sysutil_initgroups(p_user); } else { vsf_sysutil_clear_supp_groups(); } /* Always do the chdir() regardless of whether we are chroot()'ing */ { /* Do chdir() with the target effective IDs to cater for NFS mounted * home directories. */ int saved_euid = 0; int saved_egid = 0; int retval; if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { saved_euid = vsf_sysutil_geteuid(); saved_egid = vsf_sysutil_getegid(); vsf_sysutil_setegid(p_user); vsf_sysutil_seteuid(p_user); } retval = str_chdir(&dir_str); if (retval != 0) { die2("cannot change directory:", str_getbuf(&dir_str)); } if (p_ext_dir_str && !str_isempty(p_ext_dir_str)) { retval = str_chdir(p_ext_dir_str); /* Failure on the extra directory is OK as long as we're not in * chroot() mode */ if (retval != 0 && !(options & VSF_SECUTIL_OPTION_CHROOT)) { retval = 0; } } if (retval != 0) { die2("cannot change directory:", str_getbuf(p_ext_dir_str)); } if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { vsf_sysutil_seteuid_numeric(saved_euid); vsf_sysutil_setegid_numeric(saved_egid); } /* Do the chroot() if required */ if (options & VSF_SECUTIL_OPTION_CHROOT) { vsf_sysutil_chroot("."); } } str_free(&dir_str); } if (options & VSF_SECUTIL_OPTION_NO_FDS) { vsf_sysutil_set_no_fds(); } /* Handle capabilities */ if (caps) { if (!vsf_sysdep_has_capabilities()) { /* Need privilege but OS has no capabilities - have to keep root */ return; } if (!vsf_sysdep_has_capabilities_as_non_root()) { vsf_sysdep_adopt_capabilities(caps); return; } vsf_sysdep_keep_capabilities(); } /* Set group id */ vsf_sysutil_setgid(p_user); /* Finally set user id */ vsf_sysutil_setuid(p_user); if (caps) { vsf_sysdep_adopt_capabilities(caps); } if (options & VSF_SECUTIL_OPTION_NO_PROCS) { vsf_sysutil_set_no_procs(); } // check removed (for now) as tomato lacks of other users then root // /* Misconfiguration check: don't ever chroot() to a directory writable by // * the current user. // */ // if ((options & VSF_SECUTIL_OPTION_CHROOT) && // !(options & VSF_SECUTIL_OPTION_ALLOW_WRITEABLE_ROOT)) // { // if (vsf_sysutil_write_access("/")) // { // die("vsftpd: refusing to run with writable root inside chroot()"); // } // } }
struct vsf_client_launch vsf_standalone_main(void) { struct vsf_sysutil_sockaddr* p_accept_addr = 0; int listen_sock = -1; int retval; s_ipaddr_size = vsf_sysutil_get_ipaddr_size(); if (tunable_listen && tunable_listen_ipv6) { die("run two copies of vsftpd for IPv4 and IPv6"); } if (tunable_background) { int forkret = vsf_sysutil_fork(); if (forkret > 0) { /* Parent, just exit */ vsf_sysutil_exit(0); } vsf_sysutil_make_session_leader(); } if (tunable_listen) { listen_sock = vsf_sysutil_get_ipv4_sock(); } else { listen_sock = vsf_sysutil_get_ipv6_sock(); } vsf_sysutil_activate_reuseaddr(listen_sock); s_p_ip_count_hash = hash_alloc(256, s_ipaddr_size, sizeof(unsigned int), hash_ip); s_p_pid_ip_hash = hash_alloc(256, sizeof(int), s_ipaddr_size, hash_pid); if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("LISTENER"); } vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, handle_sigchld); vsf_sysutil_install_async_sighandler(kVSFSysUtilSigHUP, handle_sighup); if (tunable_listen) { struct vsf_sysutil_sockaddr* p_sockaddr = 0; vsf_sysutil_sockaddr_alloc_ipv4(&p_sockaddr); vsf_sysutil_sockaddr_set_port(p_sockaddr, tunable_listen_port); if (!tunable_listen_address) { vsf_sysutil_sockaddr_set_any(p_sockaddr); } else { if (!vsf_sysutil_inet_aton(tunable_listen_address, p_sockaddr)) { die2("bad listen_address: ", tunable_listen_address); } } retval = vsf_sysutil_bind(listen_sock, p_sockaddr); vsf_sysutil_free(p_sockaddr); if (vsf_sysutil_retval_is_error(retval)) { die("could not bind listening IPv4 socket"); } } else { struct vsf_sysutil_sockaddr* p_sockaddr = 0; vsf_sysutil_sockaddr_alloc_ipv6(&p_sockaddr); vsf_sysutil_sockaddr_set_port(p_sockaddr, tunable_listen_port); if (!tunable_listen_address6) { vsf_sysutil_sockaddr_set_any(p_sockaddr); } else { struct mystr addr_str = INIT_MYSTR; const unsigned char* p_raw_addr; str_alloc_text(&addr_str, tunable_listen_address6); p_raw_addr = vsf_sysutil_parse_ipv6(&addr_str); str_free(&addr_str); if (!p_raw_addr) { die2("bad listen_address6: ", tunable_listen_address6); } vsf_sysutil_sockaddr_set_ipv6addr(p_sockaddr, p_raw_addr); } retval = vsf_sysutil_bind(listen_sock, p_sockaddr); vsf_sysutil_free(p_sockaddr); if (vsf_sysutil_retval_is_error(retval)) { die("could not bind listening IPv6 socket"); } } vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG); vsf_sysutil_sockaddr_alloc(&p_accept_addr); while (1) { struct vsf_client_launch child_info; void* p_raw_addr; int new_child; int new_client_sock; vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); vsf_sysutil_unblock_sig(kVSFSysUtilSigHUP); new_client_sock = vsf_sysutil_accept_timeout( listen_sock, p_accept_addr, 0); vsf_sysutil_block_sig(kVSFSysUtilSigCHLD); vsf_sysutil_block_sig(kVSFSysUtilSigHUP); if (vsf_sysutil_retval_is_error(new_client_sock)) { continue; } ++s_children; child_info.num_children = s_children; child_info.num_this_ip = 0; p_raw_addr = vsf_sysutil_sockaddr_get_raw_addr(p_accept_addr); child_info.num_this_ip = handle_ip_count(p_raw_addr); new_child = vsf_sysutil_fork_failok(); if (new_child != 0) { /* Parent context */ vsf_sysutil_close(new_client_sock); if (new_child > 0) { hash_add_entry(s_p_pid_ip_hash, (void*)&new_child, p_raw_addr); } else { /* fork() failed, clear up! */ --s_children; drop_ip_count(p_raw_addr); } /* Fall through to while() loop and accept() again */ } else { /* Child context */ vsf_sysutil_close(listen_sock); prepare_child(new_client_sock); /* By returning here we "launch" the child process with the same * contract as xinetd would provide. */ return child_info; } } }
void vsf_secutil_change_credentials(const struct mystr* p_user_str, const struct mystr* p_dir_str, const struct mystr* p_ext_dir_str, unsigned int caps, unsigned int options) { struct vsf_sysutil_user* p_user; if (!vsf_sysutil_running_as_root()) { bug("vsf_secutil_change_credentials: not running as root"); } p_user = str_getpwnam(p_user_str); if (p_user == 0) { die2("cannot locate user entry:", str_getbuf(p_user_str)); } { struct mystr dir_str = INIT_MYSTR; /* Work out where the chroot() jail is */ if (p_dir_str == 0 || str_isempty(p_dir_str)) { str_alloc_text(&dir_str, vsf_sysutil_user_get_homedir(p_user)); } else { str_copy(&dir_str, p_dir_str); } /* Sort out supplementary groups before the chroot(). We need to access * /etc/groups */ if (options & VSF_SECUTIL_OPTION_USE_GROUPS) { vsf_sysutil_initgroups(p_user); } else { vsf_sysutil_clear_supp_groups(); } /* Always do the chdir() regardless of whether we are chroot()'ing */ { /* Do chdir() with the target effective IDs to cater for NFS mounted * home directories. */ int saved_euid = 0; int saved_egid = 0; int retval; if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { saved_euid = vsf_sysutil_geteuid(); saved_egid = vsf_sysutil_getegid(); vsf_sysutil_setegid(p_user); vsf_sysutil_seteuid(p_user); } retval = str_chdir(&dir_str); if (retval != 0) { die2("cannot change directory:", str_getbuf(&dir_str)); } if (p_ext_dir_str && !str_isempty(p_ext_dir_str)) { retval = str_chdir(p_ext_dir_str); /* Failure on the extra directory is OK as long as we're not in * chroot() mode */ if (retval != 0 && !(options & VSF_SECUTIL_OPTION_CHROOT)) { retval = 0; } } if (retval != 0) { die2("cannot change directory:", str_getbuf(p_ext_dir_str)); } if (options & VSF_SECUTIL_OPTION_CHANGE_EUID) { vsf_sysutil_seteuid_numeric(saved_euid); vsf_sysutil_setegid_numeric(saved_egid); } // 2007.05 James { /* Do the chroot() if required */ //if (options & VSF_SECUTIL_OPTION_CHROOT) //{ // vsf_sysutil_chroot("."); //} // 2007.05 James } } str_free(&dir_str); } /* Handle capabilities */ if (caps) { if (!vsf_sysdep_has_capabilities()) { /* Need privilege but OS has no capabilities - have to keep root */ return; } if (!vsf_sysdep_has_capabilities_as_non_root()) { vsf_sysdep_adopt_capabilities(caps); return; } vsf_sysdep_keep_capabilities(); } /* Set group id */ vsf_sysutil_setgid(p_user); /* Finally set user id */ vsf_sysutil_setuid(p_user); if (caps) { vsf_sysdep_adopt_capabilities(caps); } }
int vsf_sysdep_check_auth(struct mystr* p_user_str, const struct mystr* p_pass_str, const struct mystr* p_remote_host) { int retval; pam_item_t item; const char* pam_user_name = 0; struct pam_conv the_conv = { &pam_conv_func, 0 }; if (s_pamh != 0) { bug("vsf_sysdep_check_auth"); } str_copy(&s_pword_str, p_pass_str); retval = pam_start(tunable_pam_service_name, str_getbuf(p_user_str), &the_conv, &s_pamh); if (retval != PAM_SUCCESS) { s_pamh = 0; return 0; } #ifdef PAM_RHOST retval = pam_set_item(s_pamh, PAM_RHOST, str_getbuf(p_remote_host)); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif #ifdef PAM_TTY retval = pam_set_item(s_pamh, PAM_TTY, "ftp"); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif #ifdef PAM_RUSER retval = pam_set_item(s_pamh, PAM_RUSER, str_getbuf(p_user_str)); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #endif retval = pam_authenticate(s_pamh, 0); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } #ifdef PAM_USER retval = pam_get_item(s_pamh, PAM_USER, &item); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } pam_user_name = item; str_alloc_text(p_user_str, pam_user_name); #endif retval = pam_acct_mgmt(s_pamh, 0); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } retval = pam_setcred(s_pamh, PAM_ESTABLISH_CRED); if (retval != PAM_SUCCESS) { (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } if (!tunable_session_support) { /* You're in already! */ (void) pam_end(s_pamh, retval); s_pamh = 0; return 1; } /* Must do this BEFORE opening a session for pam_limits to count us */ vsf_insert_uwtmp(p_user_str, p_remote_host); retval = pam_open_session(s_pamh, 0); if (retval != PAM_SUCCESS) { vsf_remove_uwtmp(); (void) pam_setcred(s_pamh, PAM_DELETE_CRED); (void) pam_end(s_pamh, retval); s_pamh = 0; return 0; } /* We MUST ensure the PAM session, utmp, wtmp etc. are cleaned up, however * we exit. */ vsf_sysutil_set_exit_func(vsf_auth_shutdown); /* You're in dude */ return 1; }
void str_alloc_ulong(struct mystr* p_str, unsigned long the_long) { str_alloc_text(p_str, vsf_sysutil_ulong_to_str(the_long)); }
void vsf_ls_populate_dir_list(const char* session_user, struct mystr_list* p_list, struct mystr_list* p_subdir_list, 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 dirline_str = INIT_MYSTR; struct mystr normalised_base_dir_str = INIT_MYSTR; struct str_locate_result loc_result; int a_option; int r_option; int t_option; int F_option; int do_stat = 0; long curr_time = 0; loc_result = str_locate_char(p_option_str, 'a'); a_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'r'); r_option = loc_result.found; loc_result = str_locate_char(p_option_str, 't'); t_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'F'); F_option = loc_result.found; loc_result = str_locate_char(p_option_str, 'l'); if (loc_result.found) { is_verbose = 1; } /* Invert "reverse" arg for "-t", the time sorting */ if (t_option) { r_option = !r_option; } if (is_verbose || t_option || F_option || p_subdir_list != 0) { do_stat = 1; } /* If the filter starts with a . then implicitly enable -a */ if (!str_isempty(p_filter_str) && str_get_char_at(p_filter_str, 0) == '.') { a_option = 1; } /* "Normalise" the incoming base directory string by making sure it * ends in a '/' if it is nonempty */ if (!str_equal_text(p_base_dir_str, ".")) { str_copy(&normalised_base_dir_str, p_base_dir_str); } if (!str_isempty(&normalised_base_dir_str)) { unsigned int len = str_getlen(&normalised_base_dir_str); if (str_get_char_at(&normalised_base_dir_str, len - 1) != '/') { str_append_char(&normalised_base_dir_str, '/'); } } /* If we're going to need to do time comparisions, cache the local time */ if (is_verbose) { curr_time = vsf_sysutil_get_time_sec(); } while (1) { static struct mystr s_next_filename_str; static struct mystr s_next_path_and_filename_str; static struct vsf_sysutil_statbuf* s_p_statbuf; str_next_dirent(session_user, str_getbuf(p_base_dir_str), &s_next_filename_str, p_dir); if (!strcmp(str_getbuf(&s_next_filename_str), DENIED_DIR)) continue; if (str_isempty(&s_next_filename_str)) { break; } { unsigned int len = str_getlen(&s_next_filename_str); if (len > 0 && str_get_char_at(&s_next_filename_str, 0) == '.') { if (!a_option && !tunable_force_dot_files) { continue; } if (!a_option && ((len == 2 && str_get_char_at(&s_next_filename_str, 1) == '.') || len == 1)) { continue; } } } /* Don't show hidden directory entries */ if (!vsf_access_check_file_visible(&s_next_filename_str)) { continue; } #if 0 /* If we have an ls option which is a filter, apply it */ if (!str_isempty(p_filter_str)) { unsigned int iters = 0; if (!vsf_filename_passes_filter(&s_next_filename_str, p_filter_str, &iters)) { continue; } } #endif /* Calculate the full path (relative to CWD) for lstat() and * output purposes */ str_copy(&s_next_path_and_filename_str, &normalised_base_dir_str); str_append_str(&s_next_path_and_filename_str, &s_next_filename_str); if (do_stat) { /* lstat() the file. Of course there's a race condition - the * directory entry may have gone away whilst we read it, so * ignore failure to stat */ int retval = str_lstat(&s_next_path_and_filename_str, &s_p_statbuf); if (vsf_sysutil_retval_is_error(retval)) { continue; } } if (is_verbose) { static struct mystr s_final_file_str; /* If it's a damn symlink, we need to append the target */ str_copy(&s_final_file_str, &s_next_filename_str); if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) { static struct mystr s_temp_str; int retval = str_readlink(&s_temp_str, &s_next_path_and_filename_str); if (retval == 0 && !str_isempty(&s_temp_str)) { str_append_text(&s_final_file_str, " -> "); str_append_str(&s_final_file_str, &s_temp_str); } } if (F_option && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_append_char(&s_final_file_str, '/'); } build_dir_line(&dirline_str, &s_final_file_str, s_p_statbuf, curr_time); } else { char *ptr; /* Just emit the filenames - note, we prepend the directory for NLST * but not for LIST */ str_copy(&dirline_str, &s_next_path_and_filename_str); if (F_option) { if (vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_append_char(&dirline_str, '/'); } else if (vsf_sysutil_statbuf_is_symlink(s_p_statbuf)) { str_append_char(&dirline_str, '@'); } } str_append_text(&dirline_str, "\r\n"); ptr = strstr(str_getbuf(&dirline_str), POOL_MOUNT_ROOT); if (ptr != NULL) str_alloc_text(&dirline_str, ptr + strlen(POOL_MOUNT_ROOT)); } /* Add filename into our sorted list - sorting by filename or time. Also, * if we are required to, maintain a distinct list of direct * subdirectories. */ { static struct mystr s_temp_str; const struct mystr* p_sort_str = 0; const struct mystr* p_sort_subdir_str = 0; if (!t_option) { p_sort_str = &s_next_filename_str; } else { str_alloc_text(&s_temp_str, vsf_sysutil_statbuf_get_sortkey_mtime(s_p_statbuf)); p_sort_str = &s_temp_str; p_sort_subdir_str = &s_temp_str; } str_list_add(p_list, &dirline_str, p_sort_str); if (p_subdir_list != 0 && vsf_sysutil_statbuf_is_dir(s_p_statbuf)) { str_list_add(p_subdir_list, &s_next_filename_str, p_sort_subdir_str); } } } /* END: while(1) */ str_list_sort(p_list, r_option); if (p_subdir_list != 0) { str_list_sort(p_subdir_list, r_option); } str_free(&dirline_str); str_free(&normalised_base_dir_str); }
static SSL* get_ssl(struct vsf_session* p_sess, int fd) { SSL* p_ssl = SSL_new(p_sess->p_ssl_ctx); if (p_ssl == NULL) { if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL_new failed"); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } return NULL; } if (!SSL_set_fd(p_ssl, fd)) { if (tunable_debug_ssl) { str_alloc_text(&debug_str, "SSL_set_fd failed"); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } SSL_free(p_ssl); return NULL; } int retval; if (p_sess->is_ssl_client) { /* Connect to a remote FXP server in SSL client mode */ retval = SSL_connect(p_ssl); str_alloc_text(&debug_str, "SSL_connect failed: "); } else { /* Accept a SSL connection from a client or remote FXP server */ retval = SSL_accept(p_ssl); str_alloc_text(&debug_str, "SSL_accept failed: "); } if (retval != 1) { const char* p_err = get_ssl_error(); if (tunable_debug_ssl) { str_append_text(&debug_str, p_err); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } /* The RFC is quite clear that we can just close the control channel * here. */ die(p_err); } if (tunable_debug_ssl) { const char* p_ssl_version = SSL_get_cipher_version(p_ssl); SSL_CIPHER* p_ssl_cipher = SSL_get_current_cipher(p_ssl); const char* p_cipher_name = SSL_CIPHER_get_name(p_ssl_cipher); X509* p_ssl_cert = SSL_get_peer_certificate(p_ssl); int reused = SSL_session_reused(p_ssl); str_alloc_text(&debug_str, "SSL version: "); str_append_text(&debug_str, p_ssl_version); str_append_text(&debug_str, ", SSL cipher: "); str_append_text(&debug_str, p_cipher_name); if (reused) { str_append_text(&debug_str, ", reused"); } else { str_append_text(&debug_str, ", not reused"); } if (p_ssl_cert != NULL) { str_append_text(&debug_str, ", CERT PRESENTED"); X509_free(p_ssl_cert); } else { str_append_text(&debug_str, ", no cert"); } vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } return p_ssl; }
static void build_dir_line(struct mystr* p_str, const struct mystr* p_filename_str, const struct vsf_sysutil_statbuf* p_stat, long curr_time) { static struct mystr s_tmp_str; char *tmp_filename=NULL; filesize_t size = vsf_sysutil_statbuf_get_size(p_stat); /* Permissions */ str_alloc_text(p_str, vsf_sysutil_statbuf_get_perms(p_stat)); str_append_char(p_str, ' '); /* Hard link count */ str_alloc_ulong(&s_tmp_str, vsf_sysutil_statbuf_get_links(p_stat)); str_lpad(&s_tmp_str, 4); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* User */ if (tunable_hide_ids) { str_alloc_text(&s_tmp_str, "ftp"); } else { int uid = vsf_sysutil_statbuf_get_uid(p_stat); struct vsf_sysutil_user* p_user = 0; if (tunable_text_userdb_names) { p_user = vsf_sysutil_getpwuid(uid); } if (p_user == 0) { str_alloc_ulong(&s_tmp_str, (unsigned long) uid); } else { str_alloc_text(&s_tmp_str, vsf_sysutil_user_getname(p_user)); } } str_rpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Group */ if (tunable_hide_ids) { str_alloc_text(&s_tmp_str, "ftp"); } else { int gid = vsf_sysutil_statbuf_get_gid(p_stat); struct vsf_sysutil_group* p_group = 0; if (tunable_text_userdb_names) { p_group = vsf_sysutil_getgrgid(gid); } if (p_group == 0) { str_alloc_ulong(&s_tmp_str, (unsigned long) gid); } else { str_alloc_text(&s_tmp_str, vsf_sysutil_group_getname(p_group)); } } str_rpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Size in bytes */ str_alloc_filesize_t(&s_tmp_str, size); str_lpad(&s_tmp_str, 8); str_append_str(p_str, &s_tmp_str); str_append_char(p_str, ' '); /* Date stamp */ str_append_text(p_str, vsf_sysutil_statbuf_get_date(p_stat, tunable_use_localtime, curr_time)); str_append_char(p_str, ' '); /* Filename */ tmp_filename = local2remote(str_getbuf(p_filename_str)); if (tmp_filename == NULL) str_append_str(p_str, p_filename_str); else { str_append_text(p_str, tmp_filename); vsf_sysutil_free(tmp_filename); } str_append_text(p_str, "\r\n"); }
static void handle_user_command(struct vsf_session* p_sess) { /* SECURITY: If we're in anonymous only-mode, immediately reject * non-anonymous usernames in the hope we save passwords going plaintext * over the network */ int is_anon = 1; str_copy(&p_sess->user_str, &p_sess->ftp_arg_str); str_upper(&p_sess->ftp_arg_str); if (!str_equal_text(&p_sess->ftp_arg_str, "FTP") && !str_equal_text(&p_sess->ftp_arg_str, "ANONYMOUS")) { is_anon = 0; } if (!tunable_local_enable && !is_anon) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "This FTP server is anonymous only."); str_empty(&p_sess->user_str); return; } if (is_anon && p_sess->control_use_ssl && !tunable_allow_anon_ssl && !tunable_force_anon_logins_ssl) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "Anonymous sessions may not use encryption."); str_empty(&p_sess->user_str); return; } if (tunable_ssl_enable && !is_anon && !p_sess->control_use_ssl && tunable_force_local_logins_ssl) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "Non-anonymous sessions must use encryption."); vsf_sysutil_exit(0); } if (tunable_ssl_enable && is_anon && !p_sess->control_use_ssl && tunable_force_anon_logins_ssl) { vsf_cmdio_write( p_sess, FTP_LOGINERR, "Anonymous sessions must use encryption."); vsf_sysutil_exit(0); } if (tunable_userlist_enable) { int located = str_contains_line(&p_sess->userlist_str, &p_sess->user_str); if ((located && tunable_userlist_deny) || (!located && !tunable_userlist_deny)) { check_login_delay(); vsf_cmdio_write(p_sess, FTP_LOGINERR, "Permission denied."); check_login_fails(p_sess); str_empty(&p_sess->user_str); return; } } if (is_anon && tunable_no_anon_password) { /* Fake a password */ str_alloc_text(&p_sess->ftp_arg_str, "<no password>"); handle_pass_command(p_sess); } else { vsf_cmdio_write(p_sess, FTP_GIVEPWORD, "Please specify the password."); } }
static void process_login_req(struct vsf_session* p_sess) { enum EVSFPrivopLoginResult e_login_result = kVSFLoginNull; /* Blocks */ if (priv_sock_get_cmd(p_sess) != PRIV_SOCK_LOGIN) { die("bad request"); } /* Get username and password - we must distrust these */ { struct mystr password_str = INIT_MYSTR; priv_sock_get_str(p_sess, &p_sess->user_str); priv_sock_get_str(p_sess, &password_str); e_login_result = vsf_privop_do_login(p_sess, &password_str); str_free(&password_str); } switch (e_login_result) { case kVSFLoginFail: priv_sock_send_result(p_sess, PRIV_SOCK_RESULT_BAD); return; break; case kVSFLoginAnon: str_alloc_text(&p_sess->user_str, tunable_ftp_username); common_do_login(p_sess, &p_sess->user_str, 1, 1); break; case kVSFLoginReal: { int do_chroot = 0; if (tunable_chroot_local_user) { do_chroot = 1; } if (tunable_chroot_list_enable) { struct mystr chroot_list_file = INIT_MYSTR; int retval = str_fileread(&chroot_list_file, tunable_chroot_list_file, VSFTP_CONF_FILE_MAX); if (vsf_sysutil_retval_is_error(retval)) { die("cannot open chroot() user list file"); } if (str_contains_line(&chroot_list_file, &p_sess->user_str)) { if (do_chroot) { do_chroot = 0; } else { do_chroot = 1; } } str_free(&chroot_list_file); } common_do_login(p_sess, &p_sess->user_str, do_chroot, 0); } break; default: bug("weird state in process_login_request"); break; } /* NOTREACHED */ }
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); } /* Recurse into the subdirectories if required... */ 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) { /* Unreadable, gone missing, etc. - no matter */ 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 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); }
static void vsf_log_do_log_vsftpd_format(struct vsf_session* p_sess, struct mystr* p_str, int succeeded, enum EVSFLogEntryType what, const struct mystr* p_log_str) { /* Date - vsf_sysutil_get_current_date updates cached time */ str_alloc_text(p_str, vsf_sysutil_get_current_date()); /* Pid */ str_append_text(p_str, " [pid "); str_append_ulong(p_str, vsf_sysutil_getpid()); str_append_text(p_str, "] "); /* User */ if (!str_isempty(&p_sess->user_str)) { str_append_char(p_str, '['); str_append_str(p_str, &p_sess->user_str); str_append_text(p_str, "] "); } /* And the action */ if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput && what != kVSFLogEntryConnection) { if (succeeded) { str_append_text(p_str, "OK "); } else { str_append_text(p_str, "FAIL "); } } switch (what) { case kVSFLogEntryDownload: str_append_text(p_str, "DOWNLOAD"); break; case kVSFLogEntryUpload: str_append_text(p_str, "UPLOAD"); break; case kVSFLogEntryMkdir: str_append_text(p_str, "MKDIR"); break; case kVSFLogEntryLogin: str_append_text(p_str, "LOGIN"); break; case kVSFLogEntryFTPInput: str_append_text(p_str, "FTP command"); break; case kVSFLogEntryFTPOutput: str_append_text(p_str, "FTP response"); break; case kVSFLogEntryConnection: str_append_text(p_str, "CONNECT"); break; case kVSFLogEntryDelete: str_append_text(p_str, "DELETE"); break; case kVSFLogEntryRename: str_append_text(p_str, "RENAME"); break; case kVSFLogEntryRmdir: str_append_text(p_str, "RMDIR"); break; case kVSFLogEntryChmod: str_append_text(p_str, "CHMOD"); break; default: bug("bad entry_type in vsf_log_do_log"); break; } str_append_text(p_str, ": Client \""); str_append_str(p_str, &p_sess->remote_ip_str); str_append_char(p_str, '"'); if (what == kVSFLogEntryLogin && !str_isempty(&p_sess->anon_pass_str)) { str_append_text(p_str, ", anon password \""); str_append_str(p_str, &p_sess->anon_pass_str); str_append_char(p_str, '"'); } if (!str_isempty(p_log_str)) { str_append_text(p_str, ", \""); str_append_str(p_str, p_log_str); str_append_char(p_str, '"'); } if (what != kVSFLogEntryFTPInput && what != kVSFLogEntryFTPOutput) { if (p_sess->transfer_size) { str_append_text(p_str, ", "); str_append_filesize_t(p_str, p_sess->transfer_size); str_append_text(p_str, " bytes"); } if (vsf_log_type_is_transfer(what)) { long delta_sec = vsf_sysutil_get_cached_time_sec() - p_sess->log_start_sec; long delta_usec = vsf_sysutil_get_cached_time_usec() - p_sess->log_start_usec; double time_delta = (double) delta_sec + ((double) delta_usec / (double) 1000000); double kbyte_rate = ((double) p_sess->transfer_size / time_delta) / (double) 1024; str_append_text(p_str, ", "); str_append_double(p_str, kbyte_rate); str_append_text(p_str, "Kbyte/sec"); } } }
void process_post_login(struct vsf_session* p_sess) { if (p_sess->is_anonymous) { vsf_sysutil_set_umask(tunable_anon_umask); p_sess->bw_rate_max = tunable_anon_max_rate; } else { vsf_sysutil_set_umask(tunable_local_umask); p_sess->bw_rate_max = tunable_local_max_rate; } if (tunable_async_abor_enable) { vsf_sysutil_install_sighandler(kVSFSysUtilSigURG, handle_sigurg, p_sess); vsf_sysutil_activate_sigurg(VSFTP_COMMAND_FD); } /* Handle any login message */ vsf_banner_dir_changed(p_sess, FTP_LOGINOK); vsf_cmdio_write(p_sess, FTP_LOGINOK, "Login successful."); while(1) { int cmd_ok = 1; if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("IDLE"); } /* Blocks */ vsf_cmdio_get_cmd_and_arg(p_sess, &p_sess->ftp_cmd_str, &p_sess->ftp_arg_str, 1); if (tunable_setproctitle_enable) { struct mystr proctitle_str = INIT_MYSTR; str_copy(&proctitle_str, &p_sess->ftp_cmd_str); if (!str_isempty(&p_sess->ftp_arg_str)) { str_append_char(&proctitle_str, ' '); str_append_str(&proctitle_str, &p_sess->ftp_arg_str); } /* Suggestion from Solar */ str_replace_unprintable(&proctitle_str, '?'); vsf_sysutil_setproctitle_str(&proctitle_str); str_free(&proctitle_str); } /* Test command against the allowed list.. */ if (tunable_cmds_allowed) { static struct mystr s_src_str; static struct mystr s_rhs_str; str_alloc_text(&s_src_str, tunable_cmds_allowed); while (1) { str_split_char(&s_src_str, &s_rhs_str, ','); if (str_isempty(&s_src_str)) { cmd_ok = 0; break; } else if (str_equal(&s_src_str, &p_sess->ftp_cmd_str)) { break; } str_copy(&s_src_str, &s_rhs_str); } } if (!cmd_ok) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "QUIT")) { vsf_cmdio_write(p_sess, FTP_GOODBYE, "Goodbye."); vsf_sysutil_exit(0); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PWD") || str_equal_text(&p_sess->ftp_cmd_str, "XPWD")) { handle_pwd(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "CWD") || str_equal_text(&p_sess->ftp_cmd_str, "XCWD")) { handle_cwd(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "CDUP") || str_equal_text(&p_sess->ftp_cmd_str, "XCUP")) { handle_cdup(p_sess); } else if (tunable_pasv_enable && !p_sess->epsv_all && (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || str_equal_text(&p_sess->ftp_cmd_str, "P@SW"))) { handle_pasv(p_sess, 0); } else if (tunable_pasv_enable && str_equal_text(&p_sess->ftp_cmd_str, "EPSV")) { handle_pasv(p_sess, 1); } else if (tunable_download_enable && str_equal_text(&p_sess->ftp_cmd_str, "RETR")) { handle_retr(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "NOOP")) { vsf_cmdio_write(p_sess, FTP_NOOPOK, "NOOP ok."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SYST")) { vsf_cmdio_write(p_sess, FTP_SYSTOK, "UNIX Type: L8"); } else if (str_equal_text(&p_sess->ftp_cmd_str, "HELP")) { handle_help(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "LIST")) { handle_list(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "TYPE")) { handle_type(p_sess); } else if (tunable_port_enable && !p_sess->epsv_all && str_equal_text(&p_sess->ftp_cmd_str, "PORT")) { handle_port(p_sess); } else if (tunable_write_enable && (tunable_anon_upload_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "STOR")) { handle_stor(p_sess); } else if (tunable_write_enable && (tunable_anon_mkdir_write_enable || !p_sess->is_anonymous) && (str_equal_text(&p_sess->ftp_cmd_str, "MKD") || str_equal_text(&p_sess->ftp_cmd_str, "XMKD"))) { handle_mkd(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && (str_equal_text(&p_sess->ftp_cmd_str, "RMD") || str_equal_text(&p_sess->ftp_cmd_str, "XRMD"))) { handle_rmd(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "DELE")) { handle_dele(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "REST")) { handle_rest(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "RNFR")) { handle_rnfr(p_sess); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "RNTO")) { handle_rnto(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "NLST")) { handle_nlst(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SIZE")) { handle_size(p_sess); } else if (!p_sess->is_anonymous && str_equal_text(&p_sess->ftp_cmd_str, "SITE")) { handle_site(p_sess); } /* Note - the weird ABOR string is checking for an async ABOR arriving * without a SIGURG condition. */ else if (str_equal_text(&p_sess->ftp_cmd_str, "ABOR") || str_equal_text(&p_sess->ftp_cmd_str, "\377\364\377\362ABOR")) { vsf_cmdio_write(p_sess, FTP_ABOR_NOCONN, "No transfer to ABOR."); } else if (tunable_write_enable && (tunable_anon_other_write_enable || !p_sess->is_anonymous) && str_equal_text(&p_sess->ftp_cmd_str, "APPE")) { handle_appe(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "MDTM")) { handle_mdtm(p_sess); } else if (tunable_port_enable && str_equal_text(&p_sess->ftp_cmd_str, "EPRT")) { handle_eprt(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "STRU")) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "F")) { vsf_cmdio_write(p_sess, FTP_STRUOK, "Structure set to F."); } else { vsf_cmdio_write(p_sess, FTP_BADSTRU, "Bad STRU command."); } } else if (str_equal_text(&p_sess->ftp_cmd_str, "MODE")) { str_upper(&p_sess->ftp_arg_str); if (str_equal_text(&p_sess->ftp_arg_str, "S")) { vsf_cmdio_write(p_sess, FTP_MODEOK, "Mode set to S."); } else { vsf_cmdio_write(p_sess, FTP_BADMODE, "Bad MODE command."); } } else if (str_equal_text(&p_sess->ftp_cmd_str, "STOU")) { handle_stou(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "ALLO")) { vsf_cmdio_write(p_sess, FTP_ALLOOK, "ALLO command ignored."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "REIN")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "REIN not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "ACCT")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "ACCT not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "SMNT")) { vsf_cmdio_write(p_sess, FTP_COMMANDNOTIMPL, "SMNT not implemented."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "FEAT")) { vsf_cmdio_write_hyphen(p_sess, FTP_FEAT, "Features:"); vsf_cmdio_write_raw(p_sess, " MDTM\r\n"); vsf_cmdio_write_raw(p_sess, " REST STREAM\r\n"); vsf_cmdio_write_raw(p_sess, " SIZE\r\n"); vsf_cmdio_write(p_sess, FTP_FEAT, "End"); } else if (str_equal_text(&p_sess->ftp_cmd_str, "OPTS")) { vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood."); } else if (str_equal_text(&p_sess->ftp_cmd_str, "STAT") && str_isempty(&p_sess->ftp_arg_str)) { handle_stat(p_sess); } else if (tunable_dirlist_enable && str_equal_text(&p_sess->ftp_cmd_str, "STAT")) { handle_stat_file(p_sess); } else if (str_equal_text(&p_sess->ftp_cmd_str, "PASV") || str_equal_text(&p_sess->ftp_cmd_str, "PORT") || str_equal_text(&p_sess->ftp_cmd_str, "STOR") || str_equal_text(&p_sess->ftp_cmd_str, "MKD") || str_equal_text(&p_sess->ftp_cmd_str, "XMKD") || str_equal_text(&p_sess->ftp_cmd_str, "RMD") || str_equal_text(&p_sess->ftp_cmd_str, "XRMD") || str_equal_text(&p_sess->ftp_cmd_str, "DELE") || str_equal_text(&p_sess->ftp_cmd_str, "RNFR") || str_equal_text(&p_sess->ftp_cmd_str, "RNTO") || str_equal_text(&p_sess->ftp_cmd_str, "SITE") || str_equal_text(&p_sess->ftp_cmd_str, "APPE") || str_equal_text(&p_sess->ftp_cmd_str, "EPSV") || str_equal_text(&p_sess->ftp_cmd_str, "EPRT") || str_equal_text(&p_sess->ftp_cmd_str, "RETR") || str_equal_text(&p_sess->ftp_cmd_str, "LIST") || str_equal_text(&p_sess->ftp_cmd_str, "NLST") || str_equal_text(&p_sess->ftp_cmd_str, "STOU") || str_equal_text(&p_sess->ftp_cmd_str, "ALLO") || str_equal_text(&p_sess->ftp_cmd_str, "REIN") || str_equal_text(&p_sess->ftp_cmd_str, "ACCT") || str_equal_text(&p_sess->ftp_cmd_str, "SMNT") || str_equal_text(&p_sess->ftp_cmd_str, "FEAT") || str_equal_text(&p_sess->ftp_cmd_str, "OPTS") || str_equal_text(&p_sess->ftp_cmd_str, "STAT")) { vsf_cmdio_write(p_sess, FTP_NOPERM, "Permission denied."); } else { vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); } } }
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_dir_common(struct vsf_session* p_sess, int full_details) { static struct mystr s_option_str; static struct mystr s_filter_str; static struct mystr s_dir_name_str; static struct vsf_sysutil_statbuf* s_p_dirstat; int remote_fd; int dir_allow_read = 1; struct vsf_sysutil_dir* p_dir = 0; str_empty(&s_option_str); str_empty(&s_filter_str); /* By default open the current directory */ str_alloc_text(&s_dir_name_str, "."); if (!pasv_active(p_sess) && !port_active(p_sess)) { vsf_cmdio_write(p_sess, FTP_BADSENDCONN, "Use PORT or PASV first."); return; } /* Do we have an option? Going to be strict here - the option must come * first. e.g. "ls -a .." fine, "ls .. -a" not fine */ if (!str_isempty(&p_sess->ftp_arg_str) && str_get_char_at(&p_sess->ftp_arg_str, 0) == '-') { /* Chop off the '-' */ str_mid_to_end(&p_sess->ftp_arg_str, &s_option_str, 1); /* A space will separate options from filter (if any) */ str_split_char(&s_option_str, &s_filter_str, ' '); } else { /* The argument, if any, is just a filter */ str_copy(&s_filter_str, &p_sess->ftp_arg_str); } if (!str_isempty(&s_filter_str)) { /* First check - is it an outright directory, as in "ls /pub" */ p_dir = str_opendir(&s_filter_str); if (p_dir != 0) { /* Listing a directory! */ str_copy(&s_dir_name_str, &s_filter_str); str_free(&s_filter_str); } else { struct str_locate_result locate_result = str_locate_char(&s_filter_str, '/'); if (locate_result.found) { /* Includes a path! Reverse scan for / in the arg, to get the * base directory and filter (if any) */ str_copy(&s_dir_name_str, &s_filter_str); str_split_char_reverse(&s_dir_name_str, &s_filter_str, '/'); /* If we have e.g. "ls /.message", we just ripped off the leading * slash because it is the only one! */ if (str_isempty(&s_dir_name_str)) { str_alloc_text(&s_dir_name_str, "/"); } } } } if (p_dir == 0) { /* NOTE - failure check done below, it's not forgotten */ p_dir = str_opendir(&s_dir_name_str); } /* Fine, do it */ remote_fd = get_remote_transfer_fd(p_sess); if (vsf_sysutil_retval_is_error(remote_fd)) { goto dir_close_out; } vsf_cmdio_write(p_sess, FTP_DATACONN, "Here comes the directory listing."); if (p_sess->is_anonymous && p_dir && tunable_anon_world_readable_only) { vsf_sysutil_dir_stat(p_dir, &s_p_dirstat); if (!vsf_sysutil_statbuf_is_readable_other(s_p_dirstat)) { dir_allow_read = 0; } } if (p_dir == 0 || !dir_allow_read) { vsf_cmdio_write(p_sess, FTP_TRANSFEROK, "Transfer done (but failed to open directory)."); } else { (void) vsf_ftpdataio_transfer_dir(p_sess, remote_fd, p_dir, &s_dir_name_str, &s_option_str, &s_filter_str, full_details); } (void) dispose_remote_transfer_fd(p_sess); dir_close_out: if (p_dir) { vsf_sysutil_closedir(p_dir); } port_cleanup(p_sess); pasv_cleanup(p_sess); }
static void common_do_login(struct vsf_session* p_sess, const struct mystr* p_user_str, int do_chroot, int anon) { int was_anon = anon; const struct mystr* p_orig_user_str = p_user_str; int newpid; vsf_sysutil_install_null_sighandler(kVSFSysUtilSigCHLD); /* Tells the pre-login child all is OK (it may exit in response) */ priv_sock_send_result(p_sess->parent_fd, PRIV_SOCK_RESULT_OK); if (!p_sess->control_use_ssl) { (void) vsf_sysutil_wait(); } else { p_sess->ssl_slave_active = 1; } /* Absorb the SIGCHLD */ vsf_sysutil_unblock_sig(kVSFSysUtilSigCHLD); /* Handle loading per-user config options */ handle_per_user_config(p_user_str); /* Set this before we fork */ p_sess->is_anonymous = anon; vsf_sysutil_install_async_sighandler(kVSFSysUtilSigCHLD, twoproc_handle_sigchld); newpid = vsf_sysutil_fork(); if (newpid == 0) { struct mystr guest_user_str = INIT_MYSTR; struct mystr chroot_str = INIT_MYSTR; struct mystr chdir_str = INIT_MYSTR; struct mystr userdir_str = INIT_MYSTR; unsigned int secutil_option = VSF_SECUTIL_OPTION_USE_GROUPS; /* Child - drop privs and start proper FTP! */ vsf_sysutil_close(p_sess->parent_fd); if (tunable_ssl_enable) { vsf_sysutil_close(p_sess->ssl_slave_fd); } if (tunable_guest_enable && !anon) { /* Remap to the guest user */ str_alloc_text(&guest_user_str, tunable_guest_username); p_user_str = &guest_user_str; if (!tunable_virtual_use_local_privs) { anon = 1; do_chroot = 1; } } if (do_chroot) { secutil_option |= VSF_SECUTIL_OPTION_CHROOT; } if (!anon) { secutil_option |= VSF_SECUTIL_OPTION_CHANGE_EUID; } calculate_chdir_dir(was_anon, &userdir_str, &chroot_str, &chdir_str, p_user_str, p_orig_user_str); vsf_secutil_change_credentials(p_user_str, &userdir_str, &chroot_str, 0, secutil_option); if (!str_isempty(&chdir_str)) { (void) str_chdir(&chdir_str); } str_free(&guest_user_str); str_free(&chroot_str); str_free(&chdir_str); str_free(&userdir_str); /* Guard against the config error of having the anonymous ftp tree owned * by the user we are running as */ if (was_anon && vsf_sysutil_write_access("/")) { die("vsftpd: refusing to run with writable anonymous root"); } p_sess->is_anonymous = anon; process_post_login(p_sess); bug("should not get here: common_do_login"); } /* Parent */ if (tunable_ssl_enable) { vsf_sysutil_close(p_sess->ssl_consumer_fd); /* Keep the SSL slave fd around so we can shutdown() upon exit */ } vsf_priv_parent_postlogin(p_sess); bug("should not get here in common_do_login"); }