393 lines
10 KiB
C
393 lines
10 KiB
C
/* This file is part of the Project Athena Zephyr Notification System.
|
|
* It contains functions for the Zephyr server class manager subsystem.
|
|
*
|
|
* Created by: John T. Kohl
|
|
*
|
|
* $Source$
|
|
* $Author$
|
|
*
|
|
* Copyright (c) 1987 by the Massachusetts Institute of Technology.
|
|
* For copying and distribution information, see the file
|
|
* "mit-copyright.h".
|
|
*/
|
|
|
|
#include <zephyr/mit-copyright.h>
|
|
#include "zserver.h" /* includes zephyr/zephyr.h */
|
|
#include <assert.h>
|
|
|
|
#if !defined (lint) && !defined (SABER)
|
|
static const char rcsid_class_c[] =
|
|
"$Id$";
|
|
#endif
|
|
|
|
/*
|
|
* Class manager subsystem.
|
|
*
|
|
*
|
|
* External functions are:
|
|
*
|
|
* Code_t triplet_register(client, subs, realm)
|
|
*
|
|
* Code_t triplet_deregister(client, subs, realm)
|
|
*
|
|
* Client *triplet_lookup(subs)
|
|
* Client *client;
|
|
* Destlist *subs;
|
|
*
|
|
* Acl *class_get_acl(class_name)
|
|
* String *class_name;
|
|
*
|
|
* Code_t class_restrict(class_name, acl)
|
|
* char *class_name;
|
|
* Acl *acl;
|
|
*
|
|
* Code_t class_setup_restricted(class_name, acl)
|
|
* char *class_name;
|
|
* Acl *acl;
|
|
*
|
|
* and several Destination methods.
|
|
*/
|
|
|
|
/*
|
|
* The data structure used for the class manager is an array of hash buckets
|
|
* each containing a pointer to a doubly linked circular list (in the style
|
|
* of insque/remque). Each element of this list contains a class.instance
|
|
* name (which hashes into the bucket associated with this list) and a
|
|
* doubly linked list of clients which are interested in this class.
|
|
* The data pointed to by these clients is owned by other modules. Care
|
|
* must be taken by the caller not to a free()'d client
|
|
* structure.
|
|
*
|
|
* If any hash bucket is empty, the pointer is null.
|
|
*
|
|
* The first element in the hash bucket is a special header unused for
|
|
* storing classes, and is used for finding the end of the list.
|
|
*
|
|
* If any list of interested clients is empty, the class name is garbage
|
|
* collected, unless the class has been registered as restricted.
|
|
*/
|
|
|
|
/* Private variables */
|
|
#define EMPTY_CLASS 2000
|
|
|
|
#define ALLOC_OFFSET 8 /* Allocate 32 bytes less than a power of 2. */
|
|
#define ALLOC_INIT 8 /* Initial number of subscriptions. */
|
|
|
|
#define HASHSIZE 1023
|
|
#define HASHVAL(c, i, r) (((c)->hash_val ^ (i)->hash_val ^ (r)->hash_val) \
|
|
% HASHSIZE)
|
|
#define DEST_HASHVAL(dest) HASHVAL((dest).classname, (dest).inst, (dest).recip)
|
|
|
|
static Triplet *triplet_bucket[HASHSIZE]; /* the hash table of pointers */
|
|
|
|
static Code_t remove_client(Triplet *triplet, Client *client, ZRealm *realm);
|
|
static Code_t insert_client(Triplet *triplet, Client *client, ZRealm *realm);
|
|
static Triplet *triplet_alloc(String *classname, String *inst,
|
|
String *recipient);
|
|
static void free_triplet(Triplet *);
|
|
|
|
/* public routines */
|
|
|
|
/*
|
|
* Determine if two destination triplets are equal. Note the backup
|
|
* case-insensitive recipient check in the third term. Recipients are
|
|
* not downcased at subscription time (in order to preserve case for,
|
|
* say, "zctl ret"), but traditional zephyr server behavior has not
|
|
* been case-sensitive in the recipient string. In most cases, a
|
|
* failed match will fail on the classname or instance, and a successful
|
|
* match will succeed on the (d1->recip == d2->recip) check, so this
|
|
* shouldn't affect performance.
|
|
*/
|
|
|
|
int
|
|
ZDest_eq(Destination *d1,
|
|
Destination *d2)
|
|
{
|
|
return((d1->classname == d2->classname) &&
|
|
(d1->inst == d2->inst) &&
|
|
(d1->recip == d2->recip ||
|
|
strcasecmp(d1->recip->string, d2->recip->string) == 0));
|
|
}
|
|
|
|
|
|
/* the client as interested in a triplet */
|
|
|
|
Code_t
|
|
triplet_register(Client *client,
|
|
Destination *dest,
|
|
ZRealm *realm)
|
|
{
|
|
Triplet *triplet;
|
|
unsigned long hashval;
|
|
|
|
hashval = DEST_HASHVAL(*dest);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (ZDest_eq(&triplet->dest, dest))
|
|
return insert_client(triplet, client, realm);
|
|
}
|
|
|
|
/* Triplet not present in hash table, insert it. */
|
|
triplet = triplet_alloc(dest->classname, dest->inst, dest->recip);
|
|
Triplet_insert(&triplet_bucket[hashval], triplet);
|
|
return insert_client(triplet, client, realm);
|
|
}
|
|
|
|
/* dissociate client from the class, garbage collecting if appropriate */
|
|
|
|
Code_t
|
|
triplet_deregister(Client *client,
|
|
Destination *dest,
|
|
ZRealm *realm)
|
|
{
|
|
Triplet *triplet;
|
|
int retval;
|
|
unsigned long hashval;
|
|
|
|
hashval = DEST_HASHVAL(*dest);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (ZDest_eq(&triplet->dest, dest)) {
|
|
retval = remove_client(triplet, client, realm);
|
|
if (retval != ZERR_NONE)
|
|
return retval;
|
|
if (*triplet->clients == NULL && !triplet->acl) {
|
|
Triplet_delete(triplet);
|
|
free_triplet(triplet);
|
|
return ZSRV_EMPTYCLASS;
|
|
}
|
|
return ZERR_NONE;
|
|
}
|
|
}
|
|
return(ZSRV_BADASSOC);
|
|
}
|
|
|
|
/* return a linked list of what clients are interested in this triplet */
|
|
|
|
Client **
|
|
triplet_lookup(Destination *dest)
|
|
{
|
|
Triplet *triplet;
|
|
unsigned long hashval;
|
|
|
|
hashval = DEST_HASHVAL(*dest);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (ZDest_eq(&triplet->dest, dest))
|
|
return triplet->clients;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* return the acl structure associated with class, or NULL if there is
|
|
* no such acl struct
|
|
*/
|
|
|
|
Acl *
|
|
class_get_acl(String *class_name)
|
|
{
|
|
Triplet *triplet;
|
|
unsigned long hashval;
|
|
|
|
hashval = HASHVAL(class_name, empty, empty);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (triplet->dest.classname == class_name &&
|
|
triplet->dest.inst == empty && triplet->dest.recip == empty)
|
|
return triplet->acl;
|
|
}
|
|
|
|
/* No acl found, not restricted. */
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* restrict class by associating it with the acl structure acl.
|
|
* return ZERR_NONE if no error, or ZSRV_NOCLASS if there is no such
|
|
* class, or ZSRV_CLASSRESTRICTED if it is already restricted.
|
|
*/
|
|
|
|
Code_t
|
|
class_restrict(char *class_name,
|
|
Acl *acl)
|
|
{
|
|
Triplet *triplet;
|
|
String *d;
|
|
unsigned long hashval;
|
|
|
|
d = make_string(class_name,1);
|
|
hashval = HASHVAL(d, empty, empty);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (triplet->dest.classname == d && triplet->dest.inst == empty &&
|
|
triplet->dest.recip == empty) {
|
|
if (triplet->acl)
|
|
return ZSRV_CLASSRESTRICTED;
|
|
triplet->acl = acl;
|
|
free_string(d);
|
|
return ZERR_NONE;
|
|
}
|
|
}
|
|
|
|
free_string(d);
|
|
return ZSRV_NOCLASS;
|
|
}
|
|
|
|
/*
|
|
* restrict class by registering it and associating it with the acl
|
|
* structure acl. return ZERR_NONE if no error, or ZSRV_CLASSXISTS
|
|
* if the class is already registered, or ENOMEM in case of malloc failure.
|
|
*/
|
|
|
|
Code_t
|
|
class_setup_restricted(char *class_name,
|
|
Acl *acl)
|
|
{
|
|
Triplet *triplet;
|
|
String *d;
|
|
unsigned long hashval;
|
|
|
|
d = make_string(class_name,1);
|
|
hashval = HASHVAL(d, empty, empty);
|
|
for (triplet = triplet_bucket[hashval]; triplet; triplet = triplet->next) {
|
|
if (triplet->dest.classname == d && triplet->dest.inst == empty &&
|
|
triplet->dest.recip == d) {
|
|
free_string(d);
|
|
return ZSRV_CLASSXISTS;
|
|
}
|
|
}
|
|
|
|
/* Triplet not present in hash table, insert it. */
|
|
triplet = triplet_alloc(d, empty, empty);
|
|
free_string(d);
|
|
if (!triplet)
|
|
return ENOMEM;
|
|
triplet->acl = acl;
|
|
Triplet_insert(&triplet_bucket[hashval], triplet);
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
/* private routines */
|
|
|
|
/* allocate space for a class structure */
|
|
|
|
static Triplet *
|
|
triplet_alloc(String *classname,
|
|
String *inst,
|
|
String *recipient)
|
|
{
|
|
Triplet *triplet;
|
|
|
|
triplet = (Triplet *) malloc(sizeof(Triplet));
|
|
if (!triplet)
|
|
return NULL;
|
|
|
|
triplet->dest.classname = dup_string(classname);
|
|
triplet->dest.inst = dup_string(inst);
|
|
triplet->dest.recip = dup_string(recipient);
|
|
triplet->clients = NULL;
|
|
triplet->acl = NULL;
|
|
|
|
return triplet;
|
|
}
|
|
|
|
/* insert a client into the list associated with the class *ptr */
|
|
|
|
static Code_t
|
|
insert_client(Triplet *triplet,
|
|
Client *client,
|
|
ZRealm *realm)
|
|
{
|
|
Client **clientp, **newclients;
|
|
int new_size;
|
|
|
|
if (triplet->clients) {
|
|
/* Avoid duplication. */
|
|
for (clientp = triplet->clients; *clientp; clientp++) {
|
|
if (*clientp == client || (realm && (*clientp)->realm == realm))
|
|
return ZSRV_CLASSXISTS;
|
|
}
|
|
|
|
if (clientp + 1 - triplet->clients >= triplet->clients_size) {
|
|
new_size = triplet->clients_size * 2 + ALLOC_OFFSET;
|
|
newclients = (Client **) realloc(triplet->clients,
|
|
new_size * sizeof(Client *));
|
|
if (newclients == NULL)
|
|
return ENOMEM;
|
|
clientp = newclients + (clientp - triplet->clients);
|
|
triplet->clients = newclients;
|
|
triplet->clients_size = new_size;
|
|
}
|
|
} else {
|
|
/* Allocate an initial list of client pointers. */
|
|
triplet->clients = (Client **) malloc(ALLOC_INIT * sizeof(Client *));
|
|
if (triplet->clients == NULL)
|
|
return ENOMEM;
|
|
triplet->clients_size = ALLOC_INIT;
|
|
clientp = triplet->clients;
|
|
}
|
|
|
|
*clientp = client;
|
|
clientp[1] = NULL;
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
/*
|
|
* remove the client from the list associated with class *ptr, garbage
|
|
* collecting if appropriate
|
|
*/
|
|
|
|
static Code_t
|
|
remove_client(Triplet *triplet,
|
|
Client *client,
|
|
ZRealm *realm)
|
|
{
|
|
Client **clientp;
|
|
|
|
for (clientp = triplet->clients; *clientp; clientp++) {
|
|
if (*clientp == client || (realm && (*clientp)->realm == realm)) {
|
|
for (; *clientp; clientp++)
|
|
*clientp = clientp[1];
|
|
return ZERR_NONE;
|
|
}
|
|
}
|
|
|
|
return ZSRV_BADASSOC;
|
|
}
|
|
|
|
static void
|
|
free_triplet(Triplet *triplet)
|
|
{
|
|
if (triplet->clients)
|
|
free(triplet->clients);
|
|
free_string(triplet->dest.classname);
|
|
free_string(triplet->dest.inst);
|
|
free_string(triplet->dest.recip);
|
|
free(triplet);
|
|
}
|
|
|
|
void
|
|
triplet_dump_subs(FILE *fp)
|
|
{
|
|
int i;
|
|
Triplet *triplet;
|
|
Client **clientp;
|
|
|
|
for (i = 0; i < HASHSIZE; i++) {
|
|
for (triplet = triplet_bucket[i]; triplet; triplet = triplet->next) {
|
|
fputs("Triplet '", fp);
|
|
dump_quote(triplet->dest.classname->string, fp);
|
|
fputs("' '", fp);
|
|
dump_quote(triplet->dest.inst->string, fp);
|
|
fputs("' '", fp);
|
|
dump_quote(triplet->dest.recip->string, fp);
|
|
fputs("':\n", fp);
|
|
if (triplet->clients) {
|
|
for (clientp = triplet->clients; *clientp; clientp++) {
|
|
fprintf(fp, "\t%s %d (%s)\n",
|
|
inet_ntoa((*clientp)->addr.sin_addr),
|
|
ntohs((*clientp)->addr.sin_port),
|
|
(*clientp)->principal->string);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|