source: trunk/athena/bin/olc/server/olcd/acl_files.c @ 2025

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