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

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