/* Copyright 2000 Beau Kuiper
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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */
#include "ftpd.h"
#include "reply.h"
extern int nummalloc; /* number of areas malloced at once */
int signumber;
pid_t *deadlist;
int deadcount;
void logfullmessage(int error, unsigned int ip)
{
switch(error)
{
case HOSTFULL:
log_addentry(MYLOG_INFO, NULL, "main maxusers limit reached. Server full");
break;
case GROUPFULL:
log_addentry(MYLOG_INFO, NULL, "group maxusers limit reached. User denied");
break;
case VSERVERFULL:
log_addentry(MYLOG_INFO, NULL, "virtual server maxusers limit reached. User denied");
break;
case IPHOSTFULL:
log_giveentry(MYLOG_INFO, NULL, safe_snprintf("main connections per ip reached for %s", getipstr(ip)));
break;
case IPVSERVERFULL:
log_giveentry(MYLOG_INFO, NULL, safe_snprintf("virtual server maximum connections per ip reached for %s", getipstr(ip)));
break;
default:
log_addentry(MYLOG_INFO, NULL, "Unknown host error.");
}
}
VSERVER *findvserver(unsigned int ip, int port)
{
VSERVERCONN *vs;
vs = config->inports;
while (vs != NULL)
{
if (vs->port == port)
if ((vs->ip == 0) || (vs->ip == ip))
return(vs->vptr);
vs = vs->next;
}
return(NULL);
}
/* this function finds all ports binding to multiple ip and uses a single
bind to use fewer fd's. It runs in N^2 time, but shouldn't be too slow
even for very large sites. */
void smartbind(SELECTER *selset, VSERVERCONN *list, int dodie)
{
VSERVERCONN *pos, *spos;
int dobind, fd;
unsigned int bindip;
pos = list;
while(pos != NULL)
{
/* make sure port hasn't already been bound */
spos = list;
dobind = TRUE; /* assume the bind goes ahead */
bindip = pos->ip; /* assume binding to a single port */
fd = -1;
/* see if port number is in any ealier binds. If so, then
no need to bind this address */
while((spos != pos) && (dobind))
{
if (spos->port == pos->port)
{
fd = spos->fd;
dobind = FALSE;
}
spos = spos->next;
}
if (dobind)
{
unsigned int zeroip;
getnetworkint("0.0.0.0", &zeroip);
spos = pos->next;
/* now search for binds to this address later in the
chain. Set ip to 0.0.0.0 if this is the case */
while((spos != NULL) && (bindip != zeroip))
{
if (spos->port == pos->port)
bindip = zeroip;
spos = spos->next;
}
/* see if we need to bind to zero instead of
bindip */
if ((config->zerobind) && (bindip != zeroip))
{
bindip = zeroip;
}
/* now actually bind it */
pos->fd = inport_bind(pos->port, bindip, dodie);
if (pos->fd != -1)
{
select_addfd(selset, pos->fd);
if (bindip == pos->ip)
select_addread(selset, pos->fd, inport_getconn, pos);
else
select_addread(selset, pos->fd, inport_getconn, NULL);
}
}
else
pos->fd = fd;
pos = pos->next;
}
}
/* This function does binding the dumb way. It runs in N time, but consumes
more file descriptors. However, it also doesn't bind to unneeded ip/port
combinations and is more robust. */
void dumbbind(SELECTER *selset, VSERVERCONN *list, int dodie)
{
VSERVERCONN *pos = list;
while(pos != NULL)
{
pos->fd = inport_bind(pos->port, pos->ip, dodie);
if (pos->fd != -1)
{
select_addfd(selset, pos->fd);
select_addread(selset, pos->fd, inport_getconn, pos);
}
pos = pos->next;
}
}
void sighandler(int signum)
{
signumber = signum;
debuglog("main got a signal %d", signum);
if (signum == SIGCHLD)
{
int errno2, result;
pid_t deadpid;
debuglog("sighandler - main waiting for child!");
errno2 = errno;
while((deadpid = waitpid(0, &result, WNOHANG)) > 0)
{
debuglog("pid %d died with result %d!", deadpid, result);
deadlist[deadcount] = deadpid;
deadcount += 1;
}
errno = errno2;
debuglog("sighandler - main child %d finished!", (int)deadpid);
signal(SIGCHLD, sighandler);
}
else
if (signum == SIGUSR1)
{
signal(SIGUSR1, sighandler);
}
}
int mainprog(char *fconfig, int runforeground, int verbose)
{
int signum = 0;
SELECTER *mainports;
#ifdef RLIMIT_NPROC
struct rlimit newlimit;
#endif
test_libc(verbose);
setsid();
signal(SIGIO, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, sighandler);
signal(SIGHUP, sighandler);
signal(SIGCHLD, sighandler);
signal(SIGUSR1, sighandler);
signal(SIGINT, sighandler);
logerrors = TERMINAL;
/* close all file descriptiors */
fd_closeall_nonterminal();
init_pwgrfiles();
ftpd_init(fconfig, verbose);
config->toobusycount = 0;
deadlist = mallocwrapper(sizeof(pid_t) * config->defaults->maxusers + 1);
deadcount = 0;
logerrors = MUDLOG;
mainports = select_new();
/* Set process limit very high so that the 'poor user' doesn't
need to worry about setting it */
#ifdef RLIMIT_NPROC
getrlimit(RLIMIT_NPROC, &newlimit);
newlimit.rlim_cur += config->defaults->maxusers + 1;
newlimit.rlim_max += config->defaults->maxusers + 1;
setrlimit(RLIMIT_NPROC, &newlimit);
#endif
/* Listen to all the input sockets, they are changed to the fd */
if (config->smartbind)
smartbind(mainports, config->inports, TRUE);
else
dumbbind(mainports, config->inports, TRUE);
if (((int)config->uidt_asuid != (int)getuid()) ||
((int)config->gidt_asgid != (int)getgid()))
{
if ((int)getuid() == 0)
{
if (verbose)
printf("Switching to user '%s' (UID: %d, GID %d)\n",
config->username,
(int)config->uidt_asuid, (int)config->gidt_asgid);
/* Tell the parent the terminal can be given back! */
/* It must be done before we change uid because we
lose permission to do so after the setuid! */
if (!runforeground)
kill(getppid(), SIGHUP);
/* Changing user to the one the user wanted */
setgid(config->gidt_asgid);
setuid(config->uidt_asuid);
}
else
ERRORMSGFATAL("The server does not have permission to switch to the specified user.");
}
else
{
if (!runforeground)
/* Tell the parent the terminal can be given back! */
kill(getppid(), SIGHUP);
}
/* close terminal file descriptors */
if (!runforeground)
{
close(0); close(1); close(2);
}
log_addentry(MYLOG_INFO, NULL, PROGNAME" ("VERSTR") server started. Waiting on connections.");
while ((signum != SIGTERM) && (signum != SIGINT))
{
select_do(mainports, &signum, -1);
blockallsignals();
if (deadcount > 0)
{
shinfo_freethreads(deadcount, deadlist);
deadcount = 0;
}
if ((signum == SIGHUP) && (config->username == NULL))
{
/* rebuild configuration */
CONFIGDATA *newdata;
int result;
log_addentry(MYLOG_INFO, NULL, "Received SIGHUP, reloading configuration");
newdata = ftpd_loadconfig(fconfig, FALSE, config->defaults->umask);
if (newdata)
result = ftpd_checkconfig(newdata);
if ((!newdata) || (!result))
{
log_addentry(MYLOG_INFO, NULL, "Errors reloading config file, resuming old config");
if (newdata)
ftpd_killconfig(newdata);
}
else
{
int t = config->toobusycount;
ftpd_killconfig(config);
config = newdata;
config->toobusycount = t;
log_shutdown();
log_setcontext(config->logout, config->defaults->loglevel);
log_addentry(MYLOG_INFO, NULL, "New configuration loaded, using new configuration");
select_shutdown(mainports);
mainports = select_new();
if (config->smartbind)
smartbind(mainports, config->inports, FALSE);
else
dumbbind(mainports, config->inports, FALSE);
shinfo_reinit();
}
signal(SIGHUP, sighandler);
}
else if ((signum == SIGHUP) && (config->username != NULL))
log_addentry(MYLOG_INFO, NULL, "You cannot reload the configuration while using the runasuser directive");
else if (signum == SIGUSR1)
{
int newcontext = log_initcontext(config->defaults->logfile);
if (newcontext == -1)
log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Could not open logfile '%s', couldn't reopen.", config->defaults->logfile));
else
{
config->logout = newcontext;
log_shutdown();
pnums_signalchildren(SIGUSR1);
log_setcontext(config->logout, config->defaults->loglevel);
log_addentry(MYLOG_INFO, NULL, "Log file reopened after SIGUSR1.");
}
}
unblockallsignals();
signal(SIGIO, SIG_IGN);
signal(SIGHUP, sighandler);
}
shinfo_shutdown();
select_shutdown(mainports);
log_addentry(MYLOG_INFO, NULL, PROGNAME" ("VERSTR") server shutting down.");
freewrapper(deadlist);
ftpd_killconfig(config);
log_shutdown();
kill_uidgidfiles();
exit(0);
}
void mainprog_inetd(char *fconfig, unsigned int ip)
{
char *scratchfile;
int tnum, port, result;
unsigned int ipl;
VSERVER *vserver;
int error;
test_libc(0);
inetd = TRUE;
signal(SIGUSR1, sighandler);
init_pwgrfiles();
logerrors = SYSLOG;
ftpd_preinit();
config = ftpd_loadconfig(fconfig, TRUE, umask(0));
if (config)
result = ftpd_checkconfig(config);
if ((!config) || (!result))
ERRORMSGFATAL("Errors loading config file, cannot continue!");
log_setcontext(config->logout, config->defaults->loglevel);
result = getsectionid(config->configfile, "main");
loadstrfromconfig(config->configfile, result, "scratchfile",
&scratchfile, SCRATCHFILE);
if (scratchfile[0] != '/')
ERRORMSGFATAL("Scratchfile is not a valid absolute filename");
ftpd_setnogroups();
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, sighandler);
signal(SIGHUP, sighandler);
inetd_init(scratchfile);
if (((int)config->uidt_asuid != (int)getuid()) ||
((int)config->gidt_asgid != (int)getgid()))
{
if (getuid() == 0)
{
setgid(config->gidt_asgid);
setuid(config->uidt_asuid);
}
}
/* get the virtual server the user connected to! */
getsockinfo(0, &ipl, &port);
vserver = findvserver(ipl, port);
if (vserver == NULL)
exit(0);
tnum = shinfo_adduser_inetd(ip, config->defaults->maxusers,
config->defaults->maxperip, &error);
if (tnum != -1)
result = shinfo_setvserver(tnum, vserver->sectionname, ip,
vserver->maxusers,
vserver->maxperip,
&error);
if ((result) && (tnum != -1))
ftpserverside_main(0, ip, tnum, port, vserver);
else
{
if (vserver->toobusy)
write(0, vserver->toobusy, strlen(vserver->toobusy));
else
write(0, REPLY_SERVERBUSY, strlen(REPLY_SERVERBUSY));
logfullmessage(error, ip);
}
ftpd_killconfig(config);
kill_uidgidfiles();
exit(0);
}
void diehandler(int signum)
{
exit(0);
}
void usage(char *name)
{
printf(PROGNAME": ftp daemon.\n\n");
printf("Usage: %s [-V][-d][-h][-c configfile]\n\n", name);
printf(" -V Show version information.\n");
printf(" -v Verbose startup.\n");
printf(" -d Debug mode. Does not fork into background.\n");
printf(" -h Show usage information.\n");
printf(" -c configfile Specify an alternative config file.\n\n");
exit(1);
}
int main(int argc, char **argv)
{
unsigned int ip;
pid_t forkresult;
int verbose = FALSE;
int ch;
int runforeground = FALSE;
char *fconfig = NULL;
extern char *optarg;
while((ch = getopt(argc, argv, "Vvc:hd")) != EOF)
{
switch(ch)
{
case 'V':
showversion(PROGNAME);
case 'v':
verbose = TRUE;
break;
case 'c':
fconfig = optarg;
break;
case 'd':
runforeground = TRUE;
break;
case 'h':
default:
usage(argv[0]);
}
}
if (fconfig == NULL)
fconfig = CONFIGFILE;
if ((ip = getremoteip(fileno(stdin))) != 1)
mainprog_inetd(fconfig, ip);
inetd = FALSE;
signal(SIGCHLD, diehandler);
signal(SIGHUP, diehandler);
if (!runforeground)
{
forkresult = fork();
if ((int)forkresult == -1)
ERRORMSGFATAL("Could not fork into background");
if ((int)forkresult == 0)
mainprog(fconfig, FALSE, verbose);
/* wait forever for the child to die or give us a Hangup signal */
while(TRUE)
pause();
}
else
mainprog(fconfig, TRUE, verbose);
return(0);
}
syntax highlighted by Code2HTML, v. 0.9.1