/* utils.c General utility functions 
   Copyright Beau Kuiper 1999.

   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 SHOWMALLOC

/* Wapper for malloc that syslogs and then dies if malloc fails before
   damage is done. */ 
   
int nummalloc = 0;
int doesdint = -1;
/* 			memory routines 			       *
 ***********************************************************************/
   
void *mallocwrapper(int size)
{
	void *outmem;

	outmem = (void *)malloc(size);

#ifdef SHOWMALLOC
	nummalloc++;
	printf("malloc: %d\n", nummalloc);
#endif
	
	if (outmem == NULL)
		ERRORMSGFATAL("malloc error, out of memory");
		
	return(outmem);
}

/* Wapper for remalloc that syslogs and then dies if malloc fails before
   damage is done */

void reallocwrapper(int size, void **inarea)
{
	void *outmem;

	assert(size > 0);
	
	if (*inarea == NULL)
	 	outmem = mallocwrapper(size);
	else
		outmem = (void *)realloc(*inarea, size);
	
	if (outmem == NULL)
	{
		ERRORMSG("realloc error, out of memory");
		exit(1);
	}

#if DEBUG
	if (*inarea == outmem)
	{
		outmem = mallocwrapper(size);  /* make sure memory is moved */
		memmove(outmem, *inarea, size);
		freewrapper(*inarea);     /* free the original area */
	}
#endif
	*inarea = outmem;
}
   
char *strdupwrapper(char *s)
{
	char *outstr;
	
	assert(s != NULL);
	
	outstr = malloc(strlen(s) + 2);
	
	if (outstr == NULL)
		ERRORMSGFATAL("strdup error, out of memory");

#ifdef SHOWMALLOC
	nummalloc++;
	printf("malloc: %d\n", nummalloc);
#endif
	
	strcpy(outstr, s);
	
	return(outstr);
}

void freewrapper(void *tofree)
{
	if (tofree == NULL)
	{
		ERRORMSG("Trying to free null pointer!");
	}

	free(tofree);
	
#ifdef SHOWMALLOC
	printf("free: %d\n", nummalloc);
	nummalloc--;
#endif
}

void freeifnotnull(void *tofree)
{
	if (tofree != NULL)
	{
		free(tofree);

#ifdef SHOWMALLOC
	printf("free: %d\n", nummalloc);
	nummalloc--;
#endif
	}
}

#ifndef HAVE_MEMMOVE

void *memmove(void *dest, void *src, int n)
{
	int p;
	
	assert(dest != NULL);
	assert(src != NULL);
	assert(n >= 0);
	
	if (src == dest)
		return(dest);
	
	if (src < dest)
		for(p = 0; p < n; p++)
			*(char *)(src + n) = *(char *)(dest + n);
	else
		for(p = n; p > 0; p--)
			*(char *)(src + n) = *(char *)(dest + n);	

	return(dest);
}

#endif

/* 			string routines 			       *
 ***********************************************************************/

void strtrimspace(char *string)
{
	int pos, pos2;
	
	pos2 = 0;
	pos = 0;
	while(string[pos] != 0)
	{
		if (string[pos] > 32)
		{
			string[pos2] = string[pos];
			pos2++;
		}
		pos++;
	}
	string[pos2] = 0;
}

int strchrcount(char *string, char tok)
{
	char *lastoccur = string;
	int ret = 0;
	
	while((lastoccur = strchr(lastoccur, tok)) != NULL)
	{
		lastoccur++;
		ret++;
	}
	return(ret);
}

char *safe_vsnprintf(int size, char *format, va_list ap)
{
	char *buffer = mallocwrapper(size+1);
	int result;
	
	result = vsnprintf(buffer, size+1, format, ap);
	return(buffer);
}

char *safe_snprintf(char *format, ...)
{
	va_list printfargs;
	int size = BUFFSMALL;
	char *buf = mallocwrapper(size);
	int result;

	va_start(printfargs, format);
	result = vsnprintf(buf, size, format, printfargs);
	va_end(printfargs);
	
	if (result >= size)
	{
		reallocwrapper(result+1, (void *)&buf);
		va_start(printfargs, format);
		result = vsnprintf(buf, result+1, format, printfargs);
		va_end(printfargs);
	}
	return(buf);
}

char *getcwd2(void)
{
	int size = BUFFSMALL;
	char *buffer = mallocwrapper(size);
	char *result;
	
	do
	{
		result = getcwd(buffer, size-1);
	
		if (result == NULL)
		{
			if (errno == ERANGE)
			{
				if (size > (64 * 1024))
					size += (64 * 1024);
				else
					size += size;
				reallocwrapper(size, (void *)&buffer);
			}
			else
			{
				freewrapper(buffer);
				return(NULL);
			}
		}
	} while (result == NULL);
	
	return(buffer);
}

void pathname_simplify(char *pathname)
{
	int pos, pos2;
	
	pos = 0, pos2 = 0;
	
	while(pathname[pos] != 0)
	{
		switch(pathname[pos])
		{
			case '/':	
				if (pathname[pos+1] == '/');
				else if ((strncmp(pathname + pos, "/./", 3) == 0) ||
					 (strcmp(pathname + pos, "/.") == 0))
					pos++;
				else if ((strncmp(pathname + pos, "/../", 4) == 0) ||
					 (strcmp(pathname + pos, "/..") == 0))
				{
					if (pos2 > 1)
						pos2--;
					while((pos2 > 1) && (pathname[pos2] != '/'))
						pos2--;
					pos += 2;
				}
				else
				{
					pathname[pos2] = '/';
					pos2++;			
				}
				break;		
			default:
				pathname[pos2] = pathname[pos];
			pos2++;
		}
		pos++;
	}
	
	if (pos2 == 0)
	{
		pathname[pos2] = '/';
		pos2++;
	}
	
	pathname[pos2] = 0;
}	

/* This is meant to be quick */

char *offt_tostr(off_t size)
{
	/* the divide should be optimized away automagicly */
	static char sizestr[sizeof(off_t) * 4];
	char *l = sizestr + (sizeof(off_t) * 4 - 1);
	off_t esize = size;
	
	*l = 0;
	l--;
	
	do
	{
		*l = '0' + (esize % 10);
		l--;
		esize /= 10;
	}
	while(esize > 0);

	if (size < 0)
	{
		*l = '-';
		l--;
	}
	return(l + 1);
}

int strto_offt(char *str, off_t *ret)
{
	off_t r;
	char *l = str;
	int neg = (*l == '-');
	
	r = 0;
	if (*l == '-')
		l++;
	
	while((*l >= '0') && (*l <= '9'))
	{
		r = r * 10 + (*l - '0');
		l++;
	}
	// error if not end of string
	if (*l != 0)
		return(-1);
	
	*ret = (neg ? -r : r);
	return(0);
}
					
void test_libc(int verbose)
{
	char instr[15];
	int result;
	long long a;
	
	if (verbose)
		printf("Testing libraries!!\n");
	
	/* test snprintf schematics */
	
	if (verbose)
		printf("Now, does snprintf work  . . . . . . . . ");
	
	memset(instr, 0, 15);
	/* this will overflow the string */
	result = snprintf(instr, 10, "test %s", "testing");
	if (instr[10] != 0)
	{
		if (verbose) printf("No\n");
		ERRORMSGFATAL("snprintf is broken! Cannot continue!");
	}
	if (verbose)
		printf("Yes\n");
		
	/* testing double int support */
	
	if (verbose)
		printf("Now, can I use double ints . . . . . . . ");

	a = 1000000000000LL;
	sprintf(instr, "%lld", a);
	sscanf(instr, "%lld", &a);
	
	if ((strcmp(instr, "1000000000000") == 0) && (a == 1000000000000LL))
	{
		if (verbose) printf("Yes\n");
		doesdint = TRUE;
	}
	else
	{
		if (verbose) printf("No, ratio support disabled\n");
		doesdint = FALSE;
	}
	
	if ((!doesdint) && (sizeof(long long) == sizeof(off_t)))
		ERRORMSGFATAL("snprintf doesn't support double int but needed to do file lengths");
}
                                                
#ifndef HAVE_USLEEP

int usleep(int usecs)
{
	struct timeval tv;
	
	tv.tv_sec = usecs / 1000000;
	tv.tv_usec = usecs % 1000000;
	
	return(select(0, NULL, NULL, NULL, &tv));
}
 
#endif

/*			hash functions				       *
 ***********************************************************************/
 


/* 			string cache routines 			       *
 ***********************************************************************/

STRCACHE *strcache_new(void)
{
	STRCACHE *cache = mallocwrapper(sizeof(STRCACHE));
	
	memset(cache, 0, sizeof(STRCACHE));
	return(cache);
}	

char *strcache_check(STRCACHE *cache, int num)
{
	int counter = 0;
	
	while (counter < cache->size)
	{
		if (cache->data[counter].num == num)
			return(cache->data[counter].str);
		counter++;
	}
	
	return(NULL);
}

void strcache_add(STRCACHE *cache, int num, char *str)
{
	if (cache->size < STRCACHESIZE)
	{
		cache->data[cache->size].num = num;
		cache->data[cache->size].str = strdupwrapper(str);
		(cache->size)++;
	}
}

void strcache_free(STRCACHE *cache)
{
	int counter = 0;
	
	while (counter < cache->size)
	{
		freewrapper(cache->data[counter].str);
		counter++;
	}
	freewrapper(cache);
}


/* 			token set routines 			       *
 ***********************************************************************/

TOKENSET *tokenset_new(void)
{
	TOKENSET *newset = mallocwrapper(sizeof (TOKENSET));

	memset(newset, 0, sizeof(TOKENSET));
	return(newset);
}

void tokenset_settoken(TOKENSET *tset, unsigned char tok, char *data)
{
	int token = (int)tok;
	
	assert(token < 128);
	assert(tset != NULL);

/*	printf("adding a token %d %s\n", token, data); */
		
	if(tset->params[token] != NULL)
		freewrapper(tset->params[token]);
	
	tset->params[token] = data;
}

void tokenset_deltoken(TOKENSET *tset, unsigned char tok)
{
	int token = (int)tok;

	assert(token < 128);
	assert(tset != NULL);

	if (tset->params[token] != NULL)
		freewrapper(tset->params[token]);
	
	tset->params[token] = NULL;
}

char *tokenset_apply(TOKENSET *tok, char *inputstr, int escapecookies)
{
	int instrlen;
	char *outstring;
	int inpos = 0;
	int outpos = 0;
	int outstrlen;
	
	if (inputstr == NULL)
		return(NULL);
		
	instrlen = strlen(inputstr);
	outstring = mallocwrapper(instrlen+1);	
	outstrlen = instrlen + 1;
	
	while(inpos < instrlen)
	{
		/* WARNING: This code contains strange things. Look carefully
			before frobing */
			
		if (inputstr[inpos] != '%')
			outstring[outpos++] = inputstr[inpos++];
		else if (inputstr[inpos + 1] == '%')
		{
			outstring[outpos++] = '%';
			inpos += 2;
		}
		else if (inputstr[inpos + 1] == '(')
		{
			int a, b, strl, pc, allcookie = FALSE;
			
			/* try X to n format */
			
			if (sscanf(inputstr + inpos, "%%(%d,*)", &a) == 1)
				allcookie = TRUE;
			else if (sscanf(inputstr + inpos, "%%(%d,%d)", &a, &b) < 2)
				goto fail;
			
			while(inputstr[inpos++] != ')')
				if (inputstr[inpos] == 0)
					goto fail;
			pc = (int)inputstr[inpos];
			if (pc >= 128)
				goto fail;
			if (tok->params[pc] == NULL)
				goto fail;
			if (allcookie)
				b = strlen(tok->params[pc]);
			if (a < 0)
				a = 0;
			if (a > b)
				goto fail;
			strl = b - a + 1;
			if (strl > strlen(tok->params[pc]) - a)
				strl = strlen(tok->params[pc]) - a;
			inpos++;
			if (escapecookies)
				outstrlen += strl;
			outstrlen += strl;
			reallocwrapper(outstrlen+1, (void *)&outstring);
			if (escapecookies)
			{
				b = a + strl;
				while(a < b)
				{
					outstring[outpos] = '\\';
					outstring[outpos+1] = tok->params[pc][a];
					a++; outpos += 2;
				}	
			}
			else
			{
				strncpy(outstring + outpos, tok->params[pc] + a, strl);
				outpos += strl; 
			}
		}
		else
		{
			int strl, pc, a;
			
			pc = (int)inputstr[++inpos];
			if (pc >= 128)
				goto fail;
			if (tok->params[pc] == NULL)
				goto fail;
			strl = strlen(tok->params[pc]);

			outstrlen += strl;
			if (escapecookies)
				outstrlen += strl;
			
			reallocwrapper(outstrlen+1, (void *)&outstring);
			
			if (escapecookies)
			{
				for (a = 0; a < strl; a++)
				{
					outstring[outpos] = '\\';
					outstring[outpos+1] = tok->params[pc][a];
					outpos += 2;
				}	
			}
			else
			{
				strncpy(outstring + outpos, tok->params[pc], strl);
				outpos += strl;
			}
			inpos++;
		}
	}
	freewrapper(inputstr);
	outstring[outpos] = 0;
	return(outstring);

fail:
	freewrapper(outstring);

	/* log the error so it can get fixed, because it may cause security
	   problems when cookies don't parse correctly! */
	/* These logs annoy too much, I will just forget about it */
/*
	outstring = safe_snprintf("Failed to parse cookie in line: '%s'", inputstr);
	log_addentry(MYLOG_INFO, NULL, outstring);
	freewrapper(outstring);
*/
	return(inputstr);
}

void tokenset_finish(TOKENSET *tset)
{
	int count;
	
	for (count = 0; count < 128; count++)
		freeifnotnull(tset->params[count]);

	freewrapper(tset);
}

/* 			uid and gid routines 			       *
 ***********************************************************************/

/* this loads a username given a uid or a gid given a groupname */

/* this holds fd's to groupname, username files */
/* FIXME: This stuff sucks at NIS wise */

FILE *uidfile = NULL;
FILE *gidfile = NULL;

char *uid_retstr = NULL;
char *gid_retstr = NULL;

#define GROUPFILE "/etc/group"
#define USERFILE "/etc/passwd"

void init_pwgrfiles(void)
{
	if (uidfile)
		fclose(uidfile);
	if (gidfile)
		fclose(gidfile);
	uidfile = fopen(USERFILE, "r");
	gidfile = fopen(GROUPFILE, "r"); 
}

char *get_passwdname(uid_t inuid, int usefile)
{
	struct passwd *pw;

	freeifnotnull(uid_retstr);
	uid_retstr = NULL;
	
	if (!usefile)
	{
		pw = getpwuid(inuid);
		if (pw)
			uid_retstr = strdupwrapper(pw->pw_name);
		return(uid_retstr);
	}
	
	if (!uidfile)
		return(NULL);
	rewind(uidfile);
			
	while((pw = fgetpwent(uidfile)) != NULL)
		if (pw->pw_uid == inuid)
		{
			uid_retstr = strdupwrapper(pw->pw_name);
			return(uid_retstr);
		}
	return(NULL);
}

char *get_groupname(gid_t ingid, int usefile)
{
	struct group *gr;

	freeifnotnull(gid_retstr);
	gid_retstr = NULL;
	
	if (!usefile)
	{
		gr = getgrgid(ingid);
		if (gr)
			gid_retstr = strdupwrapper(gr->gr_name);
		return(gid_retstr);
	}
	
	if (!gidfile)
		return(NULL);
	rewind(gidfile);
		
	while((gr = fgetgrent(gidfile)) != NULL)
		if (gr->gr_gid == ingid)
		{
			gid_retstr = strdupwrapper(gr->gr_name);
			return(gid_retstr);
		}
	return(NULL);
}

/* this must not be called in chroot jail. I shall use plain old getgrent
   so nis works */

gid_t *getusergrouplist(char *username)
{
	gid_t *retlist = mallocwrapper(sizeof(gid_t));
	struct group *gr;
	int count = 0;
	
	/* make sure info isn't stale */
	endgrent();	
	setgrent();
	
	retlist[0] = 0;
	if (!gidfile)
		return(retlist);
	rewind(gidfile);
	while((gr = getgrent()) != NULL)
	{
		int count2 = 0;
		while(gr->gr_mem[count2] != NULL)
		{
			if (strcmp(gr->gr_mem[count2], username) == 0)
				if ((retlist[count] != gr->gr_gid) || (count == 0))
				{
					count++;
					reallocwrapper(sizeof(gid_t) * (count + 1), (void *)&retlist);
					retlist[count] = gr->gr_gid;
					retlist[0]++;
				}
			count2++;
		}
	}
	return(retlist);
}

gid_t *newgidlist(void)
{
	gid_t *retlist = mallocwrapper(sizeof(gid_t));
	retlist[0] = 0;
	return(retlist);
}

gid_t *addgidlist(gid_t *list, gid_t new)
{
	list[0]++;
	reallocwrapper(sizeof(gid_t) * (list[0] + 1), (void *)&list);
	list[list[0]] = new;
	return(list);
}

void delgidlist(gid_t *list, gid_t old)
{
	int pos, pos2;
	int count;
	
	pos = 1;
	pos2 = 1;
	count = list[0];
	
	while(pos <= count)
	{
		if (list[pos] != old)
		{
			list[pos2] = list[pos];
			pos2++;
		}
		else
			list[0]--;
		pos++;
	}
}

gid_t *parsegidlist(char *str)
{
	gid_t *list = newgidlist();
	char *pos = str;
	int newval;
	gid_t new;
	int result;
	int do_remove;

	strtrimspace(str);
	
	if (strcmp(str, "*") == 0)
		return(list);
	
	do
	{
		if (strcmp(pos, "") == 0)
			break;
			
		if (*pos == '!')
		{
			do_remove = TRUE;
			pos++;
		}
		else
			do_remove = FALSE;
		
		if (sscanf(pos, "%d", &newval) == 1)
		{
			new = (gid_t)newval;
			if (do_remove)
				delgidlist(list, new);
			else
				list = addgidlist(list, new);
		}
		else
		{
			list[0] = 0;
			return(list);
		}
		
		pos = strchr(pos, ',');
		result = (pos == NULL);
		pos++;
	}
	while(!result);
	
	return(list);
}
	
char *makegidliststr(gid_t *list)
{
	int count;
	char *ret = mallocwrapper(12 * list[0] + 1);
	char *pos = ret;
	
	strcpy(ret, "");
	for (count = 1; count <= list[0]; count++)
	{
		sprintf(pos, "%u,", list[count]);
		
		while(*pos != 0)
			pos++;
	}
	if (*(pos-1) == ',')
		*(pos-1) = 0;
	return(ret);	
}

void kill_uidgidfiles(void)
{
	if (uidfile)
		fclose(uidfile);
	if (gidfile)
		fclose(gidfile);
	freeifnotnull(gid_retstr);
	freeifnotnull(uid_retstr);
}

int isfilesafe(int fd)
{
	struct stat buf;
	
	fstat(fd, &buf);
	
	if ((buf.st_uid == geteuid()) && (buf.st_gid == getegid()) &&
	        (!(buf.st_mode & 0022)))
		return TRUE;

	return(FALSE);
}

/*			limiter functions				*
 *		This is for the bandwidth limiter			*
 ************************************************************************/
 
LIMITER *limiter_new(int maxspeed)
{
	LIMITER *new;
	struct timezone tz;
	
	new = mallocwrapper(sizeof(LIMITER));
	new->maxspeed = maxspeed;
	new->bytes_transfered = 0;
	gettimeofday(&(new->current_time), &tz); 
	
	return(new);
}

void limiter_add(LIMITER *l, int byte_count, int force)
{
	int dif;
	struct timeval tv;
	struct timezone tz;
	
	l->bytes_transfered += byte_count;

	/* if at least 1 second of data is downloaded, assess the situation
	   and determine how much time to wait */
	if ((l->bytes_transfered >= l->maxspeed) || force)
	{
		gettimeofday(&tv, &tz); 
		dif = (tv.tv_sec - l->current_time.tv_sec) * 1000 
			+ (tv.tv_usec - l->current_time.tv_usec) / 1000;
		dif = (((1000 * l->bytes_transfered) / l->maxspeed) - dif) * 1000;

		/* if usleep takes too long, this will compensate by
		   putting the expected time after usleep into l->current_time
		   instead of reading the real time after an inaccurate
		   usleep, allowing the transfer to catch up */
		memcpy(&(l->current_time), &tv, sizeof(struct timeval));
		l->current_time.tv_usec += (dif % 1000000);
		l->current_time.tv_sec += (dif / 1000000);
		if (dif > 0)
			usleep(dif);
		l->bytes_transfered = 0; 
	}
}

/*	giving up root code. this will use whatever capibilites         *
 *	possible to give up root access but still be able to bind to    *
 *	a low port (if possible)					*
 ************************************************************************/
 
int giveuproot(uid_t uid, gid_t gid)
{
	int error = FALSE;
#ifdef HAVE_CAP_INIT
	cap_t currentset;

#ifdef IRIX
	cap_value_t flags[] = { CAP_PRIV_PORT };
#else
	cap_value_t flags[] = { CAP_NET_BIND_SERVICE };
#endif

#endif
	setregid(gid, gid);
	setreuid(uid, 0);

#ifdef HAVE_CAP_INIT
	/* permit us to set the CAP_NET_BIND_SERVICE capibility
	   after setuid */
	currentset = cap_init();
	cap_clear(currentset);
	cap_set_flag(currentset, CAP_PERMITTED, 1, flags, CAP_SET);
 	if (cap_set_proc(currentset) == -1)
		error = TRUE;
#endif
	/* now finish the switch */
	setuid(uid);

#ifdef HAVE_CAP_INIT
	/* set the CAP_NET_BIND_SERVICE (bind port < 1024) to the 
	   effective set */
	cap_set_flag(currentset, CAP_EFFECTIVE, 1, flags, CAP_SET);
	if (cap_set_proc(currentset) == -1)
		error = TRUE;
#endif
	return(error);
}

/*			blocking signals				*
 ************************************************************************/
 
void blockallsignals()
{
	sigset_t sig_data;
	
	sigfillset(&sig_data);
	sigprocmask(SIG_BLOCK, &sig_data, NULL);
}

void unblockallsignals()
{
	sigset_t sig_data;
	
	sigfillset(&sig_data);
	sigprocmask(SIG_UNBLOCK, &sig_data, NULL);
}

/* 			error routines					*
 ************************************************************************/

/* Sends a message to the screen and then returns to the program */

void errormsg( char *errmessage, char *file, int line )
{
	if (inetd)
		syslog(LOG_ERR, PROGNAME" error in file %s line %d: %s", file, line, errmessage);
	else
		fprintf(stderr, PROGNAME" error in file %s line %d: %s\n", file, line, errmessage);
}

/* Sends a message to the screen and exits gracefully */

void errormsgfatal( char *errmessage, char *file, int line )
{
	errormsg( errmessage, file, line );
	if (inetd)
		syslog(LOG_ERR, "muddleftpd is exiting\n");
	else
		fprintf( stderr, "CANNOT RESUME. Goodbye\n");

	/* Shutdown */
	exit(1);
}

/* 			close file descriptors 				*
 ************************************************************************/

// try to close all non-terminal file descriptors.

void fd_closeall_nonterminal(void)
{
	int count, maxfilefd = 1024;
#ifdef RLIMIT_NPROC
	struct rlimit lim;
	
	getrlimit(RLIMIT_NOFILE, &lim);
	maxfilefd = lim.rlim_max;
#endif
	for (count = 3; count < maxfilefd; count++)
		close(count);
}
