1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* lib/krb5/ccache/t_cccol.py - Test ccache collection via API */
3 /*
4 * Copyright (C) 2013 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include <krb5.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <assert.h>
38
39 static krb5_context ctx;
40
41 /* Check that code is 0. Display an error message first if it is not. */
42 static void
check(krb5_error_code code)43 check(krb5_error_code code)
44 {
45 const char *errmsg;
46
47 if (code != 0) {
48 errmsg = krb5_get_error_message(ctx, code);
49 fprintf(stderr, "%s\n", errmsg);
50 krb5_free_error_message(ctx, errmsg);
51 }
52 assert(code == 0);
53 }
54
55 /* Construct a list of the names of each credential cache in the collection. */
56 static void
get_collection_names(char *** list_out,size_t * count_out)57 get_collection_names(char ***list_out, size_t *count_out)
58 {
59 krb5_cccol_cursor cursor;
60 krb5_ccache cache;
61 char **list = NULL;
62 size_t count = 0;
63 char *name;
64
65 check(krb5_cccol_cursor_new(ctx, &cursor));
66 while (1) {
67 check(krb5_cccol_cursor_next(ctx, cursor, &cache));
68 if (cache == NULL)
69 break;
70 check(krb5_cc_get_full_name(ctx, cache, &name));
71 krb5_cc_close(ctx, cache);
72 list = realloc(list, (count + 1) * sizeof(*list));
73 assert(list != NULL);
74 list[count++] = name;
75 }
76 krb5_cccol_cursor_free(ctx, &cursor);
77 *list_out = list;
78 *count_out = count;
79 }
80
81 /* Return true if list contains name. */
82 static krb5_boolean
in_list(char ** list,size_t count,const char * name)83 in_list(char **list, size_t count, const char *name)
84 {
85 size_t i;
86
87 for (i = 0; i < count; i++) {
88 if (strcmp(list[i], name) == 0)
89 return TRUE;
90 }
91 return FALSE;
92 }
93
94 /* Release the memory for a list of credential cache names. */
95 static void
free_list(char ** list,size_t count)96 free_list(char **list, size_t count)
97 {
98 size_t i;
99
100 for (i = 0; i < count; i++)
101 krb5_free_string(ctx, list[i]);
102 free(list);
103 }
104
105 /*
106 * Check that the cache names within the current collection begin with first
107 * (unless first is NULL), that the other elements match the remaining
108 * arguments in some order. others must be the number of additional cache
109 * names.
110 */
111 static void
check_collection(const char * first,size_t others,...)112 check_collection(const char *first, size_t others, ...)
113 {
114 va_list ap;
115 char **list;
116 size_t count, i;
117 const char *name;
118
119 get_collection_names(&list, &count);
120 if (first != NULL) {
121 assert(strcmp(first, list[0]) == 0);
122 assert(count == others + 1);
123 } else {
124 assert(count == others);
125 }
126 va_start(ap, others);
127 for (i = 0; i < others; i++) {
128 name = va_arg(ap, const char *);
129 assert(in_list(list, count, name));
130 }
131 va_end(ap);
132 free_list(list, count);
133 }
134
135 /* Check that the name of cache matches expected_name. */
136 static void
check_name(krb5_ccache cache,const char * expected_name)137 check_name(krb5_ccache cache, const char *expected_name)
138 {
139 char *name;
140
141 check(krb5_cc_get_full_name(ctx, cache, &name));
142 assert(strcmp(name, expected_name) == 0);
143 krb5_free_string(ctx, name);
144 }
145
146 /* Check that when collection_name is resolved, the resulting cache's name
147 * matches expected_name. */
148 static void
check_primary_name(const char * collection_name,const char * expected_name)149 check_primary_name(const char *collection_name, const char *expected_name)
150 {
151 krb5_ccache cache;
152
153 check(krb5_cc_resolve(ctx, collection_name, &cache));
154 check_name(cache, expected_name);
155 krb5_cc_close(ctx, cache);
156 }
157
158 /* Check that when name is resolved, the resulting cache's principal matches
159 * expected_princ, or has no principal if expected_princ is NULL. */
160 static void
check_princ(const char * name,krb5_principal expected_princ)161 check_princ(const char *name, krb5_principal expected_princ)
162 {
163 krb5_ccache cache;
164 krb5_principal princ;
165
166 check(krb5_cc_resolve(ctx, name, &cache));
167 if (expected_princ != NULL) {
168 check(krb5_cc_get_principal(ctx, cache, &princ));
169 assert(krb5_principal_compare(ctx, princ, expected_princ));
170 krb5_free_principal(ctx, princ);
171 } else {
172 assert(krb5_cc_get_principal(ctx, cache, &princ) != 0);
173 }
174 krb5_cc_close(ctx, cache);
175 }
176
177 /* Check that krb5_cc_cache_match on princ returns a cache whose name matches
178 * expected_name, or that the match fails if expected_name is NULL. */
179 static void
check_match(krb5_principal princ,const char * expected_name)180 check_match(krb5_principal princ, const char *expected_name)
181 {
182 krb5_ccache cache;
183
184 if (expected_name != NULL) {
185 check(krb5_cc_cache_match(ctx, princ, &cache));
186 check_name(cache, expected_name);
187 krb5_cc_close(ctx, cache);
188 } else {
189 assert(krb5_cc_cache_match(ctx, princ, &cache) != 0);
190 }
191 }
192
193 int
main(int argc,char ** argv)194 main(int argc, char **argv)
195 {
196 krb5_ccache ccinitial, ccu1, ccu2;
197 krb5_principal princ1, princ2, princ3;
198 const char *collection_name, *typename;
199 char *initial_primary_name, *unique1_name, *unique2_name;
200
201 /*
202 * Get the collection name from the command line. This is a ccache name
203 * with collection semantics, like DIR:/path/to/directory. This test
204 * program assumes that the collection is empty to start with.
205 */
206 assert(argc == 2);
207 collection_name = argv[1];
208
209 /*
210 * Set the default ccache for the context to be the collection name, so the
211 * library can find the collection.
212 */
213 check(krb5_init_context(&ctx));
214 check(krb5_cc_set_default_name(ctx, collection_name));
215
216 /*
217 * Resolve the collection name. Since the collection is empty, this should
218 * generate a subsidiary name of an uninitialized cache. Getting the name
219 * of the resulting cache should give us the subsidiary name, not the
220 * collection name. This resulting subsidiary name should be consistent if
221 * we resolve the collection name again, and the collection should still be
222 * empty since we haven't initialized the cache.
223 */
224 check(krb5_cc_resolve(ctx, collection_name, &ccinitial));
225 check(krb5_cc_get_full_name(ctx, ccinitial, &initial_primary_name));
226 assert(strcmp(initial_primary_name, collection_name) != 0);
227 check_primary_name(collection_name, initial_primary_name);
228 check_collection(NULL, 0);
229 check_princ(collection_name, NULL);
230 check_princ(initial_primary_name, NULL);
231
232 /*
233 * Before initializing the primary ccache, generate and initialize two
234 * unique caches of the collection's type. Check that the cache names
235 * resolve to the generated caches and appear in the collection. (They
236 * might appear before being initialized; that's not currently considered
237 * important). The primary cache for the collection should remain as the
238 * uninitialized cache from the previous step.
239 */
240 typename = krb5_cc_get_type(ctx, ccinitial);
241 check(krb5_cc_new_unique(ctx, typename, NULL, &ccu1));
242 check(krb5_cc_get_full_name(ctx, ccu1, &unique1_name));
243 check(krb5_parse_name(ctx, "princ1@X", &princ1));
244 check(krb5_cc_initialize(ctx, ccu1, princ1));
245 check_princ(unique1_name, princ1);
246 check_match(princ1, unique1_name);
247 check_collection(NULL, 1, unique1_name);
248 check(krb5_cc_new_unique(ctx, typename, NULL, &ccu2));
249 check(krb5_cc_get_full_name(ctx, ccu2, &unique2_name));
250 check(krb5_parse_name(ctx, "princ2@X", &princ2));
251 check(krb5_cc_initialize(ctx, ccu2, princ2));
252 check_princ(unique2_name, princ2);
253 check_match(princ1, unique1_name);
254 check_match(princ2, unique2_name);
255 check_collection(NULL, 2, unique1_name, unique2_name);
256 assert(strcmp(unique1_name, initial_primary_name) != 0);
257 assert(strcmp(unique1_name, collection_name) != 0);
258 assert(strcmp(unique2_name, initial_primary_name) != 0);
259 assert(strcmp(unique2_name, collection_name) != 0);
260 assert(strcmp(unique2_name, unique1_name) != 0);
261 check_primary_name(collection_name, initial_primary_name);
262
263 /*
264 * Initialize the initial primary cache. Make sure it didn't change names,
265 * that the previously retrieved name and the collection name both resolve
266 * to the initialized cache, and that it now appears first in the
267 * collection.
268 */
269 check(krb5_parse_name(ctx, "princ3@X", &princ3));
270 check(krb5_cc_initialize(ctx, ccinitial, princ3));
271 check_name(ccinitial, initial_primary_name);
272 check_princ(initial_primary_name, princ3);
273 check_princ(collection_name, princ3);
274 check_match(princ3, initial_primary_name);
275 check_collection(initial_primary_name, 2, unique1_name, unique2_name);
276
277 /*
278 * Switch the primary cache to each cache we have open. One each switch,
279 * check the primary name, check that the collection resolves to the
280 * expected cache, and check that the new primary name appears first in the
281 * collection.
282 */
283 check(krb5_cc_switch(ctx, ccu1));
284 check_primary_name(collection_name, unique1_name);
285 check_princ(collection_name, princ1);
286 check_collection(unique1_name, 2, initial_primary_name, unique2_name);
287 check(krb5_cc_switch(ctx, ccu2));
288 check_primary_name(collection_name, unique2_name);
289 check_princ(collection_name, princ2);
290 check_collection(unique2_name, 2, initial_primary_name, unique1_name);
291 check(krb5_cc_switch(ctx, ccinitial));
292 check_primary_name(collection_name, initial_primary_name);
293 check_princ(collection_name, princ3);
294 check_collection(initial_primary_name, 2, unique1_name, unique2_name);
295
296 /*
297 * Temporarily set the context default ccache to a subsidiary name, and
298 * check that iterating over the collection yields that subsidiary cache
299 * and no others.
300 */
301 check(krb5_cc_set_default_name(ctx, unique1_name));
302 check_collection(unique1_name, 0);
303 check(krb5_cc_set_default_name(ctx, collection_name));
304
305 /*
306 * Destroy the primary cache. Make sure this causes both the initial
307 * primary name and the collection name to resolve to an uninitialized
308 * cache. Make sure the primary name doesn't change and doesn't appear in
309 * the collection any more.
310 */
311 check(krb5_cc_destroy(ctx, ccinitial));
312 check_princ(initial_primary_name, NULL);
313 check_princ(collection_name, NULL);
314 check_primary_name(collection_name, initial_primary_name);
315 check_match(princ1, unique1_name);
316 check_match(princ2, unique2_name);
317 check_match(princ3, NULL);
318 check_collection(NULL, 2, unique1_name, unique2_name);
319
320 /*
321 * Switch to the first unique cache after destroying the primary cache.
322 * Check that the collection name resolves to this cache and that the new
323 * primary name appears first in the collection.
324 */
325 check(krb5_cc_switch(ctx, ccu1));
326 check_primary_name(collection_name, unique1_name);
327 check_princ(collection_name, princ1);
328 check_collection(unique1_name, 1, unique2_name);
329
330 /*
331 * Destroy the second unique cache (which is not the current primary),
332 * check that it is on longer initialized, and check that it no longer
333 * appears in the collection. Check that destroying the non-primary cache
334 * doesn't affect the primary name.
335 */
336 check(krb5_cc_destroy(ctx, ccu2));
337 check_princ(unique2_name, NULL);
338 check_match(princ2, NULL);
339 check_collection(unique1_name, 0);
340 check_primary_name(collection_name, unique1_name);
341 check_match(princ1, unique1_name);
342 check_princ(collection_name, princ1);
343
344 /*
345 * Destroy the first unique cache. Check that the collection is empty and
346 * still has the same primary name.
347 */
348 check(krb5_cc_destroy(ctx, ccu1));
349 check_princ(unique1_name, NULL);
350 check_princ(collection_name, NULL);
351 check_primary_name(collection_name, unique1_name);
352 check_match(princ1, NULL);
353 check_collection(NULL, 0);
354
355 krb5_free_string(ctx, initial_primary_name);
356 krb5_free_string(ctx, unique1_name);
357 krb5_free_string(ctx, unique2_name);
358 krb5_free_principal(ctx, princ1);
359 krb5_free_principal(ctx, princ2);
360 krb5_free_principal(ctx, princ3);
361 krb5_free_context(ctx);
362 return 0;
363 }
364