/* ldb database library Copyright (C) Andrew Tridgell 2004 ** NOTE! The following LGPL license applies to the ldb ** library. This does NOT imply that all of Samba is released ** under the LGPL This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Name: ldb * * Component: ldb sqlite backend * * Description: core files for SQLITE3 backend * * Author: Derrell Lipman (based on Andrew Tridgell's LDAP backend) */ #include "includes.h" #include "ldb/include/ldb.h" #include "ldb/include/ldb_private.h" #include "ldb/ldb_sqlite3/ldb_sqlite3.h" #if 0 /* we don't need this right now, but will once we add some backend options */ /* find an option in an option list (a null terminated list of strings) this assumes the list is short. If it ever gets long then we really should do this in some smarter way */ static const char *lsqlite3_option_find(const struct lsqlite3_private *lsqlite3, const char *name) { int i; size_t len = strlen(name); if (!lsqlite3->options) return NULL; for (i=0;lsqlite3->options[i];i++) { if (strncmp(lsqlite3->options[i], name, len) == 0 && lsqlite3->options[i][len] == '=') { return &lsqlite3->options[i][len+1]; } } return NULL; } #endif /* rename a record */ static int lsqlite3_rename(struct ldb_module *module, const char *olddn, const char *newdn) { int column; struct lsqlite3_private *lsqlite3 = module->private_data; /* ignore ltdb specials */ if (olddn[0] == '@' ||newdn[0] == '@') { return 0; } /* Bind old distinguished names */ column = sqlite3_bind_parameter_index(lsqlite3->renameDN, ":oldDN"); if (sqlite3_bind_text(lsqlite3->renameDN, column, olddn, strlen(olddn), SQLITE_STATIC) != SQLITE_OK) { return -1; } /* Bind new distinguished names */ column = sqlite3_bind_parameter_index(lsqlite3->renameDN, ":newDN"); if (sqlite3_bind_text(lsqlite3->renameDN, column, newdn, strlen(newdn), SQLITE_STATIC) != SQLITE_OK) { return -1; } do { lsqlite3->last_rc = sqlite3_step(lsqlite3->renameDN); (void) sqlite3_reset(lsqlite3->renameDN); } while lsqlite3->last_rc == SQLITE3_BUSY; return lsqlite3->last_rc == 0 ? 0 : -1; } /* delete a record */ static int lsqlite3_delete(struct ldb_module *module, const char *dn) { int ret = 0; int column; struct lsqlite3_private *lsqlite3 = module->private_data; /* ignore ltdb specials */ if (dn[0] == '@') { return 0; } /* Bind new distinguished names */ column = sqlite3_bind_parameter_index(lsqlite3->renameDN, ":dn"); if (sqlite3_bind_text(lsqlite3->deleteDN, column, dn, strlen(dn), SQLITE_STATIC) != SQLITE_OK) { return -1; } do { lsqlite3->last_rc = sqlite3_step(lsqlite3->deleteDN); (void) sqlite3_reset(lsqlite3->deleteDN); } while lsqlite3->last_rc == SQLITE3_BUSY; return lsqlite3->last_rc == 0 ? 0 : -1; } /* free a search result */ static int lsqlite3_search_free(struct ldb_module *module, struct ldb_message **res) { talloc_free(res); return 0; } /* add a single set of ldap message values to a ldb_message */ static int lsqlite3_add_msg_attr(struct ldb_context *ldb, struct ldb_message *msg, const char *attr, struct berval **bval) { int count, i; struct ldb_message_element *el; count = ldap_count_values_len(bval); if (count <= 0) { return -1; } el = talloc_realloc(msg, msg->elements, struct ldb_message_element, msg->num_elements + 1); if (!el) { errno = ENOMEM; return -1; } msg->elements = el; el = &msg->elements[msg->num_elements]; el->name = talloc_strdup(msg->elements, attr); if (!el->name) { errno = ENOMEM; return -1; } el->flags = 0; el->num_values = 0; el->values = talloc_array(msg->elements, struct ldb_val, count); if (!el->values) { errno = ENOMEM; return -1; } for (i=0;ivalues[i].data = talloc_memdup(el->values, bval[i]->bv_val, bval[i]->bv_len); if (!el->values[i].data) { return -1; } el->values[i].length = bval[i]->bv_len; el->num_values++; } msg->num_elements++; return 0; } /* search for matching records */ static int lsqlite3_search(struct ldb_module *module, const char *base, enum ldb_scope scope, const char *expression, const char * const *attrs, struct ldb_message ***res) { struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; int count, msg_count; if (base == NULL) { base = ""; } lsqlite3->last_rc = ldap_search_s(lsqlite3->ldap, base, (int)scope, expression, discard_const_p(char *, attrs), 0, &ldapres); if (lsqlite3->last_rc != LDAP_SUCCESS) { return -1; } count = ldap_count_entries(lsqlite3->ldap, ldapres); if (count == -1 || count == 0) { ldap_msgfree(ldapres); return count; } (*res) = talloc_array(lsqlite3, struct ldb_message *, count+1); if (! *res) { ldap_msgfree(ldapres); errno = ENOMEM; return -1; } (*res)[0] = NULL; msg_count = 0; /* loop over all messages */ for (msg=ldap_first_entry(lsqlite3->ldap, ldapres); msg; msg=ldap_next_entry(lsqlite3->ldap, msg)) { BerElement *berptr = NULL; char *attr, *dn; if (msg_count == count) { /* hmm, got too many? */ ldb_debug(ldb, LDB_DEBUG_FATAL, "Fatal: ldap message count inconsistent\n"); break; } (*res)[msg_count] = talloc(*res, struct ldb_message); if (!(*res)[msg_count]) { goto failed; } (*res)[msg_count+1] = NULL; dn = ldap_get_dn(lsqlite3->ldap, msg); if (!dn) { goto failed; } (*res)[msg_count]->dn = talloc_strdup((*res)[msg_count], dn); ldap_memfree(dn); if (!(*res)[msg_count]->dn) { goto failed; } (*res)[msg_count]->num_elements = 0; (*res)[msg_count]->elements = NULL; (*res)[msg_count]->private_data = NULL; /* loop over all attributes */ for (attr=ldap_first_attribute(lsqlite3->ldap, msg, &berptr); attr; attr=ldap_next_attribute(lsqlite3->ldap, msg, berptr)) { struct berval **bval; bval = ldap_get_values_len(lsqlite3->ldap, msg, attr); if (bval) { lsqlite3_add_msg_attr(ldb, (*res)[msg_count], attr, bval); ldap_value_free_len(bval); } ldap_memfree(attr); } if (berptr) ber_free(berptr, 0); msg_count++; } ldap_msgfree(ldapres); return msg_count; failed: if (*res) lsqlite3_search_free(module, *res); return -1; } /* Issue a series of SQL statements to implement the requests in the ldb_message */ static int lsqlite3_msg_to_sql(struct ldb_context *ldb, const struct ldb_message *msg, int modify_existing) { unsigned int i, j; struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; sqlite3_stmt *stmt = NULL; for (i=0;inum_elements;i++) { const struct ldb_message_element *el = &msg->elements[i]; if (! modify_existing) { /* This is a new DN. Bind new distinguished name */ column = sqlite3_bind_parameter_index( lsqlite3->queries.newDN, ":dn"); if (sqlite3_bind_text(lsqlite3->queries.newDN, column, msg->dn, strlen(msg->dn), SQLITE_STATIC) != SQLITE_OK) { return -1; } /* Add this new DN */ do { lsqlite3->last_rc = sqlite3_step(lsqlite3->queries.newDN); (void) sqlite3_reset(lsqlite3->queries.newDN); } while lsqlite3->last_rc == SQLITE_BUSY; if (lsqlite3->last_rc != SQLITE_DONE) { return -1; } dn_id = last_insert_rowid(lsqlite3->sqlite3); stmt = lsqlite3->queries.newAttribute; } else { /* Get the dn_id for the specified DN */ xxx; switch (el->flags & LDB_FLAG_MOD_MASK) { case LDB_FLAG_MOD_ADD: stmt = lsqlite3->queries.addAttrValuePair; break; case LDB_FLAG_MOD_DELETE: stmt = lsqlite3->queries.deleteAttrValuePairs; break; case LDB_FLAG_MOD_REPLACE: stmt = lsqlite3->queries.replaceAttrValuePairs; break; } } for (j=0;jnum_values;j++) { mods[num_mods]->mod_vals.modv_bvals[j] = talloc(mods[num_mods]->mod_vals.modv_bvals, struct berval); if (!mods[num_mods]->mod_vals.modv_bvals[j]) { goto failed; } mods[num_mods]->mod_vals.modv_bvals[j]->bv_val = el->values[j].data; mods[num_mods]->mod_vals.modv_bvals[j]->bv_len = el->values[j].length; } mods[num_mods]->mod_vals.modv_bvals[j] = NULL; num_mods++; } return mods; failed: talloc_free(mods); return NULL; } /* add a record */ static int lsqlite3_add(struct ldb_module *module, const struct ldb_message *msg) { struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; LDAPMod **mods; int ret = 0; /* ignore ltdb specials */ if (msg->dn[0] == '@') { return 0; } mods = lsqlite3_msg_to_mods(ldb, msg, 0); lsqlite3->last_rc = ldap_add_s(lsqlite3->ldap, msg->dn, mods); if (lsqlite3->last_rc != LDAP_SUCCESS) { ret = -1; } talloc_free(mods); return ret; } /* modify a record */ static int lsqlite3_modify(struct ldb_module *module, const struct ldb_message *msg) { struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; LDAPMod **mods; int ret = 0; /* ignore ltdb specials */ if (msg->dn[0] == '@') { return 0; } mods = lsqlite3_msg_to_mods(ldb, msg, 1); lsqlite3->last_rc = ldap_modify_s(lsqlite3->ldap, msg->dn, mods); if (lsqlite3->last_rc != LDAP_SUCCESS) { ret = -1; } talloc_free(mods); return ret; } static int lsqlite3_lock(struct ldb_module *module, const char *lockname) { int ret = 0; struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; if (lockname == NULL) { return -1; } /* If we're already locked, just update lock count */ if (++lsqlite3->lock_count > 1) { return -1; } /* Write-lock (but not read-lock) the database */ lsqlite3->last_rc = sqlite3_step(lsqlite3->begin); /* Ready the compiled statememt for its next use */ (void ) sqlite_reset(lsqlite3->begin); return lsqlite3->last_rc == 0 ? 0 : -1; } static int lsqlite3_unlock(struct ldb_module *module, const char *lockname) { int ret = 0; struct ldb_context *ldb = module->ldb; struct lsqlite3_private *lsqlite3 = module->private_data; if (lockname == NULL) { return -1; } /* If we're not already locked, there's nothing to do */ if (lsqlite3->lock_count == 0) { return 0; } /* Decrement lock count */ if (--lsqlite3->lock_count == 0) { /* Final unlock. Unlock the database */ lsqlite3->last_rc = sqlite3_step(lsqlite3->commit); /* Ready the compiled statememt for its next use */ (void ) sqlite_reset(lsqlite3->commit); } return lsqlite3->last_rc == 0 ? 0 : -1; } /* return extended error information */ static const char *lsqlite3_errstring(struct ldb_module *module) { struct lsqlite3_private *lsqlite3 = module->private_data; return sqlite3_errmsg(lsqlite3->sqlite3); } static const struct ldb_module_ops lsqlite3_ops = { "sqlite", lsqlite3_search, lsqlite3_search_free, lsqlite3_add, lsqlite3_modify, lsqlite3_delete, lsqlite3_rename, lsqlite3_lock, lsqlite3_unlock, lsqlite3_errstring }; static int lsqlite3_destructor(void *p) { struct lsqlite3_private *lsqlite3 = p; (void) sqlite3_close(lsqlite3->sqlite3); return 0; } static int lsqlite3_initialize(lsqlite3_private *lsqlite3, const char *url) { int bNewDatabase = False; char *p; char *pTail; struct stat statbuf; sqlite3_stmt *stmt; const char *schema = " -- ------------------------------------------------------ PRAGMA auto_vacuum=1; -- ------------------------------------------------------ BEGIN EXCLUSIVE; -- ------------------------------------------------------ CREATE TABLE ldb_info AS SELECT 'LDB' AS database_type, '1.0' AS version; CREATE TABLE ldb_distinguished_names ( dn_id INTEGER PRIMARY KEY AUTOINCREMENT, dn TEXT UNIQUE ); CREATE TABLE ldb_object_classes ( class_name TEXT PRIMARY KEY, tree_key TEXT, max_child_num INTEGER ); CREATE TABLE ldb_dn_object_classes ( dn_id INTEGER REFERENCES ldb_distinguished_names, class_name TEXT REFERENCES ldb_object_classes ); CREATE TABLE ldb_attributes ( attr_name TEXT PRIMARY KEY, case_insensitive_p BOOLEAN DEFAULT FALSE, wildcard_p BOOLEAN DEFAULT FALSE, hidden_p BOOLEAN DEFAULT FALSE, integer_p BOOLEAN DEFAULT FALSE ); CREATE TABLE ldb_attr_value_pairs ( dn_id INTEGER REFERENCES ldb_distinguished_names, attr_name TEXT REFERENCES ldb_attributes, attr_value TEXT ); -- ------------------------------------------------------ CREATE TRIGGER ldb_distinguished_names_delete_tr AFTER DELETE ON ldb_distinguished_names FOR EACH ROW BEGIN DELETE FROM ldb_attr_value_pairs WHERE dn_id = old.dn_id; DELETE FROM ldb_dn_object_classes WHERE dn_id = old.dn_id; END; CREATE TRIGGER ldb_attr_value_pairs_insert_tr BEFORE INSERT ON ldb_attr_value_pairs FOR EACH ROW BEGIN INSERT OR IGNORE INTO ldb_attributes (attr_name) VALUES (new.attr_name); END; CREATE TRIGGER ldb_attr_value_pairs_delete_tr AFTER DELETE ON ldb_attr_value_pairs FOR EACH ROW BEGIN DELETE FROM ldb_attributes WHERE (SELECT COUNT(*) FROM ldb_attr_value_pairs WHERE attr_name = old.attr_name) = 0 AND attr_name = old.attr_name; END; -- ------------------------------------------------------ CREATE INDEX ldb_distinguished_names_dn_idx ON ldb_distinguished_names (dn); CREATE INDEX ldb_object_classes_tree_key_idx ON ldb_object_classes (tree_key); CREATE INDEX ldb_dn_object_classes_dn_id_idx ON ldb_dn_object_classes (dn_id); CREATE INDEX ldb_dn_object_classes_class_name_idx ON ldb_dn_object_classes (class_name); CREATE INDEX ldb_attr_value_pairs_dn_id_name_case_idx ON ldb_attr_value_pairs (dn_id, attr_name); CREATE INDEX ldb_attr_value_pairs_dn_id_name_nocase_idx ON ldb_attr_value_pairs (dn_id, attr_name COLLATE NOCASE); -- ------------------------------------------------------ /* all defaults for dn, initially */ INSERT INTO ldb_attributes (attr_name) VALUES ('dn'); /* We need an implicit "top" level object class */ INSERT INTO ldb_object_classes (class_name, tree_key) SELECT 'top', /* next_tree_key(NULL) */ '0001'; -- ------------------------------------------------------ COMMIT; -- ------------------------------------------------------ "; /* Skip protocol indicator of url */ if ((p = strchr(url, ':')) == NULL) { return SQLITE_MISUSE; } else { ++p; } /* * See if we'll be creating a new database, or opening an existing one */ if ((stat(p, &statbuf) < 0 && errno == ENOENT) || statbuf.st_size == 0) { bNewDatabase = True; } /* Try to open the (possibly empty/non-existent) database */ if ((lsqlite3->last_rc = sqlite3_open(p, &lsqlite3->sqlite3)) != SQLITE_SUCCESS) { return ret; } if (bNewDatabase) { /* * Create the database schema */ for (pTail = schema; pTail != NULL; ) { if ((lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, pTail, -1, &stmt, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_step(stmt)) != SQLITE_DONE || (lsqlite3->last_rc = sqlite_finalize(stmt)) != SQLITE_SUCCESS) { (void) sqlite3_close(lsqlite3->sqlite3); return ret; } } } else { /* * Ensure that the database we opened is one of ours */ if ((lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "SELECT COUNT(*) " " FROM sqlite_master " " WHERE type = 'table' " " AND name IN " " (" " 'ldb_info', " " 'ldb_distinguished_names', " " 'ldb_object_classes', " " 'ldb_dn_object_classes', " " 'ldb_attributes', " " 'ldb_attr_value_pairs' " " );", -1, &stmt, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_step(stmt)) != SQLITE_ROW || sqlite3_column_int(stmt, 0) != 6 || (lsqlite3->last_rc = sqlite_finalize(stmt)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "SELECT 1 " " FROM ldb_info " " WHERE database_type = 'LDB' " " AND version = '1.0';", -1, &stmt, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_step(stmt)) != SQLITE_ROW || (lsqlite3->last_rc = sqlite_finalize(stmt)) != SQLITE_SUCCESS) { /* It's not one that we created. See ya! */ (void) sqlite3_close(lsqlite3->sqlite3); return SQLITE_MISUSE; } } /* * Pre-compile each of the queries we'll be using. */ if ((lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "BEGIN IMMEDIATE;", -1, &lsqlite3->queries.begin, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "COMMIT;", -1, &lsqlite3->queries.commit, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "ROLLBACK;", -1, &lsqlite3->queries.rollback, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT INTO ldb_distinguished_names (dn_id, dn) " " VALUES (:dn_id, :dn);", -1, &lsqlite3->queries.newDN, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "UPDATE ldb_distinguished_names " " SET dn = :newDN " " WHERE dn = :oldDN;", -1, &lsqlite3->queries.renameDN, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "DELETE FROM ldb_distinguished_names " " WHERE dn = :dn;", -1, &lsqlite3->queries.deleteDN, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT OR IGNORE INTO ldb_object_classes " " (class_name, tree_key)" " SELECT :class_name, next_tree_key(NULL);", -1, &lsqlite3->queries.newObjectClass, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT OR REPLACE INTO ldb_dn_object_classes " " (dn_id, class_name) " " VALUES (:dn_id, :class_name);", -1, &lsqlite3->queries.assignObjectClass, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT OR IGNORE INTO ldb_attributes (name) " " VALUES (:name);", -1, &lsqlite3->queries.newAttributeUseDefaults, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT OR REPLACE INTO ldb_attributes " " (name, " " case_insensitive_p, " " wildcard_p, " " hidden_p, " " integer_p) " " VALUES (:name, " " :case_insensitive_p, " " :wildcard_p, " " :hidden_p, " " :integer_p);", -1, &lsqlite3->queries.newAttribute, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT INTO ldb_attr_value_pairs " " (dn_id, attr_name, attr_value) " " VALUES (:dn_id, :attr_name, :attr_value);", -1, &lsqlite3->queries.addAttrValuePair, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "UPDATE ldb_attr_value_pairs " " SET attr_value = :attr_value " " WHERE dn_id = :dn_id " " AND attr_name = :attr_name;", -1, &lsqlite3->queries.addAttrValuePair, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "DELETE FROM ldb_attr_value_pairs " " WHERE dn_id = :dn_id " " AND attr_name = :attr_name;" -1, &lsqlite3->queries.deleteAttrValuePair, &pTail)) != SQLITE_SUCCESS || (lsqlite3->last_rc = sqlite3_prepare( lsqlite3->sqlite3, "INSERT OR REPLACE INTO ldb_object_classes " " (class_name, tree_key) " " SELECT :child_class, next_tree_key(:parent_class);" -1, &lsqlite3->queries.insertSubclass, &pTail)) != SQLITE_SUCCESS) { (void) sqlite3_close(lsqlite3->sqlite3); return ret; } return SQLITE_SUCCESS; } /* connect to the database */ struct ldb_context *lsqlite3_connect(const char *url, unsigned int flags, const char *options[]) { struct ldb_context *ldb = NULL; struct lsqlite3_private *lsqlite3 = NULL; int i; ldb = talloc(NULL, struct ldb_context); if (!ldb) { errno = ENOMEM; goto failed; } lsqlite3 = talloc(ldb, struct lsqlite3_private); if (!lsqlite3) { errno = ENOMEM; goto failed; } lsqlite3->sqlite3 = NULL; lsqlite3->options = NULL; lsqlite3->lock_count = 0; lsqlite3->last_rc = lsqlite3_initialize(&lsqlite3->sqlite3, url); if (lsqlite3->last_rc != LDAP_SUCCESS) { goto failed; } talloc_set_destructor(lsqlite3, lsqlite3_destructor); ldb->modules = talloc(ldb, struct ldb_module); if (!ldb->modules) { errno = ENOMEM; goto failed; } ldb->modules->ldb = ldb; ldb->modules->prev = ldb->modules->next = NULL; ldb->modules->private_data = lsqlite3; ldb->modules->ops = &lsqlite3_ops; if (options) { /* take a copy of the options array, so we don't have to rely on the caller keeping it around (it might be dynamic) */ for (i=0;options[i];i++) ; lsqlite3->options = talloc_array(lsqlite3, char *, i+1); if (!lsqlite3->options) { goto failed; } for (i=0;options[i];i++) { lsqlite3->options[i+1] = NULL; lsqlite3->options[i] = talloc_strdup(lsqlite3->options, options[i]); if (!lsqlite3->options[i]) { goto failed; } } } return ldb; failed: if (lsqlite3->sqlite3 != NULL) { (void) sqlite3_close(lsqlite3->sqlite3); } talloc_free(ldb); return NULL; }