/*
 *
 *	Copyright (C) 1988, 1989 by the Massachusetts Institute of Technology
 *    	Developed by the MIT Student Information Processing Board (SIPB).
 *    	For copying information, see the file mit-copyright.h in this release.
 *
 */
/*
 *	$Id: acl.c,v 1.18 2006-03-10 07:11:39 ghudson Exp $
 *
 * 	Routines for the manipulation of access control lists in core,
 *	along with routines to move them to and from files.
 *
 */

#ifndef __STDC__
#define const
#endif

#ifndef lint
static const char rcsid_acl_c[] =
    "$Id: acl.c,v 1.18 2006-03-10 07:11:39 ghudson Exp $";
#endif /* lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <discuss/discuss.h>
#include "internal.h"

char *acl_union(), *acl_intersection(), *acl_subtract();
static void panic ();

bool acl_check(list, principal, modes)
    dsc_acl *list;
    const char *principal;
    const char *modes;
{
	register dsc_acl_entry *ae;
	register int n;
	for (ae = list->acl_entries, n=list->acl_length;
	     n;
	     ae++, n--) {
		if ((strcmp(principal, ae->principal) == 0) ||
		    (strcmp("*", ae->principal) == 0))
			return(acl_is_subset(modes, ae->modes));
	}
	return (FALSE);
}

dsc_acl *acl_read(fd)
    int fd;
{
	static char buf[128];
	FILE *f=fdopen(dup(fd), "r");
	register char *cp;
	register int n;
	register dsc_acl_entry *ae;
	register dsc_acl *list;

	if (!f) return NULL;	/* oops. */
	
	list = (dsc_acl *) malloc(sizeof(dsc_acl));
	if (!list) goto punt;
	list->acl_entries = (dsc_acl_entry *)NULL;
	list->acl_length = 0;
	
	if (fgets(buf, 128, f) == NULL) goto punt;
	if (!isdigit((unsigned)buf[0])) goto punt;
	
	n=atoi(buf);
	list->acl_entries =
	    (dsc_acl_entry *)malloc((unsigned)(n * sizeof(dsc_acl_entry)));
	if (!list->acl_entries) goto punt;
	
	list->acl_length = 0;
	for (ae=list->acl_entries; n; --n)  {
		buf[0]=0;
		if (fgets(buf, 128, f) == NULL) goto punt;
		if(cp=strchr(buf, '\n')) *cp='\0';
		if(cp=strchr(buf, ':')) {
			*cp='\0';
			list->acl_length++;
			ae->principal = NULL;
			ae->modes = malloc((unsigned)(strlen(buf)+1));
			if (!ae->modes) goto punt;
			(void) strcpy(ae->modes, buf);
			ae->principal = malloc((unsigned)(strlen(cp+1)+1));
			if (!ae->principal) goto punt;
			(void) strcpy(ae->principal, cp+1);
			ae++;
		} else { /* skip line */
		}
	}
	(void) fclose(f);
	return(list);
punt:
	fclose(f);
	if (list) acl_destroy(list);
	return NULL;
}
/*
 * Attempt to write an access control list to fd.
 * Return FALSE on failure with reason in errno.
 */

bool acl_write(fd, list)
     int fd;
     dsc_acl *list;
{
	FILE *f=fdopen(dup(fd), "w");
	register int n;
	register dsc_acl_entry *ae;
	char buf[BUFSIZ];
	int len;

	(void) sprintf(buf, "%d\n", list->acl_length);
	len = strlen(buf);
	if (fwrite(buf, 1, len, f) != len) goto punt;
	
	for (ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		(void) sprintf(buf, "%s:%.100s\n", ae->modes, ae->principal);
		len = strlen(buf);
		if (fwrite(buf, 1, len, f) != len) goto punt;
	}
	if (fflush(f) == EOF) goto punt;
	if (fsync(fileno(f)) < 0) goto punt;
	if (fclose(f) == EOF) return FALSE;
	return(TRUE);
punt:
	fclose(f);
	return(FALSE);
}

acl_add_access(list, principal, modes)
    dsc_acl *list;
    const char *principal;
    const char *modes;
{
	register int n;
	register dsc_acl_entry *ae;
	char *new_modes;
	for(ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		if (!strcmp(ae->principal, principal)) {
			new_modes = acl_union(modes, ae->modes);
			(void) free(ae->modes);
			ae->modes=new_modes;
			return;
		}
	}
	/*
	 * Whoops.. fell through.
	 * Realloc the world - or at least the vector.
	 */
	list->acl_length++;
	list->acl_entries =
	    (dsc_acl_entry *) realloc((char *)(list->acl_entries),
				  (unsigned)(list->acl_length
					     * sizeof(dsc_acl_entry)));
	ae = list->acl_entries + list->acl_length - 2;
	/*
	 * Is the last entry "*"?  If so, push it back one.
	 */
	if (list -> acl_length > 1) {		/* non-empty acl */
	     if (!strcmp(ae->principal, "*")) {
		  if(!strcmp(principal, "*")) 
			  panic("acl broke");
		  *(ae+1) = *ae;
		  --ae;
	     }
	}
	ae++;
	ae->principal = malloc((unsigned)(strlen(principal)+1));
	(void) strcpy(ae->principal, principal);
	ae->modes = malloc((unsigned)(strlen(modes)+1));
	(void) strcpy(ae->modes, modes);
}

#ifdef notdef
acl_delete_modes(list, principal, modes)
     dsc_acl *list;
     char *principal;
     char *modes;
{
	register dsc_acl_entry *ae, *ae1;
	register int n;
	char *new_modes;
	for(ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		if (!strcmp(ae->principal, principal)) {
			new_modes = acl_subtract(modes, ae->modes);
			(void) free(ae->modes);
			ae->modes=new_modes;
			goto cleanup;
		}
	}
	return;
cleanup:
	ae1 = list->acl_entries + list->acl_length - 1;
	if ((strcmp(ae1->principal, "*")!= 0 && strcmp(ae->modes, "")) ||
	    (!strcmp(ae1->principal, "*") && !strcmp(ae->modes, ae1->modes))) {
		    (void) free(ae->principal);
		    (void) free(ae->modes);
		for (; ae <ae1; ae++) *ae= *(ae+1);
		list->acl_length--;
		list->acl_entries = (acl_entry *) realloc(list->acl_entries,
				       list->acl_length * sizeof(dsc_acl_entry));
	}

}	       
#endif /* notdef */
acl_replace_access(list, principal, modes)
    dsc_acl *list;
    const char *principal;
    const char *modes;
{
	register dsc_acl_entry *ae;
	register int n;
	for(ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		if (!strcmp(ae->principal, principal)) {
			(void) free(ae->modes);
			ae->modes = malloc((unsigned)(strlen(modes)+1));
			(void) strcpy(ae->modes, modes);
			return;
		}
	}
	/* Didn't find it.  Insert it.. the easy way. */
	acl_add_access(list, principal, modes);
}

bool
acl_delete_access(list, principal)
	dsc_acl *list;
	const char *principal;
{
	register dsc_acl_entry *ae;
	register int n;
	for(ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		if (!strcmp(ae->principal, principal)) {
			/* gotcha! */
			list->acl_length--;
			while (--n) {
				*ae= *(ae+1);
				ae++;
			}
			return TRUE;
		}
	}
	return FALSE;
}
/*
 * Return empty ACL
 */

dsc_acl *acl_create()
{
	register dsc_acl *result = (dsc_acl *) malloc(sizeof(dsc_acl));
	result->acl_length=0;
	result->acl_entries=(dsc_acl_entry *) malloc(sizeof(dsc_acl_entry));
	return(result);
}

dsc_acl *acl_copy (old_acl)
    dsc_acl *old_acl;
{
     register dsc_acl *new_acl = (dsc_acl *) malloc (sizeof(dsc_acl));
     register dsc_acl_entry *old_ae,*new_ae;
     register int n;

     new_acl -> acl_length = old_acl -> acl_length;
     new_acl -> acl_entries = (dsc_acl_entry *) malloc ((unsigned)(new_acl -> acl_length * sizeof(dsc_acl_entry)));

     for (old_ae = old_acl->acl_entries, new_ae = new_acl->acl_entries, n=new_acl->acl_length;
	  n;
	  --n, ++old_ae, ++new_ae) {
	       new_ae->principal = malloc (strlen(old_ae->principal)+1);
	       strcpy (new_ae->principal,old_ae->principal);
	       new_ae->modes = malloc (strlen(old_ae->modes)+1);
	       strcpy (new_ae->modes,old_ae->modes);
	  }
	       
     return (new_acl);
}

const char *acl_get_access(list, principal)
	dsc_acl *list;
	const char *principal;
{
	register dsc_acl_entry *ae;
	register int n;

	for(ae=list->acl_entries, n=list->acl_length; n; --n, ++ae) {
		if (!strcmp(ae->principal, principal) ||
		    !strcmp(ae->principal, "*")) {
			return(ae->modes);
		}
	}
	return("");
}
/*
 * Destroy an ACL.     
 */

acl_destroy(list)
     dsc_acl *list;
{
	register dsc_acl_entry *ae;
	register int n;
	if(!list) return;
	for (ae=list->acl_entries, n=list->acl_length;
	     ae && n;
	     --n, ++ae) {
		     if (ae->principal) (void) free(ae->principal);
		     if (ae->modes) (void) free(ae->modes);
	}
	if (ae) (void) free((char *)(list->acl_entries));
	(void) free((char *)list);
}

/*
 * Returns true if every character of s1 occurs in s2.
 */
bool acl_is_subset(s1, s2)
     register const char *s1, *s2;
{
	register char *last;
	while(*s1 && (last = strchr(s2, *s1))) s1++;

	return(last != NULL);
}

/*
 * Returns the intersection of the two strings s1 and s2.
 * This function allocates a new string, which should be freed 
 * when not needed.
 */
char *acl_intersection(s1, s2)
     register const char *s1, *s2;
{
	register char *result=malloc(1);
	register int resp=0;

	while(*s1) {
		if(strchr(s2, *s1)) {
			result[resp++] = *s1;
			result=realloc(result, (unsigned)(resp+1));
		}
		s1++;
	}
	result[resp] = '\0';
	return(result);
}

char *acl_union(s1, s2)
     register const char *s1, *s2;
{
	register int resp=strlen(s2);
	register char *result=malloc((unsigned)(resp+1));
	strcpy(result, s2);
	while(*s1) {
		if (!strchr(result, *s1)) {
			result[resp++]= *s1;
			result=realloc(result, (unsigned)(resp+1));
		}
		s1++;
	}
	result[resp]='\0';
	return(result);
}

char *acl_subtract(s1, s2)
     register const char *s1, *s2;
{
	register char *result = malloc(1);
	register int len=0;
	for (; *s2; s2++) {
		if (!strchr(s1, *s2)) {
			result = realloc(result, (unsigned)(len+2));
			result[len++]= *s2;
		}
	}
	result[len]='\0';
	return(result);
}

/*
 * Canonicalize an acl string; sort the characters of s1 into 
 * the order found in s2, removing duplicates, and returning an error code if 
 * any of them are not in s2.
 * This is a sucky algorithm, but who really gives?
 */

char *acl_canon(s1, s2, code)
	register const char *s1, *s2;
	int *code;
{
	register const char *cp;
	register char *out;
	register int len, maxlen;

	*code = 0;
	for (cp = s1; *cp; cp++) {
		if (*cp != ' ' && !strchr(s2, *cp)) 
			*code = BAD_MODES;
	}
	maxlen = strlen(s2);
	out = malloc(maxlen + 1); len = 0;
	for (cp = s2; *cp; cp++) {
		len++;
		if (len > maxlen) /* shouldn't happen, but.. */
			out = realloc(out, (unsigned)len + 1);

		out[len-1] = (strchr(s1, *cp) ? *cp : ' ');
	}
	out[len]='\0';
	return(out);
}

	


#ifdef TESTS
#include <sys/file.h>
main() 
{
	int fd;
	dsc_acl *a;

	printf("%s * %s = %s\n", "eabce", "abcd", 
	       acl_intersection("eabce", "abcd"));
	printf("%s + %s = %s\n", "abc", "cbade", acl_union("abc", "cbade"));
	printf("%s < %s = %d\n", "ab", "bcde", acl_is_subset("ab", "bcde"));
	printf("%s < %s = %d\n", "ab", "abcde", acl_is_subset("ab", "abcde"));

	fd = open ("foo.acl", O_RDONLY);
	a=acl_read(fd);
	(void) close(fd);
	printf("a for wesommer: %d\n", acl_check(a, "wesommer", "a"));
	printf("a for foobar: %d\n", acl_check(a, "foobar", "a"));
	printf("a for spook: %d\n", acl_check(a, "spook", "a"));
	printf("g for foobar: %d\n", acl_check(a, "foobar", "g"));
	printf("g for wesommer: %d\n", acl_check(a, "wesommer", "g"));
	printf("g for spook: %d\n", acl_check(a, "spook", "g"));
	printf("d for spook: %d\n", acl_check(a, "spook", "d"));
	printf("d for wesommer: %d\n", acl_check(a, "wesommer", "d"));
	acl_add_access(a, "zot", "qna");
	acl_add_access(a, "spook", "d");
	acl_add_access(a, "*", "w");
	fd = open("bar.acl", O_WRONLY+O_CREAT+O_TRUNC);
	acl_write(fd, a);
	(void) close(fd);
}
#endif /* TESTS */

static void panic(s)
	char *s;
{
	printf("%s\n", s);
	fflush(stdout);
	abort();
}
