123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409 |
- /*
- * Part of Very Secure FTPd
- * License: GPL v2
- * Author: Chris Evans
- * privops.c
- *
- * Code implementing the privileged operations that the unprivileged client
- * might request.
- * Look for suitable paranoia in this file.
- */
- #include "privops.h"
- #include "session.h"
- #include "sysdeputil.h"
- #include "sysutil.h"
- #include "utility.h"
- #include "str.h"
- #include "tunables.h"
- #include "defs.h"
- #include "logging.h"
- /* File private functions */
- static enum EVSFPrivopLoginResult handle_anonymous_login(
- struct vsf_session* p_sess, const struct mystr* p_pass_str);
- static enum EVSFPrivopLoginResult handle_local_login(
- struct vsf_session* p_sess, struct mystr* p_user_str,
- const struct mystr* p_pass_str);
- static void setup_username_globals(struct vsf_session* p_sess,
- const struct mystr* p_str);
- static enum EVSFPrivopLoginResult handle_login(
- struct vsf_session* p_sess, struct mystr* p_user_str,
- const struct mystr* p_pass_str);
- int
- vsf_privop_get_ftp_port_sock(struct vsf_session* p_sess,
- unsigned short remote_port,
- int use_port_sockaddr)
- {
- static struct vsf_sysutil_sockaddr* p_sockaddr;
- const struct vsf_sysutil_sockaddr* p_connect_to;
- int retval;
- int i;
- int s = vsf_sysutil_get_ipsock(p_sess->p_local_addr);
- unsigned short port = 0;
- if (p_sess->pasv_listen_fd != -1)
- {
- die("listed fd is active?");
- }
- if (vsf_sysutil_is_port_reserved(remote_port))
- {
- die("Illegal port request");
- }
- if (tunable_connect_from_port_20)
- {
- port = (unsigned short) tunable_ftp_data_port;
- }
- vsf_sysutil_activate_reuseaddr(s);
- /* A report of failure here on Solaris, presumably buggy address reuse
- * support? We'll retry.
- */
- for (i = 0; i < 2; ++i)
- {
- double sleep_for;
- vsf_sysutil_sockaddr_clone(&p_sockaddr, p_sess->p_local_addr);
- vsf_sysutil_sockaddr_set_port(p_sockaddr, port);
- retval = vsf_sysutil_bind(s, p_sockaddr);
- if (retval == 0)
- {
- break;
- }
- if (vsf_sysutil_get_error() != kVSFSysUtilErrADDRINUSE || i == 1)
- {
- die("vsf_sysutil_bind");
- }
- sleep_for = vsf_sysutil_get_random_byte();
- sleep_for /= 256.0;
- sleep_for += 1.0;
- vsf_sysutil_sleep(sleep_for);
- }
- if (use_port_sockaddr)
- {
- p_connect_to = p_sess->p_port_sockaddr;
- }
- else
- {
- vsf_sysutil_sockaddr_set_port(p_sess->p_remote_addr, remote_port);
- p_connect_to = p_sess->p_remote_addr;
- }
- retval = vsf_sysutil_connect_timeout(s, p_connect_to,
- tunable_connect_timeout);
- if (vsf_sysutil_retval_is_error(retval))
- {
- vsf_sysutil_close(s);
- s = -1;
- }
- return s;
- }
- void
- vsf_privop_pasv_cleanup(struct vsf_session* p_sess)
- {
- if (p_sess->pasv_listen_fd != -1)
- {
- vsf_sysutil_close(p_sess->pasv_listen_fd);
- p_sess->pasv_listen_fd = -1;
- }
- }
- int
- vsf_privop_pasv_active(struct vsf_session* p_sess)
- {
- if (p_sess->pasv_listen_fd != -1)
- {
- return 1;
- }
- return 0;
- }
- unsigned short
- vsf_privop_pasv_listen(struct vsf_session* p_sess)
- {
- static struct vsf_sysutil_sockaddr* s_p_sockaddr;
- int bind_retries = 10;
- unsigned short the_port;
- /* IPPORT_RESERVED */
- unsigned short min_port = 1024;
- unsigned short max_port = 65535;
- int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr);
- if (p_sess->pasv_listen_fd != -1)
- {
- die("listed fd already active");
- }
- if (tunable_pasv_min_port > min_port && tunable_pasv_min_port <= max_port)
- {
- min_port = (unsigned short) tunable_pasv_min_port;
- }
- if (tunable_pasv_max_port >= min_port && tunable_pasv_max_port < max_port)
- {
- max_port = (unsigned short) tunable_pasv_max_port;
- }
- while (--bind_retries)
- {
- int retval;
- double scaled_port;
- the_port = vsf_sysutil_get_random_byte();
- the_port = (unsigned short) (the_port << 8);
- the_port = (unsigned short) (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;
- if (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);
- 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))
- {
- retval = vsf_sysutil_listen(p_sess->pasv_listen_fd, 1);
- if (!vsf_sysutil_retval_is_error(retval))
- {
- break;
- }
- }
- /* SELinux systems can give you an inopportune EACCES, it seems. */
- if (vsf_sysutil_get_error() == kVSFSysUtilErrADDRINUSE ||
- vsf_sysutil_get_error() == kVSFSysUtilErrACCES)
- {
- vsf_sysutil_close(p_sess->pasv_listen_fd);
- p_sess->pasv_listen_fd = -1;
- continue;
- }
- die("vsf_sysutil_bind / listen");
- }
- if (!bind_retries)
- {
- die("vsf_sysutil_bind");
- }
- return the_port;
- }
- int
- vsf_privop_accept_pasv(struct vsf_session* p_sess)
- {
- struct vsf_sysutil_sockaddr* p_accept_addr = 0;
- int remote_fd;
- if (p_sess->pasv_listen_fd == -1)
- {
- die("listed fd not active");
- }
- vsf_sysutil_sockaddr_alloc(&p_accept_addr);
- remote_fd = vsf_sysutil_accept_timeout(p_sess->pasv_listen_fd, p_accept_addr,
- tunable_accept_timeout);
- if (vsf_sysutil_retval_is_error(remote_fd))
- {
- vsf_sysutil_sockaddr_clear(&p_accept_addr);
- return -1;
- }
- /* SECURITY:
- * Reject the connection if it wasn't from the same IP as the
- * control connection.
- */
- if (!tunable_pasv_promiscuous)
- {
- if (!vsf_sysutil_sockaddr_addr_equal(p_sess->p_remote_addr, p_accept_addr))
- {
- vsf_sysutil_close(remote_fd);
- vsf_sysutil_sockaddr_clear(&p_accept_addr);
- return -2;
- }
- }
- vsf_sysutil_sockaddr_clear(&p_accept_addr);
- return remote_fd;
- }
- void
- vsf_privop_do_file_chown(struct vsf_session* p_sess, int fd)
- {
- static struct vsf_sysutil_statbuf* s_p_statbuf;
- vsf_sysutil_fstat(fd, &s_p_statbuf);
- /* Do nothing if it is already owned by the desired user. */
- if (vsf_sysutil_statbuf_get_uid(s_p_statbuf) ==
- p_sess->anon_upload_chown_uid)
- {
- return;
- }
- /* Drop it like a hot potato unless it's a regular file owned by
- * the the anonymous ftp user
- */
- if (p_sess->anon_upload_chown_uid == -1 ||
- !vsf_sysutil_statbuf_is_regfile(s_p_statbuf) ||
- (vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->anon_ftp_uid &&
- vsf_sysutil_statbuf_get_uid(s_p_statbuf) != p_sess->guest_user_uid))
- {
- die("invalid fd in cmd_process_chown");
- }
- /* SECURITY! You need an OS which strips SUID/SGID bits on chown(),
- * otherwise a compromise of the FTP user will lead to compromise of
- * the "anon_upload_chown_uid" user (think chmod +s).
- */
- vsf_sysutil_fchown(fd, p_sess->anon_upload_chown_uid, -1);
- }
- enum EVSFPrivopLoginResult
- vsf_privop_do_login(struct vsf_session* p_sess,
- const struct mystr* p_pass_str)
- {
- enum EVSFPrivopLoginResult result =
- handle_login(p_sess, &p_sess->user_str, p_pass_str);
- vsf_log_start_entry(p_sess, kVSFLogEntryLogin);
- if (result == kVSFLoginFail)
- {
- vsf_log_do_log(p_sess, 0);
- if (tunable_delay_failed_login)
- {
- vsf_sysutil_sleep((double) tunable_delay_failed_login);
- }
- }
- else
- {
- vsf_log_do_log(p_sess, 1);
- if (tunable_delay_successful_login)
- {
- vsf_sysutil_sleep((double) tunable_delay_successful_login);
- }
- }
- return result;
- }
- static enum EVSFPrivopLoginResult
- handle_login(struct vsf_session* p_sess, 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;
- char first_char;
- unsigned int len = str_getlen(p_user_str);
- if (len == 0 || len > VSFTP_USERNAME_MAX)
- {
- return kVSFLoginFail;
- }
- /* Throw out dodgy start characters */
- first_char = str_get_char_at(p_user_str, 0);
- if (!vsf_sysutil_isalnum(first_char) &&
- first_char != '_' &&
- first_char != '.')
- {
- 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;
- }
- }
- static enum EVSFPrivopLoginResult
- handle_anonymous_login(struct vsf_session* p_sess,
- const struct mystr* p_pass_str)
- {
- if (!str_isempty(&p_sess->banned_email_str) &&
- str_contains_line(&p_sess->banned_email_str, p_pass_str))
- {
- return kVSFLoginFail;
- }
- if (!str_isempty(&p_sess->email_passwords_str) &&
- (!str_contains_line(&p_sess->email_passwords_str, p_pass_str) ||
- str_isempty(p_pass_str)))
- {
- return kVSFLoginFail;
- }
- /* Store the anonymous identity string */
- str_copy(&p_sess->anon_pass_str, p_pass_str);
- if (str_isempty(&p_sess->anon_pass_str))
- {
- str_alloc_text(&p_sess->anon_pass_str, "?");
- }
- /* "Fix" any characters which might upset the log processing */
- str_replace_char(&p_sess->anon_pass_str, ' ', '_');
- str_replace_char(&p_sess->anon_pass_str, '\n', '?');
- {
- struct mystr ftp_username_str = INIT_MYSTR;
- if (tunable_ftp_username)
- {
- str_alloc_text(&ftp_username_str, tunable_ftp_username);
- }
- setup_username_globals(p_sess, &ftp_username_str);
- str_free(&ftp_username_str);
- }
- str_free(&p_sess->banned_email_str);
- str_free(&p_sess->email_passwords_str);
- return kVSFLoginAnon;
- }
- static enum EVSFPrivopLoginResult
- handle_local_login(struct vsf_session* p_sess,
- struct mystr* p_user_str,
- const struct mystr* p_pass_str)
- {
- if (!vsf_sysdep_check_auth(p_user_str, p_pass_str, &p_sess->remote_ip_str))
- {
- return kVSFLoginFail;
- }
- setup_username_globals(p_sess, p_user_str);
- return kVSFLoginReal;
- }
- static void
- setup_username_globals(struct vsf_session* p_sess, const struct mystr* p_str)
- {
- str_copy(&p_sess->user_str, p_str);
- if (tunable_setproctitle_enable)
- {
- struct mystr prefix_str = INIT_MYSTR;
- str_copy(&prefix_str, &p_sess->remote_ip_str);
- str_append_char(&prefix_str, '/');
- str_append_str(&prefix_str, p_str);
- vsf_sysutil_set_proctitle_prefix(&prefix_str);
- str_free(&prefix_str);
- }
- }
|