#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include "krb.h"
#include "kadm_server.h"
#ifndef KRB_REALM
#define KRB_REALM "ATHENA.MIT.EDU"
#endif
#define MAX_PRINCIPAL_SIZE (ANAME_SZ + INST_SZ + REALM_SZ + 3)
#define INST_SEP '.'
#define REALM_SEP '@'
#define LINESIZE 2048
#define NEW_FILE "%s.~NEWACL~"
#define WAIT_TIME 300
#define CACHED_ACLS 8
#define ACL_LEN 16
#define MAX(a,b) (((a)>(b))?(a):(b))
#define MIN(a,b) (((a)<(b))?(a):(b))
#define COR(a,b) ((a!=NULL)?(a):(b))
#ifndef HAVE_STDLIB_H
extern char *malloc(), *calloc();
#endif
extern time_t time();
static int acl_abort(char *, FILE *);
void acl_canonicalize_principal(principal, canon)
char *principal;
char *canon;
{
char *dot, *atsign, *end, *canon_save = canon;
char realm[REALM_SZ];
int len;
dot = strchr(principal, INST_SEP);
atsign = strchr(principal, REALM_SEP);
if(dot != NULL && atsign != NULL) {
if(dot < atsign) {
strncpy(canon, principal, MAX_PRINCIPAL_SIZE);
canon[MAX_PRINCIPAL_SIZE-1] = '\0';
return;
} else {
dot = NULL;
}
}
end = principal + strlen(principal);
len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal);
if(canon + len < canon_save + MAX_PRINCIPAL_SIZE) {
strncpy(canon, principal, len);
canon += len;
} else {
strcpy(canon, "");
return;
}
if(canon + 1 < canon_save + MAX_PRINCIPAL_SIZE) {
*canon++ = INST_SEP;
} else {
strcpy(canon, "");
return;
}
if(dot != NULL) {
++dot;
len = MIN(INST_SZ, COR(atsign, end) - dot);
if(canon + len < canon_save + MAX_PRINCIPAL_SIZE) {
strncpy(canon, dot, len);
canon += len;
} else {
strcpy(canon, "");
return;
}
}
*canon++ = REALM_SEP;
if(atsign != NULL) {
++atsign;
len = MIN(REALM_SZ, end - atsign);
if(canon + len + 1 < canon_save + MAX_PRINCIPAL_SIZE) {
strncpy(canon, atsign, len);
canon += len;
*canon++ = '\0';
} else {
strcpy(canon, "");
return;
}
} else if(krb_get_lrealm(realm, 1) != KSUCCESS) {
if(canon + strlen(KRB_REALM) < canon_save + MAX_PRINCIPAL_SIZE) {
strcpy(canon, KRB_REALM);
} else {
strcpy(canon, "");
return;
}
} else {
if (canon + strlen(realm) < canon_save + MAX_PRINCIPAL_SIZE) {
strcpy(canon, realm);
} else {
strcpy(canon, "");
return;
}
}
}
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(;;) {
if((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
if(errno == EEXIST) {
if(stat(new, &s) < 0) return(NULL);
if(time(0) - s.st_ctime > WAIT_TIME) {
unlink(new);
continue;
} else {
sleep(1);
continue;
}
} else {
return(NULL);
}
}
if((nf = fdopen(nfd, "w")) == NULL) {
unlink(new);
}
return(nf);
}
}
static int acl_commit(acl_file, f)
char *acl_file;
FILE *f;
{
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);
}
static int acl_abort(acl_file, f)
char *acl_file;
FILE *f;
{
char new[LINESIZE];
int ret;
struct stat s;
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);
}
}
int acl_initialize(acl_file, perm)
char *acl_file;
int perm;
{
FILE *new;
int fd;
if((new = acl_lock_file(acl_file)) != NULL) {
return(acl_commit(acl_file, new));
} else {
if((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
return(-1);
} else {
close(fd);
return(0);
}
}
}
static void nuke_whitespace(buf)
char *buf;
{
register char *pin, *pout;
for(pin = pout = buf; *pin != '\0'; pin++)
if(!isspace((int) *pin)) *pout++ = *pin;
*pout = '\0';
}
struct hashtbl {
int size;
int entries;
char **tbl;
};
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);
}
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);
}
static unsigned hashval(s)
register char *s;
{
register unsigned hv;
for(hv = 0; *s != '\0'; s++) {
hv ^= ((hv << 3) ^ *s);
}
return(hv);
}
static void add_hash(h, el)
struct hashtbl *h;
char *el;
{
unsigned hv;
char *s;
char **old;
int i;
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 = (char *) malloc(strlen(el)+1);
strcpy(s, el);
h->tbl[hv] = s;
h->entries++;
}
static int check_hash(h, el)
struct hashtbl *h;
char *el;
{
unsigned hv;
for(hv = hashval(el) % h->size;
h->tbl[hv] != NULL;
hv = (hv + 1) % h->size) {
if(!strcmp(h->tbl[hv], el)) return(1);
}
return(0);
}
struct acl {
char filename[LINESIZE];
int fd;
struct stat status;
struct hashtbl *acl;
};
static struct acl acl_cache[CACHED_ACLS];
static int acl_cache_count = 0;
static int acl_cache_next = 0;
static int acl_load(name)
char *name;
{
int i;
FILE *f;
struct stat s;
char buf[MAX_PRINCIPAL_SIZE];
char canon[MAX_PRINCIPAL_SIZE];
for(i = 0; i < acl_cache_count; i++) {
if(!strcmp(acl_cache[i].filename, name)
&& acl_cache[i].fd >= 0) goto got_it;
}
if(acl_cache_count < CACHED_ACLS) {
i = acl_cache_count++;
} else {
i = acl_cache_next;
acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS;
close(acl_cache[i].fd);
if(acl_cache[i].acl) {
destroy_hash(acl_cache[i].acl);
acl_cache[i].acl = (struct hashtbl *) 0;
}
}
if (strlen (name) >= sizeof (acl_cache[i].filename) - 1) {
return -1;
}
strncpy(acl_cache[i].filename, name, sizeof(acl_cache[i].filename) - 1);
acl_cache[i].filename[sizeof(acl_cache[i].filename) - 1] = '\0';
if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
acl_cache[i].acl = (struct hashtbl *) 0;
got_it:
if(stat(acl_cache[i].filename, &s) < 0) return(-1);
if(acl_cache[i].acl == (struct hashtbl *) 0
|| s.st_nlink != acl_cache[i].status.st_nlink
|| s.st_mtime != acl_cache[i].status.st_mtime
|| s.st_ctime != acl_cache[i].status.st_ctime) {
if(acl_cache[i].fd >= 0) close(acl_cache[i].fd);
if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
if((f = fdopen(acl_cache[i].fd, "r")) == NULL) 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);
if(strlen(canon) > 0) {
add_hash(acl_cache[i].acl, canon);
}
}
fclose(f);
acl_cache[i].status = s;
}
return(i);
}
int
acl_exact_match(acl, principal)
char *acl;
char *principal;
{
int idx;
return((idx = acl_load(acl)) >= 0
&& check_hash(acl_cache[idx].acl, principal));
}
int
acl_check(acl, principal)
char *acl;
char *principal;
{
char buf[MAX_PRINCIPAL_SIZE];
char canon[MAX_PRINCIPAL_SIZE];
char *realm, *tmp;
acl_canonicalize_principal(principal, canon);
if(strlen(canon) == 0) return(0);
if(acl_exact_match(acl, canon)) return(1);
realm = strchr(canon, REALM_SEP);
tmp = strchr(canon, INST_SEP);
*tmp = '\0';
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);
}
int
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(strlen(canon) == 0) return(-1);
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);
}
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) == EOF
|| putc('\n', new) != '\n') {
acl_abort(acl, new);
return(-1);
}
}
}
fputs(canon, new);
putc('\n', new);
return(acl_commit(acl, new));
}
int
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(strlen(canon) == 0) return(-1);
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);
}
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));
}