mirror of
https://github.com/samba-team/samba.git
synced 2025-01-10 01:18:15 +03:00
77ad9096d0
Signed-off-by: Douglas Bagnall <douglas.bagnall@catalyst.net.nz> Reviewed-by: Andreas Schneider <asn@samba.org>
1367 lines
33 KiB
C
1367 lines
33 KiB
C
/*
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* Name: ldb
|
|
*
|
|
* Component: ldb pack/unpack
|
|
*
|
|
* Description: pack/unpack routines for ldb messages as key/value blobs
|
|
*
|
|
* Author: Andrew Tridgell
|
|
*/
|
|
|
|
#include "ldb_private.h"
|
|
|
|
/*
|
|
* These macros are from byte_array.h via libssh
|
|
* TODO: This will be replaced with use of the byte_array.h header when it
|
|
* becomes available.
|
|
*
|
|
* Macros for handling integer types in byte arrays
|
|
*
|
|
* This file is originally from the libssh.org project
|
|
*
|
|
* Copyright (c) 2018 Andreas Schneider <asn@cryptomilk.org>
|
|
*
|
|
* 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.1 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
#define _DATA_BYTE_CONST(data, pos) \
|
|
((uint8_t)(((const uint8_t *)(data))[(pos)]))
|
|
#define PULL_LE_U8(data, pos) \
|
|
(_DATA_BYTE_CONST(data, pos))
|
|
#define PULL_LE_U16(data, pos) \
|
|
((uint16_t)PULL_LE_U8(data, pos) |\
|
|
((uint16_t)(PULL_LE_U8(data, (pos) + 1))) << 8)
|
|
#define PULL_LE_U32(data, pos) \
|
|
((uint32_t)(PULL_LE_U16(data, pos) |\
|
|
((uint32_t)PULL_LE_U16(data, (pos) + 2)) << 16))
|
|
|
|
#define _DATA_BYTE(data, pos) \
|
|
(((uint8_t *)(data))[(pos)])
|
|
#define PUSH_LE_U8(data, pos, val) \
|
|
(_DATA_BYTE(data, pos) = ((uint8_t)(val)))
|
|
#define PUSH_LE_U16(data, pos, val) \
|
|
(PUSH_LE_U8((data), (pos), (uint8_t)((uint16_t)(val) & 0xff)),\
|
|
PUSH_LE_U8((data), (pos) + 1,\
|
|
(uint8_t)((uint16_t)(val) >> 8)))
|
|
#define PUSH_LE_U32(data, pos, val) \
|
|
(PUSH_LE_U16((data), (pos), (uint16_t)((uint32_t)(val) & 0xffff)),\
|
|
PUSH_LE_U16((data), (pos) + 2, (uint16_t)((uint32_t)(val) >> 16)))
|
|
|
|
#define U32_LEN 4
|
|
#define U16_LEN 2
|
|
#define U8_LEN 1
|
|
#define NULL_PAD_BYTE_LEN 1
|
|
|
|
static int attribute_storable_values(const struct ldb_message_element *el)
|
|
{
|
|
if (el->num_values == 0) return 0;
|
|
|
|
if (ldb_attr_cmp(el->name, "distinguishedName") == 0) return 0;
|
|
|
|
return el->num_values;
|
|
}
|
|
|
|
static int ldb_pack_data_v1(struct ldb_context *ldb,
|
|
const struct ldb_message *message,
|
|
struct ldb_val *data)
|
|
{
|
|
unsigned int i, j, real_elements=0;
|
|
size_t size, dn_len, attr_len, value_len;
|
|
const char *dn;
|
|
uint8_t *p;
|
|
size_t len;
|
|
|
|
dn = ldb_dn_get_linearized(message->dn);
|
|
if (dn == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/* work out how big it needs to be */
|
|
size = U32_LEN * 2 + NULL_PAD_BYTE_LEN;
|
|
|
|
dn_len = strlen(dn);
|
|
if (size + dn_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += dn_len;
|
|
|
|
/*
|
|
* First calculate the buffer size we need, and check for
|
|
* overflows
|
|
*/
|
|
for (i=0;i<message->num_elements;i++) {
|
|
if (attribute_storable_values(&message->elements[i]) == 0) {
|
|
continue;
|
|
}
|
|
|
|
real_elements++;
|
|
|
|
if (size + U32_LEN + NULL_PAD_BYTE_LEN < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += U32_LEN + NULL_PAD_BYTE_LEN;
|
|
|
|
attr_len = strlen(message->elements[i].name);
|
|
if (size + attr_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += attr_len;
|
|
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
if (size + U32_LEN + NULL_PAD_BYTE_LEN < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += U32_LEN + NULL_PAD_BYTE_LEN;
|
|
|
|
value_len = message->elements[i].values[j].length;
|
|
if (size + value_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += value_len;
|
|
}
|
|
}
|
|
|
|
/* allocate it */
|
|
data->data = talloc_array(ldb, uint8_t, size);
|
|
if (!data->data) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
data->length = size;
|
|
|
|
p = data->data;
|
|
PUSH_LE_U32(p, 0, LDB_PACKING_FORMAT);
|
|
p += U32_LEN;
|
|
PUSH_LE_U32(p, 0, real_elements);
|
|
p += U32_LEN;
|
|
|
|
/* the dn needs to be packed so we can be case preserving
|
|
while hashing on a case folded dn */
|
|
len = dn_len;
|
|
memcpy(p, dn, len+NULL_PAD_BYTE_LEN);
|
|
p += len + NULL_PAD_BYTE_LEN;
|
|
|
|
for (i=0;i<message->num_elements;i++) {
|
|
if (attribute_storable_values(&message->elements[i]) == 0) {
|
|
continue;
|
|
}
|
|
len = strlen(message->elements[i].name);
|
|
memcpy(p, message->elements[i].name, len+NULL_PAD_BYTE_LEN);
|
|
p += len + NULL_PAD_BYTE_LEN;
|
|
PUSH_LE_U32(p, 0, message->elements[i].num_values);
|
|
p += U32_LEN;
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
PUSH_LE_U32(p, 0,
|
|
message->elements[i].values[j].length);
|
|
p += U32_LEN;
|
|
memcpy(p, message->elements[i].values[j].data,
|
|
message->elements[i].values[j].length);
|
|
p[message->elements[i].values[j].length] = 0;
|
|
p += message->elements[i].values[j].length +
|
|
NULL_PAD_BYTE_LEN;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* New pack version designed based on performance profiling of version 1.
|
|
* The approach is to separate value data from the rest of the record's data.
|
|
* This improves performance because value data is not needed during unpacking
|
|
* or filtering of the message's attribute list. During filtering we only copy
|
|
* attributes which are present in the attribute list, however at the parse
|
|
* stage we need to point to all attributes as they may be referenced in the
|
|
* search expression.
|
|
* With this new format, we don't lose time loading data (eg via
|
|
* talloc_memdup()) that is never needed (for the vast majority of attributes
|
|
* are are never found in either the search expression or attribute list).
|
|
* Additional changes include adding a canonicalized DN (for later
|
|
* optimizations) and variable width length fields for faster unpacking.
|
|
* The pack and unpack performance improvement is tested in the torture
|
|
* test torture_ldb_pack_format_perf.
|
|
*
|
|
* Layout:
|
|
*
|
|
* Version (4 bytes)
|
|
* Number of Elements (4 bytes)
|
|
* DN length (4 bytes)
|
|
* DN with null terminator (DN length + 1 bytes)
|
|
* Canonicalized DN length (4 bytes)
|
|
* Canonicalized DN with null terminator (Canonicalized DN length + 1 bytes)
|
|
* Number of bytes from here to value data section (4 bytes)
|
|
* # For each element:
|
|
* Element name length (4 bytes)
|
|
* Element name with null terminator (Element name length + 1 bytes)
|
|
* Number of values (4 bytes)
|
|
* Width of value lengths
|
|
* # For each value:
|
|
* Value data length (#bytes given by width field above)
|
|
* # For each element:
|
|
* # For each value:
|
|
* Value data (#bytes given by corresponding length above)
|
|
*/
|
|
static int ldb_pack_data_v2(struct ldb_context *ldb,
|
|
const struct ldb_message *message,
|
|
struct ldb_val *data)
|
|
{
|
|
unsigned int i, j, real_elements=0;
|
|
size_t size, dn_len, dn_canon_len, attr_len, value_len;
|
|
const char *dn, *dn_canon;
|
|
uint8_t *p, *q;
|
|
size_t len;
|
|
size_t max_val_len;
|
|
uint8_t val_len_width;
|
|
|
|
/*
|
|
* First half of this function will calculate required size for
|
|
* packed data. Initial size is 20 = 5 * 4. 5 fixed fields are:
|
|
* version, num elements, dn len, canon dn len, attr section len
|
|
*/
|
|
size = U32_LEN * 5;
|
|
|
|
/*
|
|
* Get linearized and canonicalized form of the DN and add the lengths
|
|
* of each to size, plus 1 for null terminator.
|
|
*/
|
|
dn = ldb_dn_get_linearized(message->dn);
|
|
if (dn == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
dn_len = strlen(dn) + NULL_PAD_BYTE_LEN;
|
|
if (size + dn_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += dn_len;
|
|
|
|
if (ldb_dn_is_special(message->dn)) {
|
|
dn_canon_len = NULL_PAD_BYTE_LEN;
|
|
dn_canon = discard_const_p(char, "\0");
|
|
} else {
|
|
dn_canon = ldb_dn_canonical_string(message->dn, message->dn);
|
|
if (dn_canon == NULL) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
dn_canon_len = strlen(dn_canon) + NULL_PAD_BYTE_LEN;
|
|
if (size + dn_canon_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
size += dn_canon_len;
|
|
|
|
/* Add the size required by each element */
|
|
for (i=0;i<message->num_elements;i++) {
|
|
if (attribute_storable_values(&message->elements[i]) == 0) {
|
|
continue;
|
|
}
|
|
|
|
real_elements++;
|
|
|
|
/*
|
|
* Add length of element name + 9 for:
|
|
* 1 for null terminator
|
|
* 4 for element name length field
|
|
* 4 for number of values field
|
|
*/
|
|
attr_len = strlen(message->elements[i].name);
|
|
if (size + attr_len + U32_LEN * 2 + NULL_PAD_BYTE_LEN < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += attr_len + U32_LEN * 2 + NULL_PAD_BYTE_LEN;
|
|
|
|
/*
|
|
* Find the max value length, so we can calculate the width
|
|
* required for the value length fields.
|
|
*/
|
|
max_val_len = 0;
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
value_len = message->elements[i].values[j].length;
|
|
if (value_len > max_val_len) {
|
|
max_val_len = value_len;
|
|
}
|
|
|
|
if (size + value_len + NULL_PAD_BYTE_LEN < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += value_len + NULL_PAD_BYTE_LEN;
|
|
}
|
|
|
|
if (max_val_len <= UCHAR_MAX) {
|
|
val_len_width = U8_LEN;
|
|
} else if (max_val_len <= USHRT_MAX) {
|
|
val_len_width = U16_LEN;
|
|
} else if (max_val_len <= UINT_MAX) {
|
|
val_len_width = U32_LEN;
|
|
} else {
|
|
errno = EMSGSIZE;
|
|
return -1;
|
|
}
|
|
|
|
/* Total size required for val lengths (re-using variable) */
|
|
max_val_len = (val_len_width*message->elements[i].num_values);
|
|
|
|
/* Add one for storing the width */
|
|
max_val_len += U8_LEN;
|
|
if (size + max_val_len < size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
size += max_val_len;
|
|
}
|
|
|
|
/* Allocate */
|
|
data->data = talloc_array(ldb, uint8_t, size);
|
|
if (!data->data) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
data->length = size;
|
|
|
|
/* Packing format version and number of element */
|
|
p = data->data;
|
|
PUSH_LE_U32(p, 0, LDB_PACKING_FORMAT_V2);
|
|
p += U32_LEN;
|
|
PUSH_LE_U32(p, 0, real_elements);
|
|
p += U32_LEN;
|
|
|
|
/* Pack DN and Canonicalized DN */
|
|
PUSH_LE_U32(p, 0, dn_len-NULL_PAD_BYTE_LEN);
|
|
p += U32_LEN;
|
|
memcpy(p, dn, dn_len);
|
|
p += dn_len;
|
|
|
|
PUSH_LE_U32(p, 0, dn_canon_len-NULL_PAD_BYTE_LEN);
|
|
p += U32_LEN;
|
|
memcpy(p, dn_canon, dn_canon_len);
|
|
p += dn_canon_len;
|
|
|
|
/*
|
|
* Save pointer at this point and leave a U32_LEN gap for
|
|
* storing the size of the attribute names and value lengths
|
|
* section
|
|
*/
|
|
q = p;
|
|
p += U32_LEN;
|
|
|
|
for (i=0;i<message->num_elements;i++) {
|
|
if (attribute_storable_values(&message->elements[i]) == 0) {
|
|
continue;
|
|
}
|
|
|
|
/* Length of el name */
|
|
len = strlen(message->elements[i].name);
|
|
PUSH_LE_U32(p, 0, len);
|
|
p += U32_LEN;
|
|
|
|
/*
|
|
* Even though we have the element name's length, put a null
|
|
* terminator at the end so if any code uses the name
|
|
* directly, it'll be safe to do things requiring null
|
|
* termination like strlen
|
|
*/
|
|
memcpy(p, message->elements[i].name, len+NULL_PAD_BYTE_LEN);
|
|
p += len + NULL_PAD_BYTE_LEN;
|
|
/* Num values */
|
|
PUSH_LE_U32(p, 0, message->elements[i].num_values);
|
|
p += U32_LEN;
|
|
|
|
/*
|
|
* Calculate value length width again. It's faster to
|
|
* calculate it again than do the array management to
|
|
* store the result during size calculation.
|
|
*/
|
|
max_val_len = 0;
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
value_len = message->elements[i].values[j].length;
|
|
if (value_len > max_val_len) {
|
|
max_val_len = value_len;
|
|
}
|
|
}
|
|
|
|
if (max_val_len <= UCHAR_MAX) {
|
|
val_len_width = U8_LEN;
|
|
} else if (max_val_len <= USHRT_MAX) {
|
|
val_len_width = U16_LEN;
|
|
} else if (max_val_len <= UINT_MAX) {
|
|
val_len_width = U32_LEN;
|
|
} else {
|
|
errno = EMSGSIZE;
|
|
return -1;
|
|
}
|
|
|
|
/* Pack the width */
|
|
*p = val_len_width & 0xFF;
|
|
p += U8_LEN;
|
|
|
|
/*
|
|
* Pack each value's length using the minimum number of bytes
|
|
* required, which we just calculated. We repeat the loop
|
|
* for each case here so the compiler can inline code.
|
|
*/
|
|
if (val_len_width == U8_LEN) {
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
PUSH_LE_U8(p, 0,
|
|
message->elements[i].values[j].length);
|
|
p += U8_LEN;
|
|
}
|
|
} else if (val_len_width == U16_LEN) {
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
PUSH_LE_U16(p, 0,
|
|
message->elements[i].values[j].length);
|
|
p += U16_LEN;
|
|
}
|
|
} else if (val_len_width == U32_LEN) {
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
PUSH_LE_U32(p, 0,
|
|
message->elements[i].values[j].length);
|
|
p += U32_LEN;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We've finished packing the attr names and value lengths
|
|
* section, so store the size in the U32_LEN gap we left
|
|
* earlier
|
|
*/
|
|
PUSH_LE_U32(q, 0, p-q);
|
|
|
|
/* Now pack the values */
|
|
for (i=0;i<message->num_elements;i++) {
|
|
if (attribute_storable_values(&message->elements[i]) == 0) {
|
|
continue;
|
|
}
|
|
for (j=0;j<message->elements[i].num_values;j++) {
|
|
memcpy(p, message->elements[i].values[j].data,
|
|
message->elements[i].values[j].length);
|
|
|
|
/*
|
|
* Even though we have the data length, put a null
|
|
* terminator at the end of each value's data so if
|
|
* any code uses the data directly, it'll be safe to
|
|
* do things requiring null termination like strlen.
|
|
*/
|
|
p[message->elements[i].values[j].length] = 0;
|
|
p += message->elements[i].values[j].length +
|
|
NULL_PAD_BYTE_LEN;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we didn't end up at the end of the data here, something has
|
|
* gone very wrong.
|
|
*/
|
|
if (p != data->data + size) {
|
|
errno = ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
pack a ldb message into a linear buffer in a ldb_val
|
|
|
|
note that this routine avoids saving elements with zero values,
|
|
as these are equivalent to having no element
|
|
|
|
caller frees the data buffer after use
|
|
*/
|
|
int ldb_pack_data(struct ldb_context *ldb,
|
|
const struct ldb_message *message,
|
|
struct ldb_val *data,
|
|
uint32_t pack_format_version) {
|
|
|
|
if (pack_format_version == LDB_PACKING_FORMAT) {
|
|
return ldb_pack_data_v1(ldb, message, data);
|
|
} else if (pack_format_version == LDB_PACKING_FORMAT_V2) {
|
|
return ldb_pack_data_v2(ldb, message, data);
|
|
} else {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unpack a ldb message from a linear buffer in ldb_val
|
|
*/
|
|
static int ldb_unpack_data_flags_v1(struct ldb_context *ldb,
|
|
const struct ldb_val *data,
|
|
struct ldb_message *message,
|
|
unsigned int flags,
|
|
unsigned format)
|
|
{
|
|
uint8_t *p;
|
|
size_t remaining;
|
|
size_t dn_len;
|
|
unsigned int i, j;
|
|
unsigned int nelem = 0;
|
|
size_t len;
|
|
struct ldb_val *ldb_val_single_array = NULL;
|
|
|
|
message->elements = NULL;
|
|
|
|
p = data->data;
|
|
|
|
/* Format (U32, already read) + U32 for num_elements */
|
|
if (data->length < U32_LEN * 2) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
/* Skip first 4 bytes, format already read */
|
|
p += U32_LEN;
|
|
message->num_elements = PULL_LE_U32(p, 0);
|
|
p += U32_LEN;
|
|
|
|
remaining = data->length - U32_LEN * 2;
|
|
|
|
switch (format) {
|
|
case LDB_PACKING_FORMAT_NODN:
|
|
message->dn = NULL;
|
|
break;
|
|
|
|
case LDB_PACKING_FORMAT:
|
|
/*
|
|
* With this check, we know that the DN at p is \0
|
|
* terminated.
|
|
*/
|
|
dn_len = strnlen((char *)p, remaining);
|
|
if (dn_len == remaining) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_DN) {
|
|
message->dn = NULL;
|
|
} else {
|
|
struct ldb_val blob;
|
|
blob.data = discard_const_p(uint8_t, p);
|
|
blob.length = dn_len;
|
|
message->dn = ldb_dn_from_ldb_val(message, ldb, &blob);
|
|
if (message->dn == NULL) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
/*
|
|
* Redundant: by definition, remaining must be more
|
|
* than one less than dn_len, as otherwise it would be
|
|
* == dn_len
|
|
*/
|
|
if (remaining < dn_len + NULL_PAD_BYTE_LEN) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
remaining -= dn_len + NULL_PAD_BYTE_LEN;
|
|
p += dn_len + NULL_PAD_BYTE_LEN;
|
|
break;
|
|
|
|
default:
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_ATTRS) {
|
|
message->num_elements = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (message->num_elements == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (message->num_elements > remaining / 6) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
message->elements = talloc_zero_array(message, struct ldb_message_element,
|
|
message->num_elements);
|
|
if (!message->elements) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* In typical use, most values are single-valued. This makes
|
|
* it quite expensive to allocate an array of ldb_val for each
|
|
* of these, just to then hold the pointer to the data buffer
|
|
* So with LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC we allocate this
|
|
* ahead of time and use it for the single values where possible.
|
|
* (This is used in the normal search case, but not in the
|
|
* index case because of caller requirements).
|
|
*/
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) {
|
|
ldb_val_single_array = talloc_array(message->elements, struct ldb_val,
|
|
message->num_elements);
|
|
if (ldb_val_single_array == NULL) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
for (i=0;i<message->num_elements;i++) {
|
|
const char *attr = NULL;
|
|
size_t attr_len;
|
|
struct ldb_message_element *element = NULL;
|
|
|
|
/*
|
|
* Sanity check: Element must be at least the size of empty
|
|
* attr name and value and NULL terms for each.
|
|
*/
|
|
if (remaining < U32_LEN * 2 + NULL_PAD_BYTE_LEN * 2) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* With this check, we know that the attribute name at
|
|
* p is \0 terminated.
|
|
*/
|
|
attr_len = strnlen((char *)p, remaining-6);
|
|
if (attr_len == remaining-6) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
if (attr_len == 0) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
attr = (char *)p;
|
|
|
|
element = &message->elements[nelem];
|
|
element->name = attr;
|
|
element->flags = 0;
|
|
|
|
if (remaining < (attr_len + NULL_PAD_BYTE_LEN)) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
remaining -= attr_len + NULL_PAD_BYTE_LEN;
|
|
p += attr_len + NULL_PAD_BYTE_LEN;
|
|
element->num_values = PULL_LE_U32(p, 0);
|
|
element->values = NULL;
|
|
if ((flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) && element->num_values == 1) {
|
|
element->values = &ldb_val_single_array[nelem];
|
|
element->flags |= LDB_FLAG_INTERNAL_SHARED_VALUES;
|
|
} else if (element->num_values != 0) {
|
|
element->values = talloc_array(message->elements,
|
|
struct ldb_val,
|
|
element->num_values);
|
|
if (!element->values) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
p += U32_LEN;
|
|
if (remaining < U32_LEN) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
remaining -= U32_LEN;
|
|
for (j = 0; j < element->num_values; j++) {
|
|
/*
|
|
* Sanity check: Value must be at least the size of
|
|
* empty val and NULL terminator.
|
|
*/
|
|
if (remaining < U32_LEN + NULL_PAD_BYTE_LEN) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
remaining -= U32_LEN + NULL_PAD_BYTE_LEN;
|
|
|
|
len = PULL_LE_U32(p, 0);
|
|
if (remaining < len) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
if (len + NULL_PAD_BYTE_LEN < len) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
element->values[j].length = len;
|
|
element->values[j].data = p + U32_LEN;
|
|
remaining -= len;
|
|
p += len + U32_LEN + NULL_PAD_BYTE_LEN;
|
|
}
|
|
nelem++;
|
|
}
|
|
/*
|
|
* Adapt the number of elements to the real number of unpacked elements,
|
|
* it means that we overallocated elements array.
|
|
*/
|
|
message->num_elements = nelem;
|
|
|
|
/*
|
|
* Shrink the allocated size. On current talloc behaviour
|
|
* this will help if we skipped 32 or more attributes.
|
|
*/
|
|
message->elements = talloc_realloc(message, message->elements,
|
|
struct ldb_message_element,
|
|
message->num_elements);
|
|
|
|
if (remaining != 0) {
|
|
ldb_debug(ldb, LDB_DEBUG_ERROR,
|
|
"Error: %zu bytes unread in ldb_unpack_data_flags",
|
|
remaining);
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed:
|
|
talloc_free(message->elements);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Unpack a ldb message from a linear buffer in ldb_val
|
|
*/
|
|
static int ldb_unpack_data_flags_v2(struct ldb_context *ldb,
|
|
const struct ldb_val *data,
|
|
struct ldb_message *message,
|
|
unsigned int flags)
|
|
{
|
|
uint8_t *p, *q, *end_p, *value_section_p;
|
|
unsigned int i, j;
|
|
unsigned int nelem = 0;
|
|
size_t len;
|
|
struct ldb_val *ldb_val_single_array = NULL;
|
|
uint8_t val_len_width;
|
|
|
|
message->elements = NULL;
|
|
|
|
p = data->data;
|
|
end_p = p + data->length;
|
|
|
|
/* Skip first 4 bytes, format already read */
|
|
p += U32_LEN;
|
|
|
|
/* First fields are fixed: num_elements, DN length */
|
|
if (U32_LEN * 2 > end_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
message->num_elements = PULL_LE_U32(p, 0);
|
|
p += U32_LEN;
|
|
|
|
len = PULL_LE_U32(p, 0);
|
|
p += U32_LEN;
|
|
|
|
if (len + NULL_PAD_BYTE_LEN > end_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_DN) {
|
|
message->dn = NULL;
|
|
} else {
|
|
struct ldb_val blob;
|
|
blob.data = discard_const_p(uint8_t, p);
|
|
blob.length = len;
|
|
message->dn = ldb_dn_from_ldb_val(message, ldb, &blob);
|
|
if (message->dn == NULL) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
p += len + NULL_PAD_BYTE_LEN;
|
|
|
|
if (*(p-NULL_PAD_BYTE_LEN) != '\0') {
|
|
errno = EINVAL;
|
|
goto failed;
|
|
}
|
|
|
|
/* Now skip the canonicalized DN and its length */
|
|
len = PULL_LE_U32(p, 0) + NULL_PAD_BYTE_LEN;
|
|
p += U32_LEN;
|
|
|
|
if (len > end_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
p += len;
|
|
|
|
if (*(p-NULL_PAD_BYTE_LEN) != '\0') {
|
|
errno = EINVAL;
|
|
goto failed;
|
|
}
|
|
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_ATTRS) {
|
|
message->num_elements = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (message->num_elements == 0) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Sanity check (17 bytes is the minimum element size)
|
|
*/
|
|
if (message->num_elements > (end_p - p) / 17) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
message->elements = talloc_zero_array(message,
|
|
struct ldb_message_element,
|
|
message->num_elements);
|
|
if (!message->elements) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* In typical use, most values are single-valued. This makes
|
|
* it quite expensive to allocate an array of ldb_val for each
|
|
* of these, just to then hold the pointer to the data buffer.
|
|
* So with LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC we allocate this
|
|
* ahead of time and use it for the single values where possible.
|
|
* (This is used in the normal search case, but not in the
|
|
* index case because of caller requirements).
|
|
*/
|
|
if (flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) {
|
|
ldb_val_single_array = talloc_array(message->elements,
|
|
struct ldb_val,
|
|
message->num_elements);
|
|
if (ldb_val_single_array == NULL) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
q = p + PULL_LE_U32(p, 0);
|
|
value_section_p = q;
|
|
p += U32_LEN;
|
|
|
|
for (i=0;i<message->num_elements;i++) {
|
|
const char *attr = NULL;
|
|
size_t attr_len;
|
|
struct ldb_message_element *element = NULL;
|
|
|
|
/* Sanity check: minimum element size */
|
|
if ((U32_LEN * 2) + /* attr name len, num values */
|
|
(U8_LEN * 2) + /* value length width, one val length */
|
|
(NULL_PAD_BYTE_LEN * 2) /* null for attr name + val */
|
|
> value_section_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
attr_len = PULL_LE_U32(p, 0);
|
|
p += U32_LEN;
|
|
|
|
if (attr_len == 0) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
attr = (char *)p;
|
|
|
|
p += attr_len + NULL_PAD_BYTE_LEN;
|
|
/*
|
|
* num_values, val_len_width
|
|
*
|
|
* val_len_width is the width specifier
|
|
* for the variable length encoding
|
|
*/
|
|
if (U32_LEN + U8_LEN > value_section_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
if (*(p-NULL_PAD_BYTE_LEN) != '\0') {
|
|
errno = EINVAL;
|
|
goto failed;
|
|
}
|
|
|
|
element = &message->elements[nelem];
|
|
element->name = attr;
|
|
element->flags = 0;
|
|
|
|
element->num_values = PULL_LE_U32(p, 0);
|
|
element->values = NULL;
|
|
if ((flags & LDB_UNPACK_DATA_FLAG_NO_VALUES_ALLOC) &&
|
|
element->num_values == 1) {
|
|
element->values = &ldb_val_single_array[nelem];
|
|
element->flags |= LDB_FLAG_INTERNAL_SHARED_VALUES;
|
|
} else if (element->num_values != 0) {
|
|
element->values = talloc_array(message->elements,
|
|
struct ldb_val,
|
|
element->num_values);
|
|
if (!element->values) {
|
|
errno = ENOMEM;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
p += U32_LEN;
|
|
|
|
/*
|
|
* Here we read how wide the remaining lengths are
|
|
* which avoids storing and parsing a lot of leading
|
|
* 0s
|
|
*/
|
|
val_len_width = *p;
|
|
p += U8_LEN;
|
|
|
|
if (val_len_width * element->num_values >
|
|
value_section_p - p) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* This is structured weird for compiler optimization
|
|
* purposes, but we need to pull the array of widths
|
|
* with different macros depending on how wide the
|
|
* biggest one is (specified by val_len_width)
|
|
*/
|
|
if (val_len_width == U8_LEN) {
|
|
for (j = 0; j < element->num_values; j++) {
|
|
element->values[j].length = PULL_LE_U8(p, 0);
|
|
p += U8_LEN;
|
|
}
|
|
} else if (val_len_width == U16_LEN) {
|
|
for (j = 0; j < element->num_values; j++) {
|
|
element->values[j].length = PULL_LE_U16(p, 0);
|
|
p += U16_LEN;
|
|
}
|
|
} else if (val_len_width == U32_LEN) {
|
|
for (j = 0; j < element->num_values; j++) {
|
|
element->values[j].length = PULL_LE_U32(p, 0);
|
|
p += U32_LEN;
|
|
}
|
|
} else {
|
|
errno = ERANGE;
|
|
goto failed;
|
|
}
|
|
|
|
for (j = 0; j < element->num_values; j++) {
|
|
len = element->values[j].length;
|
|
if (len + NULL_PAD_BYTE_LEN < len) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
if (len + NULL_PAD_BYTE_LEN > end_p - q) {
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
element->values[j].data = q;
|
|
q += len + NULL_PAD_BYTE_LEN;
|
|
}
|
|
nelem++;
|
|
}
|
|
|
|
/*
|
|
* If p isn't now pointing at the beginning of the value section,
|
|
* something went very wrong.
|
|
*/
|
|
if (p != value_section_p) {
|
|
ldb_debug(ldb, LDB_DEBUG_ERROR,
|
|
"Error: Data corruption in ldb_unpack_data_flags");
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* Adapt the number of elements to the real number of unpacked
|
|
* elements it means that we overallocated elements array.
|
|
*/
|
|
message->num_elements = nelem;
|
|
|
|
/*
|
|
* Shrink the allocated size. On current talloc behaviour
|
|
* this will help if we skipped 32 or more attributes.
|
|
*/
|
|
message->elements = talloc_realloc(message, message->elements,
|
|
struct ldb_message_element,
|
|
message->num_elements);
|
|
|
|
if (q != end_p) {
|
|
ldb_debug(ldb, LDB_DEBUG_ERROR,
|
|
"Error: %zu bytes unread in ldb_unpack_data_flags",
|
|
end_p - q);
|
|
errno = EIO;
|
|
goto failed;
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed:
|
|
talloc_free(message->elements);
|
|
return -1;
|
|
}
|
|
|
|
int ldb_unpack_get_format(const struct ldb_val *data,
|
|
uint32_t *pack_format_version)
|
|
{
|
|
if (data->length < U32_LEN) {
|
|
return LDB_ERR_OPERATIONS_ERROR;
|
|
}
|
|
*pack_format_version = PULL_LE_U32(data->data, 0);
|
|
return LDB_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Unpack a ldb message from a linear buffer in ldb_val
|
|
*/
|
|
int ldb_unpack_data_flags(struct ldb_context *ldb,
|
|
const struct ldb_val *data,
|
|
struct ldb_message *message,
|
|
unsigned int flags)
|
|
{
|
|
unsigned format;
|
|
|
|
if (data->length < U32_LEN) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
format = PULL_LE_U32(data->data, 0);
|
|
if (format == LDB_PACKING_FORMAT_V2) {
|
|
return ldb_unpack_data_flags_v2(ldb, data, message, flags);
|
|
}
|
|
|
|
/*
|
|
* The v1 function we're about to call takes either LDB_PACKING_FORMAT
|
|
* or LDB_PACKING_FORMAT_NODN packing format versions, and will error
|
|
* if given some other version, so we don't need to do any further
|
|
* checks on 'format'.
|
|
*/
|
|
return ldb_unpack_data_flags_v1(ldb, data, message, flags, format);
|
|
}
|
|
|
|
|
|
/*
|
|
* Unpack a ldb message from a linear buffer in ldb_val
|
|
*
|
|
* Free with ldb_unpack_data_free()
|
|
*/
|
|
int ldb_unpack_data(struct ldb_context *ldb,
|
|
const struct ldb_val *data,
|
|
struct ldb_message *message)
|
|
{
|
|
return ldb_unpack_data_flags(ldb, data, message, 0);
|
|
}
|
|
|
|
/*
|
|
add the special distinguishedName element
|
|
*/
|
|
int ldb_msg_add_distinguished_name(struct ldb_message *msg)
|
|
{
|
|
const char *dn_attr = "distinguishedName";
|
|
char *dn = NULL;
|
|
|
|
if (ldb_msg_find_element(msg, dn_attr)) {
|
|
/*
|
|
* This should not happen, but this is
|
|
* existing behaviour...
|
|
*/
|
|
return LDB_SUCCESS;
|
|
}
|
|
|
|
dn = ldb_dn_alloc_linearized(msg, msg->dn);
|
|
if (dn == NULL) {
|
|
return LDB_ERR_OPERATIONS_ERROR;
|
|
}
|
|
|
|
return ldb_msg_add_steal_string(msg, dn_attr, dn);
|
|
}
|
|
|
|
/*
|
|
* filter the specified list of attributes from msg,
|
|
* adding requested attributes, and perhaps all for *,
|
|
* but not the DN to filtered_msg.
|
|
*/
|
|
int ldb_filter_attrs(struct ldb_context *ldb,
|
|
const struct ldb_message *msg,
|
|
const char *const *attrs,
|
|
struct ldb_message *filtered_msg)
|
|
{
|
|
unsigned int i;
|
|
bool keep_all = false;
|
|
bool add_dn = false;
|
|
uint32_t num_elements;
|
|
uint32_t elements_size;
|
|
|
|
if (attrs) {
|
|
/* check for special attrs */
|
|
for (i = 0; attrs[i]; i++) {
|
|
int cmp = strcmp(attrs[i], "*");
|
|
if (cmp == 0) {
|
|
keep_all = true;
|
|
break;
|
|
}
|
|
cmp = ldb_attr_cmp(attrs[i], "distinguishedName");
|
|
if (cmp == 0) {
|
|
add_dn = true;
|
|
}
|
|
}
|
|
} else {
|
|
keep_all = true;
|
|
}
|
|
|
|
if (keep_all) {
|
|
add_dn = true;
|
|
elements_size = msg->num_elements + 1;
|
|
|
|
/* Shortcuts for the simple cases */
|
|
} else if (add_dn && i == 1) {
|
|
if (ldb_msg_add_distinguished_name(filtered_msg) != 0) {
|
|
goto failed;
|
|
}
|
|
return 0;
|
|
} else if (i == 0) {
|
|
return 0;
|
|
|
|
/*
|
|
* Otherwise we are copying at most as many elements as we
|
|
* have attributes
|
|
*/
|
|
} else {
|
|
elements_size = i;
|
|
}
|
|
|
|
filtered_msg->elements = talloc_array(filtered_msg,
|
|
struct ldb_message_element,
|
|
elements_size);
|
|
if (filtered_msg->elements == NULL) goto failed;
|
|
|
|
num_elements = 0;
|
|
|
|
for (i = 0; i < msg->num_elements; i++) {
|
|
struct ldb_message_element *el = &msg->elements[i];
|
|
|
|
/*
|
|
* el2 is assigned after the Pigeonhole principle
|
|
* check below for clarity
|
|
*/
|
|
struct ldb_message_element *el2 = NULL;
|
|
unsigned int j;
|
|
|
|
if (keep_all == false) {
|
|
bool found = false;
|
|
for (j = 0; attrs[j]; j++) {
|
|
int cmp = ldb_attr_cmp(el->name, attrs[j]);
|
|
if (cmp == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (found == false) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Pigeonhole principle: we can't have more elements
|
|
* than the number of attributes if they are unique in
|
|
* the DB.
|
|
*/
|
|
if (num_elements >= elements_size) {
|
|
goto failed;
|
|
}
|
|
|
|
el2 = &filtered_msg->elements[num_elements];
|
|
|
|
*el2 = *el;
|
|
el2->name = talloc_strdup(filtered_msg->elements,
|
|
el->name);
|
|
if (el2->name == NULL) {
|
|
goto failed;
|
|
}
|
|
el2->values = talloc_array(filtered_msg->elements,
|
|
struct ldb_val, el->num_values);
|
|
if (el2->values == NULL) {
|
|
goto failed;
|
|
}
|
|
for (j=0;j<el->num_values;j++) {
|
|
el2->values[j] = ldb_val_dup(el2->values, &el->values[j]);
|
|
if (el2->values[j].data == NULL && el->values[j].length != 0) {
|
|
goto failed;
|
|
}
|
|
}
|
|
num_elements++;
|
|
}
|
|
|
|
filtered_msg->num_elements = num_elements;
|
|
|
|
if (add_dn) {
|
|
if (ldb_msg_add_distinguished_name(filtered_msg) != 0) {
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
if (filtered_msg->num_elements > 0) {
|
|
filtered_msg->elements
|
|
= talloc_realloc(filtered_msg,
|
|
filtered_msg->elements,
|
|
struct ldb_message_element,
|
|
filtered_msg->num_elements);
|
|
if (filtered_msg->elements == NULL) {
|
|
goto failed;
|
|
}
|
|
} else {
|
|
TALLOC_FREE(filtered_msg->elements);
|
|
}
|
|
|
|
return 0;
|
|
failed:
|
|
TALLOC_FREE(filtered_msg->elements);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* filter the specified list of attributes from msg,
|
|
* adding requested attributes, and perhaps all for *.
|
|
* Unlike ldb_filter_attrs(), the DN will not be added
|
|
* if it is missing.
|
|
*/
|
|
int ldb_filter_attrs_in_place(struct ldb_message *msg,
|
|
const char *const *attrs)
|
|
{
|
|
unsigned int i = 0;
|
|
bool keep_all = false;
|
|
unsigned int num_del = 0;
|
|
|
|
if (attrs) {
|
|
/* check for special attrs */
|
|
for (i = 0; attrs[i]; i++) {
|
|
int cmp = strcmp(attrs[i], "*");
|
|
if (cmp == 0) {
|
|
keep_all = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!keep_all && i == 0) {
|
|
msg->num_elements = 0;
|
|
return LDB_SUCCESS;
|
|
}
|
|
} else {
|
|
keep_all = true;
|
|
}
|
|
|
|
if (keep_all) {
|
|
return LDB_SUCCESS;
|
|
}
|
|
/*
|
|
* Find the intersection between the msg elements and attrs.
|
|
*
|
|
* TODO, maybe: use a faster algorithm when (n * m) is too large.
|
|
*/
|
|
for (i = 0; i < msg->num_elements; i++) {
|
|
bool found = false;
|
|
unsigned int j;
|
|
|
|
for (j = 0; attrs[j]; j++) {
|
|
int cmp = ldb_attr_cmp(msg->elements[i].name, attrs[j]);
|
|
if (cmp == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
++num_del;
|
|
} else if (num_del != 0) {
|
|
msg->elements[i - num_del] = msg->elements[i];
|
|
}
|
|
}
|
|
|
|
msg->num_elements -= num_del;
|
|
|
|
return LDB_SUCCESS;
|
|
}
|
|
|
|
/* Have an unpacked ldb message take talloc ownership of its elements. */
|
|
int ldb_msg_elements_take_ownership(struct ldb_message *msg)
|
|
{
|
|
unsigned int i = 0;
|
|
|
|
for (i = 0; i < msg->num_elements; i++) {
|
|
struct ldb_message_element *el = &msg->elements[i];
|
|
const char *name;
|
|
unsigned int j;
|
|
|
|
name = talloc_strdup(msg->elements,
|
|
el->name);
|
|
if (name == NULL) {
|
|
return -1;
|
|
}
|
|
el->name = name;
|
|
|
|
if (el->flags & LDB_FLAG_INTERNAL_SHARED_VALUES) {
|
|
struct ldb_val *values = talloc_memdup(msg->elements, el->values,
|
|
sizeof(struct ldb_val) * el->num_values);
|
|
if (values == NULL) {
|
|
return -1;
|
|
}
|
|
el->values = values;
|
|
el->flags &= ~LDB_FLAG_INTERNAL_SHARED_VALUES;
|
|
}
|
|
|
|
for (j = 0; j < el->num_values; j++) {
|
|
struct ldb_val val = ldb_val_dup(el->values, &el->values[j]);
|
|
if (val.data == NULL && el->values[j].length != 0) {
|
|
return -1;
|
|
}
|
|
el->values[j] = val;
|
|
}
|
|
}
|
|
|
|
return LDB_SUCCESS;
|
|
}
|