Commit | Line | Data |
---|---|---|
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. */ | |
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 | } |