Initial import from FreeBSD RELENG_4:
[games.git] / crypto / kerberosIV / lib / acl / acl_files.c
1 /* 
2   Copyright (C) 1989 by the Massachusetts Institute of Technology
3
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.
8
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
18 or implied warranty.
19
20   */
21
22 #include "config.h"
23 #include "protos.h"
24
25 RCSID("$Id: acl_files.c,v 1.14 1999/09/16 20:41:43 assar Exp $");
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
34 #ifdef HAVE_SYS_TYPES_H
35 #include <sys/types.h>
36 #endif
37 #include <time.h>
38 #ifdef HAVE_FCNTL_H
39 #include <fcntl.h>
40 #endif
41 #ifdef HAVE_SYS_FILE_H
42 #include <sys/file.h>
43 #endif
44 #ifdef HAVE_SYS_STAT_H
45 #include <sys/stat.h>
46 #endif
47
48 #include <errno.h>
49 #include <ctype.h>
50
51 #include <roken.h>
52
53 #include <krb.h>
54 #include <acl.h>
55
56 /*** Routines for manipulating access control list files ***/
57
58 /* "aname.inst@realm" */
59 #define MAX_PRINCIPAL_SIZE  (ANAME_SZ + INST_SZ + REALM_SZ + 3)
60 #define INST_SEP '.'
61 #define REALM_SEP '@'
62
63 #define LINESIZE 2048           /* Maximum line length in an acl file */
64
65 #define NEW_FILE "%s.~NEWACL~"  /* Format for name of altered acl file */
66 #define WAIT_TIME 300           /* Maximum time allowed write acl file */
67
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 */
71
72 #define COR(a,b) ((a!=NULL)?(a):(b))
73
74 /*
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
80  *
81  */
82
83 void
84 acl_canonicalize_principal(char *principal, char *canon)
85 {
86     krb_principal princ;
87     int ret;
88     ret = krb_parse_name(principal, &princ);
89     if(ret) { /* ? */
90         *canon = '\0';
91         return;
92     }
93     if(princ.realm[0] == '\0')
94         krb_get_lrealm(princ.realm, 1);
95     krb_unparse_name_r(&princ, canon);
96 }
97             
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 */
102 static
103 FILE *acl_lock_file(char *acl_file)
104 {
105     struct stat s;
106     char new[LINESIZE];
107     int nfd;
108     FILE *nf;
109     int mode;
110
111     if(stat(acl_file, &s) < 0) return(NULL);
112     mode = s.st_mode;
113     snprintf(new, sizeof(new), NEW_FILE, acl_file);
114     for(;;) {
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 */
122                     unlink(new);
123                     continue;
124                 } else {
125                     /* Wait and try again */
126                     sleep(1);
127                     continue;
128                 }
129             } else {
130                 /* Some other error, we lose */
131                 return(NULL);
132             }
133         }
134
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) {
138             /* Oops, clean up */
139             unlink(new);
140         }
141         return(nf);
142     }
143 }
144
145 /* Abort changes to acl_file written onto FILE *f */
146 /* Returns 0 if successful, < 0 otherwise */
147 /* Closes f */
148 static int
149 acl_abort(char *acl_file, FILE *f)
150 {
151     char new[LINESIZE];
152     int ret;
153     struct stat s;
154
155     /* make sure we aren't nuking someone else's file */
156     if(fstat(fileno(f), &s) < 0
157        || s.st_nlink == 0) {
158            fclose(f);
159            return(-1);
160        } else {
161            snprintf(new, sizeof(new), NEW_FILE, acl_file);
162            ret = unlink(new);
163            fclose(f);
164            return(ret);
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 */
173 static int
174 acl_commit(char *acl_file, FILE *f)
175 {
176     char new[LINESIZE];
177     int ret;
178     struct stat s;
179
180     snprintf(new, sizeof(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 }
192
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 */
197 int
198 acl_initialize(char *acl_file, int perm)
199 {
200     FILE *new;
201     int fd;
202
203     /* Check if the file exists already */
204     if((new = acl_lock_file(acl_file)) != NULL) {
205         return(acl_commit(acl_file, new));
206     } else {
207         /* File must be readable and writable by owner */
208         if((fd = open(acl_file, O_CREAT|O_EXCL, perm|0600)) < 0) {
209             return(-1);
210         } else {
211             close(fd);
212             return(0);
213         }
214     }
215 }
216
217 /* Eliminate all whitespace character in buf */
218 /* Modifies its argument */
219 static void
220 nuke_whitespace(char *buf)
221 {
222     unsigned char *pin, *pout;
223
224     for(pin = pout = (unsigned char *)buf; *pin != '\0'; pin++)
225         if(!isspace(*pin))
226             *pout++ = *pin;
227     *pout = '\0';               /* Terminate the string */
228 }
229
230 /* Hash table stuff */
231
232 struct hashtbl {
233     int size;                   /* Max number of entries */
234     int entries;                /* Actual number of entries */
235     char **tbl;                 /* Pointer to start of table */
236 };
237
238 /* Make an empty hash table of size s */
239 static struct hashtbl *
240 make_hash(int size)
241 {
242     struct hashtbl *h;
243
244     if(size < 1) size = 1;
245     h = (struct hashtbl *) malloc(sizeof(struct hashtbl));
246     if (h == NULL)
247         return NULL;
248     h->size = size;
249     h->entries = 0;
250     h->tbl = (char **) calloc(size, sizeof(char *));
251     if (h->tbl == NULL) {
252         free (h);
253         return NULL;
254     }
255     return(h);
256 }
257
258 /* Destroy a hash table */
259 static void
260 destroy_hash(struct hashtbl *h)
261 {
262     int i;
263
264     for(i = 0; i < h->size; i++) {
265         if(h->tbl[i] != NULL) free(h->tbl[i]);
266     }
267     free(h->tbl);
268     free(h);
269 }
270
271 /* Compute hash value for a string */
272 static unsigned int
273 hashval(char *s)
274 {
275     unsigned hv;
276
277     for(hv = 0; *s != '\0'; s++) {
278         hv ^= ((hv << 3) ^ *s);
279     }
280     return(hv);
281 }
282
283 /* Add an element to a hash table */
284 static void
285 add_hash(struct hashtbl *h, char *el)
286 {
287     unsigned hv;
288     char *s;
289     char **old;
290     int i;
291
292     /* Make space if it isn't there already */
293     if(h->entries + 1 > (h->size >> 1)) {
294         old = h->tbl;
295         h->tbl = (char **) calloc(h->size << 1, sizeof(char *));
296         for(i = 0; i < h->size; i++) {
297             if(old[i] != NULL) {
298                 hv = hashval(old[i]) % (h->size << 1);
299                 while(h->tbl[hv] != NULL) hv = (hv+1) % (h->size << 1);
300                 h->tbl[hv] = old[i];
301             }
302         }
303         h->size = h->size << 1;
304         free(old);
305     }
306
307     hv = hashval(el) % h->size;
308     while(h->tbl[hv] != NULL && strcmp(h->tbl[hv], el)) hv = (hv+1) % h->size;
309     s = strdup(el);
310     if (s != NULL) {
311         h->tbl[hv] = s;
312         h->entries++;
313     }
314 }
315
316 /* Returns nonzero if el is in h */
317 static int
318 check_hash(struct hashtbl *h, char *el)
319 {
320     unsigned hv;
321
322     for(hv = hashval(el) % h->size;
323         h->tbl[hv] != NULL;
324         hv = (hv + 1) % h->size) {
325             if(!strcmp(h->tbl[hv], el)) return(1);
326         }
327     return(0);
328 }
329
330 struct acl {
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 */
335 };
336
337 static struct acl acl_cache[CACHED_ACLS];
338
339 static int acl_cache_count = 0;
340 static int acl_cache_next = 0;
341
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 */
345 static int
346 acl_load(char *name)
347 {
348     int i;
349     FILE *f;
350     struct stat s;
351     char buf[MAX_PRINCIPAL_SIZE];
352     char canon[MAX_PRINCIPAL_SIZE];
353
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;
358     }
359
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++;
364     } else {
365         /* No room, clean one out */
366         i = acl_cache_next;
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;
372         }
373     }
374
375     /* Set up the acl */
376     strlcpy(acl_cache[i].filename, name, LINESIZE);
377     if((acl_cache[i].fd = open(name, O_RDONLY, 0)) < 0) return(-1);
378     /* Force reload */
379     acl_cache[i].acl = (struct hashtbl *) 0;
380
381  got_it:
382     /*
383      * See if the stat matches
384      *
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.
388      */
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) {
394            /* Gotta reload */
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);
404            }
405            fclose(f);
406            acl_cache[i].status = s;
407        }
408     return(i);
409 }
410
411 /* Returns nonzero if it can be determined that acl contains principal */
412 /* Principal is not canonicalized, and no wildcarding is done */
413 int
414 acl_exact_match(char *acl, char *principal)
415 {
416     int idx;
417
418     return((idx = acl_load(acl)) >= 0
419            && check_hash(acl_cache[idx].acl, principal));
420 }
421
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 *.*@* */
425 int
426 acl_check(char *acl, char *principal)
427 {
428     char buf[MAX_PRINCIPAL_SIZE];
429     char canon[MAX_PRINCIPAL_SIZE];
430     char *realm;
431
432     acl_canonicalize_principal(principal, canon);
433
434     /* Is it there? */
435     if(acl_exact_match(acl, canon)) return(1);
436
437     /* Try the wildcards */
438     realm = strchr(canon, REALM_SEP);
439     *strchr(canon, INST_SEP) = '\0';    /* Chuck the instance */
440
441     snprintf(buf, sizeof(buf), "%s.*%s", canon, realm);
442     if(acl_exact_match(acl, buf)) return(1);
443
444     snprintf(buf, sizeof(buf), "*.*%s", realm);
445     if(acl_exact_match(acl, buf) || acl_exact_match(acl, "*.*@*")) return(1);
446        
447     return(0);
448 }
449
450 /* Adds principal to acl */
451 /* Wildcards are interpreted literally */
452 int
453 acl_add(char *acl, char *principal)
454 {
455     int idx;
456     int i;
457     FILE *new;
458     char canon[MAX_PRINCIPAL_SIZE];
459
460     acl_canonicalize_principal(principal, canon);
461
462     if((new = acl_lock_file(acl)) == NULL) return(-1);
463     if((acl_exact_match(acl, canon))
464        || (idx = acl_load(acl)) < 0) {
465            acl_abort(acl, new);
466            return(-1);
467        }
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') {
473                    acl_abort(acl, new);
474                    return(-1);
475                }
476         }
477     }
478     fputs(canon, new);
479     putc('\n', new);
480     return(acl_commit(acl, new));
481 }
482
483 /* Removes principal from acl */
484 /* Wildcards are interpreted literally */
485 int
486 acl_delete(char *acl, char *principal)
487 {
488     int idx;
489     int i;
490     FILE *new;
491     char canon[MAX_PRINCIPAL_SIZE];
492
493     acl_canonicalize_principal(principal, canon);
494
495     if((new = acl_lock_file(acl)) == NULL) return(-1);
496     if((!acl_exact_match(acl, canon))
497        || (idx = acl_load(acl)) < 0) {
498            acl_abort(acl, new);
499            return(-1);
500        }
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);
506                putc('\n', new);
507         }
508     }
509     return(acl_commit(acl, new));
510 }