#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* Class AccessGroup
 *
 *	IP network access list management
 *
 */
#define _CLASS_AccessGroup_PRIVATE_
#include "access.h"

/* Modified the access list maintenance so exact matches (no mask bits)
 * are kept at the head of the list and permission searches end on the
 * first address match.  This has the effect of giving exact matches
 * a higher priority than wildcard matches.  This is also the way the
 * cisco access lists work.
 */

typedef struct _AccessCondition_ {
	unsigned long address;
	unsigned long mask;
	enum PERM {
		deny=0, permit=1, readonly=1,
		writeonly=2, readwrite=3
	} perm;
	struct _AccessCondition_* next;
} condition_t;

typedef struct ACCESSLISTHEAD {
	condition_t*	head;		/* first member of list */
	condition_t*	lastExact;	/* last non-wildcard member of list */
	condition_t*	tail;		/* last member of list */
} listhead_t;


#if defined (__STDC__)

	int	accessGroup_printOn (
			AccessGroup self,
			FILE* file
		);

	static int accessList_dispose (
			listhead_t* alp
		);
	static int accessList_addCondition (
			listhead_t* alp,
			condition_t* condition
		);
	static int accessList_validateAddress (
			listhead_t* alp,
			unsigned long source,
			int type
		);

	static int	accessListPrintOn (
			listhead_t* alp,
			FILE* file
		);

	static int compileAccessCondition (
			AccessGroup self,
			int	ac,
			char**	av,
			condition_t* ptr
		);
	static int accessListVerifyAddress ();
#else
	static int	accessList_dispose ();
	static int	accessList_addCondition ();
	static int	accessList_validateAddress ();

	static int	compileAccessCondition ();
#endif



/*
 *
 *	Access Group Functions
 */


/* Create new AccessGroup structure and return it to user
 */

AccessGroup
accessGroup_new ()
{
	AccessGroup self;
	extern char* calloc();

	self = (AccessGroup)calloc (1, sizeof (*self));
	return self;
}

int
accessGroup_dispose (self)
AccessGroup self;
{
	int i;
	if (self == 0)
		return 0;

	for (i = 1; i < self->max; i++) {
		if (self->list[i].head)
			accessList_dispose (&self->list[i]);
	}

	free (self);
	return 0;
}


/* Add an access list specification
 * Returns access list number (positive integer) on success.
 * Returns 0 on failure.  Consult external "char* accessError"
 * for details.
 */
int
accessGroup_add (self, argc, argv)
AccessGroup self;
int	argc;
char	**argv;
{
	condition_t condition;
	int list;

	self->accessError = 0;
	if ((list = compileAccessCondition(self, argc, argv, &condition)) != 0)
		accessGroup_addCondition (self, list, &condition);
	return list;
}


int
accessGroup_addCondition (self, list, condition)
AccessGroup self;
condition_t* condition;
int list;
{
	condition_t* cp, * lp;
	listhead_t* hp;
	char *tcp;

	/* get enough space for the condition */
	cp = (condition_t*)malloc(sizeof *condition);

	/* copy argument into allocated storage */
	*cp = *condition;

	/* See if we need to make global list bigger
	 * If so, reallocate it and clear any extra.
	 */
	if (list > self->max) {
		int newmax = list;
		int newsize, oldsize;
		listhead_t* newlist;

		if (newmax - self->max < 10)
			newmax = self->max + 10;

		newsize = (newmax+1) * sizeof (self->list[0]);
		oldsize = self->max
			? ((self->max+1) * sizeof (self->list[0]))
			: 0 ;

		/* get new space and clear it */
		newlist = (listhead_t*)malloc(newsize);
		tcp = (char*) &newlist[0];
		bzero (&tcp[oldsize], newsize-oldsize);

		/* if there used to be space, copy it */
		if (self->list) {
			bcopy ((char*)self->list, (char*)newlist, oldsize);
			free (self->list);
		}

		self->max = newmax;
		self->list = newlist;
	}
	accessList_addCondition (&self->list[list], cp);
}


/* validate that the source system named by:
 * 'source' is permitted to access the
 * given filename according to access lists.
 *
 * Return 1 if access is permitted under old default rules
 * (i.e., no access list governs); 0 if not.
 *
 * Return 2 if more particular access is permitted by an access list.
 * This gets around the old public read/writable requirements.
 */ 
int
accessGroup_validateAddress (self, list, source, type)
AccessGroup self;
int list;
unsigned long source; 
int type;
{
	listhead_t* alp;


	/* If the named list doesn't exist, use the default */
	alp = &self->list[list];
	if (list <= 0 || list > self->max || alp->head == 0) {
		self->accessError = "Validate: bad list number";
		return -1;
	}
	return accessListVerifyAddress (alp, source, type);
}


/* Debugging function */
accessGroup_printOn(self, file)
AccessGroup self;
FILE*	file;
{
	register int i;

	for (i = 1; i <= self->max; i++) {
		if (self->list[i].head != 0) {
			fprintf (file, "access list %d:\n", i);
			accessListPrintOn (&self->list[i], file);
		}
	}
}

/*
 *
 *	AccessList Functions
 *
 * These are all static, as they are only called from AccessGroup functions.
 */

/* Dispose of (free all memory) for access list <num> in access group
 * <self>
 */
static int
accessList_dispose (alp)
listhead_t* alp;
{
	condition_t* cp, * lp;


	for (cp = alp->head; cp; cp = lp) {
		lp = cp->next;
		free (cp);
	}

	alp->head = 0;
	alp->tail = 0;
	alp->lastExact = 0;

	return 0;
}

/* Add an access condition to access list <list>.
 * An access list is maintained in the order specified, except
 * that exact matches are at the front of the list and wildcard
 * matches are at the end.
 */
static int
accessList_addCondition (alp, cp)
listhead_t* alp;
condition_t* cp;
{
	condition_t*  lp;


	/* if this particular list has no conditions, put this one
	 * at the head.  Set 'lastExact' if this condition is not
	 * a wildcard.
	 */
	if ((lp = alp->tail) == 0) {
		alp->tail = alp->head = cp;
		cp->next = 0;
		if (cp->mask == 0)
			alp->lastExact = cp;
		return 0;
	}

	/* Otherwise, if a mask is not specified, add this to the
	 * end of all those conditions without masks.  This has
	 * the effect of checking all non-wildcard conditions first,
	 * and only if no match found checking the wildcard conditions.
	 */
	if (cp->mask == 0) {
		condition_t* former;

		/* The list is non-empty, and contains non-wildcard
		 * conditions.  Put this one after the last exact
		 * condition.
		 */
		if ((former = alp->lastExact) != 0) {
			cp->next = former->next;
			former->next = cp;

			/* If the last exact condition was also the
			 * end of the list, set the tail to this
			 * condition.
			 */
			if (former == lp)
				alp->tail = cp;
		}
		/* Otherwise, the list contains only wildcard conditions.
		 * Put this one at the front.
		 */
		else {
			alp->lastExact = cp;
			cp->next = alp->head;
			alp->head = cp;
		}
	}
	/* Otherwise, just append it to the tail */
	else {
		lp->next = cp;
		alp->tail = cp;
		cp->next = 0;
	}

	return 0;
}

int
accessListVerifyAddress (alp, source, type)
listhead_t*	alp;
unsigned long	source;
int		type;
{
	condition_t*	cp;
	int		permission = 0;


	/* cycle through the conditions in this access list and
	 * check against source.  Search ends on first match.
	 */
	for (cp = alp->head; cp != 0; cp = cp->next) {
		if ((source & ~cp->mask) == cp->address) {
			permission = cp->perm;
			break;
		}
	}

	/* Check permissions:
	 * If type is 'deny' refuse.
	 * If type is 'permit' return old default access permission
	 * If type is 'readonly' and asking for write permission, refuse.
	 * Otherwise accept.
	 */
	return (type & permission);
}


/* for debugging printouts */
static char* permNames[] ={ "deny", "readonly", "writeonly", "readwrite" };
#define NITEMS(X)	(sizeof(X) / sizeof (X)[0])
#define PERMNAME(N)	((unsigned)(N) >= NITEMS(permNames) \
				? "<unknown>" : permNames[N])


accessListPrintOn (alp, file)
listhead_t* alp;
FILE*	file;
{
	char* permission;
	char addrbuf[32], maskbuf[32];
	struct in_addr t;
	condition_t* list;

	list = alp->head;
	while (list != 0) {
		permission = PERMNAME(list->perm);
		t.s_addr = list->address;
		strcpy (addrbuf, inet_ntoa(t));
		t.s_addr = list->mask;
		strcpy (maskbuf, inet_ntoa(t));
		fprintf (file, " >> %s %s %s\n",
			permission, addrbuf, maskbuf);
		list = list->next;
	}
}

/* Compile a text description of an access list into
 * binary format and store it in the argument location.
 * As a side effect, set the external accessError
 * to the textual description of any format error encountered.
 *
 * It returns the list for which the condition has been
 * compiled.
 *
 * Arguments are word-parsed and packed into an argument
 * vector like that used for "main".
 */
static int
compileAccessCondition (self, ac, av, ptr)
AccessGroup self;
int	ac;
char**	av;
condition_t* ptr;
{
	int listnum;

	/* an access command must have at least 3 arguments, but
	 * not more than four.  Skip the command name itself.
	 */
	ac--, av++;
	if (ac < 2 || ac > 4) {
		self->accessError = "illegal argument count (< 2 || > 4)";
		return 0;
	}

	/* The first argument must be numeric and must be a
	 * positive number.
	 */
	listnum = atoi (*av);
	if (listnum <= 0) {
		self->accessError = "list number not a positive integer";
		return 0;
	}
	av++; ac--;

	if (ac == 1 && **av == '-' || **av == '+') {
		int addlist;
		listhead_t* hp;
		condition_t* cp;

		if (**av == '-') {
			self->accessError = "-<list> not implemented yet";
			return 0;
		}
		if (!isdigit (av[0][1]) || (addlist = atoi (&av[0][1])) < 0) {
			self->accessError =
				"+<list> requires positive integer argument";
			return 0;
		}
		hp = &self->list[addlist];
		if (addlist > self->max || hp->head == 0) {
			self->accessError = "+<list> argument not defined";
			return 0;
		}

		/* Add all the conditions in the list to the current list */
		for (cp = hp->head; cp->next ; cp = cp->next) {
			accessGroup_addCondition (self, listnum, cp);
		}
		*ptr = *cp;
		return listnum;
	}

	/* The second argument must either be "permit", "deny", "readonly"
	 * or "readwrite".  "permit" gives old-style access.
	 */
	if (strcmp (*av, "permit") == 0)
		ptr->perm = readonly;
	else if(strcmp(*av, "deny") == 0)
		ptr->perm = deny;
	else if(strcmp(*av, "readonly") == 0)
		ptr->perm = readonly;
	else if(strcmp(*av, "writeonly") == 0)
		ptr->perm = writeonly;
	else if(strcmp(*av, "readwrite") == 0)
		ptr->perm = readwrite;
	else {
		self->accessError =
			"list keyword not {permit,deny,readonly,readwrite}";
		return 0;
	}
	av++; ac--;


	/* The third argument must either be dotted decimal, or hex
	 * integer with leading "0x".
	 */
	ptr->address = inet_addr(*av);
	av++; ac--;

	/* If there is a fourth argument, it must also be either
	 * dotted decimal, or hex integer with leading "0x".
	 * If absent, mask defaults to zero.
	 */
	if (ac > 0)
		ptr->mask = inet_addr(*av);
	else
		ptr->mask = 0;

	return listnum;
}



/* Debugging functions */


#ifdef TEST

#include <stdio.h>

extern char*		cfgets();
extern int		strsplit();

main (argc, argv)
int	argc;
char**	argv;
{
	FILE* f;
	char buf[1024];
	char* vec[12];
	int nf;
	int num = 0;
	int ret;
	AccessGroup agp;

	agp = accessGroup_new();

	for (argc--,argv++; argc > 0; argc--,argv++) {
		if ((f = fopen (*argv, "r")) == NULL) {
			perror (*argv);
			continue;
		}
		while (cfgets (buf, sizeof buf, f, &num)) {
			if ((nf = strsplit (buf, vec, 12, " \t\n")) == 0)
				continue;
			if (accessGroup_add (agp, nf, vec) == 0) {
				printf ("access list error '%s': line %d\n",
					agp->accessError, num);
			}
		}
		fclose (f);
		accessGroup_printOn(agp, stdout);
	}

	printf ("Enter source internet address and list pairs:\n");
	while (fgets(buf, sizeof buf, stdin)) {
		nf = strsplit (buf, vec, 12, " \t\r\n");
		if (nf != 2) {
			printf ("bad input format: expected <addr,list>\n");
			continue;
		}
		num = atoi (vec[1]);
		ret = accessGroup_validateAddress (agp, num,
						  inet_addr(vec[0]), 3);
		printf ("validate (%d, %s, 3) returns %d\n",
			num, vec[0], ret);
		if (ret == -1) {
			printf ("validate error: '%s'\n",
				agp->accessError);
		}
	}

}

#endif


syntax highlighted by Code2HTML, v. 0.9.1