/* * $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 * Pieter Hollants * Marius Tomaschewski * Volker Wiegand * * 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 #if defined(STDC_HEADERS) # include # include # include # include # include #endif #if defined(HAVE_UNISTD_H) # include #endif #if defined(TIME_WITH_SYS_TIME) # include # include #else # if defined(HAVE_SYS_TIME_H) # include # else # include # endif #endif #include #include #if defined(HAVE_FCNTL_H) # include #elif defined(HAVE_SYS_FCNTL_H) # include #endif #include #if defined(HAVE_SYS_WAIT_H) # include #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 #endif #if !defined(_PATH_DEVNULL) # define _PATH_DEVNULL "/dev/null" #endif #include #include #include #include #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 * * ------------------------------------------------------------ */