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