From 39bfb62bd1a01d88f661ad94d96dab68bb79fd16 Mon Sep 17 00:00:00 2001 From: Kenneth G Raeburn Date: Tue, 13 Nov 1990 12:03:45 +0000 Subject: Initial revision --- server/acl_files.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 server/acl_files.c (limited to 'server/acl_files.c') diff --git a/server/acl_files.c b/server/acl_files.c new file mode 100644 index 0000000..203274c --- /dev/null +++ b/server/acl_files.c @@ -0,0 +1,584 @@ +/* This file is part of the Project Athena Zephyr Notification System. + * It contains functions for maintaining Access Control Lists. + * + * Created by: John T. Kohl + * + * $Source$ + * $Author$ + * + * Copyright (c) 1987,1988 by the Massachusetts Institute of Technology. + * For copying and distribution information, see the file + * "mit-copyright.h". + */ + +/* Define this if you really want the ACL-writing code included. */ +/* #define WRITE_ACL */ + +/* + * Stolen from lib/acl_files.c because acl_load needs to be externally + * declared and not statically declared. + */ + +#include + +#ifndef lint +static char rcsid_acl_files_c[] = "$Id$"; +#endif lint + +/*** Routines for manipulating access control list files ***/ + +#include +#include +#include +#include +#include +#include /* for MAXHOSTNAMELEN */ + +/* "aname.inst@realm" */ +#define MAX_PRINCIPAL_SIZE (ANAME_SZ + INST_SZ + REALM_SZ + 3) +#define INST_SEP '.' +#define REALM_SEP '@' + +#define LINESIZE 2048 /* Maximum line length in an acl file */ + +#define NEW_FILE "%s.~NEWACL~" /* Format for name of altered acl file */ +#define WAIT_TIME 300 /* Maximum time allowed write acl file */ + +#define CACHED_ACLS 64 /* How many acls to cache */ +#define ACL_LEN 256 /* Twice a reasonable acl length */ + +#define MAX(a,b) (((a)>(b))?(a):(b)) +#define MIN(a,b) (((a)<(b))?(a):(b)) + +#define COR(a,b) ((a!=NULL)?(a):(b)) + +extern int errno; + +extern char *malloc(), *calloc(); +extern time_t time(); + +/* Canonicalize a principal name */ +/* If instance is missing, it becomes "" */ +/* If realm is missing, it becomes the local realm */ +/* Canonicalized form is put in canon, which must be big enough to hold + MAX_PRINCIPAL_SIZE characters */ +acl_canonicalize_principal(principal, canon) +char *principal; +char *canon; +{ + char *dot, *atsign, *end; + int len; + + dot = index(principal, INST_SEP); + atsign = index(principal, REALM_SEP); + + /* Maybe we're done already */ + if(dot != NULL && atsign != NULL) { + if(dot < atsign) { + /* It's for real */ + /* Copy into canon */ + strncpy(canon, principal, MAX_PRINCIPAL_SIZE); + canon[MAX_PRINCIPAL_SIZE-1] = '\0'; + return; + } else { + /* Nope, it's part of the realm */ + dot = NULL; + } + } + + /* No such luck */ + end = principal + strlen(principal); + + /* Get the principal name */ + len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal); + strncpy(canon, principal, len); + canon += len; + + /* Add INST_SEP */ + *canon++ = INST_SEP; + + /* Get the instance, if it exists */ + if(dot != NULL) { + ++dot; + len = MIN(INST_SZ, COR(atsign, end) - dot); + strncpy(canon, dot, len); + canon += len; + } + + /* Add REALM_SEP */ + *canon++ = REALM_SEP; + + /* Get the realm, if it exists */ + /* Otherwise, default to local realm */ + if(atsign != NULL) { + ++atsign; + len = MIN(REALM_SZ, end - atsign); + strncpy(canon, atsign, len); + canon += len; + *canon++ = '\0'; + } +#ifdef KERBEROS + else if(krb_get_lrealm(canon, 1) != KSUCCESS) { + strcpy(canon, KRB_REALM); + } +#endif +} + +#ifdef notdef +/* Get a lock to modify acl_file */ +/* Return new FILE pointer */ +/* or NULL if file cannot be modified */ +/* REQUIRES WRITE PERMISSION TO CONTAINING DIRECTORY */ +static FILE *acl_lock_file(acl_file) +char *acl_file; +{ + struct stat s; + char new[LINESIZE]; + int nfd; + FILE *nf; + int mode; + + if(stat(acl_file, &s) < 0) return(NULL); + mode = s.st_mode; + sprintf(new, NEW_FILE, acl_file); + for(;;) { + /* Open the new file */ + if((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) { + if(errno == EEXIST) { + /* Maybe somebody got here already, maybe it's just old */ + if(stat(new, &s) < 0) return(NULL); + if(time(0) - s.st_ctime > WAIT_TIME) { + /* File is stale, kill it */ + unlink(new); + continue; + } else { + /* Wait and try again */ + sleep(1); + continue; + } + } else { + /* Some other error, we lose */ + return(NULL); + } + } + + /* If we got to here, the lock file is ours and ok */ + /* Reopen it under stdio */ + if((nf = fdopen(nfd, "w")) == NULL) { + /* Oops, clean up */ + unlink(new); + } + return(nf); + } +} + +/* Commit changes to acl_file written onto FILE *f */ +/* Returns zero if successful */ +/* Returns > 0 if lock was broken */ +/* Returns < 0 if some other error occurs */ +/* Closes f */ +static int acl_commit(acl_file, f) +char *acl_file; +FILE *f; +{ +#ifdef WRITE_ACL + char new[LINESIZE]; + int ret; + struct stat s; + + sprintf(new, NEW_FILE, acl_file); + if(fflush(f) < 0 + || fstat(fileno(f), &s) < 0 + || s.st_nlink == 0) { + acl_abort(acl_file, f); + return(-1); + } + + ret = rename(new, acl_file); + fclose(f); + return(ret); +#else + abort (); +#endif +} + +/* Abort changes to acl_file written onto FILE *f */ +/* Returns 0 if successful, < 0 otherwise */ +/* Closes f */ +static int acl_abort(acl_file, f) +char *acl_file; +FILE *f; +{ +#ifdef WRITE_ACL + char new[LINESIZE]; + int ret; + struct stat s; + + /* make sure we aren't nuking someone else's file */ + if(fstat(fileno(f), &s) < 0 + || s.st_nlink == 0) { + fclose(f); + return(-1); + } else { + sprintf(new, NEW_FILE, acl_file); + ret = unlink(new); + fclose(f); + return(ret); + } +#else + abort (); +#endif +} + +/* Initialize an acl_file */ +/* Creates the file with permissions perm if it does not exist */ +/* Erases it if it does */ +/* Returns return value of acl_commit */ +int acl_initialize(acl_file, perm) +char *acl_file; +int perm; +{ + FILE *new; + int fd; + + /* Check if the file exists already */ + if((new = acl_lock_file(acl_file)) != NULL) { + return(acl_commit(acl_file, new)); + } else { + /* File must be readable and writable by owner */ + if((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) { + return(-1); + } else { + close(fd); + return(0); + } + } +} + +#endif /* notdef */ + +/* Eliminate all whitespace character in buf */ +/* Modifies its argument */ +static nuke_whitespace(buf) +char *buf; +{ + register char *pin, *pout; + + for(pin = pout = buf; *pin != '\0'; pin++) + if(!isspace(*pin)) *pout++ = *pin; + *pout = '\0'; /* Terminate the string */ +} + +/* Hash table stuff */ + +struct hashtbl { + int size; /* Max number of entries */ + int entries; /* Actual number of entries */ + char **tbl; /* Pointer to start of table */ +}; + +/* Make an empty hash table of size s */ +static struct hashtbl *make_hash(size) +int size; +{ + struct hashtbl *h; + + if(size < 1) size = 1; + h = (struct hashtbl *) malloc(sizeof(struct hashtbl)); + h->size = size; + h->entries = 0; + h->tbl = (char **) calloc(size, sizeof(char *)); + return(h); +} + +/* Destroy a hash table */ +static void +destroy_hash(h) + struct hashtbl *h; +{ + int i; + + for(i = 0; i < h->size; i++) { + if(h->tbl[i] != NULL) free(h->tbl[i]); + } + free(h->tbl); + free(h); +} + +/* Compute hash value for a string */ +static unsigned hashval(s) +register char *s; +{ + register unsigned hv; + + for(hv = 0; *s != '\0'; s++) { + hv ^= ((hv << 3) ^ *s); + } + return(hv); +} + +/* Add an element to a hash table */ +static add_hash(h, el) +struct hashtbl *h; +char *el; +{ + unsigned hv; + char *s; + char **old; + int i; + +#if 0 + fprintf (stderr, "adding %s to acl hash %08X\n", el, h); +#endif + /* Make space if it isn't there already */ + if(h->entries + 1 > (h->size >> 1)) { + old = h->tbl; + h->tbl = (char **) calloc(h->size << 1, sizeof(char *)); + for(i = 0; i < h->size; i++) { + if(old[i] != NULL) { + hv = hashval(old[i]) % (h->size << 1); + while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1); + h->tbl[hv] = old[i]; + } + } + h->size = h->size << 1; + free(old); + } + + hv = hashval(el) % h->size; + while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size; + s = malloc(strlen(el)+1); + strcpy(s, el); + h->tbl[hv] = s; + h->entries++; +} + +/* Returns nonzero if el is in h */ +static check_hash(h, el) +struct hashtbl *h; +char *el; +{ + unsigned hv; + +#if 0 + fprintf (stderr, "looking for %s in acl %08X\n", el, h); +#endif + for(hv = hashval(el) % h->size; h->tbl[hv]; hv = (hv + 1) % h->size) { +#if 0 + fprintf (stderr, "\tstrcmp (%s,...)\n", h->tbl[hv]); +#endif + if (!strcmp(h->tbl[hv], el)) { +#if 0 + fprintf (stderr, "success!\n"); +#endif + return 1; + } + } +#if 0 + fprintf (stderr, "failure\n"); +#endif + return 0; +} + +struct acl { + char filename[LINESIZE]; /* Name of acl file */ + struct hashtbl *acl; /* Acl entries */ +}; + +static struct acl acl_cache[CACHED_ACLS]; + +static int acl_cache_count = 0; +static int acl_cache_next = 0; + +/* Returns < 0 if unsuccessful in loading acl */ +/* Returns index into acl_cache otherwise */ +/* Note that if acl is already loaded, this is just a lookup */ +int acl_load(name) +char *name; +{ + int i,fd; + FILE *f; + char buf[MAX_PRINCIPAL_SIZE]; + char canon[MAX_PRINCIPAL_SIZE]; + + /* See if it's there already */ + for(i = 0; i < acl_cache_count; i++) { + if (!strcmp(acl_cache[i].filename, name)) + goto got_it; + } + + /* It isn't, load it in */ + /* maybe there's still room */ + if(acl_cache_count < CACHED_ACLS) { + i = acl_cache_count++; + } else { + /* No room, clean one out */ + i = acl_cache_next; + acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS; + if(acl_cache[i].acl) { + destroy_hash(acl_cache[i].acl); + acl_cache[i].acl = (struct hashtbl *) 0; + } + } + + /* Set up the acl */ + strcpy(acl_cache[i].filename, name); + /* Force reload */ + acl_cache[i].acl = (struct hashtbl *) 0; + + got_it: + /* + * See if we need to reload the ACL + */ + if (acl_cache[i].acl == (struct hashtbl *) 0) { + /* Gotta reload */ +#if 0 + fprintf (stderr, "attempting to load %s\n", name); +#endif + if ((f = fopen(name, "r")) == NULL) { +#if 0 + perror (name); +#endif + return -1; + } + if (acl_cache[i].acl) destroy_hash(acl_cache[i].acl); + acl_cache[i].acl = make_hash(ACL_LEN); + while(fgets(buf, sizeof(buf), f) != NULL) { + nuke_whitespace(buf); + acl_canonicalize_principal(buf, canon); + add_hash(acl_cache[i].acl, canon); + } + fclose(f); + } + return(i); +} + +/* + * This destroys all cached ACL's so that new ones will be loaded in + * the next time they are requested. + */ +acl_cache_reset() +{ + int i; + + /* See if it's there already */ + for(i = 0; i < acl_cache_count; i++) + if (acl_cache[i].acl) { + destroy_hash(acl_cache[i].acl); + acl_cache[i].acl = (struct hashtbl *) 0; + } + acl_cache_count = 0; + acl_cache_next = 0; +} + + +/* Returns nonzero if it can be determined that acl contains principal */ +/* Principal is not canonicalized, and no wildcarding is done */ +acl_exact_match(acl, principal) +char *acl; +char *principal; +{ + int idx; + +#if 0 + fprintf (stderr, "checking for %s in %s\n", principal, acl); +#endif + return((idx = acl_load(acl)) >= 0 + && check_hash(acl_cache[idx].acl, principal)); +} + +/* Returns nonzero if it can be determined that acl contains principal */ +/* Recognizes wildcards in acl of the form + name.*@realm, *.*@realm, and *.*@* */ +acl_check(acl, principal) +char *acl; +char *principal; +{ + char buf[MAX_PRINCIPAL_SIZE]; + char canon[MAX_PRINCIPAL_SIZE]; + char *realm; + + acl_canonicalize_principal(principal, canon); + + /* Is it there? */ + if (acl_exact_match(acl, canon)) + return 1; + + /* Try the wildcards */ + realm = index(canon, REALM_SEP); + *index(canon, INST_SEP) = '\0'; /* Chuck the instance */ + + sprintf(buf, "%s.*%s", canon, realm); + if(acl_exact_match(acl, buf)) return 1; + + sprintf(buf, "*.*%s", realm); + if(acl_exact_match(acl, buf) || acl_exact_match(acl, "*.*@*")) return(1); + + return(0); +} + +#ifdef notdef +/* Adds principal to acl */ +/* Wildcards are interpreted literally */ +acl_add(acl, principal) +char *acl; +char *principal; +{ + int idx; + int i; + FILE *new; + char canon[MAX_PRINCIPAL_SIZE]; + + acl_canonicalize_principal(principal, canon); + + if((new = acl_lock_file(acl)) == NULL) return(-1); + if((acl_exact_match(acl, canon)) + || (idx = acl_load(acl)) < 0) { + acl_abort(acl, new); + return(-1); + } + /* It isn't there yet, copy the file and put it in */ + for(i = 0; i < acl_cache[idx].acl->size; i++) { + if(acl_cache[idx].acl->tbl[i] != NULL) { + if(fputs(acl_cache[idx].acl->tbl[i], new) == NULL + || putc('\n', new) != '\n') { + acl_abort(acl, new); + return(-1); + } + } + } + fputs(canon, new); + putc('\n', new); + return(acl_commit(acl, new)); +} + +/* Removes principal from acl */ +/* Wildcards are interpreted literally */ +acl_delete(acl, principal) +char *acl; +char *principal; +{ + int idx; + int i; + FILE *new; + char canon[MAX_PRINCIPAL_SIZE]; + + acl_canonicalize_principal(principal, canon); + + if((new = acl_lock_file(acl)) == NULL) return(-1); + if((!acl_exact_match(acl, canon)) + || (idx = acl_load(acl)) < 0) { + acl_abort(acl, new); + return(-1); + } + /* It isn't there yet, copy the file and put it in */ + for(i = 0; i < acl_cache[idx].acl->size; i++) { + if(acl_cache[idx].acl->tbl[i] != NULL + && strcmp(acl_cache[idx].acl->tbl[i], canon)) { + fputs(acl_cache[idx].acl->tbl[i], new); + putc('\n', new); + } + } + return(acl_commit(acl, new)); +} +#endif /* notdef */ -- cgit v1.2.3