/* XXX - really, this should be refactored into a "buffered writer" object */ static int write_dir_list(struct vsf_session* p_sess, struct mystr_list* p_dir_list, enum EVSFRWTarget target) { /* This function writes out a list of strings to the client, over the * data socket. We now coalesce the strings into fewer write() syscalls, * which saved 33% CPU time writing a large directory. */ int retval = 0; unsigned int dir_index_max = str_list_get_length(p_dir_list); unsigned int dir_index; struct mystr buf_str = INIT_MYSTR; str_reserve(&buf_str, VSFTP_DIR_BUFSIZE); for (dir_index = 0; dir_index < dir_index_max; dir_index++) { str_append_str(&buf_str, str_list_get_pstr(p_dir_list, dir_index)); if (dir_index == dir_index_max - 1 || str_getlen(&buf_str) + str_getlen(str_list_get_pstr(p_dir_list, dir_index + 1)) > VSFTP_DIR_BUFSIZE) { /* Writeout needed - we're either at the end, or we filled the buffer */ int writeret = ftp_write_str(p_sess, &buf_str, target); if (writeret != 0) { retval = 1; break; } str_empty(&buf_str); } } str_free(&buf_str); return retval; }
static enum EVSFPrivopLoginResult handle_login(struct vsf_session* p_sess, const struct mystr* p_user_str, const struct mystr* p_pass_str) { /* Do not assume PAM can cope with dodgy input, even though it * almost certainly can. */ int anonymous_login = 0; unsigned int len = str_getlen(p_user_str); if (len == 0 || len > VSFTP_USERNAME_MAX) { return kVSFLoginFail; } /* Throw out dodgy start characters */ if (!vsf_sysutil_isalnum(str_get_char_at(p_user_str, 0))) { return kVSFLoginFail; } /* Throw out non-printable characters and space in username */ if (str_contains_space(p_user_str) || str_contains_unprintable(p_user_str)) { return kVSFLoginFail; } /* Throw out excessive length passwords */ len = str_getlen(p_pass_str); if (len > VSFTP_PASSWORD_MAX) { return kVSFLoginFail; } /* Check for an anonymous login or "real" login */ if (tunable_anonymous_enable) { struct mystr upper_str = INIT_MYSTR; str_copy(&upper_str, p_user_str); str_upper(&upper_str); if (str_equal_text(&upper_str, "FTP") || str_equal_text(&upper_str, "ANONYMOUS")) { anonymous_login = 1; } str_free(&upper_str); } { enum EVSFPrivopLoginResult result = kVSFLoginFail; if (anonymous_login) { result = handle_anonymous_login(p_sess, p_pass_str); } else { if (!tunable_local_enable) { die("unexpected local login in handle_login"); } result = handle_local_login(p_sess, p_user_str, p_pass_str); } return result; } }
void vsf_sysutil_setproctitle_internal(const char* p_buf) { struct mystr proctitle_str = INIT_MYSTR; unsigned int to_copy; if (!s_proctitle_inited) { bug("vsf_sysutil_setproctitle: not initialized"); } vsf_sysutil_memclr(s_p_proctitle, s_proctitle_space); if (s_proctitle_space < 32) { return; } str_alloc_text(&proctitle_str, "vsftpd: "); str_append_text(&proctitle_str, p_buf); to_copy = str_getlen(&proctitle_str); if (to_copy > s_proctitle_space - 1) { to_copy = s_proctitle_space - 1; } vsf_sysutil_memcpy(s_p_proctitle, str_getbuf(&proctitle_str), to_copy); str_free(&proctitle_str); s_p_proctitle[to_copy] = '\0'; }
static int str_netfd_write_common(const struct mystr* p_str, int fd, int noblock) { int ret = 0; int retval; unsigned int str_len = str_getlen(p_str); if (str_len == 0) { bug("zero str_len in str_netfd_write_common"); } if (noblock) { vsf_sysutil_activate_noblock(fd); } retval = str_write_loop(p_str, fd); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != str_len) { ret = -1; } if (noblock) { vsf_sysutil_deactivate_noblock(fd); } return ret; }
static int ipv6_parse_hex(struct mystr* p_out_str, const struct mystr* p_in_str) { unsigned int len = str_getlen(p_in_str); unsigned int i; unsigned int val = 0; for (i=0; i<len; ++i) { int ch = vsf_sysutil_toupper(str_get_char_at(p_in_str, i)); if (ch >= '0' && ch <= '9') { ch -= '0'; } else if (ch >= 'A' && ch <= 'F') { ch -= 'A'; ch += 10; } else { return 0; } val <<= 4; val |= ch; if (val > 0xFFFF) { return 0; } } str_append_char(p_out_str, (val >> 8)); str_append_char(p_out_str, (val & 0xFF)); return 1; }
static int control_getline(struct mystr* p_str, struct vsf_session* p_sess) { int ret; if (p_sess->p_control_line_buf == 0) { vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE); } ret = ftp_getline(p_sess, p_str, p_sess->p_control_line_buf); if (ret == 0) { return ret; } else if (ret < 0) { vsf_cmdio_write_exit(p_sess, FTP_BADCMD, "Input line too long.", 1); } /* As mandated by the FTP specifications.. */ str_replace_char(p_str, '\0', '\n'); /* If the last character is a \r, strip it */ { unsigned int len = str_getlen(p_str); while (len > 0 && str_get_char_at(p_str, len - 1) == '\r') { str_trunc(p_str, len - 1); --len; } } return 1; }
int str_getline(const struct mystr* p_str, struct mystr* p_line_str, unsigned int* p_pos) { unsigned int start_pos = *p_pos; unsigned int curr_pos = start_pos; unsigned int buf_len = str_getlen(p_str); const char* p_buf = str_getbuf(p_str); unsigned int out_len; if (start_pos > buf_len) { bug("p_pos out of range in str_getline"); } str_empty(p_line_str); if (start_pos == buf_len) { return 0; } while (curr_pos < buf_len && p_buf[curr_pos] != '\n') { curr_pos++; } out_len = curr_pos - start_pos; /* If we ended on a \n - skip it */ if (curr_pos < buf_len && p_buf[curr_pos] == '\n') { curr_pos++; } private_str_alloc_memchunk(p_line_str, p_buf + start_pos, out_len); *p_pos = curr_pos; return 1; }
int ssl_write_str(void* p_ssl, const struct mystr* p_str) { unsigned int len = str_getlen(p_str); int ret = SSL_write((SSL*) p_ssl, str_getbuf(p_str), len); if ((unsigned int) ret != len) { return -1; } return 0; }
int ssl_handshake(struct vsf_session* p_sess, int fd) { /* SECURITY: data SSL connections don't have any auth on them as part of the * protocol. If a client sends an unfortunately optional client cert then * we can check for a match between the control and data connections. */ SSL* p_ssl; int reused; if (p_sess->p_data_ssl != NULL) { die("p_data_ssl should be NULL."); } /* Initiate the SSL connection by either calling accept or connect */ p_ssl = get_ssl(p_sess, fd); if (p_ssl == NULL) { return 0; } p_sess->p_data_ssl = p_ssl; setup_bio_callbacks(p_ssl); reused = SSL_session_reused(p_ssl); if (tunable_require_ssl_reuse && !reused) { str_alloc_text(&debug_str, "No SSL session reuse on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (str_getlen(&p_sess->control_cert_digest) > 0) { static struct mystr data_cert_digest; if (!ssl_cert_digest(p_ssl, p_sess, &data_cert_digest)) { str_alloc_text(&debug_str, "Missing cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (str_strcmp(&p_sess->control_cert_digest, &data_cert_digest)) { str_alloc_text(&debug_str, "DIFFERENT cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); ssl_data_close(p_sess); return 0; } if (tunable_debug_ssl) { str_alloc_text(&debug_str, "Matching cert on data channel."); vsf_log_line(p_sess, kVSFLogEntryDebug, &debug_str); } } return 1; }
static int ipv4_parse_dotquad(struct mystr* p_out_str, const struct mystr* p_in_str) { unsigned int len = str_getlen(p_in_str); unsigned int i; unsigned int val = 0; unsigned int final_val = 0; int seen_char = 0; int dots = 0; for (i=0; i<len; ++i) { int ch = str_get_char_at(p_in_str, i); if (ch == '.') { if (!seen_char || dots == 3) { return 0; } seen_char = 0; dots++; final_val <<= 8; final_val |= val; val = 0; } else if (ch >= '0' && ch <= '9') { ch -= '0'; val *= 10; val += ch; if (val > 255) { return 0; } seen_char = 1; } else { return 0; } } if (dots != 3 || !seen_char) { return 0; } final_val <<= 8; final_val |= val; str_append_char(p_out_str, (final_val >> 24)); str_append_char(p_out_str, ((final_val >> 16) & 0xFF)); str_append_char(p_out_str, ((final_val >> 8) & 0xFF)); str_append_char(p_out_str, (final_val & 0xFF)); return 1; }
const unsigned char* vsf_sysutil_parse_ipv6(const struct mystr* p_str) { static struct mystr s_ret; static struct mystr s_rhs_ret; static struct mystr s_lhs_str; static struct mystr s_rhs_str; unsigned int lhs_len; unsigned int rhs_len; str_empty(&s_ret); str_empty(&s_rhs_ret); str_copy(&s_lhs_str, p_str); str_split_text(&s_lhs_str, &s_rhs_str, "::"); if (!ipv6_parse_main(&s_ret, &s_lhs_str)) { return 0; } if (!ipv6_parse_main(&s_rhs_ret, &s_rhs_str)) { return 0; } lhs_len = str_getlen(&s_ret); rhs_len = str_getlen(&s_rhs_ret); if (lhs_len + rhs_len > 16) { return 0; } if (rhs_len > 0) { unsigned int add_nulls = 16 - (lhs_len + rhs_len); while (add_nulls--) { str_append_char(&s_ret, '\0'); } str_append_str(&s_ret, &s_rhs_ret); } return (const unsigned char*) str_getbuf(&s_ret); }
void str_replace_text(struct mystr* p_str, const char* p_from, const char* p_to) { static struct mystr s_lhs_chunk_str; static struct mystr s_rhs_chunk_str; unsigned int lhs_len; str_copy(&s_lhs_chunk_str, p_str); str_free(p_str); do { lhs_len = str_getlen(&s_lhs_chunk_str); str_split_text(&s_lhs_chunk_str, &s_rhs_chunk_str, p_from); /* Copy lhs to destination */ str_append_str(p_str, &s_lhs_chunk_str); /* If this was a 'hit', append the 'to' text */ if (str_getlen(&s_lhs_chunk_str) < lhs_len) { str_append_text(p_str, p_to); } /* Current rhs becomes new lhs */ str_copy(&s_lhs_chunk_str, &s_rhs_chunk_str); } while (!str_isempty(&s_lhs_chunk_str)); }
int ssl_read_into_str(struct vsf_session* p_sess, void* p_ssl, struct mystr* p_str) { unsigned int len = str_getlen(p_str); int ret = ssl_read(p_sess, p_ssl, (char*) str_getbuf(p_str), len); if (ret >= 0) { str_trunc(p_str, (unsigned int) ret); } else { str_empty(p_str); } return ret; }
int ftp_write_str(const struct vsf_session* p_sess, const struct mystr* p_str, enum EVSFRWTarget target) { if (target == kVSFRWData) { if (p_sess->data_use_ssl && p_sess->ssl_slave_active) { int ret = -1; int written; priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_DO_SSL_WRITE); priv_sock_send_str(p_sess->ssl_consumer_fd, p_str); written = priv_sock_get_int(p_sess->ssl_consumer_fd); if (written > 0 && written == (int) str_getlen(p_str)) { ret = 0; } return ret; } else if (p_sess->data_use_ssl) { return ssl_write_str(p_sess->p_data_ssl, p_str); } else { return str_netfd_write(p_str, p_sess->data_fd); } } else { if (p_sess->control_use_ssl && p_sess->ssl_slave_active) { priv_sock_send_cmd(p_sess->ssl_consumer_fd, PRIV_SOCK_WRITE_USER_RESP); priv_sock_send_str(p_sess->ssl_consumer_fd, p_str); return priv_sock_get_int(p_sess->ssl_consumer_fd); } else if (p_sess->control_use_ssl) { return ssl_write_str(p_sess->p_control_ssl, p_str); } else { return str_netfd_write(p_str, VSFTP_COMMAND_FD); } } }
int str_netfd_write(const struct mystr* p_str, int fd) { int ret = 0; int retval; unsigned int str_len = str_getlen(p_str); if (str_len == 0) { bug("zero str_len in str_netfd_write"); } retval = str_write_loop(p_str, fd); if (vsf_sysutil_retval_is_error(retval) || (unsigned int) retval != str_len) { ret = -1; } return ret; }
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) { /* Prepare an alarm to timeout the session.. */ if (set_alarm) { vsf_cmdio_set_alarm(p_sess); } /* Blocks */ control_getline(p_cmd_str, p_sess); /* 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 (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 control_getline(struct mystr* p_str, struct vsf_session* p_sess) { if (p_sess->p_control_line_buf == 0) { vsf_secbuf_alloc(&p_sess->p_control_line_buf, VSFTP_MAX_COMMAND_LINE); } ftp_getline(p_sess, p_str, p_sess->p_control_line_buf); /* As mandated by the FTP specifications.. */ str_replace_char(p_str, '\0', '\n'); /* If the last character is a \r, strip it */ { unsigned int len = str_getlen(p_str); while (len > 0 && str_get_char_at(p_str, len - 1) == '\r') { str_trunc(p_str, len - 1); --len; } } }
static void prepend_path_to_filename(struct mystr* p_str) { static struct mystr s_tmp_str; /* Only prepend current working directory if the incoming filename is * relative */ str_empty(&s_tmp_str); if (str_isempty(p_str) || str_get_char_at(p_str, 0) != '/') { str_getcwd(&s_tmp_str); /* Careful to not emit // if we are in directory / (common with chroot) */ if (str_isempty(&s_tmp_str) || str_get_char_at(&s_tmp_str, str_getlen(&s_tmp_str) - 1) != '/') { str_append_char(&s_tmp_str, '/'); } } str_append_str(&s_tmp_str, p_str); str_copy(p_str, &s_tmp_str); }
static void ftp_getline(struct mystr* p_str) { static char* s_p_readline_buf; if (s_p_readline_buf == 0) { vsf_secbuf_alloc(&s_p_readline_buf, VSFTP_MAX_COMMAND_LINE); } str_netfd_alloc(p_str, VSFTP_COMMAND_FD, '\n', s_p_readline_buf, VSFTP_MAX_COMMAND_LINE); /* As mandated by the FTP specifications.. */ str_replace_char(p_str, '\0', '\n'); /* If the last character is a \r, strip it */ { unsigned int len = str_getlen(p_str); if (len > 0 && str_get_char_at(p_str, len - 1) == '\r') { str_trunc(p_str, len - 1); } } }
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, VSF_PROJECT ":"); 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(); #ifdef VSF_SYSDEP_HAVE_UPDWTMPX updwtmpx(WTMPX_FILE, &s_utent); #endif }
static void handle_sigurg(void* p_private) { struct mystr async_cmd_str = INIT_MYSTR; struct mystr async_arg_str = INIT_MYSTR; struct mystr real_cmd_str = INIT_MYSTR; unsigned int len; struct vsf_session* p_sess = (struct vsf_session*) p_private; /* Did stupid client sent something OOB without a data connection? */ if (p_sess->data_fd == -1) { return; } /* Get the async command - blocks (use data timeout alarm) */ vsf_cmdio_get_cmd_and_arg(p_sess, &async_cmd_str, &async_arg_str, 0); /* Chop off first four characters; they are telnet characters. The client * should have sent the first two normally and the second two as urgent * data. */ len = str_getlen(&async_cmd_str); if (len >= 4) { str_right(&async_cmd_str, &real_cmd_str, len - 4); } if (str_equal_text(&real_cmd_str, "ABOR")) { p_sess->abor_received = 1; vsf_sysutil_shutdown_failok(p_sess->data_fd); } else { /* Sorry! */ vsf_cmdio_write(p_sess, FTP_BADCMD, "Unknown command."); } str_free(&async_cmd_str); str_free(&async_arg_str); str_free(&real_cmd_str); }
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); }
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); } }
int str_write_loop(const struct mystr* p_str, const int fd) { return vsf_sysutil_write_loop(fd, str_getbuf(p_str), str_getlen(p_str)); }
int vsf_filename_passes_filter(const struct mystr* p_filename_str, const struct mystr* p_filter_str, unsigned int* iters) { /* A simple routine to match a filename against a pattern. * This routine is used instead of e.g. fnmatch(3), because we should be * reluctant to trust the latter. fnmatch(3) involves _lots_ of string * parsing and handling. There is broad potential for any given fnmatch(3) * implementation to be buggy. * * Currently supported pattern(s): * - any number of wildcards, "*" or "?" * - {,} syntax (not nested) * * Note that pattern matching is only supported within the last path * component. For example, searching for /a/b/? will work, but searching * for /a/?/c will not. */ struct mystr filter_remain_str = INIT_MYSTR; struct mystr name_remain_str = INIT_MYSTR; struct mystr temp_str = INIT_MYSTR; struct mystr brace_list_str = INIT_MYSTR; struct mystr new_filter_str = INIT_MYSTR; int ret = 0; char last_token = 0; int must_match_at_current_pos = 1; str_copy(&filter_remain_str, p_filter_str); str_copy(&name_remain_str, p_filename_str); while (!str_isempty(&filter_remain_str) && *iters < VSFTP_MATCHITERS_MAX) { static struct mystr s_match_needed_str; /* Locate next special token */ struct str_locate_result locate_result = str_locate_chars(&filter_remain_str, "*?{"); (*iters)++; /* Isolate text leading up to token (if any) - needs to be matched */ if (locate_result.found) { unsigned int indexx = locate_result.index; str_left(&filter_remain_str, &s_match_needed_str, indexx); str_mid_to_end(&filter_remain_str, &temp_str, indexx + 1); str_copy(&filter_remain_str, &temp_str); last_token = locate_result.char_found; } else { /* No more tokens. Must match remaining filter string exactly. */ str_copy(&s_match_needed_str, &filter_remain_str); str_empty(&filter_remain_str); last_token = 0; } if (!str_isempty(&s_match_needed_str)) { /* Need to match something.. could be a match which has to start at * current position, or we could allow it to start anywhere */ unsigned int indexx; locate_result = str_locate_str(&name_remain_str, &s_match_needed_str); if (!locate_result.found) { /* Fail */ goto out; } indexx = locate_result.index; if (must_match_at_current_pos && indexx > 0) { goto out; } /* Chop matched string out of remainder */ str_mid_to_end(&name_remain_str, &temp_str, indexx + str_getlen(&s_match_needed_str)); str_copy(&name_remain_str, &temp_str); } if (last_token == '?') { if (str_isempty(&name_remain_str)) { goto out; } str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); str_copy(&name_remain_str, &temp_str); must_match_at_current_pos = 1; } else if (last_token == '{') { struct str_locate_result end_brace = str_locate_char(&filter_remain_str, '}'); must_match_at_current_pos = 1; if (end_brace.found) { str_split_char(&filter_remain_str, &temp_str, '}'); str_copy(&brace_list_str, &filter_remain_str); str_copy(&filter_remain_str, &temp_str); str_split_char(&brace_list_str, &temp_str, ','); while (!str_isempty(&brace_list_str)) { str_copy(&new_filter_str, &brace_list_str); str_append_str(&new_filter_str, &filter_remain_str); if (vsf_filename_passes_filter(&name_remain_str, &new_filter_str, iters)) { ret = 1; goto out; } str_copy(&brace_list_str, &temp_str); str_split_char(&brace_list_str, &temp_str, ','); } goto out; } else if (str_isempty(&name_remain_str) || str_get_char_at(&name_remain_str, 0) != '{') { goto out; } else { str_right(&name_remain_str, &temp_str, str_getlen(&name_remain_str) - 1); str_copy(&name_remain_str, &temp_str); } } else { must_match_at_current_pos = 0; } } /* Any incoming string left means no match unless we ended on the correct * type of wildcard. */ if (str_getlen(&name_remain_str) > 0 && last_token != '*') { goto out; } /* OK, a match */ ret = 1; if (*iters == VSFTP_MATCHITERS_MAX) { ret = 0; } out: str_free(&filter_remain_str); str_free(&name_remain_str); str_free(&temp_str); str_free(&brace_list_str); str_free(&new_filter_str); return ret; }
void ssl_slave(struct vsf_session* p_sess) { struct mystr data_str = INIT_MYSTR; str_reserve(&data_str, VSFTP_DATA_BUFSIZE); /* Before becoming the slave, clear the alarm for the FTP protocol. */ vsf_sysutil_clear_alarm(); /* No need for any further communications with the privileged parent. */ priv_sock_set_parent_context(p_sess); if (tunable_setproctitle_enable) { vsf_sysutil_setproctitle("SSL handler"); } while (1) { char cmd = priv_sock_get_cmd(p_sess->ssl_slave_fd); int ret; if (cmd == PRIV_SOCK_GET_USER_CMD) { ret = ftp_getline(p_sess, &p_sess->ftp_cmd_str, p_sess->p_control_line_buf); priv_sock_send_int(p_sess->ssl_slave_fd, ret); if (ret >= 0) { priv_sock_send_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); } } else if (cmd == PRIV_SOCK_WRITE_USER_RESP) { priv_sock_get_str(p_sess->ssl_slave_fd, &p_sess->ftp_cmd_str); ret = ftp_write_str(p_sess, &p_sess->ftp_cmd_str, kVSFRWControl); priv_sock_send_int(p_sess->ssl_slave_fd, ret); } else if (cmd == PRIV_SOCK_DO_SSL_HANDSHAKE) { char result = PRIV_SOCK_RESULT_BAD; if (p_sess->data_fd != -1 || p_sess->p_data_ssl != 0) { bug("state not clean"); } p_sess->data_fd = priv_sock_recv_fd(p_sess->ssl_slave_fd); ret = ssl_accept(p_sess, p_sess->data_fd); if (ret == 1) { result = PRIV_SOCK_RESULT_OK; } else { vsf_sysutil_close(p_sess->data_fd); p_sess->data_fd = -1; } priv_sock_send_result(p_sess->ssl_slave_fd, result); } else if (cmd == PRIV_SOCK_DO_SSL_READ) { str_trunc(&data_str, VSFTP_DATA_BUFSIZE); ret = ssl_read_into_str(p_sess, p_sess->p_data_ssl, &data_str); priv_sock_send_int(p_sess->ssl_slave_fd, ret); priv_sock_send_str(p_sess->ssl_slave_fd, &data_str); } else if (cmd == PRIV_SOCK_DO_SSL_WRITE) { priv_sock_get_str(p_sess->ssl_slave_fd, &data_str); ret = ssl_write(p_sess->p_data_ssl, str_getbuf(&data_str), str_getlen(&data_str)); priv_sock_send_int(p_sess->ssl_slave_fd, ret); } else if (cmd == PRIV_SOCK_DO_SSL_CLOSE) { char result = PRIV_SOCK_RESULT_BAD; if (p_sess->data_fd == -1 && p_sess->p_data_ssl == 0) { result = PRIV_SOCK_RESULT_OK; } else { ret = ssl_data_close(p_sess); if (ret == 1) { result = PRIV_SOCK_RESULT_OK; } vsf_sysutil_close(p_sess->data_fd); p_sess->data_fd = -1; } priv_sock_send_result(p_sess->ssl_slave_fd, result); } else { die("bad request in process_ssl_slave_req"); } } }