diff --git a/lib/krb5/scache.c b/lib/krb5/scache.c index 029f572b6..1a017f190 100644 --- a/lib/krb5/scache.c +++ b/lib/krb5/scache.c @@ -41,17 +41,20 @@ typedef struct krb5_scache { char *file; sqlite3 *db; - int cid; + sqlite_uint64 cid; /* */ HEIMDAL_MUTEX imutex; sqlite3_stmt *icred; + sqlite3_stmt *dcred; sqlite3_stmt *iprincipal; sqlite3_stmt *icache; + sqlite3_stmt *ucachen; sqlite3_stmt *ucachep; + sqlite3_stmt *dcache; sqlite3_stmt *scache; - + sqlite3_stmt *scache_name; } krb5_scache; @@ -62,15 +65,17 @@ typedef struct krb5_scache { #define SCACHE_STRING(x) #x #define SCACHE_XSTRING(x) SCACHE_STRING(x) -#define SCACHE_DEF_CID 0 +#define SCACHE_INVALID_CID ((sqlite_uint64)-1) +#define SCACHE_DEF_CID 1 #define SCACHE_DEF_CID_NAME SCACHE_XSTRING(SCACHE_DEF_CID) +#define SCACHE_DEF_NAME "Default-cache" /* * */ #define SQL_CMASTER "" \ - "CREATE TABLE master (" \ + "CREATE TABLE master (" \ "version INTEGER NOT NULL," \ "defaultcache INTEGER NOT NULL" \ ")" @@ -83,9 +88,18 @@ typedef struct krb5_scache { "name TEXT NOT NULL" \ ")" +#define SQL_TCACHE "" \ + "CREATE TRIGGER CacheDropCreds AFTER DELETE ON caches " \ + "FOR EACH ROW BEGIN " \ + "DELETE FROM credentials WHERE cid=old.oid;" \ + "END" + #define SQL_ICACHE "INSERT INTO caches (name) VALUES(?)" +#define SQL_UCACHE_NAME "UPDATE caches SET name=? WHERE OID=?" #define SQL_UCACHE_PRINCIPAL "UPDATE caches SET principal=? WHERE OID=?" +#define SQL_DCACHE "DELETE FROM caches WHERE OID=?" #define SQL_SCACHE "SELECT principal,name FROM caches WHERE OID=?" +#define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=?" #define SQL_CCREDS "" \ "CREATE TABLE credentials (" \ @@ -95,7 +109,14 @@ typedef struct krb5_scache { "cred BLOB NOT NULL" \ ")" +#define SQL_TCRED "" \ + "CREATE TRIGGER credDropPrincipal AFTER DELETE ON credentials " \ + "FOR EACH ROW BEGIN " \ + "DELETE FROM principals WHERE credential_id=old.oid;" \ + "END" + #define SQL_ICRED "INSERT INTO credentials (cid, kvno, etype, cred) VALUES (?,?,?,?)" +#define SQL_DCRED "DELETE FROM credentials WHERE cid=?" #define SQL_CPRINCIPALS "" \ "CREATE TABLE principals (" \ @@ -122,15 +143,17 @@ free_krb5(void *str) krb5_xfree(str); } -/* - * - */ - -static const char* -scc_get_name(krb5_context context, - krb5_ccache id) +static void +scc_free(krb5_scache *s) { - return SCACHE(id)->name; + if (s->file) + free(s->file); + if (s->name) + free(s->name); + /* XXX handle stmts */ + if (s->db) + sqlite3_close(s->db); + free(s); } static krb5_scache * @@ -142,10 +165,31 @@ scc_alloc(const char *name) if(s == NULL) return NULL; - /* XXX resolve name */ - s->cid = SCACHE_DEF_CID; - s->file = DEFAULT_SCACHE; - s->name = SCACHE_DEF_CID_NAME; + s->cid = SCACHE_INVALID_CID; + + if (name) { + char *file; + + if (*name == '\0') { + /* XXX get default cid */ + name = SCACHE_DEF_NAME; + } + s->name = strdup(name); + file = strchr(s->name, ':'); + if (file) { + *file++ = '\0'; + s->file = strdup(file); + } else { + s->file = strdup(DEFAULT_SCACHE); + } + } else { + s->file = strdup(DEFAULT_SCACHE); + asprintf(&s->name, "unique:%08x", (unsigned long)s); + } + if (s->file == NULL || s->name == NULL) { + scc_free(s); + return NULL; + } HEIMDAL_MUTEX_init(&s->imutex); @@ -171,10 +215,68 @@ open_database(krb5_context context, krb5_scache *s, int flags) } static krb5_error_code -make_database(krb5_context context, krb5_scache *s, int create) +prepare_stmt(krb5_context context, sqlite3 *db, sqlite3_stmt **stmt, const char *str) { int ret; + ret = sqlite3_prepare_v2(db, str, -1, stmt, NULL); + if (ret != SQLITE_OK) { + krb5_set_error_string(context, "Failed to prepare stmt %s: %s", + str, sqlite3_errmsg(db)); + return ENOENT; + } + return 0; +} + +static krb5_error_code +exec_stmt(krb5_context context, sqlite3 *db, const char *str, + krb5_error_code code) +{ + int ret; + + ret = sqlite3_exec(db, str, NULL, NULL, NULL); + if (ret != SQLITE_OK) { + krb5_set_error_string(context, "Execute %s: %s", str, + sqlite3_errmsg(db)); + return code; + } + return 0; +} + +static krb5_error_code +create_cache(krb5_context context, krb5_scache *s) +{ + int ret; + + sqlite3_bind_text(s->icache, 1, s->name, -1, NULL); + do { + ret = sqlite3_step(s->icache); + } while (ret == SQLITE_ROW); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to add scache: %d", ret); + return KRB5_CC_IO; + } + sqlite3_reset(s->icache); + + s->cid = sqlite3_last_insert_rowid(s->db); + + return 0; +} + +#if 0 +static void +trace(void* ptr, const char * str) +{ + printf("SQL: %s\n", str); +} +#endif + +static krb5_error_code +make_database(krb5_context context, krb5_scache *s) +{ + int created_file = 0; + int ret; + if (s->db) return 0; @@ -184,75 +286,134 @@ make_database(krb5_context context, krb5_scache *s, int create) if (ret) return ret; - sqlite3_exec(s->db, SQL_CMASTER, NULL, NULL, NULL); - sqlite3_exec(s->db, SQL_CCACHE, NULL, NULL, NULL); - ret = sqlite3_exec(s->db, SQL_CCREDS, NULL, NULL, NULL); - if (ret != SQLITE_OK) { - krb5_set_error_string(context, "Failed to create table creds: %s", - sqlite3_errmsg(s->db)); - goto end; - } - sqlite3_exec(s->db, SQL_CPRINCIPALS, NULL, NULL, NULL); + created_file = 1; - sqlite3_exec(s->db, SQL_SETUP_MASTER, NULL, NULL, NULL); + ret = exec_stmt(context, s->db, SQL_CMASTER, KRB5_CC_IO); + if (ret) goto out; + ret = exec_stmt(context, s->db, SQL_CCACHE, KRB5_CC_IO); + if (ret) goto out; + ret = exec_stmt(context, s->db, SQL_CCREDS, KRB5_CC_IO); + if (ret) goto out; + ret = exec_stmt(context, s->db, SQL_CPRINCIPALS, KRB5_CC_IO); + if (ret) goto out; + ret = exec_stmt(context, s->db, SQL_SETUP_MASTER, KRB5_CC_IO); + if (ret) goto out; + + ret = exec_stmt(context, s->db, SQL_TCACHE, KRB5_CC_IO); + if (ret) goto out; + ret = exec_stmt(context, s->db, SQL_TCRED, KRB5_CC_IO); + if (ret) goto out; } - ret = sqlite3_prepare_v2(s->db, SQL_ICRED, -1, &s->icred, NULL); - if (ret != SQLITE_OK) { - krb5_set_error_string(context, "Failed to create icred: %s", - sqlite3_errmsg(s->db)); - goto end; - } - ret = sqlite3_prepare_v2(s->db, SQL_IPRINCIPAL, -1, &s->iprincipal, NULL); - if (ret != SQLITE_OK) { - krb5_set_error_string(context, "Failed to create iprincipal: %s", - sqlite3_errmsg(s->db)); - goto end; - } +#if 0 + sqlite3_trace(s->db, trace, NULL); +#endif - ret = sqlite3_prepare_v2(s->db, SQL_ICACHE, -1, &s->icache, NULL); - if (ret != SQLITE_OK) { - krb5_set_error_string(context, "Failed to create icache: %s", - sqlite3_errmsg(s->db)); - goto end; - } - sqlite3_prepare_v2(s->db, SQL_UCACHE_PRINCIPAL, -1, &s->ucachep, NULL); - sqlite3_prepare_v2(s->db, SQL_SCACHE, -1, &s->scache, NULL); - - if (create) { - /* create inital entry */ - sqlite3_bind_text(s->icache, 1, s->name, -1, NULL); - do { - ret = sqlite3_step(s->icache); - } while (ret == SQLITE_ROW); - if (ret != SQLITE_DONE) { - krb5_set_error_string(context, "Failed to add scache: %d", ret); - goto end; - } - sqlite3_reset(s->icred); - } + ret = prepare_stmt(context, s->db, &s->icred, SQL_ICRED); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->dcred, SQL_DCRED); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->iprincipal, SQL_IPRINCIPAL); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->icache, SQL_ICACHE); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->ucachen, SQL_UCACHE_NAME); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->ucachep, SQL_UCACHE_PRINCIPAL); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->dcache, SQL_DCACHE); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->scache, SQL_SCACHE); + if (ret) goto out; + ret = prepare_stmt(context, s->db, &s->scache_name, SQL_SCACHE_NAME); + if (ret) goto out; return 0; -end: +out: if (s->db) sqlite3_close(s->db); - if (create) + if (created_file) unlink(s->file); - return ENOENT; + return ret; +} + +static krb5_error_code +bind_principal(krb5_context context, + sqlite3 *db, + sqlite3_stmt *stmt, + int col, + krb5_const_principal principal) +{ + krb5_error_code ret; + char *str; + + ret = krb5_unparse_name(context, principal, &str); + if (ret) + return ret; + + ret = sqlite3_bind_text(stmt, col, str, -1, free_krb5); + if (ret != SQLITE_OK) { + krb5_xfree(str); + krb5_set_error_string(context, "bind principal: %s", + sqlite3_errmsg(db)); + return ENOMEM; + } + return 0; +} + +/* + * + */ + +static const char* +scc_get_name(krb5_context context, + krb5_ccache id) +{ + return SCACHE(id)->name; } static krb5_error_code scc_resolve(krb5_context context, krb5_ccache *id, const char *res) { krb5_scache *s; + int ret; s = scc_alloc(res); if (s == NULL) { krb5_set_error_string (context, "malloc: out of memory"); return KRB5_CC_NOMEM; } + + ret = make_database(context, s); + if (ret) { + scc_free(s); + return ret; + } + + ret = sqlite3_bind_text(s->scache_name, 1, s->name, -1, NULL); + if (ret != SQLITE_OK) { + krb5_set_error_string(context, "bind name: %s", sqlite3_errmsg(s->db)); + scc_free(s); + return ENOMEM; + } + + if (sqlite3_step(s->scache_name) == SQLITE_ROW) { + + if (sqlite3_column_type(s->scache_name, 0) != SQLITE_INTEGER) { + sqlite3_reset(s->scache); + krb5_set_error_string(context, "Cache name of wrong type " + "for scache %ld", + (unsigned long)s->name); + return KRB5_CC_END; + } + + s->cid = sqlite3_column_int(s->scache_name, 0); + } else { + s->cid = SCACHE_INVALID_CID; + } + sqlite3_reset(s->scache); (*id)->data.data = s; (*id)->data.length = sizeof(*s); @@ -271,7 +432,6 @@ scc_gen_new(krb5_context context, krb5_ccache *id) krb5_set_error_string (context, "malloc: out of memory"); return KRB5_CC_NOMEM; } - s->cid = 0; (*id)->data.data = s; (*id)->data.length = sizeof(*s); @@ -286,27 +446,54 @@ scc_initialize(krb5_context context, { krb5_scache *s = SCACHE(id); krb5_error_code ret; - char *str; - ret = make_database(context, s, 1); + ret = make_database(context, s); if (ret) return ret; - ret = krb5_unparse_name(context, primary_principal, &str); - if (ret) - return ret; + sqlite3_exec(s->db, "BEGIN TRANSACTION", NULL, NULL, NULL); - sqlite3_bind_text(s->ucachep, 1, str, -1, free_krb5); + if (s->cid == SCACHE_INVALID_CID) { + ret = create_cache(context, s); + if (ret) + goto rollback; + } else { + sqlite3_bind_int(s->dcred, 1, s->cid); + do { + ret = sqlite3_step(s->dcred); + } while (ret == SQLITE_ROW); + sqlite3_reset(s->dcred); + if (ret != SQLITE_DONE) { + krb5_clear_error_string(context); + ret = KRB5_CC_IO; + goto rollback; + } + } + + ret = bind_principal(context, s->db, s->ucachep, 1, primary_principal); + if (ret) + goto rollback; sqlite3_bind_int(s->ucachep, 2, s->cid); - if (sqlite3_step(s->ucachep) != SQLITE_DONE) - ret = EPERM; /* XXX */ - + do { + ret = sqlite3_step(s->ucachep); + } while (ret == SQLITE_ROW); sqlite3_reset(s->ucachep); + if (ret != SQLITE_DONE) { + krb5_clear_error_string(context); + ret = KRB5_CC_IO; + goto rollback; + } - /* XXX delete all entries for this cid, trigger ? */ + sqlite3_exec(s->db, "END TRANSACTION", NULL, NULL, NULL); + + return 0; + +rollback: + sqlite3_exec(s->db, "ROLLBACK", NULL, NULL, NULL); return ret; + } static krb5_error_code @@ -351,18 +538,40 @@ encode_creds(krb5_context context, krb5_creds *creds, krb5_data *data) return ret; } +static krb5_error_code +decode_creds(krb5_context context, const void *data, size_t length, + krb5_creds *creds) +{ + krb5_error_code ret; + krb5_storage *sp; + + sp = krb5_storage_from_readonly_mem(data, length); + if (sp == NULL) { + krb5_set_error_string(context, "malloc: out of memory"); + return ENOMEM; + } + + ret = krb5_ret_creds(sp, creds); + krb5_storage_free(sp); + if (ret) { + krb5_set_error_string(context, "Failed to read credential"); + return ret; + } + return 0; +} + + static krb5_error_code scc_store_cred(krb5_context context, krb5_ccache id, krb5_creds *creds) { - unsigned long long credid; + sqlite_uint64 credid; krb5_scache *s = SCACHE(id); krb5_error_code ret; krb5_data data; - char *str; - ret = make_database(context, s, 0); + ret = make_database(context, s); if (ret) return ret; @@ -370,6 +579,7 @@ scc_store_cred(krb5_context context, if (ret) return ret; + sqlite3_bind_int(s->icred, 1, s->cid); { int kvno = 0; Ticket t; @@ -385,47 +595,58 @@ scc_store_cred(krb5_context context, if(t.enc_part.kvno) kvno = *t.enc_part.kvno; - sqlite3_bind_int(s->icred, 1, kvno); - sqlite3_bind_int(s->icred, 2, t.enc_part.etype); + sqlite3_bind_int(s->icred, 2, kvno); + sqlite3_bind_int(s->icred, 3, t.enc_part.etype); free_Ticket(&t); } - sqlite3_bind_blob(s->icred, 3, data.data, data.length, free_data); + sqlite3_bind_blob(s->icred, 4, data.data, data.length, free_data); sqlite3_exec(s->db, "BEGIN TRANSACTION", NULL, NULL, NULL); - if (sqlite3_step(s->icred) != SQLITE_DONE) - goto rollback; - + do { + ret = sqlite3_step(s->icred); + } while (ret == SQLITE_ROW); sqlite3_reset(s->icred); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to add credential: %s", + sqlite3_errmsg(s->db)); + goto rollback; + } credid = sqlite3_last_insert_rowid(s->db); { - krb5_unparse_name(context, creds->server, &str); - - sqlite3_bind_text(s->iprincipal, 1, str, -1, free_krb5); + bind_principal(context, s->db, s->iprincipal, 1, creds->server); sqlite3_bind_int(s->iprincipal, 2, 1); sqlite3_bind_int(s->iprincipal, 3, credid); - if (sqlite3_step(s->icred) != SQLITE_DONE) + do { + ret = sqlite3_step(s->iprincipal); + } while (ret == SQLITE_ROW); + sqlite3_reset(s->iprincipal); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to add principal: %s", + sqlite3_errmsg(s->db)); goto rollback; - - sqlite3_reset(s->icred); + } } { - krb5_unparse_name(context, creds->client, &str); - - sqlite3_bind_text(s->iprincipal, 1, str, -1, free_krb5); + bind_principal(context, s->db, s->iprincipal, 1, creds->client); sqlite3_bind_int(s->iprincipal, 2, 0); sqlite3_bind_int(s->iprincipal, 3, credid); - if (sqlite3_step(s->icred) != SQLITE_DONE) + do { + ret = sqlite3_step(s->iprincipal); + } while (ret == SQLITE_ROW); + sqlite3_reset(s->iprincipal); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to add principal: %s", + sqlite3_errmsg(s->db)); goto rollback; - - sqlite3_reset(s->icred); + } } sqlite3_exec(s->db, "END TRANSACTION", NULL, NULL, NULL); @@ -433,7 +654,7 @@ scc_store_cred(krb5_context context, return 0; rollback: - + krb5_set_error_string(context, "store credentials: %s", sqlite3_errmsg(s->db)); sqlite3_exec(s->db, "ROLLBACK", NULL, NULL, NULL); return ret; @@ -450,7 +671,7 @@ scc_get_principal(krb5_context context, *principal = NULL; - ret = make_database(context, s, 0); + ret = make_database(context, s); if (ret) return ret; @@ -458,23 +679,26 @@ scc_get_principal(krb5_context context, if (sqlite3_step(s->scache) != SQLITE_ROW) { sqlite3_reset(s->scache); - krb5_set_error_string(context, "No principal for scache %d", s->cid); - return ENOENT; + krb5_set_error_string(context, "No principal for scache %ld", + (unsigned long)s->cid); + return KRB5_CC_END; } - if (sqlite3_column_type(s->scache, 2) == SQLITE_TEXT) { + if (sqlite3_column_type(s->scache, 0) != SQLITE_TEXT) { sqlite3_reset(s->scache); krb5_set_error_string(context, "Principal data of wrong type " - "for scache %d", s->cid); - return ENOENT; + "for scache %ld", + (unsigned long)s->cid); + return KRB5_CC_END; } - str = (const char *)sqlite3_column_text(s->scache, 2); + str = (const char *)sqlite3_column_text(s->scache, 0); if (str == NULL) { sqlite3_reset(s->scache); krb5_set_error_string(context, "Principal not set " - "for scache %d", s->cid); - return ENOENT; + "for scache %ld", + (unsigned long)s->cid); + return KRB5_CC_END; } ret = krb5_parse_name(context, str, principal); @@ -491,13 +715,31 @@ scc_get_first (krb5_context context, { krb5_scache *s = SCACHE(id); krb5_error_code ret; + sqlite3_stmt *stmt; - ret = make_database(context, s, 0); + *cursor = NULL; + + ret = make_database(context, s); if (ret) return ret; - krb5_clear_error_string(context); - return KRB5_CC_END; + if (s->cid == SCACHE_INVALID_CID) + return KRB5_CC_END; + + ret = prepare_stmt(context, s->db, &stmt, + "SELECT cred FROM credentials WHERE cid = ?"); + if (ret) + return ret; + + ret = sqlite3_bind_int(stmt, 1, s->cid); + if (ret) { + sqlite3_finalize(stmt); + krb5_clear_error_string(context); + return KRB5_CC_END; + } + *cursor = stmt; + + return 0; } static krb5_error_code @@ -506,8 +748,37 @@ scc_get_next (krb5_context context, krb5_cc_cursor *cursor, krb5_creds *creds) { + sqlite3_stmt *stmt = *cursor; + krb5_scache *s = SCACHE(id); + krb5_error_code ret; + + const void *data = NULL; + size_t len = 0; + + ret = sqlite3_step(stmt); + if (ret == SQLITE_DONE) { + krb5_clear_error_string(context); + return KRB5_CC_END; + } else if (ret != SQLITE_ROW) { + krb5_set_error_string(context, "Database failed: %s", + sqlite3_errmsg(s->db)); + return KRB5_CC_IO; + } + + if (sqlite3_column_type(stmt, 0) != SQLITE_BLOB) { + krb5_set_error_string(context, "credential of wrong type " + "for scache %ld", + (unsigned long)s->cid); + return KRB5_CC_END; + } + + data = sqlite3_column_blob(stmt, 0); + len = sqlite3_column_bytes(stmt, 0); + + ret = decode_creds(context, data, len, creds); + krb5_clear_error_string(context); - return KRB5_CC_END; + return ret; } static krb5_error_code @@ -515,6 +786,7 @@ scc_end_get (krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor) { + sqlite3_finalize((sqlite3_stmt *)*cursor); return 0; } @@ -527,7 +799,7 @@ scc_remove_cred(krb5_context context, krb5_scache *s = SCACHE(id); krb5_error_code ret; - ret = make_database(context, s, 0); + ret = make_database(context, s); if (ret) return ret; @@ -569,22 +841,64 @@ scc_move(krb5_context context, krb5_ccache from, krb5_ccache to) krb5_scache *sto = SCACHE(to); krb5_error_code ret; - ret = make_database(context, sfrom, 0); + if (strcmp(sfrom->file, sto->file) != 0) { + krb5_set_error_string(context, "Can't handle cross database " + "credential move: %s -> %s", + sfrom->file, sto->file); + return KRB5_CC_BADNAME; + } + + ret = make_database(context, sfrom); if (ret) return ret; - ret = make_database(context, sto, 1); - if (ret) - return ret; + sqlite3_exec(sfrom->db, "BEGIN TRANSACTION", NULL, NULL, NULL); + if (sto->cid != SCACHE_INVALID_CID) { + /* drop old cache entry */ + + sqlite3_bind_int(sfrom->dcache, 1, sto->cid); + do { + ret = sqlite3_step(sfrom->dcache); + } while (ret == SQLITE_ROW); + sqlite3_reset(sfrom->dcache); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to delete old cache: %d", ret); + goto rollback; + } + } + + sqlite3_bind_text(sfrom->ucachen, 1, sto->name, -1, NULL); + sqlite3_bind_int(sfrom->ucachen, 2, sfrom->cid); + + do { + ret = sqlite3_step(sfrom->ucachen); + } while (ret == SQLITE_ROW); + sqlite3_reset(sfrom->ucachen); + if (ret != SQLITE_DONE) { + krb5_set_error_string(context, "Failed to update new cache: %d", ret); + goto rollback; + } + + sto->cid = sfrom->cid; + + sqlite3_exec(sfrom->db, "END TRANSACTION", NULL, NULL, NULL); + + /* free sfrom */ return 0; + +rollback: + + sqlite3_exec(sfrom->db, "ROLLBACK", NULL, NULL, NULL); + + return KRB5_CC_IO; } static krb5_error_code scc_default_name(krb5_context context, char **str) { - asprintf(str, "SCACHE:%s:%s", DEFAULT_SCACHE, SCACHE_DEF_CID_NAME); + asprintf(str, "SCACHE:%s:%s", DEFAULT_SCACHE, SCACHE_DEF_NAME); if (*str == NULL) { krb5_set_error_string(context, "out of memory"); return ENOMEM;