1 /* This file is part of the Project Athena Zephyr Notification System.
2 * It contains functions for maintaining Access Control Lists.
4 * Created by: John T. Kohl
8 * Copyright (c) 1987,1988 by the Massachusetts Institute of Technology.
9 * For copying and distribution information, see the file
13 /* Define this if you really want the ACL-writing code included. */
14 /* #define WRITE_ACL */
17 * Stolen from lib/acl_files.c because acl_load needs to be externally
18 * declared and not statically declared.
21 #include <zephyr/mit-copyright.h>
27 static const char rcsid_acl_files_c[] = "$Id$";
31 /*** Routines for manipulating access control list files ***/
33 /* "aname.inst@realm" */
34 #define MAX_PRINCIPAL_SIZE (ANAME_SZ + INST_SZ + REALM_SZ + 3)
38 #define LINESIZE 2048 /* Maximum line length in an acl file */
40 #define NEW_FILE "%s.~NEWACL~" /* Format for name of altered acl file */
41 #define WAIT_TIME 300 /* Maximum time allowed write acl file */
43 #define CACHED_ACLS 64 /* How many acls to cache */
44 #define ACL_LEN 256 /* Twice a reasonable acl length */
46 #define MAX(a,b) (((a)>(b))?(a):(b))
47 #define MIN(a,b) (((a)<(b))?(a):(b))
49 #define COR(a,b) ((a!=NULL)?(a):(b))
51 /* Canonicalize a principal name */
52 /* If instance is missing, it becomes "" */
53 /* If realm is missing, it becomes the local realm */
54 /* Canonicalized form is put in canon, which must be big enough to hold
55 MAX_PRINCIPAL_SIZE characters */
56 void acl_canonicalize_principal(char *principal,
63 dot = strchr(principal, INST_SEP);
64 atsign = strchr(principal, REALM_SEP);
66 /* Maybe we're done already */
67 if (dot != NULL && atsign != NULL) {
71 strncpy(canon, principal, MAX_PRINCIPAL_SIZE);
72 canon[MAX_PRINCIPAL_SIZE-1] = '\0';
75 /* Nope, it's part of the realm */
81 end = principal + strlen(principal);
83 /* Get the principal name */
84 len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal);
85 strncpy(canon, principal, len);
91 /* Get the instance, if it exists */
94 len = MIN(INST_SZ, COR(atsign, end) - dot);
95 strncpy(canon, dot, len);
100 *canon++ = REALM_SEP;
102 /* Get the realm, if it exists */
103 /* Otherwise, default to local realm */
104 if (atsign != NULL) {
106 len = MIN(REALM_SZ, end - atsign);
107 strncpy(canon, atsign, len);
112 else if (krb_get_lrealm(canon, 1) != KSUCCESS) {
113 strcpy(canon, KRB_REALM);
119 /* Get a lock to modify acl_file */
120 /* Return new FILE pointer */
121 /* or NULL if file cannot be modified */
122 /* REQUIRES WRITE PERMISSION TO CONTAINING DIRECTORY */
123 static FILE *acl_lock_file(acl_file)
132 if (stat(acl_file, &s) < 0) return(NULL);
134 sprintf(new, NEW_FILE, acl_file);
136 /* Open the new file */
137 if ((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
138 if (errno == EEXIST) {
139 /* Maybe somebody got here already, maybe it's just old */
140 if (stat(new, &s) < 0) return(NULL);
141 if (time(0) - s.st_ctime > WAIT_TIME) {
142 /* File is stale, kill it */
146 /* Wait and try again */
151 /* Some other error, we lose */
156 /* If we got to here, the lock file is ours and ok */
157 /* Reopen it under stdio */
158 if ((nf = fdopen(nfd, "w")) == NULL) {
166 /* Commit changes to acl_file written onto FILE *f */
167 /* Returns zero if successful */
168 /* Returns > 0 if lock was broken */
169 /* Returns < 0 if some other error occurs */
171 static int acl_commit(acl_file, f)
180 sprintf(new, NEW_FILE, acl_file);
182 || fstat(fileno(f), &s) < 0
183 || s.st_nlink == 0) {
184 acl_abort(acl_file, f);
188 ret = rename(new, acl_file);
196 /* Abort changes to acl_file written onto FILE *f */
197 /* Returns 0 if successful, < 0 otherwise */
199 static int acl_abort(acl_file, f)
208 /* make sure we aren't nuking someone else's file */
209 if (fstat(fileno(f), &s) < 0 || s.st_nlink == 0) {
213 sprintf(new, NEW_FILE, acl_file);
223 /* Initialize an acl_file */
224 /* Creates the file with permissions perm if it does not exist */
225 /* Erases it if it does */
226 /* Returns return value of acl_commit */
228 acl_initialize(acl_file, perm)
235 /* Check if the file exists already */
236 if ((new = acl_lock_file(acl_file)) != NULL) {
237 return(acl_commit(acl_file, new));
239 /* File must be readable and writable by owner */
240 if ((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
251 /* Eliminate all whitespace character in buf */
252 /* Modifies its argument */
254 nuke_whitespace(char *buf)
258 for (pin = pout = buf; *pin != '\0'; pin++)
259 if (!isspace(*pin)) *pout++ = *pin;
260 *pout = '\0'; /* Terminate the string */
263 /* Hash table stuff */
266 int size; /* Max number of entries */
267 int entries; /* Actual number of entries */
268 char **tbl; /* Pointer to start of table */
271 /* Make an empty hash table of size s */
272 static struct hashtbl *
277 if (size < 1) size = 1;
278 h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
281 h->tbl = (char **) calloc(size, sizeof(char *));
285 /* Destroy a hash table */
287 destroy_hash(struct hashtbl *h)
291 for (i = 0; i < h->size; i++) {
292 if (h->tbl[i] != NULL) free(h->tbl[i]);
298 /* Compute hash value for a string */
304 for (hv = 0; *s != '\0'; s++) {
305 hv ^= ((hv << 3) ^ *s);
310 /* Add an element to a hash table */
312 add_hash(struct hashtbl *h,
321 fprintf (stderr, "adding %s to acl hash %08X\n", el, h);
323 /* Make space if it isn't there already */
324 if (h->entries + 1 > (h->size >> 1)) {
326 h->tbl = (char **) calloc(h->size << 1, sizeof(char *));
327 for (i = 0; i < h->size; i++) {
328 if (old[i] != NULL) {
329 hv = hashval(old[i]) % (h->size << 1);
330 while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1);
334 h->size = h->size << 1;
338 hv = hashval(el) % h->size;
339 while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size;
340 s = (char *) malloc(strlen(el)+1);
346 /* Returns nonzero if el is in h */
348 check_hash(struct hashtbl *h,
354 fprintf (stderr, "looking for %s in acl %08X\n", el, h);
356 for (hv = hashval(el) % h->size; h->tbl[hv]; hv = (hv + 1) % h->size) {
358 fprintf (stderr, "\tstrcmp (%s,...)\n", h->tbl[hv]);
360 if (!strcmp(h->tbl[hv], el)) {
362 fprintf (stderr, "success!\n");
368 fprintf (stderr, "failure\n");
374 char filename[LINESIZE]; /* Name of acl file */
375 struct hashtbl *acl; /* Acl entries */
378 static struct acl acl_cache[CACHED_ACLS];
380 static int acl_cache_count = 0;
381 static int acl_cache_next = 0;
383 /* Returns < 0 if unsuccessful in loading acl */
384 /* Returns index into acl_cache otherwise */
385 /* Note that if acl is already loaded, this is just a lookup */
386 int acl_load(char *name)
390 char buf[MAX_PRINCIPAL_SIZE];
391 char canon[MAX_PRINCIPAL_SIZE];
393 /* See if it's there already */
394 for (i = 0; i < acl_cache_count; i++) {
395 if (!strcmp(acl_cache[i].filename, name))
399 /* It isn't, load it in */
400 /* maybe there's still room */
401 if (acl_cache_count < CACHED_ACLS) {
402 i = acl_cache_count++;
404 /* No room, clean one out */
406 acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS;
407 if (acl_cache[i].acl) {
408 destroy_hash(acl_cache[i].acl);
409 acl_cache[i].acl = (struct hashtbl *) 0;
414 strcpy(acl_cache[i].filename, name);
416 acl_cache[i].acl = (struct hashtbl *) 0;
420 * See if we need to reload the ACL
422 if (acl_cache[i].acl == (struct hashtbl *) 0) {
425 fprintf (stderr, "attempting to load %s\n", name);
427 if ((f = fopen(name, "r")) == NULL) {
433 if (acl_cache[i].acl) destroy_hash(acl_cache[i].acl);
434 acl_cache[i].acl = make_hash(ACL_LEN);
435 while(fgets(buf, sizeof(buf), f) != NULL) {
436 nuke_whitespace(buf);
437 acl_canonicalize_principal(buf, canon);
438 add_hash(acl_cache[i].acl, canon);
446 * This destroys all cached ACL's so that new ones will be loaded in
447 * the next time they are requested.
450 acl_cache_reset(void)
454 /* See if it's there already */
455 for (i = 0; i < acl_cache_count; i++)
456 if (acl_cache[i].acl) {
457 destroy_hash(acl_cache[i].acl);
458 acl_cache[i].acl = (struct hashtbl *) 0;
465 /* Returns nonzero if it can be determined that acl contains principal */
466 /* Principal is not canonicalized, and no wildcarding is done */
468 acl_exact_match(char *acl,
474 fprintf (stderr, "checking for %s in %s\n", principal, acl);
476 return((idx = acl_load(acl)) >= 0
477 && check_hash(acl_cache[idx].acl, principal));
480 /* Returns nonzero if it can be determined that acl contains principal */
481 /* Recognizes wildcards in acl. */
486 char buf[MAX_PRINCIPAL_SIZE];
487 char canon[MAX_PRINCIPAL_SIZE];
488 char *instance, *realm;
491 /* Parse into principal, instance, and realm. */
492 acl_canonicalize_principal(principal, canon);
493 instance = (char *) strchr(canon, INST_SEP);
495 realm = (char *) strchr(instance, REALM_SEP);
498 for (p = 0; p <= 1; p++) {
499 for (i = 0; i <= 1; i++) {
500 for (r = 0; r <= 1; r++) {
501 sprintf(buf, "%s%c%s%c%s", (p) ? canon : "*", INST_SEP,
502 (i) ? instance : "*", REALM_SEP, (r) ? realm : "*");
503 if (acl_exact_match(acl, buf))
513 /* Adds principal to acl */
514 /* Wildcards are interpreted literally */
516 acl_add(acl, principal)
523 char canon[MAX_PRINCIPAL_SIZE];
525 acl_canonicalize_principal(principal, canon);
527 if ((new = acl_lock_file(acl)) == NULL) return(-1);
528 if ((acl_exact_match(acl, canon))
529 || (idx = acl_load(acl)) < 0) {
533 /* It isn't there yet, copy the file and put it in */
534 for (i = 0; i < acl_cache[idx].acl->size; i++) {
535 if (acl_cache[idx].acl->tbl[i] != NULL) {
536 if (fputs(acl_cache[idx].acl->tbl[i], new) == NULL
537 || putc('\n', new) != '\n') {
545 return(acl_commit(acl, new));
548 /* Removes principal from acl */
549 /* Wildcards are interpreted literally */
551 acl_delete(acl, principal)
558 char canon[MAX_PRINCIPAL_SIZE];
560 acl_canonicalize_principal(principal, canon);
562 if ((new = acl_lock_file(acl)) == NULL) return(-1);
563 if ((!acl_exact_match(acl, canon))
564 || (idx = acl_load(acl)) < 0) {
568 /* It isn't there yet, copy the file and put it in */
569 for (i = 0; i < acl_cache[idx].acl->size; i++) {
570 if (acl_cache[idx].acl->tbl[i] != NULL
571 && strcmp(acl_cache[idx].acl->tbl[i], canon)) {
572 fputs(acl_cache[idx].acl->tbl[i], new);
576 return(acl_commit(acl, new));