/* Copyright (C) 1999 Beau Kuiper hmmm, could be setuid root. 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" int lockfd; SHRMEMHEADER *shrmemptr; #define FALSE 0 #define TRUE !FALSE char *getipstr(unsigned int server) { static char ipstr[20]; snprintf(ipstr, 20, "%d.%d.%d.%d", server >> 24, (server >> 16) & 0xFF, (server >> 8) & 0xFF, server & 0xFF); return(ipstr); } int lockarea(int fd, int pos, int len, int locktype, int do_wait) { struct flock lock; int lockt; register int err; lockt = (do_wait == TRUE ? F_SETLKW : F_SETLK); lock.l_type = locktype; lock.l_whence = SEEK_SET; lock.l_start = pos; lock.l_len = len; err = fcntl(fd, lockt, &lock); if (err == -1) { if (errno != EAGAIN) ERRORMSGFATAL(strerror(errno)); else return(FALSE); } return(TRUE); } void *shmem_connect(char *configfile) { int shmemnum; key_t ipckey = ftok(configfile, '/'); lockfd = open(configfile, O_RDWR); if (lockfd == -1) ERRORMSGFATAL(safe_snprintf("Could not open lock file, reason: %s", strerror(errno))); #ifdef DEBUG printf("[shmat_init]\n"); #endif shmemnum = shmget(ipckey, 0, 0); if (shmemnum == -1) return(NULL); shrmemptr = shmat(shmemnum, NULL, 0600); if ((int)shrmemptr == -1) ERRORMSGFATAL(safe_snprintf("Could not connect to shared memory: %s", strerror(errno))); /* get semaphores. Using the config file so others can join in (we use file locks for portability, simplicity and usability.) */ /* assume that if standalone isn't active, inetd is */ if (lockarea(lockfd, 3, 1, F_WRLCK, FALSE)) return(NULL); if (shrmemptr->magic != CURRENTMAGIC) ERRORMSGFATAL("Incorrect version!"); return(shrmemptr); } void pnums_showpid(void) { int pid; lockarea(lockfd, 0, 1, F_WRLCK, TRUE); pid = (int)shrmemptr->pid; lockarea(lockfd, 0, 1, F_UNLCK, TRUE); printf("%d\n", pid); exit(0); } void pnums_count(char *username) { SHRMEMDATA *dpos; SHRMEMHEADER *shdata; int count; shdata = mallocwrapper(SHMEMPROCSTART); /* make a working copy of the stuff we want to display so I can release the locks quickly */ lockarea(lockfd, 0, 3, F_WRLCK, TRUE); memcpy(shdata, shrmemptr, sizeof(SHRMEMHEADER) + sizeof(SHRMEMDATA) * (shrmemptr->numvserver + shrmemptr->numgroups)); lockarea(lockfd, 0, 3, F_UNLCK, TRUE); dpos = SHRMEMDATPOS(shdata, 0); if (username) { int done = FALSE; for (count = 0; count < shdata->numvserver + shdata->numgroups; count++) { if (strncmp(dpos->name, username, MAXNAMELEN) == 0) { done = TRUE; printf("%d %d\n", dpos->count, dpos->max); } dpos++; } if (!done) printf("Group/virtual server not found!\n"); } else { printf("User counts: (%d users, maximum %d users)\n", shdata->serverusercount, shdata->servermaxcount); if (shdata->numvserver > 0) { printf("Virtual Server Current Users Max Users\n"); for (count = 0; count < shdata->numvserver; count++) { printf("%-50s %-15d %d\n", dpos->name, dpos->count, dpos->max); dpos++; } printf("\n"); } printf("Group Current Users Max Users\n"); for (count = 0; count < shdata->numgroups; count++) { printf("%-50s %-15d %d\n", dpos->name, dpos->count, dpos->max); dpos++; } printf("\n"); } exit(0); } char **buildgrouplist(CONFIGFILECACHE *c, char **vserverlist) { char **temp_grouplist, **grouplist; char **vserver, **group, **currentgroup; int doadd, len; /* this is cheating, but is easier */ grouplist = (char **)mallocwrapper(sizeof(char *) * c->sectioncount); grouplist[0] = NULL; vserver = vserverlist; len = 0; while(*vserver != NULL) { temp_grouplist = makeconfiglist(c, *vserver, "group"); currentgroup = temp_grouplist; while(*currentgroup != NULL) { doadd = TRUE; group = grouplist; while (*group != NULL) { if (strcmp(*group, *currentgroup) == 0) doadd = FALSE; group++; } if (doadd) { *group = *currentgroup; group++; *group = NULL; } currentgroup++; } vserver++; } return(grouplist); } void inetd_count(CONFIGFILECACHE *c, char *username) { SCRFILEREC dat; int count, pos, sectionid, count2; int max, defmax, isvserver, found; int usercount, *vcnt, *gcnt; char **vserverlist, **grouplist; /* build required information from config file */ sectionid = getsectionid(c, "main"); loadintfromconfig(c, sectionid, "maxusers", &defmax, MAXUSERS); vserverlist = makeconfiglist(c, "main", "vserver"); if (vserverlist[0] == NULL) grouplist = makeconfiglist(c, "main", "group"); else grouplist = buildgrouplist(c, vserverlist); /* check name lengths */ count = 0; while (vserverlist[count] != NULL) { if (strlen(vserverlist[count]) >= MAXSECTIONLEN) ERRORMSGFATAL(safe_snprintf("Vserver '%s', name too long.", vserverlist[count])); count++; } count = 0; while (grouplist[count] != NULL) { if (strlen(grouplist[count]) >= MAXSECTIONLEN) ERRORMSGFATAL(safe_snprintf("Group '%s', name too long.", grouplist[count])); count++; } if (username) { /* get information about part user asked for */ sectionid = getsectionid(c, username); count = 0; isvserver = FALSE; found = FALSE; while((vserverlist[count] != NULL) && (!isvserver)) { if (strcmp(vserverlist[count], username) == 0) isvserver = TRUE; count++; } count = 0; found = isvserver; while((grouplist[count] != NULL) && (!found)) { if (strcmp(grouplist[count], username) == 0) found = TRUE; count++; } if ((sectionid == -1) || (!found)) ERRORMSGFATAL("Group/virtual server not found!\n"); loadintfromconfig(c, sectionid, "maxusers", &max, defmax); lockarea(lockfd, 0, 1, F_WRLCK, TRUE); lseek(lockfd, 0, SEEK_SET); count = 0; pos = 0; while(read(lockfd, &dat, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC)) { if (lockarea(lockfd, 10 + pos, 1, F_WRLCK, FALSE)) lockarea(lockfd, 10 + pos, 1, F_UNLCK, TRUE); else { if (isvserver) { if (strcmp(username, dat.vserver) == 0) count++; } else { if (strcmp(username, dat.groupname) == 0) count++; } } pos++; } lockarea(lockfd, 0, 1, F_UNLCK, TRUE); printf("%d %d\n", count, max); } else { vcnt = (int *)mallocwrapper(sizeof(int) * c->sectioncount * 2); gcnt = (int *)mallocwrapper(sizeof(int) * c->sectioncount * 2); memset(vcnt, 0, c->sectioncount * sizeof(int)* 2); memset(gcnt, 0, c->sectioncount * sizeof(int)* 2); /* determine maximum users */ count2 = 0; while(vserverlist[count2]) { sectionid = getsectionid(c, vserverlist[count2]); if (sectionid == -1) ERRORMSGFATAL(safe_snprintf("could not find vserver section '%s'", vserverlist[count2])); loadintfromconfig(c, sectionid, "maxusers", vcnt + (c->sectioncount + count2), defmax); count2++; } count2 = 0; while(grouplist[count2]) { sectionid = getsectionid(c, grouplist[count2]); if (sectionid == -1) ERRORMSGFATAL(safe_snprintf("could not find group section '%s'", vserverlist[count2])); loadintfromconfig(c, sectionid, "maxusers", gcnt + (c->sectioncount + count2), defmax); count2++; } lockarea(lockfd, 0, 1, F_WRLCK, TRUE); lseek(lockfd, 0, SEEK_SET); count = 0; pos = 0; usercount = 0; while(read(lockfd, &dat, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC)) { if (lockarea(lockfd, 10 + pos, 1, F_WRLCK, FALSE)) lockarea(lockfd, 10 + pos, 1, F_UNLCK, TRUE); else { usercount++; count2 = 0; while(vserverlist[count2]) { if (strcmp(vserverlist[count2], dat.vserver) == 0) vcnt[count2]++; count2++; } count2 = 0; while(grouplist[count2]) { if (strcmp(grouplist[count2], dat.groupname) == 0) gcnt[count2]++; count2++; } } pos++; } lockarea(lockfd, 0, 1, F_UNLCK, TRUE); printf("User counts: (%d users, maximum %d users)\n", usercount, defmax); if (vserverlist[0] != NULL) { printf("Virtual Server Current Users Max Users\n"); for (count = 0; vserverlist[count] != NULL; count++) printf("%-50s %-15d %d\n", vserverlist[count], vcnt[count], vcnt[c->sectioncount + count]); printf("\n"); } printf("Group Current Users Max Users\n"); for (count = 0; grouplist[count]; count++) if (gcnt[c->sectioncount + count] != 0) printf("%-50s %-15d %d\n", grouplist[count], gcnt[count], gcnt[c->sectioncount + count]); printf("\n"); } exit(0); } void pnums_listdisplay(SCRMEMREC *newdat, SHRMEMHEADER *shdata, int useips) { char *usergroupstr = NULL; SHRMEMDATA *dpos; if (newdat->group == -1) usergroupstr = safe_snprintf("not logged in", newdat->username); else if (newdat->group == -2) usergroupstr = safe_snprintf("%s/none", newdat->username); else { dpos = SHRMEMDATPOS(shdata, newdat->group + shdata->numvserver); usergroupstr = safe_snprintf("%s/%s", newdat->username, dpos->name); } if (useips) printf("%-5d %-20s %-20s %s\n", newdat->pid, usergroupstr, getipstr(newdat->ip), newdat->currentop); else printf("%-5d %-20s %-20s %s\n", newdat->pid, usergroupstr, newdat->remotehost, newdat->currentop); freewrapper(usergroupstr); } void inetd_listdisplay(STRING **s, SCRFILEREC *newdat, int useips) { char *usergroupstr = NULL; if (strcmp(newdat->groupname, "none") == 0) usergroupstr = strdupwrapper("not logged in"); else usergroupstr = safe_snprintf("%s/%s", newdat->username, newdat->groupname); if (useips) string_catprintf(s, "%-5d %-20s %-20s %s\n", newdat->pid, usergroupstr, getipstr(newdat->ip), newdat->currentop); else string_catprintf(s, "%-5d %-20s %-20s %s\n", newdat->pid, usergroupstr, newdat->remotehost, newdat->currentop); freewrapper(usergroupstr); } void pnums_list(char *username, int useips) { SCRMEMREC *dat, *newdat; SHRMEMHEADER *shdata; int count; int count2 = 0; shdata = mallocwrapper(SHMEMPROCSTART); newdat = mallocwrapper(sizeof(SCRMEMREC)); lockarea(lockfd, 0, 3, F_WRLCK, TRUE); memcpy(shdata, shrmemptr, sizeof(SHRMEMHEADER) + sizeof(SHRMEMDATA) * (shrmemptr->numvserver + shrmemptr->numgroups)); lockarea(lockfd, 0, 3, F_UNLCK, TRUE); dat = (SCRMEMREC *)((char *)shrmemptr + (SHMEMPROCSTART)); printf("Pid Username/Group Host Operation\n"); for (count = 0; count < shrmemptr->numrecs; count++) { lockarea(lockfd, 10+count, 1, F_WRLCK, TRUE); memcpy(newdat, dat, sizeof(SCRMEMREC)); lockarea(lockfd, 10+count, 1, F_UNLCK, TRUE); if (newdat->pid > 0) { if (username != NULL) { if ((strcmp(username, newdat->username) == 0) && (newdat->group != -1)) { pnums_listdisplay(newdat, shdata, useips); count2++; } } else { pnums_listdisplay(newdat, shdata, useips); count2++; } } dat++; } printf("------- %d users.\n", count2); } void inetd_list(char *username, int useips) { SCRFILEREC dat; /* I need to cache the output into memory instead of to the terminal so that the master lock on the scratchfile is held as little as possible! */ STRING *outstring = string_new(); int count; int count2 = 0; lockarea(lockfd, 0, 1, F_WRLCK, TRUE); lseek(lockfd, 0, SEEK_SET); string_catprintf(&outstring, "Pid Username/Group Host Operation\n"); count = 0; while(read(lockfd, &dat, sizeof(SCRFILEREC)) == sizeof(SCRFILEREC)) { if (lockarea(lockfd, 10 + count, 1, F_WRLCK, FALSE)) lockarea(lockfd, 10 + count, 1, F_UNLCK, TRUE); else { if (username != NULL) { if (strcmp(username, dat.username) == 0) { inetd_listdisplay(&outstring, &dat, useips); count2++; } } else { inetd_listdisplay(&outstring, &dat, useips); count2++; } } count++; } lockarea(lockfd, 0, 1, F_UNLCK, TRUE); string_catprintf(&outstring, "------- %d users.\n", count2); /* now print the output */ printf("%s", STRTOCHAR(outstring)); } void usage(char *name) { printf("ftpwho, shows who is logged in and what they are doing.\n\n"); printf("Usage: %s [-V][-h][-c configfile]\n\n", name); printf(" -V Show version information.\n"); printf(" -h Show usage information.\n"); printf(" -p Get pid of "PROGNAME" deamon.\n"); printf(" -c configfile Specify config file "PROGNAME" is running as.\n"); printf(" -C Return counts of groups and vservers.\n"); printf(" -n Return IP's instead of hostnames.\n"); printf(" -u namespec Return logins of user 'namespec' or if\n"); printf(" -C is used, counts of specific group or vserver.\n\n"); exit(1); } int main(int argc, char **argv) { CONFIGFILECACHE *cfiledata; int count = FALSE; int do_getpid = FALSE; int useips = FALSE; char *username = NULL; char *fconfig = NULL; char *scratchfile = NULL; int section, line, error; void *sharea; int ch; int saved_uid = geteuid(); int saved_gid = getegid(); int extraperms = FALSE; extern char *optarg; if ((saved_uid != getuid()) || (saved_gid != getgid())) extraperms = TRUE; while((ch = getopt(argc, argv, "Vc:hu:Cnp")) != EOF) { switch(ch) { case 'V': showversion("ftpwho"); case 'c': fconfig = optarg; break; case 'u': username = optarg; break; case 'C': count = TRUE; break; case 'n': useips = TRUE; break; case 'p': do_getpid = TRUE; break; case 'h': default: usage(argv[0]); } } if (fconfig == NULL) fconfig = CONFIGFILE; if ((do_getpid && count) || (do_getpid && username)) { printf("invalid arguments.\n"); usage(argv[0]); } cfiledata = loadconfigcache(fconfig, &line, &error); /* obtain scratchfile name */ if (cfiledata == NULL) ERRORMSGFATAL(safe_snprintf("Could not load line %d of config file: %s", line, config_errorstr(error))); section = getsectionid(cfiledata, "main"); if (section == -1) ERRORMSGFATAL("Could not find main section in config file"); loadstrfromconfig(cfiledata, section, "scratchfile", &scratchfile, SCRATCHFILE); if (scratchfile[0] != '/') ERRORMSGFATAL("Scratchfile is not a valid absolute filename"); sharea = shmem_connect(scratchfile); if (sharea != NULL) { if (do_getpid) pnums_showpid(); else if (count) pnums_count(username); else pnums_list(username, useips); } else { if (do_getpid) ERRORMSGFATAL("No parent pid in inetd mode."); else if (count) inetd_count(cfiledata, username); else inetd_list(username, useips); } return(0); }