1
0
This repository has been archived on 2024-07-22. You can view files and clone it, but cannot push or open issues or pull requests.
2024-07-22 01:58:46 -03:00

314 lines
7.5 KiB
C

/*
* Part of Very Secure FTPd
* Licence: GPL v2
* Author: Chris Evans
* standalone.c
*
* Code to listen on the network and launch children servants.
*/
#include "standalone.h"
#include "parseconf.h"
#include "tunables.h"
#include "sysutil.h"
#include "sysdeputil.h"
#include "utility.h"
#include "defs.h"
#include "hash.h"
#include "str.h"
#include "ipaddrparse.h"
static unsigned int s_children;
static struct hash* s_p_ip_count_hash;
static struct hash* s_p_pid_ip_hash;
static unsigned int s_ipaddr_size;
static void handle_sigchld(void* duff);
static void handle_sighup(void* duff);
static void prepare_child(int sockfd);
static unsigned int handle_ip_count(void* p_raw_addr);
static void drop_ip_count(void* p_raw_addr);
static unsigned int hash_ip(unsigned int buckets, void* p_key);
static unsigned int hash_pid(unsigned int buckets, void* p_key);
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);
}
/* Son, close standard FDs to avoid SSH hang-on-exit */
vsf_sysutil_reopen_standard_fds();
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_sighandler(kVSFSysUtilSigCHLD, handle_sigchld, 0, 1);
vsf_sysutil_install_sighandler(kVSFSysUtilSigHUP, handle_sighup, 0, 1);
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");
}
}
retval = vsf_sysutil_listen(listen_sock, VSFTP_LISTEN_BACKLOG);
if (vsf_sysutil_retval_is_error(retval))
{
die("could not listen");
}
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;
new_client_sock = vsf_sysutil_accept_timeout(
listen_sock, p_accept_addr, 0);
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);
if (tunable_isolate)
{
if (tunable_http_enable && tunable_isolate_network)
{
new_child = vsf_sysutil_fork_isolate_all_failok();
}
else
{
new_child = vsf_sysutil_fork_isolate_failok();
}
}
else
{
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_set_die_if_parent_dies();
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;
}
}
}
static void
prepare_child(int new_client_sock)
{
/* We must satisfy the contract: command socket on fd 0, 1, 2 */
vsf_sysutil_dupfd2(new_client_sock, 0);
vsf_sysutil_dupfd2(new_client_sock, 1);
vsf_sysutil_dupfd2(new_client_sock, 2);
if (new_client_sock > 2)
{
vsf_sysutil_close(new_client_sock);
}
}
static void
drop_ip_count(void* p_raw_addr)
{
unsigned int count;
unsigned int* p_count =
(unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_raw_addr);
if (!p_count)
{
bug("IP address missing from hash");
}
count = *p_count;
if (!count)
{
bug("zero count for IP address");
}
count--;
*p_count = count;
if (!count)
{
hash_free_entry(s_p_ip_count_hash, p_raw_addr);
}
}
static void
handle_sigchld(void* duff)
{
unsigned int reap_one = 1;
(void) duff;
while (reap_one)
{
reap_one = (unsigned int)vsf_sysutil_wait_reap_one();
if (reap_one)
{
struct vsf_sysutil_ipaddr* p_ip;
/* Account total number of instances */
--s_children;
/* Account per-IP limit */
p_ip = (struct vsf_sysutil_ipaddr*)
hash_lookup_entry(s_p_pid_ip_hash, (void*)&reap_one);
drop_ip_count(p_ip);
hash_free_entry(s_p_pid_ip_hash, (void*)&reap_one);
}
}
}
static void
handle_sighup(void* duff)
{
(void) duff;
/* We don't crash the out the listener if an invalid config was added */
tunables_load_defaults();
vsf_parseconf_load_file(0, 0);
}
static unsigned int
hash_ip(unsigned int buckets, void* p_key)
{
const unsigned char* p_raw_ip = (const unsigned char*)p_key;
unsigned int val = 0;
int shift = 24;
unsigned int i;
for (i = 0; i < s_ipaddr_size; ++i)
{
val ^= p_raw_ip[i] << shift;
shift -= 8;
if (shift < 0)
{
shift = 24;
}
}
return val % buckets;
}
static unsigned int
hash_pid(unsigned int buckets, void* p_key)
{
unsigned int* p_pid = (unsigned int*)p_key;
return (*p_pid) % buckets;
}
static unsigned int
handle_ip_count(void* p_ipaddr)
{
unsigned int* p_count =
(unsigned int*)hash_lookup_entry(s_p_ip_count_hash, p_ipaddr);
unsigned int count;
if (!p_count)
{
count = 1;
hash_add_entry(s_p_ip_count_hash, p_ipaddr, (void*)&count);
}
else
{
count = *p_count;
count++;
*p_count = count;
}
return count;
}