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