/* Copyright (C) 1999 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 "ftpcmd.h" #include "reply.h" typedef struct overload_info { int pos; char *msg; int len; } OVERLOAD_INFO; extern FTPCMD mainftpcmd[]; extern pid_t *deadlist; int controlportgotdata(SELECTER *sel, int fd, void *peerdata) { FTPSTATE *peer = (FTPSTATE *)peerdata; char *instring = NULL; int connclosed = readcmd(peer); int exits = FALSE; INPUTLINE cmd; if (connclosed) return(TRUE); else { if (popcmd(peer, &instring)) { ftp_write(peer, FALSE, 500, REPLY_ONECMDONLY); return(TRUE); } if (instring) /* we have a command */ { cmd_split(peer, &cmd, instring, mainftpcmd, TRUE, (peer->loggedin ? peer->cmddisableset : NULL)); if (!peer->dport) if (cmd.command->ftpfunc != ftp_pass) shinfo_changeop(instring); exits = ftp_run(peer, &cmd, "FTP"); freeifnotnull(cmd.parameters); if (!peer->dport) shinfo_changeop("idle"); string_clear(&(peer->inbuffer)); } return(exits); } } char *remove_rootcomponent(FTPSTATE *peer, char *filename, char *descript) { int baselen = strlen(peer->basedir); pathname_simplify(filename); if (strncmp(peer->basedir, filename, baselen) == 0) memmove(filename, filename + baselen, strlen(filename) - baselen + 1); else { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("%s(%s) is outside of rootdir(%s) with chroot enabled!", descript, filename, peer->basedir)); freewrapper(filename); return(NULL); } return(filename); } void dochroot(FTPSTATE *peer) { /* this prevents bugs in resolving filenames */ if (strcmp(peer->basedir, "/") == 0) { peer->chroot = FALSE; return; } if (config->rootmode) { int res = chroot(peer->basedir); if (res == 0) { /* put ourselves into the tree */ chdir("/"); /* once chroot is done, nothing can undo it, so user cannot relogin */ peer->jailenabled = TRUE; /* now change dumped file names */ pathname_simplify(peer->basedir); if (peer->logindump) peer->logindump = remove_rootcomponent(peer, peer->logindump, "welcome"); if (peer->quitdump) peer->quitdump = remove_rootcomponent(peer, peer->quitdump, "quitdump"); if (peer->cwddump) if ((peer->cwddump)[0] == '/') peer->cwddump = remove_rootcomponent(peer, peer->cwddump, "cddump"); freewrapper(peer->basedir); peer->basedir = strdupwrapper("/"); } else log_giveentry(MYLOG_INFO, NULL, safe_snprintf("chroot('%s') gave error %s", peer->basedir, strerror(errno))); } else log_addentry(MYLOG_INFO, NULL, "Cannot use chroot() without root permission."); } void rotatelogs(FTPSTATE *peer) { char *logfile; int newlogcontext; if (peer->vserver->logfile) logfile = peer->vserver->logfile; else logfile = config->defaults->logfile; if ((!peer->chroot) || (!peer->droproot)) { /* use root to open log */ file_becomeroot(peer); newlogcontext = log_initcontext(logfile); file_becomeuser(peer); if (newlogcontext == -1) log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Could not open log file '%s', reopen failed", peer->vserver->logfile)); else { log_shutdown(); config->logout = newlogcontext; log_setcontext(config->logout, peer->vserver->loglevel); } } else log_addentry(MYLOG_INFO, NULL, "Cannot reopen log file in chroot or droproot mode"); } int vserver_select(FTPSTATE *peer, VSERVER *vserver) { int error, result; int newlogcontext; peer->vserver = vserver; /* initalize the new log context */ newlogcontext = config->logout; if (vserver->logfile) if (strcmp(config->defaults->logfile, vserver->logfile) != 0) { newlogcontext = log_initcontext(vserver->logfile); if (newlogcontext == -1) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Dropping connection, error opening virtual server logfile '%s': %s", vserver->logfile, strerror(errno))); return(1); } log_shutdown(); config->logout = newlogcontext; } log_setcontext(config->logout, vserver->loglevel); /* see if the user is allowed in this vserver */ if (!user_allowed(vserver->ipaccess, peer->remoteip, peer->hostname)) { log_addentry(MYLOG_DACCESS, peer, NULL); return(2); } /* set the procdata up */ result = shinfo_setvserver(peer->threadnum, vserver->sectionname, peer->remoteip, vserver->maxusers, vserver->maxperip, &error); if (!result) { logfullmessage(error, peer->remoteip); return(3); } return(0); } void ftpserverside_main(int remotefd, int remoteip, int threadnum, int portnum, VSERVER *vserver) { int newlogcontext, exits = FALSE; int flag = 1; struct rlimit mylimits = { MAXMEMUSAGE, MAXMEMUSAGE }; FTPSTATE *peer = mallocwrapper(sizeof(FTPSTATE)); char *greetline; ftpstate_init(peer, remotefd, remoteip, threadnum, portnum, vserver); /* initalize ftp state data struture */ shinfo_sethost(peer->hostname); if (!user_allowed(config->defaults->ipaccess, remoteip, peer->hostname)) { log_addentry(MYLOG_DACCESS, peer, NULL); close(remotefd); return; } newlogcontext = config->logout; if (vserver->logfile) if (strcmp(config->defaults->logfile, vserver->logfile) != 0) { newlogcontext = log_initcontext(vserver->logfile); if (newlogcontext == -1) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Dropping connection, error opening virtual server logfile '%s': %s", vserver->logfile, strerror(errno))); close(remotefd); return; } log_shutdown(); config->logout = newlogcontext; } log_setcontext(config->logout, vserver->loglevel); setrlimit(RLIMIT_DATA, &mylimits); peer->vserver = vserver; /* see if user is allowed to enter */ if (!user_allowed(vserver->ipaccess, remoteip, peer->hostname)) { log_addentry(MYLOG_DACCESS, peer, NULL); close(remotefd); return; } peer->sel = select_new(); setsockopt(peer->remotefd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); /* add the remote fd for reading */ select_addfd(peer->sel, remotefd); select_addread(peer->sel, remotefd, controlportgotdata, (void *)peer); /* output our greeting */ shinfo_changeop("idle"); if (vserver->greetline) greetline = vserver->greetline; else greetline = REPLY_GREET; if (vserver->prelogindumpdata) exits = ftp_dumpstr(peer, vserver->prelogindumpdata, 220, greetline, TRUE); else exits = ftp_dumper(peer, nfopen(vserver->prelogindump), 220, greetline, TRUE, TRUE); while(!exits) { int fd, signalnum; fd = select_do(peer->sel, &signalnum, peer->timeout); /* if we get 3 back, simply loop and update timeout */ if (fd == -1) { if ((signalnum == SIGTERM) || (signalnum == SIGHUP)) { /* server is shutting down! */ ftp_write(peer, FALSE, 421, REPLY_SERVERSHUTDOWN); exits = TRUE; } else if (signalnum == SIGUSR1) { rotatelogs(peer); } else if (signalnum == 0) exits = TRUE; } else if (fd == 0) { /* timeout has occurred */ ftp_write(peer, FALSE, 421, REPLY_TIMEOUT(peer->timeout)); exits = TRUE; } else if (fd != 3) exits = TRUE; } log_addentry(MYLOG_LOGIN, peer, "User logged out."); if (peer->acldata) acllist_dest(peer->acldata); select_shutdown(peer->sel); ftpstate_dest(peer); } int inport_bind(int portid, unsigned int bindip, int dodie) { int incon; if ((portid < 1024) && (getuid() != 0)) { char *error = safe_snprintf("root access required to bind port %d (less than 1024), skipping", portid); if (dodie) ERRORMSGFATAL(error); else log_giveentry(MYLOG_INFO, NULL, error); return(-1); } incon = listenport(portid, bindip, MAXPENDCONN); if (incon == -1) { char *error = safe_snprintf("Cannot bind to port %d, ip %s, skipping!", portid, getipstr(bindip)); if (dodie) ERRORMSGFATAL(error); else log_giveentry(MYLOG_INFO, NULL, error); } return(incon); } int overload_sendmessage(SELECTER *mainsel, int fd, void *in) { OVERLOAD_INFO *i = (OVERLOAD_INFO *)in; int c; c = write(fd, i->msg + i->pos, i->len); i->len -= c; i->pos += c; if ((c <= 0) || (i->len <= 0)) { freewrapper(i); return(2); } return(FALSE); } int inport_getconn(SELECTER *mainsel, int fd, void *vs) { int newconn; unsigned int ipaddress, lip; VSERVER *vserver; int tnum, port, error, result; pid_t fresult; newconn = get_conn(fd, &ipaddress); if (newconn == -1) { log_giveentry(MYLOG_INFO, NULL, safe_snprintf("Error accepting control connection: %s", strerror(errno))); return(FALSE); } if (config->hostvservers) { getsockinfo(newconn, &lip, &port); vserver = config->defaults; } else if (vs != NULL) { vserver = ((VSERVERCONN *)vs)->vptr; port = ((VSERVERCONN *)vs)->port; } else { getsockinfo(newconn, &lip, &port); vserver = findvserver(lip, port); } /* was not able to find a vserver for the port/ip they connected to */ if (vserver == NULL) { close(newconn); return(FALSE); } result = TRUE; tnum = shinfo_newuser_standalone(ipaddress, config->defaults->maxperip, &error); if ((tnum != -1) && (config->vservers) && (!config->hostvservers)) result = shinfo_setvserver(tnum, vserver->sectionname, ipaddress, vserver->maxusers, vserver->maxperip, &error); if ((tnum != -1) && (result)) { fresult = fork(); if ((int)fresult == 0) { shinfo_setpid(tnum, (int)getpid()); select_shutdown(mainsel); freewrapper(deadlist); ftpserverside_main(newconn, ipaddress, tnum, port, vserver); ftpd_killconfig(config); kill_uidgidfiles(); log_shutdown(); exit(0); } else if ((int)fresult == -1) { shinfo_freebynum(tnum); log_addentry(MYLOG_INFO, NULL, "Could not fork, dropping connection."); ERRORMSG("Could not fork, dropping connection!"); close(newconn); } else { close(newconn); } } else { /* server is full, or too many connections */ if (tnum != -1) shinfo_freebynum(tnum); logfullmessage(error, ipaddress); if (config->toobusycount <= MAXTOOMANYUSERS) { OVERLOAD_INFO *i = mallocwrapper(sizeof(OVERLOAD_INFO)); i->msg = vserver->toobusy; if (i->msg == NULL) i->msg = REPLY_SERVERBUSY; i->pos = 0; i->len = strlen(i->msg); fcntl(newconn, F_SETFL, O_NONBLOCK); select_addfd(mainsel, newconn); select_addwrite(mainsel, newconn, overload_sendmessage, i); } else close(newconn); } return(FALSE); }