393 lines
11 KiB
C
393 lines
11 KiB
C
/*
|
|
* 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);
|
|
int port = 0;
|
|
if (vsf_sysutil_is_port_reserved(remote_port))
|
|
{
|
|
die("Illegal port request");
|
|
}
|
|
if (tunable_connect_from_port_20)
|
|
{
|
|
port = 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 = 0;
|
|
/* 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 (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);
|
|
|
|
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;
|
|
}
|
|
|
|
while (--bind_retries)
|
|
{
|
|
int retval;
|
|
double scaled_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))
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|