]> asedeno.scripts.mit.edu Git - 1ts-debian.git/blob - zephyr/server/acl_files.c
4c44402fb51a8c012d111da0361059cb54c8d89b
[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 extern int errno;
52
53 extern time_t time();
54
55 /* Canonicalize a principal name */
56 /* If instance is missing, it becomes "" */
57 /* If realm is missing, it becomes the local realm */
58 /* Canonicalized form is put in canon, which must be big enough to hold
59    MAX_PRINCIPAL_SIZE characters */
60 void acl_canonicalize_principal(principal, canon)
61     char *principal;
62     char *canon;
63 {
64     char *end;
65     char *dot, *atsign;
66     int len;
67
68     dot = strchr(principal, INST_SEP);
69     atsign = strchr(principal, REALM_SEP);
70
71     /* Maybe we're done already */
72     if (dot != NULL && atsign != NULL) {
73         if (dot < atsign) {
74             /* It's for real */
75             /* Copy into canon */
76             strncpy(canon, principal, MAX_PRINCIPAL_SIZE);
77             canon[MAX_PRINCIPAL_SIZE-1] = '\0';
78             return;
79         } else {
80             /* Nope, it's part of the realm */
81             dot = NULL;
82         }
83     }
84     
85     /* No such luck */
86     end = principal + strlen(principal);
87
88     /* Get the principal name */
89     len = MIN(ANAME_SZ, COR(dot, COR(atsign, end)) - principal);
90     strncpy(canon, principal, len);
91     canon += len;
92
93     /* Add INST_SEP */
94     *canon++ = INST_SEP;
95
96     /* Get the instance, if it exists */
97     if (dot != NULL) {
98         ++dot;
99         len = MIN(INST_SZ, COR(atsign, end) - dot);
100         strncpy(canon, dot, len);
101         canon += len;
102     }
103
104     /* Add REALM_SEP */
105     *canon++ = REALM_SEP;
106
107     /* Get the realm, if it exists */
108     /* Otherwise, default to local realm */
109     if (atsign != NULL) {
110         ++atsign;
111         len = MIN(REALM_SZ, end - atsign);
112         strncpy(canon, atsign, len);
113         canon += len;
114         *canon++ = '\0';
115     } 
116 #ifdef HAVE_KRB4
117     else if (krb_get_lrealm(canon, 1) != KSUCCESS) {
118         strcpy(canon, KRB_REALM);
119     }
120 #endif
121 }
122
123 #ifdef notdef
124 /* Get a lock to modify acl_file */
125 /* Return new FILE pointer */
126 /* or NULL if file cannot be modified */
127 /* REQUIRES WRITE PERMISSION TO CONTAINING DIRECTORY */
128 static FILE *acl_lock_file(acl_file)
129     char *acl_file;
130 {
131     struct stat s;
132     char new[LINESIZE];
133     int nfd;
134     FILE *nf;
135     int mode;
136
137     if (stat(acl_file, &s) < 0) return(NULL);
138     mode = s.st_mode;
139     sprintf(new, NEW_FILE, acl_file);
140     for (;;) {
141         /* Open the new file */
142         if ((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
143             if (errno == EEXIST) {
144                 /* Maybe somebody got here already, maybe it's just old */
145                 if (stat(new, &s) < 0) return(NULL);
146                 if (time(0) - s.st_ctime > WAIT_TIME) {
147                     /* File is stale, kill it */
148                     unlink(new);
149                     continue;
150                 } else {
151                     /* Wait and try again */
152                     sleep(1);
153                     continue;
154                 }
155             } else {
156                 /* Some other error, we lose */
157                 return(NULL);
158             }
159         }
160
161         /* If we got to here, the lock file is ours and ok */
162         /* Reopen it under stdio */
163         if ((nf = fdopen(nfd, "w")) == NULL) {
164             /* Oops, clean up */
165             unlink(new);
166         }
167         return(nf);
168     }
169 }
170
171 /* Commit changes to acl_file written onto FILE *f */
172 /* Returns zero if successful */
173 /* Returns > 0 if lock was broken */
174 /* Returns < 0 if some other error occurs */
175 /* Closes f */
176 static int acl_commit(acl_file, f)
177     char *acl_file;
178     FILE *f;     
179 {
180 #ifdef WRITE_ACL
181     char new[LINESIZE];
182     int ret;
183     struct stat s;
184
185     sprintf(new, NEW_FILE, acl_file);
186     if (fflush(f) < 0
187        || fstat(fileno(f), &s) < 0
188        || s.st_nlink == 0) {
189         acl_abort(acl_file, f);
190         return(-1);
191     }
192
193     ret = rename(new, acl_file);
194     fclose(f);
195     return(ret);
196 #else
197     abort ();
198 #endif
199 }
200
201 /* Abort changes to acl_file written onto FILE *f */
202 /* Returns 0 if successful, < 0 otherwise */
203 /* Closes f */
204 static int acl_abort(acl_file, f)
205     char *acl_file;
206     FILE *f;     
207 {
208 #ifdef WRITE_ACL
209     char new[LINESIZE];
210     int ret;
211     struct stat s;
212
213     /* make sure we aren't nuking someone else's file */
214     if (fstat(fileno(f), &s) < 0 || s.st_nlink == 0) {
215         fclose(f);
216         return(-1);
217     } else {
218         sprintf(new, NEW_FILE, acl_file);
219         ret = unlink(new);
220         fclose(f);
221         return(ret);
222     }
223 #else
224     abort ();
225 #endif
226 }
227
228 /* Initialize an acl_file */
229 /* Creates the file with permissions perm if it does not exist */
230 /* Erases it if it does */
231 /* Returns return value of acl_commit */
232 int
233 acl_initialize(acl_file, perm)
234     char *acl_file;
235     int perm;
236 {
237     FILE *new;
238     int fd;
239
240     /* Check if the file exists already */
241     if ((new = acl_lock_file(acl_file)) != NULL) {
242         return(acl_commit(acl_file, new));
243     } else {
244         /* File must be readable and writable by owner */
245         if ((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
246             return(-1);
247         } else {
248             close(fd);
249             return(0);
250         }
251     }
252 }
253
254 #endif /* notdef */
255
256 /* Eliminate all whitespace character in buf */
257 /* Modifies its argument */
258 static void nuke_whitespace(buf)
259     char *buf;
260 {
261     char *pin, *pout;
262
263     for (pin = pout = buf; *pin != '\0'; pin++)
264         if (!isspace(*pin)) *pout++ = *pin;
265     *pout = '\0';               /* Terminate the string */
266 }
267
268 /* Hash table stuff */
269
270 struct hashtbl {
271     int size;                   /* Max number of entries */
272     int entries;                /* Actual number of entries */
273     char **tbl;                 /* Pointer to start of table */
274 };
275
276 /* Make an empty hash table of size s */
277 static struct hashtbl *make_hash(size)
278     int size;
279 {
280     struct hashtbl *h;
281
282     if (size < 1) size = 1;
283     h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
284     h->size = size;
285     h->entries = 0;
286     h->tbl = (char **) calloc(size, sizeof(char *));
287     return(h);
288 }
289
290 /* Destroy a hash table */
291 static void
292 destroy_hash(h)
293     struct hashtbl *h;
294 {
295     int i;
296
297     for (i = 0; i < h->size; i++) {
298         if (h->tbl[i] != NULL) free(h->tbl[i]);
299     }
300     free(h->tbl);
301     free(h);
302 }
303
304 /* Compute hash value for a string */
305 static unsigned int
306 hashval(s)
307     char *s;
308 {
309     unsigned hv;
310
311     for (hv = 0; *s != '\0'; s++) {
312         hv ^= ((hv << 3) ^ *s);
313     }
314     return(hv);
315 }
316
317 /* Add an element to a hash table */
318 static void add_hash(h, el)
319     struct hashtbl *h;
320     char *el;
321 {
322     unsigned hv;
323     char *s;
324     char **old;
325     int i;
326
327 #if 0
328     fprintf (stderr, "adding %s to acl hash %08X\n", el, h);
329 #endif
330     /* Make space if it isn't there already */
331     if (h->entries + 1 > (h->size >> 1)) {
332         old = h->tbl;
333         h->tbl = (char **) calloc(h->size << 1, sizeof(char *));
334         for (i = 0; i < h->size; i++) {
335             if (old[i] != NULL) {
336                 hv = hashval(old[i]) % (h->size << 1);
337                 while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1);
338                 h->tbl[hv] = old[i];
339             }
340         }
341         h->size = h->size << 1;
342         free(old);
343     }
344
345     hv = hashval(el) % h->size;
346     while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size;
347     s = (char *) malloc(strlen(el)+1);
348     strcpy(s, el);
349     h->tbl[hv] = s;
350     h->entries++;
351 }
352
353 /* Returns nonzero if el is in h */
354 static int
355 check_hash(h, el)
356     struct hashtbl *h;
357     char *el;
358 {
359     unsigned hv;
360
361 #if 0
362     fprintf (stderr, "looking for %s in acl %08X\n", el, h);
363 #endif
364     for (hv = hashval(el) % h->size; h->tbl[hv]; hv = (hv + 1) % h->size) {
365 #if 0
366         fprintf (stderr, "\tstrcmp (%s,...)\n", h->tbl[hv]);
367 #endif
368         if (!strcmp(h->tbl[hv], el)) {
369 #if 0
370             fprintf (stderr, "success!\n");
371 #endif
372             return 1;
373         }
374     }
375 #if 0
376     fprintf (stderr, "failure\n");
377 #endif
378     return 0;
379 }
380
381 struct acl {
382     char filename[LINESIZE];    /* Name of acl file */
383     struct hashtbl *acl;        /* Acl entries */
384 };
385
386 static struct acl acl_cache[CACHED_ACLS];
387
388 static int acl_cache_count = 0;
389 static int acl_cache_next = 0;
390
391 /* Returns < 0 if unsuccessful in loading acl */
392 /* Returns index into acl_cache otherwise */
393 /* Note that if acl is already loaded, this is just a lookup */
394 int acl_load(name)
395     char *name;
396 {
397     int i;
398     FILE *f;
399     char buf[MAX_PRINCIPAL_SIZE];
400     char canon[MAX_PRINCIPAL_SIZE];
401
402     /* See if it's there already */
403     for (i = 0; i < acl_cache_count; i++) {
404         if (!strcmp(acl_cache[i].filename, name))
405             goto got_it;
406     }
407
408     /* It isn't, load it in */
409     /* maybe there's still room */
410     if (acl_cache_count < CACHED_ACLS) {
411         i = acl_cache_count++;
412     } else {
413         /* No room, clean one out */
414         i = acl_cache_next;
415         acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS;
416         if (acl_cache[i].acl) {
417             destroy_hash(acl_cache[i].acl);
418             acl_cache[i].acl = (struct hashtbl *) 0;
419         }
420     }
421
422     /* Set up the acl */
423     strcpy(acl_cache[i].filename, name);
424     /* Force reload */
425     acl_cache[i].acl = (struct hashtbl *) 0;
426
427   got_it:
428     /*
429      * See if we need to reload the ACL
430      */
431     if (acl_cache[i].acl == (struct hashtbl *) 0) {
432         /* Gotta reload */
433 #if 0
434         fprintf (stderr, "attempting to load %s\n", name);
435 #endif
436         if ((f = fopen(name, "r")) == NULL) {
437 #if 0
438             perror (name);
439 #endif
440             return -1;
441         }
442         if (acl_cache[i].acl) destroy_hash(acl_cache[i].acl);
443         acl_cache[i].acl = make_hash(ACL_LEN);
444         while(fgets(buf, sizeof(buf), f) != NULL) {
445             nuke_whitespace(buf);
446             acl_canonicalize_principal(buf, canon);
447             add_hash(acl_cache[i].acl, canon);
448         }
449         fclose(f);
450     }
451     return(i);
452 }
453
454 /*
455  * This destroys all cached ACL's so that new ones will be loaded in
456  * the next time they are requested.
457  */
458 void
459 acl_cache_reset()
460 {
461         int     i;
462         
463         /* See if it's there already */
464         for (i = 0; i < acl_cache_count; i++)
465             if (acl_cache[i].acl) {
466                 destroy_hash(acl_cache[i].acl);
467                 acl_cache[i].acl = (struct hashtbl *) 0;
468             }
469         acl_cache_count = 0;
470         acl_cache_next = 0;
471     }
472
473
474 /* Returns nonzero if it can be determined that acl contains principal */
475 /* Principal is not canonicalized, and no wildcarding is done */
476 acl_exact_match(acl, principal)
477     char *acl;
478     char *principal;
479 {
480     int idx;
481
482 #if 0
483     fprintf (stderr, "checking for %s in %s\n", principal, acl);
484 #endif
485     return((idx = acl_load(acl)) >= 0
486            && check_hash(acl_cache[idx].acl, principal));
487 }
488
489 /* Returns nonzero if it can be determined that acl contains principal */
490 /* Recognizes wildcards in acl. */
491 int
492 acl_check(acl, principal)
493     char *acl;
494     char *principal;
495 {
496     char buf[MAX_PRINCIPAL_SIZE];
497     char canon[MAX_PRINCIPAL_SIZE];
498     char *instance, *realm;
499     int p, i, r;
500
501     /* Parse into principal, instance, and realm. */
502     acl_canonicalize_principal(principal, canon);
503     instance = (char *) strchr(canon, INST_SEP);
504     *instance++ = 0;
505     realm = (char *) strchr(instance, REALM_SEP);
506     *realm++ = 0;
507
508     for (p = 0; p <= 1; p++) {
509         for (i = 0; i <= 1; i++) {
510             for (r = 0; r <= 1; r++) {
511                 sprintf(buf, "%s%c%s%c%s", (p) ? canon : "*", INST_SEP,
512                         (i) ? instance : "*", REALM_SEP, (r) ? realm : "*");
513                 if (acl_exact_match(acl, buf))
514                     return 1;
515             }
516         }
517     }
518        
519     return(0);
520 }
521
522 #ifdef notdef
523 /* Adds principal to acl */
524 /* Wildcards are interpreted literally */
525 int
526 acl_add(acl, principal)
527     char *acl;
528     char *principal;
529 {
530     int idx;
531     int i;
532     FILE *new;
533     char canon[MAX_PRINCIPAL_SIZE];
534
535     acl_canonicalize_principal(principal, canon);
536
537     if ((new = acl_lock_file(acl)) == NULL) return(-1);
538     if ((acl_exact_match(acl, canon))
539        || (idx = acl_load(acl)) < 0) {
540         acl_abort(acl, new);
541         return(-1);
542     }
543     /* It isn't there yet, copy the file and put it in */
544     for (i = 0; i < acl_cache[idx].acl->size; i++) {
545         if (acl_cache[idx].acl->tbl[i] != NULL) {
546             if (fputs(acl_cache[idx].acl->tbl[i], new) == NULL
547                || putc('\n', new) != '\n') {
548                 acl_abort(acl, new);
549                 return(-1);
550             }
551         }
552     }
553     fputs(canon, new);
554     putc('\n', new);
555     return(acl_commit(acl, new));
556 }
557
558 /* Removes principal from acl */
559 /* Wildcards are interpreted literally */
560 int
561 acl_delete(acl, principal)
562     char *acl;
563     char *principal;
564 {
565     int idx;
566     int i;
567     FILE *new;
568     char canon[MAX_PRINCIPAL_SIZE];
569
570     acl_canonicalize_principal(principal, canon);
571
572     if ((new = acl_lock_file(acl)) == NULL) return(-1);
573     if ((!acl_exact_match(acl, canon))
574        || (idx = acl_load(acl)) < 0) {
575         acl_abort(acl, new);
576         return(-1);
577     }
578     /* It isn't there yet, copy the file and put it in */
579     for (i = 0; i < acl_cache[idx].acl->size; i++) {
580         if (acl_cache[idx].acl->tbl[i] != NULL
581            && strcmp(acl_cache[idx].acl->tbl[i], canon)) {
582             fputs(acl_cache[idx].acl->tbl[i], new);
583             putc('\n', new);
584         }
585     }
586     return(acl_commit(acl, new));
587 }
588 #endif /* notdef */