/* Copyright (C) 1999 Beau Kuiper
   this is a quick hack for ratiotool, works ok but that is all
   warning, this code is not buffer overflow safe. DONT RUN AS 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"

#define BYTERATIOMSK 1
#define FILERATIOMSK 2
#define BYTECREDITMSK 4
#define FILECREDITMSK 8

#define RELATIVE TRUE
#define ABSOLUTE FALSE

int lockarea(int fd, int pos, int len, int locktype, int do_wait)
{
	struct flock lock;
	register int err;
	int lockt;
	
	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;
	NOSIGNALINTR(err = fcntl(fd, lockt, &lock));
	
	if (err == -1)
	{
		if (errno != EAGAIN)
			exit(0);
		else
			return(FALSE);
	}
	return(TRUE);
}

void lock_n_read(RATIOHANDLE *rh)
{
	if (rh->ratiofilefd != 0)
	{
		lockarea(rh->ratiofilefd, rh->filepos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE);
		lseek(rh->ratiofilefd, rh->filepos, SEEK_SET);
		read(rh->ratiofilefd, rh->rdat, sizeof(RATIOFILEDATA));
	}
}

void write_n_unlock(RATIOHANDLE *rh)
{
	if (rh->ratiofilefd != 0)
	{
		lseek(rh->ratiofilefd, rh->filepos, SEEK_SET);
		write(rh->ratiofilefd, rh->rdat, sizeof(RATIOFILEDATA));
		lockarea(rh->ratiofilefd, rh->filepos, sizeof(RATIOFILEDATA), F_UNLCK, TRUE);
	}
}

int decoderatiostr(char *ratstr, int *pa, int *pb)
{
	return(sscanf(ratstr, "%d:%d", pa, pb) == 2);
}

RATIOHANDLE *getuserrh(char *filename, char *username)
{
	static RATIOHANDLE rh;
	static RATIOFILEDATA filedata;
	int pos = 0;
	
	rh.rdat = &filedata;
	rh.ratiofilefd = open(filename, O_RDWR);
	if (rh.ratiofilefd == -1)
		ERRORMSGFATAL("couldn't open ratio file!");

	lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE); 
	
	while(read(rh.ratiofilefd, &filedata, sizeof(RATIOFILEDATA)) ==
	      sizeof(RATIOFILEDATA))
	{
		lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_UNLCK, TRUE);

		if (strcmp(filedata.username, username) == 0)
		{
			rh.filepos = pos;
			return(&rh);
		}

		pos += sizeof(RATIOFILEDATA);
		lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE);
	}
	close(rh.ratiofilefd);
	return(NULL);
}

/* this just returns a pointer to the end of the file, and checks to make user
   the user doesn't exist yet */
  
RATIOHANDLE *adduserrh(char *filename, char *username)
{
	static RATIOHANDLE rh;
	static RATIOFILEDATA filedata;
	int pos = 0;
	
	rh.rdat = &filedata;
	rh.ratiofilefd = open(filename, O_RDWR | O_CREAT, 0600);
	if (rh.ratiofilefd == -1)
		ERRORMSGFATAL("couldn't open ratio file!");

	lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE); 
	
	while(read(rh.ratiofilefd, &filedata, sizeof(RATIOFILEDATA)) ==
	      sizeof(RATIOFILEDATA))
	{
		lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_UNLCK, TRUE);

		if (strcmp(filedata.username, username) == 0)
		{
			close(rh.ratiofilefd);
			return(NULL);
		}

		pos += sizeof(RATIOFILEDATA);
		lockarea(rh.ratiofilefd, pos, sizeof(RATIOFILEDATA), F_WRLCK, TRUE);
	}
	rh.filepos = pos;
	return(&rh);
}

void usage(void)
{
	printf("\nMuddleftpd Ratio File Editor.\n\n");
	printf("Usage: ratiotool [options]\n\n");
	printf("	-f ratiofile	The filename of the ratio file to modify\n");
	printf("	-a username	Adds a new username\n");
	printf("	-e username	Edits the properties of a username\n");
	printf("	-d username	Scrubs the username from the ratio file\n");
	printf("	-r fileratio	Sets a new file ratio for the user.\n");
	printf("	-R byteratio	Sets a new byte ratio for the user.\n");
	printf("	-c files	Sets a new file credit for the user.\n");
	printf("	-C bytes	Sets a new byte credit for the user.\n");
	printf("	-i username	Gets information about user.\n");
	printf("	-h		Displays this usage message.\n");
	printf("	-V		Displays version information.\n\n");

	printf("You must specify -f and only one of -a, -e, -d or -i when running ratiotool\n\n");
	exit(0);
}

void showinfo(RATIOHANDLE *rh)
{
	printf("Showing ratio information for user '%s'\n", rh->rdat->username);
	printf("\nThis user has the following download credits:\n\n");
	if (rh->rdat->flags & BYTECREDITS)
		printf("%15lld bytes\n", rh->rdat->downloadcredits / rh->rdat->byteoutmult);
	if (rh->rdat->flags & FILECREDITS)
		printf("%15d files\n", rh->rdat->filecredits / rh->rdat->fileoutmult);
	if (rh->rdat->flags == 0)
		printf("This person does not use ratios and has no credits\n");
	printf("\nThis user is subject to the following ratios:\n\n");
	if (rh->rdat->flags & BYTECREDITS)
		printf("Byte ratio: %d:%d (ie %d bytes uploaded = %d download bytes)\n", 
			rh->rdat->byteinmult, rh->rdat->byteoutmult,
			rh->rdat->byteoutmult, rh->rdat->byteinmult);
	if (rh->rdat->flags & FILECREDITS)
		printf("File ratio: %d:%d (ie %d files uploaded = %d download files)\n", 
			rh->rdat->fileinmult, rh->rdat->fileoutmult,
			rh->rdat->fileoutmult, rh->rdat->fileinmult);
	if (rh->rdat->flags == 0)
		printf("This person does not use ratios.\n");
	printf("\n\n");
}

void adduser(char *rationame, char *username, char *byteratio, char *fileratio,
	     char *filecount, char *bytecount)
{
	int flags = 0;
	int upb, downb, upf, downf;
	int initalf;
	long long int initalb;
	RATIOHANDLE *rh;
	
	if (byteratio == NULL)
	{
		byteratio = (char *)malloc(4096);
		printf("Enter a byte ratio for the user (down:up or 'none') : ");
		fgets(byteratio, 4096, stdin);
		byteratio[strlen(byteratio) - 1] = 0;
	}
	if (fileratio == NULL)
	{
		fileratio = (char *)malloc(4096);
		printf("Enter a file ratio for the user (down:up or 'none') : ");
		fgets(fileratio, 4096, stdin);
		fileratio[strlen(fileratio) - 1] = 0;
	}	

	if (strcasecmp(byteratio, "none") != 0)
		flags |= BYTECREDITS;
	if (strcasecmp(fileratio, "none") != 0)
		flags |= FILECREDITS;
	
	if (flags & BYTECREDITS)
	{
		if (bytecount == NULL)
		{
			bytecount = (char *)malloc(4096);
			printf("Enter an inital byte credit for the user (bytes) : ");
			fgets(bytecount, 4096, stdin);
			bytecount[strlen(bytecount) - 1] = 0;
		}	
		if (sscanf(byteratio, "%d:%d", &upb, &downb) != 2)
			ERRORMSGFATAL("could not decode byte ratio");
		if (sscanf(bytecount, "%lld", &initalb) != 1)
			ERRORMSGFATAL("could not decode inital byte credits");
		initalb *= downb;
	}
	
	if (flags & FILECREDITS)
	{
		if (filecount == NULL)
		{
			filecount = (char *)malloc(4096);
			printf("Enter a inital file credit for the user : ");
			fgets(filecount, 4096, stdin);
			filecount[strlen(filecount) - 1] = 0;
		}
		if (sscanf(fileratio, "%d:%d", &upf, &downf) != 2)
			ERRORMSGFATAL("could not decode file ratio");
		printf("%d\n", sscanf(filecount, "%d", &initalf));
	
		if (sscanf(filecount, "%d", &initalf) != 1)
			ERRORMSGFATAL("could not decode inital file credits");
		initalf *= downf;
	}
	
	rh = adduserrh(rationame, username);
	if (rh == NULL)
		ERRORMSGFATAL("user by that name already exists in the ratio file");
	
	lock_n_read(rh);
	memset(rh->rdat, 0, sizeof(RATIOFILEDATA));
	strncpy(rh->rdat->username, username, MAXNAMELEN);	
	rh->rdat->flags = flags;
	if (flags & FILECREDITS)
	{
		rh->rdat->filecredits = initalf;
		rh->rdat->fileinmult = (short int)upf;
		rh->rdat->fileoutmult = (short int)downf;
	}
	if (flags & BYTECREDITS)
	{
		rh->rdat->downloadcredits = initalb;
		rh->rdat->byteinmult = (short int)upb;
		rh->rdat->byteoutmult = (short int)downb;
	}
	write_n_unlock(rh);
	close(rh->ratiofilefd);
	printf("Adding user to ratio file successful!\n");
}

void edituser(char *rationame, char *username, char *byteratio, char *fileratio,
	      char *filecount, char *bytecount, int edit_mask)
{
	int upb, downb, upf, downf;
	int initalf;
	int modf = 0;
	int modb = 0;
	long long int initalb;
	RATIOHANDLE *rh;

#define BYTERATIOMSK 1
#define FILERATIOMSK 2
#define BYTECREDITMSK 4
#define FILECREDITMSK 8
	
	/* decode arguments */
	if (edit_mask & BYTERATIOMSK)
		if (strcasecmp(byteratio, "none") != 0)
			if (sscanf(byteratio, "%d:%d", &upb, &downb) != 2)
				ERRORMSGFATAL("could not decode byte ratio");

	if (edit_mask & BYTECREDITMSK)
	{
		if (bytecount[0] == 'r')
		{
			bytecount++;
			modb = RELATIVE;
		}
		else
			modb = ABSOLUTE;
		if (sscanf(bytecount, "%lld", &initalb) != 1)
			ERRORMSGFATAL("could not decode byte credits");
	}
	
	if (edit_mask & FILERATIOMSK)
		if (strcasecmp(fileratio, "none") != 0)
			if (sscanf(fileratio, "%d:%d", &upf, &downf) != 2)
				ERRORMSGFATAL("could not decode file ratio");
	
	if (edit_mask & FILECREDITMSK)
	{
		if (filecount[0] == 'r')
		{
			modf = RELATIVE;
			filecount++;
		}
		else
			modf = ABSOLUTE;
		if (sscanf(filecount, "%d", &initalf) != 1)
			ERRORMSGFATAL("could not decode file credits");
	}
	
	rh = getuserrh(rationame, username);
	if (rh == NULL)
		ERRORMSGFATAL("Username is not in ratio file.");
	
	lock_n_read(rh);
	
	if (edit_mask & BYTERATIOMSK)
	{
		if (strcasecmp(byteratio, "none") == 0)
			rh->rdat->flags &= FILECREDITS;
		else
		{
			/* this updates the ratio and the credits */
			rh->rdat->flags |= BYTECREDITS;
			rh->rdat->downloadcredits /= rh->rdat->byteoutmult;
			rh->rdat->byteinmult = (short int)upb;
			rh->rdat->byteoutmult = (short int)downb;
			rh->rdat->downloadcredits *= rh->rdat->byteoutmult;
		}
	}
	
	if (edit_mask & FILERATIOMSK)
	{
		if (strcasecmp(fileratio, "none") == 0)
			rh->rdat->flags &= BYTECREDITS;
		else
		{
			/* this updates the ratio and the credits */
			rh->rdat->flags |= FILECREDITS;
			rh->rdat->filecredits /= rh->rdat->fileoutmult;
			rh->rdat->fileinmult = (short int)upf;
			rh->rdat->fileoutmult = (short int)downf;
			rh->rdat->filecredits *= rh->rdat->fileoutmult;
		}
	}
	
	if (edit_mask & BYTECREDITMSK)
	{
		if (modb == ABSOLUTE)
			rh->rdat->downloadcredits = initalb * rh->rdat->byteoutmult;
		else
			rh->rdat->downloadcredits += initalb * rh->rdat->byteoutmult;
	}		
	if (edit_mask & FILECREDITMSK)
	{
		if (modf == ABSOLUTE)
			rh->rdat->filecredits = initalf * rh->rdat->fileoutmult;
		else
			rh->rdat->filecredits += initalf * rh->rdat->fileoutmult;
	}		

	write_n_unlock(rh);
	close(rh->ratiofilefd);
	printf("Editing user in ratio file successful!\n");
}

void deluser(char *ratiofile, char *username)
{
	RATIOHANDLE *rh;
		
	rh = getuserrh(ratiofile, username);
	if (rh == NULL)
		ERRORMSGFATAL("Username is not in ratio file!");

	lock_n_read(rh);
	rh->rdat->username[0] = 0;	
	write_n_unlock(rh);
	
	close(rh->ratiofilefd);
	printf("Deleting user from ratio file successful!\n");
}	
		
int main(int argc, char **argv)
{
	int ch;
	char *ratiofile = NULL;
	char *username = NULL;
	char *byteratio = NULL;
	char *fileratio = NULL;
	char *bytecount = NULL;
	char *filecount = NULL;
	int add_user = FALSE;
	int delete_user = FALSE;
	int edit_user = FALSE;
	int get_info = FALSE;
	int edit_mask = FALSE;
	extern char *optarg;
	
	/* stolen from smbpasswd - checking for setuid root! */
	
	/* Check the effective uid - make sure we are not setuid */
	if ((geteuid() == (uid_t)0) && (getuid() != (uid_t)0))
		ERRORMSGFATAL("ratiotool must *NOT* be setuid root.\n");
                                                   
	while ((ch = getopt(argc, argv, "f:a:d:e:r:R:c:C:i:hV")) != EOF) 
	{
		switch(ch)
		{
		case 'V':
			showversion("ratiotool");
		case 'f':
			ratiofile = optarg;
			break;
		case 'a':
			add_user = TRUE;
			username = optarg;
			break;
		case 'd':
			delete_user = TRUE;
			username = optarg;
			break;
		case 'e':
			edit_user = TRUE;
			username = optarg;
			break;
		case 'r':
			fileratio = optarg;
			edit_mask |= FILERATIOMSK;
			break;
		case 'R':
			byteratio = optarg;
			edit_mask |= BYTERATIOMSK;
			break;
		case 'c':
			filecount = optarg;
			edit_mask |= FILECREDITMSK;
			break;
		case 'C':
			bytecount = optarg;
			edit_mask |= BYTECREDITMSK;
			break;
		case 'i':
			username = optarg;
			get_info = TRUE;
			break;
		case 'h':
			usage();
			break;
		default:
			usage();
		}
	}

	if (!ratiofile)
		usage();
	
	if (add_user)
	{
		if (edit_user || delete_user || get_info)
			usage();

		adduser(ratiofile, username, byteratio, fileratio,
			filecount, bytecount);
		
		exit(0);
	} 
	
	if (delete_user)
	{
		if (edit_user || get_info)
			usage();
		
		deluser(ratiofile, username);
		exit(0);
	}

	if (edit_user)
	{
		if (!edit_mask)	
			usage();
		
		edituser(ratiofile, username, byteratio, fileratio,
			 filecount, bytecount, edit_mask);
		exit(0);
	}
	
	if (get_info)
	{
		RATIOHANDLE *rh;
		rh = getuserrh(ratiofile, username);
		if (rh == NULL)
			ERRORMSGFATAL("Username is not in ratio file!");
		showinfo(rh);
		close(rh->ratiofilefd);
		exit(0);
	}
	usage();	
	return(0);
}
