1 /*	$NetBSD: scache.c,v 1.1.1.1 2011/04/13 18:15:37 elric Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Kungliga Tekniska Högskolan
5  * (Royal Institute of Technology, Stockholm, Sweden).
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * 3. Neither the name of the Institute nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #include "krb5_locl.h"
37 
38 #ifdef HAVE_SCC
39 
40 #include <sqlite3.h>
41 
42 typedef struct krb5_scache {
43     char *name;
44     char *file;
45     sqlite3 *db;
46 
47     sqlite_uint64 cid;
48 
49     sqlite3_stmt *icred;
50     sqlite3_stmt *dcred;
51     sqlite3_stmt *iprincipal;
52 
53     sqlite3_stmt *icache;
54     sqlite3_stmt *ucachen;
55     sqlite3_stmt *ucachep;
56     sqlite3_stmt *dcache;
57     sqlite3_stmt *scache;
58     sqlite3_stmt *scache_name;
59     sqlite3_stmt *umaster;
60 
61 } krb5_scache;
62 
63 #define	SCACHE(X)	((krb5_scache *)(X)->data.data)
64 
65 #define SCACHE_DEF_NAME		"Default-cache"
66 #ifdef KRB5_USE_PATH_TOKENS
67 #define KRB5_SCACHE_DB	"%{TEMP}/krb5scc_%{uid}"
68 #else
69 #define KRB5_SCACHE_DB	"/tmp/krb5scc_%{uid}"
70 #endif
71 #define KRB5_SCACHE_NAME	"SCC:"  SCACHE_DEF_NAME ":" KRB5_SCACHE_DB
72 
73 #define SCACHE_INVALID_CID	((sqlite_uint64)-1)
74 
75 /*
76  *
77  */
78 
79 #define SQL_CMASTER ""				\
80 	"CREATE TABLE master ("			\
81         "oid INTEGER PRIMARY KEY,"		\
82 	"version INTEGER NOT NULL,"		\
83 	"defaultcache TEXT NOT NULL"		\
84 	")"
85 
86 #define SQL_SETUP_MASTER \
87 	"INSERT INTO master (version,defaultcache) VALUES(2, \"" SCACHE_DEF_NAME "\")"
88 #define SQL_UMASTER "UPDATE master SET defaultcache=? WHERE version=2"
89 
90 #define SQL_CCACHE ""				\
91 	"CREATE TABLE caches ("			\
92         "oid INTEGER PRIMARY KEY,"		\
93 	"principal TEXT,"			\
94 	"name TEXT NOT NULL"			\
95 	")"
96 
97 #define SQL_TCACHE ""						\
98 	"CREATE TRIGGER CacheDropCreds AFTER DELETE ON caches "	\
99 	"FOR EACH ROW BEGIN "					\
100 	"DELETE FROM credentials WHERE cid=old.oid;"		\
101 	"END"
102 
103 #define SQL_ICACHE "INSERT INTO caches (name) VALUES(?)"
104 #define SQL_UCACHE_NAME "UPDATE caches SET name=? WHERE OID=?"
105 #define SQL_UCACHE_PRINCIPAL "UPDATE caches SET principal=? WHERE OID=?"
106 #define SQL_DCACHE "DELETE FROM caches WHERE OID=?"
107 #define SQL_SCACHE "SELECT principal,name FROM caches WHERE OID=?"
108 #define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=?"
109 
110 #define SQL_CCREDS ""				\
111 	"CREATE TABLE credentials ("		\
112         "oid INTEGER PRIMARY KEY,"		\
113 	"cid INTEGER NOT NULL,"			\
114 	"kvno INTEGER NOT NULL,"		\
115 	"etype INTEGER NOT NULL,"		\
116         "created_at INTEGER NOT NULL,"		\
117 	"cred BLOB NOT NULL"			\
118 	")"
119 
120 #define SQL_TCRED ""							\
121 	"CREATE TRIGGER credDropPrincipal AFTER DELETE ON credentials "	\
122 	"FOR EACH ROW BEGIN "						\
123 	"DELETE FROM principals WHERE credential_id=old.oid;"		\
124 	"END"
125 
126 #define SQL_ICRED "INSERT INTO credentials (cid, kvno, etype, cred, created_at) VALUES (?,?,?,?,?)"
127 #define SQL_DCRED "DELETE FROM credentials WHERE cid=?"
128 
129 #define SQL_CPRINCIPALS ""			\
130 	"CREATE TABLE principals ("		\
131         "oid INTEGER PRIMARY KEY,"		\
132 	"principal TEXT NOT NULL,"		\
133 	"type INTEGER NOT NULL,"		\
134 	"credential_id INTEGER NOT NULL"	\
135 	")"
136 
137 #define SQL_IPRINCIPAL "INSERT INTO principals (principal, type, credential_id) VALUES (?,?,?)"
138 
139 /*
140  * sqlite destructors
141  */
142 
143 static void
144 free_data(void *data)
145 {
146     free(data);
147 }
148 
149 static void
150 free_krb5(void *str)
151 {
152     krb5_xfree(str);
153 }
154 
155 static void
156 scc_free(krb5_scache *s)
157 {
158     if (s->file)
159 	free(s->file);
160     if (s->name)
161 	free(s->name);
162 
163     if (s->icred)
164 	sqlite3_finalize(s->icred);
165     if (s->dcred)
166 	sqlite3_finalize(s->dcred);
167     if (s->iprincipal)
168 	sqlite3_finalize(s->iprincipal);
169     if (s->icache)
170 	sqlite3_finalize(s->icache);
171     if (s->ucachen)
172 	sqlite3_finalize(s->ucachen);
173     if (s->ucachep)
174 	sqlite3_finalize(s->ucachep);
175     if (s->dcache)
176 	sqlite3_finalize(s->dcache);
177     if (s->scache)
178 	sqlite3_finalize(s->scache);
179     if (s->scache_name)
180 	sqlite3_finalize(s->scache_name);
181     if (s->umaster)
182 	sqlite3_finalize(s->umaster);
183 
184     if (s->db)
185 	sqlite3_close(s->db);
186     free(s);
187 }
188 
189 #ifdef TRACEME
190 static void
191 trace(void* ptr, const char * str)
192 {
193     printf("SQL: %s\n", str);
194 }
195 #endif
196 
197 static krb5_error_code
198 prepare_stmt(krb5_context context, sqlite3 *db,
199 	     sqlite3_stmt **stmt, const char *str)
200 {
201     int ret;
202 
203     ret = sqlite3_prepare_v2(db, str, -1, stmt, NULL);
204     if (ret != SQLITE_OK) {
205 	krb5_set_error_message(context, ENOENT,
206 			       N_("Failed to prepare stmt %s: %s", ""),
207 			       str, sqlite3_errmsg(db));
208 	return ENOENT;
209     }
210     return 0;
211 }
212 
213 static krb5_error_code
214 exec_stmt(krb5_context context, sqlite3 *db, const char *str,
215 	  krb5_error_code code)
216 {
217     int ret;
218 
219     ret = sqlite3_exec(db, str, NULL, NULL, NULL);
220     if (ret != SQLITE_OK && code) {
221 	krb5_set_error_message(context, code,
222 			       N_("scache execute %s: %s", ""), str,
223 			       sqlite3_errmsg(db));
224 	return code;
225     }
226     return 0;
227 }
228 
229 static krb5_error_code
230 default_db(krb5_context context, sqlite3 **db)
231 {
232     char *name;
233     int ret;
234 
235     ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &name);
236     if (ret)
237 	return ret;
238 
239     ret = sqlite3_open_v2(name, db, SQLITE_OPEN_READWRITE, NULL);
240     free(name);
241     if (ret != SQLITE_OK) {
242 	krb5_clear_error_message(context);
243 	return ENOENT;
244     }
245 
246 #ifdef TRACEME
247     sqlite3_trace(*db, trace, NULL);
248 #endif
249 
250     return 0;
251 }
252 
253 static krb5_error_code
254 get_def_name(krb5_context context, char **str)
255 {
256     krb5_error_code ret;
257     sqlite3_stmt *stmt;
258     const char *name;
259     sqlite3 *db;
260 
261     ret = default_db(context, &db);
262     if (ret)
263 	return ret;
264 
265     ret = prepare_stmt(context, db, &stmt, "SELECT defaultcache FROM master");
266     if (ret) {
267 	sqlite3_close(db);
268 	return ret;
269     }
270 
271     ret = sqlite3_step(stmt);
272     if (ret != SQLITE_ROW)
273 	goto out;
274 
275     if (sqlite3_column_type(stmt, 0) != SQLITE_TEXT)
276 	goto out;
277 
278     name = (const char *)sqlite3_column_text(stmt, 0);
279     if (name == NULL)
280 	goto out;
281 
282     *str = strdup(name);
283     if (*str == NULL)
284 	goto out;
285 
286     sqlite3_finalize(stmt);
287     sqlite3_close(db);
288     return 0;
289 out:
290     sqlite3_finalize(stmt);
291     sqlite3_close(db);
292     krb5_clear_error_message(context);
293     return ENOENT;
294 }
295 
296 
297 
298 static krb5_scache * KRB5_CALLCONV
299 scc_alloc(krb5_context context, const char *name)
300 {
301     krb5_error_code ret;
302     krb5_scache *s;
303 
304     ALLOC(s, 1);
305     if(s == NULL)
306 	return NULL;
307 
308     s->cid = SCACHE_INVALID_CID;
309 
310     if (name) {
311 	char *file;
312 
313 	if (*name == '\0') {
314 	    krb5_error_code ret;
315 	    ret = get_def_name(context, &s->name);
316 	    if (ret)
317 		s->name = strdup(SCACHE_DEF_NAME);
318 	} else
319 	    s->name = strdup(name);
320 
321 	file = strrchr(s->name, ':');
322 	if (file) {
323 	    *file++ = '\0';
324 	    s->file = strdup(file);
325 	    ret = 0;
326 	} else {
327 	    ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file);
328 	}
329     } else {
330 	_krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file);
331 	ret = asprintf(&s->name, "unique-%p", s);
332     }
333     if (ret < 0 || s->file == NULL || s->name == NULL) {
334 	scc_free(s);
335 	return NULL;
336     }
337 
338     return s;
339 }
340 
341 static krb5_error_code
342 open_database(krb5_context context, krb5_scache *s, int flags)
343 {
344     int ret;
345 
346     ret = sqlite3_open_v2(s->file, &s->db, SQLITE_OPEN_READWRITE|flags, NULL);
347     if (ret) {
348 	if (s->db) {
349 	    krb5_set_error_message(context, ENOENT,
350 				   N_("Error opening scache file %s: %s", ""),
351 				   s->file, sqlite3_errmsg(s->db));
352 	    sqlite3_close(s->db);
353 	    s->db = NULL;
354 	} else
355 	    krb5_set_error_message(context, ENOENT,
356 				   N_("malloc: out of memory", ""));
357 	return ENOENT;
358     }
359     return 0;
360 }
361 
362 static krb5_error_code
363 create_cache(krb5_context context, krb5_scache *s)
364 {
365     int ret;
366 
367     sqlite3_bind_text(s->icache, 1, s->name, -1, NULL);
368     do {
369 	ret = sqlite3_step(s->icache);
370     } while (ret == SQLITE_ROW);
371     if (ret != SQLITE_DONE) {
372 	krb5_set_error_message(context, KRB5_CC_IO,
373 			       N_("Failed to add scache: %d", ""), ret);
374 	return KRB5_CC_IO;
375     }
376     sqlite3_reset(s->icache);
377 
378     s->cid = sqlite3_last_insert_rowid(s->db);
379 
380     return 0;
381 }
382 
383 static krb5_error_code
384 make_database(krb5_context context, krb5_scache *s)
385 {
386     int created_file = 0;
387     int ret;
388 
389     if (s->db)
390 	return 0;
391 
392     ret = open_database(context, s, 0);
393     if (ret) {
394 	mode_t oldumask = umask(077);
395 	ret = open_database(context, s, SQLITE_OPEN_CREATE);
396 	umask(oldumask);
397 	if (ret) goto out;
398 
399 	created_file = 1;
400 
401 	ret = exec_stmt(context, s->db, SQL_CMASTER, KRB5_CC_IO);
402 	if (ret) goto out;
403 	ret = exec_stmt(context, s->db, SQL_CCACHE, KRB5_CC_IO);
404 	if (ret) goto out;
405 	ret = exec_stmt(context, s->db, SQL_CCREDS, KRB5_CC_IO);
406 	if (ret) goto out;
407 	ret = exec_stmt(context, s->db, SQL_CPRINCIPALS, KRB5_CC_IO);
408 	if (ret) goto out;
409 	ret = exec_stmt(context, s->db, SQL_SETUP_MASTER, KRB5_CC_IO);
410 	if (ret) goto out;
411 
412 	ret = exec_stmt(context, s->db, SQL_TCACHE, KRB5_CC_IO);
413 	if (ret) goto out;
414 	ret = exec_stmt(context, s->db, SQL_TCRED, KRB5_CC_IO);
415 	if (ret) goto out;
416     }
417 
418 #ifdef TRACEME
419     sqlite3_trace(s->db, trace, NULL);
420 #endif
421 
422     ret = prepare_stmt(context, s->db, &s->icred, SQL_ICRED);
423     if (ret) goto out;
424     ret = prepare_stmt(context, s->db, &s->dcred, SQL_DCRED);
425     if (ret) goto out;
426     ret = prepare_stmt(context, s->db, &s->iprincipal, SQL_IPRINCIPAL);
427     if (ret) goto out;
428     ret = prepare_stmt(context, s->db, &s->icache, SQL_ICACHE);
429     if (ret) goto out;
430     ret = prepare_stmt(context, s->db, &s->ucachen, SQL_UCACHE_NAME);
431     if (ret) goto out;
432     ret = prepare_stmt(context, s->db, &s->ucachep, SQL_UCACHE_PRINCIPAL);
433     if (ret) goto out;
434     ret = prepare_stmt(context, s->db, &s->dcache, SQL_DCACHE);
435     if (ret) goto out;
436     ret = prepare_stmt(context, s->db, &s->scache, SQL_SCACHE);
437     if (ret) goto out;
438     ret = prepare_stmt(context, s->db, &s->scache_name, SQL_SCACHE_NAME);
439     if (ret) goto out;
440     ret = prepare_stmt(context, s->db, &s->umaster, SQL_UMASTER);
441     if (ret) goto out;
442 
443     return 0;
444 
445 out:
446     if (s->db)
447 	sqlite3_close(s->db);
448     if (created_file)
449 	unlink(s->file);
450 
451     return ret;
452 }
453 
454 static krb5_error_code
455 bind_principal(krb5_context context,
456 	       sqlite3 *db,
457 	       sqlite3_stmt *stmt,
458 	       int col,
459 	       krb5_const_principal principal)
460 {
461     krb5_error_code ret;
462     char *str;
463 
464     ret = krb5_unparse_name(context, principal, &str);
465     if (ret)
466 	return ret;
467 
468     ret = sqlite3_bind_text(stmt, col, str, -1, free_krb5);
469     if (ret != SQLITE_OK) {
470 	krb5_xfree(str);
471 	krb5_set_error_message(context, ENOMEM,
472 			       N_("scache bind principal: %s", ""),
473 			       sqlite3_errmsg(db));
474 	return ENOMEM;
475     }
476     return 0;
477 }
478 
479 /*
480  *
481  */
482 
483 static const char* KRB5_CALLCONV
484 scc_get_name(krb5_context context,
485 	     krb5_ccache id)
486 {
487     return SCACHE(id)->name;
488 }
489 
490 static krb5_error_code KRB5_CALLCONV
491 scc_resolve(krb5_context context, krb5_ccache *id, const char *res)
492 {
493     krb5_scache *s;
494     int ret;
495 
496     s = scc_alloc(context, res);
497     if (s == NULL) {
498 	krb5_set_error_message(context, KRB5_CC_NOMEM,
499 			       N_("malloc: out of memory", ""));
500 	return KRB5_CC_NOMEM;
501     }
502 
503     ret = make_database(context, s);
504     if (ret) {
505 	scc_free(s);
506 	return ret;
507     }
508 
509     ret = sqlite3_bind_text(s->scache_name, 1, s->name, -1, NULL);
510     if (ret != SQLITE_OK) {
511 	krb5_set_error_message(context, ENOMEM,
512 			       "bind name: %s", sqlite3_errmsg(s->db));
513 	scc_free(s);
514 	return ENOMEM;
515     }
516 
517     if (sqlite3_step(s->scache_name) == SQLITE_ROW) {
518 
519 	if (sqlite3_column_type(s->scache_name, 0) != SQLITE_INTEGER) {
520 	    sqlite3_reset(s->scache_name);
521 	    krb5_set_error_message(context, KRB5_CC_END,
522 				   N_("Cache name of wrong type "
523 				      "for scache %ld", ""),
524 				  (unsigned long)s->name);
525 	    scc_free(s);
526 	    return KRB5_CC_END;
527 	}
528 
529 	s->cid = sqlite3_column_int(s->scache_name, 0);
530     } else {
531 	s->cid = SCACHE_INVALID_CID;
532     }
533     sqlite3_reset(s->scache_name);
534 
535     (*id)->data.data = s;
536     (*id)->data.length = sizeof(*s);
537 
538     return 0;
539 }
540 
541 static krb5_error_code KRB5_CALLCONV
542 scc_gen_new(krb5_context context, krb5_ccache *id)
543 {
544     krb5_scache *s;
545 
546     s = scc_alloc(context, NULL);
547 
548     if (s == NULL) {
549 	krb5_set_error_message(context, KRB5_CC_NOMEM,
550 			       N_("malloc: out of memory", ""));
551 	return KRB5_CC_NOMEM;
552     }
553 
554     (*id)->data.data = s;
555     (*id)->data.length = sizeof(*s);
556 
557     return 0;
558 }
559 
560 static krb5_error_code KRB5_CALLCONV
561 scc_initialize(krb5_context context,
562 	       krb5_ccache id,
563 	       krb5_principal primary_principal)
564 {
565     krb5_scache *s = SCACHE(id);
566     krb5_error_code ret;
567 
568     ret = make_database(context, s);
569     if (ret)
570 	return ret;
571 
572     ret = exec_stmt(context, s->db, "BEGIN IMMEDIATE TRANSACTION", KRB5_CC_IO);
573     if (ret) return ret;
574 
575     if (s->cid == SCACHE_INVALID_CID) {
576 	ret = create_cache(context, s);
577 	if (ret)
578 	    goto rollback;
579     } else {
580 	sqlite3_bind_int(s->dcred, 1, s->cid);
581 	do {
582 	    ret = sqlite3_step(s->dcred);
583 	} while (ret == SQLITE_ROW);
584 	sqlite3_reset(s->dcred);
585 	if (ret != SQLITE_DONE) {
586 	    ret = KRB5_CC_IO;
587 	    krb5_set_error_message(context, ret,
588 				   N_("Failed to delete old "
589 				      "credentials: %s", ""),
590 				   sqlite3_errmsg(s->db));
591 	    goto rollback;
592 	}
593     }
594 
595     ret = bind_principal(context, s->db, s->ucachep, 1, primary_principal);
596     if (ret)
597 	goto rollback;
598     sqlite3_bind_int(s->ucachep, 2, s->cid);
599 
600     do {
601 	ret = sqlite3_step(s->ucachep);
602     } while (ret == SQLITE_ROW);
603     sqlite3_reset(s->ucachep);
604     if (ret != SQLITE_DONE) {
605 	ret = KRB5_CC_IO;
606 	krb5_set_error_message(context, ret,
607 			       N_("Failed to bind principal to cache %s", ""),
608 			       sqlite3_errmsg(s->db));
609 	goto rollback;
610     }
611 
612     ret = exec_stmt(context, s->db, "COMMIT", KRB5_CC_IO);
613     if (ret) return ret;
614 
615     return 0;
616 
617 rollback:
618     exec_stmt(context, s->db, "ROLLBACK", 0);
619 
620     return ret;
621 
622 }
623 
624 static krb5_error_code KRB5_CALLCONV
625 scc_close(krb5_context context,
626 	  krb5_ccache id)
627 {
628     scc_free(SCACHE(id));
629     return 0;
630 }
631 
632 static krb5_error_code KRB5_CALLCONV
633 scc_destroy(krb5_context context,
634 	    krb5_ccache id)
635 {
636     krb5_scache *s = SCACHE(id);
637     int ret;
638 
639     if (s->cid == SCACHE_INVALID_CID)
640 	return 0;
641 
642     sqlite3_bind_int(s->dcache, 1, s->cid);
643     do {
644 	ret = sqlite3_step(s->dcache);
645     } while (ret == SQLITE_ROW);
646     sqlite3_reset(s->dcache);
647     if (ret != SQLITE_DONE) {
648 	krb5_set_error_message(context, KRB5_CC_IO,
649 			       N_("Failed to destroy cache %s: %s", ""),
650 			       s->name, sqlite3_errmsg(s->db));
651 	return KRB5_CC_IO;
652     }
653     return 0;
654 }
655 
656 static krb5_error_code
657 encode_creds(krb5_context context, krb5_creds *creds, krb5_data *data)
658 {
659     krb5_error_code ret;
660     krb5_storage *sp;
661 
662     sp = krb5_storage_emem();
663     if (sp == NULL) {
664 	krb5_set_error_message(context, ENOMEM,
665 			       N_("malloc: out of memory", ""));
666 	return ENOMEM;
667     }
668 
669     ret = krb5_store_creds(sp, creds);
670     if (ret) {
671 	krb5_set_error_message(context, ret,
672 			       N_("Failed to store credential in scache", ""));
673 	krb5_storage_free(sp);
674 	return ret;
675     }
676 
677     ret = krb5_storage_to_data(sp, data);
678     krb5_storage_free(sp);
679     if (ret)
680 	krb5_set_error_message(context, ret,
681 			       N_("Failed to encode credential in scache", ""));
682     return ret;
683 }
684 
685 static krb5_error_code
686 decode_creds(krb5_context context, const void *data, size_t length,
687 	     krb5_creds *creds)
688 {
689     krb5_error_code ret;
690     krb5_storage *sp;
691 
692     sp = krb5_storage_from_readonly_mem(data, length);
693     if (sp == NULL) {
694 	krb5_set_error_message(context, ENOMEM,
695 			       N_("malloc: out of memory", ""));
696 	return ENOMEM;
697     }
698 
699     ret = krb5_ret_creds(sp, creds);
700     krb5_storage_free(sp);
701     if (ret) {
702 	krb5_set_error_message(context, ret,
703 			       N_("Failed to read credential in scache", ""));
704 	return ret;
705     }
706     return 0;
707 }
708 
709 
710 static krb5_error_code KRB5_CALLCONV
711 scc_store_cred(krb5_context context,
712 	       krb5_ccache id,
713 	       krb5_creds *creds)
714 {
715     sqlite_uint64 credid;
716     krb5_scache *s = SCACHE(id);
717     krb5_error_code ret;
718     krb5_data data;
719 
720     ret = make_database(context, s);
721     if (ret)
722 	return ret;
723 
724     ret = encode_creds(context, creds, &data);
725     if (ret)
726 	return ret;
727 
728     sqlite3_bind_int(s->icred, 1, s->cid);
729     {
730 	krb5_enctype etype = 0;
731 	int kvno = 0;
732 	Ticket t;
733 	size_t len;
734 
735 	ret = decode_Ticket(creds->ticket.data,
736 			    creds->ticket.length, &t, &len);
737 	if (ret == 0) {
738 	    if(t.enc_part.kvno)
739 		kvno = *t.enc_part.kvno;
740 
741 	    etype = t.enc_part.etype;
742 
743 	    free_Ticket(&t);
744 	}
745 
746 	sqlite3_bind_int(s->icred, 2, kvno);
747 	sqlite3_bind_int(s->icred, 3, etype);
748 
749     }
750 
751     sqlite3_bind_blob(s->icred, 4, data.data, data.length, free_data);
752     sqlite3_bind_int(s->icred, 5, time(NULL));
753 
754     ret = exec_stmt(context, s->db, "BEGIN IMMEDIATE TRANSACTION", KRB5_CC_IO);
755     if (ret) return ret;
756 
757     do {
758 	ret = sqlite3_step(s->icred);
759     } while (ret == SQLITE_ROW);
760     sqlite3_reset(s->icred);
761     if (ret != SQLITE_DONE) {
762 	ret = KRB5_CC_IO;
763 	krb5_set_error_message(context, ret,
764 			       N_("Failed to add credential: %s", ""),
765 			       sqlite3_errmsg(s->db));
766 	goto rollback;
767     }
768 
769     credid = sqlite3_last_insert_rowid(s->db);
770 
771     {
772 	bind_principal(context, s->db, s->iprincipal, 1, creds->server);
773 	sqlite3_bind_int(s->iprincipal, 2, 1);
774 	sqlite3_bind_int(s->iprincipal, 3, credid);
775 
776 	do {
777 	    ret = sqlite3_step(s->iprincipal);
778 	} while (ret == SQLITE_ROW);
779 	sqlite3_reset(s->iprincipal);
780 	if (ret != SQLITE_DONE) {
781 	    ret = KRB5_CC_IO;
782 	    krb5_set_error_message(context, ret,
783 				   N_("Failed to add principal: %s", ""),
784 				   sqlite3_errmsg(s->db));
785 	    goto rollback;
786 	}
787     }
788 
789     {
790 	bind_principal(context, s->db, s->iprincipal, 1, creds->client);
791 	sqlite3_bind_int(s->iprincipal, 2, 0);
792 	sqlite3_bind_int(s->iprincipal, 3, credid);
793 
794 	do {
795 	    ret = sqlite3_step(s->iprincipal);
796 	} while (ret == SQLITE_ROW);
797 	sqlite3_reset(s->iprincipal);
798 	if (ret != SQLITE_DONE) {
799 	    ret = KRB5_CC_IO;
800 	    krb5_set_error_message(context, ret,
801 				   N_("Failed to add principal: %s", ""),
802 				   sqlite3_errmsg(s->db));
803 	    goto rollback;
804 	}
805     }
806 
807     ret = exec_stmt(context, s->db, "COMMIT", KRB5_CC_IO);
808     if (ret) return ret;
809 
810     return 0;
811 
812 rollback:
813     exec_stmt(context, s->db, "ROLLBACK", 0);
814 
815     return ret;
816 }
817 
818 static krb5_error_code KRB5_CALLCONV
819 scc_get_principal(krb5_context context,
820 		  krb5_ccache id,
821 		  krb5_principal *principal)
822 {
823     krb5_scache *s = SCACHE(id);
824     krb5_error_code ret;
825     const char *str;
826 
827     *principal = NULL;
828 
829     ret = make_database(context, s);
830     if (ret)
831 	return ret;
832 
833     sqlite3_bind_int(s->scache, 1, s->cid);
834 
835     if (sqlite3_step(s->scache) != SQLITE_ROW) {
836 	sqlite3_reset(s->scache);
837 	krb5_set_error_message(context, KRB5_CC_END,
838 			       N_("No principal for cache SCC:%s:%s", ""),
839 			       s->name, s->file);
840 	return KRB5_CC_END;
841     }
842 
843     if (sqlite3_column_type(s->scache, 0) != SQLITE_TEXT) {
844 	sqlite3_reset(s->scache);
845 	krb5_set_error_message(context, KRB5_CC_END,
846 			       N_("Principal data of wrong type "
847 				  "for SCC:%s:%s", ""),
848 			       s->name, s->file);
849 	return KRB5_CC_END;
850     }
851 
852     str = (const char *)sqlite3_column_text(s->scache, 0);
853     if (str == NULL) {
854 	sqlite3_reset(s->scache);
855 	krb5_set_error_message(context, KRB5_CC_END,
856 			       N_("Principal not set for SCC:%s:%s", ""),
857 			       s->name, s->file);
858 	return KRB5_CC_END;
859     }
860 
861     ret = krb5_parse_name(context, str, principal);
862 
863     sqlite3_reset(s->scache);
864 
865     return ret;
866 }
867 
868 struct cred_ctx {
869     char *drop;
870     sqlite3_stmt *stmt;
871     sqlite3_stmt *credstmt;
872 };
873 
874 static krb5_error_code KRB5_CALLCONV
875 scc_get_first (krb5_context context,
876 	       krb5_ccache id,
877 	       krb5_cc_cursor *cursor)
878 {
879     krb5_scache *s = SCACHE(id);
880     krb5_error_code ret;
881     struct cred_ctx *ctx;
882     char *str = NULL, *name = NULL;
883 
884     *cursor = NULL;
885 
886     ctx = calloc(1, sizeof(*ctx));
887     if (ctx == NULL) {
888 	krb5_set_error_message(context, ENOMEM,
889 			       N_("malloc: out of memory", ""));
890 	return ENOMEM;
891     }
892 
893     ret = make_database(context, s);
894     if (ret) {
895 	free(ctx);
896 	return ret;
897     }
898 
899     if (s->cid == SCACHE_INVALID_CID) {
900 	krb5_set_error_message(context, KRB5_CC_END,
901 			       N_("Iterating a invalid scache %s", ""),
902 			       s->name);
903 	free(ctx);
904 	return KRB5_CC_END;
905     }
906 
907     ret = asprintf(&name, "credIteration%luPid%d",
908 	     (unsigned long)ctx, (int)getpid());
909     if (ret < 0 || name == NULL) {
910 	krb5_set_error_message(context, ENOMEM,
911 			       N_("malloc: out of memory", ""));
912 	free(ctx);
913 	return ENOMEM;
914     }
915 
916     ret = asprintf(&ctx->drop, "DROP TABLE %s", name);
917     if (ret < 0 || ctx->drop == NULL) {
918 	krb5_set_error_message(context, ENOMEM,
919 			       N_("malloc: out of memory", ""));
920 	free(name);
921 	free(ctx);
922 	return ENOMEM;
923     }
924 
925     ret = asprintf(&str, "CREATE TEMPORARY TABLE %s "
926 	     "AS SELECT oid,created_at FROM credentials WHERE cid = %lu",
927 	     name, (unsigned long)s->cid);
928     if (ret < 0 || str == NULL) {
929 	free(ctx->drop);
930 	free(name);
931 	free(ctx);
932 	return ENOMEM;
933     }
934 
935     ret = exec_stmt(context, s->db, str, KRB5_CC_IO);
936     free(str);
937     str = NULL;
938     if (ret) {
939 	free(ctx->drop);
940 	free(name);
941 	free(ctx);
942 	return ret;
943     }
944 
945     ret = asprintf(&str, "SELECT oid FROM %s ORDER BY created_at", name);
946     if (ret < 0 || str == NULL) {
947 	exec_stmt(context, s->db, ctx->drop, 0);
948 	free(ctx->drop);
949 	free(name);
950 	free(ctx);
951 	return ret;
952     }
953 
954     ret = prepare_stmt(context, s->db, &ctx->stmt, str);
955     free(str);
956     str = NULL;
957     free(name);
958     if (ret) {
959 	exec_stmt(context, s->db, ctx->drop, 0);
960 	free(ctx->drop);
961 	free(ctx);
962 	return ret;
963     }
964 
965     ret = prepare_stmt(context, s->db, &ctx->credstmt,
966 		       "SELECT cred FROM credentials WHERE oid = ?");
967     if (ret) {
968 	sqlite3_finalize(ctx->stmt);
969 	exec_stmt(context, s->db, ctx->drop, 0);
970 	free(ctx->drop);
971 	free(ctx);
972 	return ret;
973     }
974 
975     *cursor = ctx;
976 
977     return 0;
978 }
979 
980 static krb5_error_code KRB5_CALLCONV
981 scc_get_next (krb5_context context,
982 	      krb5_ccache id,
983 	      krb5_cc_cursor *cursor,
984 	      krb5_creds *creds)
985 {
986     struct cred_ctx *ctx = *cursor;
987     krb5_scache *s = SCACHE(id);
988     krb5_error_code ret;
989     sqlite_uint64 oid;
990     const void *data = NULL;
991     size_t len = 0;
992 
993 next:
994     ret = sqlite3_step(ctx->stmt);
995     if (ret == SQLITE_DONE) {
996 	krb5_clear_error_message(context);
997         return KRB5_CC_END;
998     } else if (ret != SQLITE_ROW) {
999 	krb5_set_error_message(context, KRB5_CC_IO,
1000 			       N_("scache Database failed: %s", ""),
1001 			       sqlite3_errmsg(s->db));
1002         return KRB5_CC_IO;
1003     }
1004 
1005     oid = sqlite3_column_int64(ctx->stmt, 0);
1006 
1007     /* read cred from credentials table */
1008 
1009     sqlite3_bind_int(ctx->credstmt, 1, oid);
1010 
1011     ret = sqlite3_step(ctx->credstmt);
1012     if (ret != SQLITE_ROW) {
1013 	sqlite3_reset(ctx->credstmt);
1014 	goto next;
1015     }
1016 
1017     if (sqlite3_column_type(ctx->credstmt, 0) != SQLITE_BLOB) {
1018 	krb5_set_error_message(context, KRB5_CC_END,
1019 			       N_("credential of wrong type for SCC:%s:%s", ""),
1020 			       s->name, s->file);
1021 	sqlite3_reset(ctx->credstmt);
1022 	return KRB5_CC_END;
1023     }
1024 
1025     data = sqlite3_column_blob(ctx->credstmt, 0);
1026     len = sqlite3_column_bytes(ctx->credstmt, 0);
1027 
1028     ret = decode_creds(context, data, len, creds);
1029     sqlite3_reset(ctx->credstmt);
1030     return ret;
1031 }
1032 
1033 static krb5_error_code KRB5_CALLCONV
1034 scc_end_get (krb5_context context,
1035 	     krb5_ccache id,
1036 	     krb5_cc_cursor *cursor)
1037 {
1038     struct cred_ctx *ctx = *cursor;
1039     krb5_scache *s = SCACHE(id);
1040 
1041     sqlite3_finalize(ctx->stmt);
1042     sqlite3_finalize(ctx->credstmt);
1043 
1044     exec_stmt(context, s->db, ctx->drop, 0);
1045 
1046     free(ctx->drop);
1047     free(ctx);
1048 
1049     return 0;
1050 }
1051 
1052 static krb5_error_code KRB5_CALLCONV
1053 scc_remove_cred(krb5_context context,
1054 		 krb5_ccache id,
1055 		 krb5_flags which,
1056 		 krb5_creds *mcreds)
1057 {
1058     krb5_scache *s = SCACHE(id);
1059     krb5_error_code ret;
1060     sqlite3_stmt *stmt;
1061     sqlite_uint64 credid = 0;
1062     const void *data = NULL;
1063     size_t len = 0;
1064 
1065     ret = make_database(context, s);
1066     if (ret)
1067 	return ret;
1068 
1069     ret = prepare_stmt(context, s->db, &stmt,
1070 		       "SELECT cred,oid FROM credentials "
1071 		       "WHERE cid = ?");
1072     if (ret)
1073 	return ret;
1074 
1075     sqlite3_bind_int(stmt, 1, s->cid);
1076 
1077     /* find credential... */
1078     while (1) {
1079 	krb5_creds creds;
1080 
1081 	ret = sqlite3_step(stmt);
1082 	if (ret == SQLITE_DONE) {
1083 	    ret = 0;
1084 	    break;
1085 	} else if (ret != SQLITE_ROW) {
1086 	    ret = KRB5_CC_IO;
1087 	    krb5_set_error_message(context, ret,
1088 				   N_("scache Database failed: %s", ""),
1089 				   sqlite3_errmsg(s->db));
1090 	    break;
1091 	}
1092 
1093 	if (sqlite3_column_type(stmt, 0) != SQLITE_BLOB) {
1094 	    ret = KRB5_CC_END;
1095 	    krb5_set_error_message(context, ret,
1096 				   N_("Credential of wrong type "
1097 				      "for SCC:%s:%s", ""),
1098 				   s->name, s->file);
1099 	    break;
1100 	}
1101 
1102 	data = sqlite3_column_blob(stmt, 0);
1103 	len = sqlite3_column_bytes(stmt, 0);
1104 
1105 	ret = decode_creds(context, data, len, &creds);
1106 	if (ret)
1107 	    break;
1108 
1109 	ret = krb5_compare_creds(context, which, mcreds, &creds);
1110 	krb5_free_cred_contents(context, &creds);
1111 	if (ret) {
1112 	    credid = sqlite3_column_int64(stmt, 1);
1113 	    ret = 0;
1114 	    break;
1115 	}
1116     }
1117 
1118     sqlite3_finalize(stmt);
1119 
1120     if (id) {
1121 	ret = prepare_stmt(context, s->db, &stmt,
1122 			   "DELETE FROM credentials WHERE oid=?");
1123 	if (ret)
1124 	    return ret;
1125 	sqlite3_bind_int(stmt, 1, credid);
1126 
1127 	do {
1128 	    ret = sqlite3_step(stmt);
1129 	} while (ret == SQLITE_ROW);
1130 	sqlite3_finalize(stmt);
1131 	if (ret != SQLITE_DONE) {
1132 	    ret = KRB5_CC_IO;
1133 	    krb5_set_error_message(context, ret,
1134 				   N_("failed to delete scache credental", ""));
1135 	} else
1136 	    ret = 0;
1137     }
1138 
1139     return ret;
1140 }
1141 
1142 static krb5_error_code KRB5_CALLCONV
1143 scc_set_flags(krb5_context context,
1144 	      krb5_ccache id,
1145 	      krb5_flags flags)
1146 {
1147     return 0; /* XXX */
1148 }
1149 
1150 struct cache_iter {
1151     char *drop;
1152     sqlite3 *db;
1153     sqlite3_stmt *stmt;
1154 };
1155 
1156 static krb5_error_code KRB5_CALLCONV
1157 scc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
1158 {
1159     struct cache_iter *ctx;
1160     krb5_error_code ret;
1161     char *name = NULL, *str = NULL;
1162 
1163     *cursor = NULL;
1164 
1165     ctx = calloc(1, sizeof(*ctx));
1166     if (ctx == NULL) {
1167 	krb5_set_error_message(context, ENOMEM,
1168 			       N_("malloc: out of memory", ""));
1169 	return ENOMEM;
1170     }
1171 
1172     ret = default_db(context, &ctx->db);
1173     if (ctx->db == NULL) {
1174 	free(ctx);
1175 	return ret;
1176     }
1177 
1178     ret = asprintf(&name, "cacheIteration%luPid%d",
1179 	     (unsigned long)ctx, (int)getpid());
1180     if (ret < 0 || name == NULL) {
1181 	krb5_set_error_message(context, ENOMEM,
1182 			       N_("malloc: out of memory", ""));
1183 	sqlite3_close(ctx->db);
1184 	free(ctx);
1185 	return ENOMEM;
1186     }
1187 
1188     ret = asprintf(&ctx->drop, "DROP TABLE %s", name);
1189     if (ret < 0 || ctx->drop == NULL) {
1190 	krb5_set_error_message(context, ENOMEM,
1191 			       N_("malloc: out of memory", ""));
1192 	sqlite3_close(ctx->db);
1193 	free(name);
1194 	free(ctx);
1195 	return ENOMEM;
1196     }
1197 
1198     ret = asprintf(&str, "CREATE TEMPORARY TABLE %s AS SELECT name FROM caches",
1199 	     name);
1200     if (ret < 0 || str == NULL) {
1201 	krb5_set_error_message(context, ENOMEM,
1202 			       N_("malloc: out of memory", ""));
1203 	sqlite3_close(ctx->db);
1204 	free(name);
1205 	free(ctx->drop);
1206 	free(ctx);
1207 	return ENOMEM;
1208     }
1209 
1210     ret = exec_stmt(context, ctx->db, str, KRB5_CC_IO);
1211     free(str);
1212     str = NULL;
1213     if (ret) {
1214 	sqlite3_close(ctx->db);
1215 	free(name);
1216 	free(ctx->drop);
1217 	free(ctx);
1218 	return ret;
1219     }
1220 
1221     ret = asprintf(&str, "SELECT name FROM %s", name);
1222     free(name);
1223     if (ret < 0 || str == NULL) {
1224 	exec_stmt(context, ctx->db, ctx->drop, 0);
1225 	sqlite3_close(ctx->db);
1226 	free(name);
1227 	free(ctx->drop);
1228 	free(ctx);
1229 	return ENOMEM;
1230     }
1231 
1232     ret = prepare_stmt(context, ctx->db, &ctx->stmt, str);
1233     free(str);
1234     if (ret) {
1235 	exec_stmt(context, ctx->db, ctx->drop, 0);
1236 	sqlite3_close(ctx->db);
1237 	free(ctx->drop);
1238 	free(ctx);
1239 	return ret;
1240     }
1241 
1242     *cursor = ctx;
1243 
1244     return 0;
1245 }
1246 
1247 static krb5_error_code KRB5_CALLCONV
1248 scc_get_cache_next(krb5_context context,
1249 		   krb5_cc_cursor cursor,
1250 		   krb5_ccache *id)
1251 {
1252     struct cache_iter *ctx = cursor;
1253     krb5_error_code ret;
1254     const char *name;
1255 
1256 again:
1257     ret = sqlite3_step(ctx->stmt);
1258     if (ret == SQLITE_DONE) {
1259 	krb5_clear_error_message(context);
1260         return KRB5_CC_END;
1261     } else if (ret != SQLITE_ROW) {
1262 	krb5_set_error_message(context, KRB5_CC_IO,
1263 			       N_("Database failed: %s", ""),
1264 			       sqlite3_errmsg(ctx->db));
1265         return KRB5_CC_IO;
1266     }
1267 
1268     if (sqlite3_column_type(ctx->stmt, 0) != SQLITE_TEXT)
1269 	goto again;
1270 
1271     name = (const char *)sqlite3_column_text(ctx->stmt, 0);
1272     if (name == NULL)
1273 	goto again;
1274 
1275     ret = _krb5_cc_allocate(context, &krb5_scc_ops, id);
1276     if (ret)
1277 	return ret;
1278 
1279     return scc_resolve(context, id, name);
1280 }
1281 
1282 static krb5_error_code KRB5_CALLCONV
1283 scc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
1284 {
1285     struct cache_iter *ctx = cursor;
1286 
1287     exec_stmt(context, ctx->db, ctx->drop, 0);
1288     sqlite3_finalize(ctx->stmt);
1289     sqlite3_close(ctx->db);
1290     free(ctx->drop);
1291     free(ctx);
1292     return 0;
1293 }
1294 
1295 static krb5_error_code KRB5_CALLCONV
1296 scc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
1297 {
1298     krb5_scache *sfrom = SCACHE(from);
1299     krb5_scache *sto = SCACHE(to);
1300     krb5_error_code ret;
1301 
1302     if (strcmp(sfrom->file, sto->file) != 0) {
1303 	krb5_set_error_message(context, KRB5_CC_BADNAME,
1304 			       N_("Can't handle cross database "
1305 				  "credential move: %s -> %s", ""),
1306 			       sfrom->file, sto->file);
1307 	return KRB5_CC_BADNAME;
1308     }
1309 
1310     ret = make_database(context, sfrom);
1311     if (ret)
1312 	return ret;
1313 
1314     ret = exec_stmt(context, sfrom->db,
1315 		    "BEGIN IMMEDIATE TRANSACTION", KRB5_CC_IO);
1316     if (ret) return ret;
1317 
1318     if (sto->cid != SCACHE_INVALID_CID) {
1319 	/* drop old cache entry */
1320 
1321 	sqlite3_bind_int(sfrom->dcache, 1, sto->cid);
1322 	do {
1323 	    ret = sqlite3_step(sfrom->dcache);
1324 	} while (ret == SQLITE_ROW);
1325 	sqlite3_reset(sfrom->dcache);
1326 	if (ret != SQLITE_DONE) {
1327 	    krb5_set_error_message(context, KRB5_CC_IO,
1328 				   N_("Failed to delete old cache: %d", ""),
1329 				   (int)ret);
1330 	    goto rollback;
1331 	}
1332     }
1333 
1334     sqlite3_bind_text(sfrom->ucachen, 1, sto->name, -1, NULL);
1335     sqlite3_bind_int(sfrom->ucachen, 2, sfrom->cid);
1336 
1337     do {
1338 	ret = sqlite3_step(sfrom->ucachen);
1339     } while (ret == SQLITE_ROW);
1340     sqlite3_reset(sfrom->ucachen);
1341     if (ret != SQLITE_DONE) {
1342 	krb5_set_error_message(context, KRB5_CC_IO,
1343 			       N_("Failed to update new cache: %d", ""),
1344 			       (int)ret);
1345 	goto rollback;
1346     }
1347 
1348     sto->cid = sfrom->cid;
1349 
1350     ret = exec_stmt(context, sfrom->db, "COMMIT", KRB5_CC_IO);
1351     if (ret) return ret;
1352 
1353     scc_free(sfrom);
1354 
1355     return 0;
1356 
1357 rollback:
1358     exec_stmt(context, sfrom->db, "ROLLBACK", 0);
1359     scc_free(sfrom);
1360 
1361     return KRB5_CC_IO;
1362 }
1363 
1364 static krb5_error_code KRB5_CALLCONV
1365 scc_get_default_name(krb5_context context, char **str)
1366 {
1367     krb5_error_code ret;
1368     char *name;
1369 
1370     *str = NULL;
1371 
1372     ret = get_def_name(context, &name);
1373     if (ret)
1374 	return _krb5_expand_default_cc_name(context, KRB5_SCACHE_NAME, str);
1375 
1376     ret = asprintf(str, "SCC:%s", name);
1377     free(name);
1378     if (ret < 0 || *str == NULL) {
1379 	krb5_set_error_message(context, ENOMEM,
1380 			       N_("malloc: out of memory", ""));
1381 	return ENOMEM;
1382     }
1383     return 0;
1384 }
1385 
1386 static krb5_error_code KRB5_CALLCONV
1387 scc_set_default(krb5_context context, krb5_ccache id)
1388 {
1389     krb5_scache *s = SCACHE(id);
1390     krb5_error_code ret;
1391 
1392     if (s->cid == SCACHE_INVALID_CID) {
1393 	krb5_set_error_message(context, KRB5_CC_IO,
1394 			       N_("Trying to set a invalid cache "
1395 				  "as default %s", ""),
1396 			       s->name);
1397 	return KRB5_CC_IO;
1398     }
1399 
1400     ret = sqlite3_bind_text(s->umaster, 1, s->name, -1, NULL);
1401     if (ret) {
1402 	sqlite3_reset(s->umaster);
1403 	krb5_set_error_message(context, KRB5_CC_IO,
1404 			       N_("Failed to set name of default cache", ""));
1405 	return KRB5_CC_IO;
1406     }
1407 
1408     do {
1409 	ret = sqlite3_step(s->umaster);
1410     } while (ret == SQLITE_ROW);
1411     sqlite3_reset(s->umaster);
1412     if (ret != SQLITE_DONE) {
1413 	krb5_set_error_message(context, KRB5_CC_IO,
1414 			       N_("Failed to update default cache", ""));
1415 	return KRB5_CC_IO;
1416     }
1417 
1418     return 0;
1419 }
1420 
1421 /**
1422  * Variable containing the SCC based credential cache implemention.
1423  *
1424  * @ingroup krb5_ccache
1425  */
1426 
1427 KRB5_LIB_VARIABLE const krb5_cc_ops krb5_scc_ops = {
1428     KRB5_CC_OPS_VERSION,
1429     "SCC",
1430     scc_get_name,
1431     scc_resolve,
1432     scc_gen_new,
1433     scc_initialize,
1434     scc_destroy,
1435     scc_close,
1436     scc_store_cred,
1437     NULL, /* scc_retrieve */
1438     scc_get_principal,
1439     scc_get_first,
1440     scc_get_next,
1441     scc_end_get,
1442     scc_remove_cred,
1443     scc_set_flags,
1444     NULL,
1445     scc_get_cache_first,
1446     scc_get_cache_next,
1447     scc_end_cache_get,
1448     scc_move,
1449     scc_get_default_name,
1450     scc_set_default
1451 };
1452 
1453 #endif
1454