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