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