Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / auth / realms.c
CommitLineData
805e021f
CE
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. */
25static struct opr_queue *lrealms = NULL;
26
27/**
28 * Realm and exclusion list entries.
29 */
30struct 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 */
38struct 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
45static int
46compare_realms(const void *a, const void *b)
47{
48 return strcasecmp((char *)a, (char *)b);
49}
50
51static int
52compare_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 */
72static int
73create_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 */
108static char *
109parse_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 */
147static int
148add_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 */
170static void
171free_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 */
189static void
190free_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
206destroy_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 */
233static void
234build_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 */
254static int
255read_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 */
332static int
333read_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 */
407void
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 */
433int
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 */
506int
507afsconf_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 */
540int
541afsconf_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}