tinyproxy/src/child.c
Michael Adam 8845bdbff7 BB#110 secure the hashmaps by adding a seed
Based on a patch provided by gpernot@praksys.org on bugzilla.

Signed-off-by: Michael Adam <obnox@samba.org>
(cherry picked from commit 308305d827)
2014-12-13 01:48:41 +01:00

602 lines
19 KiB
C

/* tinyproxy - A fast light-weight HTTP proxy
* Copyright (C) 2000 Robert James Kaes <rjkaes@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Handles the creation/destruction of the various children required for
* processing incoming connections.
*/
#include "main.h"
#include "child.h"
#include "daemon.h"
#include "filter.h"
#include "heap.h"
#include "log.h"
#include "reqs.h"
#include "sock.h"
#include "utils.h"
#include "conf.h"
static vector_t listen_fds;
/*
* Stores the internal data needed for each child (connection)
*/
enum child_status_t { T_EMPTY, T_WAITING, T_CONNECTED };
struct child_s {
pid_t tid;
unsigned int connects;
enum child_status_t status;
};
/*
* A pointer to an array of children. A certain number of children are
* created when the program is started.
*/
static struct child_s *child_ptr;
static struct child_config_s {
unsigned int maxclients, maxrequestsperchild;
unsigned int maxspareservers, minspareservers, startservers;
} child_config;
static unsigned int *servers_waiting; /* servers waiting for a connection */
/*
* Lock/Unlock the "servers_waiting" variable so that two children cannot
* modify it at the same time.
*/
#define SERVER_COUNT_LOCK() _child_lock_wait()
#define SERVER_COUNT_UNLOCK() _child_lock_release()
/* START OF LOCKING SECTION */
/*
* These variables are required for the locking mechanism. Also included
* are the "private" functions for locking/unlocking.
*/
static struct flock lock_it, unlock_it;
static int lock_fd = -1;
static void _child_lock_init (void)
{
char lock_file[] = "/tmp/tinyproxy.servers.lock.XXXXXX";
/* Only allow u+rw bits. This may be required for some versions
* of glibc so that mkstemp() doesn't make us vulnerable.
*/
umask (0177);
lock_fd = mkstemp (lock_file);
unlink (lock_file);
lock_it.l_type = F_WRLCK;
lock_it.l_whence = SEEK_SET;
lock_it.l_start = 0;
lock_it.l_len = 0;
unlock_it.l_type = F_UNLCK;
unlock_it.l_whence = SEEK_SET;
unlock_it.l_start = 0;
unlock_it.l_len = 0;
}
static void _child_lock_wait (void)
{
int rc;
while ((rc = fcntl (lock_fd, F_SETLKW, &lock_it)) < 0) {
if (errno == EINTR)
continue;
else
return;
}
}
static void _child_lock_release (void)
{
if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0)
return;
}
/* END OF LOCKING SECTION */
#define SERVER_INC() do { \
SERVER_COUNT_LOCK(); \
++(*servers_waiting); \
DEBUG2("INC: servers_waiting: %d", *servers_waiting); \
SERVER_COUNT_UNLOCK(); \
} while (0)
#define SERVER_DEC() do { \
SERVER_COUNT_LOCK(); \
assert(*servers_waiting > 0); \
--(*servers_waiting); \
DEBUG2("DEC: servers_waiting: %d", *servers_waiting); \
SERVER_COUNT_UNLOCK(); \
} while (0)
/*
* Set the configuration values for the various child related settings.
*/
short int child_configure (child_config_t type, unsigned int val)
{
switch (type) {
case CHILD_MAXCLIENTS:
child_config.maxclients = val;
break;
case CHILD_MAXSPARESERVERS:
child_config.maxspareservers = val;
break;
case CHILD_MINSPARESERVERS:
child_config.minspareservers = val;
break;
case CHILD_STARTSERVERS:
child_config.startservers = val;
break;
case CHILD_MAXREQUESTSPERCHILD:
child_config.maxrequestsperchild = val;
break;
default:
DEBUG2 ("Invalid type (%d)", type);
return -1;
}
return 0;
}
/**
* child signal handler for sighup
*/
static void child_sighup_handler (int sig)
{
if (sig == SIGHUP) {
/*
* Ignore the return value of reload_config for now.
* This should actually be handled somehow...
*/
reload_config ();
#ifdef FILTER_ENABLE
filter_reload ();
#endif /* FILTER_ENABLE */
}
}
/*
* This is the main (per child) loop.
*/
static void child_main (struct child_s *ptr)
{
int connfd;
struct sockaddr *cliaddr;
socklen_t clilen;
fd_set rfds;
int maxfd = 0;
ssize_t i;
int ret;
cliaddr = (struct sockaddr *)
safemalloc (sizeof(struct sockaddr_storage));
if (!cliaddr) {
log_message (LOG_CRIT,
"Could not allocate memory for child address.");
exit (0);
}
ptr->connects = 0;
srand(time(NULL));
/*
* We have to wait for connections on multiple fds,
* so use select.
*/
FD_ZERO(&rfds);
for (i = 0; i < vector_length(listen_fds); i++) {
int *fd = (int *) vector_getentry(listen_fds, i, NULL);
ret = socket_nonblocking(*fd);
if (ret != 0) {
log_message(LOG_ERR, "Failed to set the listening "
"socket %d to non-blocking: %s",
fd, strerror(errno));
exit(1);
}
FD_SET(*fd, &rfds);
maxfd = max(maxfd, *fd);
}
while (!config.quit) {
int listenfd = -1;
ptr->status = T_WAITING;
clilen = sizeof(struct sockaddr_storage);
ret = select(maxfd + 1, &rfds, NULL, NULL, NULL);
if (ret == -1) {
log_message (LOG_ERR, "error calling select: %s",
strerror(errno));
exit(1);
} else if (ret == 0) {
log_message (LOG_WARNING, "Strange: select returned 0 "
"but we did not specify a timeout...");
continue;
}
for (i = 0; i < vector_length(listen_fds); i++) {
int *fd = (int *) vector_getentry(listen_fds, i, NULL);
if (FD_ISSET(*fd, &rfds)) {
/*
* only accept the connection on the first
* fd that we find readable. - fair?
*/
listenfd = *fd;
break;
}
}
if (listenfd == -1) {
log_message(LOG_WARNING, "Strange: None of our listen "
"fds was readable after select");
continue;
}
ret = socket_blocking(listenfd);
if (ret != 0) {
log_message(LOG_ERR, "Failed to set listening "
"socket %d to blocking for accept: %s",
listenfd, strerror(errno));
exit(1);
}
/*
* We have a socket that is readable.
* Continue handling this connection.
*/
connfd = accept (listenfd, cliaddr, &clilen);
#ifndef NDEBUG
/*
* Enable the TINYPROXY_DEBUG environment variable if you
* want to use the GDB debugger.
*/
if (getenv ("TINYPROXY_DEBUG")) {
/* Pause for 10 seconds to allow us to connect debugger */
fprintf (stderr,
"Process has accepted connection: %ld\n",
(long int) ptr->tid);
sleep (10);
fprintf (stderr, "Continuing process: %ld\n",
(long int) ptr->tid);
}
#endif
/*
* Make sure no error occurred...
*/
if (connfd < 0) {
log_message (LOG_ERR,
"Accept returned an error (%s) ... retrying.",
strerror (errno));
continue;
}
ptr->status = T_CONNECTED;
SERVER_DEC ();
handle_connection (connfd);
ptr->connects++;
if (child_config.maxrequestsperchild != 0) {
DEBUG2 ("%u connections so far...", ptr->connects);
if (ptr->connects == child_config.maxrequestsperchild) {
log_message (LOG_NOTICE,
"Child has reached MaxRequestsPerChild (%u). "
"Killing child.", ptr->connects);
break;
}
}
SERVER_COUNT_LOCK ();
if (*servers_waiting > child_config.maxspareservers) {
/*
* There are too many spare children, kill ourself
* off.
*/
log_message (LOG_NOTICE,
"Waiting servers (%d) exceeds MaxSpareServers (%d). "
"Killing child.",
*servers_waiting,
child_config.maxspareservers);
SERVER_COUNT_UNLOCK ();
break;
} else {
SERVER_COUNT_UNLOCK ();
}
SERVER_INC ();
}
ptr->status = T_EMPTY;
safefree (cliaddr);
exit (0);
}
/*
* Fork a child "child" (or in our case a process) and then start up the
* child_main() function.
*/
static pid_t child_make (struct child_s *ptr)
{
pid_t pid;
if ((pid = fork ()) > 0)
return pid; /* parent */
/*
* Reset the SIGNALS so that the child can be reaped.
*/
set_signal_handler (SIGCHLD, SIG_DFL);
set_signal_handler (SIGTERM, SIG_DFL);
set_signal_handler (SIGHUP, child_sighup_handler);
child_main (ptr); /* never returns */
return -1;
}
/*
* Create a pool of children to handle incoming connections
*/
short int child_pool_create (void)
{
unsigned int i;
/*
* Make sure the number of MaxClients is not zero, since this
* variable determines the size of the array created for children
* later on.
*/
if (child_config.maxclients == 0) {
log_message (LOG_ERR,
"child_pool_create: \"MaxClients\" must be "
"greater than zero.");
return -1;
}
if (child_config.startservers == 0) {
log_message (LOG_ERR,
"child_pool_create: \"StartServers\" must be "
"greater than zero.");
return -1;
}
child_ptr =
(struct child_s *) calloc_shared_memory (child_config.maxclients,
sizeof (struct child_s));
if (!child_ptr) {
log_message (LOG_ERR,
"Could not allocate memory for children.");
return -1;
}
servers_waiting =
(unsigned int *) malloc_shared_memory (sizeof (unsigned int));
if (servers_waiting == MAP_FAILED) {
log_message (LOG_ERR,
"Could not allocate memory for child counting.");
return -1;
}
*servers_waiting = 0;
/*
* Create a "locking" file for use around the servers_waiting
* variable.
*/
_child_lock_init ();
if (child_config.startservers > child_config.maxclients) {
log_message (LOG_WARNING,
"Can not start more than \"MaxClients\" servers. "
"Starting %u servers instead.",
child_config.maxclients);
child_config.startservers = child_config.maxclients;
}
for (i = 0; i != child_config.maxclients; i++) {
child_ptr[i].status = T_EMPTY;
child_ptr[i].connects = 0;
}
for (i = 0; i != child_config.startservers; i++) {
DEBUG2 ("Trying to create child %d of %d", i + 1,
child_config.startservers);
child_ptr[i].status = T_WAITING;
child_ptr[i].tid = child_make (&child_ptr[i]);
if (child_ptr[i].tid < 0) {
log_message (LOG_WARNING,
"Could not create child number %d of %d",
i, child_config.startservers);
return -1;
} else {
log_message (LOG_INFO,
"Creating child number %d of %d ...",
i + 1, child_config.startservers);
SERVER_INC ();
}
}
log_message (LOG_INFO, "Finished creating all children.");
return 0;
}
/*
* Keep the proper number of servers running. This is the birth of the
* servers. It monitors this at least once a second.
*/
void child_main_loop (void)
{
unsigned int i;
while (1) {
if (config.quit)
return;
/* If there are not enough spare servers, create more */
SERVER_COUNT_LOCK ();
if (*servers_waiting < child_config.minspareservers) {
log_message (LOG_NOTICE,
"Waiting servers (%d) is less than MinSpareServers (%d). "
"Creating new child.",
*servers_waiting,
child_config.minspareservers);
SERVER_COUNT_UNLOCK ();
for (i = 0; i != child_config.maxclients; i++) {
if (child_ptr[i].status == T_EMPTY) {
child_ptr[i].status = T_WAITING;
child_ptr[i].tid =
child_make (&child_ptr[i]);
if (child_ptr[i].tid < 0) {
log_message (LOG_NOTICE,
"Could not create child");
child_ptr[i].status = T_EMPTY;
break;
}
SERVER_INC ();
break;
}
}
} else {
SERVER_COUNT_UNLOCK ();
}
sleep (5);
/* Handle log rotation if it was requested */
if (received_sighup) {
/*
* Ignore the return value of reload_config for now.
* This should actually be handled somehow...
*/
reload_config ();
#ifdef FILTER_ENABLE
filter_reload ();
#endif /* FILTER_ENABLE */
/* propagate filter reload to all children */
child_kill_children (SIGHUP);
received_sighup = FALSE;
}
}
}
/*
* Go through all the non-empty children and cancel them.
*/
void child_kill_children (int sig)
{
unsigned int i;
for (i = 0; i != child_config.maxclients; i++) {
if (child_ptr[i].status != T_EMPTY)
kill (child_ptr[i].tid, sig);
}
}
/**
* Listen on the various configured interfaces
*/
int child_listening_sockets(vector_t listen_addrs, uint16_t port)
{
int ret;
ssize_t i;
assert (port > 0);
if (listen_fds == NULL) {
listen_fds = vector_create();
if (listen_fds == NULL) {
log_message (LOG_ERR, "Could not create the list "
"of listening fds");
return -1;
}
}
if ((listen_addrs == NULL) ||
(vector_length(listen_addrs) == 0))
{
/*
* no Listen directive:
* listen on the wildcard address(es)
*/
ret = listen_sock(NULL, port, listen_fds);
return ret;
}
for (i = 0; i < vector_length(listen_addrs); i++) {
const char *addr;
addr = (char *)vector_getentry(listen_addrs, i, NULL);
if (addr == NULL) {
log_message(LOG_WARNING,
"got NULL from listen_addrs - skipping");
continue;
}
ret = listen_sock(addr, port, listen_fds);
if (ret != 0) {
return ret;
}
}
return 0;
}
void child_close_sock (void)
{
ssize_t i;
for (i = 0; i < vector_length(listen_fds); i++) {
int *fd = (int *) vector_getentry(listen_fds, i, NULL);
close (*fd);
}
vector_delete(listen_fds);
listen_fds = NULL;
}