376 lines
8.8 KiB
C
376 lines
8.8 KiB
C
/* This file is part of the Project Athena Zephyr Notification System.
|
|
* It contains functions for maintaining Access Control Lists.
|
|
*
|
|
* Created by: John T. Kohl
|
|
*
|
|
* $Id$
|
|
*
|
|
* 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. */
|
|
|
|
/*
|
|
* Stolen from lib/acl_files.c because acl_load needs to be externally
|
|
* declared and not statically declared.
|
|
*/
|
|
|
|
#include <zephyr/mit-copyright.h>
|
|
#include "zserver.h"
|
|
#include <fnmatch.h>
|
|
|
|
#ifndef SABER
|
|
#ifndef lint
|
|
static const char rcsid_acl_files_c[] = "$Id$";
|
|
#endif /* lint */
|
|
#endif /* SABER */
|
|
|
|
/*** Routines for manipulating access control list files ***/
|
|
|
|
#define REALM_SEP '@'
|
|
#define ESCAPE '\\'
|
|
|
|
#define CACHED_ACLS 256 /* How many acls to cache */
|
|
#define ACL_LEN 256 /* Twice a reasonable acl length */
|
|
|
|
/* Eliminate all whitespace character in buf */
|
|
/* Modifies its argument */
|
|
static void
|
|
nuke_whitespace(char *buf)
|
|
{
|
|
char *pin, *pout;
|
|
|
|
for (pin = pout = buf; *pin != '\0'; pin++)
|
|
if (!isspace(*pin)) *pout++ = *pin;
|
|
*pout = '\0'; /* Terminate the string */
|
|
}
|
|
|
|
struct host_ace {
|
|
struct host_ace *next;
|
|
unsigned long addr, mask;
|
|
};
|
|
|
|
static int
|
|
add_host(struct host_ace **list,
|
|
char *buf)
|
|
{
|
|
struct host_ace *e;
|
|
struct in_addr addr;
|
|
unsigned long mask = 0;
|
|
long i;
|
|
char *m, *x;
|
|
|
|
m = strchr(buf, '/');
|
|
if (m) {
|
|
*(m++) = 0;
|
|
if (!*m)
|
|
return EINVAL;
|
|
i = strtol(m, &x, 10);
|
|
if (*x || i < 0 || i > 32)
|
|
return EINVAL;
|
|
while (i--)
|
|
mask = (mask >> 1) | 0x80000000;
|
|
} else {
|
|
mask = 0xffffffff;
|
|
}
|
|
|
|
if (!inet_aton(buf, &addr))
|
|
return EINVAL;
|
|
|
|
e = (struct host_ace *)malloc(sizeof(struct host_ace));
|
|
if (e == NULL)
|
|
return errno;
|
|
memset(e, 0, sizeof(struct host_ace));
|
|
e->addr = addr.s_addr;
|
|
e->mask = htonl(mask);
|
|
e->next = *list;
|
|
*list = e;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
destroy_hosts(struct host_ace **list)
|
|
{
|
|
struct host_ace *e;
|
|
|
|
while ((e = *list)) {
|
|
*list = e->next;
|
|
free(e);
|
|
}
|
|
}
|
|
|
|
static char *
|
|
split_name(char *princ) {
|
|
int i;
|
|
int len = strlen(princ);
|
|
|
|
for (i = 0; i < len && princ[i] != REALM_SEP; i++)
|
|
if (princ[i] == ESCAPE && (i + 1) < len)
|
|
i++;
|
|
if (i != len) { /* yay found an @ */
|
|
princ[i] = 0;
|
|
return &princ[i + 1];
|
|
}
|
|
return &princ[i]; /* failure, just return a pointer empty string */
|
|
}
|
|
|
|
struct user_ace {
|
|
struct user_ace *next;
|
|
char *princ;
|
|
char *realm;
|
|
};
|
|
|
|
static int
|
|
add_user(struct user_ace **list, char *princ) {
|
|
struct user_ace *e;
|
|
char *realm = split_name(princ);
|
|
|
|
e = (struct user_ace *)malloc(sizeof(struct user_ace));
|
|
if (e == NULL)
|
|
return errno;
|
|
memset(e, 0, sizeof(struct user_ace));
|
|
|
|
if (!strcmp(princ, "*.*")) /* #ifdef KRB4_COMPAT */
|
|
e->princ = strdup("*");
|
|
else /* #endif */
|
|
e->princ = strdup(princ);
|
|
if (e->princ == NULL) {
|
|
free(e);
|
|
return errno;
|
|
}
|
|
|
|
e->realm = strdup(realm);
|
|
if (e->realm == NULL) {
|
|
free(e->princ);
|
|
free(e);
|
|
return errno;
|
|
}
|
|
e->next = *list;
|
|
*list = e;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
destroy_user(struct user_ace **list) {
|
|
struct user_ace *e;
|
|
while ((e = *list)) {
|
|
*list = e->next;
|
|
free(e->princ);
|
|
free(e->realm);
|
|
free(e);
|
|
}
|
|
}
|
|
|
|
static int
|
|
check_user(struct user_ace *list, char *princ, char *realm) {
|
|
struct user_ace *e;
|
|
|
|
e = list;
|
|
while (e) {
|
|
if (fnmatch(e->princ, princ, 0) == 0
|
|
&& fnmatch(e->realm, realm, 0) == 0)
|
|
return 1;
|
|
e = e->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct acl {
|
|
String *filename; /* Name of acl file */
|
|
int loaded;
|
|
struct user_ace *acl; /* Positive acl entries */
|
|
struct user_ace *negacl; /* Negative acl entries */
|
|
struct host_ace *hosts; /* Positive host entries */
|
|
struct host_ace *neghosts; /* Negative host entries */
|
|
};
|
|
|
|
static struct acl acl_cache[CACHED_ACLS];
|
|
|
|
static int acl_cache_count = 0;
|
|
static int acl_cache_next = 0;
|
|
|
|
/* wipe an entry in the acl cache */
|
|
static void
|
|
destroy_acl(int i) {
|
|
if (acl_cache[i].filename)
|
|
free_string(acl_cache[i].filename);
|
|
if (acl_cache[i].acl)
|
|
destroy_user(&acl_cache[i].acl);
|
|
if (acl_cache[i].negacl)
|
|
destroy_user(&acl_cache[i].negacl);
|
|
if (acl_cache[i].hosts)
|
|
destroy_hosts(&acl_cache[i].hosts);
|
|
if (acl_cache[i].neghosts)
|
|
destroy_hosts(&acl_cache[i].neghosts);
|
|
acl_cache[i].filename = NULL;
|
|
acl_cache[i].acl = (struct user_ace *) 0;
|
|
acl_cache[i].negacl = (struct user_ace *) 0;
|
|
acl_cache[i].hosts = (struct host_ace *) 0;
|
|
acl_cache[i].neghosts = (struct host_ace *) 0;
|
|
acl_cache[i].loaded = 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(char *name)
|
|
{
|
|
int i;
|
|
FILE *f;
|
|
char buf[BUFSIZ];
|
|
int ret = 0;
|
|
String *interned_name;
|
|
|
|
syslog(LOG_DEBUG, "acl_load(%s)", name);
|
|
/* See if it's there already */
|
|
interned_name = make_string(name, 0);
|
|
for (i = 0; i < acl_cache_count; i++) {
|
|
if (acl_cache[i].filename == interned_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;
|
|
destroy_acl(i);
|
|
}
|
|
|
|
/* Set up the acl */
|
|
acl_cache[i].filename = interned_name;
|
|
/* Force reload */
|
|
acl_cache[i].loaded = 0;
|
|
|
|
got_it:
|
|
/*
|
|
* See if we need to reload the ACL
|
|
*/
|
|
if (!acl_cache[i].loaded) {
|
|
syslog(LOG_DEBUG, "acl_load(%s) actually loading", name);
|
|
/* Gotta reload */
|
|
if ((f = fopen(name, "r")) == NULL) {
|
|
syslog(LOG_ERR, "Error loading acl file %s: %m", name);
|
|
return -errno;
|
|
}
|
|
if (acl_cache[i].acl)
|
|
destroy_user(&acl_cache[i].acl);
|
|
if (acl_cache[i].negacl)
|
|
destroy_user(&acl_cache[i].negacl);
|
|
acl_cache[i].acl = (struct user_ace *)0;
|
|
acl_cache[i].negacl = (struct user_ace *)0;
|
|
while(fgets(buf, sizeof(buf), f) != NULL && ret == 0) {
|
|
nuke_whitespace(buf);
|
|
if (!buf[0])
|
|
continue;
|
|
if (buf[0] == '!' && buf[1] == '@')
|
|
ret = add_host(&acl_cache[i].neghosts, buf + 2);
|
|
else if (buf[0] == '@')
|
|
ret = add_host(&acl_cache[i].hosts, buf + 1);
|
|
else if (buf[0] == '!')
|
|
ret = add_user(&acl_cache[i].negacl, buf + 1);
|
|
else
|
|
ret = add_user(&acl_cache[i].acl, buf);
|
|
}
|
|
fclose(f);
|
|
if (ret) {
|
|
destroy_acl(i);
|
|
return -ret;
|
|
}
|
|
acl_cache[i].loaded = 1;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* This destroys all cached ACL's so that new ones will be loaded in
|
|
* the next time they are requested.
|
|
*/
|
|
void
|
|
acl_cache_reset(void)
|
|
{
|
|
int i;
|
|
|
|
syslog(LOG_DEBUG, "acl_cache_reset()");
|
|
for (i = 0; i < acl_cache_count; i++)
|
|
destroy_acl(i);
|
|
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 */
|
|
/* If neg is nonzero, we look for negative entries */
|
|
static int
|
|
acl_match(char *acl, char *princ, char *realm, int neg)
|
|
{
|
|
int idx;
|
|
|
|
if ((idx = acl_load(acl)) < 0)
|
|
return 0;
|
|
if (neg)
|
|
return check_user(acl_cache[idx].negacl, princ, realm);
|
|
else
|
|
return check_user(acl_cache[idx].acl, princ, realm);
|
|
}
|
|
|
|
/* Returns nonzero if it can be determined that acl contains who */
|
|
/* If neg is nonzero, we look for negative entries */
|
|
static int
|
|
acl_host_match(char *acl,
|
|
unsigned long who,
|
|
int neg)
|
|
{
|
|
int idx;
|
|
struct host_ace *e;
|
|
|
|
if ((idx = acl_load(acl)) < 0)
|
|
return 0;
|
|
e = neg ? acl_cache[idx].neghosts : acl_cache[idx].hosts;
|
|
while (e) {
|
|
if ((e->addr & e->mask) == (who & e->mask))
|
|
return 1;
|
|
e = e->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Returns nonzero if it can be determined that acl contains principal */
|
|
/* Recognizes wildcards in acl. */
|
|
/* Also checks for IP address entries and applies negative ACL's */
|
|
int
|
|
acl_check(char *acl, char *princ, struct sockaddr_in *who)
|
|
{
|
|
char *realm;
|
|
char *name;
|
|
int result = 0;
|
|
|
|
if (princ) {
|
|
name = strdup(princ);
|
|
realm = split_name(name);
|
|
|
|
if (acl_match(acl, name, realm, 1))
|
|
return 0;
|
|
if (acl_match(acl, name, realm, 0))
|
|
result = 1;
|
|
free(name);
|
|
}
|
|
|
|
if (who) {
|
|
if (acl_host_match(acl, who->sin_addr.s_addr, 1))
|
|
return 0;
|
|
if (acl_host_match(acl, who->sin_addr.s_addr, 0))
|
|
result = 1;
|
|
}
|
|
|
|
return result;
|
|
}
|