/* 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"
/* these are used when the server is run in standalone mode. In standalone
mode shared memory is used to hold user info */
SHRMEMHEADER *shdata = NULL; /* The start of the shared memory, containing
the vserver, group records */
SCRMEMREC *procdata = NULL; /* start of array holding thread info. This
is all in memory since it is much faster. */
int nextthid = -1; /* the next free thread id in the list */
int shlockfile = -1; /* file fd used with fcntl file locking to
control access to the shared memory.
This is typically the config file */
int shnumber = -1; /* the number allocated to the shared memory
area. */
/* these are used when the server is run in inetd mode */
int scratchfile = -1; /* The fd of the scratchfile. The scratchfile
is used to co-ordinate group limits. */
/* used by both modes */
int thid = -1; /* The thread id of the current muddleftpd
client */
/* these simplify writing to the process areas */
void writescratch(int pos, int size, char *buff)
{
lseek(scratchfile, pos, SEEK_SET);
write(scratchfile, buff, size);
}
void writeshmem(int num, int pos, int len, void *buff)
{
lockarea(shlockfile, 10+num, 1, F_WRLCK, TRUE);
/* messy. Basicly num is added to procdata as a SCRMEMREC pointer
then pos is added as a char pointer */
memcpy((char *)(procdata + num) + pos, buff, len);
lockarea(shlockfile, 10+num, 1, F_UNLCK, TRUE);
}
void write_procstr(int cthid, int shmem_pos, int file_pos, int length,
char *strbuff)
{
char endchr = 0;
int writelen = strlen(strbuff) + 1;
/* if string is too long, null terminate it! */
if (writelen >= length)
{
endchr = strbuff[length-1];
strbuff[length-1] = 0;
writelen = length;
}
if (inetd)
writescratch(cthid * sizeof(SCRFILEREC) + file_pos,
writelen, strbuff);
else
writeshmem(cthid, shmem_pos, writelen, strbuff);
if (endchr != 0)
strbuff[length-1] = endchr;
}
void shinfo_addtogrouplist(VSERVER *vs)
{
SHRMEMDATA *dpos;
int gpos = 0;
int count, flag, section, maxcount;
while(vs->grouplist[gpos] != NULL)
{
/* check to see if the group is already in the list */
flag = FALSE;
dpos = SHRMEMDATPOS(shdata, shdata->numvserver);
for(count = 0; count < shdata->numgroups; count++)
{
if (strncmp(vs->grouplist[gpos], dpos->name, MAXSECTIONLEN) == 0)
flag = TRUE;
dpos++;
}
if (!flag)
{
/* get maxuser information */
section = getsectionid(config->configfile, vs->grouplist[gpos]);
loadintfromconfig(config->configfile, section, "maxusers", &maxcount, config->defaults->maxusers);
/* A group with 0 maxusers doesn't get added! */
if (maxcount == 0)
flag = TRUE;
}
if (!flag)
{
/* add the group, dpos is already initalized from above
loop */
strncpy(dpos->name, vs->grouplist[gpos], MAXNAMLEN);
dpos->name[MAXSECTIONLEN-1] = 0;
dpos->max = maxcount;
dpos->count = 0;
shdata->numgroups++;
}
gpos++;
}
}
void shinfo_setuparea(void)
{
int count = 0;
SHRMEMDATA *vpos;
VSERVER *vs;
shdata->magic = CURRENTMAGIC;
shdata->serverusercount = 0;
shdata->servermaxcount = config->defaults->maxusers;
shdata->numvserver = 0;
shdata->numgroups = 0;
shdata->pid = getpid();
vpos = SHRMEMDATPOS(shdata, 0);
vs = config->vservers;
while(config->vserverlist[count] != NULL)
{
vpos->count = 0;
vpos->max = vs->maxusers;
strncpy(vpos->name, config->vserverlist[count], MAXSECTIONLEN);
vpos->name[MAXSECTIONLEN-1] = 0;
vpos++;
vs = vs->next;
shdata->numvserver++;
count++;
}
vs = config->vservers;
if (!vs)
shinfo_addtogrouplist(config->defaults);
else
while(vs)
{
shinfo_addtogrouplist(vs);
vs = vs->next;
}
if (((config->defaults->maxusers * sizeof(SCRMEMREC)) >
(SHMEMSIZE - SHMEMPROCSTART)) || ((shdata->numvserver +
shdata->numgroups) * sizeof(SHRMEMDATA) + sizeof(SHRMEMHEADER)
> SHMEMPROCSTART))
ERRORMSGFATAL("Not enough shared memory to support configuration.");
}
void shinfo_init(char *scfilename)
{
int count, new;
scratchfile = open(scfilename, O_RDWR | O_CREAT, 0600);
if (scratchfile == -1)
ERRORMSGFATAL("Could not open scratch file!");
shdata = (SHRMEMHEADER *)shmem_get(scfilename, SHMEMSIZE, &shnumber, &new, &shlockfile);
/* attempt to lock the shared area to say I am home */
if (!lockarea(shlockfile, 3, 1, F_WRLCK, FALSE))
ERRORMSGFATAL("Muddleftpd already running!");
/* make sure no-body else gets in */
lockarea(shlockfile, 0, 3, F_WRLCK, TRUE);
shinfo_setuparea();
lockarea(shlockfile, 0, 3, F_UNLCK, TRUE);
procdata = (SCRMEMREC *)((char *)(shdata) + SHMEMPROCSTART);
/* initalize pidmapping list. stores free list as negative values */
for (count = 0; count < config->defaults->maxusers; count++)
{
procdata[count].pid = -(count + 1);
strcpy(procdata[count].username, "none");
procdata[count].vserver = -1;
procdata[count].group = -1;
procdata[count].ip = 0;
}
shdata->numrecs = config->defaults->maxusers;
/* set the start pointer to the first thread */
nextthid = 0;
}
void inetd_init(char *scfile)
{
scratchfile = open(scfile, O_RDWR | O_CREAT, 0600);
if (scratchfile == -1)
ERRORMSGFATAL("Could not open scratch file!");
}
/* this reinitalizes the shared memory area, when config file is updated */
void shinfo_reinit(void)
{
int count, count2;
SHRMEMHEADER *old = mallocwrapper(SHMEMPROCSTART);
SHRMEMDATA *datnew, *datold;
lockarea(shlockfile, 0, 3, F_WRLCK, TRUE);
lockarea(shlockfile, 10, shdata->numrecs, F_WRLCK, TRUE);
memcpy(old, shdata, sizeof(SHRMEMHEADER) + sizeof(SHRMEMDATA) *
(shdata->numvserver + shdata->numgroups));
/* reinitalize area */
shinfo_setuparea();
/* move over old data */
shdata->serverusercount = old->serverusercount;
shdata->numrecs = old->numrecs;
/* update vservers */
datnew = SHRMEMDATPOS(shdata, 0);
for(count = 0; count < shdata->numvserver; count++)
{
datold = SHRMEMDATPOS(old, 0);
for(count2 = 0; count2 < old->numvserver; count2++)
{
if (strncmp(datnew->name, datold->name, MAXSECTIONLEN) == 0)
datnew->count = datold->count;
datold++;
}
datnew++;
}
for(count = 0; count < shdata->numgroups; count++)
{
datold = SHRMEMDATPOS(old, old->numvserver);
for(count2 = 0; count2 < old->numgroups; count2++)
{
if (strncmp(datnew->name, datold->name, MAXSECTIONLEN) == 0)
datnew->count = datold->count;
datold++;
}
datnew++;
}
/* now update all vserver and group id numbers */
for (count = 0; count < shdata->numrecs; count++)
{
char *vserver, *group;
if ((procdata[count].vserver != -1) && (shdata->numvserver > 0))
{
datold = SHRMEMDATPOS(old, procdata[count].vserver);
vserver = datold->name;
datnew = SHRMEMDATPOS(shdata, 0);
procdata[count].vserver = -1;
for (count2 = 0; count2 < shdata->numvserver; count2++)
{
if (strncmp(vserver, datnew->name, MAXSECTIONLEN) == 0)
procdata[count].vserver = count2;
datnew++;
}
}
else
procdata[count].vserver = -1;
if (procdata[count].group >= 0)
{
datold = SHRMEMDATPOS(old, old->numvserver + procdata[count].group);
group = datold->name;
datnew = SHRMEMDATPOS(shdata, shdata->numvserver);
/* say the user is orphaned */
procdata[count].group = -2;
for (count2 = 0; count2 < shdata->numgroups; count2++)
{
if (strncmp(group, datnew->name, MAXSECTIONLEN) == 0)
procdata[count].group = count2;
datnew++;
}
}
}
if (shdata->servermaxcount > shdata->numrecs)
{
for (count = shdata->numrecs; count < shdata->servermaxcount; count++)
{
procdata[count].pid = -(count + 1);
strcpy(procdata[count].username, "none");
procdata[count].vserver = -1;
procdata[count].group = -1;
procdata[count].ip = -1;
}
shdata->numrecs = shdata->servermaxcount;
}
lockarea(shlockfile, 0, 3, F_UNLCK, TRUE);
lockarea(shlockfile, 10, old->numrecs, F_UNLCK, TRUE);
freewrapper(old);
}
/* set the vserver for a logged in user. REQUIRED for HOST command
support */
int shinfo_setvserver_standalone(int cthid, char *vservername, unsigned int ip,
int viplimit, int *error)
{
int pos = 0;
int done = 0;
SHRMEMDATA *d = SHRMEMDATPOS(shdata, 0);
int vipcount = 0;
/* lock vserver-group counts */
*error = VSERVERFULL;
lockarea(shlockfile, 0, 2, F_WRLCK, TRUE);
while((pos < shdata->numvserver) && (!done))
{
if (strncmp(vservername, d->name, MAXSECTIONLEN) == 0)
if (d->count < d->max)
/* found it all */
done = TRUE;
if (!done)
{
pos++;
d++;
}
}
if ((done) && (viplimit > 0))
{
/* safe as long as IP is not set elsewhere */
int count;
for (count = 0; count < shdata->numrecs; count++)
{
lockarea(shlockfile, 10 + count, 1, F_WRLCK, TRUE);
if ((procdata[count].ip == ip) &&
(procdata[count].vserver == pos))
vipcount++;
lockarea(shlockfile, 10 + count, 1, F_UNLCK, TRUE);
}
if (vipcount >= viplimit)
{
*error = IPVSERVERFULL;
done = FALSE;
}
}
if (done)
{
lockarea(shlockfile, 10 + cthid, 1, F_WRLCK, TRUE);
procdata[thid].vserver = pos;
d->count++;
lockarea(shlockfile, 10 + cthid, 1, F_UNLCK, TRUE);
}
lockarea(shlockfile, 0, 2, F_UNLCK, TRUE);
return(done);
}
int shinfo_setvserver_inetd(int newthid, char *vservername, unsigned int ip,
int vlimit, int viplimit, int *error)
{
int pos;
int vipcount;
int vcount;
int done;
SCRFILEREC d;
pos = 0; vcount = 0; vipcount = 0; done = FALSE;
/* grab general scratchfile lock */
lockarea(scratchfile, 0, 1, F_WRLCK, TRUE);
lseek(scratchfile, 0, SEEK_SET);
while(read(scratchfile, &d, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC))
{
/* don't count ourselves */
if (pos == thid)
pos++;
else if (lockarea(scratchfile, 10 + pos, 1, F_WRLCK, FALSE))
{
/* if locking successed, no-one is home, unlock it */
lockarea(scratchfile, 10 + pos, 1, F_UNLCK, TRUE);
}
else
{
if (strncmp(d.vserver, vservername, MAXSECTIONLEN) == 0)
{
vcount++;
if (d.ip == ip)
vipcount++;
}
pos++;
}
}
if ((viplimit > 0) && (vipcount >= viplimit))
*error = IPVSERVERFULL;
else if (vcount >= vlimit)
*error = VSERVERFULL;
else
{
write_procstr(newthid, 0, SCRF_VSERVER, MAXSECTIONLEN,
vservername);
done = TRUE;
}
lockarea(scratchfile, 0, 1, F_UNLCK, TRUE);
return(done);
}
int shinfo_setvserver(int cthid, char *vservername, unsigned int ip,
int vlimit, int viplimit, int *error)
{
if (inetd)
return(shinfo_setvserver_inetd(cthid, vservername, ip, vlimit,
viplimit, error));
else
return(shinfo_setvserver_standalone(cthid, vservername, ip,
viplimit, error));
}
int shinfo_newuser_standalone(unsigned int ip, int iplimit, int *error)
{
int tempnextthid, done = FALSE;
lockarea(shlockfile, 0, 2, F_WRLCK, TRUE);
/* see if we have space */
if (shdata->serverusercount >= shdata->servermaxcount)
{
thid = -1;
*error = HOSTFULL;
}
else
{
thid = nextthid;
tempnextthid = -procdata[thid].pid;
done = TRUE;
}
if ((done) && (iplimit > 0))
{
/* safe as long as IP is not set elsewhere (other than below) */
int count;
int ipcount = 0;
for (count = 0; count < shdata->numrecs; count++)
{
if (procdata[count].ip == ip)
ipcount++;
}
if (ipcount >= iplimit)
{
done = FALSE;
*error = IPHOSTFULL;
}
}
if (done)
{
/* increment server count now login is confirmed */
shdata->serverusercount++;
/* update shared memory */
lockarea(shlockfile, 10 + thid, 1, F_WRLCK, TRUE);
strcpy(procdata[thid].username, "<unknown>");
procdata[thid].group = -1;
procdata[thid].vserver = -1;
strcpy(procdata[thid].currentop, "none");
strcpy(procdata[thid].remotehost, "unknown");
procdata[thid].pid = -1;
procdata[thid].ip = ip;
lockarea(shlockfile, 10 + thid, 1, F_UNLCK, TRUE);
/* now move the thids along */
nextthid = tempnextthid;
}
else
thid = -1;
lockarea(shlockfile, 0, 2, F_UNLCK, TRUE);
return(thid);
}
int shinfo_adduser_inetd(unsigned int ip, int slimit, int iplimit, int *error)
{
SCRFILEREC d;
int scount, ipcount, pos, full;
/* we are running inetd. go through scratch file, find an
empty record, and count space in file. */
pos = 0; scount = 0; thid = -1;
full = FALSE;
/* grab general scratchfile lock */
lockarea(scratchfile, 0, 1, F_WRLCK, TRUE);
lseek(scratchfile, 0, SEEK_SET);
while(read(scratchfile, &d, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC))
{
if (lockarea(scratchfile, 10 + pos, 1, F_WRLCK, FALSE))
{
/* if no allocated thid yet, assign it, otherwise
unlock free area */
if (thid == -1)
thid = pos;
else
lockarea(scratchfile, 10 + pos, 1, F_UNLCK, TRUE);
}
else
{
if (d.ip == ip)
ipcount++;
scount++;
}
pos++;
}
if (thid == -1)
{
thid = pos;
lockarea(scratchfile, 10 + pos, 1, F_WRLCK, TRUE);
}
if (scount >= slimit)
{
*error = HOSTFULL;
full = TRUE;
}
if ((iplimit > 0) && (ipcount >= iplimit))
{
*error = IPHOSTFULL;
full = TRUE;
}
if (full)
{
lockarea(scratchfile, 10 + pos, 1, F_UNLCK, TRUE);
lockarea(scratchfile, 0, 1, F_UNLCK, TRUE);
return(-1);
}
/* need to add data to file */
memset(&d, 0, sizeof(SCRFILEREC));
strcpy(d.username, "<unknown>");
strcpy(d.groupname, "none");
strncpy(d.vserver, "none", MAXSECTIONLEN);
/* null terminate d.vserver */
d.vserver[MAXSECTIONLEN-1] = 0;
strcpy(d.currentop, "none");
strcpy(d.remotehost, "unknown");
d.pid = (int)getpid();
d.ip = ip;
writescratch(thid * sizeof(SCRFILEREC), sizeof(SCRFILEREC), (char *)&d);
lockarea(scratchfile, 0, 1, F_UNLCK, TRUE);
return(thid);
}
void shinfo_changeop(char *operation)
{
write_procstr(thid, MAXNAMELEN, SCRF_CURRENTOP, DESCRIPLEN, operation);
}
void shinfo_changeuser(char *username)
{
write_procstr(thid, 0, 0, MAXNAMELEN, username);
}
void shinfo_sethost(char *hostname)
{
write_procstr(thid, MAXNAMELEN + DESCRIPLEN, SCRF_REMOTEHOST,
MAXNAMELEN, hostname);
}
int shinfo_addusergroup(char *groupname, int limit)
{
int count = 0;
int pos = 0;
int done = FALSE;
SHRMEMDATA *d;
SCRFILEREC da;
if (inetd)
{
/* must find count of users in group */
lockarea(scratchfile, 0, 1, F_WRLCK, TRUE);
pos = 0;
lseek(scratchfile, 0, SEEK_SET);
while(read(scratchfile, &da, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC))
{
if (pos != thid)
{
if (!lockarea(scratchfile, 10 + pos, 1, F_WRLCK, FALSE))
{
if (strncmp(da.groupname, groupname, MAXSECTIONLEN) == 0)
count++;
}
else
lockarea(scratchfile, 10 + pos, 1, F_UNLCK, TRUE);
}
pos++;
}
done = (count < limit);
if (done)
{
/* update scratchfile */
write_procstr(thid, 0, SCRF_GROUPNAME, MAXSECTIONLEN, groupname);
count++;
}
lockarea(scratchfile, 0, 1, F_UNLCK, TRUE);
}
else
{
/* non-inetd stuff */
lockarea(shlockfile, 0, 1, F_WRLCK, TRUE);
lockarea(shlockfile, 2, 1, F_WRLCK, TRUE);
d = SHRMEMDATPOS(shdata, shdata->numvserver);
/* find the group in the list */
while((pos < shdata->numgroups) && (!done))
{
if (strncmp(groupname, d->name, MAXSECTIONLEN) == 0)
if (d->count < d->max)
{
/* found it all */
d->count++;
count = d->count;
done = TRUE;
}
pos++;
d++;
}
if (done)
{
lockarea(shlockfile, 10 + thid, 1, F_WRLCK, TRUE);
if (procdata[thid].group != -1)
ERRORMSG("ack, inconsistency found!");
procdata[thid].group = pos - 1;
lockarea(shlockfile, 10 + thid, 1, F_UNLCK, TRUE);
}
lockarea(shlockfile, 2, 1, F_UNLCK, TRUE);
lockarea(shlockfile, 0, 1, F_UNLCK, TRUE);
}
if (done)
return(count);
else
return(-1);
}
void shinfo_setpid(int thrid, int pid)
{
if (!inetd)
procdata[thrid].pid = pid;
else
writescratch(thrid * sizeof(SCRFILEREC) + SCRF_PID,
sizeof(int), (char *)&pid);
}
void shinfo_delusergroup(char *groupname)
{
int pos = 0;
int done = FALSE;
SHRMEMDATA *d;
if (!inetd)
{
lockarea(shlockfile, 0, 1, F_WRLCK, TRUE);
lockarea(shlockfile, 2, 1, F_WRLCK, TRUE);
d = SHRMEMDATPOS(shdata, shdata->numvserver);
while((pos < shdata->numgroups) && (!done))
{
if (strncmp(groupname, d->name, MAXSECTIONLEN) == 0)
{
/* found it all */
d->count--;
done = TRUE;
}
pos++;
d++;
}
lockarea(shlockfile, 10 + thid, 1, F_WRLCK, TRUE);
procdata[thid].group = -1;
lockarea(shlockfile, 10 + thid, 1, F_UNLCK, TRUE);
lockarea(shlockfile, 2, 1, F_UNLCK, TRUE);
lockarea(shlockfile, 0, 1, F_UNLCK, TRUE);
}
else
/* update scratchfile */
writescratch(thid * sizeof(SCRFILEREC) + SCRF_GROUPNAME,
5, "none");
}
void shinfo_freebynum(int threadnum)
{
SHRMEMDATA *d;
/* free and update pointers */
lockarea(shlockfile, 0, 3, F_WRLCK, TRUE);
lockarea(shlockfile, 10 + threadnum, 1, F_WRLCK, TRUE);
if (procdata[threadnum].group >= 0)
{
d = SHRMEMDATPOS(shdata, shdata->numvserver + procdata[threadnum].group);
d->count--;
}
shdata->serverusercount--;
if ((shdata->numvserver > 0) && (procdata[threadnum].vserver >= 0))
{
d = SHRMEMDATPOS(shdata, procdata[threadnum].vserver);
d->count--;
}
procdata[threadnum].group = -1;
procdata[threadnum].vserver = -1;
procdata[threadnum].pid = -nextthid;
procdata[threadnum].ip = -1;
nextthid = threadnum;
lockarea(shlockfile, threadnum + 10, 1, F_UNLCK, TRUE);
lockarea(shlockfile, 0, 3, F_UNLCK, TRUE);
}
void shinfo_freethreads(int freecount, pid_t *freelist)
{
int count = 0;
int listcount;
/* sort the freelist */
for(listcount = 0; listcount < freecount; listcount++)
{
count = 0;
while(count < shdata->numrecs)
{
/* pid is safe since parent process is the only
modifier */
if (procdata[count].pid == freelist[listcount])
shinfo_freebynum(count);
count++;
}
}
}
void pnums_signalchildren(int signalnum)
{
int count;
for (count = 0; count < shdata->numrecs; count++)
if (procdata[count].pid > 0)
kill((pid_t)procdata[count].pid, signalnum);
}
void shinfo_shutdown(void)
{
if (!inetd)
{
/* kill the children kindly */
pnums_signalchildren(SIGTERM);
shmem_finish(shnumber);
close(shlockfile);
}
else
close(scratchfile);
}
syntax highlighted by Code2HTML, v. 0.9.1