/* 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);
}
syntax highlighted by Code2HTML, v. 0.9.1