2 Copyright (C) 1989 by the Massachusetts Institute of Technology
4 Export of this software from the United States of America is assumed
5 to require a specific license from the United States Government.
6 It is the responsibility of any person or organization contemplating
7 export to obtain such a license before exporting.
9 WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
10 distribute this software and its documentation for any purpose and
11 without fee is hereby granted, provided that the above copyright
12 notice appear in all copies and that both that copyright notice and
13 this permission notice appear in supporting documentation, and that
14 the name of M.I.T. not be used in advertising or publicity pertaining
15 to distribution of the software without specific, written prior
16 permission. M.I.T. makes no representations about the suitability of
17 this software for any purpose. It is provided "as is" without express
25 RCSID("$Id: acl_files.c,v 1.14 1999/09/16 20:41:43 assar Exp $");
34 #ifdef HAVE_SYS_TYPES_H
35 #include <sys/types.h>
41 #ifdef HAVE_SYS_FILE_H
44 #ifdef HAVE_SYS_STAT_H
56 /*** Routines for manipulating access control list files ***/
58 /* "aname.inst@realm" */
59 #define MAX_PRINCIPAL_SIZE (ANAME_SZ + INST_SZ + REALM_SZ + 3)
63 #define LINESIZE 2048 /* Maximum line length in an acl file */
65 #define NEW_FILE "%s.~NEWACL~" /* Format for name of altered acl file */
66 #define WAIT_TIME 300 /* Maximum time allowed write acl file */
68 #define CACHED_ACLS 8 /* How many acls to cache */
69 /* Each acl costs 1 open file descriptor */
70 #define ACL_LEN 16 /* Twice a reasonable acl length */
72 #define COR(a,b) ((a!=NULL)?(a):(b))
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
79 * hold MAX_PRINCIPAL_SIZE characters
84 acl_canonicalize_principal(char *principal, char *canon)
88 ret = krb_parse_name(principal, &princ);
93 if(princ.realm[0] == '\0')
94 krb_get_lrealm(princ.realm, 1);
95 krb_unparse_name_r(&princ, canon);
98 /* Get a lock to modify acl_file */
99 /* Return new FILE pointer */
100 /* or NULL if file cannot be modified */
101 /* REQUIRES WRITE PERMISSION TO CONTAINING DIRECTORY */
103 FILE *acl_lock_file(char *acl_file)
111 if(stat(acl_file, &s) < 0) return(NULL);
113 snprintf(new, sizeof(new), NEW_FILE, acl_file);
115 /* Open the new file */
116 if((nfd = open(new, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0) {
117 if(errno == EEXIST) {
118 /* Maybe somebody got here already, maybe it's just old */
119 if(stat(new, &s) < 0) return(NULL);
120 if(time(0) - s.st_ctime > WAIT_TIME) {
121 /* File is stale, kill it */
125 /* Wait and try again */
130 /* Some other error, we lose */
135 /* If we got to here, the lock file is ours and ok */
136 /* Reopen it under stdio */
137 if((nf = fdopen(nfd, "w")) == NULL) {
145 /* Abort changes to acl_file written onto FILE *f */
146 /* Returns 0 if successful, < 0 otherwise */
149 acl_abort(char *acl_file, FILE *f)
155 /* make sure we aren't nuking someone else's file */
156 if(fstat(fileno(f), &s) < 0
157 || s.st_nlink == 0) {
161 snprintf(new, sizeof(new), NEW_FILE, acl_file);
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 */
174 acl_commit(char *acl_file, FILE *f)
180 snprintf(new, sizeof(new), NEW_FILE, acl_file);
182 || fstat(fileno(f), &s) < 0
183 || s.st_nlink == 0) {
184 acl_abort(acl_file, f);
188 ret = rename(new, acl_file);
193 /* Initialize an acl_file */
194 /* Creates the file with permissions perm if it does not exist */
195 /* Erases it if it does */
196 /* Returns return value of acl_commit */
198 acl_initialize(char *acl_file, int perm)
203 /* Check if the file exists already */
204 if((new = acl_lock_file(acl_file)) != NULL) {
205 return(acl_commit(acl_file, new));
207 /* File must be readable and writable by owner */
208 if((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
217 /* Eliminate all whitespace character in buf */
218 /* Modifies its argument */
220 nuke_whitespace(char *buf)
222 unsigned char *pin, *pout;
224 for(pin = pout = (unsigned char *)buf; *pin != '\0'; pin++)
227 *pout = '\0'; /* Terminate the string */
230 /* Hash table stuff */
233 int size; /* Max number of entries */
234 int entries; /* Actual number of entries */
235 char **tbl; /* Pointer to start of table */
238 /* Make an empty hash table of size s */
239 static struct hashtbl *
244 if(size < 1) size = 1;
245 h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
250 h->tbl = (char **) calloc(size, sizeof(char *));
251 if (h->tbl == NULL) {
258 /* Destroy a hash table */
260 destroy_hash(struct hashtbl *h)
264 for(i = 0; i < h->size; i++) {
265 if(h->tbl[i] != NULL) free(h->tbl[i]);
271 /* Compute hash value for a string */
277 for(hv = 0; *s != '\0'; s++) {
278 hv ^= ((hv << 3) ^ *s);
283 /* Add an element to a hash table */
285 add_hash(struct hashtbl *h, char *el)
292 /* Make space if it isn't there already */
293 if(h->entries + 1 > (h->size >> 1)) {
295 h->tbl = (char **) calloc(h->size << 1, sizeof(char *));
296 for(i = 0; i < h->size; i++) {
298 hv = hashval(old[i]) % (h->size << 1);
299 while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1);
303 h->size = h->size << 1;
307 hv = hashval(el) % h->size;
308 while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size;
316 /* Returns nonzero if el is in h */
318 check_hash(struct hashtbl *h, char *el)
322 for(hv = hashval(el) % h->size;
324 hv = (hv + 1) % h->size) {
325 if(!strcmp(h->tbl[hv], el)) return(1);
331 char filename[LINESIZE]; /* Name of acl file */
332 int fd; /* File descriptor for acl file */
333 struct stat status; /* File status at last read */
334 struct hashtbl *acl; /* Acl entries */
337 static struct acl acl_cache[CACHED_ACLS];
339 static int acl_cache_count = 0;
340 static int acl_cache_next = 0;
342 /* Returns < 0 if unsuccessful in loading acl */
343 /* Returns index into acl_cache otherwise */
344 /* Note that if acl is already loaded, this is just a lookup */
351 char buf[MAX_PRINCIPAL_SIZE];
352 char canon[MAX_PRINCIPAL_SIZE];
354 /* See if it's there already */
355 for(i = 0; i < acl_cache_count; i++) {
356 if(!strcmp(acl_cache[i].filename, name)
357 && acl_cache[i].fd >= 0) goto got_it;
360 /* It isn't, load it in */
361 /* maybe there's still room */
362 if(acl_cache_count < CACHED_ACLS) {
363 i = acl_cache_count++;
365 /* No room, clean one out */
367 acl_cache_next = (acl_cache_next + 1) % CACHED_ACLS;
368 close(acl_cache[i].fd);
369 if(acl_cache[i].acl) {
370 destroy_hash(acl_cache[i].acl);
371 acl_cache[i].acl = (struct hashtbl *) 0;
376 strlcpy(acl_cache[i].filename, name, LINESIZE);
377 if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
379 acl_cache[i].acl = (struct hashtbl *) 0;
383 * See if the stat matches
385 * Use stat(), not fstat(), as the file may have been re-created by
386 * acl_add or acl_delete. If this happens, the old inode will have
387 * no changes in the mod-time and the following test will fail.
389 if(stat(acl_cache[i].filename, &s) < 0) return(-1);
390 if(acl_cache[i].acl == (struct hashtbl *) 0
391 || s.st_nlink != acl_cache[i].status.st_nlink
392 || s.st_mtime != acl_cache[i].status.st_mtime
393 || s.st_ctime != acl_cache[i].status.st_ctime) {
395 if(acl_cache[i].fd >= 0) close(acl_cache[i].fd);
396 if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
397 if((f = fdopen(acl_cache[i].fd, "r")) == NULL) return(-1);
398 if(acl_cache[i].acl) destroy_hash(acl_cache[i].acl);
399 acl_cache[i].acl = make_hash(ACL_LEN);
400 while(fgets(buf, sizeof(buf), f) != NULL) {
401 nuke_whitespace(buf);
402 acl_canonicalize_principal(buf, canon);
403 add_hash(acl_cache[i].acl, canon);
406 acl_cache[i].status = s;
411 /* Returns nonzero if it can be determined that acl contains principal */
412 /* Principal is not canonicalized, and no wildcarding is done */
414 acl_exact_match(char *acl, char *principal)
418 return((idx = acl_load(acl)) >= 0
419 && check_hash(acl_cache[idx].acl, principal));
422 /* Returns nonzero if it can be determined that acl contains principal */
423 /* Recognizes wildcards in acl of the form
424 name.*@realm, *.*@realm, and *.*@* */
426 acl_check(char *acl, char *principal)
428 char buf[MAX_PRINCIPAL_SIZE];
429 char canon[MAX_PRINCIPAL_SIZE];
432 acl_canonicalize_principal(principal, canon);
435 if(acl_exact_match(acl, canon)) return(1);
437 /* Try the wildcards */
438 realm = strchr(canon, REALM_SEP);
439 *strchr(canon, INST_SEP) = '\0'; /* Chuck the instance */
441 snprintf(buf, sizeof(buf), "%s.*%s", canon, realm);
442 if(acl_exact_match(acl, buf)) return(1);
444 snprintf(buf, sizeof(buf), "*.*%s", realm);
445 if(acl_exact_match(acl, buf) || acl_exact_match(acl, "*.*@*")) return(1);
450 /* Adds principal to acl */
451 /* Wildcards are interpreted literally */
453 acl_add(char *acl, char *principal)
458 char canon[MAX_PRINCIPAL_SIZE];
460 acl_canonicalize_principal(principal, canon);
462 if((new = acl_lock_file(acl)) == NULL) return(-1);
463 if((acl_exact_match(acl, canon))
464 || (idx = acl_load(acl)) < 0) {
468 /* It isn't there yet, copy the file and put it in */
469 for(i = 0; i < acl_cache[idx].acl->size; i++) {
470 if(acl_cache[idx].acl->tbl[i] != NULL) {
471 if(fputs(acl_cache[idx].acl->tbl[i], new) == EOF
472 || putc('\n', new) != '\n') {
480 return(acl_commit(acl, new));
483 /* Removes principal from acl */
484 /* Wildcards are interpreted literally */
486 acl_delete(char *acl, char *principal)
491 char canon[MAX_PRINCIPAL_SIZE];
493 acl_canonicalize_principal(principal, canon);
495 if((new = acl_lock_file(acl)) == NULL) return(-1);
496 if((!acl_exact_match(acl, canon))
497 || (idx = acl_load(acl)) < 0) {
501 /* It isn't there yet, copy the file and put it in */
502 for(i = 0; i < acl_cache[idx].acl->size; i++) {
503 if(acl_cache[idx].acl->tbl[i] != NULL
504 && strcmp(acl_cache[idx].acl->tbl[i], canon)) {
505 fputs(acl_cache[idx].acl->tbl[i], new);
509 return(acl_commit(acl, new));