/*
**	acl.c	- 
**
**
** Copyright (c) 1993-95  David J. Hughes
** Copyright (c) 1995  Hughes Technologies Pty Ltd
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
** This software is provided "as is" without any expressed or implied warranty.
**
** ID = "$Id:"
**
*/


#include <stdio.h>
#include <sys/types.h>
#include <ctype.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netdb.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#ifdef WIN32
#  include <winsock.h>
#endif


#include <common/site.h>
#include <common/portability.h>
#include "msql_priv.h"




#define	ALLOW	1
#define REJECT	2

typedef	struct acc_s {
	char	name[50];
	int	access;
	struct	acc_s *next;
} acc_t;


typedef struct acl_s {
	char	db[NAME_LEN];
	acc_t	*host,
		*read,
		*write;
	tlist_t	*access,
		*option;
	struct	acl_s *next;
} acl_t;



static acl_t	*aclHead = NULL;
static int	accessPerms;

extern  char    *packet;

#define	ERR(msg)	if (verbose) printf msg 

#define	DATABASE	1
#define	READ		2
#define	WRITE		3
#define	HOST		4
#define	ACCESS		5
#define OPTION		6


static int checkToken(tok)
	char	*tok;
{
	if (strcmp(tok,"database") == 0)
		return(DATABASE);
	if (strcmp(tok,"read") == 0)
		return(READ);
	if (strcmp(tok,"write") == 0)
		return(WRITE);
	if (strcmp(tok,"host") == 0)
		return(HOST);
	if (strcmp(tok,"access") == 0)
		return(ACCESS);
	if (strcmp(tok,"option") == 0)
		return(OPTION);
	return(-1);
}



int msqlLoadAcl(verbose)
	int	verbose;
{
	acl_t	*new,
		*aclTail;
	tlist_t	*tNew,
		*tTail;
	acc_t	*accNew,
		*accTail;
	char	buf[1024],
		path[MAXPATHLEN],
		*tok;
	FILE	*fp;
	int	newEntry,
		lineNum;


	/*
	** Open the acl file
	*/
	(void)snprintf(path,MAXPATHLEN,"%s/msql.acl", 
		(char *)msqlGetCharConf("general", "inst_dir"));
	fp = fopen(path,"r");
	if (!fp)
	{
		if (verbose)
		{
			fprintf(stderr,
			   "\nWarning : No ACL file.  Using global read/write access.\n");
		}
		snprintf(packet,PKT_LEN, 
			"-1:Warning - Couldn't open ACL file\n");
		return(-1);
	}


	/*
	** Process the file
	*/
	fgets(buf,sizeof(buf),fp);
	newEntry = 1;
	lineNum = 1;
	while(!feof(fp))
	{
		tok = (char *)strtok(buf," \t\n\r=,");
		if (!tok)
		{
			/* Blank line ends a db entry */
			newEntry = 1;
			fgets(buf,sizeof(buf),fp);
			lineNum++;
			continue;
		}
			

		if (*tok == '#')
		{
			/* Comments are skipped */
			fgets(buf,sizeof(buf),fp);
			lineNum++;
			continue;
		}



		switch(checkToken(tok))
		{
		    case DATABASE:
			if (!newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet,PKT_LEN,
				   "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			newEntry = 0;
			tok = (char *)strtok(NULL," \t\n\r");
			if (!tok)
			{
				ERR(("Missing database name at line %d\n",
					lineNum));
				printf(packet,
					"-1:Missing database name at line %d\n",
					lineNum);
				fclose(fp);
				return(-1);
			}
			new = (acl_t *)malloc(sizeof(acl_t));
			(void)bzero(new,sizeof(acl_t));
			(void)strcpy(new->db,tok);
			if (aclHead)
			{
				aclTail->next = new;
				aclTail = new;
			}
			else
			{
				aclHead = aclTail = new;
			}
			break;


		    case READ:
			if (newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet, PKT_LEN,
				    "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			if (new->read)
			{
				accTail = new->read;
				while(accTail->next)
					accTail = accTail->next;
			}
			else
			{
				accTail = NULL;
			}
			tok = (char *)strtok(NULL," \t,\n\r");
			while(tok)
			{
				accNew = (acc_t *)malloc(sizeof(acc_t));
				(void)bzero(accNew,sizeof(acc_t));
				if (*tok == '-')
				{
					strcpy(accNew->name,tok+1);
					accNew->access = REJECT;
				}
				else
				{
					strcpy(accNew->name,tok);
					accNew->access = ALLOW;
				}
				if (accTail)
				{
					accTail->next = accNew;
				}
				else
				{
					new->read = accNew;
				}
				accTail = accNew;
				tok = (char *)strtok(NULL," \t,\n\r");
			}
			break;


		    case WRITE:
			if (newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet,PKT_LEN,
				    "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			if (new->write)
			{
				accTail = new->write;
				while(accTail->next)
					accTail = accTail->next;
			}
			else
			{
				accTail = NULL;
			}
			tok = (char *)strtok(NULL," \t,\n\r");
			while(tok)
			{
				accNew = (acc_t *)malloc(sizeof(acc_t));
				(void)bzero(accNew,sizeof(acc_t));
				if (*tok == '-')
				{
					strcpy(accNew->name,tok+1);
					accNew->access = REJECT;
				}
				else
				{
					strcpy(accNew->name,tok);
					accNew->access = ALLOW;
				}
				if (accTail)
				{
					accTail->next = accNew;
				}
				else
				{
					new->write = accNew;
				}
				accTail = accNew;
				tok = (char *)strtok(NULL," \t,\n\r");
			}
			break;


		    case HOST:
			if (newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet,PKT_LEN,
				    "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			if (new->host)
			{
				accTail = new->host;
				while(accTail->next)
					accTail = accTail->next;
			}
			else
			{
				accTail = NULL;
			}
			tok = (char *)strtok(NULL," \t,\n\r");
			while(tok)
			{
				accNew = (acc_t *)malloc(sizeof(acc_t));
				(void)bzero(accNew,sizeof(acc_t));
				if (*tok == '-')
				{
					strcpy(accNew->name,tok+1);
					accNew->access = REJECT;
				}
				else
				{
					strcpy(accNew->name,tok);
					accNew->access = ALLOW;
				}
				if (accTail)
				{
					accTail->next = accNew;
				}
				else
				{
					new->host = accNew;
				}
				accTail = accNew;
				tok = (char *)strtok(NULL," \t,\n\r");
			}
			break;


		    case ACCESS:
			if (newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet,PKT_LEN,
				    "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			if (new->access)
			{
				tTail = new->access;
				while(tTail->next)
					tTail = tTail->next;
			}
			else
			{
				tTail = NULL;
			}
			tok = (char *)strtok(NULL," \t,\n\r");
			while(tok)
			{
				tNew = (tlist_t *)malloc(sizeof(tlist_t));
				(void)bzero(tNew,sizeof(tlist_t));
				strcpy(tNew->name,tok);
				if (tTail)
				{
					tTail->next = tNew;
				}
				else
				{
					new->access = tNew;
				}
				tTail = tNew;
				tok = (char *)strtok(NULL," \t,\n\r");
			}
			break;


		    case OPTION:
			if (newEntry)
			{
				ERR(("Bad entry header location at line %d\n",
					lineNum));
				snprintf(packet,PKT_LEN,
				    "-1:Bad entry header location at line %d\n"
					,lineNum);
				fclose(fp);
				return(-1);
			}
			if (new->option)
			{
				tTail = new->option;
				while(tTail->next)
					tTail = tTail->next;
			}
			else
			{
				tTail = NULL;
			}
			tok = (char *)strtok(NULL," \t,\n\r");
			while(tok)
			{
				tNew = (tlist_t *)malloc(sizeof(tlist_t));
				(void)bzero(tNew,sizeof(tlist_t));
				strcpy(tNew->name,tok);
				if (tTail)
				{
					tTail->next = tNew;
				}
				else
				{
					new->option = tNew;
				}
				tTail = tNew;
				tok = (char *)strtok(NULL," \t,\n\r");
			}
			break;


		    default:
			ERR(("Unknown ACL command \"%s\" at line %d\n", 
				tok,lineNum));
			snprintf(packet,PKT_LEN,
				"-1:Unknown ACL command \"%s\" at line %d\n", 
				tok,lineNum);
			fclose(fp);
			return(-1);
			break;

		}

		fgets(buf,sizeof(buf),fp);
		lineNum++;
	}
	fclose(fp);
	return(0);
}


static int matchToken(pattern,tok)
	char	*pattern,
		*tok;
{
	register char 	*cp;
	char	*buf1,
		*buf2,
		*cp2;
	int	length1,
		length2;

	/*
	** Put everything to lower case
	*/
	buf1 = (char *)strdup(pattern);
	buf2 = (char *)strdup(tok);

	cp = buf1;
	while(*cp)
	{
		*cp = tolower(*cp);
		cp++;
	}

	cp = buf2;
	while(*cp)
	{
		*cp = tolower(*cp);
		cp++;
	}
	
	/*
	** Is this a wildcard?
	*/
	cp = pattern;
	if (*cp == '*')
	{
		if (*(cp+1) == 0)	/* match anything */
		{
			(void)free(buf1);
			(void)free(buf2);
			return(1);
		}
		length1 = strlen(cp)-1;
		length2 = strlen(tok);
		if (length1 > length2)
		{
			(void)free(buf1);
			(void)free(buf2);
			return(0);
		}
		cp2 = buf2 + length2 - length1;
		if (strcmp(cp+1,cp2) == 0)
		{
			(void)free(buf1);
			(void)free(buf2);
			return(1);
		}
		else
		{
			(void)free(buf1);
			(void)free(buf2);
			return(0);
		}
	}

	/*
	** OK, does the actual text match
	*/
	if (strcmp(buf1,buf2) == 0)
	{
		(void)free(buf1);
		(void)free(buf2);
		return(1);
	}
	else
	{
		(void)free(buf1);
		(void)free(buf2);
		return(0);
	}
}



static int matchTextList(list,tok)
	tlist_t	*list;
	char	*tok;
{
	tlist_t	*cur;

	cur = list;
	while(cur)
	{
		if (matchToken(cur->name, tok))
		{
			return(1);
		}
		cur = cur->next;
	}
	return(0);
}



static int matchAccessList(list,tok)
	acc_t	*list;
	char	*tok;
{
	acc_t	*cur;

	cur = list;
	while(cur)
	{
		if (matchToken(cur->name, tok))
		{
			if (cur->access == ALLOW)
			{
				return(1);
			}
			else
			{
				return(0);
			}
		}
		cur = cur->next;
	}
	return(0);
}



int msqlCheckAccess(db,info)
	char	*db;
	cinfo_t	*info;
{
	char 	*host,
		*user;
	struct  sockaddr *local, 
		*remote;
	acl_t	*curAcl;
	int	perms;


	host = info->host;
	user = info->user;
	local = (struct sockaddr *)&(info->local);
	remote = (struct sockaddr *)&(info->remote);
	
	/*
	** Find an ACL entry that matches this DB
	*/
	curAcl = aclHead;
	while(curAcl)
	{
		if(matchToken(curAcl->db,db))
		{
			break;
		}
		curAcl = curAcl->next;
	}

	if (!curAcl)
	{
		return(RW_ACCESS);	/* default if no specific ACL */
	}

	/*
	** Now check the connection details
	*/
	if (!host)
	{
		/* No host == local connection */
		
		if (!matchTextList(curAcl->access,"local"))
		{
			return(NO_ACCESS);
		}
	}
	else
	{
		if (!matchTextList(curAcl->access,"remote"))
		{
			return(NO_ACCESS);
		}
		if (!matchAccessList(curAcl->host,host))
		{
			return(NO_ACCESS);
		}
	}

	/*
	** Now check the access perms
	*/
	perms = 0;
	if (matchAccessList(curAcl->read, user))
	{
		perms |= READ_ACCESS;
	}
	if (matchAccessList(curAcl->write, user))
	{
		perms |= WRITE_ACCESS;
	}
	if (perms == 0)
	{
		return(NO_ACCESS);
	}


	/*
	** Now perform any options for this connection
	*/

	
	return(perms);
}



static void freeAcc(head)
	acc_t	*head;
{
	register acc_t	*curAcc,
			*prevAcc;

	curAcc = head;
	while(curAcc)
	{
		prevAcc = curAcc;
		curAcc = curAcc->next;
		(void)free(prevAcc);
	}
}

static void freeTlist(head)
	tlist_t	*head;
{
	register tlist_t	*curText,
			*prevText;

	curText = head;
	while(curText)
	{
		prevText = curText;
		curText = curText->next;
		(void)free(prevText);
	}
}


static void freeAcls()
{
	register acl_t	*curAcl,
			*prevAcl;

	curAcl = aclHead;
	while(curAcl)
	{
		freeAcc(curAcl->host);
		freeAcc(curAcl->read);
		freeAcc(curAcl->write);
		freeTlist(curAcl->access);
		freeTlist(curAcl->option);
		prevAcl = curAcl;
		curAcl = curAcl->next;
		(void)free(prevAcl);
	}
	aclHead = NULL;
}

void msqlServerReloadAcls(sock)
	int	sock;
{
	freeAcls();
	msqlLoadAcl(0);
}


void msqlSetPerms(perms)
	int	perms;
{
	accessPerms = perms;
}

int msqlCheckPerms(access)
	int	access;
{
	return( (accessPerms & access) == access);
}



int msqlCheckLocal(info)
	cinfo_t	*info;
{
	char 	*host,
		*user;
	struct  sockaddr *local, 
		*remote;

	host = info->host;
	user = info->user;
	local = (struct sockaddr *)&(info->local);
	remote = (struct sockaddr *)&(info->remote);

#ifdef WIN32
	if (local->sa_data[2] == 127 &&
	    local->sa_data[3] == 0 &&
	    local->sa_data[4] == 0 &&
	    local->sa_data[5] == 1)
	{
		/* Localhost connection */
		return(1);
	}
	return(0);
#else
	if ( (!info->host || strcmp(host,"localhost") == 0))
	{
		if (strcmp(info->user,
			(char *)msqlGetCharConf("general","admin_user"))!=0)
		{
			return(0);
		}
	}
	else
	{
		return(0);
	}
	return(1);
#endif
}
