summaryrefslogtreecommitdiff
path: root/source4
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2005-11-06 14:15:34 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 13:45:50 -0500
commitfb2394d309f33bdccde3a4e17f6fd994d452b425 (patch)
treef1caf0a70e1fb2729b15f03564d1b68dbaa412e3 /source4
parentdf5b70db2c228bd781d8472440858dcdf502f9f9 (diff)
downloadsamba-fb2394d309f33bdccde3a4e17f6fd994d452b425.tar.gz
samba-fb2394d309f33bdccde3a4e17f6fd994d452b425.tar.bz2
samba-fb2394d309f33bdccde3a4e17f6fd994d452b425.zip
r11536: Add a hook for client-principal access control to hdb-ldb, re-using
the code in auth/auth_sam.c for consistancy. This will also allow us to have one place for a backend directory hook. I will use a very similar hook to add the PAC. Andrew Bartlett (This used to be commit 4315836cd8c94eb8340c4050804face4d0066810)
Diffstat (limited to 'source4')
-rw-r--r--source4/heimdal/kdc/kdc_locl.h9
-rw-r--r--source4/heimdal/kdc/kerberos5.c72
-rw-r--r--source4/heimdal/kdc/misc.c56
-rw-r--r--source4/heimdal/lib/hdb/hdb-protos.h3
-rw-r--r--source4/heimdal/lib/hdb/hdb.c10
-rw-r--r--source4/heimdal/lib/hdb/hdb.h21
-rw-r--r--source4/kdc/hdb-ldb.c104
7 files changed, 231 insertions, 44 deletions
diff --git a/source4/heimdal/kdc/kdc_locl.h b/source4/heimdal/kdc/kdc_locl.h
index b0501abb8d..8658d33b68 100644
--- a/source4/heimdal/kdc/kdc_locl.h
+++ b/source4/heimdal/kdc/kdc_locl.h
@@ -71,10 +71,19 @@ krb5_error_code
_kdc_db_fetch(krb5_context, krb5_kdc_configuration *,
krb5_principal, enum hdb_ent_type, hdb_entry **);
+krb5_error_code
+_kdc_db_fetch_ex(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_principal principal, enum hdb_ent_type ent_type,
+ hdb_entry_ex **h);
+
void
_kdc_free_ent(krb5_context context, hdb_entry *);
void
+_kdc_free_ent_ex(krb5_context context, hdb_entry_ex *ent);
+
+void
loop(krb5_context context, krb5_kdc_configuration *config);
krb5_error_code
diff --git a/source4/heimdal/kdc/kerberos5.c b/source4/heimdal/kdc/kerberos5.c
index 1c02e66211..0df090eef3 100644
--- a/source4/heimdal/kdc/kerberos5.c
+++ b/source4/heimdal/kdc/kerberos5.c
@@ -767,7 +767,8 @@ _kdc_as_rep(krb5_context context,
KDC_REQ_BODY *b = &req->req_body;
AS_REP rep;
KDCOptions f = b->kdc_options;
- hdb_entry *client = NULL, *server = NULL;
+ hdb_entry_ex *client = NULL;
+ hdb_entry *server = NULL;
krb5_enctype cetype, setype;
EncTicketPart et;
EncKDCRepPart ek;
@@ -813,7 +814,7 @@ _kdc_as_rep(krb5_context context,
kdc_log(context, config, 0, "AS-REQ %s from %s for %s",
client_name, from, server_name);
- ret = _kdc_db_fetch(context, config, client_princ, HDB_ENT_TYPE_CLIENT, &client);
+ ret = _kdc_db_fetch_ex(context, config, client_princ, HDB_ENT_TYPE_CLIENT, &client);
if(ret){
kdc_log(context, config, 0, "UNKNOWN -- %s: %s", client_name,
krb5_get_err_text(context, ret));
@@ -830,12 +831,19 @@ _kdc_as_rep(krb5_context context,
}
ret = _kdc_check_flags(context, config,
- client, client_name,
+ &client->entry, client_name,
server, server_name,
TRUE);
if(ret)
goto out;
+ if (client->check_client_access) {
+ ret = client->check_client_access(context, client,
+ b->addresses);
+ if(ret)
+ goto out;
+ }
+
memset(&et, 0, sizeof(et));
memset(&ek, 0, sizeof(ek));
@@ -875,7 +883,7 @@ _kdc_as_rep(krb5_context context,
ret = _kdc_pk_check_client(context,
config,
client_princ,
- client,
+ &client->entry,
pkp,
&client_cert);
if (ret) {
@@ -924,7 +932,7 @@ _kdc_as_rep(krb5_context context,
goto out;
}
- ret = hdb_enctype2key(context, client, enc_data.etype, &pa_key);
+ ret = hdb_enctype2key(context, &client->entry, enc_data.etype, &pa_key);
if(ret){
char *estr;
e_text = "No key matches pa-data";
@@ -974,7 +982,7 @@ _kdc_as_rep(krb5_context context,
krb5_get_err_text(context, ret));
free(str);
- if(hdb_next_enctype2key(context, client,
+ if(hdb_next_enctype2key(context, &client->entry,
enc_data.etype, &pa_key) == 0)
goto try_next_key;
e_text = "Failed to decrypt PA-DATA";
@@ -1030,7 +1038,7 @@ _kdc_as_rep(krb5_context context,
goto out;
}
}else if (config->require_preauth
- || client->flags.require_preauth
+ || client->entry.flags.require_preauth
|| server->flags.require_preauth) {
METHOD_DATA method_data;
PA_DATA *pa;
@@ -1058,10 +1066,10 @@ _kdc_as_rep(krb5_context context,
/* XXX check ret */
if (only_older_enctype_p(req))
- ret = get_pa_etype_info(context, config, &method_data, client,
+ ret = get_pa_etype_info(context, config, &method_data, &client->entry,
b->etype.val, b->etype.len);
/* XXX check ret */
- ret = get_pa_etype_info2(context, config, &method_data, client,
+ ret = get_pa_etype_info2(context, config, &method_data, &client->entry,
b->etype.val, b->etype.len);
@@ -1089,7 +1097,7 @@ _kdc_as_rep(krb5_context context,
}
ret = find_keys(context, config,
- client, server, &ckey, &cetype, &skey, &setype,
+ &client->entry, server, &ckey, &cetype, &skey, &setype,
b->etype.val, b->etype.len);
if(ret) {
kdc_log(context, config, 0, "Server/client has no support for etypes");
@@ -1154,19 +1162,19 @@ _kdc_as_rep(krb5_context context,
rep.pvno = 5;
rep.msg_type = krb_as_rep;
- copy_Realm(&client->principal->realm, &rep.crealm);
+ copy_Realm(&client->entry.principal->realm, &rep.crealm);
if (f.request_anonymous)
make_anonymous_principalname (&rep.cname);
else
_krb5_principal2principalname(&rep.cname,
- client->principal);
+ client->entry.principal);
rep.ticket.tkt_vno = 5;
copy_Realm(&server->principal->realm, &rep.ticket.realm);
_krb5_principal2principalname(&rep.ticket.sname,
server->principal);
et.flags.initial = 1;
- if(client->flags.forwardable && server->flags.forwardable)
+ if(client->entry.flags.forwardable && server->flags.forwardable)
et.flags.forwardable = f.forwardable;
else if (f.forwardable) {
ret = KRB5KDC_ERR_POLICY;
@@ -1174,7 +1182,7 @@ _kdc_as_rep(krb5_context context,
"Ticket may not be forwardable -- %s", client_name);
goto out;
}
- if(client->flags.proxiable && server->flags.proxiable)
+ if(client->entry.flags.proxiable && server->flags.proxiable)
et.flags.proxiable = f.proxiable;
else if (f.proxiable) {
ret = KRB5KDC_ERR_POLICY;
@@ -1182,7 +1190,7 @@ _kdc_as_rep(krb5_context context,
"Ticket may not be proxiable -- %s", client_name);
goto out;
}
- if(client->flags.postdate && server->flags.postdate)
+ if(client->entry.flags.postdate && server->flags.postdate)
et.flags.may_postdate = f.allow_postdate;
else if (f.allow_postdate){
ret = KRB5KDC_ERR_POLICY;
@@ -1220,8 +1228,8 @@ _kdc_as_rep(krb5_context context,
/* be careful not overflowing */
- if(client->max_life)
- t = start + min(t - start, *client->max_life);
+ if(client->entry.max_life)
+ t = start + min(t - start, *client->entry.max_life);
if(server->max_life)
t = start + min(t - start, *server->max_life);
#if 0
@@ -1241,8 +1249,8 @@ _kdc_as_rep(krb5_context context,
t = *b->rtime;
if(t == 0)
t = MAX_TIME;
- if(client->max_renew)
- t = start + min(t - start, *client->max_renew);
+ if(client->entry.max_renew)
+ t = start + min(t - start, *client->entry.max_renew);
if(server->max_renew)
t = start + min(t - start, *server->max_renew);
#if 0
@@ -1278,16 +1286,16 @@ _kdc_as_rep(krb5_context context,
*/
ek.last_req.val = malloc(2 * sizeof(*ek.last_req.val));
ek.last_req.len = 0;
- if (client->pw_end
+ if (client->entry.pw_end
&& (config->kdc_warn_pwexpire == 0
- || kdc_time + config->kdc_warn_pwexpire <= *client->pw_end)) {
+ || kdc_time + config->kdc_warn_pwexpire <= *client->entry.pw_end)) {
ek.last_req.val[ek.last_req.len].lr_type = LR_PW_EXPTIME;
- ek.last_req.val[ek.last_req.len].lr_value = *client->pw_end;
+ ek.last_req.val[ek.last_req.len].lr_value = *client->entry.pw_end;
++ek.last_req.len;
}
- if (client->valid_end) {
+ if (client->entry.valid_end) {
ek.last_req.val[ek.last_req.len].lr_type = LR_ACCT_EXPTIME;
- ek.last_req.val[ek.last_req.len].lr_value = *client->valid_end;
+ ek.last_req.val[ek.last_req.len].lr_value = *client->entry.valid_end;
++ek.last_req.len;
}
if (ek.last_req.len == 0) {
@@ -1296,15 +1304,15 @@ _kdc_as_rep(krb5_context context,
++ek.last_req.len;
}
ek.nonce = b->nonce;
- if (client->valid_end || client->pw_end) {
+ if (client->entry.valid_end || client->entry.pw_end) {
ALLOC(ek.key_expiration);
- if (client->valid_end) {
- if (client->pw_end)
- *ek.key_expiration = min(*client->valid_end, *client->pw_end);
+ if (client->entry.valid_end) {
+ if (client->entry.pw_end)
+ *ek.key_expiration = min(*client->entry.valid_end, *client->entry.pw_end);
else
- *ek.key_expiration = *client->valid_end;
+ *ek.key_expiration = *client->entry.valid_end;
} else
- *ek.key_expiration = *client->pw_end;
+ *ek.key_expiration = *client->entry.pw_end;
} else
ek.key_expiration = NULL;
ek.flags = et.flags;
@@ -1352,7 +1360,7 @@ _kdc_as_rep(krb5_context context,
ret = encode_reply(context, config,
&rep, &et, &ek, setype, server->kvno, &skey->key,
- client->kvno, reply_key, &e_text, reply);
+ client->entry.kvno, reply_key, &e_text, reply);
free_EncTicketPart(&et);
free_EncKDCRepPart(&ek);
out:
@@ -1381,7 +1389,7 @@ _kdc_as_rep(krb5_context context,
krb5_free_principal(context, server_princ);
free(server_name);
if(client)
- _kdc_free_ent(context, client);
+ _kdc_free_ent_ex(context, client);
if(server)
_kdc_free_ent(context, server);
return ret;
diff --git a/source4/heimdal/kdc/misc.c b/source4/heimdal/kdc/misc.c
index 5a251607b6..b14bb50ea5 100644
--- a/source4/heimdal/kdc/misc.c
+++ b/source4/heimdal/kdc/misc.c
@@ -82,3 +82,59 @@ _kdc_free_ent(krb5_context context, hdb_entry *ent)
free (ent);
}
+krb5_error_code
+_kdc_db_fetch_ex(krb5_context context,
+ krb5_kdc_configuration *config,
+ krb5_principal principal, enum hdb_ent_type ent_type,
+ hdb_entry_ex **h)
+{
+ hdb_entry_ex *ent;
+ krb5_error_code ret = HDB_ERR_NOENTRY;
+ int i;
+
+ ent = malloc (sizeof (*ent));
+ if (ent == NULL)
+ return ENOMEM;
+ memset(ent, '\0', sizeof(*ent));
+
+ ent->entry.principal = principal;
+
+ for(i = 0; i < config->num_db; i++) {
+ ret = config->db[i]->hdb_open(context, config->db[i], O_RDONLY, 0);
+ if (ret) {
+ kdc_log(context, config, 0, "Failed to open database: %s",
+ krb5_get_err_text(context, ret));
+ continue;
+ }
+ if (config->db[i]->hdb_fetch_ex) {
+ ret = config->db[i]->hdb_fetch_ex(context,
+ config->db[i],
+ HDB_F_DECRYPT,
+ principal,
+ ent_type,
+ ent);
+ } else {
+ ret = config->db[i]->hdb_fetch(context,
+ config->db[i],
+ HDB_F_DECRYPT,
+ principal,
+ ent_type,
+ &ent->entry);
+ }
+ config->db[i]->hdb_close(context, config->db[i]);
+ if(ret == 0) {
+ *h = ent;
+ return 0;
+ }
+ }
+ free(ent);
+ return ret;
+}
+
+void
+_kdc_free_ent_ex(krb5_context context, hdb_entry_ex *ent)
+{
+ hdb_free_entry_ex (context, ent);
+ free (ent);
+}
+
diff --git a/source4/heimdal/lib/hdb/hdb-protos.h b/source4/heimdal/lib/hdb/hdb-protos.h
index 799f013eba..7557b46bff 100644
--- a/source4/heimdal/lib/hdb/hdb-protos.h
+++ b/source4/heimdal/lib/hdb/hdb-protos.h
@@ -120,6 +120,9 @@ hdb_free_entry (
hdb_entry */*ent*/);
void
+hdb_free_entry_ex(krb5_context context, hdb_entry_ex *ent);
+
+void
hdb_free_key (Key */*key*/);
void
diff --git a/source4/heimdal/lib/hdb/hdb.c b/source4/heimdal/lib/hdb/hdb.c
index c66579fab0..e8161afbc1 100644
--- a/source4/heimdal/lib/hdb/hdb.c
+++ b/source4/heimdal/lib/hdb/hdb.c
@@ -144,6 +144,16 @@ hdb_free_entry(krb5_context context, hdb_entry *ent)
free_hdb_entry(ent);
}
+void
+hdb_free_entry_ex(krb5_context context, hdb_entry_ex *ent)
+{
+ if (ent->free_private) {
+ ent->free_private(context, ent);
+ }
+
+ free_hdb_entry(&ent->entry);
+}
+
krb5_error_code
hdb_foreach(krb5_context context,
HDB *db,
diff --git a/source4/heimdal/lib/hdb/hdb.h b/source4/heimdal/lib/hdb/hdb.h
index fe86f0ae72..41cc03cf36 100644
--- a/source4/heimdal/lib/hdb/hdb.h
+++ b/source4/heimdal/lib/hdb/hdb.h
@@ -54,6 +54,23 @@ enum hdb_ent_type{ HDB_ENT_TYPE_CLIENT, HDB_ENT_TYPE_SERVER, HDB_ENT_TYPE_ANY };
typedef struct hdb_master_key_data *hdb_master_key;
+typedef struct hdb_entry_ex {
+ struct hdb_entry entry;
+ void *private;
+
+ krb5_error_code (*free_private)(krb5_context, struct hdb_entry_ex *);
+ krb5_error_code (*check_client_access)(krb5_context, struct hdb_entry_ex *, HostAddresses *);
+ krb5_error_code (*authz_data_as_req)(krb5_context, struct hdb_entry_ex *,
+ AuthorizationData *in,
+ EncryptionKey *tgtkey,
+ AuthorizationData *out);
+ krb5_error_code (*authz_data_tgs_req)(krb5_context, struct hdb_entry_ex *,
+ AuthorizationData *in,
+ EncryptionKey *tgtkey,
+ EncryptionKey *servicekey,
+ AuthorizationData *out);
+} hdb_entry_ex;
+
typedef struct HDB{
void *hdb_db;
void *hdb_dbc;
@@ -66,6 +83,8 @@ typedef struct HDB{
krb5_error_code (*hdb_close)(krb5_context, struct HDB*);
krb5_error_code (*hdb_fetch)(krb5_context,struct HDB*,unsigned hdb_flags, krb5_const_principal principal,
enum hdb_ent_type ent_type, hdb_entry*);
+ krb5_error_code (*hdb_fetch_ex)(krb5_context,struct HDB*,unsigned hdb_flags, krb5_const_principal principal,
+ enum hdb_ent_type ent_type, hdb_entry_ex*);
krb5_error_code (*hdb_store)(krb5_context,struct HDB*,unsigned,hdb_entry*);
krb5_error_code (*hdb_remove)(krb5_context, struct HDB*, hdb_entry*);
krb5_error_code (*hdb_firstkey)(krb5_context, struct HDB*,
@@ -82,7 +101,7 @@ typedef struct HDB{
krb5_error_code (*hdb_destroy)(krb5_context, struct HDB*);
}HDB;
-#define HDB_INTERFACE_VERSION 2
+#define HDB_INTERFACE_VERSION 3
struct hdb_so_method {
int version;
diff --git a/source4/kdc/hdb-ldb.c b/source4/kdc/hdb-ldb.c
index 80e8cdc74e..87e8273898 100644
--- a/source4/kdc/hdb-ldb.c
+++ b/source4/kdc/hdb-ldb.c
@@ -38,6 +38,7 @@
#include "hdb.h"
#include "lib/ldb/include/ldb.h"
#include "system/iconv.h"
+#include "librpc/gen_ndr/netlogon.h"
enum hdb_ldb_ent_type
{ HDB_LDB_ENT_TYPE_CLIENT, HDB_LDB_ENT_TYPE_SERVER,
@@ -72,6 +73,12 @@ static const char *realm_ref_attrs[] = {
NULL
};
+struct hdb_ldb_private {
+ struct ldb_context *samdb;
+ struct ldb_message **msg;
+ struct ldb_message **realm_ref_msg;
+};
+
static KerberosTime ldb_msg_find_krb5time_ldap_time(struct ldb_message *msg, const char *attr, KerberosTime default_val)
{
const char *tmp;
@@ -611,10 +618,48 @@ static krb5_error_code LDB_rename(krb5_context context, HDB *db, const char *new
return HDB_ERR_DB_INUSE;
}
-static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
- krb5_const_principal principal,
- enum hdb_ent_type ent_type,
- hdb_entry *entry)
+static krb5_error_code hdb_ldb_free_private(krb5_context context, hdb_entry_ex *entry_ex)
+{
+ talloc_free(entry_ex->private);
+ return 0;
+}
+
+static krb5_error_code hdb_ldb_check_client_access(krb5_context context, hdb_entry_ex *entry_ex,
+ HostAddresses *addresses)
+{
+ krb5_error_code ret;
+ NTSTATUS nt_status;
+ TALLOC_CTX *tmp_ctx = talloc_new(entry_ex->private);
+ struct hdb_ldb_private *private = entry_ex->private;
+ char *name, *workstation = NULL;
+ if (!tmp_ctx) {
+ return ENOMEM;
+ }
+
+ ret = krb5_unparse_name(context, entry_ex->entry.principal, &name);
+ if (ret != 0) {
+ talloc_free(tmp_ctx);
+ }
+ nt_status = authsam_account_ok(tmp_ctx,
+ private->samdb,
+ MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT,
+ private->msg,
+ private->realm_ref_msg,
+ workstation,
+ name);
+ free(name);
+
+ if (!NT_STATUS_IS_OK(nt_status)) {
+ return KRB5KDC_ERR_POLICY;
+ }
+ return 0;
+}
+
+
+static krb5_error_code LDB_fetch_ex(krb5_context context, HDB *db, unsigned flags,
+ krb5_const_principal principal,
+ enum hdb_ent_type ent_type,
+ hdb_entry_ex *entry_ex)
{
struct ldb_message **msg = NULL;
struct ldb_message **realm_ref_msg = NULL;
@@ -631,10 +676,6 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
return ENOMEM;
}
- /* Cludge, cludge cludge. If the realm part of krbtgt/realm,
- * is in our db, then direct the caller at our primary
- * krgtgt */
-
switch (ent_type) {
case HDB_ENT_TYPE_CLIENT:
{
@@ -662,7 +703,23 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
ret = LDB_message2entry(context, db, mem_ctx,
principal, ldb_ent_type,
- msg[0], realm_ref_msg[0], entry);
+ msg[0], realm_ref_msg[0], &entry_ex->entry);
+
+ if (ret == 0) {
+ struct hdb_ldb_private *private = talloc(db, struct hdb_ldb_private);
+ if (!private) {
+ hdb_free_entry(context, &entry_ex->entry);
+ ret = ENOMEM;
+ }
+ private->msg = talloc_steal(private, msg);
+ private->realm_ref_msg = talloc_steal(private, realm_ref_msg);
+ private->samdb = (struct ldb_context *)db->hdb_db;
+
+ entry_ex->private = private;
+ entry_ex->free_private = hdb_ldb_free_private;
+ entry_ex->check_client_access = hdb_ldb_check_client_access;
+ }
+
talloc_free(mem_ctx);
return ret;
}
@@ -673,6 +730,10 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
if ((LDB_lookup_realm(context, (struct ldb_context *)db->hdb_db,
mem_ctx, principal->name.name_string.val[1], &realm_fixed_msg) == 0)) {
/* us */
+ /* Cludge, cludge cludge. If the realm part of krbtgt/realm,
+ * is in our db, then direct the caller at our primary
+ * krgtgt */
+
const char *dnsdomain = ldb_msg_find_string(realm_fixed_msg[0], "dnsRoot", NULL);
char *realm_fixed = strupper_talloc(mem_ctx, dnsdomain);
if (!realm_fixed) {
@@ -741,7 +802,7 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
ret = LDB_message2entry(context, db, mem_ctx,
principal, ldb_ent_type,
- msg[0], realm_ref_msg[0], entry);
+ msg[0], realm_ref_msg[0], &entry_ex->entry);
talloc_free(mem_ctx);
return ret;
@@ -784,7 +845,7 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
} else {
ret = LDB_message2entry(context, db, mem_ctx,
principal, ldb_ent_type,
- msg[0], realm_ref_msg[0], entry);
+ msg[0], realm_ref_msg[0], &entry_ex->entry);
if (ret != 0) {
krb5_warnx(context, "LDB_fetch: message2entry failed\n");
}
@@ -794,6 +855,26 @@ static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
return ret;
}
+static krb5_error_code LDB_fetch(krb5_context context, HDB *db, unsigned flags,
+ krb5_const_principal principal,
+ enum hdb_ent_type ent_type,
+ hdb_entry *entry)
+{
+ struct hdb_entry_ex entry_ex;
+ krb5_error_code ret;
+
+ memset(&entry_ex, '\0', sizeof(entry_ex));
+ ret = LDB_fetch_ex(context, db, flags, principal, ent_type, &entry_ex);
+
+ if (ret == 0) {
+ if (entry_ex.free_private) {
+ entry_ex.free_private(context, &entry_ex);
+ }
+ *entry = entry_ex.entry;
+ }
+ return ret;
+}
+
static krb5_error_code LDB_store(krb5_context context, HDB *db, unsigned flags, hdb_entry *entry)
{
return HDB_ERR_DB_INUSE;
@@ -967,6 +1048,7 @@ krb5_error_code hdb_ldb_create(TALLOC_CTX *mem_ctx,
(*db)->hdb_open = LDB_open;
(*db)->hdb_close = LDB_close;
(*db)->hdb_fetch = LDB_fetch;
+ (*db)->hdb_fetch_ex = LDB_fetch_ex;
(*db)->hdb_store = LDB_store;
(*db)->hdb_remove = LDB_remove;
(*db)->hdb_firstkey = LDB_firstkey;