]> asedeno.scripts.mit.edu Git - 1ts-debian.git/blob - zephyr/server/acl_files.c
r4265@bucket (orig r255): kcr | 2008-01-20 22:14:09 -0500
[1ts-debian.git] / zephyr / server / acl_files.c
1 /* This file is part of the Project Athena Zephyr Notification System.
2  * It contains functions for maintaining Access Control Lists.
3  *
4  *      Created by:     John T. Kohl
5  *
6  *      $Id$
7  *
8  *      Copyright (c) 1987,1988 by the Massachusetts Institute of Technology.
9  *      For copying and distribution information, see the file
10  *      "mit-copyright.h". 
11  */
12
13 /* Define this if you really want the ACL-writing code included.  */
14 /* #define WRITE_ACL */
15
16 /*
17  * Stolen from lib/acl_files.c because acl_load needs to be externally
18  * declared and not statically declared.
19  */
20
21 #include <zephyr/mit-copyright.h>
22 #include "zserver.h"
23
24
25 #ifndef SABER
26 #ifndef lint
27 static const char rcsid_acl_files_c[] = "$Id$";
28 #endif /* lint */
29 #endif /* SABER */
30
31 /*** Routines for manipulating access control list files ***/
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 64          /* How many acls to cache */
44 #define ACL_LEN 256             /* Twice a reasonable acl length */
45
46 #define MAX(a,b) (((a)>(b))?(a):(b))
47 #define MIN(a,b) (((a)<(b))?(a):(b))
48
49 #define COR(a,b) ((a!=NULL)?(a):(b))
50
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,
57                                 char *canon)
58 {
59     char *end;
60     char *dot, *atsign;
61     int len;
62
63     dot = strchr(principal, INST_SEP);
64     atsign = strchr(principal, REALM_SEP);
65
66     /* Maybe we're done already */
67     if (dot != NULL && atsign != NULL) {
68         if (dot < atsign) {
69             /* It's for real */
70             /* Copy into canon */
71             strncpy(canon, principal, MAX_PRINCIPAL_SIZE);
72             canon[MAX_PRINCIPAL_SIZE-1] = '\0';
73             return;
74         } else {
75             /* Nope, it's part of the realm */
76             dot = NULL;
77         }
78     }
79     
80     /* No such luck */
81     end = principal + strlen(principal);
82
83     /* Get the principal name */
84     len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal);
85     strncpy(canon, principal, len);
86     canon += len;
87
88     /* Add INST_SEP */
89     *canon++ = INST_SEP;
90
91     /* Get the instance, if it exists */
92     if (dot != NULL) {
93         ++dot;
94         len = MIN(INST_SZ, COR(atsign, end) - dot);
95         strncpy(canon, dot, len);
96         canon += len;
97     }
98
99     /* Add REALM_SEP */
100     *canon++ = REALM_SEP;
101
102     /* Get the realm, if it exists */
103     /* Otherwise, default to local realm */
104     if (atsign != NULL) {
105         ++atsign;
106         len = MIN(REALM_SZ, end - atsign);
107         strncpy(canon, atsign, len);
108         canon += len;
109         *canon++ = '\0';
110     } 
111 #ifdef HAVE_KRB4
112     else if (krb_get_lrealm(canon, 1) != KSUCCESS) {
113         strcpy(canon, KRB_REALM);
114     }
115 #endif
116 }
117
118 #ifdef notdef
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)
124     char *acl_file;
125 {
126     struct stat s;
127     char new[LINESIZE];
128     int nfd;
129     FILE *nf;
130     int mode;
131
132     if (stat(acl_file, &s) < 0) return(NULL);
133     mode = s.st_mode;
134     sprintf(new, NEW_FILE, acl_file);
135     for (;;) {
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 */
143                     unlink(new);
144                     continue;
145                 } else {
146                     /* Wait and try again */
147                     sleep(1);
148                     continue;
149                 }
150             } else {
151                 /* Some other error, we lose */
152                 return(NULL);
153             }
154         }
155
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) {
159             /* Oops, clean up */
160             unlink(new);
161         }
162         return(nf);
163     }
164 }
165
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 */
170 /* Closes f */
171 static int acl_commit(acl_file, f)
172     char *acl_file;
173     FILE *f;     
174 {
175 #ifdef WRITE_ACL
176     char new[LINESIZE];
177     int ret;
178     struct stat s;
179
180     sprintf(new, NEW_FILE, acl_file);
181     if (fflush(f) < 0
182        || fstat(fileno(f), &s) < 0
183        || s.st_nlink == 0) {
184         acl_abort(acl_file, f);
185         return(-1);
186     }
187
188     ret = rename(new, acl_file);
189     fclose(f);
190     return(ret);
191 #else
192     abort ();
193 #endif
194 }
195
196 /* Abort changes to acl_file written onto FILE *f */
197 /* Returns 0 if successful, < 0 otherwise */
198 /* Closes f */
199 static int acl_abort(acl_file, f)
200     char *acl_file;
201     FILE *f;     
202 {
203 #ifdef WRITE_ACL
204     char new[LINESIZE];
205     int ret;
206     struct stat s;
207
208     /* make sure we aren't nuking someone else's file */
209     if (fstat(fileno(f), &s) < 0 || s.st_nlink == 0) {
210         fclose(f);
211         return(-1);
212     } else {
213         sprintf(new, NEW_FILE, acl_file);
214         ret = unlink(new);
215         fclose(f);
216         return(ret);
217     }
218 #else
219     abort ();
220 #endif
221 }
222
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 */
227 int
228 acl_initialize(acl_file, perm)
229     char *acl_file;
230     int perm;
231 {
232     FILE *new;
233     int fd;
234
235     /* Check if the file exists already */
236     if ((new = acl_lock_file(acl_file)) != NULL) {
237         return(acl_commit(acl_file, new));
238     } else {
239         /* File must be readable and writable by owner */
240         if ((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
241             return(-1);
242         } else {
243             close(fd);
244             return(0);
245         }
246     }
247 }
248
249 #endif /* notdef */
250
251 /* Eliminate all whitespace character in buf */
252 /* Modifies its argument */
253 static void
254 nuke_whitespace(char *buf)
255 {
256     char *pin, *pout;
257
258     for (pin = pout = buf; *pin != '\0'; pin++)
259         if (!isspace(*pin)) *pout++ = *pin;
260     *pout = '\0';               /* Terminate the string */
261 }
262
263 /* Hash table stuff */
264
265 struct hashtbl {
266     int size;                   /* Max number of entries */
267     int entries;                /* Actual number of entries */
268     char **tbl;                 /* Pointer to start of table */
269 };
270
271 /* Make an empty hash table of size s */
272 static struct hashtbl *
273 make_hash(int size)
274 {
275     struct hashtbl *h;
276
277     if (size < 1) size = 1;
278     h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
279     h->size = size;
280     h->entries = 0;
281     h->tbl = (char **) calloc(size, sizeof(char *));
282     return(h);
283 }
284
285 /* Destroy a hash table */
286 static void
287 destroy_hash(struct hashtbl *h)
288 {
289     int i;
290
291     for (i = 0; i < h->size; i++) {
292         if (h->tbl[i] != NULL) free(h->tbl[i]);
293     }
294     free(h->tbl);
295     free(h);
296 }
297
298 /* Compute hash value for a string */
299 static unsigned int
300 hashval(char *s)
301 {
302     unsigned hv;
303
304     for (hv = 0; *s != '\0'; s++) {
305         hv ^= ((hv << 3) ^ *s);
306     }
307     return(hv);
308 }
309
310 /* Add an element to a hash table */
311 static void
312 add_hash(struct hashtbl *h,
313          char *el)
314 {
315     unsigned hv;
316     char *s;
317     char **old;
318     int i;
319
320 #if 0
321     fprintf (stderr, "adding %s to acl hash %08X\n", el, h);
322 #endif
323     /* Make space if it isn't there already */
324     if (h->entries + 1 > (h->size >> 1)) {
325         old = h->tbl;
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);
331                 h->tbl[hv] = old[i];
332             }
333         }
334         h->size = h->size << 1;
335         free(old);
336     }
337
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);
341     strcpy(s, el);
342     h->tbl[hv] = s;
343     h->entries++;
344 }
345
346 /* Returns nonzero if el is in h */
347 static int
348 check_hash(struct hashtbl *h,
349            char *el)
350 {
351     unsigned hv;
352
353 #if 0
354     fprintf (stderr, "looking for %s in acl %08X\n", el, h);
355 #endif
356     for (hv = hashval(el) % h->size; h->tbl[hv]; hv = (hv + 1) % h->size) {
357 #if 0
358         fprintf (stderr, "\tstrcmp (%s,...)\n", h->tbl[hv]);
359 #endif
360         if (!strcmp(h->tbl[hv], el)) {
361 #if 0
362             fprintf (stderr, "success!\n");
363 #endif
364             return 1;
365         }
366     }
367 #if 0
368     fprintf (stderr, "failure\n");
369 #endif
370     return 0;
371 }
372
373 struct acl {
374     char filename[LINESIZE];    /* Name of acl file */
375     struct hashtbl *acl;        /* Acl entries */
376 };
377
378 static struct acl acl_cache[CACHED_ACLS];
379
380 static int acl_cache_count = 0;
381 static int acl_cache_next = 0;
382
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)
387 {
388     int i;
389     FILE *f;
390     char buf[MAX_PRINCIPAL_SIZE];
391     char canon[MAX_PRINCIPAL_SIZE];
392
393     /* See if it's there already */
394     for (i = 0; i < acl_cache_count; i++) {
395         if (!strcmp(acl_cache[i].filename, name))
396             goto got_it;
397     }
398
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++;
403     } else {
404         /* No room, clean one out */
405         i = acl_cache_next;
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;
410         }
411     }
412
413     /* Set up the acl */
414     strcpy(acl_cache[i].filename, name);
415     /* Force reload */
416     acl_cache[i].acl = (struct hashtbl *) 0;
417
418   got_it:
419     /*
420      * See if we need to reload the ACL
421      */
422     if (acl_cache[i].acl == (struct hashtbl *) 0) {
423         /* Gotta reload */
424 #if 0
425         fprintf (stderr, "attempting to load %s\n", name);
426 #endif
427         if ((f = fopen(name, "r")) == NULL) {
428 #if 0
429             perror (name);
430 #endif
431             return -1;
432         }
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);
439         }
440         fclose(f);
441     }
442     return(i);
443 }
444
445 /*
446  * This destroys all cached ACL's so that new ones will be loaded in
447  * the next time they are requested.
448  */
449 void
450 acl_cache_reset(void)
451 {
452         int     i;
453         
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;
459             }
460         acl_cache_count = 0;
461         acl_cache_next = 0;
462     }
463
464
465 /* Returns nonzero if it can be determined that acl contains principal */
466 /* Principal is not canonicalized, and no wildcarding is done */
467 int
468 acl_exact_match(char *acl,
469                 char *principal)
470 {
471     int idx;
472
473 #if 0
474     fprintf (stderr, "checking for %s in %s\n", principal, acl);
475 #endif
476     return((idx = acl_load(acl)) >= 0
477            && check_hash(acl_cache[idx].acl, principal));
478 }
479
480 /* Returns nonzero if it can be determined that acl contains principal */
481 /* Recognizes wildcards in acl. */
482 int
483 acl_check(char *acl,
484           char *principal)
485 {
486     char buf[MAX_PRINCIPAL_SIZE];
487     char canon[MAX_PRINCIPAL_SIZE];
488     char *instance, *realm;
489     int p, i, r;
490
491     /* Parse into principal, instance, and realm. */
492     acl_canonicalize_principal(principal, canon);
493     instance = (char *) strchr(canon, INST_SEP);
494     *instance++ = 0;
495     realm = (char *) strchr(instance, REALM_SEP);
496     *realm++ = 0;
497
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))
504                     return 1;
505             }
506         }
507     }
508        
509     return(0);
510 }
511
512 #ifdef notdef
513 /* Adds principal to acl */
514 /* Wildcards are interpreted literally */
515 int
516 acl_add(acl, principal)
517     char *acl;
518     char *principal;
519 {
520     int idx;
521     int i;
522     FILE *new;
523     char canon[MAX_PRINCIPAL_SIZE];
524
525     acl_canonicalize_principal(principal, canon);
526
527     if ((new = acl_lock_file(acl)) == NULL) return(-1);
528     if ((acl_exact_match(acl, canon))
529        || (idx = acl_load(acl)) < 0) {
530         acl_abort(acl, new);
531         return(-1);
532     }
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') {
538                 acl_abort(acl, new);
539                 return(-1);
540             }
541         }
542     }
543     fputs(canon, new);
544     putc('\n', new);
545     return(acl_commit(acl, new));
546 }
547
548 /* Removes principal from acl */
549 /* Wildcards are interpreted literally */
550 int
551 acl_delete(acl, principal)
552     char *acl;
553     char *principal;
554 {
555     int idx;
556     int i;
557     FILE *new;
558     char canon[MAX_PRINCIPAL_SIZE];
559
560     acl_canonicalize_principal(principal, canon);
561
562     if ((new = acl_lock_file(acl)) == NULL) return(-1);
563     if ((!acl_exact_match(acl, canon))
564        || (idx = acl_load(acl)) < 0) {
565         acl_abort(acl, new);
566         return(-1);
567     }
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);
573             putc('\n', new);
574         }
575     }
576     return(acl_commit(acl, new));
577 }
578 #endif /* notdef */