/*
* $Id: ftp-daemon.c,v 1.4 2002/01/14 19:31:14 mt Exp $
*
* Functions for the FTP Proxy daemon mode
*
* Author(s): Jens-Gero Boehm <jens-gero.boehm@suse.de>
* Pieter Hollants <pieter.hollants@suse.de>
* Marius Tomaschewski <mt@suse.de>
* Volker Wiegand <volker.wiegand@suse.de>
*
* This file is part of the SuSE Proxy Suite
* See also http://proxy-suite.suse.de/
*
* 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* A history log can be found at the end of this file.
*/
#ifndef lint
static char rcsid[] = "$Id: ftp-daemon.c,v 1.4 2002/01/14 19:31:14 mt Exp $";
#endif
#include <config.h>
#if defined(STDC_HEADERS)
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <stdarg.h>
# include <errno.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#if defined(TIME_WITH_SYS_TIME)
# include <sys/time.h>
# include <time.h>
#else
# if defined(HAVE_SYS_TIME_H)
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#include <sys/types.h>
#include <sys/stat.h>
#if defined(HAVE_FCNTL_H)
# include <fcntl.h>
#elif defined(HAVE_SYS_FCNTL_H)
# include <sys/fcntl.h>
#endif
#include <signal.h>
#if defined(HAVE_SYS_WAIT_H)
# include <sys/wait.h>
#endif
#if !defined(WEXITSTATUS)
# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
#endif
#if !defined(WIFEXITED)
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
#if defined(HAVE_PATHS_H)
# include <paths.h>
#endif
#if !defined(_PATH_DEVNULL)
# define _PATH_DEVNULL "/dev/null"
#endif
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include "com-config.h"
#include "com-debug.h"
#include "com-misc.h"
#include "com-socket.h"
#include "com-syslog.h"
#include "ftp-client.h"
#include "ftp-daemon.h"
#include "ftp-main.h"
/* ------------------------------------------------------------ */
#define MAX_CLIENTS 512 /* Max. concurrent user limit */
#define LISTEN_WAIT 30 /* Wait up to 30sec for listen */
#define FORK_INTERVAL 60 /* Interval for ForkLimit */
#define MAX_FORKS 40 /* Default fork-resource-limit */
typedef struct {
pid_t pid; /* Proc-id of child (0=empty) */
char peer[PEER_LEN]; /* Dotted decimal IP address */
} CLIENT;
/* ------------------------------------------------------------ */
static RETSIGTYPE daemon_signal(int signo);
static void daemon_cleanup(void);
/* ------------------------------------------------------------ */
static int initflag = 0; /* Have we been initialized? */
static pid_t daemon_pid = 0; /* Daemon PID for cleanups, ... */
static time_t last_slice = 0; /* Last time slice with clients */
static int last_count = 0; /* Clients in last_slice */
static CLIENT clients[MAX_CLIENTS];
/* ------------------------------------------------------------ **
**
** Function......: daemon_signal
**
** Parameters....: signo Signal to be handled
**
** Return........: (none)
**
** Purpose.......: Handler for signals, mainly waiting.
**
** ------------------------------------------------------------ */
static RETSIGTYPE daemon_signal(int signo)
{
int tmperr = errno; /* Save errno for later */
pid_t pid;
int i, status;
CLIENT *clp;
#if defined(HAVE_WAITPID)
while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
#elif defined(HAVE_WAIT3)
while ((pid = wait3(&status, WNOHANG, NULL)) > 0)
#else
if ((pid = wait(&status)) > 0)
#endif
{
for (i = 0, clp = clients; i < MAX_CLIENTS; i++, clp++) {
if (clp->pid == pid) {
clp->pid = (pid_t) 0;
#if defined(COMPILE_DEBUG)
debug(1, "client pid=%d (%s) gone",
(int) pid, clp->peer);
#endif
memset(clp->peer, 0, PEER_LEN);
break;
}
}
}
signal(signo, daemon_signal);
errno = tmperr; /* Restore errno */
#if RETSIGTYPE != void
return 0;
#endif
}
/* ------------------------------------------------------------ **
**
** Function......: detach_signal
**
** Parameters....: signo Signal to be handled
**
** Return........: (none)
**
** Purpose.......: private signal handler to return proper
** exit status back to the shell about the
** initialization of the detached child in
** daemon_init function.
**
** ------------------------------------------------------------ */
static RETSIGTYPE detach_signal(int signo)
{
switch(signo) {
case SIGHUP:
/*
** initialization succeed
*/
exit(EXIT_SUCCESS);
break;
case SIGCHLD:
/*
** initialization failure
*/
exit(EXIT_FAILURE);
break;
}
#if RETSIGTYPE != void
return 0;
#endif
}
/* ------------------------------------------------------------ **
**
** Function......: daemon_init
**
** Parameters....: detach Detach from controlling
** terminal if set
**
** Return........: (none)
**
** Purpose.......: Initialize the FTP daemon functions.
**
** ------------------------------------------------------------ */
void daemon_init(int detach)
{
u_int32_t laddr;
u_int16_t lport;
pid_t oldpid;
char *p;
int i;
/*
** Cleanup the client array
*/
for (i = 0; i < MAX_CLIENTS; i++) {
clients[i].pid = (pid_t) 0;
memset(clients[i].peer, 0, PEER_LEN);
}
/*
** 1. STEP: Fork, if requested
*/
oldpid = getpid();
if (detach) {
pid_t pid;
/*
** set detach init status signals
*/
signal(SIGHUP, detach_signal);
signal(SIGCHLD, detach_signal);
pid = fork();
switch (pid) {
case -1:
syslog_error("can't fork daemon");
exit(EXIT_FAILURE);
break;
case 0:
/******** child ********/
/*
** forget this status signal
*/
signal(SIGHUP, SIG_DFL);
break;
default:
/******** parent ********/
#if defined(COMPILE_DEBUG)
debug_forget();
#endif
/*
** wait for client init completed;
** see 2. STEP of detach bellow...
*/
sleep(10);
/* huh?! kill the naughty child! */
kill(pid, SIGTERM);
syslog_error("can't detach daemon");
exit(EXIT_FAILURE);
break;
}
#if defined(COMPILE_DEBUG)
debug(2, "fork: PID %d --> %d",
(int) oldpid, (int) getpid());
#endif
}
/*
** The initial fork (if any) is done, prepare for exit
*/
daemon_pid = getpid();
if (initflag == 0) {
atexit(daemon_cleanup);
initflag = 1;
}
/*
** Open a listening socket
*/
laddr = config_addr(NULL, "Listen", (u_int32_t) INADDR_ANY);
lport = config_port(NULL, "Port", (u_int16_t) IPPORT_FTP);
for (i = 0; i < MAX_RETRIES; i++) {
if (socket_listen(laddr, lport, daemon_accept) == 0)
break;
sleep(LISTEN_WAIT);
}
if (i >= MAX_RETRIES) {
syslog_error("can't bind daemon to %d", (int) lport);
exit(EXIT_FAILURE);
}
/*
** Install the signal handler
*/
signal(SIGCHLD, daemon_signal);
/*
** Create a PID-File if requested
*/
misc_pidfile(config_str(NULL, "PidFile", NULL));
/*
** Change root directory
*/
if(0 == misc_chroot(config_str(NULL, "ServerRoot", NULL))) {
struct stat st;
/*
** dump config file into the chroot
** only if it does not exists there
*/
if(stat(config_filename(), &st)) {
FILE *out;
int fd;
fd = open(config_filename(),
O_WRONLY|O_CREAT, 0644);
if(-1 != fd && (out = fdopen(fd, "w"))) {
config_dump(out);
fflush(out);
fclose(out);
} else {
syslog_error(
"can't write config file into chroot");
if(-1 != fd)
close(fd);
exit(EXIT_FAILURE);
}
}
}
/*
** singal parent about successfull init;
** we can still report errors to stderr,
** but are unable to send a signal to
** parent after we've dropped the UID...
*/
if(detach) {
kill(oldpid, SIGHUP);
}
/*
** Change (drop) user- and group-id if requested
*/
misc_uidgid(CONFIG_UID, CONFIG_GID);
/*
** Open the log if requested
*/
if ((p = config_str(NULL, "LogDestination", NULL)) != NULL)
syslog_open(p, config_str(NULL, "LogLevel", NULL));
else syslog_close();
/*
** 2. STEP: Detach from controlling terminal, if requested
*/
if(detach) {
freopen(_PATH_DEVNULL, "r", stdin);
freopen(_PATH_DEVNULL, "w", stdout);
freopen(_PATH_DEVNULL, "w", stderr);
chdir("/");
#if defined(HAVE_SETSID)
setsid();
#endif
}
syslog_write(T_DBG,
"daemon runs in '%.1024s' with uid=%d gid=%d",
config_str(NULL, "ServerRoot", "/"),
(int) getuid(), (int) getgid());
}
/* ------------------------------------------------------------ **
**
** Function......: daemon_accept
**
** Parameters....: sock Accepted socket descriptor
**
** Return........: (none)
**
** Purpose.......: Callback to accept a client connection.
**
** ------------------------------------------------------------ */
void daemon_accept(int sock)
{
time_t slice;
int cnt, i;
CLIENT *clp;
char str[1024], *p, *q, *peer;
FILE *fp;
/*
** Get the peer address for diagnostic output
*/
peer = socket_addr2str(socket_sck2addr(sock, REM_END, NULL));
/*
** Check whether to limit the number of incoming
** client connections per minute. Use half values
** each to avoid "neighborhood effects". This is
** effectively a Denial of Service prevention.
*/
if ((cnt = config_int(NULL, "ForkLimit", MAX_FORKS)) > 0) {
slice = time(NULL) / (FORK_INTERVAL / 2);
if (slice != last_slice) {
last_slice = slice;
last_count = 0;
}
if (++last_count >= (cnt / 2)) {
close(sock);
syslog_write(U_ERR,
"reject: '%s' (ForkLimit %d)",
peer, cnt);
return;
}
}
/*
** Check if we are fully loaded already
*/
if ((cnt = config_int(NULL, "MaxClients", MAX_CLIENTS)) < 1)
cnt = 1;
else if (cnt > MAX_CLIENTS)
cnt = MAX_CLIENTS;
for (i = 0, clp = clients; i < cnt; i++, clp++) {
/*
** santoniu@libertysurf.fr:
** Verifying if the child is alive or not.
*/
if ((clp->pid != (pid_t) 0) && (kill(clp->pid, 0)!=0) ) {
syslog_write(T_WRN,
"child with PID %d went away (removing it)",
(pid_t)clp->pid);
clp->pid = 0;
break;
}
if (clp->pid == (pid_t) 0)
break;
}
if (i >= cnt) {
p = config_str(NULL, "MaxClientsMessage", NULL);
if (p != NULL && (fp = fopen(p, "r")) != NULL) {
while (fgets(str, sizeof(str) - 4, fp) != NULL) {
p = socket_msgline(str);
if ((q = strchr(p, '\n')) != NULL)
strcpy(q, "\r\n");
else
strcat(p, "\r\n");
send(sock, "421-", 4, 0);
send(sock, p, strlen(p), 0);
}
fclose(fp);
}
if ((p = config_str(NULL,
"MaxClientsString", NULL)) != NULL)
p = socket_msgline(p);
else
p = "Service not available";
send(sock, "421 ", 4, 0);
send(sock, p, strlen(p), 0);
send(sock, ".\r\n", 3, 0);
close(sock);
syslog_write(U_ERR,
"reject: '%s' (MaxClients %d)", peer, cnt);
return;
}
/*
** Fork a new client process (clp is still valid)
*/
switch (clp->pid = fork()) {
case -1:
clp->pid = (pid_t) 0;
if (errno != EAGAIN) {
syslog_error("can't fork client");
}
close(sock);
syslog_write(T_WRN, "can't fork client now");
return;
case 0:
/******** child ********/
break;
default:
/******** parent ********/
close(sock);
strcpy(clp->peer, peer);
#if defined(COMPILE_DEBUG)
debug(1, "client pid=%d (%s) added",
(int) clp->pid, clp->peer);
#endif
return;
}
/*
** Maintain the init/exit message balance
*/
misc_setprog("ftp-child", NULL);
#if defined(COMPILE_DEBUG)
debug(1, "{{{{{ %s client-fork", misc_getprog());
#endif
/*
** To be consistent with inetd-mode, make the client
** socket our standard path. stderr is still /dev/null.
*/
dup2(sock, fileno(stdin));
dup2(sock, fileno(stdout));
close(sock);
/*
** Get out of the daemon's way in terms of cleanup
*/
misc_forget();
socket_lclose(0);
/*
** Well, time to do the client job
*/
client_run();
}
/* ------------------------------------------------------------ **
**
** Function......: daemon_cleanup
**
** Parameters....: (none)
**
** Return........: (none)
**
** Purpose.......: Clean up the daemon related data.
**
** ------------------------------------------------------------ */
static void daemon_cleanup(void)
{
int i;
CLIENT *clp;
if(getpid() == daemon_pid) /* clean up our childs list */
for (i = 0, clp = clients; i < MAX_CLIENTS; i++, clp++) {
if (clp->pid == (pid_t) 0)
continue;
#if defined(COMPILE_DEBUG)
debug(1, "client %d=%s still alive",
(int) clp->pid, clp->peer);
#endif
/*
** Make sure this child does not survive
*/
kill(clp->pid, SIGTERM);
}
}
/* ------------------------------------------------------------
* $Log: ftp-daemon.c,v $
* Revision 1.4 2002/01/14 19:31:14 mt
* reordered chroot, uidgid-dropping, syslog opening in detach_init
* implemented waiting for child-init after fork for proper exit code
*
* Revision 1.3 2001/11/06 23:04:44 mt
* applied / merged with transparent proxy patches v8
* see ftp-proxy/NEWS for more detailed release news
*
* Revision 1.2 1999/09/21 07:14:19 wiegand
* syslog / abort cleanup and review
*
* Revision 1.1 1999/09/15 14:06:22 wiegand
* initial checkin
*
* ------------------------------------------------------------ */
syntax highlighted by Code2HTML, v. 0.9.1