diff options
Diffstat (limited to 'source4/libcli/ldap')
-rw-r--r-- | source4/libcli/ldap/config.mk | 18 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap.c | 1401 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap.h | 261 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_bind.c | 411 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_client.c | 824 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_client.h | 140 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_controls.c | 1229 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_ildap.c | 129 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_msg.c | 86 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_ndr.c | 96 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_ndr.h | 12 |
11 files changed, 4607 insertions, 0 deletions
diff --git a/source4/libcli/ldap/config.mk b/source4/libcli/ldap/config.mk new file mode 100644 index 0000000000..02678eed7a --- /dev/null +++ b/source4/libcli/ldap/config.mk @@ -0,0 +1,18 @@ +[SUBSYSTEM::LIBCLI_LDAP] +PUBLIC_DEPENDENCIES = LIBSAMBA-ERRORS LIBEVENTS LIBPACKET +PRIVATE_DEPENDENCIES = LIBCLI_COMPOSITE samba-socket NDR_SAMR LIBTLS ASN1_UTIL \ + LDAP_ENCODE LIBNDR LP_RESOLVE gensec + +LIBCLI_LDAP_OBJ_FILES = $(addprefix $(libclisrcdir)/ldap/, \ + ldap.o ldap_client.o ldap_bind.o \ + ldap_msg.o ldap_ildap.o ldap_controls.o) + + +PUBLIC_HEADERS += $(libclisrcdir)/ldap/ldap.h $(libclisrcdir)/ldap/ldap_ndr.h + +$(eval $(call proto_header_template,$(libclisrcdir)/ldap/ldap_proto.h,$(LIBCLI_LDAP_OBJ_FILES:.o=.c))) + +[SUBSYSTEM::LDAP_ENCODE] +# FIXME PRIVATE_DEPENDENCIES = LIBLDB + +LDAP_ENCODE_OBJ_FILES = $(libclisrcdir)/ldap/ldap_ndr.o diff --git a/source4/libcli/ldap/ldap.c b/source4/libcli/ldap/ldap.c new file mode 100644 index 0000000000..fc6de7993e --- /dev/null +++ b/source4/libcli/ldap/ldap.c @@ -0,0 +1,1401 @@ +/* + Unix SMB/CIFS mplementation. + LDAP protocol helper functions for SAMBA + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Simo Sorce 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/util/asn1.h" +#include "libcli/ldap/ldap.h" +#include "libcli/ldap/ldap_proto.h" + + +static bool ldap_push_filter(struct asn1_data *data, struct ldb_parse_tree *tree) +{ + int i; + + switch (tree->operation) { + case LDB_OP_AND: + case LDB_OP_OR: + asn1_push_tag(data, ASN1_CONTEXT(tree->operation==LDB_OP_AND?0:1)); + for (i=0; i<tree->u.list.num_elements; i++) { + if (!ldap_push_filter(data, tree->u.list.elements[i])) { + return false; + } + } + asn1_pop_tag(data); + break; + + case LDB_OP_NOT: + asn1_push_tag(data, ASN1_CONTEXT(2)); + if (!ldap_push_filter(data, tree->u.isnot.child)) { + return false; + } + asn1_pop_tag(data); + break; + + case LDB_OP_EQUALITY: + /* equality test */ + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, tree->u.equality.attr, + strlen(tree->u.equality.attr)); + asn1_write_OctetString(data, tree->u.equality.value.data, + tree->u.equality.value.length); + asn1_pop_tag(data); + break; + + case LDB_OP_SUBSTRING: + /* + SubstringFilter ::= SEQUENCE { + type AttributeDescription, + -- at least one must be present + substrings SEQUENCE OF CHOICE { + initial [0] LDAPString, + any [1] LDAPString, + final [2] LDAPString } } + */ + asn1_push_tag(data, ASN1_CONTEXT(4)); + asn1_write_OctetString(data, tree->u.substring.attr, strlen(tree->u.substring.attr)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + i = 0; + if ( ! tree->u.substring.start_with_wildcard) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(0)); + asn1_write_DATA_BLOB_LDAPString(data, tree->u.substring.chunks[i]); + asn1_pop_tag(data); + i++; + } + while (tree->u.substring.chunks[i]) { + int ctx; + + if (( ! tree->u.substring.chunks[i + 1]) && + (tree->u.substring.end_with_wildcard == 0)) { + ctx = 2; + } else { + ctx = 1; + } + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(ctx)); + asn1_write_DATA_BLOB_LDAPString(data, tree->u.substring.chunks[i]); + asn1_pop_tag(data); + i++; + } + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + + case LDB_OP_GREATER: + /* greaterOrEqual test */ + asn1_push_tag(data, ASN1_CONTEXT(5)); + asn1_write_OctetString(data, tree->u.comparison.attr, + strlen(tree->u.comparison.attr)); + asn1_write_OctetString(data, tree->u.comparison.value.data, + tree->u.comparison.value.length); + asn1_pop_tag(data); + break; + + case LDB_OP_LESS: + /* lessOrEqual test */ + asn1_push_tag(data, ASN1_CONTEXT(6)); + asn1_write_OctetString(data, tree->u.comparison.attr, + strlen(tree->u.comparison.attr)); + asn1_write_OctetString(data, tree->u.comparison.value.data, + tree->u.comparison.value.length); + asn1_pop_tag(data); + break; + + case LDB_OP_PRESENT: + /* present test */ + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(7)); + asn1_write_LDAPString(data, tree->u.present.attr); + asn1_pop_tag(data); + return !data->has_error; + + case LDB_OP_APPROX: + /* approx test */ + asn1_push_tag(data, ASN1_CONTEXT(8)); + asn1_write_OctetString(data, tree->u.comparison.attr, + strlen(tree->u.comparison.attr)); + asn1_write_OctetString(data, tree->u.comparison.value.data, + tree->u.comparison.value.length); + asn1_pop_tag(data); + break; + + case LDB_OP_EXTENDED: + /* + MatchingRuleAssertion ::= SEQUENCE { + matchingRule [1] MatchingRuleID OPTIONAL, + type [2] AttributeDescription OPTIONAL, + matchValue [3] AssertionValue, + dnAttributes [4] BOOLEAN DEFAULT FALSE + } + */ + asn1_push_tag(data, ASN1_CONTEXT(9)); + if (tree->u.extended.rule_id) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(1)); + asn1_write_LDAPString(data, tree->u.extended.rule_id); + asn1_pop_tag(data); + } + if (tree->u.extended.attr) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(2)); + asn1_write_LDAPString(data, tree->u.extended.attr); + asn1_pop_tag(data); + } + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(3)); + asn1_write_DATA_BLOB_LDAPString(data, &tree->u.extended.value); + asn1_pop_tag(data); + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(4)); + asn1_write_uint8(data, tree->u.extended.dnAttributes); + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + + default: + return false; + } + return !data->has_error; +} + +static void ldap_encode_response(struct asn1_data *data, struct ldap_Result *result) +{ + asn1_write_enumerated(data, result->resultcode); + asn1_write_OctetString(data, result->dn, + (result->dn) ? strlen(result->dn) : 0); + asn1_write_OctetString(data, result->errormessage, + (result->errormessage) ? + strlen(result->errormessage) : 0); + if (result->referral) { + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, result->referral, + strlen(result->referral)); + asn1_pop_tag(data); + } +} + +_PUBLIC_ bool ldap_encode(struct ldap_message *msg, DATA_BLOB *result, TALLOC_CTX *mem_ctx) +{ + struct asn1_data *data = asn1_init(mem_ctx); + int i, j; + + if (!data) return false; + + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_Integer(data, msg->messageid); + + switch (msg->type) { + case LDAP_TAG_BindRequest: { + struct ldap_BindRequest *r = &msg->r.BindRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_Integer(data, r->version); + asn1_write_OctetString(data, r->dn, + (r->dn != NULL) ? strlen(r->dn) : 0); + + switch (r->mechanism) { + case LDAP_AUTH_MECH_SIMPLE: + /* context, primitive */ + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(0)); + asn1_write(data, r->creds.password, + strlen(r->creds.password)); + asn1_pop_tag(data); + break; + case LDAP_AUTH_MECH_SASL: + /* context, constructed */ + asn1_push_tag(data, ASN1_CONTEXT(3)); + asn1_write_OctetString(data, r->creds.SASL.mechanism, + strlen(r->creds.SASL.mechanism)); + if (r->creds.SASL.secblob) { + asn1_write_OctetString(data, r->creds.SASL.secblob->data, + r->creds.SASL.secblob->length); + } + asn1_pop_tag(data); + break; + default: + return false; + } + + asn1_pop_tag(data); + break; + } + case LDAP_TAG_BindResponse: { + struct ldap_BindResponse *r = &msg->r.BindResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, &r->response); + if (r->SASL.secblob) { + asn1_write_ContextSimple(data, 7, r->SASL.secblob); + } + asn1_pop_tag(data); + break; + } + case LDAP_TAG_UnbindRequest: { +/* struct ldap_UnbindRequest *r = &msg->r.UnbindRequest; */ + break; + } + case LDAP_TAG_SearchRequest: { + struct ldap_SearchRequest *r = &msg->r.SearchRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->basedn, strlen(r->basedn)); + asn1_write_enumerated(data, r->scope); + asn1_write_enumerated(data, r->deref); + asn1_write_Integer(data, r->sizelimit); + asn1_write_Integer(data, r->timelimit); + asn1_write_BOOLEAN(data, r->attributesonly); + + if (!ldap_push_filter(data, r->tree)) { + return false; + } + + asn1_push_tag(data, ASN1_SEQUENCE(0)); + for (i=0; i<r->num_attributes; i++) { + asn1_write_OctetString(data, r->attributes[i], + strlen(r->attributes[i])); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_SearchResultEntry: { + struct ldap_SearchResEntry *r = &msg->r.SearchResultEntry; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->dn, strlen(r->dn)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + for (i=0; i<r->num_attributes; i++) { + struct ldb_message_element *attr = &r->attributes[i]; + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OctetString(data, attr->name, + strlen(attr->name)); + asn1_push_tag(data, ASN1_SEQUENCE(1)); + for (j=0; j<attr->num_values; j++) { + asn1_write_OctetString(data, + attr->values[j].data, + attr->values[j].length); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_SearchResultDone: { + struct ldap_Result *r = &msg->r.SearchResultDone; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ModifyRequest: { + struct ldap_ModifyRequest *r = &msg->r.ModifyRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->dn, strlen(r->dn)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + + for (i=0; i<r->num_mods; i++) { + struct ldb_message_element *attrib = &r->mods[i].attrib; + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_enumerated(data, r->mods[i].type); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OctetString(data, attrib->name, + strlen(attrib->name)); + asn1_push_tag(data, ASN1_SET); + for (j=0; j<attrib->num_values; j++) { + asn1_write_OctetString(data, + attrib->values[j].data, + attrib->values[j].length); + + } + asn1_pop_tag(data); + asn1_pop_tag(data); + asn1_pop_tag(data); + } + + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ModifyResponse: { + struct ldap_Result *r = &msg->r.ModifyResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_AddRequest: { + struct ldap_AddRequest *r = &msg->r.AddRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->dn, strlen(r->dn)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + + for (i=0; i<r->num_attributes; i++) { + struct ldb_message_element *attrib = &r->attributes[i]; + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OctetString(data, attrib->name, + strlen(attrib->name)); + asn1_push_tag(data, ASN1_SET); + for (j=0; j<r->attributes[i].num_values; j++) { + asn1_write_OctetString(data, + attrib->values[j].data, + attrib->values[j].length); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_AddResponse: { + struct ldap_Result *r = &msg->r.AddResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_DelRequest: { + struct ldap_DelRequest *r = &msg->r.DelRequest; + asn1_push_tag(data, ASN1_APPLICATION_SIMPLE(msg->type)); + asn1_write(data, r->dn, strlen(r->dn)); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_DelResponse: { + struct ldap_Result *r = &msg->r.DelResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ModifyDNRequest: { + struct ldap_ModifyDNRequest *r = &msg->r.ModifyDNRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->dn, strlen(r->dn)); + asn1_write_OctetString(data, r->newrdn, strlen(r->newrdn)); + asn1_write_BOOLEAN(data, r->deleteolddn); + if (r->newsuperior) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(0)); + asn1_write(data, r->newsuperior, + strlen(r->newsuperior)); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ModifyDNResponse: { + struct ldap_Result *r = &msg->r.ModifyDNResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_CompareRequest: { + struct ldap_CompareRequest *r = &msg->r.CompareRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->dn, strlen(r->dn)); + asn1_push_tag(data, ASN1_SEQUENCE(0)); + asn1_write_OctetString(data, r->attribute, + strlen(r->attribute)); + asn1_write_OctetString(data, r->value.data, + r->value.length); + asn1_pop_tag(data); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_CompareResponse: { + struct ldap_Result *r = &msg->r.ModifyDNResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, r); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_AbandonRequest: { + struct ldap_AbandonRequest *r = &msg->r.AbandonRequest; + asn1_push_tag(data, ASN1_APPLICATION_SIMPLE(msg->type)); + asn1_write_implicit_Integer(data, r->messageid); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_SearchResultReference: { + struct ldap_SearchResRef *r = &msg->r.SearchResultReference; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_write_OctetString(data, r->referral, strlen(r->referral)); + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ExtendedRequest: { + struct ldap_ExtendedRequest *r = &msg->r.ExtendedRequest; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(0)); + asn1_write(data, r->oid, strlen(r->oid)); + asn1_pop_tag(data); + if (r->value) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(1)); + asn1_write(data, r->value->data, r->value->length); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + break; + } + case LDAP_TAG_ExtendedResponse: { + struct ldap_ExtendedResponse *r = &msg->r.ExtendedResponse; + asn1_push_tag(data, ASN1_APPLICATION(msg->type)); + ldap_encode_response(data, &r->response); + if (r->oid) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(10)); + asn1_write(data, r->oid, strlen(r->oid)); + asn1_pop_tag(data); + } + if (r->value) { + asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(11)); + asn1_write(data, r->value->data, r->value->length); + asn1_pop_tag(data); + } + asn1_pop_tag(data); + break; + } + default: + return false; + } + + if (msg->controls != NULL) { + asn1_push_tag(data, ASN1_CONTEXT(0)); + + for (i = 0; msg->controls[i] != NULL; i++) { + if (!ldap_encode_control(mem_ctx, data, msg->controls[i])) { + return false; + } + } + + asn1_pop_tag(data); + } + + asn1_pop_tag(data); + + if (data->has_error) { + asn1_free(data); + return false; + } + + *result = data_blob_talloc(mem_ctx, data->data, data->length); + asn1_free(data); + return true; +} + +static const char *blob2string_talloc(TALLOC_CTX *mem_ctx, + DATA_BLOB blob) +{ + char *result = talloc_array(mem_ctx, char, blob.length+1); + memcpy(result, blob.data, blob.length); + result[blob.length] = '\0'; + return result; +} + +static bool asn1_read_OctetString_talloc(TALLOC_CTX *mem_ctx, + struct asn1_data *data, + const char **result) +{ + DATA_BLOB string; + if (!asn1_read_OctetString(data, mem_ctx, &string)) + return false; + *result = blob2string_talloc(mem_ctx, string); + data_blob_free(&string); + return true; +} + +static void ldap_decode_response(TALLOC_CTX *mem_ctx, + struct asn1_data *data, + struct ldap_Result *result) +{ + asn1_read_enumerated(data, &result->resultcode); + asn1_read_OctetString_talloc(mem_ctx, data, &result->dn); + asn1_read_OctetString_talloc(mem_ctx, data, &result->errormessage); + if (asn1_peek_tag(data, ASN1_CONTEXT(3))) { + asn1_start_tag(data, ASN1_CONTEXT(3)); + asn1_read_OctetString_talloc(mem_ctx, data, &result->referral); + asn1_end_tag(data); + } else { + result->referral = NULL; + } +} + +static struct ldb_val **ldap_decode_substring(TALLOC_CTX *mem_ctx, struct ldb_val **chunks, int chunk_num, char *value) +{ + + chunks = talloc_realloc(mem_ctx, chunks, struct ldb_val *, chunk_num + 2); + if (chunks == NULL) { + return NULL; + } + + chunks[chunk_num] = talloc(mem_ctx, struct ldb_val); + if (chunks[chunk_num] == NULL) { + return NULL; + } + + chunks[chunk_num]->data = (uint8_t *)talloc_strdup(mem_ctx, value); + if (chunks[chunk_num]->data == NULL) { + return NULL; + } + chunks[chunk_num]->length = strlen(value); + + chunks[chunk_num + 1] = '\0'; + + return chunks; +} + + +/* + parse the ASN.1 formatted search string into a ldb_parse_tree +*/ +static struct ldb_parse_tree *ldap_decode_filter_tree(TALLOC_CTX *mem_ctx, + struct asn1_data *data) +{ + uint8_t filter_tag; + struct ldb_parse_tree *ret; + + if (!asn1_peek_uint8(data, &filter_tag)) { + return NULL; + } + + filter_tag &= 0x1f; /* strip off the asn1 stuff */ + + ret = talloc(mem_ctx, struct ldb_parse_tree); + if (ret == NULL) return NULL; + + switch(filter_tag) { + case 0: + case 1: + /* AND or OR of one or more filters */ + ret->operation = (filter_tag == 0)?LDB_OP_AND:LDB_OP_OR; + ret->u.list.num_elements = 0; + ret->u.list.elements = NULL; + + if (!asn1_start_tag(data, ASN1_CONTEXT(filter_tag))) { + goto failed; + } + + while (asn1_tag_remaining(data) > 0) { + struct ldb_parse_tree *subtree; + subtree = ldap_decode_filter_tree(ret, data); + if (subtree == NULL) { + goto failed; + } + ret->u.list.elements = + talloc_realloc(ret, ret->u.list.elements, + struct ldb_parse_tree *, + ret->u.list.num_elements+1); + if (ret->u.list.elements == NULL) { + goto failed; + } + talloc_steal(ret->u.list.elements, subtree); + ret->u.list.elements[ret->u.list.num_elements] = subtree; + ret->u.list.num_elements++; + } + if (!asn1_end_tag(data)) { + goto failed; + } + break; + + case 2: + /* 'not' operation */ + if (!asn1_start_tag(data, ASN1_CONTEXT(filter_tag))) { + goto failed; + } + + ret->operation = LDB_OP_NOT; + ret->u.isnot.child = ldap_decode_filter_tree(ret, data); + if (ret->u.isnot.child == NULL) { + goto failed; + } + if (!asn1_end_tag(data)) { + goto failed; + } + break; + + case 3: { + /* equalityMatch */ + const char *attrib; + DATA_BLOB value; + + asn1_start_tag(data, ASN1_CONTEXT(filter_tag)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib); + asn1_read_OctetString(data, mem_ctx, &value); + asn1_end_tag(data); + if ((data->has_error) || (attrib == NULL) || (value.data == NULL)) { + goto failed; + } + + ret->operation = LDB_OP_EQUALITY; + ret->u.equality.attr = talloc_steal(ret, attrib); + ret->u.equality.value.data = talloc_steal(ret, value.data); + ret->u.equality.value.length = value.length; + break; + } + case 4: { + /* substrings */ + DATA_BLOB attr; + uint8_t subs_tag; + char *value; + int chunk_num = 0; + + if (!asn1_start_tag(data, ASN1_CONTEXT(filter_tag))) { + goto failed; + } + if (!asn1_read_OctetString(data, mem_ctx, &attr)) { + goto failed; + } + + ret->operation = LDB_OP_SUBSTRING; + ret->u.substring.attr = talloc_strndup(ret, (char *)attr.data, attr.length); + ret->u.substring.chunks = NULL; + ret->u.substring.start_with_wildcard = 1; + ret->u.substring.end_with_wildcard = 1; + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + goto failed; + } + + while (asn1_tag_remaining(data)) { + asn1_peek_uint8(data, &subs_tag); + subs_tag &= 0x1f; /* strip off the asn1 stuff */ + if (subs_tag > 2) goto failed; + + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(subs_tag)); + asn1_read_LDAPString(data, mem_ctx, &value); + asn1_end_tag(data); + + switch (subs_tag) { + case 0: + if (ret->u.substring.chunks != NULL) { + /* initial value found in the middle */ + goto failed; + } + + ret->u.substring.chunks = ldap_decode_substring(ret, NULL, 0, value); + if (ret->u.substring.chunks == NULL) { + goto failed; + } + + ret->u.substring.start_with_wildcard = 0; + chunk_num = 1; + break; + + case 1: + if (ret->u.substring.end_with_wildcard == 0) { + /* "any" value found after a "final" value */ + goto failed; + } + + ret->u.substring.chunks = ldap_decode_substring(ret, + ret->u.substring.chunks, + chunk_num, + value); + if (ret->u.substring.chunks == NULL) { + goto failed; + } + + chunk_num++; + break; + + case 2: + ret->u.substring.chunks = ldap_decode_substring(ret, + ret->u.substring.chunks, + chunk_num, + value); + if (ret->u.substring.chunks == NULL) { + goto failed; + } + + ret->u.substring.end_with_wildcard = 0; + break; + + default: + goto failed; + } + + } + + if (!asn1_end_tag(data)) { /* SEQUENCE */ + goto failed; + } + + if (!asn1_end_tag(data)) { + goto failed; + } + break; + } + case 5: { + /* greaterOrEqual */ + const char *attrib; + DATA_BLOB value; + + asn1_start_tag(data, ASN1_CONTEXT(filter_tag)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib); + asn1_read_OctetString(data, mem_ctx, &value); + asn1_end_tag(data); + if ((data->has_error) || (attrib == NULL) || (value.data == NULL)) { + goto failed; + } + + ret->operation = LDB_OP_GREATER; + ret->u.comparison.attr = talloc_steal(ret, attrib); + ret->u.comparison.value.data = talloc_steal(ret, value.data); + ret->u.comparison.value.length = value.length; + break; + } + case 6: { + /* lessOrEqual */ + const char *attrib; + DATA_BLOB value; + + asn1_start_tag(data, ASN1_CONTEXT(filter_tag)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib); + asn1_read_OctetString(data, mem_ctx, &value); + asn1_end_tag(data); + if ((data->has_error) || (attrib == NULL) || (value.data == NULL)) { + goto failed; + } + + ret->operation = LDB_OP_LESS; + ret->u.comparison.attr = talloc_steal(ret, attrib); + ret->u.comparison.value.data = talloc_steal(ret, value.data); + ret->u.comparison.value.length = value.length; + break; + } + case 7: { + /* Normal presence, "attribute=*" */ + char *attr; + + if (!asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(filter_tag))) { + goto failed; + } + if (!asn1_read_LDAPString(data, ret, &attr)) { + goto failed; + } + + ret->operation = LDB_OP_PRESENT; + ret->u.present.attr = talloc_steal(ret, attr); + + if (!asn1_end_tag(data)) { + goto failed; + } + break; + } + case 8: { + /* approx */ + const char *attrib; + DATA_BLOB value; + + asn1_start_tag(data, ASN1_CONTEXT(filter_tag)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib); + asn1_read_OctetString(data, mem_ctx, &value); + asn1_end_tag(data); + if ((data->has_error) || (attrib == NULL) || (value.data == NULL)) { + goto failed; + } + + ret->operation = LDB_OP_APPROX; + ret->u.comparison.attr = talloc_steal(ret, attrib); + ret->u.comparison.value.data = talloc_steal(ret, value.data); + ret->u.comparison.value.length = value.length; + break; + } + case 9: { + char *oid = NULL, *attr = NULL, *value; + uint8_t dnAttributes; + /* an extended search */ + if (!asn1_start_tag(data, ASN1_CONTEXT(filter_tag))) { + goto failed; + } + + /* FIXME: read carefully rfc2251.txt there are a number of 'MUST's + we need to check we properly implement --SSS */ + /* either oid or type must be defined */ + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(1))) { /* optional */ + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(1)); + asn1_read_LDAPString(data, ret, &oid); + asn1_end_tag(data); + } + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(2))) { /* optional */ + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(2)); + asn1_read_LDAPString(data, ret, &attr); + asn1_end_tag(data); + } + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(3)); + asn1_read_LDAPString(data, ret, &value); + asn1_end_tag(data); + /* dnAttributes is marked as BOOLEAN DEFAULT FALSE + it is not marked as OPTIONAL but openldap tools + do not set this unless it is to be set as TRUE + NOTE: openldap tools do not work with AD as it + seems that AD always requires the dnAttributes + boolean value to be set */ + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(4))) { + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(4)); + asn1_read_uint8(data, &dnAttributes); + asn1_end_tag(data); + } else { + dnAttributes = 0; + } + if ((oid == NULL && attr == NULL) || (value == NULL)) { + goto failed; + } + + if (oid) { + ret->operation = LDB_OP_EXTENDED; + + /* From the RFC2251: If the type field is + absent and matchingRule is present, the matchValue is compared + against all attributes in an entry which support that matchingRule + */ + if (attr) { + ret->u.extended.attr = talloc_steal(ret, attr); + } else { + ret->u.extended.attr = talloc_strdup(ret, "*"); + } + ret->u.extended.rule_id = talloc_steal(ret, oid); + ret->u.extended.value.data = talloc_steal(ret, value); + ret->u.extended.value.length = strlen(value); + ret->u.extended.dnAttributes = dnAttributes; + } else { + ret->operation = LDB_OP_EQUALITY; + ret->u.equality.attr = talloc_steal(ret, attr); + ret->u.equality.value.data = talloc_steal(ret, value); + ret->u.equality.value.length = strlen(value); + } + if (!asn1_end_tag(data)) { + goto failed; + } + break; + } + + default: + DEBUG(0,("Unsupported LDAP filter operation 0x%x\n", filter_tag)); + goto failed; + } + + return ret; + +failed: + talloc_free(ret); + return NULL; +} + + +static void ldap_decode_attrib(TALLOC_CTX *mem_ctx, struct asn1_data *data, + struct ldb_message_element *attrib) +{ + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib->name); + asn1_start_tag(data, ASN1_SET); + while (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + DATA_BLOB blob; + asn1_read_OctetString(data, mem_ctx, &blob); + add_value_to_attrib(mem_ctx, &blob, attrib); + } + asn1_end_tag(data); + asn1_end_tag(data); + +} + +static void ldap_decode_attribs(TALLOC_CTX *mem_ctx, struct asn1_data *data, + struct ldb_message_element **attributes, + int *num_attributes) +{ + asn1_start_tag(data, ASN1_SEQUENCE(0)); + while (asn1_peek_tag(data, ASN1_SEQUENCE(0))) { + struct ldb_message_element attrib; + ZERO_STRUCT(attrib); + ldap_decode_attrib(mem_ctx, data, &attrib); + add_attrib_to_array_talloc(mem_ctx, &attrib, + attributes, num_attributes); + } + asn1_end_tag(data); +} + +/* This routine returns LDAP status codes */ + +_PUBLIC_ NTSTATUS ldap_decode(struct asn1_data *data, struct ldap_message *msg) +{ + uint8_t tag; + + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_Integer(data, &msg->messageid); + + if (!asn1_peek_uint8(data, &tag)) + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + + switch(tag) { + + case ASN1_APPLICATION(LDAP_TAG_BindRequest): { + struct ldap_BindRequest *r = &msg->r.BindRequest; + msg->type = LDAP_TAG_BindRequest; + asn1_start_tag(data, tag); + asn1_read_Integer(data, &r->version); + asn1_read_OctetString_talloc(msg, data, &r->dn); + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(0))) { + int pwlen; + r->creds.password = ""; + r->mechanism = LDAP_AUTH_MECH_SIMPLE; + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(0)); + pwlen = asn1_tag_remaining(data); + if (pwlen == -1) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + if (pwlen != 0) { + char *pw = talloc_array(msg, char, pwlen+1); + if (!pw) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + asn1_read(data, pw, pwlen); + pw[pwlen] = '\0'; + r->creds.password = pw; + } + asn1_end_tag(data); + } else if (asn1_peek_tag(data, ASN1_CONTEXT(3))){ + asn1_start_tag(data, ASN1_CONTEXT(3)); + r->mechanism = LDAP_AUTH_MECH_SASL; + asn1_read_OctetString_talloc(msg, data, &r->creds.SASL.mechanism); + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { /* optional */ + DATA_BLOB tmp_blob = data_blob(NULL, 0); + asn1_read_OctetString(data, msg, &tmp_blob); + r->creds.SASL.secblob = talloc(msg, DATA_BLOB); + if (!r->creds.SASL.secblob) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + *r->creds.SASL.secblob = data_blob_talloc(r->creds.SASL.secblob, + tmp_blob.data, tmp_blob.length); + data_blob_free(&tmp_blob); + } else { + r->creds.SASL.secblob = NULL; + } + asn1_end_tag(data); + } else { + /* Neither Simple nor SASL bind */ + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_BindResponse): { + struct ldap_BindResponse *r = &msg->r.BindResponse; + msg->type = LDAP_TAG_BindResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, &r->response); + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(7))) { + DATA_BLOB tmp_blob = data_blob(NULL, 0); + asn1_read_ContextSimple(data, 7, &tmp_blob); + r->SASL.secblob = talloc(msg, DATA_BLOB); + if (!r->SASL.secblob) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + *r->SASL.secblob = data_blob_talloc(r->SASL.secblob, + tmp_blob.data, tmp_blob.length); + data_blob_free(&tmp_blob); + } else { + r->SASL.secblob = NULL; + } + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION_SIMPLE(LDAP_TAG_UnbindRequest): { + msg->type = LDAP_TAG_UnbindRequest; + asn1_start_tag(data, tag); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchRequest): { + struct ldap_SearchRequest *r = &msg->r.SearchRequest; + msg->type = LDAP_TAG_SearchRequest; + asn1_start_tag(data, tag); + asn1_read_OctetString_talloc(msg, data, &r->basedn); + asn1_read_enumerated(data, (int *)&(r->scope)); + asn1_read_enumerated(data, (int *)&(r->deref)); + asn1_read_Integer(data, &r->sizelimit); + asn1_read_Integer(data, &r->timelimit); + asn1_read_BOOLEAN(data, &r->attributesonly); + + r->tree = ldap_decode_filter_tree(msg, data); + if (r->tree == NULL) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + asn1_start_tag(data, ASN1_SEQUENCE(0)); + + r->num_attributes = 0; + r->attributes = NULL; + + while (asn1_tag_remaining(data) > 0) { + + const char *attr; + if (!asn1_read_OctetString_talloc(msg, data, + &attr)) + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + if (!add_string_to_array(msg, attr, + &r->attributes, + &r->num_attributes)) + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + asn1_end_tag(data); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchResultEntry): { + struct ldap_SearchResEntry *r = &msg->r.SearchResultEntry; + msg->type = LDAP_TAG_SearchResultEntry; + r->attributes = NULL; + r->num_attributes = 0; + asn1_start_tag(data, tag); + asn1_read_OctetString_talloc(msg, data, &r->dn); + ldap_decode_attribs(msg, data, &r->attributes, + &r->num_attributes); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchResultDone): { + struct ldap_Result *r = &msg->r.SearchResultDone; + msg->type = LDAP_TAG_SearchResultDone; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchResultReference): { + struct ldap_SearchResRef *r = &msg->r.SearchResultReference; + msg->type = LDAP_TAG_SearchResultReference; + asn1_start_tag(data, tag); + asn1_read_OctetString_talloc(msg, data, &r->referral); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyRequest): { + struct ldap_ModifyRequest *r = &msg->r.ModifyRequest; + msg->type = LDAP_TAG_ModifyRequest; + asn1_start_tag(data, ASN1_APPLICATION(LDAP_TAG_ModifyRequest)); + asn1_read_OctetString_talloc(msg, data, &r->dn); + asn1_start_tag(data, ASN1_SEQUENCE(0)); + + r->num_mods = 0; + r->mods = NULL; + + while (asn1_tag_remaining(data) > 0) { + struct ldap_mod mod; + int v; + ZERO_STRUCT(mod); + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_enumerated(data, &v); + mod.type = v; + ldap_decode_attrib(msg, data, &mod.attrib); + asn1_end_tag(data); + if (!add_mod_to_array_talloc(msg, &mod, + &r->mods, &r->num_mods)) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + } + + asn1_end_tag(data); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyResponse): { + struct ldap_Result *r = &msg->r.ModifyResponse; + msg->type = LDAP_TAG_ModifyResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_AddRequest): { + struct ldap_AddRequest *r = &msg->r.AddRequest; + msg->type = LDAP_TAG_AddRequest; + asn1_start_tag(data, tag); + asn1_read_OctetString_talloc(msg, data, &r->dn); + + r->attributes = NULL; + r->num_attributes = 0; + ldap_decode_attribs(msg, data, &r->attributes, + &r->num_attributes); + + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_AddResponse): { + struct ldap_Result *r = &msg->r.AddResponse; + msg->type = LDAP_TAG_AddResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION_SIMPLE(LDAP_TAG_DelRequest): { + struct ldap_DelRequest *r = &msg->r.DelRequest; + int len; + char *dn; + msg->type = LDAP_TAG_DelRequest; + asn1_start_tag(data, + ASN1_APPLICATION_SIMPLE(LDAP_TAG_DelRequest)); + len = asn1_tag_remaining(data); + if (len == -1) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + dn = talloc_array(msg, char, len+1); + if (dn == NULL) + break; + asn1_read(data, dn, len); + dn[len] = '\0'; + r->dn = dn; + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_DelResponse): { + struct ldap_Result *r = &msg->r.DelResponse; + msg->type = LDAP_TAG_DelResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyDNRequest): { + struct ldap_ModifyDNRequest *r = &msg->r.ModifyDNRequest; + msg->type = LDAP_TAG_ModifyDNRequest; + asn1_start_tag(data, + ASN1_APPLICATION(LDAP_TAG_ModifyDNRequest)); + asn1_read_OctetString_talloc(msg, data, &r->dn); + asn1_read_OctetString_talloc(msg, data, &r->newrdn); + asn1_read_BOOLEAN(data, &r->deleteolddn); + r->newsuperior = NULL; + if (asn1_tag_remaining(data) > 0) { + int len; + char *newsup; + asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(0)); + len = asn1_tag_remaining(data); + if (len == -1) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + newsup = talloc_array(msg, char, len+1); + if (newsup == NULL) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + asn1_read(data, newsup, len); + newsup[len] = '\0'; + r->newsuperior = newsup; + asn1_end_tag(data); + } + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyDNResponse): { + struct ldap_Result *r = &msg->r.ModifyDNResponse; + msg->type = LDAP_TAG_ModifyDNResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_CompareRequest): { + struct ldap_CompareRequest *r = &msg->r.CompareRequest; + msg->type = LDAP_TAG_CompareRequest; + asn1_start_tag(data, + ASN1_APPLICATION(LDAP_TAG_CompareRequest)); + asn1_read_OctetString_talloc(msg, data, &r->dn); + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_OctetString_talloc(msg, data, &r->attribute); + asn1_read_OctetString(data, msg, &r->value); + if (r->value.data) { + talloc_steal(msg, r->value.data); + } + asn1_end_tag(data); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_CompareResponse): { + struct ldap_Result *r = &msg->r.CompareResponse; + msg->type = LDAP_TAG_CompareResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, r); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION_SIMPLE(LDAP_TAG_AbandonRequest): { + struct ldap_AbandonRequest *r = &msg->r.AbandonRequest; + msg->type = LDAP_TAG_AbandonRequest; + asn1_start_tag(data, tag); + asn1_read_implicit_Integer(data, &r->messageid); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ExtendedRequest): { + struct ldap_ExtendedRequest *r = &msg->r.ExtendedRequest; + DATA_BLOB tmp_blob = data_blob(NULL, 0); + + msg->type = LDAP_TAG_ExtendedRequest; + asn1_start_tag(data,tag); + if (!asn1_read_ContextSimple(data, 0, &tmp_blob)) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + r->oid = blob2string_talloc(msg, tmp_blob); + data_blob_free(&tmp_blob); + if (!r->oid) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(1))) { + asn1_read_ContextSimple(data, 1, &tmp_blob); + r->value = talloc(msg, DATA_BLOB); + if (!r->value) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + *r->value = data_blob_talloc(r->value, tmp_blob.data, tmp_blob.length); + data_blob_free(&tmp_blob); + } else { + r->value = NULL; + } + + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ExtendedResponse): { + struct ldap_ExtendedResponse *r = &msg->r.ExtendedResponse; + DATA_BLOB tmp_blob = data_blob(NULL, 0); + + msg->type = LDAP_TAG_ExtendedResponse; + asn1_start_tag(data, tag); + ldap_decode_response(msg, data, &r->response); + + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(10))) { + asn1_read_ContextSimple(data, 1, &tmp_blob); + r->oid = blob2string_talloc(msg, tmp_blob); + data_blob_free(&tmp_blob); + if (!r->oid) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + } else { + r->oid = NULL; + } + + if (asn1_peek_tag(data, ASN1_CONTEXT_SIMPLE(11))) { + asn1_read_ContextSimple(data, 1, &tmp_blob); + r->value = talloc(msg, DATA_BLOB); + if (!r->value) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + *r->value = data_blob_talloc(r->value, tmp_blob.data, tmp_blob.length); + data_blob_free(&tmp_blob); + } else { + r->value = NULL; + } + + asn1_end_tag(data); + break; + } + default: + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + msg->controls = NULL; + msg->controls_decoded = NULL; + + if (asn1_peek_tag(data, ASN1_CONTEXT(0))) { + int i = 0; + struct ldb_control **ctrl = NULL; + bool *decoded = NULL; + + asn1_start_tag(data, ASN1_CONTEXT(0)); + + while (asn1_peek_tag(data, ASN1_SEQUENCE(0))) { + DATA_BLOB value; + /* asn1_start_tag(data, ASN1_SEQUENCE(0)); */ + + ctrl = talloc_realloc(msg, ctrl, struct ldb_control *, i+2); + if (!ctrl) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + + decoded = talloc_realloc(msg, decoded, bool, i+1); + if (!decoded) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + + ctrl[i] = talloc(ctrl, struct ldb_control); + if (!ctrl[i]) { + return NT_STATUS_LDAP(LDAP_OPERATIONS_ERROR); + } + + if (!ldap_decode_control_wrapper(ctrl, data, ctrl[i], &value)) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + if (!ldap_decode_control_value(ctrl, value, ctrl[i])) { + if (ctrl[i]->critical) { + ctrl[i]->data = NULL; + decoded[i] = false; + i++; + } else { + talloc_free(ctrl[i]); + ctrl[i] = NULL; + } + } else { + decoded[i] = true; + i++; + } + } + + if (ctrl != NULL) { + ctrl[i] = NULL; + } + + msg->controls = ctrl; + msg->controls_decoded = decoded; + + asn1_end_tag(data); + } + + asn1_end_tag(data); + if ((data->has_error) || (data->nesting != NULL)) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + return NT_STATUS_OK; +} + + +/* + return NT_STATUS_OK if a blob has enough bytes in it to be a full + ldap packet. Set packet_size if true. +*/ +NTSTATUS ldap_full_packet(void *private_data, DATA_BLOB blob, size_t *packet_size) +{ + return asn1_full_tag(blob, ASN1_SEQUENCE(0), packet_size); +} diff --git a/source4/libcli/ldap/ldap.h b/source4/libcli/ldap/ldap.h new file mode 100644 index 0000000000..a336a7ad85 --- /dev/null +++ b/source4/libcli/ldap/ldap.h @@ -0,0 +1,261 @@ +/* + Unix SMB/CIFS Implementation. + LDAP protocol helper functions for SAMBA + Copyright (C) Volker Lendecke 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#ifndef _SMB_LDAP_H +#define _SMB_LDAP_H + +#include "lib/ldb/include/ldb.h" +#include "librpc/gen_ndr/misc.h" + +enum ldap_request_tag { + LDAP_TAG_BindRequest = 0, + LDAP_TAG_BindResponse = 1, + LDAP_TAG_UnbindRequest = 2, + LDAP_TAG_SearchRequest = 3, + LDAP_TAG_SearchResultEntry = 4, + LDAP_TAG_SearchResultDone = 5, + LDAP_TAG_ModifyRequest = 6, + LDAP_TAG_ModifyResponse = 7, + LDAP_TAG_AddRequest = 8, + LDAP_TAG_AddResponse = 9, + LDAP_TAG_DelRequest = 10, + LDAP_TAG_DelResponse = 11, + LDAP_TAG_ModifyDNRequest = 12, + LDAP_TAG_ModifyDNResponse = 13, + LDAP_TAG_CompareRequest = 14, + LDAP_TAG_CompareResponse = 15, + LDAP_TAG_AbandonRequest = 16, + LDAP_TAG_SearchResultReference = 19, + LDAP_TAG_ExtendedRequest = 23, + LDAP_TAG_ExtendedResponse = 24 +}; + +enum ldap_auth_mechanism { + LDAP_AUTH_MECH_SIMPLE = 0, + LDAP_AUTH_MECH_SASL = 3 +}; + +enum ldap_result_code { + LDAP_SUCCESS = 0, + LDAP_OPERATIONS_ERROR = 1, + LDAP_PROTOCOL_ERROR = 2, + LDAP_TIME_LIMIT_EXCEEDED = 3, + LDAP_SIZE_LIMIT_EXCEEDED = 4, + LDAP_COMPARE_FALSE = 5, + LDAP_COMPARE_TRUE = 6, + LDAP_AUTH_METHOD_NOT_SUPPORTED = 7, + LDAP_STRONG_AUTH_REQUIRED = 8, + LDAP_REFERRAL = 10, + LDAP_ADMIN_LIMIT_EXCEEDED = 11, + LDAP_UNAVAILABLE_CRITICAL_EXTENSION = 12, + LDAP_CONFIDENTIALITY_REQUIRED = 13, + LDAP_SASL_BIND_IN_PROGRESS = 14, + LDAP_NO_SUCH_ATTRIBUTE = 16, + LDAP_UNDEFINED_ATTRIBUTE_TYPE = 17, + LDAP_INAPPROPRIATE_MATCHING = 18, + LDAP_CONSTRAINT_VIOLATION = 19, + LDAP_ATTRIBUTE_OR_VALUE_EXISTS = 20, + LDAP_INVALID_ATTRIBUTE_SYNTAX = 21, + LDAP_NO_SUCH_OBJECT = 32, + LDAP_ALIAS_PROBLEM = 33, + LDAP_INVALID_DN_SYNTAX = 34, + LDAP_ALIAS_DEREFERENCING_PROBLEM = 36, + LDAP_INAPPROPRIATE_AUTHENTICATION = 48, + LDAP_INVALID_CREDENTIALS = 49, + LDAP_INSUFFICIENT_ACCESS_RIGHTS = 50, + LDAP_BUSY = 51, + LDAP_UNAVAILABLE = 52, + LDAP_UNWILLING_TO_PERFORM = 53, + LDAP_LOOP_DETECT = 54, + LDAP_NAMING_VIOLATION = 64, + LDAP_OBJECT_CLASS_VIOLATION = 65, + LDAP_NOT_ALLOWED_ON_NON_LEAF = 66, + LDAP_NOT_ALLOWED_ON_RDN = 67, + LDAP_ENTRY_ALREADY_EXISTS = 68, + LDAP_OBJECT_CLASS_MODS_PROHIBITED = 69, + LDAP_AFFECTS_MULTIPLE_DSAS = 71, + LDAP_OTHER = 80 +}; + +struct ldap_Result { + int resultcode; + const char *dn; + const char *errormessage; + const char *referral; +}; + +struct ldap_BindRequest { + int version; + const char *dn; + enum ldap_auth_mechanism mechanism; + union { + const char *password; + struct { + const char *mechanism; + DATA_BLOB *secblob;/* optional */ + } SASL; + } creds; +}; + +struct ldap_BindResponse { + struct ldap_Result response; + union { + DATA_BLOB *secblob;/* optional */ + } SASL; +}; + +struct ldap_UnbindRequest { + uint8_t __dummy; +}; + +enum ldap_scope { + LDAP_SEARCH_SCOPE_BASE = 0, + LDAP_SEARCH_SCOPE_SINGLE = 1, + LDAP_SEARCH_SCOPE_SUB = 2 +}; + +enum ldap_deref { + LDAP_DEREFERENCE_NEVER = 0, + LDAP_DEREFERENCE_IN_SEARCHING = 1, + LDAP_DEREFERENCE_FINDING_BASE = 2, + LDAP_DEREFERENCE_ALWAYS +}; + +struct ldap_SearchRequest { + const char *basedn; + enum ldap_scope scope; + enum ldap_deref deref; + uint32_t timelimit; + uint32_t sizelimit; + bool attributesonly; + struct ldb_parse_tree *tree; + int num_attributes; + const char **attributes; +}; + +struct ldap_SearchResEntry { + const char *dn; + int num_attributes; + struct ldb_message_element *attributes; +}; + +struct ldap_SearchResRef { + const char *referral; +}; + +enum ldap_modify_type { + LDAP_MODIFY_NONE = -1, + LDAP_MODIFY_ADD = 0, + LDAP_MODIFY_DELETE = 1, + LDAP_MODIFY_REPLACE = 2 +}; + +struct ldap_mod { + enum ldap_modify_type type; + struct ldb_message_element attrib; +}; + +struct ldap_ModifyRequest { + const char *dn; + int num_mods; + struct ldap_mod *mods; +}; + +struct ldap_AddRequest { + const char *dn; + int num_attributes; + struct ldb_message_element *attributes; +}; + +struct ldap_DelRequest { + const char *dn; +}; + +struct ldap_ModifyDNRequest { + const char *dn; + const char *newrdn; + bool deleteolddn; + const char *newsuperior;/* optional */ +}; + +struct ldap_CompareRequest { + const char *dn; + const char *attribute; + DATA_BLOB value; +}; + +struct ldap_AbandonRequest { + uint32_t messageid; +}; + +struct ldap_ExtendedRequest { + const char *oid; + DATA_BLOB *value;/* optional */ +}; + +struct ldap_ExtendedResponse { + struct ldap_Result response; + const char *oid;/* optional */ + DATA_BLOB *value;/* optional */ +}; + +union ldap_Request { + struct ldap_Result GeneralResult; + struct ldap_BindRequest BindRequest; + struct ldap_BindResponse BindResponse; + struct ldap_UnbindRequest UnbindRequest; + struct ldap_SearchRequest SearchRequest; + struct ldap_SearchResEntry SearchResultEntry; + struct ldap_Result SearchResultDone; + struct ldap_SearchResRef SearchResultReference; + struct ldap_ModifyRequest ModifyRequest; + struct ldap_Result ModifyResponse; + struct ldap_AddRequest AddRequest; + struct ldap_Result AddResponse; + struct ldap_DelRequest DelRequest; + struct ldap_Result DelResponse; + struct ldap_ModifyDNRequest ModifyDNRequest; + struct ldap_Result ModifyDNResponse; + struct ldap_CompareRequest CompareRequest; + struct ldap_Result CompareResponse; + struct ldap_AbandonRequest AbandonRequest; + struct ldap_ExtendedRequest ExtendedRequest; + struct ldap_ExtendedResponse ExtendedResponse; +}; + + +struct ldap_message { + int messageid; + enum ldap_request_tag type; + union ldap_Request r; + struct ldb_control **controls; + bool *controls_decoded; +}; + +struct event_context; +struct cli_credentials; +struct dom_sid; +struct asn1_data; + +struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx); +NTSTATUS ldap_decode(struct asn1_data *data, struct ldap_message *msg); +bool ldap_encode(struct ldap_message *msg, DATA_BLOB *result, TALLOC_CTX *mem_ctx); + +#endif diff --git a/source4/libcli/ldap/ldap_bind.c b/source4/libcli/ldap/ldap_bind.c new file mode 100644 index 0000000000..65673116be --- /dev/null +++ b/source4/libcli/ldap/ldap_bind.c @@ -0,0 +1,411 @@ +/* + Unix SMB/CIFS mplementation. + + LDAP bind calls + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Volker Lendecke 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "libcli/ldap/ldap.h" +#include "libcli/ldap/ldap_proto.h" +#include "libcli/ldap/ldap_client.h" +#include "lib/tls/tls.h" +#include "auth/gensec/gensec.h" +#include "auth/credentials/credentials.h" +#include "lib/stream/packet.h" +#include "param/param.h" + +struct ldap_simple_creds { + const char *dn; + const char *pw; +}; + +_PUBLIC_ NTSTATUS ldap_rebind(struct ldap_connection *conn) +{ + NTSTATUS status; + struct ldap_simple_creds *creds; + + switch (conn->bind.type) { + case LDAP_BIND_SASL: + status = ldap_bind_sasl(conn, (struct cli_credentials *)conn->bind.creds, + conn->lp_ctx); + break; + + case LDAP_BIND_SIMPLE: + creds = (struct ldap_simple_creds *)conn->bind.creds; + + if (creds == NULL) { + return NT_STATUS_UNSUCCESSFUL; + } + + status = ldap_bind_simple(conn, creds->dn, creds->pw); + break; + + default: + return NT_STATUS_UNSUCCESSFUL; + } + + return status; +} + + +static struct ldap_message *new_ldap_simple_bind_msg(struct ldap_connection *conn, + const char *dn, const char *pw) +{ + struct ldap_message *res; + + res = new_ldap_message(conn); + if (!res) { + return NULL; + } + + res->type = LDAP_TAG_BindRequest; + res->r.BindRequest.version = 3; + res->r.BindRequest.dn = talloc_strdup(res, dn); + res->r.BindRequest.mechanism = LDAP_AUTH_MECH_SIMPLE; + res->r.BindRequest.creds.password = talloc_strdup(res, pw); + res->controls = NULL; + + return res; +} + + +/* + perform a simple username/password bind +*/ +_PUBLIC_ NTSTATUS ldap_bind_simple(struct ldap_connection *conn, + const char *userdn, const char *password) +{ + struct ldap_request *req; + struct ldap_message *msg; + const char *dn, *pw; + NTSTATUS status; + + if (conn == NULL) { + return NT_STATUS_INVALID_CONNECTION; + } + + if (userdn) { + dn = userdn; + } else { + if (conn->auth_dn) { + dn = conn->auth_dn; + } else { + dn = ""; + } + } + + if (password) { + pw = password; + } else { + if (conn->simple_pw) { + pw = conn->simple_pw; + } else { + pw = ""; + } + } + + msg = new_ldap_simple_bind_msg(conn, dn, pw); + NT_STATUS_HAVE_NO_MEMORY(msg); + + /* send the request */ + req = ldap_request_send(conn, msg); + talloc_free(msg); + NT_STATUS_HAVE_NO_MEMORY(req); + + /* wait for replies */ + status = ldap_request_wait(req); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return status; + } + + /* check its a valid reply */ + msg = req->replies[0]; + if (msg->type != LDAP_TAG_BindResponse) { + talloc_free(req); + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + + status = ldap_check_response(conn, &msg->r.BindResponse.response); + + talloc_free(req); + + if (NT_STATUS_IS_OK(status)) { + struct ldap_simple_creds *creds = talloc(conn, struct ldap_simple_creds); + if (creds == NULL) { + return NT_STATUS_NO_MEMORY; + } + creds->dn = talloc_strdup(creds, dn); + creds->pw = talloc_strdup(creds, pw); + if (creds->dn == NULL || creds->pw == NULL) { + return NT_STATUS_NO_MEMORY; + } + conn->bind.type = LDAP_BIND_SIMPLE; + conn->bind.creds = creds; + } + + return status; +} + + +static struct ldap_message *new_ldap_sasl_bind_msg(struct ldap_connection *conn, + const char *sasl_mechanism, + DATA_BLOB *secblob) +{ + struct ldap_message *res; + + res = new_ldap_message(conn); + if (!res) { + return NULL; + } + + res->type = LDAP_TAG_BindRequest; + res->r.BindRequest.version = 3; + res->r.BindRequest.dn = ""; + res->r.BindRequest.mechanism = LDAP_AUTH_MECH_SASL; + res->r.BindRequest.creds.SASL.mechanism = talloc_strdup(res, sasl_mechanism); + if (secblob) { + res->r.BindRequest.creds.SASL.secblob = talloc(res, DATA_BLOB); + if (!res->r.BindRequest.creds.SASL.secblob) { + talloc_free(res); + return NULL; + } + *res->r.BindRequest.creds.SASL.secblob = *secblob; + } else { + res->r.BindRequest.creds.SASL.secblob = NULL; + } + res->controls = NULL; + + return res; +} + + +/* + perform a sasl bind using the given credentials +*/ +_PUBLIC_ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, + struct cli_credentials *creds, + struct loadparm_context *lp_ctx) +{ + NTSTATUS status; + TALLOC_CTX *tmp_ctx = NULL; + + DATA_BLOB input = data_blob(NULL, 0); + DATA_BLOB output = data_blob(NULL, 0); + + struct ldap_message **sasl_mechs_msgs; + struct ldap_SearchResEntry *search; + int count, i; + + const char **sasl_names; + uint32_t old_gensec_features; + static const char *supported_sasl_mech_attrs[] = { + "supportedSASLMechanisms", + NULL + }; + + gensec_init(lp_ctx); + + status = gensec_client_start(conn, &conn->gensec, + conn->event.event_ctx, lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to start GENSEC engine (%s)\n", nt_errstr(status))); + goto failed; + } + + /* require Kerberos SIGN/SEAL only if we don't use SSL + * Windows seem not to like double encryption */ + old_gensec_features = cli_credentials_get_gensec_features(creds); + if (tls_enabled(conn->sock)) { + cli_credentials_set_gensec_features(creds, old_gensec_features & ~(GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL)); + } + + /* this call also sets the gensec_want_features */ + status = gensec_set_credentials(conn->gensec, creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to set GENSEC creds: %s\n", + nt_errstr(status))); + goto failed; + } + + /* reset the original gensec_features (on the credentials + * context, so we don't tatoo it ) */ + cli_credentials_set_gensec_features(creds, old_gensec_features); + + if (conn->host) { + status = gensec_set_target_hostname(conn->gensec, conn->host); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to set GENSEC target hostname: %s\n", + nt_errstr(status))); + goto failed; + } + } + + status = gensec_set_target_service(conn->gensec, "ldap"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to set GENSEC target service: %s\n", + nt_errstr(status))); + goto failed; + } + + status = ildap_search(conn, "", LDAP_SEARCH_SCOPE_BASE, "", supported_sasl_mech_attrs, + false, NULL, NULL, &sasl_mechs_msgs); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: %s\n", + nt_errstr(status))); + goto failed; + } + + count = ildap_count_entries(conn, sasl_mechs_msgs); + if (count != 1) { + DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of replies: %d\n", + count)); + goto failed; + } + + tmp_ctx = talloc_new(conn); + if (tmp_ctx == NULL) goto failed; + + search = &sasl_mechs_msgs[0]->r.SearchResultEntry; + if (search->num_attributes != 1) { + DEBUG(1, ("Failed to inquire of target's available sasl mechs in rootdse search: wrong number of attributes: %d\n", + search->num_attributes)); + goto failed; + } + + sasl_names = talloc_array(tmp_ctx, const char *, search->attributes[0].num_values + 1); + if (!sasl_names) { + DEBUG(1, ("talloc_arry(char *, %d) failed\n", + count)); + goto failed; + } + + for (i=0; i<search->attributes[0].num_values; i++) { + sasl_names[i] = (const char *)search->attributes[0].values[i].data; + } + sasl_names[i] = NULL; + + status = gensec_start_mech_by_sasl_list(conn->gensec, sasl_names); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("None of the %d proposed SASL mechs were acceptable: %s\n", + count, nt_errstr(status))); + goto failed; + } + + while (1) { + NTSTATUS gensec_status; + struct ldap_message *response; + struct ldap_message *msg; + struct ldap_request *req; + int result = LDAP_OTHER; + + status = gensec_update(conn->gensec, tmp_ctx, + input, + &output); + /* The status value here, from GENSEC is vital to the security + * of the system. Even if the other end accepts, if GENSEC + * claims 'MORE_PROCESSING_REQUIRED' then you must keep + * feeding it blobs, or else the remote host/attacker might + * avoid mutal authentication requirements. + * + * Likewise, you must not feed GENSEC too much (after the OK), + * it doesn't like that either + */ + + gensec_status = status; + + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) && + !NT_STATUS_IS_OK(status)) { + break; + } + if (NT_STATUS_IS_OK(status) && output.length == 0) { + break; + } + + /* Perhaps we should make gensec_start_mech_by_sasl_list() return the name we got? */ + msg = new_ldap_sasl_bind_msg(tmp_ctx, conn->gensec->ops->sasl_name, (output.data?&output:NULL)); + if (msg == NULL) { + status = NT_STATUS_NO_MEMORY; + goto failed; + } + + req = ldap_request_send(conn, msg); + if (req == NULL) { + status = NT_STATUS_NO_MEMORY; + goto failed; + } + talloc_steal(tmp_ctx, req); + + status = ldap_result_n(req, 0, &response); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + if (response->type != LDAP_TAG_BindResponse) { + status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + goto failed; + } + + result = response->r.BindResponse.response.resultcode; + + if (result != LDAP_SUCCESS && result != LDAP_SASL_BIND_IN_PROGRESS) { + status = ldap_check_response(conn, + &response->r.BindResponse.response); + break; + } + + /* This is where we check if GENSEC wanted to be fed more data */ + if (!NT_STATUS_EQUAL(gensec_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + break; + } + if (response->r.BindResponse.SASL.secblob) { + input = *response->r.BindResponse.SASL.secblob; + } else { + input = data_blob(NULL, 0); + } + } + + talloc_free(tmp_ctx); + + if (NT_STATUS_IS_OK(status)) { + struct socket_context *sasl_socket; + status = gensec_socket_init(conn->gensec, + conn->sock, + conn->event.event_ctx, + ldap_read_io_handler, + conn, + &sasl_socket); + if (!NT_STATUS_IS_OK(status)) goto failed; + + conn->sock = sasl_socket; + packet_set_socket(conn->packet, conn->sock); + + conn->bind.type = LDAP_BIND_SASL; + conn->bind.creds = creds; + } + + return status; + +failed: + talloc_free(tmp_ctx); + talloc_free(conn->gensec); + conn->gensec = NULL; + return status; +} diff --git a/source4/libcli/ldap/ldap_client.c b/source4/libcli/ldap/ldap_client.c new file mode 100644 index 0000000000..844238afdb --- /dev/null +++ b/source4/libcli/ldap/ldap_client.c @@ -0,0 +1,824 @@ +/* + Unix SMB/CIFS mplementation. + LDAP protocol helper functions for SAMBA + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + Copyright (C) Stefan Metzmacher 2004 + Copyright (C) Simo Sorce 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/util/asn1.h" +#include "lib/util/dlinklist.h" +#include "lib/events/events.h" +#include "lib/socket/socket.h" +#include "libcli/ldap/ldap.h" +#include "libcli/ldap/ldap_proto.h" +#include "libcli/ldap/ldap_client.h" +#include "libcli/composite/composite.h" +#include "lib/stream/packet.h" +#include "lib/tls/tls.h" +#include "auth/gensec/gensec.h" +#include "system/time.h" +#include "param/param.h" +#include "libcli/resolve/resolve.h" + +/** + create a new ldap_connection stucture. The event context is optional +*/ +_PUBLIC_ struct ldap_connection *ldap4_new_connection(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct event_context *ev) +{ + struct ldap_connection *conn; + + if (ev == NULL) { + return NULL; + } + + conn = talloc_zero(mem_ctx, struct ldap_connection); + if (conn == NULL) { + return NULL; + } + + conn->next_messageid = 1; + conn->event.event_ctx = ev; + + conn->lp_ctx = lp_ctx; + + /* set a reasonable request timeout */ + conn->timeout = 60; + + /* explicitly avoid reconnections by default */ + conn->reconnect.max_retries = 0; + + return conn; +} + +/* + the connection is dead +*/ +static void ldap_connection_dead(struct ldap_connection *conn) +{ + struct ldap_request *req; + + /* return an error for any pending request ... */ + while (conn->pending) { + req = conn->pending; + DLIST_REMOVE(req->conn->pending, req); + req->state = LDAP_REQUEST_DONE; + req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + if (req->async.fn) { + req->async.fn(req); + } + } + + talloc_free(conn->sock); /* this will also free event.fde */ + talloc_free(conn->packet); + conn->sock = NULL; + conn->event.fde = NULL; + conn->packet = NULL; +} + +static void ldap_reconnect(struct ldap_connection *conn); + +/* + handle packet errors +*/ +static void ldap_error_handler(void *private_data, NTSTATUS status) +{ + struct ldap_connection *conn = talloc_get_type(private_data, + struct ldap_connection); + ldap_connection_dead(conn); + + /* but try to reconnect so that the ldb client can go on */ + ldap_reconnect(conn); +} + + +/* + match up with a pending message, adding to the replies list +*/ +static void ldap_match_message(struct ldap_connection *conn, struct ldap_message *msg) +{ + struct ldap_request *req; + int i; + + for (req=conn->pending; req; req=req->next) { + if (req->messageid == msg->messageid) break; + } + /* match a zero message id to the last request sent. + It seems that servers send 0 if unable to parse */ + if (req == NULL && msg->messageid == 0) { + req = conn->pending; + } + if (req == NULL) { + DEBUG(0,("ldap: no matching message id for %u\n", + msg->messageid)); + talloc_free(msg); + return; + } + + /* Check for undecoded critical extensions */ + for (i=0; msg->controls && msg->controls[i]; i++) { + if (!msg->controls_decoded[i] && + msg->controls[i]->critical) { + req->status = NT_STATUS_LDAP(LDAP_UNAVAILABLE_CRITICAL_EXTENSION); + req->state = LDAP_REQUEST_DONE; + DLIST_REMOVE(conn->pending, req); + if (req->async.fn) { + req->async.fn(req); + } + return; + } + } + + /* add to the list of replies received */ + talloc_steal(req, msg); + req->replies = talloc_realloc(req, req->replies, + struct ldap_message *, req->num_replies+1); + if (req->replies == NULL) { + req->status = NT_STATUS_NO_MEMORY; + req->state = LDAP_REQUEST_DONE; + DLIST_REMOVE(conn->pending, req); + if (req->async.fn) { + req->async.fn(req); + } + return; + } + + req->replies[req->num_replies] = talloc_steal(req->replies, msg); + req->num_replies++; + + if (msg->type != LDAP_TAG_SearchResultEntry && + msg->type != LDAP_TAG_SearchResultReference) { + /* currently only search results expect multiple + replies */ + req->state = LDAP_REQUEST_DONE; + DLIST_REMOVE(conn->pending, req); + } + + if (req->async.fn) { + req->async.fn(req); + } +} + + +/* + decode/process LDAP data +*/ +static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob) +{ + NTSTATUS status; + struct ldap_connection *conn = talloc_get_type(private_data, + struct ldap_connection); + struct ldap_message *msg = talloc(conn, struct ldap_message); + struct asn1_data *asn1 = asn1_init(conn); + + if (asn1 == NULL || msg == NULL) { + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + if (!asn1_load(asn1, blob)) { + talloc_free(msg); + talloc_free(asn1); + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + + status = ldap_decode(asn1, msg); + if (!NT_STATUS_IS_OK(status)) { + asn1_free(asn1); + return status; + } + + ldap_match_message(conn, msg); + + data_blob_free(&blob); + asn1_free(asn1); + return NT_STATUS_OK; +} + +/* Handle read events, from the GENSEC socket callback, or real events */ +void ldap_read_io_handler(void *private_data, uint16_t flags) +{ + struct ldap_connection *conn = talloc_get_type(private_data, + struct ldap_connection); + packet_recv(conn->packet); +} + +/* + handle ldap socket events +*/ +static void ldap_io_handler(struct event_context *ev, struct fd_event *fde, + uint16_t flags, void *private_data) +{ + struct ldap_connection *conn = talloc_get_type(private_data, + struct ldap_connection); + if (flags & EVENT_FD_WRITE) { + packet_queue_run(conn->packet); + if (!tls_enabled(conn->sock)) return; + } + if (flags & EVENT_FD_READ) { + ldap_read_io_handler(private_data, flags); + } +} + +/* + parse a ldap URL +*/ +static NTSTATUS ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url, + char **host, uint16_t *port, bool *ldaps) +{ + int tmp_port = 0; + char protocol[11]; + char tmp_host[1025]; + int ret; + + /* Paranoia check */ + SMB_ASSERT(sizeof(protocol)>10 && sizeof(tmp_host)>254); + + ret = sscanf(url, "%10[^:]://%254[^:/]:%d", protocol, tmp_host, &tmp_port); + if (ret < 2) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strequal(protocol, "ldap")) { + *port = 389; + *ldaps = false; + } else if (strequal(protocol, "ldaps")) { + *port = 636; + *ldaps = true; + } else { + DEBUG(0, ("unrecognised ldap protocol (%s)!\n", protocol)); + return NT_STATUS_PROTOCOL_UNREACHABLE; + } + + if (tmp_port != 0) + *port = tmp_port; + + *host = talloc_strdup(mem_ctx, tmp_host); + NT_STATUS_HAVE_NO_MEMORY(*host); + + return NT_STATUS_OK; +} + +/* + connect to a ldap server +*/ + +struct ldap_connect_state { + struct composite_context *ctx; + struct ldap_connection *conn; +}; + +static void ldap_connect_recv_unix_conn(struct composite_context *ctx); +static void ldap_connect_recv_tcp_conn(struct composite_context *ctx); + +_PUBLIC_ struct composite_context *ldap_connect_send(struct ldap_connection *conn, + const char *url) +{ + struct composite_context *result, *ctx; + struct ldap_connect_state *state; + char protocol[11]; + int ret; + + result = talloc_zero(conn, struct composite_context); + if (result == NULL) goto failed; + result->state = COMPOSITE_STATE_IN_PROGRESS; + result->async.fn = NULL; + result->event_ctx = conn->event.event_ctx; + + state = talloc(result, struct ldap_connect_state); + if (state == NULL) goto failed; + state->ctx = result; + result->private_data = state; + + state->conn = conn; + + if (conn->reconnect.url == NULL) { + conn->reconnect.url = talloc_strdup(conn, url); + if (conn->reconnect.url == NULL) goto failed; + } + + /* Paranoia check */ + SMB_ASSERT(sizeof(protocol)>10); + + ret = sscanf(url, "%10[^:]://", protocol); + if (ret < 1) { + return NULL; + } + + if (strequal(protocol, "ldapi")) { + struct socket_address *unix_addr; + char path[1025]; + + NTSTATUS status = socket_create("unix", SOCKET_TYPE_STREAM, &conn->sock, 0); + if (!NT_STATUS_IS_OK(status)) { + return NULL; + } + talloc_steal(conn, conn->sock); + SMB_ASSERT(sizeof(protocol)>10); + SMB_ASSERT(sizeof(path)>1024); + + /* LDAPI connections are to localhost, so give the local host name as the target for gensec */ + conn->host = talloc_asprintf(conn, "%s.%s", lp_netbios_name(conn->lp_ctx), lp_realm(conn->lp_ctx)); + if (composite_nomem(conn->host, state->ctx)) { + return result; + } + + /* The %c specifier doesn't null terminate :-( */ + ZERO_STRUCT(path); + ret = sscanf(url, "%10[^:]://%1025c", protocol, path); + if (ret < 2) { + composite_error(state->ctx, NT_STATUS_INVALID_PARAMETER); + return result; + } + + rfc1738_unescape(path); + + unix_addr = socket_address_from_strings(conn, conn->sock->backend_name, + path, 0); + if (!unix_addr) { + return NULL; + } + + ctx = socket_connect_send(conn->sock, NULL, unix_addr, + 0, lp_resolve_context(conn->lp_ctx), conn->event.event_ctx); + ctx->async.fn = ldap_connect_recv_unix_conn; + ctx->async.private_data = state; + return result; + } else { + NTSTATUS status = ldap_parse_basic_url(conn, url, &conn->host, + &conn->port, &conn->ldaps); + if (!NT_STATUS_IS_OK(state->ctx->status)) { + composite_error(state->ctx, status); + return result; + } + + ctx = socket_connect_multi_send(state, conn->host, 1, &conn->port, + lp_resolve_context(conn->lp_ctx), conn->event.event_ctx); + if (ctx == NULL) goto failed; + + ctx->async.fn = ldap_connect_recv_tcp_conn; + ctx->async.private_data = state; + return result; + } + failed: + talloc_free(result); + return NULL; +} + +static void ldap_connect_got_sock(struct composite_context *ctx, + struct ldap_connection *conn) +{ + /* setup a handler for events on this socket */ + conn->event.fde = event_add_fd(conn->event.event_ctx, conn->sock, + socket_get_fd(conn->sock), + EVENT_FD_READ | EVENT_FD_AUTOCLOSE, ldap_io_handler, conn); + if (conn->event.fde == NULL) { + composite_error(ctx, NT_STATUS_INTERNAL_ERROR); + return; + } + + socket_set_flags(conn->sock, SOCKET_FLAG_NOCLOSE); + + talloc_steal(conn, conn->sock); + if (conn->ldaps) { + struct socket_context *tls_socket; + char *cafile = private_path(conn->sock, conn->lp_ctx, lp_tls_cafile(conn->lp_ctx)); + + if (!cafile || !*cafile) { + talloc_free(conn->sock); + return; + } + + tls_socket = tls_init_client(conn->sock, conn->event.fde, cafile); + talloc_free(cafile); + + if (tls_socket == NULL) { + talloc_free(conn->sock); + return; + } + talloc_unlink(conn, conn->sock); + conn->sock = tls_socket; + talloc_steal(conn, conn->sock); + } + + conn->packet = packet_init(conn); + if (conn->packet == NULL) { + talloc_free(conn->sock); + return; + } + + packet_set_private(conn->packet, conn); + packet_set_socket(conn->packet, conn->sock); + packet_set_callback(conn->packet, ldap_recv_handler); + packet_set_full_request(conn->packet, ldap_full_packet); + packet_set_error_handler(conn->packet, ldap_error_handler); + packet_set_event_context(conn->packet, conn->event.event_ctx); + packet_set_fde(conn->packet, conn->event.fde); + packet_set_serialise(conn->packet); + + composite_done(ctx); +} + +static void ldap_connect_recv_tcp_conn(struct composite_context *ctx) +{ + struct ldap_connect_state *state = + talloc_get_type(ctx->async.private_data, + struct ldap_connect_state); + struct ldap_connection *conn = state->conn; + uint16_t port; + NTSTATUS status = socket_connect_multi_recv(ctx, state, &conn->sock, + &port); + if (!NT_STATUS_IS_OK(status)) { + composite_error(state->ctx, status); + return; + } + + ldap_connect_got_sock(state->ctx, conn); +} + +static void ldap_connect_recv_unix_conn(struct composite_context *ctx) +{ + struct ldap_connect_state *state = + talloc_get_type(ctx->async.private_data, + struct ldap_connect_state); + struct ldap_connection *conn = state->conn; + + NTSTATUS status = socket_connect_recv(ctx); + + if (!NT_STATUS_IS_OK(state->ctx->status)) { + composite_error(state->ctx, status); + return; + } + + ldap_connect_got_sock(state->ctx, conn); +} + +_PUBLIC_ NTSTATUS ldap_connect_recv(struct composite_context *ctx) +{ + NTSTATUS status = composite_wait(ctx); + talloc_free(ctx); + return status; +} + +_PUBLIC_ NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url) +{ + struct composite_context *ctx = ldap_connect_send(conn, url); + return ldap_connect_recv(ctx); +} + +/* set reconnect parameters */ + +_PUBLIC_ void ldap_set_reconn_params(struct ldap_connection *conn, int max_retries) +{ + if (conn) { + conn->reconnect.max_retries = max_retries; + conn->reconnect.retries = 0; + conn->reconnect.previous = time(NULL); + } +} + +/* Actually this function is NOT ASYNC safe, FIXME? */ +static void ldap_reconnect(struct ldap_connection *conn) +{ + NTSTATUS status; + time_t now = time(NULL); + + /* do we have set up reconnect ? */ + if (conn->reconnect.max_retries == 0) return; + + /* is the retry time expired ? */ + if (now > conn->reconnect.previous + 30) { + conn->reconnect.retries = 0; + conn->reconnect.previous = now; + } + + /* are we reconnectind too often and too fast? */ + if (conn->reconnect.retries > conn->reconnect.max_retries) return; + + /* keep track of the number of reconnections */ + conn->reconnect.retries++; + + /* reconnect */ + status = ldap_connect(conn, conn->reconnect.url); + if ( ! NT_STATUS_IS_OK(status)) { + return; + } + + /* rebind */ + status = ldap_rebind(conn); + if ( ! NT_STATUS_IS_OK(status)) { + ldap_connection_dead(conn); + } +} + +/* destroy an open ldap request */ +static int ldap_request_destructor(struct ldap_request *req) +{ + if (req->state == LDAP_REQUEST_PENDING) { + DLIST_REMOVE(req->conn->pending, req); + } + return 0; +} + +/* + called on timeout of a ldap request +*/ +static void ldap_request_timeout(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct ldap_request *req = talloc_get_type(private_data, struct ldap_request); + req->status = NT_STATUS_IO_TIMEOUT; + if (req->state == LDAP_REQUEST_PENDING) { + DLIST_REMOVE(req->conn->pending, req); + } + req->state = LDAP_REQUEST_DONE; + if (req->async.fn) { + req->async.fn(req); + } +} + + +/* + called on completion of a one-way ldap request +*/ +static void ldap_request_complete(struct event_context *ev, struct timed_event *te, + struct timeval t, void *private_data) +{ + struct ldap_request *req = talloc_get_type(private_data, struct ldap_request); + if (req->async.fn) { + req->async.fn(req); + } +} + +/* + send a ldap message - async interface +*/ +_PUBLIC_ struct ldap_request *ldap_request_send(struct ldap_connection *conn, + struct ldap_message *msg) +{ + struct ldap_request *req; + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + + req = talloc_zero(conn, struct ldap_request); + if (req == NULL) return NULL; + + if (conn->sock == NULL) { + status = NT_STATUS_INVALID_CONNECTION; + goto failed; + } + + req->state = LDAP_REQUEST_SEND; + req->conn = conn; + req->messageid = conn->next_messageid++; + if (conn->next_messageid == 0) { + conn->next_messageid = 1; + } + req->type = msg->type; + if (req->messageid == -1) { + goto failed; + } + + talloc_set_destructor(req, ldap_request_destructor); + + msg->messageid = req->messageid; + + if (!ldap_encode(msg, &req->data, req)) { + status = NT_STATUS_INTERNAL_ERROR; + goto failed; + } + + status = packet_send(conn->packet, req->data); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + /* some requests don't expect a reply, so don't add those to the + pending queue */ + if (req->type == LDAP_TAG_AbandonRequest || + req->type == LDAP_TAG_UnbindRequest) { + req->status = NT_STATUS_OK; + req->state = LDAP_REQUEST_DONE; + /* we can't call the async callback now, as it isn't setup, so + call it as next event */ + event_add_timed(conn->event.event_ctx, req, timeval_zero(), + ldap_request_complete, req); + return req; + } + + req->state = LDAP_REQUEST_PENDING; + DLIST_ADD(conn->pending, req); + + /* put a timeout on the request */ + req->time_event = event_add_timed(conn->event.event_ctx, req, + timeval_current_ofs(conn->timeout, 0), + ldap_request_timeout, req); + + return req; + +failed: + req->status = status; + req->state = LDAP_REQUEST_ERROR; + event_add_timed(conn->event.event_ctx, req, timeval_zero(), + ldap_request_complete, req); + + return req; +} + + +/* + wait for a request to complete + note that this does not destroy the request +*/ +_PUBLIC_ NTSTATUS ldap_request_wait(struct ldap_request *req) +{ + while (req->state < LDAP_REQUEST_DONE) { + if (event_loop_once(req->conn->event.event_ctx) != 0) { + req->status = NT_STATUS_UNEXPECTED_NETWORK_ERROR; + break; + } + } + return req->status; +} + + +/* + a mapping of ldap response code to strings +*/ +static const struct { + enum ldap_result_code code; + const char *str; +} ldap_code_map[] = { +#define _LDAP_MAP_CODE(c) { c, #c } + _LDAP_MAP_CODE(LDAP_SUCCESS), + _LDAP_MAP_CODE(LDAP_OPERATIONS_ERROR), + _LDAP_MAP_CODE(LDAP_PROTOCOL_ERROR), + _LDAP_MAP_CODE(LDAP_TIME_LIMIT_EXCEEDED), + _LDAP_MAP_CODE(LDAP_SIZE_LIMIT_EXCEEDED), + _LDAP_MAP_CODE(LDAP_COMPARE_FALSE), + _LDAP_MAP_CODE(LDAP_COMPARE_TRUE), + _LDAP_MAP_CODE(LDAP_AUTH_METHOD_NOT_SUPPORTED), + _LDAP_MAP_CODE(LDAP_STRONG_AUTH_REQUIRED), + _LDAP_MAP_CODE(LDAP_REFERRAL), + _LDAP_MAP_CODE(LDAP_ADMIN_LIMIT_EXCEEDED), + _LDAP_MAP_CODE(LDAP_UNAVAILABLE_CRITICAL_EXTENSION), + _LDAP_MAP_CODE(LDAP_CONFIDENTIALITY_REQUIRED), + _LDAP_MAP_CODE(LDAP_SASL_BIND_IN_PROGRESS), + _LDAP_MAP_CODE(LDAP_NO_SUCH_ATTRIBUTE), + _LDAP_MAP_CODE(LDAP_UNDEFINED_ATTRIBUTE_TYPE), + _LDAP_MAP_CODE(LDAP_INAPPROPRIATE_MATCHING), + _LDAP_MAP_CODE(LDAP_CONSTRAINT_VIOLATION), + _LDAP_MAP_CODE(LDAP_ATTRIBUTE_OR_VALUE_EXISTS), + _LDAP_MAP_CODE(LDAP_INVALID_ATTRIBUTE_SYNTAX), + _LDAP_MAP_CODE(LDAP_NO_SUCH_OBJECT), + _LDAP_MAP_CODE(LDAP_ALIAS_PROBLEM), + _LDAP_MAP_CODE(LDAP_INVALID_DN_SYNTAX), + _LDAP_MAP_CODE(LDAP_ALIAS_DEREFERENCING_PROBLEM), + _LDAP_MAP_CODE(LDAP_INAPPROPRIATE_AUTHENTICATION), + _LDAP_MAP_CODE(LDAP_INVALID_CREDENTIALS), + _LDAP_MAP_CODE(LDAP_INSUFFICIENT_ACCESS_RIGHTS), + _LDAP_MAP_CODE(LDAP_BUSY), + _LDAP_MAP_CODE(LDAP_UNAVAILABLE), + _LDAP_MAP_CODE(LDAP_UNWILLING_TO_PERFORM), + _LDAP_MAP_CODE(LDAP_LOOP_DETECT), + _LDAP_MAP_CODE(LDAP_NAMING_VIOLATION), + _LDAP_MAP_CODE(LDAP_OBJECT_CLASS_VIOLATION), + _LDAP_MAP_CODE(LDAP_NOT_ALLOWED_ON_NON_LEAF), + _LDAP_MAP_CODE(LDAP_NOT_ALLOWED_ON_RDN), + _LDAP_MAP_CODE(LDAP_ENTRY_ALREADY_EXISTS), + _LDAP_MAP_CODE(LDAP_OBJECT_CLASS_MODS_PROHIBITED), + _LDAP_MAP_CODE(LDAP_AFFECTS_MULTIPLE_DSAS), + _LDAP_MAP_CODE(LDAP_OTHER) +}; + +/* + used to setup the status code from a ldap response +*/ +_PUBLIC_ NTSTATUS ldap_check_response(struct ldap_connection *conn, struct ldap_Result *r) +{ + int i; + const char *codename = "unknown"; + + if (r->resultcode == LDAP_SUCCESS) { + return NT_STATUS_OK; + } + + if (conn->last_error) { + talloc_free(conn->last_error); + } + + for (i=0;i<ARRAY_SIZE(ldap_code_map);i++) { + if (r->resultcode == ldap_code_map[i].code) { + codename = ldap_code_map[i].str; + break; + } + } + + conn->last_error = talloc_asprintf(conn, "LDAP error %u %s - %s <%s> <%s>", + r->resultcode, + codename, + r->dn?r->dn:"(NULL)", + r->errormessage?r->errormessage:"", + r->referral?r->referral:""); + + return NT_STATUS_LDAP(r->resultcode); +} + +/* + return error string representing the last error +*/ +_PUBLIC_ const char *ldap_errstr(struct ldap_connection *conn, + TALLOC_CTX *mem_ctx, + NTSTATUS status) +{ + if (NT_STATUS_IS_LDAP(status) && conn->last_error != NULL) { + return talloc_strdup(mem_ctx, conn->last_error); + } + return talloc_asprintf(mem_ctx, "LDAP client internal error: %s", nt_errstr(status)); +} + + +/* + return the Nth result message, waiting if necessary +*/ +_PUBLIC_ NTSTATUS ldap_result_n(struct ldap_request *req, int n, struct ldap_message **msg) +{ + *msg = NULL; + + NT_STATUS_HAVE_NO_MEMORY(req); + + while (req->state < LDAP_REQUEST_DONE && n >= req->num_replies) { + if (event_loop_once(req->conn->event.event_ctx) != 0) { + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + } + + if (n < req->num_replies) { + *msg = req->replies[n]; + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(req->status)) { + return req->status; + } + + return NT_STATUS_NO_MORE_ENTRIES; +} + + +/* + return a single result message, checking if it is of the expected LDAP type +*/ +_PUBLIC_ NTSTATUS ldap_result_one(struct ldap_request *req, struct ldap_message **msg, int type) +{ + NTSTATUS status; + status = ldap_result_n(req, 0, msg); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if ((*msg)->type != type) { + *msg = NULL; + return NT_STATUS_UNEXPECTED_NETWORK_ERROR; + } + return status; +} + +/* + a simple ldap transaction, for single result requests that only need a status code + this relies on single valued requests having the response type == request type + 1 +*/ +_PUBLIC_ NTSTATUS ldap_transaction(struct ldap_connection *conn, struct ldap_message *msg) +{ + struct ldap_request *req = ldap_request_send(conn, msg); + struct ldap_message *res; + NTSTATUS status; + status = ldap_result_n(req, 0, &res); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(req); + return status; + } + if (res->type != msg->type + 1) { + talloc_free(req); + return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); + } + status = ldap_check_response(conn, &res->r.GeneralResult); + talloc_free(req); + return status; +} diff --git a/source4/libcli/ldap/ldap_client.h b/source4/libcli/ldap/ldap_client.h new file mode 100644 index 0000000000..13b0bf725c --- /dev/null +++ b/source4/libcli/ldap/ldap_client.h @@ -0,0 +1,140 @@ +/* + Unix SMB/CIFS Implementation. + + ldap client side header + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "libcli/ldap/ldap.h" + +enum ldap_request_state { LDAP_REQUEST_SEND=1, LDAP_REQUEST_PENDING=2, LDAP_REQUEST_DONE=3, LDAP_REQUEST_ERROR=4 }; + +/* this is the handle that the caller gets when an async ldap message + is sent */ +struct ldap_request { + struct ldap_request *next, *prev; + struct ldap_connection *conn; + + enum ldap_request_tag type; + int messageid; + enum ldap_request_state state; + + int num_replies; + struct ldap_message **replies; + + NTSTATUS status; + DATA_BLOB data; + struct { + void (*fn)(struct ldap_request *); + void *private_data; + } async; + + struct timed_event *time_event; +}; + + +/* main context for a ldap client connection */ +struct ldap_connection { + struct socket_context *sock; + struct loadparm_context *lp_ctx; + + char *host; + uint16_t port; + bool ldaps; + + const char *auth_dn; + const char *simple_pw; + + struct { + char *url; + int max_retries; + int retries; + time_t previous; + } reconnect; + + struct { + enum { LDAP_BIND_SIMPLE, LDAP_BIND_SASL } type; + void *creds; + } bind; + + /* next message id to assign */ + unsigned next_messageid; + + /* Outstanding LDAP requests that have not yet been replied to */ + struct ldap_request *pending; + + /* Let's support SASL */ + struct gensec_security *gensec; + + /* the default timeout for messages */ + int timeout; + + /* last error message */ + char *last_error; + + struct { + struct event_context *event_ctx; + struct fd_event *fde; + } event; + + struct packet_context *packet; +}; + +struct ldap_connection *ldap4_new_connection(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct event_context *ev); + +NTSTATUS ldap_connect(struct ldap_connection *conn, const char *url); +struct composite_context *ldap_connect_send(struct ldap_connection *conn, + const char *url); + +NTSTATUS ldap_rebind(struct ldap_connection *conn); +NTSTATUS ldap_bind_simple(struct ldap_connection *conn, + const char *userdn, const char *password); +NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, + struct cli_credentials *creds, + struct loadparm_context *lp_ctx); +struct ldap_request *ldap_request_send(struct ldap_connection *conn, + struct ldap_message *msg); +NTSTATUS ldap_request_wait(struct ldap_request *req); +struct composite_context; +NTSTATUS ldap_connect_recv(struct composite_context *ctx); +NTSTATUS ldap_result_n(struct ldap_request *req, int n, struct ldap_message **msg); +NTSTATUS ldap_result_one(struct ldap_request *req, struct ldap_message **msg, int type); +NTSTATUS ldap_transaction(struct ldap_connection *conn, struct ldap_message *msg); +const char *ldap_errstr(struct ldap_connection *conn, + TALLOC_CTX *mem_ctx, + NTSTATUS status); +NTSTATUS ldap_check_response(struct ldap_connection *conn, struct ldap_Result *r); +void ldap_set_reconn_params(struct ldap_connection *conn, int max_retries); +int ildap_count_entries(struct ldap_connection *conn, struct ldap_message **res); +NTSTATUS ildap_search_bytree(struct ldap_connection *conn, const char *basedn, + int scope, struct ldb_parse_tree *tree, + const char * const *attrs, bool attributesonly, + struct ldb_control **control_req, + struct ldb_control ***control_res, + struct ldap_message ***results); +NTSTATUS ildap_search(struct ldap_connection *conn, const char *basedn, + int scope, const char *expression, + const char * const *attrs, bool attributesonly, + struct ldb_control **control_req, + struct ldb_control ***control_res, + struct ldap_message ***results); + + + diff --git a/source4/libcli/ldap/ldap_controls.c b/source4/libcli/ldap/ldap_controls.c new file mode 100644 index 0000000000..3b94580033 --- /dev/null +++ b/source4/libcli/ldap/ldap_controls.c @@ -0,0 +1,1229 @@ +/* + Unix SMB/CIFS mplementation. + LDAP protocol helper functions for SAMBA + + Copyright (C) Simo Sorce 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/util/asn1.h" +#include "libcli/ldap/ldap.h" +#include "lib/ldb/include/ldb.h" + +struct control_handler { + const char *oid; + bool (*decode)(void *mem_ctx, DATA_BLOB in, void **out); + bool (*encode)(void *mem_ctx, void *in, DATA_BLOB *out); +}; + +static bool decode_server_sort_response(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB attr; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_sort_resp_control *lsrc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lsrc = talloc(mem_ctx, struct ldb_sort_resp_control); + if (!lsrc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_enumerated(data, &(lsrc->result))) { + return false; + } + + lsrc->attr_desc = NULL; + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + if (!asn1_read_OctetString(data, mem_ctx, &attr)) { + return false; + } + lsrc->attr_desc = talloc_strndup(lsrc, (const char *)attr.data, attr.length); + if (!lsrc->attr_desc) { + return false; + } + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lsrc; + + return true; +} + +static bool decode_server_sort_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB attr; + DATA_BLOB rule; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_server_sort_control **lssc; + int num; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + lssc = NULL; + + for (num = 0; asn1_peek_tag(data, ASN1_SEQUENCE(0)); num++) { + lssc = talloc_realloc(mem_ctx, lssc, struct ldb_server_sort_control *, num + 2); + if (!lssc) { + return false; + } + lssc[num] = talloc_zero(lssc, struct ldb_server_sort_control); + if (!lssc[num]) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_OctetString(data, mem_ctx, &attr)) { + return false; + } + + lssc[num]->attributeName = talloc_strndup(lssc[num], (const char *)attr.data, attr.length); + if (!lssc [num]->attributeName) { + return false; + } + + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + if (!asn1_read_OctetString(data, mem_ctx, &rule)) { + return false; + } + lssc[num]->orderingRule = talloc_strndup(lssc[num], (const char *)rule.data, rule.length); + if (!lssc[num]->orderingRule) { + return false; + } + } + + if (asn1_peek_tag(data, ASN1_BOOLEAN)) { + bool reverse; + if (!asn1_read_BOOLEAN(data, &reverse)) { + return false; + } + lssc[num]->reverse = reverse; + } + + if (!asn1_end_tag(data)) { + return false; + } + } + + if (lssc != NULL) { + lssc[num] = NULL; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lssc; + + return true; +} + +static bool decode_extended_dn_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + struct asn1_data *data; + struct ldb_extended_dn_control *ledc; + + /* The content of this control is optional */ + if (in.length == 0) { + *out = NULL; + return true; + } + + data = asn1_init(mem_ctx); + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + ledc = talloc(mem_ctx, struct ldb_extended_dn_control); + if (!ledc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(ledc->type))) { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = ledc; + + return true; +} + +static bool decode_sd_flags_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_sd_flags_control *lsdfc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lsdfc = talloc(mem_ctx, struct ldb_sd_flags_control); + if (!lsdfc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lsdfc->secinfo_flags))) { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lsdfc; + + return true; +} + +static bool decode_search_options_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_search_options_control *lsoc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lsoc = talloc(mem_ctx, struct ldb_search_options_control); + if (!lsoc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lsoc->search_options))) { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lsoc; + + return true; +} + +static bool decode_paged_results_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB cookie; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_paged_control *lprc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lprc = talloc(mem_ctx, struct ldb_paged_control); + if (!lprc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lprc->size))) { + return false; + } + + if (!asn1_read_OctetString(data, mem_ctx, &cookie)) { + return false; + } + lprc->cookie_len = cookie.length; + if (lprc->cookie_len) { + lprc->cookie = talloc_memdup(lprc, cookie.data, cookie.length); + + if (!(lprc->cookie)) { + return false; + } + } else { + lprc->cookie = NULL; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lprc; + + return true; +} + +static bool decode_dirsync_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB cookie; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_dirsync_control *ldc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + ldc = talloc(mem_ctx, struct ldb_dirsync_control); + if (!ldc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(ldc->flags))) { + return false; + } + + if (!asn1_read_Integer(data, &(ldc->max_attributes))) { + return false; + } + + if (!asn1_read_OctetString(data, mem_ctx, &cookie)) { + return false; + } + ldc->cookie_len = cookie.length; + if (ldc->cookie_len) { + ldc->cookie = talloc_memdup(ldc, cookie.data, cookie.length); + + if (!(ldc->cookie)) { + return false; + } + } else { + ldc->cookie = NULL; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = ldc; + + return true; +} + +/* seem that this controls has 2 forms one in case it is used with + * a Search Request and another when used ina Search Response + */ +static bool decode_asq_control(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB source_attribute; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_asq_control *lac; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lac = talloc(mem_ctx, struct ldb_asq_control); + if (!lac) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + + if (!asn1_read_OctetString(data, mem_ctx, &source_attribute)) { + return false; + } + lac->src_attr_len = source_attribute.length; + if (lac->src_attr_len) { + lac->source_attribute = talloc_strndup(lac, (const char *)source_attribute.data, source_attribute.length); + + if (!(lac->source_attribute)) { + return false; + } + } else { + lac->source_attribute = NULL; + } + + lac->request = 1; + + } else if (asn1_peek_tag(data, ASN1_ENUMERATED)) { + + if (!asn1_read_enumerated(data, &(lac->result))) { + return false; + } + + lac->request = 0; + + } else { + return false; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lac; + + return true; +} + +static bool decode_domain_scope_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + if (in.length != 0) { + return false; + } + + return true; +} + +static bool decode_notification_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + if (in.length != 0) { + return false; + } + + return true; +} + +static bool decode_show_deleted_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + if (in.length != 0) { + return false; + } + + return true; +} + +static bool decode_permissive_modify_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + if (in.length != 0) { + return false; + } + + return true; +} + +static bool decode_manageDSAIT_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + if (in.length != 0) { + return false; + } + + return true; +} + +static bool decode_vlv_request(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB assertion_value, context_id; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_vlv_req_control *lvrc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lvrc = talloc(mem_ctx, struct ldb_vlv_req_control); + if (!lvrc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->beforeCount))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->afterCount))) { + return false; + } + + if (asn1_peek_tag(data, ASN1_CONTEXT(0))) { + + lvrc->type = 0; + + if (!asn1_start_tag(data, ASN1_CONTEXT(0))) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->match.byOffset.offset))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->match.byOffset.contentCount))) { + return false; + } + + if (!asn1_end_tag(data)) { /*SEQUENCE*/ + return false; + } + + if (!asn1_end_tag(data)) { /*CONTEXT*/ + return false; + } + + } else { + + lvrc->type = 1; + + if (!asn1_start_tag(data, ASN1_CONTEXT(1))) { + return false; + } + + if (!asn1_read_OctetString(data, mem_ctx, &assertion_value)) { + return false; + } + lvrc->match.gtOrEq.value_len = assertion_value.length; + if (lvrc->match.gtOrEq.value_len) { + lvrc->match.gtOrEq.value = talloc_memdup(lvrc, assertion_value.data, assertion_value.length); + + if (!(lvrc->match.gtOrEq.value)) { + return false; + } + } else { + lvrc->match.gtOrEq.value = NULL; + } + + if (!asn1_end_tag(data)) { /*CONTEXT*/ + return false; + } + } + + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + if (!asn1_read_OctetString(data, mem_ctx, &context_id)) { + return false; + } + lvrc->ctxid_len = context_id.length; + if (lvrc->ctxid_len) { + lvrc->contextId = talloc_memdup(lvrc, context_id.data, context_id.length); + + if (!(lvrc->contextId)) { + return false; + } + } else { + lvrc->contextId = NULL; + } + } else { + lvrc->contextId = NULL; + lvrc->ctxid_len = 0; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lvrc; + + return true; +} + +static bool decode_vlv_response(void *mem_ctx, DATA_BLOB in, void **out) +{ + DATA_BLOB context_id; + struct asn1_data *data = asn1_init(mem_ctx); + struct ldb_vlv_resp_control *lvrc; + + if (!data) return false; + + if (!asn1_load(data, in)) { + return false; + } + + lvrc = talloc(mem_ctx, struct ldb_vlv_resp_control); + if (!lvrc) { + return false; + } + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->targetPosition))) { + return false; + } + + if (!asn1_read_Integer(data, &(lvrc->contentCount))) { + return false; + } + + if (!asn1_read_enumerated(data, &(lvrc->vlv_result))) { + return false; + } + + if (asn1_peek_tag(data, ASN1_OCTET_STRING)) { + if (!asn1_read_OctetString(data, mem_ctx, &context_id)) { + return false; + } + lvrc->contextId = talloc_strndup(lvrc, (const char *)context_id.data, context_id.length); + if (!lvrc->contextId) { + return false; + } + lvrc->ctxid_len = context_id.length; + } else { + lvrc->contextId = NULL; + lvrc->ctxid_len = 0; + } + + if (!asn1_end_tag(data)) { + return false; + } + + *out = lvrc; + + return true; +} + +static bool encode_server_sort_response(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_sort_resp_control *lsrc = talloc_get_type(in, struct ldb_sort_resp_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_enumerated(data, lsrc->result)) { + return false; + } + + if (lsrc->attr_desc) { + if (!asn1_write_OctetString(data, lsrc->attr_desc, strlen(lsrc->attr_desc))) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_server_sort_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_server_sort_control **lssc = talloc_get_type(in, struct ldb_server_sort_control *); + struct asn1_data *data = asn1_init(mem_ctx); + int num; + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + for (num = 0; lssc[num]; num++) { + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_OctetString(data, lssc[num]->attributeName, strlen(lssc[num]->attributeName))) { + return false; + } + + if (lssc[num]->orderingRule) { + if (!asn1_write_OctetString(data, lssc[num]->orderingRule, strlen(lssc[num]->orderingRule))) { + return false; + } + } + + if (lssc[num]->reverse) { + if (!asn1_write_BOOLEAN(data, lssc[num]->reverse)) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_extended_dn_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_extended_dn_control *ledc = talloc_get_type(in, struct ldb_extended_dn_control); + struct asn1_data *data; + + if (!in) { + *out = data_blob(NULL, 0); + return true; + } + + data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, ledc->type)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_sd_flags_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_sd_flags_control *lsdfc = talloc_get_type(in, struct ldb_sd_flags_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lsdfc->secinfo_flags)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_search_options_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_search_options_control *lsoc = talloc_get_type(in, struct ldb_search_options_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lsoc->search_options)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_paged_results_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_paged_control *lprc = talloc_get_type(in, struct ldb_paged_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lprc->size)) { + return false; + } + + if (!asn1_write_OctetString(data, lprc->cookie, lprc->cookie_len)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +/* seem that this controls has 2 forms one in case it is used with + * a Search Request and another when used ina Search Response + */ +static bool encode_asq_control(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_asq_control *lac = talloc_get_type(in, struct ldb_asq_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (lac->request) { + + if (!asn1_write_OctetString(data, lac->source_attribute, lac->src_attr_len)) { + return false; + } + } else { + if (!asn1_write_enumerated(data, lac->result)) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_dirsync_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_dirsync_control *ldc = talloc_get_type(in, struct ldb_dirsync_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, ldc->flags)) { + return false; + } + + if (!asn1_write_Integer(data, ldc->max_attributes)) { + return false; + } + + if (!asn1_write_OctetString(data, ldc->cookie, ldc->cookie_len)) { + return false; + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_domain_scope_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + if (in) { + return false; + } + + *out = data_blob(NULL, 0); + return true; +} + +static bool encode_notification_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + if (in) { + return false; + } + + *out = data_blob(NULL, 0); + return true; +} + +static bool encode_show_deleted_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + if (in) { + return false; + } + + *out = data_blob(NULL, 0); + return true; +} + +static bool encode_permissive_modify_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + if (in) { + return false; + } + + *out = data_blob(NULL, 0); + return true; +} + +static bool encode_manageDSAIT_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + if (in) { + return false; + } + + *out = data_blob(NULL, 0); + return true; +} + +static bool encode_vlv_request(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_vlv_req_control *lvrc = talloc_get_type(in, struct ldb_vlv_req_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->beforeCount)) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->afterCount)) { + return false; + } + + if (lvrc->type == 0) { + if (!asn1_push_tag(data, ASN1_CONTEXT(0))) { + return false; + } + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->match.byOffset.offset)) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->match.byOffset.contentCount)) { + return false; + } + + if (!asn1_pop_tag(data)) { /*SEQUENCE*/ + return false; + } + + if (!asn1_pop_tag(data)) { /*CONTEXT*/ + return false; + } + } else { + if (!asn1_push_tag(data, ASN1_CONTEXT(1))) { + return false; + } + + if (!asn1_write_OctetString(data, lvrc->match.gtOrEq.value, lvrc->match.gtOrEq.value_len)) { + return false; + } + + if (!asn1_pop_tag(data)) { /*CONTEXT*/ + return false; + } + } + + if (lvrc->ctxid_len) { + if (!asn1_write_OctetString(data, lvrc->contextId, lvrc->ctxid_len)) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +static bool encode_vlv_response(void *mem_ctx, void *in, DATA_BLOB *out) +{ + struct ldb_vlv_resp_control *lvrc = talloc_get_type(in, struct ldb_vlv_resp_control); + struct asn1_data *data = asn1_init(mem_ctx); + + if (!data) return false; + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->targetPosition)) { + return false; + } + + if (!asn1_write_Integer(data, lvrc->contentCount)) { + return false; + } + + if (!asn1_write_enumerated(data, lvrc->vlv_result)) { + return false; + } + + if (lvrc->ctxid_len) { + if (!asn1_write_OctetString(data, lvrc->contextId, lvrc->ctxid_len)) { + return false; + } + } + + if (!asn1_pop_tag(data)) { + return false; + } + + *out = data_blob_talloc(mem_ctx, data->data, data->length); + if (out->data == NULL) { + return false; + } + talloc_free(data); + + return true; +} + +struct control_handler ldap_known_controls[] = { + { "1.2.840.113556.1.4.319", decode_paged_results_request, encode_paged_results_request }, + { "1.2.840.113556.1.4.529", decode_extended_dn_request, encode_extended_dn_request }, + { "1.2.840.113556.1.4.473", decode_server_sort_request, encode_server_sort_request }, + { "1.2.840.113556.1.4.474", decode_server_sort_response, encode_server_sort_response }, + { "1.2.840.113556.1.4.1504", decode_asq_control, encode_asq_control }, + { "1.2.840.113556.1.4.841", decode_dirsync_request, encode_dirsync_request }, + { "1.2.840.113556.1.4.528", decode_notification_request, encode_notification_request }, + { "1.2.840.113556.1.4.417", decode_show_deleted_request, encode_show_deleted_request }, + { "1.2.840.113556.1.4.1413", decode_permissive_modify_request, encode_permissive_modify_request }, + { "1.2.840.113556.1.4.801", decode_sd_flags_request, encode_sd_flags_request }, + { "1.2.840.113556.1.4.1339", decode_domain_scope_request, encode_domain_scope_request }, + { "1.2.840.113556.1.4.1340", decode_search_options_request, encode_search_options_request }, + { "2.16.840.1.113730.3.4.2", decode_manageDSAIT_request, encode_manageDSAIT_request }, + { "2.16.840.1.113730.3.4.9", decode_vlv_request, encode_vlv_request }, + { "2.16.840.1.113730.3.4.10", decode_vlv_response, encode_vlv_response }, +/* DSDB_CONTROL_CURRENT_PARTITION_OID is internal only, and has no network representation */ + { "1.3.6.1.4.1.7165.4.3.2", NULL, NULL }, +/* DSDB_EXTENDED_REPLICATED_OBJECTS_OID is internal only, and has no network representation */ + { "1.3.6.1.4.1.7165.4.4.1", NULL, NULL }, + { NULL, NULL, NULL } +}; + +bool ldap_decode_control_value(void *mem_ctx, DATA_BLOB value, struct ldb_control *ctrl) +{ + int i; + + for (i = 0; ldap_known_controls[i].oid != NULL; i++) { + if (strcmp(ldap_known_controls[i].oid, ctrl->oid) == 0) { + if (!ldap_known_controls[i].decode || !ldap_known_controls[i].decode(mem_ctx, value, &ctrl->data)) { + return false; + } + break; + } + } + if (ldap_known_controls[i].oid == NULL) { + return false; + } + + return true; +} + +bool ldap_decode_control_wrapper(void *mem_ctx, struct asn1_data *data, struct ldb_control *ctrl, DATA_BLOB *value) +{ + DATA_BLOB oid; + + if (!asn1_start_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_read_OctetString(data, mem_ctx, &oid)) { + return false; + } + ctrl->oid = talloc_strndup(mem_ctx, (char *)oid.data, oid.length); + if (!ctrl->oid) { + return false; + } + + if (asn1_peek_tag(data, ASN1_BOOLEAN)) { + bool critical; + if (!asn1_read_BOOLEAN(data, &critical)) { + return false; + } + ctrl->critical = critical; + } else { + ctrl->critical = false; + } + + ctrl->data = NULL; + + if (!asn1_peek_tag(data, ASN1_OCTET_STRING)) { + *value = data_blob(NULL, 0); + goto end_tag; + } + + if (!asn1_read_OctetString(data, mem_ctx, value)) { + return false; + } + +end_tag: + if (!asn1_end_tag(data)) { + return false; + } + + return true; +} + +bool ldap_encode_control(void *mem_ctx, struct asn1_data *data, struct ldb_control *ctrl) +{ + DATA_BLOB value; + int i; + + for (i = 0; ldap_known_controls[i].oid != NULL; i++) { + if (strcmp(ldap_known_controls[i].oid, ctrl->oid) == 0) { + if (!ldap_known_controls[i].encode) { + if (ctrl->critical) { + return false; + } else { + /* not encoding this control */ + return true; + } + } + if (!ldap_known_controls[i].encode(mem_ctx, ctrl->data, &value)) { + return false; + } + break; + } + } + if (ldap_known_controls[i].oid == NULL) { + return false; + } + + if (!asn1_push_tag(data, ASN1_SEQUENCE(0))) { + return false; + } + + if (!asn1_write_OctetString(data, ctrl->oid, strlen(ctrl->oid))) { + return false; + } + + if (ctrl->critical) { + if (!asn1_write_BOOLEAN(data, ctrl->critical)) { + return false; + } + } + + if (!ctrl->data) { + goto pop_tag; + } + + if (!asn1_write_OctetString(data, value.data, value.length)) { + return false; + } + +pop_tag: + if (!asn1_pop_tag(data)) { + return false; + } + + return true; +} diff --git a/source4/libcli/ldap/ldap_ildap.c b/source4/libcli/ldap/ldap_ildap.c new file mode 100644 index 0000000000..8f21af0690 --- /dev/null +++ b/source4/libcli/ldap/ldap_ildap.c @@ -0,0 +1,129 @@ +/* + Unix SMB/CIFS mplementation. + + ildap api - an api similar to the traditional ldap api + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "libcli/ldap/ldap.h" +#include "libcli/ldap/ldap_client.h" + + +/* + count the returned search entries +*/ +_PUBLIC_ int ildap_count_entries(struct ldap_connection *conn, struct ldap_message **res) +{ + int i; + for (i=0;res && res[i];i++) /* noop */ ; + return i; +} + + +/* + perform a synchronous ldap search +*/ +_PUBLIC_ NTSTATUS ildap_search_bytree(struct ldap_connection *conn, const char *basedn, + int scope, struct ldb_parse_tree *tree, + const char * const *attrs, bool attributesonly, + struct ldb_control **control_req, + struct ldb_control ***control_res, + struct ldap_message ***results) +{ + struct ldap_message *msg; + int n, i; + NTSTATUS status; + struct ldap_request *req; + + if (control_res) + *control_res = NULL; + *results = NULL; + + msg = new_ldap_message(conn); + NT_STATUS_HAVE_NO_MEMORY(msg); + + for (n=0;attrs && attrs[n];n++) /* noop */ ; + + msg->type = LDAP_TAG_SearchRequest; + msg->r.SearchRequest.basedn = basedn; + msg->r.SearchRequest.scope = scope; + msg->r.SearchRequest.deref = LDAP_DEREFERENCE_NEVER; + msg->r.SearchRequest.timelimit = 0; + msg->r.SearchRequest.sizelimit = 0; + msg->r.SearchRequest.attributesonly = attributesonly; + msg->r.SearchRequest.tree = tree; + msg->r.SearchRequest.num_attributes = n; + msg->r.SearchRequest.attributes = discard_const(attrs); + msg->controls = control_req; + + req = ldap_request_send(conn, msg); + talloc_steal(msg, req); + + for (i=n=0;true;i++) { + struct ldap_message *res; + status = ldap_result_n(req, i, &res); + if (!NT_STATUS_IS_OK(status)) break; + + if (res->type == LDAP_TAG_SearchResultDone) { + status = ldap_check_response(conn, &res->r.GeneralResult); + if (control_res) { + *control_res = talloc_steal(conn, res->controls); + } + break; + } + + if (res->type != LDAP_TAG_SearchResultEntry && + res->type != LDAP_TAG_SearchResultReference) + continue; + + (*results) = talloc_realloc(conn, *results, struct ldap_message *, n+2); + if (*results == NULL) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + (*results)[n] = talloc_steal(*results, res); + (*results)[n+1] = NULL; + n++; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_NO_MORE_ENTRIES)) { + status = NT_STATUS_OK; + } + + return status; +} + +/* + perform a ldap search +*/ +_PUBLIC_ NTSTATUS ildap_search(struct ldap_connection *conn, const char *basedn, + int scope, const char *expression, + const char * const *attrs, bool attributesonly, + struct ldb_control **control_req, + struct ldb_control ***control_res, + struct ldap_message ***results) +{ + struct ldb_parse_tree *tree = ldb_parse_tree(conn, expression); + NTSTATUS status; + status = ildap_search_bytree(conn, basedn, scope, tree, attrs, + attributesonly, control_req, + control_res, results); + talloc_free(tree); + return status; +} diff --git a/source4/libcli/ldap/ldap_msg.c b/source4/libcli/ldap/ldap_msg.c new file mode 100644 index 0000000000..c712e1e654 --- /dev/null +++ b/source4/libcli/ldap/ldap_msg.c @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS mplementation. + + LDAP protocol helper functions for SAMBA + + Copyright (C) Andrew Tridgell 2005 + Copyright (C) Volker Lendecke 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "libcli/ldap/ldap.h" +#include "libcli/ldap/ldap_client.h" + + +_PUBLIC_ struct ldap_message *new_ldap_message(TALLOC_CTX *mem_ctx) +{ + return talloc_zero(mem_ctx, struct ldap_message); +} + + +bool add_value_to_attrib(TALLOC_CTX *mem_ctx, struct ldb_val *value, + struct ldb_message_element *attrib) +{ + attrib->values = talloc_realloc(mem_ctx, + attrib->values, + DATA_BLOB, + attrib->num_values+1); + if (attrib->values == NULL) + return false; + + attrib->values[attrib->num_values].data = talloc_steal(attrib->values, + value->data); + attrib->values[attrib->num_values].length = value->length; + attrib->num_values += 1; + return true; +} + +bool add_attrib_to_array_talloc(TALLOC_CTX *mem_ctx, + const struct ldb_message_element *attrib, + struct ldb_message_element **attribs, + int *num_attribs) +{ + *attribs = talloc_realloc(mem_ctx, + *attribs, + struct ldb_message_element, + *num_attribs+1); + + if (*attribs == NULL) + return false; + + (*attribs)[*num_attribs] = *attrib; + talloc_steal(*attribs, attrib->values); + talloc_steal(*attribs, attrib->name); + *num_attribs += 1; + return true; +} + +bool add_mod_to_array_talloc(TALLOC_CTX *mem_ctx, + struct ldap_mod *mod, + struct ldap_mod **mods, + int *num_mods) +{ + *mods = talloc_realloc(mem_ctx, *mods, struct ldap_mod, (*num_mods)+1); + + if (*mods == NULL) + return false; + + (*mods)[*num_mods] = *mod; + *num_mods += 1; + return true; +} + diff --git a/source4/libcli/ldap/ldap_ndr.c b/source4/libcli/ldap/ldap_ndr.c new file mode 100644 index 0000000000..a10f80ae2c --- /dev/null +++ b/source4/libcli/ldap/ldap_ndr.c @@ -0,0 +1,96 @@ +/* + Unix SMB/CIFS mplementation. + + wrap/unwrap NDR encoded elements for ldap calls + + Copyright (C) Andrew Tridgell 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +*/ + +#include "includes.h" +#include "lib/events/events.h" +#include "libcli/ldap/ldap.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_misc.h" + +/* + encode a NDR uint32 as a ldap filter element +*/ +char *ldap_encode_ndr_uint32(TALLOC_CTX *mem_ctx, uint32_t value) +{ + uint8_t buf[4]; + struct ldb_val val; + SIVAL(buf, 0, value); + val.data = buf; + val.length = 4; + return ldb_binary_encode(mem_ctx, val); +} + +/* + encode a NDR dom_sid as a ldap filter element +*/ +char *ldap_encode_ndr_dom_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + char *ret; + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, NULL, sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + ret = ldb_binary_encode(mem_ctx, blob); + data_blob_free(&blob); + return ret; +} + + +/* + encode a NDR GUID as a ldap filter element +*/ +char *ldap_encode_ndr_GUID(TALLOC_CTX *mem_ctx, struct GUID *guid) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + char *ret; + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, NULL, guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NULL; + } + ret = ldb_binary_encode(mem_ctx, blob); + data_blob_free(&blob); + return ret; +} + +/* + decode a NDR GUID from a ldap filter element +*/ +NTSTATUS ldap_decode_ndr_GUID(TALLOC_CTX *mem_ctx, struct ldb_val val, struct GUID *guid) +{ + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + blob.data = val.data; + blob.length = val.length; + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, NULL, guid, + (ndr_pull_flags_fn_t)ndr_pull_GUID); + talloc_free(val.data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return ndr_map_error2ntstatus(ndr_err); + } + return NT_STATUS_OK; +} diff --git a/source4/libcli/ldap/ldap_ndr.h b/source4/libcli/ldap/ldap_ndr.h new file mode 100644 index 0000000000..ee1f702c78 --- /dev/null +++ b/source4/libcli/ldap/ldap_ndr.h @@ -0,0 +1,12 @@ +#ifndef __LIBCLI_LDAP_LDAP_NDR_H__ +#define __LIBCLI_LDAP_LDAP_NDR_H__ + +#include "librpc/gen_ndr/ndr_misc.h" + +char *ldap_encode_ndr_uint32(TALLOC_CTX *mem_ctx, uint32_t value); +char *ldap_encode_ndr_dom_sid(TALLOC_CTX *mem_ctx, const struct dom_sid *sid); +char *ldap_encode_ndr_GUID(TALLOC_CTX *mem_ctx, struct GUID *guid); +NTSTATUS ldap_decode_ndr_GUID(TALLOC_CTX *mem_ctx, struct ldb_val val, struct GUID *guid); + +#endif /* __LIBCLI_LDAP_LDAP_NDR_H__ */ + |