/* 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); }