source: trunk/athena/lib/acl/acl_files.c @ 8982

Revision 8982, 13.2 KB checked in by ghudson, 28 years ago (diff)
index -> strchr
Line 
1/*
2 *      $Source: /afs/dev.mit.edu/source/repository/athena/lib/acl/acl_files.c,v $
3 *      $Author: ghudson $
4 */
5
6#ifndef lint
7static char rcsid_acl_files_c[] = "$Header: /afs/dev.mit.edu/source/repository/athena/lib/acl/acl_files.c,v 1.12 1996-09-23 01:54:16 ghudson Exp $";
8#endif /* lint */
9
10/*
11
12Copyright 1987 by the Massachusetts Institute of Technology
13
14All Rights Reserved.
15
16*/
17
18/*** Routines for manipulating access control list files ***/
19
20#include <stdio.h>
21#include <string.h>
22#include <sys/types.h>
23#include <sys/file.h>
24#ifdef POSIX
25#include <fcntl.h>
26#endif /* SOLARIS */
27#ifdef SOLARIS
28#include <netdb.h>
29#endif /* SOLARIS */
30#include <sys/stat.h>
31#include <sys/errno.h>
32#include <ctype.h>
33#ifdef KERBEROS
34#include <krb.h>
35#endif
36
37#ifdef KERBEROS
38#ifndef KRB_REALM
39#define KRB_REALM       "ATHENA.MIT.EDU"
40#endif
41#else
42#include <sys/param.h> /* for MAXHOSTNAMELEN */
43#define REALM_SZ        MAXHOSTNAMELEN
44#define INST_SZ         0               /* no instances w/o Kerberos */
45#define ANAME_SZ        9               /* size of a username + null */
46#define KRB_REALM       "ATHENA.MIT.EDU" /* your local "realm" */
47#endif
48
49/* "aname.inst@realm" */
50#define MAX_PRINCIPAL_SIZE  (ANAME_SZ + INST_SZ + REALM_SZ + 3)
51#define INST_SEP '.'
52#define REALM_SEP '@'
53
54#define LINESIZE 2048           /* Maximum line length in an acl file */
55
56#define NEW_FILE "%s.~NEWACL~"  /* Format for name of altered acl file */
57#define WAIT_TIME 300           /* Maximum time allowed write acl file */
58
59#define CACHED_ACLS 8           /* How many acls to cache */
60                                /* Each acl costs 1 open file descriptor */
61#define ACL_LEN 16              /* Twice a reasonable acl length */
62
63#define MAX(a,b) (((a)>(b))?(a):(b))
64#define MIN(a,b) (((a)<(b))?(a):(b))
65
66#define COR(a,b) ((a!=NULL)?(a):(b))
67
68extern int errno;
69
70extern char *malloc(), *calloc();
71extern time_t time();
72
73static int acl_abort();
74
75/* Canonicalize a principal name */
76/* If instance is missing, it becomes "" */
77/* If realm is missing, it becomes the local realm */
78/* Canonicalized form is put in canon, which must be big enough to hold
79   MAX_PRINCIPAL_SIZE characters */
80acl_canonicalize_principal(principal, canon)
81char *principal;
82char *canon;
83{
84    char *dot, *atsign, *end;
85    int len;
86
87    dot = strchr(principal, INST_SEP);
88    atsign = strchr(principal, REALM_SEP);
89
90    /* Maybe we're done already */
91    if(dot != NULL && atsign != NULL) {
92        if(dot < atsign) {
93            /* It's for real */
94            /* Copy into canon */
95            strncpy(canon, principal, MAX_PRINCIPAL_SIZE);
96            canon[MAX_PRINCIPAL_SIZE-1] = '\0';
97            return;
98        } else {
99            /* Nope, it's part of the realm */
100            dot = NULL;
101        }
102    }
103   
104    /* No such luck */
105    end = principal + strlen(principal);
106
107    /* Get the principal name */
108    len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal);
109    strncpy(canon, principal, len);
110    canon += len;
111
112    /* Add INST_SEP */
113    *canon++ = INST_SEP;
114
115    /* Get the instance, if it exists */
116    if(dot != NULL) {
117        ++dot;
118        len = MIN(INST_SZ, COR(atsign, end) - dot);
119        strncpy(canon, dot, len);
120        canon += len;
121    }
122
123    /* Add REALM_SEP */
124    *canon++ = REALM_SEP;
125
126    /* Get the realm, if it exists */
127    /* Otherwise, default to local realm */
128    if(atsign != NULL) {
129        ++atsign;
130        len = MIN(REALM_SZ, end - atsign);
131        strncpy(canon, atsign, len);
132        canon += len;
133        *canon++ = '\0';
134    }
135#ifdef KERBEROS
136    else if(krb_get_lrealm(canon, 1) != KSUCCESS) {
137        strcpy(canon, KRB_REALM);
138    }
139#else
140    else strcpy(canon, KRB_REALM);
141#endif
142}
143           
144/* Get a lock to modify acl_file */
145/* Return new FILE pointer */
146/* or NULL if file cannot be modified */
147/* REQUIRES WRITE PERMISSION TO CONTAINING DIRECTORY */
148static FILE *acl_lock_file(acl_file)
149char *acl_file;
150{
151    struct stat s;
152    char new[LINESIZE];
153    int nfd;
154    FILE *nf;
155    int mode;
156
157    if(stat(acl_file, &s) < 0) return(NULL);
158    mode = s.st_mode;
159    sprintf(new, NEW_FILE, acl_file);
160    for(;;) {
161        /* Open the new file */
162        if((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
163            if(errno == EEXIST) {
164                /* Maybe somebody got here already, maybe it's just old */
165                if(stat(new, &s) < 0) return(NULL);
166                if(time(0) - s.st_ctime > WAIT_TIME) {
167                    /* File is stale, kill it */
168                    unlink(new);
169                    continue;
170                } else {
171                    /* Wait and try again */
172                    sleep(1);
173                    continue;
174                }
175            } else {
176                /* Some other error, we lose */
177                return(NULL);
178            }
179        }
180
181        /* If we got to here, the lock file is ours and ok */
182        /* Reopen it under stdio */
183        if((nf = fdopen(nfd, "w")) == NULL) {
184            /* Oops, clean up */
185            unlink(new);
186        }
187        return(nf);
188    }
189}
190
191/* Commit changes to acl_file written onto FILE *f */
192/* Returns zero if successful */
193/* Returns > 0 if lock was broken */
194/* Returns < 0 if some other error occurs */
195/* Closes f */
196static int acl_commit(acl_file, f)
197char *acl_file;
198FILE *f;     
199{
200    char new[LINESIZE];
201    int ret;
202    struct stat s;
203
204    sprintf(new, NEW_FILE, acl_file);
205    if(fflush(f) < 0
206       || fstat(fileno(f), &s) < 0
207       || s.st_nlink == 0) {
208        acl_abort(acl_file, f);
209        return(-1);
210    }
211
212    ret = rename(new, acl_file);
213    fclose(f);
214    return(ret);
215}
216
217/* Abort changes to acl_file written onto FILE *f */
218/* Returns 0 if successful, < 0 otherwise */
219/* Closes f */
220static int acl_abort(acl_file, f)
221char *acl_file;
222FILE *f;     
223{
224    char new[LINESIZE];
225    int ret;
226    struct stat s;
227
228    /* make sure we aren't nuking someone else's file */
229    if(fstat(fileno(f), &s) < 0
230       || s.st_nlink == 0) {
231           fclose(f);
232           return(-1);
233       } else {
234           sprintf(new, NEW_FILE, acl_file);
235           ret = unlink(new);
236           fclose(f);
237           return(ret);
238       }
239}
240
241/* Initialize an acl_file */
242/* Creates the file with permissions perm if it does not exist */
243/* Erases it if it does */
244/* Returns return value of acl_commit */
245int acl_initialize(acl_file, perm)
246char *acl_file;
247int perm;
248{
249    FILE *new;
250    int fd;
251
252    /* Check if the file exists already */
253    if((new = acl_lock_file(acl_file)) != NULL) {
254        return(acl_commit(acl_file, new));
255    } else {
256        /* File must be readable and writable by owner */
257        if((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
258            return(-1);
259        } else {
260            close(fd);
261            return(0);
262        }
263    }
264}
265
266/* Eliminate all whitespace character in buf */
267/* Modifies its argument */
268static nuke_whitespace(buf)
269char *buf;
270{
271    register char *pin, *pout;
272
273    for(pin = pout = buf; *pin != '\0'; pin++)
274        if(!isspace(*pin)) *pout++ = *pin;
275    *pout = '\0';               /* Terminate the string */
276}
277
278/* Hash table stuff */
279
280struct hashtbl {
281    int size;                   /* Max number of entries */
282    int entries;                /* Actual number of entries */
283    char **tbl;                 /* Pointer to start of table */
284};
285
286/* Make an empty hash table of size s */
287static struct hashtbl *make_hash(size)
288int size;
289{
290    struct hashtbl *h;
291
292    if(size < 1) size = 1;
293    h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
294    h->size = size;
295    h->entries = 0;
296    h->tbl = (char **) calloc(size, sizeof(char *));
297    return(h);
298}
299
300/* Destroy a hash table */
301static destroy_hash(h)
302struct hashtbl *h;
303{
304    int i;
305
306    for(i = 0; i < h->size; i++) {
307        if(h->tbl[i] != NULL) free(h->tbl[i]);
308    }
309    free(h->tbl);
310    free(h);
311}
312
313/* Compute hash value for a string */
314static unsigned hashval(s)
315register char *s;
316{
317    register unsigned hv;
318
319    for(hv = 0; *s != '\0'; s++) {
320        hv ^= ((hv << 3) ^ *s);
321    }
322    return(hv);
323}
324
325/* Add an element to a hash table */
326static add_hash(h, el)
327struct hashtbl *h;
328char *el;
329{
330    unsigned hv;
331    char *s;
332    char **old;
333    int i;
334
335    /* Make space if it isn't there already */
336    if(h->entries + 1 > (h->size >> 1)) {
337        old = h->tbl;
338        h->tbl = (char **) calloc(h->size << 1, sizeof(char *));
339        for(i = 0; i < h->size; i++) {
340            if(old[i] != NULL) {
341                hv = hashval(old[i]) % (h->size << 1);
342                while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1);
343                h->tbl[hv] = old[i];
344            }
345        }
346        h->size = h->size << 1;
347        free(old);
348    }
349
350    hv = hashval(el) % h->size;
351    while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size;
352    s = malloc(strlen(el)+1);
353    strcpy(s, el);
354    h->tbl[hv] = s;
355    h->entries++;
356}
357
358/* Returns nonzero if el is in h */
359static check_hash(h, el)
360struct hashtbl *h;
361char *el;
362{
363    unsigned hv;
364
365    for(hv = hashval(el) % h->size;
366        h->tbl[hv] != NULL;
367        hv = (hv + 1) % h->size) {
368            if(!strcmp(h->tbl[hv], el)) return(1);
369        }
370    return(0);
371}
372
373struct acl {
374    char filename[LINESIZE];    /* Name of acl file */
375    int fd;                     /* File descriptor for acl file */
376    struct stat status;         /* File status at last read */
377    struct hashtbl *acl;        /* Acl entries */
378};
379
380static struct acl acl_cache[CACHED_ACLS];
381
382static int acl_cache_count = 0;
383static int acl_cache_next = 0;
384
385/* Returns < 0 if unsuccessful in loading acl */
386/* Returns index into acl_cache otherwise */
387/* Note that if acl is already loaded, this is just a lookup */
388static int acl_load(name)
389char *name;
390{
391    int i;
392    FILE *f;
393    struct stat s;
394    char buf[MAX_PRINCIPAL_SIZE];
395    char canon[MAX_PRINCIPAL_SIZE];
396
397    /* See if it's there already */
398    for(i = 0; i < acl_cache_count; i++) {
399        if(!strcmp(acl_cache[i].filename, name)
400           && acl_cache[i].fd >= 0) goto got_it;
401    }
402
403    /* It isn't, load it in */
404    /* maybe there's still room */
405    if(acl_cache_count < CACHED_ACLS) {
406        i = acl_cache_count++;
407    } else {
408        /* No room, clean one out */
409        i = acl_cache_next;
410        acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS;
411        close(acl_cache[i].fd);
412        if(acl_cache[i].acl) {
413            destroy_hash(acl_cache[i].acl);
414            acl_cache[i].acl = (struct hashtbl *) 0;
415        }
416    }
417
418    /* Set up the acl */
419    strcpy(acl_cache[i].filename, name);
420    if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
421    /* Force reload */
422    acl_cache[i].acl = (struct hashtbl *) 0;
423
424 got_it:
425    /*
426     * See if the stat matches
427     *
428     * Use stat(), not fstat(), as the file may have been re-created by
429     * acl_add or acl_delete.  If this happens, the old inode will have
430     * no changes in the mod-time and the following test will fail.
431     */
432    if(stat(acl_cache[i].filename, &s) < 0) return(-1);
433    if(acl_cache[i].acl == (struct hashtbl *) 0
434       || s.st_nlink != acl_cache[i].status.st_nlink
435       || s.st_mtime != acl_cache[i].status.st_mtime
436       || s.st_ctime != acl_cache[i].status.st_ctime) {
437           /* Gotta reload */
438           if(acl_cache[i].fd >= 0) close(acl_cache[i].fd);
439           if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
440           if((f = fdopen(acl_cache[i].fd, "r")) == NULL) return(-1);
441           if(acl_cache[i].acl) destroy_hash(acl_cache[i].acl);
442           acl_cache[i].acl = make_hash(ACL_LEN);
443           while(fgets(buf, sizeof(buf), f) != NULL) {
444               nuke_whitespace(buf);
445               acl_canonicalize_principal(buf, canon);
446               add_hash(acl_cache[i].acl, canon);
447           }
448           fclose(f);
449           acl_cache[i].status = s;
450       }
451    return(i);
452}
453
454/* Returns nonzero if it can be determined that acl contains principal */
455/* Principal is not canonicalized, and no wildcarding is done */
456acl_exact_match(acl, principal)
457char *acl;
458char *principal;
459{
460    int idx;
461
462    return((idx = acl_load(acl)) >= 0
463           && check_hash(acl_cache[idx].acl, principal));
464}
465
466/* Returns nonzero if it can be determined that acl contains principal */
467/* Recognizes wildcards in acl of the form
468   name.*@realm, *.*@realm, and *.*@* */
469acl_check(acl, principal)
470char *acl;
471char *principal;
472{
473    char buf[MAX_PRINCIPAL_SIZE];
474    char canon[MAX_PRINCIPAL_SIZE];
475    char *realm;
476
477    acl_canonicalize_principal(principal, canon);
478
479    /* Is it there? */
480    if(acl_exact_match(acl, canon)) return(1);
481
482    /* Try the wildcards */
483    realm = strchr(canon, REALM_SEP);
484    *strchr(canon, INST_SEP) = '\0';    /* Chuck the instance */
485
486    sprintf(buf, "%s.*%s", canon, realm);
487    if(acl_exact_match(acl, buf)) return(1);
488
489    sprintf(buf, "*.*%s", realm);
490    if(acl_exact_match(acl, buf) || acl_exact_match(acl, "*.*@*")) return(1);
491       
492    return(0);
493}
494
495/* Adds principal to acl */
496/* Wildcards are interpreted literally */
497acl_add(acl, principal)
498char *acl;
499char *principal;
500{
501    int idx;
502    int i;
503    FILE *new;
504    char canon[MAX_PRINCIPAL_SIZE];
505
506    acl_canonicalize_principal(principal, canon);
507
508    if((new = acl_lock_file(acl)) == NULL) return(-1);
509    if((acl_exact_match(acl, canon))
510       || (idx = acl_load(acl)) < 0) {
511           acl_abort(acl, new);
512           return(-1);
513       }
514    /* It isn't there yet, copy the file and put it in */
515    for(i = 0; i < acl_cache[idx].acl->size; i++) {
516        if(acl_cache[idx].acl->tbl[i] != NULL) {
517            if(fputs(acl_cache[idx].acl->tbl[i], new) == NULL
518               || putc('\n', new) != '\n') {
519                   acl_abort(acl, new);
520                   return(-1);
521               }
522        }
523    }
524    fputs(canon, new);
525    putc('\n', new);
526    return(acl_commit(acl, new));
527}
528
529/* Removes principal from acl */
530/* Wildcards are interpreted literally */
531acl_delete(acl, principal)
532char *acl;
533char *principal;
534{
535    int idx;
536    int i;
537    FILE *new;
538    char canon[MAX_PRINCIPAL_SIZE];
539
540    acl_canonicalize_principal(principal, canon);
541
542    if((new = acl_lock_file(acl)) == NULL) return(-1);
543    if((!acl_exact_match(acl, canon))
544       || (idx = acl_load(acl)) < 0) {
545           acl_abort(acl, new);
546           return(-1);
547       }
548    /* It isn't there yet, copy the file and put it in */
549    for(i = 0; i < acl_cache[idx].acl->size; i++) {
550        if(acl_cache[idx].acl->tbl[i] != NULL
551           && strcmp(acl_cache[idx].acl->tbl[i], canon)) {
552               fputs(acl_cache[idx].acl->tbl[i], new);
553               putc('\n', new);
554        }
555    }
556    return(acl_commit(acl, new));
557}
558
Note: See TracBrowser for help on using the repository browser.