auth: plug auth realms memory leaks
[openafs.git] / src / auth / realms.c
1 /*
2  * Copyright 2012, Sine Nomine Associates and others.
3  * All Rights Reserved.
4  *
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12 #include <roken.h>
13 #include <opr/queue.h>
14 #include <afs/stds.h>
15 #include <afs/pthread_glock.h>
16 #include <afs/afsutil.h>
17 #include <ctype.h>
18 #include <search.h>
19 #include "cellconfig.h"
20 #include "internal.h"
21
22 #define MAXLINESIZE 2047
23
24 /* Can be set during initialization, overriding the krb.conf file. */
25 static struct opr_queue *lrealms = NULL;
26
27 /**
28  * Realm and exclusion list entries.
29  */
30 struct afsconf_realm_entry {
31     struct opr_queue link;  /**< linked list header */
32     char *value;            /**< local realm or principal */
33 };
34
35 /**
36  * Realm and exclusion lists.
37  */
38 struct afsconf_realms {
39     struct opr_queue list;  /**< list of afsconf_realm_entry */
40     int time_read;          /**< time when read from file */
41     void *tree;             /**< for lookup */
42     int (*compare) (const void *, const void *); /**< compare entries */
43 };
44
45 static int
46 compare_realms(const void *a, const void *b)
47 {
48     return strcasecmp((char *)a, (char *)b);
49 }
50
51 static int
52 compare_principals(const void *a, const void *b)
53 {
54     return strcmp((char *)a, (char *)b);
55 }
56
57 /**
58  * Format the k4-style principal string.
59  *
60  * @param[out] pvname output buffer, must be freed by caller
61  * @param[in]  name   user name, required
62  * @param[in]  inst   user instance, optional
63  * @param[in]  cell   cell name, optional
64  *
65  * @return status
66  *   @retval 0 success
67  *   @retval EINVAL invalid arguments
68  *   @retval E2BIG insufficient output buffer space
69  *
70  * @internal
71  */
72 static int
73 create_name(char **pvname, const char *name,
74             const char *inst, const char *cell)
75 {
76     int code = 0;
77
78     if (!name || !*name) {
79         return EINVAL;
80     }
81     if (cell && *cell) {
82         if (inst && *inst) {
83             code = asprintf(pvname, "%s.%s@%s", name, inst, cell);
84         } else {
85             code = asprintf(pvname, "%s@%s", name, cell);
86         }
87     } else {
88         if (inst && *inst) {
89             code = asprintf(pvname, "%s.%s", name, inst);
90         } else {
91             code = asprintf(pvname, "%s", name);
92         }
93     }
94     return (code < 0 ? ENOMEM : 0);
95 }
96
97 /**
98  * Parse whitespace delimited values
99  *
100  * @param[in]    buffer    input string
101  * @param[out]   result    output string
102  * @param[in]    size      size of result buffer
103  *
104  * @return pointer to the next value
105  *
106  * @internal
107  */
108 static char *
109 parse_str(char *buffer, char *result, int size)
110 {
111     int n = 0;
112
113     if (!buffer)
114         goto cleanup;
115
116     while (*buffer && isspace(*buffer))
117         buffer++;
118     while (*buffer && !isspace(*buffer)) {
119         if (n < size - 1) {
120             *result++ = *buffer++;
121             n++;
122         } else {
123             buffer++;
124         }
125     }
126
127   cleanup:
128     *result = '\0';
129     return buffer;
130 }
131
132 /**
133  * Add a new list element.
134  *
135  * Add the name element if not already present in the list.
136  * The names are case insensitive.
137  *
138  * @param[inout] list  list of name elements
139  * @param[in]    name  name to add
140  *
141  * @return status
142  *   @retval 0 success
143  *   @retval ENOMEM unable to allocate new entry
144  *
145  * @internal
146  */
147 static int
148 add_entry(struct opr_queue *list, const char *name)
149 {
150     struct afsconf_realm_entry *entry;
151
152     entry = malloc(sizeof(struct afsconf_realm_entry));
153     if (!entry) {
154         return ENOMEM;
155     }
156     entry->value = strdup(name);
157     opr_queue_Append(list, (struct opr_queue *)entry);
158     return 0;
159 }
160
161 /**
162  * Free all entries in a list.
163  *
164  * @param[in] list  list of entries
165  *
166  * @return none
167  *
168  * @internal
169  */
170 static void
171 free_realm_entries(struct opr_queue *list)
172 {
173     struct afsconf_realm_entry *entry;
174
175     while (!opr_queue_IsEmpty(list)) {
176         entry = opr_queue_First(list, struct afsconf_realm_entry, link);
177         opr_queue_Remove(&entry->link);
178         if (entry->value) {
179             free(entry->value);
180         }
181         free(entry);
182     }
183 }
184
185 #if HAVE_TDESTROY
186 /**
187  * Placeholder for tdestroy.
188  */
189 static void
190 free_tree_node(void *nodep)
191 {
192     return;                     /* empty */
193 }
194 #endif
195
196 /**
197  * Delete all the entries from the search tree.
198  *
199  * @param[in] list  list of entries
200  *
201  * @return none
202  *
203  * @internal
204  */
205 /*static*/ void
206 destroy_tree(struct afsconf_realms *entries)
207 {
208     if (entries->tree) {
209 #if HAVE_TDESTROY
210         tdestroy(entries->tree, free_tree_node);
211 #else
212         struct opr_queue *cursor;
213         struct afsconf_realm_entry *entry;
214
215         for (opr_queue_Scan(&entries->list, cursor)) {
216             entry = opr_queue_Entry(cursor, struct afsconf_realm_entry, link);
217             tdelete(entry->value, &entries->tree, entries->compare);
218         }
219 #endif
220         entries->tree = NULL;
221     }
222 }
223
224 /**
225  * Build a search tree from the list of entries.
226  *
227  * @param[in] list  list of entries
228  *
229  * @return none
230  *
231  * @internal
232  */
233 static void
234 build_tree(struct afsconf_realms *entries)
235 {
236     struct opr_queue *cursor;
237     struct afsconf_realm_entry *entry;
238
239     for (opr_queue_Scan(&entries->list, cursor)) {
240         entry = opr_queue_Entry(cursor, struct afsconf_realm_entry, link);
241         tsearch(entry->value, &entries->tree, entries->compare);
242     }
243 }
244
245 /**
246  * Read the list of local realms from a config file.
247  *
248  * @param[inout]  dir   config dir object
249  *
250  * @return status
251  *
252  * @internal
253  */
254 static int
255 read_local_realms(struct afsconf_realms *entries, const char *path)
256 {
257     int code = 0;
258     char realm[AFS_REALM_SZ];
259     struct opr_queue temp;
260     char *filename = NULL;
261     struct stat tstat;
262     FILE *cnffile = NULL;
263     char *linebuf = NULL;
264     char *p;
265
266     opr_queue_Init(&temp);
267     code = asprintf(&filename, "%s/%s", path, AFSDIR_KCONF_FILE);
268     if (code < 0) {
269         code = ENOMEM;
270         goto done;
271     }
272     code = stat(filename, &tstat);
273     if (code < 0) {
274         code = (errno == ENOENT ? 0 : errno);   /* this file is optional */
275         goto done;
276     }
277     if (tstat.st_mtime == entries->time_read) {
278         code = 0;
279         goto done;
280     }
281     entries->time_read = tstat.st_mtime;
282     if ((cnffile = fopen(filename, "r")) == NULL) {
283         code = (errno == ENOENT ? 0 : errno);   /* this file is optional */
284         goto done;
285     }
286     linebuf = malloc(sizeof(char) * (MAXLINESIZE + 1));
287     if (!linebuf) {
288         code = ENOMEM;
289         goto done;
290     }
291     if (fgets(linebuf, MAXLINESIZE, cnffile) == NULL) {
292         code = errno;
293         goto done;
294     }
295     linebuf[MAXLINESIZE] = '\0';
296     for (p = linebuf; *p;) {
297         p = parse_str(p, realm, AFS_REALM_SZ);
298         if (*realm) {
299             code = add_entry(&temp, realm);
300             if (code) {
301                 goto done;
302             }
303         }
304     }
305     destroy_tree(entries);
306     opr_queue_Swap(&temp, &entries->list);
307     build_tree(entries);
308
309   done:
310     free_realm_entries(&temp);
311     if (filename) {
312         free(filename);
313     }
314     if (linebuf) {
315         free(linebuf);
316     }
317     if (cnffile) {
318         fclose(cnffile);
319     }
320     return code;
321 }
322
323 /**
324  * Read the list of local exclusions from a config file.
325  *
326  * @param[inout]  dir   config dir object
327  *
328  * @return status
329  *
330  * @internal
331  */
332 static int
333 read_local_exclusions(struct afsconf_realms *entries, const char *path)
334 {
335     int code = 0;
336     char *linebuf = NULL;
337     char *filename = NULL;
338     char name[256];
339     FILE *cnffile = NULL;
340     struct opr_queue temp;
341     struct stat tstat;
342
343     opr_queue_Init(&temp);
344     code = asprintf(&filename, "%s/%s", path, AFSDIR_KRB_EXCL_FILE);
345     if (code < 0) {
346         code = ENOMEM;
347         goto done;
348     }
349     code = stat(filename, &tstat);
350     if (code < 0) {
351         code = (errno == ENOENT ? 0 : errno);   /* this file is optional */
352         goto done;
353     }
354     if (tstat.st_mtime == entries->time_read) {
355         code = 0;
356         goto done;
357     }
358     if ((cnffile = fopen(filename, "r")) == NULL) {
359         code = (errno != ENOENT ? errno : 0);   /* this file is optional */
360         goto done;
361     }
362     linebuf = malloc(sizeof(char) * (MAXLINESIZE + 1));
363     if (!linebuf) {
364         code = ENOMEM;
365         goto done;
366     }
367     for (;;) {
368         if (fgets(linebuf, MAXLINESIZE, cnffile) == NULL) {
369             break;
370         }
371         linebuf[MAXLINESIZE] = '\0';
372         parse_str(linebuf, name, sizeof(name));
373         if (*name) {
374             code = add_entry(&temp, name);
375             if (code) {
376                 goto done;
377             }
378         }
379     }
380     destroy_tree(entries);
381     opr_queue_Swap(&temp, &entries->list);
382     build_tree(entries);
383   done:
384     free_realm_entries(&temp);
385     if (filename) {
386         free(filename);
387     }
388     if (linebuf) {
389         free(linebuf);
390     }
391     if (cnffile) {
392         fclose(cnffile);
393     }
394     return code;
395 }
396
397
398 /**
399  * Free the local realms and exclusions lists.
400  *
401  * @param[in] dir afsconf dir object
402  *
403  * @return none
404  *
405  * @internal
406  */
407 void
408 _afsconf_FreeRealms(struct afsconf_dir *dir)
409 {
410     if (dir) {
411         if (dir->local_realms) {
412             destroy_tree(dir->local_realms);
413             free_realm_entries(&dir->local_realms->list);
414             free(dir->local_realms);
415             dir->local_realms = NULL;
416         }
417         if (dir->exclusions) {
418             destroy_tree(dir->exclusions);
419             free_realm_entries(&dir->exclusions->list);
420             free(dir->exclusions);
421             dir->exclusions = NULL;
422         }
423     }
424 }
425
426 /**
427  * Load the local realms and exclusions lists.
428  *
429  * @param[in] dir afsconf dir object
430  *
431  * @return none
432  *
433  * @internal
434  */
435 int
436 _afsconf_LoadRealms(struct afsconf_dir *dir)
437 {
438     int code = 0;
439     struct afsconf_realms *local_realms = NULL;
440     struct afsconf_realms *exclusions = NULL;
441
442     /* Create and load the list of local realms. */
443     local_realms = calloc(1, sizeof(struct afsconf_realms));
444     if (!local_realms) {
445         code = ENOMEM;
446         goto cleanup;
447     }
448     opr_queue_Init(&local_realms->list);
449     local_realms->compare = compare_realms;
450
451     if (!lrealms) {
452         code = read_local_realms(local_realms, dir->name);
453         if (code) {
454             goto cleanup;
455         }
456     } else {
457         struct opr_queue *cursor;
458         struct afsconf_realm_entry *entry;
459         for (opr_queue_Scan(lrealms, cursor)) {
460             entry = opr_queue_Entry(cursor, struct afsconf_realm_entry, link);
461             code = add_entry(&local_realms->list, entry->value);
462             if (code) {
463                 goto cleanup;
464             }
465         }
466         build_tree(local_realms);
467     }
468
469     /* Create and load the list of excluded principals. */
470     exclusions = calloc(1, sizeof(struct afsconf_realms));
471     if (!exclusions) {
472         code = ENOMEM;
473         goto cleanup;
474     }
475     opr_queue_Init(&exclusions->list);
476     exclusions->compare = compare_principals;
477     code = read_local_exclusions(exclusions, dir->name);
478     if (code) {
479         goto cleanup;
480     }
481
482     dir->local_realms = local_realms;
483     dir->exclusions = exclusions;
484     return 0;
485
486   cleanup:
487     if (local_realms) {
488         destroy_tree(local_realms);
489         free_realm_entries(&local_realms->list);
490         free(local_realms);
491     }
492     if (exclusions) {
493         destroy_tree(dir->exclusions);
494         free_realm_entries(&exclusions->list);
495         free(exclusions);
496     }
497     return code;
498 }
499
500 /**
501  * Set a local realm, instead of retrieving the local realms from the
502  * configuration file krb.conf (if it exists).  Maybe called multiple
503  * times during application initialization to set one or more local
504  * realms.
505  *
506  * @return status
507  *   @retval 0 success
508  *   @retval ENOMEM unable to allocate new entry
509  */
510 int
511 afsconf_SetLocalRealm(const char *realm)
512 {
513     int code = 0;
514
515     LOCK_GLOBAL_MUTEX;
516     if (!lrealms) {
517         lrealms = malloc(sizeof(struct opr_queue));
518         if (!lrealms) {
519             code = ENOMEM;
520             goto done;
521         }
522         opr_queue_Init(lrealms);
523     }
524     code = add_entry(lrealms, realm);
525   done:
526     UNLOCK_GLOBAL_MUTEX;
527     return code;
528 }
529
530 /**
531  * Determine if a principal is local to this cell.
532  *
533  * @param[in]  dir     afsconf dir object
534  * @param[out] plocal  set to 1 if user is local, 0 if foreign
535  * @param[in]  name    user name
536  * @param[in]  inst    user instance
537  * @param[in]  cell    user cell name
538  *
539  * @returns status
540  *   @retval 0 success
541  *   @retval ENOMEM unable to allocate memory
542  *   @retval EINVAL invalid argument
543  */
544 int
545 afsconf_IsLocalRealmMatch(struct afsconf_dir *dir, afs_int32 * plocal,
546                           const char *name, const char *inst,
547                           const char *cell)
548 {
549     int code = 0;
550     char *localcell = NULL;
551     char *tvname = NULL;
552     struct afsconf_realms *local_realms = NULL;
553     struct afsconf_realms *exclusions = NULL;
554
555     if (!name)
556         return EINVAL;
557
558     if (!cell || !*cell) {
559         *plocal = 1;
560         return code;
561     }
562
563     LOCK_GLOBAL_MUTEX;
564     code = _afsconf_GetLocalCell(dir, &localcell, 1);
565     if (code)
566         goto done;
567
568     /* Does the cell match the local cell name? */
569     if (strcasecmp(localcell, cell) == 0) {
570         *plocal = 1;            /* cell matches the local cell name. */
571         goto done;
572     }
573
574     /* Does the cell match one of the local_realms? */
575     local_realms = dir->local_realms;
576     if (!tfind(cell, &local_realms->tree, local_realms->compare)) {
577         *plocal = 0;            /* Cell name not found in local realms. */
578         goto done;
579     }
580
581     /* Local realm matches, make sure the principal is not in the
582      * exclusion list, if one. */
583     exclusions = dir->exclusions;
584     if (!exclusions->tree) {
585         *plocal = 1;            /* Matches one of the local realms; no exclusions */
586         goto done;
587     }
588
589     /* Create a full principal name for the exclusion check. */
590     code = create_name(&tvname, name, inst, cell);
591     if (!code) {
592         if (tfind(tvname, &exclusions->tree, exclusions->compare)) {
593             *plocal = 0;        /* name found in the exclusion list */
594         } else {
595             *plocal = 1;        /* not in the exclusion list */
596         }
597     }
598     if (tvname)
599         free(tvname);
600
601   done:
602     UNLOCK_GLOBAL_MUTEX;
603
604     return code;
605 }