diff options
Diffstat (limited to 'lib/ldb/common/ldb_pack.c')
-rw-r--r-- | lib/ldb/common/ldb_pack.c | 1360 |
1 files changed, 1360 insertions, 0 deletions
diff --git a/lib/ldb/common/ldb_pack.c b/lib/ldb/common/ldb_pack.c new file mode 100644 index 0000000..28b9a8d --- /dev/null +++ b/lib/ldb/common/ldb_pack.c @@ -0,0 +1,1360 @@ +/* + 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 calcuate 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) { + 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 the 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 (p + U32_LEN * 2 > end_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 (p + len + NULL_PAD_BYTE_LEN > end_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 (p + len > end_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) { + 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 the 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 (p + (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) { + 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 (p + U32_LEN + U8_LEN > value_section_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 (p + val_len_width * element->num_values > + value_section_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 (q + len + NULL_PAD_BYTE_LEN > end_p) { + 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; + } + + for (i = 0; i < msg->num_elements; i++) { + bool found = false; + unsigned int j; + + if (keep_all) { + found = true; + } else { + 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; +} |