Use calloc, rather than malloc/memset
[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             dir->local_realms = NULL;
415         }
416         if (dir->exclusions) {
417             destroy_tree(dir->exclusions);
418             free_realm_entries(&dir->exclusions->list);
419             dir->exclusions = NULL;
420         }
421     }
422 }
423
424 /**
425  * Load the local realms and exclusions lists.
426  *
427  * @param[in] dir afsconf dir object
428  *
429  * @return none
430  *
431  * @internal
432  */
433 int
434 _afsconf_LoadRealms(struct afsconf_dir *dir)
435 {
436     int code = 0;
437     struct afsconf_realms *local_realms = NULL;
438     struct afsconf_realms *exclusions = NULL;
439
440     /* Create and load the list of local realms. */
441     local_realms = calloc(1, sizeof(struct afsconf_realms));
442     if (!local_realms) {
443         code = ENOMEM;
444         goto cleanup;
445     }
446     opr_queue_Init(&local_realms->list);
447     local_realms->compare = compare_realms;
448
449     if (!lrealms) {
450         code = read_local_realms(local_realms, dir->name);
451         if (code) {
452             goto cleanup;
453         }
454     } else {
455         struct opr_queue *cursor;
456         struct afsconf_realm_entry *entry;
457         for (opr_queue_Scan(lrealms, cursor)) {
458             entry = opr_queue_Entry(cursor, struct afsconf_realm_entry, link);
459             code = add_entry(&local_realms->list, entry->value);
460             if (code) {
461                 goto cleanup;
462             }
463         }
464         build_tree(local_realms);
465     }
466
467     /* Create and load the list of excluded principals. */
468     exclusions = calloc(1, sizeof(struct afsconf_realms));
469     if (!exclusions) {
470         code = ENOMEM;
471         goto cleanup;
472     }
473     opr_queue_Init(&exclusions->list);
474     exclusions->compare = compare_principals;
475     code = read_local_exclusions(exclusions, dir->name);
476     if (code) {
477         goto cleanup;
478     }
479
480     dir->local_realms = local_realms;
481     dir->exclusions = exclusions;
482     return 0;
483
484   cleanup:
485     if (local_realms) {
486         destroy_tree(local_realms);
487         free_realm_entries(&local_realms->list);
488     }
489     if (exclusions) {
490         destroy_tree(dir->exclusions);
491         free_realm_entries(&exclusions->list);
492     }
493     return code;
494 }
495
496 /**
497  * Set a local realm, instead of retrieving the local realms from the
498  * configuration file krb.conf (if it exists).  Maybe called multiple
499  * times during application initialization to set one or more local
500  * realms.
501  *
502  * @return status
503  *   @retval 0 success
504  *   @retval ENOMEM unable to allocate new entry
505  */
506 int
507 afsconf_SetLocalRealm(const char *realm)
508 {
509     int code = 0;
510
511     LOCK_GLOBAL_MUTEX;
512     if (!lrealms) {
513         lrealms = malloc(sizeof(struct opr_queue));
514         if (!lrealms) {
515             code = ENOMEM;
516             goto done;
517         }
518         opr_queue_Init(lrealms);
519     }
520     code = add_entry(lrealms, realm);
521   done:
522     UNLOCK_GLOBAL_MUTEX;
523     return code;
524 }
525
526 /**
527  * Determine if a principal is local to this cell.
528  *
529  * @param[in]  dir     afsconf dir object
530  * @param[out] plocal  set to 1 if user is local, 0 if foreign
531  * @param[in]  name    user name
532  * @param[in]  inst    user instance
533  * @param[in]  cell    user cell name
534  *
535  * @returns status
536  *   @retval 0 success
537  *   @retval ENOMEM unable to allocate memory
538  *   @retval EINVAL invalid argument
539  */
540 int
541 afsconf_IsLocalRealmMatch(struct afsconf_dir *dir, afs_int32 * plocal,
542                           const char *name, const char *inst,
543                           const char *cell)
544 {
545     int code = 0;
546     char *localcell = NULL;
547     char *tvname = NULL;
548     struct afsconf_realms *local_realms = NULL;
549     struct afsconf_realms *exclusions = NULL;
550
551     if (!name)
552         return EINVAL;
553
554     if (!cell || !*cell) {
555         *plocal = 1;
556         return code;
557     }
558
559     LOCK_GLOBAL_MUTEX;
560     code = _afsconf_GetLocalCell(dir, &localcell, 1);
561     if (code)
562         goto done;
563
564     /* Does the cell match the local cell name? */
565     if (strcasecmp(localcell, cell) == 0) {
566         *plocal = 1;            /* cell matches the local cell name. */
567         goto done;
568     }
569
570     /* Does the cell match one of the local_realms? */
571     local_realms = dir->local_realms;
572     if (!tfind(cell, &local_realms->tree, local_realms->compare)) {
573         *plocal = 0;            /* Cell name not found in local realms. */
574         goto done;
575     }
576
577     /* Local realm matches, make sure the principal is not in the
578      * exclusion list, if one. */
579     exclusions = dir->exclusions;
580     if (!exclusions->tree) {
581         *plocal = 1;            /* Matches one of the local realms; no exclusions */
582         goto done;
583     }
584
585     /* Create a full principal name for the exclusion check. */
586     code = create_name(&tvname, name, inst, cell);
587     if (!code) {
588         if (tfind(tvname, &exclusions->tree, exclusions->compare)) {
589             *plocal = 0;        /* name found in the exclusion list */
590         } else {
591             *plocal = 1;        /* not in the exclusion list */
592         }
593     }
594     if (tvname)
595         free(tvname);
596
597   done:
598     UNLOCK_GLOBAL_MUTEX;
599
600     return code;
601 }