1226031Sstas/*
2226031Sstas * Copyright (c) 2009 Kungliga Tekniska H�gskolan
3226031Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4226031Sstas * All rights reserved.
5226031Sstas *
6226031Sstas * Redistribution and use in source and binary forms, with or without
7226031Sstas * modification, are permitted provided that the following conditions
8226031Sstas * are met:
9226031Sstas *
10226031Sstas * 1. Redistributions of source code must retain the above copyright
11226031Sstas *    notice, this list of conditions and the following disclaimer.
12226031Sstas *
13226031Sstas * 2. Redistributions in binary form must reproduce the above copyright
14226031Sstas *    notice, this list of conditions and the following disclaimer in the
15226031Sstas *    documentation and/or other materials provided with the distribution.
16226031Sstas *
17226031Sstas * 3. Neither the name of the Institute nor the names of its contributors
18226031Sstas *    may be used to endorse or promote products derived from this software
19226031Sstas *    without specific prior written permission.
20226031Sstas *
21226031Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22226031Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23226031Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24226031Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25226031Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26226031Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27226031Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28226031Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29226031Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30226031Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31226031Sstas * SUCH DAMAGE.
32226031Sstas */
33226031Sstas
34226031Sstas#include "hdb_locl.h"
35226031Sstas#include "sqlite3.h"
36226031Sstas
37226031Sstas#define MAX_RETRIES 10
38226031Sstas
39226031Sstastypedef struct hdb_sqlite_db {
40226031Sstas    double version;
41226031Sstas    sqlite3 *db;
42226031Sstas    char *db_file;
43226031Sstas
44226031Sstas    sqlite3_stmt *get_version;
45226031Sstas    sqlite3_stmt *fetch;
46226031Sstas    sqlite3_stmt *get_ids;
47226031Sstas    sqlite3_stmt *add_entry;
48226031Sstas    sqlite3_stmt *add_principal;
49226031Sstas    sqlite3_stmt *add_alias;
50226031Sstas    sqlite3_stmt *delete_aliases;
51226031Sstas    sqlite3_stmt *update_entry;
52226031Sstas    sqlite3_stmt *remove;
53226031Sstas    sqlite3_stmt *get_all_entries;
54226031Sstas
55226031Sstas} hdb_sqlite_db;
56226031Sstas
57226031Sstas/* This should be used to mark updates which make the code incompatible
58226031Sstas * with databases created with previous versions. Don't update it if
59226031Sstas * compatibility is not broken. */
60226031Sstas#define HDBSQLITE_VERSION 0.1
61226031Sstas
62226031Sstas#define _HDBSQLITE_STRINGIFY(x) #x
63226031Sstas#define HDBSQLITE_STRINGIFY(x) _HDBSQLITE_STRINGIFY(x)
64226031Sstas
65226031Sstas#define HDBSQLITE_CREATE_TABLES \
66226031Sstas                 " BEGIN TRANSACTION;" \
67226031Sstas                 " CREATE TABLE Version (number REAL);" \
68226031Sstas                 " INSERT INTO Version (number)" \
69226031Sstas                 " VALUES (" HDBSQLITE_STRINGIFY(HDBSQLITE_VERSION) ");" \
70226031Sstas                 " CREATE TABLE Principal" \
71226031Sstas                 "  (id INTEGER PRIMARY KEY," \
72226031Sstas                 "   principal TEXT UNIQUE NOT NULL," \
73226031Sstas                 "   canonical INTEGER," \
74226031Sstas                 "   entry INTEGER);" \
75226031Sstas                 " CREATE TABLE Entry" \
76226031Sstas                 "  (id INTEGER PRIMARY KEY," \
77226031Sstas                 "   data BLOB);" \
78226031Sstas                 " COMMIT"
79226031Sstas#define HDBSQLITE_CREATE_TRIGGERS \
80226031Sstas                 " CREATE TRIGGER remove_principals AFTER DELETE ON Entry" \
81226031Sstas                 " BEGIN" \
82226031Sstas                 "  DELETE FROM Principal" \
83226031Sstas                 "  WHERE entry = OLD.id;" \
84226031Sstas                 " END"
85226031Sstas#define HDBSQLITE_GET_VERSION \
86226031Sstas                 " SELECT number FROM Version"
87226031Sstas#define HDBSQLITE_FETCH \
88226031Sstas                 " SELECT Entry.data FROM Principal, Entry" \
89226031Sstas                 " WHERE Principal.principal = ? AND" \
90226031Sstas                 "       Entry.id = Principal.entry"
91226031Sstas#define HDBSQLITE_GET_IDS \
92226031Sstas                 " SELECT id, entry FROM Principal" \
93226031Sstas                 " WHERE principal = ?"
94226031Sstas#define HDBSQLITE_ADD_ENTRY \
95226031Sstas                 " INSERT INTO Entry (data) VALUES (?)"
96226031Sstas#define HDBSQLITE_ADD_PRINCIPAL \
97226031Sstas                 " INSERT INTO Principal (principal, entry, canonical)" \
98226031Sstas                 " VALUES (?, last_insert_rowid(), 1)"
99226031Sstas#define HDBSQLITE_ADD_ALIAS \
100226031Sstas                 " INSERT INTO Principal (principal, entry, canonical)" \
101226031Sstas                 " VALUES(?, ?, 0)"
102226031Sstas#define HDBSQLITE_DELETE_ALIASES \
103226031Sstas                 " DELETE FROM Principal" \
104226031Sstas                 " WHERE entry = ? AND canonical = 0"
105226031Sstas#define HDBSQLITE_UPDATE_ENTRY \
106226031Sstas                 " UPDATE Entry SET data = ?" \
107226031Sstas                 " WHERE id = ?"
108226031Sstas#define HDBSQLITE_REMOVE \
109226031Sstas                 " DELETE FROM ENTRY WHERE id = " \
110226031Sstas                 "  (SELECT entry FROM Principal" \
111226031Sstas                 "   WHERE principal = ?)"
112226031Sstas#define HDBSQLITE_GET_ALL_ENTRIES \
113226031Sstas                 " SELECT data FROM Entry"
114226031Sstas
115226031Sstas/**
116226031Sstas * Wrapper around sqlite3_prepare_v2.
117226031Sstas *
118226031Sstas * @param context   The current krb5 context
119226031Sstas * @param statement Where to store the pointer to the statement
120226031Sstas *                  after preparing it
121226031Sstas * @param str       SQL code for the statement
122226031Sstas *
123226031Sstas * @return          0 if OK, an error code if not
124226031Sstas */
125226031Sstasstatic krb5_error_code
126226031Sstashdb_sqlite_prepare_stmt(krb5_context context,
127226031Sstas                        sqlite3 *db,
128226031Sstas                        sqlite3_stmt **statement,
129226031Sstas                        const char *str)
130226031Sstas{
131226031Sstas    int ret, tries = 0;
132226031Sstas
133226031Sstas    ret = sqlite3_prepare_v2(db, str, -1, statement, NULL);
134226031Sstas    while((tries++ < MAX_RETRIES) &&
135226031Sstas	  ((ret == SQLITE_BUSY) ||
136226031Sstas           (ret == SQLITE_IOERR_BLOCKED) ||
137226031Sstas           (ret == SQLITE_LOCKED))) {
138226031Sstas	krb5_warnx(context, "hdb-sqlite: prepare busy");
139226031Sstas        sleep(1);
140226031Sstas        ret = sqlite3_prepare_v2(db, str, -1, statement, NULL);
141226031Sstas    }
142226031Sstas
143226031Sstas    if (ret != SQLITE_OK) {
144226031Sstas        krb5_set_error_message(context, EINVAL,
145226031Sstas			       "Failed to prepare stmt %s: %s",
146226031Sstas			       str, sqlite3_errmsg(db));
147226031Sstas        return EINVAL;
148226031Sstas    }
149226031Sstas
150226031Sstas    return 0;
151226031Sstas}
152226031Sstas
153226031Sstas/**
154226031Sstas * A wrapper around sqlite3_exec.
155226031Sstas *
156226031Sstas * @param context    The current krb5 context
157226031Sstas * @param database   An open sqlite3 database handle
158226031Sstas * @param statement  SQL code to execute
159226031Sstas * @param error_code What to return if the statement fails
160226031Sstas *
161226031Sstas * @return           0 if OK, else error_code
162226031Sstas */
163226031Sstasstatic krb5_error_code
164226031Sstashdb_sqlite_exec_stmt(krb5_context context,
165226031Sstas                     sqlite3 *database,
166226031Sstas                     const char *statement,
167226031Sstas                     krb5_error_code error_code)
168226031Sstas{
169226031Sstas    int ret;
170226031Sstas
171226031Sstas    ret = sqlite3_exec(database, statement, NULL, NULL, NULL);
172226031Sstas
173226031Sstas    while(((ret == SQLITE_BUSY) ||
174226031Sstas           (ret == SQLITE_IOERR_BLOCKED) ||
175226031Sstas           (ret == SQLITE_LOCKED))) {
176226031Sstas	krb5_warnx(context, "hdb-sqlite: exec busy: %d", (int)getpid());
177226031Sstas        sleep(1);
178226031Sstas        ret = sqlite3_exec(database, statement, NULL, NULL, NULL);
179226031Sstas    }
180226031Sstas
181226031Sstas    if (ret != SQLITE_OK && error_code) {
182226031Sstas        krb5_set_error_message(context, error_code,
183226031Sstas			       "Execute %s: %s", statement,
184226031Sstas                              sqlite3_errmsg(database));
185226031Sstas        return error_code;
186226031Sstas    }
187226031Sstas
188226031Sstas    return 0;
189226031Sstas}
190226031Sstas
191226031Sstas/**
192226031Sstas * Opens an sqlite3 database handle to a file, may create the
193226031Sstas * database file depending on flags.
194226031Sstas *
195226031Sstas * @param context The current krb5 context
196226031Sstas * @param db      Heimdal database handle
197226031Sstas * @param flags   Controls whether or not the file may be created,
198226031Sstas *                may be 0 or SQLITE_OPEN_CREATE
199226031Sstas */
200226031Sstasstatic krb5_error_code
201226031Sstashdb_sqlite_open_database(krb5_context context, HDB *db, int flags)
202226031Sstas{
203226031Sstas    int ret;
204226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db*) db->hdb_db;
205226031Sstas
206226031Sstas    ret = sqlite3_open_v2(hsdb->db_file, &hsdb->db,
207226031Sstas                          SQLITE_OPEN_READWRITE | flags, NULL);
208226031Sstas
209226031Sstas    if (ret) {
210226031Sstas        if (hsdb->db) {
211226031Sstas	    ret = ENOENT;
212226031Sstas            krb5_set_error_message(context, ret,
213226031Sstas                                  "Error opening sqlite database %s: %s",
214226031Sstas                                  hsdb->db_file, sqlite3_errmsg(hsdb->db));
215226031Sstas            sqlite3_close(hsdb->db);
216226031Sstas            hsdb->db = NULL;
217226031Sstas        } else
218226031Sstas	    ret = krb5_enomem(context);
219226031Sstas        return ret;
220226031Sstas    }
221226031Sstas
222226031Sstas    return 0;
223226031Sstas}
224226031Sstas
225226031Sstasstatic int
226226031Sstashdb_sqlite_step(krb5_context context, sqlite3 *db, sqlite3_stmt *stmt)
227226031Sstas{
228226031Sstas    int ret;
229226031Sstas
230226031Sstas    ret = sqlite3_step(stmt);
231226031Sstas    while(((ret == SQLITE_BUSY) ||
232226031Sstas           (ret == SQLITE_IOERR_BLOCKED) ||
233226031Sstas           (ret == SQLITE_LOCKED))) {
234226031Sstas	krb5_warnx(context, "hdb-sqlite: step busy: %d", (int)getpid());
235226031Sstas        sleep(1);
236226031Sstas        ret = sqlite3_step(stmt);
237226031Sstas    }
238226031Sstas    return ret;
239226031Sstas}
240226031Sstas
241226031Sstas/**
242226031Sstas * Closes the database and frees memory allocated for statements.
243226031Sstas *
244226031Sstas * @param context The current krb5 context
245226031Sstas * @param db      Heimdal database handle
246226031Sstas */
247226031Sstasstatic krb5_error_code
248226031Sstashdb_sqlite_close_database(krb5_context context, HDB *db)
249226031Sstas{
250226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
251226031Sstas
252226031Sstas    sqlite3_finalize(hsdb->get_version);
253226031Sstas    sqlite3_finalize(hsdb->fetch);
254226031Sstas    sqlite3_finalize(hsdb->get_ids);
255226031Sstas    sqlite3_finalize(hsdb->add_entry);
256226031Sstas    sqlite3_finalize(hsdb->add_principal);
257226031Sstas    sqlite3_finalize(hsdb->add_alias);
258226031Sstas    sqlite3_finalize(hsdb->delete_aliases);
259226031Sstas    sqlite3_finalize(hsdb->update_entry);
260226031Sstas    sqlite3_finalize(hsdb->remove);
261226031Sstas    sqlite3_finalize(hsdb->get_all_entries);
262226031Sstas
263226031Sstas    sqlite3_close(hsdb->db);
264226031Sstas
265226031Sstas    return 0;
266226031Sstas}
267226031Sstas
268226031Sstas/**
269226031Sstas * Opens an sqlite database file and prepares it for use.
270226031Sstas * If the file does not exist it will be created.
271226031Sstas *
272226031Sstas * @param context  The current krb5_context
273226031Sstas * @param db       The heimdal database handle
274226031Sstas * @param filename Where to store the database file
275226031Sstas *
276226031Sstas * @return         0 if everything worked, an error code if not
277226031Sstas */
278226031Sstasstatic krb5_error_code
279226031Sstashdb_sqlite_make_database(krb5_context context, HDB *db, const char *filename)
280226031Sstas{
281226031Sstas    int ret;
282226031Sstas    int created_file = 0;
283226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
284226031Sstas
285226031Sstas    hsdb->db_file = strdup(filename);
286226031Sstas    if(hsdb->db_file == NULL)
287226031Sstas        return ENOMEM;
288226031Sstas
289226031Sstas    ret = hdb_sqlite_open_database(context, db, 0);
290226031Sstas    if (ret) {
291226031Sstas        ret = hdb_sqlite_open_database(context, db, SQLITE_OPEN_CREATE);
292226031Sstas        if (ret) goto out;
293226031Sstas
294226031Sstas        created_file = 1;
295226031Sstas
296226031Sstas        ret = hdb_sqlite_exec_stmt(context, hsdb->db,
297226031Sstas                                   HDBSQLITE_CREATE_TABLES,
298226031Sstas                                   EINVAL);
299226031Sstas        if (ret) goto out;
300226031Sstas
301226031Sstas        ret = hdb_sqlite_exec_stmt(context, hsdb->db,
302226031Sstas                                   HDBSQLITE_CREATE_TRIGGERS,
303226031Sstas                                   EINVAL);
304226031Sstas        if (ret) goto out;
305226031Sstas    }
306226031Sstas
307226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
308226031Sstas                                  &hsdb->get_version,
309226031Sstas                                  HDBSQLITE_GET_VERSION);
310226031Sstas    if (ret) goto out;
311226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
312226031Sstas                                  &hsdb->fetch,
313226031Sstas                                  HDBSQLITE_FETCH);
314226031Sstas    if (ret) goto out;
315226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
316226031Sstas                                  &hsdb->get_ids,
317226031Sstas                                  HDBSQLITE_GET_IDS);
318226031Sstas    if (ret) goto out;
319226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
320226031Sstas                                  &hsdb->add_entry,
321226031Sstas                                  HDBSQLITE_ADD_ENTRY);
322226031Sstas    if (ret) goto out;
323226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
324226031Sstas                                  &hsdb->add_principal,
325226031Sstas                                  HDBSQLITE_ADD_PRINCIPAL);
326226031Sstas    if (ret) goto out;
327226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
328226031Sstas                                  &hsdb->add_alias,
329226031Sstas                                  HDBSQLITE_ADD_ALIAS);
330226031Sstas    if (ret) goto out;
331226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
332226031Sstas                                  &hsdb->delete_aliases,
333226031Sstas                                  HDBSQLITE_DELETE_ALIASES);
334226031Sstas    if (ret) goto out;
335226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
336226031Sstas                                  &hsdb->update_entry,
337226031Sstas                                  HDBSQLITE_UPDATE_ENTRY);
338226031Sstas    if (ret) goto out;
339226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
340226031Sstas                                  &hsdb->remove,
341226031Sstas                                  HDBSQLITE_REMOVE);
342226031Sstas    if (ret) goto out;
343226031Sstas    ret = hdb_sqlite_prepare_stmt(context, hsdb->db,
344226031Sstas                                  &hsdb->get_all_entries,
345226031Sstas                                  HDBSQLITE_GET_ALL_ENTRIES);
346226031Sstas    if (ret) goto out;
347226031Sstas
348226031Sstas    ret = hdb_sqlite_step(context, hsdb->db, hsdb->get_version);
349226031Sstas    if(ret == SQLITE_ROW) {
350226031Sstas        hsdb->version = sqlite3_column_double(hsdb->get_version, 0);
351226031Sstas    }
352226031Sstas    sqlite3_reset(hsdb->get_version);
353226031Sstas    ret = 0;
354226031Sstas
355226031Sstas    if(hsdb->version != HDBSQLITE_VERSION) {
356226031Sstas        ret = EINVAL;
357226031Sstas        krb5_set_error_message(context, ret, "HDBSQLITE_VERSION mismatch");
358226031Sstas    }
359226031Sstas
360226031Sstas    if(ret) goto out;
361226031Sstas
362226031Sstas    return 0;
363226031Sstas
364226031Sstas out:
365226031Sstas    if (hsdb->db)
366226031Sstas        sqlite3_close(hsdb->db);
367226031Sstas    if (created_file)
368226031Sstas        unlink(hsdb->db_file);
369226031Sstas
370226031Sstas    return ret;
371226031Sstas}
372226031Sstas
373226031Sstas/**
374226031Sstas * Retrieves an entry by searching for the given
375226031Sstas * principal in the Principal database table, both
376226031Sstas * for canonical principals and aliases.
377226031Sstas *
378226031Sstas * @param context   The current krb5_context
379226031Sstas * @param db        Heimdal database handle
380226031Sstas * @param principal The principal whose entry to search for
381226031Sstas * @param flags     Currently only for HDB_F_DECRYPT
382226031Sstas * @param kvno	    kvno to fetch is HDB_F_KVNO_SPECIFIED use used
383226031Sstas *
384226031Sstas * @return          0 if everything worked, an error code if not
385226031Sstas */
386226031Sstasstatic krb5_error_code
387226031Sstashdb_sqlite_fetch_kvno(krb5_context context, HDB *db, krb5_const_principal principal,
388226031Sstas		      unsigned flags, krb5_kvno kvno, hdb_entry_ex *entry)
389226031Sstas{
390226031Sstas    int sqlite_error;
391226031Sstas    krb5_error_code ret;
392226031Sstas    char *principal_string;
393226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db*)(db->hdb_db);
394226031Sstas    sqlite3_stmt *fetch = hsdb->fetch;
395226031Sstas    krb5_data value;
396226031Sstas
397226031Sstas    ret = krb5_unparse_name(context, principal, &principal_string);
398226031Sstas    if (ret) {
399226031Sstas        free(principal_string);
400226031Sstas        return ret;
401226031Sstas    }
402226031Sstas
403226031Sstas    sqlite3_bind_text(fetch, 1, principal_string, -1, SQLITE_STATIC);
404226031Sstas
405226031Sstas    sqlite_error = hdb_sqlite_step(context, hsdb->db, fetch);
406226031Sstas    if (sqlite_error != SQLITE_ROW) {
407226031Sstas        if(sqlite_error == SQLITE_DONE) {
408226031Sstas            ret = HDB_ERR_NOENTRY;
409226031Sstas            goto out;
410226031Sstas        } else {
411226031Sstas            ret = EINVAL;
412226031Sstas            krb5_set_error_message(context, ret,
413226031Sstas                                  "sqlite fetch failed: %d",
414226031Sstas                                  sqlite_error);
415226031Sstas            goto out;
416226031Sstas        }
417226031Sstas    }
418226031Sstas
419226031Sstas    value.length = sqlite3_column_bytes(fetch, 0);
420226031Sstas    value.data = (void *) sqlite3_column_blob(fetch, 0);
421226031Sstas
422226031Sstas    ret = hdb_value2entry(context, &value, &entry->entry);
423226031Sstas    if(ret)
424226031Sstas        goto out;
425226031Sstas
426226031Sstas    if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) {
427226031Sstas        ret = hdb_unseal_keys(context, db, &entry->entry);
428226031Sstas        if(ret) {
429226031Sstas           hdb_free_entry(context, entry);
430226031Sstas           goto out;
431226031Sstas        }
432226031Sstas    }
433226031Sstas
434226031Sstas    ret = 0;
435226031Sstas
436226031Sstasout:
437226031Sstas
438226031Sstas    sqlite3_clear_bindings(fetch);
439226031Sstas    sqlite3_reset(fetch);
440226031Sstas
441226031Sstas    free(principal_string);
442226031Sstas
443226031Sstas    return ret;
444226031Sstas}
445226031Sstas
446226031Sstas/**
447226031Sstas * Convenience function to step a prepared statement with no
448226031Sstas * value once.
449226031Sstas *
450226031Sstas * @param context   The current krb5_context
451226031Sstas * @param statement A prepared sqlite3 statement
452226031Sstas *
453226031Sstas * @return        0 if everything worked, an error code if not
454226031Sstas */
455226031Sstasstatic krb5_error_code
456226031Sstashdb_sqlite_step_once(krb5_context context, HDB *db, sqlite3_stmt *statement)
457226031Sstas{
458226031Sstas    int ret;
459226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
460226031Sstas
461226031Sstas    ret = hdb_sqlite_step(context, hsdb->db, statement);
462226031Sstas    sqlite3_clear_bindings(statement);
463226031Sstas    sqlite3_reset(statement);
464226031Sstas
465226031Sstas    return ret;
466226031Sstas}
467226031Sstas
468226031Sstas
469226031Sstas/**
470226031Sstas * Stores an hdb_entry in the database. If flags contains HDB_F_REPLACE
471226031Sstas * a previous entry may be replaced.
472226031Sstas *
473226031Sstas * @param context The current krb5_context
474226031Sstas * @param db      Heimdal database handle
475226031Sstas * @param flags   May currently only contain HDB_F_REPLACE
476226031Sstas * @param entry   The data to store
477226031Sstas *
478226031Sstas * @return        0 if everything worked, an error code if not
479226031Sstas */
480226031Sstasstatic krb5_error_code
481226031Sstashdb_sqlite_store(krb5_context context, HDB *db, unsigned flags,
482226031Sstas                 hdb_entry_ex *entry)
483226031Sstas{
484226031Sstas    int ret;
485226031Sstas    int i;
486226031Sstas    sqlite_int64 entry_id;
487226031Sstas    char *principal_string = NULL;
488226031Sstas    char *alias_string;
489226031Sstas    const HDB_Ext_Aliases *aliases;
490226031Sstas
491226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *)(db->hdb_db);
492226031Sstas    krb5_data value;
493226031Sstas    sqlite3_stmt *get_ids = hsdb->get_ids;
494226031Sstas
495226031Sstas    ret = hdb_sqlite_exec_stmt(context, hsdb->db,
496226031Sstas                               "BEGIN IMMEDIATE TRANSACTION", EINVAL);
497226031Sstas    if(ret != SQLITE_OK) {
498226031Sstas	ret = EINVAL;
499226031Sstas        krb5_set_error_message(context, ret,
500226031Sstas			       "SQLite BEGIN TRANSACTION failed: %s",
501226031Sstas			       sqlite3_errmsg(hsdb->db));
502226031Sstas        goto rollback;
503226031Sstas    }
504226031Sstas
505226031Sstas    ret = krb5_unparse_name(context,
506226031Sstas                            entry->entry.principal, &principal_string);
507226031Sstas    if (ret) {
508226031Sstas        goto rollback;
509226031Sstas    }
510226031Sstas
511226031Sstas    ret = hdb_seal_keys(context, db, &entry->entry);
512226031Sstas    if(ret) {
513226031Sstas        goto rollback;
514226031Sstas    }
515226031Sstas
516226031Sstas    ret = hdb_entry2value(context, &entry->entry, &value);
517226031Sstas    if(ret) {
518226031Sstas        goto rollback;
519226031Sstas    }
520226031Sstas
521226031Sstas    sqlite3_bind_text(get_ids, 1, principal_string, -1, SQLITE_STATIC);
522226031Sstas    ret = hdb_sqlite_step(context, hsdb->db, get_ids);
523226031Sstas
524226031Sstas    if(ret == SQLITE_DONE) { /* No such principal */
525226031Sstas
526226031Sstas        sqlite3_bind_blob(hsdb->add_entry, 1,
527226031Sstas                          value.data, value.length, SQLITE_STATIC);
528226031Sstas        ret = hdb_sqlite_step(context, hsdb->db, hsdb->add_entry);
529226031Sstas        sqlite3_clear_bindings(hsdb->add_entry);
530226031Sstas        sqlite3_reset(hsdb->add_entry);
531226031Sstas        if(ret != SQLITE_DONE)
532226031Sstas            goto rollback;
533226031Sstas
534226031Sstas        sqlite3_bind_text(hsdb->add_principal, 1,
535226031Sstas                          principal_string, -1, SQLITE_STATIC);
536226031Sstas        ret = hdb_sqlite_step(context, hsdb->db, hsdb->add_principal);
537226031Sstas        sqlite3_clear_bindings(hsdb->add_principal);
538226031Sstas        sqlite3_reset(hsdb->add_principal);
539226031Sstas        if(ret != SQLITE_DONE)
540226031Sstas            goto rollback;
541226031Sstas
542226031Sstas        entry_id = sqlite3_column_int64(get_ids, 1);
543226031Sstas
544226031Sstas    } else if(ret == SQLITE_ROW) { /* Found a principal */
545226031Sstas
546226031Sstas        if(! (flags & HDB_F_REPLACE)) /* Not allowed to replace it */
547226031Sstas            goto rollback;
548226031Sstas
549226031Sstas        entry_id = sqlite3_column_int64(get_ids, 1);
550226031Sstas
551226031Sstas        sqlite3_bind_int64(hsdb->delete_aliases, 1, entry_id);
552226031Sstas        ret = hdb_sqlite_step_once(context, db, hsdb->delete_aliases);
553226031Sstas        if(ret != SQLITE_DONE)
554226031Sstas            goto rollback;
555226031Sstas
556226031Sstas        sqlite3_bind_blob(hsdb->update_entry, 1,
557226031Sstas                          value.data, value.length, SQLITE_STATIC);
558226031Sstas        sqlite3_bind_int64(hsdb->update_entry, 2, entry_id);
559226031Sstas        ret = hdb_sqlite_step_once(context, db, hsdb->update_entry);
560226031Sstas        if(ret != SQLITE_DONE)
561226031Sstas            goto rollback;
562226031Sstas
563226031Sstas    } else {
564226031Sstas	/* Error! */
565226031Sstas        goto rollback;
566226031Sstas    }
567226031Sstas
568226031Sstas    ret = hdb_entry_get_aliases(&entry->entry, &aliases);
569226031Sstas    if(ret || aliases == NULL)
570226031Sstas        goto commit;
571226031Sstas
572226031Sstas    for(i = 0; i < aliases->aliases.len; i++) {
573226031Sstas
574226031Sstas        ret = krb5_unparse_name(context, &aliases->aliases.val[i],
575226031Sstas				&alias_string);
576226031Sstas        if (ret) {
577226031Sstas            free(alias_string);
578226031Sstas            goto rollback;
579226031Sstas        }
580226031Sstas
581226031Sstas        sqlite3_bind_text(hsdb->add_alias, 1, alias_string,
582226031Sstas                          -1, SQLITE_STATIC);
583226031Sstas        sqlite3_bind_int64(hsdb->add_alias, 2, entry_id);
584226031Sstas        ret = hdb_sqlite_step_once(context, db, hsdb->add_alias);
585226031Sstas
586226031Sstas        free(alias_string);
587226031Sstas
588226031Sstas        if(ret != SQLITE_DONE)
589226031Sstas            goto rollback;
590226031Sstas    }
591226031Sstas
592226031Sstas    ret = 0;
593226031Sstas
594226031Sstascommit:
595226031Sstas
596226031Sstas    free(principal_string);
597226031Sstas
598226031Sstas    krb5_data_free(&value);
599226031Sstas
600226031Sstas    sqlite3_clear_bindings(get_ids);
601226031Sstas    sqlite3_reset(get_ids);
602226031Sstas
603226031Sstas    ret = hdb_sqlite_exec_stmt(context, hsdb->db, "COMMIT", EINVAL);
604226031Sstas    if(ret != SQLITE_OK)
605226031Sstas	krb5_warnx(context, "hdb-sqlite: COMMIT problem: %d: %s",
606226031Sstas		   ret, sqlite3_errmsg(hsdb->db));
607226031Sstas
608226031Sstas    return ret;
609226031Sstas
610226031Sstasrollback:
611226031Sstas
612226031Sstas    krb5_warnx(context, "hdb-sqlite: store rollback problem: %d: %s",
613226031Sstas	       ret, sqlite3_errmsg(hsdb->db));
614226031Sstas
615226031Sstas    free(principal_string);
616226031Sstas
617226031Sstas    ret = hdb_sqlite_exec_stmt(context, hsdb->db,
618226031Sstas                               "ROLLBACK", EINVAL);
619226031Sstas    return ret;
620226031Sstas}
621226031Sstas
622226031Sstas/**
623226031Sstas * This may be called often by other code, since the BDB backends
624226031Sstas * can not have several open connections. SQLite can handle
625226031Sstas * many processes with open handles to the database file
626226031Sstas * and closing/opening the handle is an expensive operation.
627226031Sstas * Hence, this function does nothing.
628226031Sstas *
629226031Sstas * @param context The current krb5 context
630226031Sstas * @param db      Heimdal database handle
631226031Sstas *
632226031Sstas * @return        Always returns 0
633226031Sstas */
634226031Sstasstatic krb5_error_code
635226031Sstashdb_sqlite_close(krb5_context context, HDB *db)
636226031Sstas{
637226031Sstas    return 0;
638226031Sstas}
639226031Sstas
640226031Sstas/**
641226031Sstas * The opposite of hdb_sqlite_close. Since SQLite accepts
642226031Sstas * many open handles to the database file the handle does not
643226031Sstas * need to be closed, or reopened.
644226031Sstas *
645226031Sstas * @param context The current krb5 context
646226031Sstas * @param db      Heimdal database handle
647226031Sstas * @param flags
648226031Sstas * @param mode_t
649226031Sstas *
650226031Sstas * @return        Always returns 0
651226031Sstas */
652226031Sstasstatic krb5_error_code
653226031Sstashdb_sqlite_open(krb5_context context, HDB *db, int flags, mode_t mode)
654226031Sstas{
655226031Sstas    return 0;
656226031Sstas}
657226031Sstas
658226031Sstas/**
659226031Sstas * Closes the databse and frees all resources.
660226031Sstas *
661226031Sstas * @param context The current krb5 context
662226031Sstas * @param db      Heimdal database handle
663226031Sstas *
664226031Sstas * @return        0 on success, an error code if not
665226031Sstas */
666226031Sstasstatic krb5_error_code
667226031Sstashdb_sqlite_destroy(krb5_context context, HDB *db)
668226031Sstas{
669226031Sstas    int ret;
670226031Sstas    hdb_sqlite_db *hsdb;
671226031Sstas
672226031Sstas    ret = hdb_clear_master_key(context, db);
673226031Sstas
674226031Sstas    hdb_sqlite_close_database(context, db);
675226031Sstas
676226031Sstas    hsdb = (hdb_sqlite_db*)(db->hdb_db);
677226031Sstas
678226031Sstas    free(hsdb->db_file);
679226031Sstas    free(db->hdb_db);
680226031Sstas    free(db);
681226031Sstas
682226031Sstas    return ret;
683226031Sstas}
684226031Sstas
685226031Sstas/*
686226031Sstas * Not sure if this is needed.
687226031Sstas */
688226031Sstasstatic krb5_error_code
689226031Sstashdb_sqlite_lock(krb5_context context, HDB *db, int operation)
690226031Sstas{
691226031Sstas    krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
692226031Sstas			   "lock not implemented");
693226031Sstas    return HDB_ERR_CANT_LOCK_DB;
694226031Sstas}
695226031Sstas
696226031Sstas/*
697226031Sstas * Not sure if this is needed.
698226031Sstas */
699226031Sstasstatic krb5_error_code
700226031Sstashdb_sqlite_unlock(krb5_context context, HDB *db)
701226031Sstas{
702226031Sstas    krb5_set_error_message(context, HDB_ERR_CANT_LOCK_DB,
703226031Sstas			  "unlock not implemented");
704226031Sstas    return HDB_ERR_CANT_LOCK_DB;
705226031Sstas}
706226031Sstas
707226031Sstas/*
708226031Sstas * Should get the next entry, to allow iteration over all entries.
709226031Sstas */
710226031Sstasstatic krb5_error_code
711226031Sstashdb_sqlite_nextkey(krb5_context context, HDB *db, unsigned flags,
712226031Sstas                   hdb_entry_ex *entry)
713226031Sstas{
714226031Sstas    krb5_error_code ret = 0;
715226031Sstas    int sqlite_error;
716226031Sstas    krb5_data value;
717226031Sstas
718226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
719226031Sstas
720226031Sstas    sqlite_error = hdb_sqlite_step(context, hsdb->db, hsdb->get_all_entries);
721226031Sstas    if(sqlite_error == SQLITE_ROW) {
722226031Sstas	/* Found an entry */
723226031Sstas        value.length = sqlite3_column_bytes(hsdb->get_all_entries, 0);
724226031Sstas        value.data = (void *) sqlite3_column_blob(hsdb->get_all_entries, 0);
725226031Sstas        memset(entry, 0, sizeof(*entry));
726226031Sstas        ret = hdb_value2entry(context, &value, &entry->entry);
727226031Sstas    }
728226031Sstas    else if(sqlite_error == SQLITE_DONE) {
729226031Sstas	/* No more entries */
730226031Sstas        ret = HDB_ERR_NOENTRY;
731226031Sstas        sqlite3_reset(hsdb->get_all_entries);
732226031Sstas    }
733226031Sstas    else {
734226031Sstas	/* XXX SQLite error. Should be handled in some way. */
735226031Sstas        ret = EINVAL;
736226031Sstas    }
737226031Sstas
738226031Sstas    return ret;
739226031Sstas}
740226031Sstas
741226031Sstas/*
742226031Sstas * Should get the first entry in the database.
743226031Sstas * What is flags used for?
744226031Sstas */
745226031Sstasstatic krb5_error_code
746226031Sstashdb_sqlite_firstkey(krb5_context context, HDB *db, unsigned flags,
747226031Sstas                    hdb_entry_ex *entry)
748226031Sstas{
749226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
750226031Sstas    krb5_error_code ret;
751226031Sstas
752226031Sstas    sqlite3_reset(hsdb->get_all_entries);
753226031Sstas
754226031Sstas    ret = hdb_sqlite_nextkey(context, db, flags, entry);
755226031Sstas    if(ret)
756226031Sstas        return ret;
757226031Sstas
758226031Sstas    return 0;
759226031Sstas}
760226031Sstas
761226031Sstas/*
762226031Sstas * Renames the database file.
763226031Sstas */
764226031Sstasstatic krb5_error_code
765226031Sstashdb_sqlite_rename(krb5_context context, HDB *db, const char *new_name)
766226031Sstas{
767226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db *) db->hdb_db;
768226031Sstas    int ret;
769226031Sstas
770226031Sstas    krb5_warnx(context, "hdb_sqlite_rename");
771226031Sstas
772226031Sstas    if (strncasecmp(new_name, "sqlite:", 7) == 0)
773226031Sstas	new_name += 7;
774226031Sstas
775226031Sstas    hdb_sqlite_close_database(context, db);
776226031Sstas
777226031Sstas    ret = rename(hsdb->db_file, new_name);
778226031Sstas    free(hsdb->db_file);
779226031Sstas
780226031Sstas    hdb_sqlite_make_database(context, db, new_name);
781226031Sstas
782226031Sstas    return ret;
783226031Sstas}
784226031Sstas
785226031Sstas/*
786226031Sstas * Removes a principal, including aliases and associated entry.
787226031Sstas */
788226031Sstasstatic krb5_error_code
789226031Sstashdb_sqlite_remove(krb5_context context, HDB *db,
790226031Sstas                  krb5_const_principal principal)
791226031Sstas{
792226031Sstas    krb5_error_code ret;
793226031Sstas    char *principal_string;
794226031Sstas    hdb_sqlite_db *hsdb = (hdb_sqlite_db*)(db->hdb_db);
795226031Sstas    sqlite3_stmt *remove = hsdb->remove;
796226031Sstas
797226031Sstas    ret = krb5_unparse_name(context, principal, &principal_string);
798226031Sstas    if (ret) {
799226031Sstas        free(principal_string);
800226031Sstas        return ret;
801226031Sstas    }
802226031Sstas
803226031Sstas    sqlite3_bind_text(remove, 1, principal_string, -1, SQLITE_STATIC);
804226031Sstas
805226031Sstas    ret = hdb_sqlite_step(context, hsdb->db, remove);
806226031Sstas    if (ret != SQLITE_DONE) {
807226031Sstas	ret = EINVAL;
808226031Sstas        krb5_set_error_message(context, ret,
809226031Sstas                              "sqlite remove failed: %d",
810226031Sstas                              ret);
811226031Sstas    } else
812226031Sstas        ret = 0;
813226031Sstas
814226031Sstas    sqlite3_clear_bindings(remove);
815226031Sstas    sqlite3_reset(remove);
816226031Sstas
817226031Sstas    return ret;
818226031Sstas}
819226031Sstas
820226031Sstas/**
821226031Sstas * Create SQLITE object, and creates the on disk database if its doesn't exists.
822226031Sstas *
823226031Sstas * @param context A Kerberos 5 context.
824226031Sstas * @param db a returned database handle.
825226031Sstas * @param argument filename
826226031Sstas *
827226031Sstas * @return        0 on success, an error code if not
828226031Sstas */
829226031Sstas
830226031Sstaskrb5_error_code
831226031Sstashdb_sqlite_create(krb5_context context, HDB **db, const char *argument)
832226031Sstas{
833226031Sstas    krb5_error_code ret;
834226031Sstas    hdb_sqlite_db *hsdb;
835226031Sstas
836226031Sstas    *db = calloc(1, sizeof (**db));
837226031Sstas    if (*db == NULL)
838226031Sstas	return krb5_enomem(context);
839226031Sstas
840226031Sstas    hsdb = (hdb_sqlite_db*) calloc(1, sizeof (*hsdb));
841226031Sstas    if (hsdb == NULL) {
842226031Sstas        free(*db);
843226031Sstas        *db = NULL;
844226031Sstas	return krb5_enomem(context);
845226031Sstas    }
846226031Sstas
847226031Sstas    (*db)->hdb_db = hsdb;
848226031Sstas
849226031Sstas    /* XXX make_database should make sure everything else is freed on error */
850226031Sstas    ret = hdb_sqlite_make_database(context, *db, argument);
851226031Sstas    if (ret) {
852226031Sstas        free((*db)->hdb_db);
853226031Sstas        free(*db);
854226031Sstas
855226031Sstas        return ret;
856226031Sstas    }
857226031Sstas
858226031Sstas    (*db)->hdb_master_key_set = 0;
859226031Sstas    (*db)->hdb_openp = 0;
860226031Sstas    (*db)->hdb_capability_flags = 0;
861226031Sstas
862226031Sstas    (*db)->hdb_open = hdb_sqlite_open;
863226031Sstas    (*db)->hdb_close = hdb_sqlite_close;
864226031Sstas
865226031Sstas    (*db)->hdb_lock = hdb_sqlite_lock;
866226031Sstas    (*db)->hdb_unlock = hdb_sqlite_unlock;
867226031Sstas    (*db)->hdb_firstkey = hdb_sqlite_firstkey;
868226031Sstas    (*db)->hdb_nextkey = hdb_sqlite_nextkey;
869226031Sstas    (*db)->hdb_fetch_kvno = hdb_sqlite_fetch_kvno;
870226031Sstas    (*db)->hdb_store = hdb_sqlite_store;
871226031Sstas    (*db)->hdb_remove = hdb_sqlite_remove;
872226031Sstas    (*db)->hdb_destroy = hdb_sqlite_destroy;
873226031Sstas    (*db)->hdb_rename = hdb_sqlite_rename;
874226031Sstas    (*db)->hdb__get = NULL;
875226031Sstas    (*db)->hdb__put = NULL;
876226031Sstas    (*db)->hdb__del = NULL;
877226031Sstas
878226031Sstas    return 0;
879226031Sstas}
880