diff options
-rw-r--r-- | source4/configure.in | 1 | ||||
-rw-r--r-- | source4/include/asn_1.h | 3 | ||||
-rw-r--r-- | source4/include/includes.h | 1 | ||||
-rw-r--r-- | source4/libcli/ldap/config.m4 | 1 | ||||
-rw-r--r-- | source4/libcli/ldap/config.mk | 6 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap.c | 1989 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap.h | 246 | ||||
-rw-r--r-- | source4/libcli/util/asn1.c | 160 |
8 files changed, 2379 insertions, 28 deletions
diff --git a/source4/configure.in b/source4/configure.in index a86a99f49d..49328b91df 100644 --- a/source4/configure.in +++ b/source4/configure.in @@ -20,6 +20,7 @@ SMB_INCLUDE_M4(lib/cmdline/config.m4) SMB_INCLUDE_M4(param/config.m4) SMB_INCLUDE_M4(libcli/auth/config.m4) SMB_INCLUDE_M4(libcli/auth/gensec.m4) +SMB_INCLUDE_M4(libcli/ldap/config.m4) SMB_INCLUDE_M4(libcli/config.m4) SMB_INCLUDE_M4(librpc/config.m4) SMB_INCLUDE_M4(libcli/libsmb.m4) diff --git a/source4/include/asn_1.h b/source4/include/asn_1.h index 78cc340f44..6bd79e3fbe 100644 --- a/source4/include/asn_1.h +++ b/source4/include/asn_1.h @@ -35,10 +35,11 @@ typedef struct { BOOL has_error; } ASN1_DATA; - #define ASN1_APPLICATION(x) ((x)+0x60) +#define ASN1_APPLICATION_SIMPLE(x) ((x)+0x40) #define ASN1_SEQUENCE(x) ((x)+0x30) #define ASN1_CONTEXT(x) ((x)+0xa0) +#define ASN1_CONTEXT_SIMPLE(x) ((x)+0x80) #define ASN1_GENERAL_STRING 0x1b #define ASN1_OCTET_STRING 0x4 #define ASN1_OID 0x6 diff --git a/source4/include/includes.h b/source4/include/includes.h index 1e6cc0e5a4..3821269f2b 100644 --- a/source4/include/includes.h +++ b/source4/include/includes.h @@ -641,6 +641,7 @@ extern int errno; #include "rewrite.h" #include "smb.h" #include "ads.h" +#include "libcli/ldap/ldap.h" #include "nameserv.h" #include "secrets.h" diff --git a/source4/libcli/ldap/config.m4 b/source4/libcli/ldap/config.m4 new file mode 100644 index 0000000000..01f78279bf --- /dev/null +++ b/source4/libcli/ldap/config.m4 @@ -0,0 +1 @@ +SMB_SUBSYSTEM_MK(LIBCLI_LDAP,libcli/ldap/config.mk) diff --git a/source4/libcli/ldap/config.mk b/source4/libcli/ldap/config.mk new file mode 100644 index 0000000000..397cfe0b72 --- /dev/null +++ b/source4/libcli/ldap/config.mk @@ -0,0 +1,6 @@ +################################# +# Start SUBSYSTEM LIBCLI_LDAP +[SUBSYSTEM::LIBCLI_LDAP] +ADD_OBJ_FILES = libcli/ldap/ldap.o +# End SUBSYSTEM LIBCLI_LDAP +################################# diff --git a/source4/libcli/ldap/ldap.c b/source4/libcli/ldap/ldap.c new file mode 100644 index 0000000000..38fff7e357 --- /dev/null +++ b/source4/libcli/ldap/ldap.c @@ -0,0 +1,1989 @@ +/* + Unix SMB/CIFS mplementation. + LDAP protocol helper functions for SAMBA + + Copyright (C) Andrew Tridgell 2004 + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "includes.h" +#include "smb_ldap.h" + +/**************************************************************************** + * + * LDAP filter parser -- main routine is ldb_parse_filter + * + * Shamelessly stolen and adapted from Samba 4. + * + ***************************************************************************/ + +/* Hmm. A blob might be more appropriate here :-) */ + +struct ldb_val { + unsigned int length; + void *data; +}; + +enum ldb_parse_op {LDB_OP_SIMPLE, LDB_OP_AND, LDB_OP_OR, LDB_OP_NOT}; + +struct ldb_parse_tree { + enum ldb_parse_op operation; + union { + struct { + char *attr; + struct ldb_val value; + } simple; + struct { + unsigned int num_elements; + struct ldb_parse_tree **elements; + } list; + struct { + struct ldb_parse_tree *child; + } not; + } u; +}; + +#define LDB_ALL_SEP "()&|=!" + +/* + return next token element. Caller frees +*/ +static char *ldb_parse_lex(TALLOC_CTX *mem_ctx, const char **s, + const char *sep) +{ + const char *p = *s; + char *ret; + + while (isspace(*p)) { + p++; + } + *s = p; + + if (*p == 0) { + return NULL; + } + + if (strchr(sep, *p)) { + (*s) = p+1; + ret = talloc_strndup(mem_ctx, p, 1); + if (!ret) { + errno = ENOMEM; + } + return ret; + } + + while (*p && (isalnum(*p) || !strchr(sep, *p))) { + p++; + } + + if (p == *s) { + return NULL; + } + + ret = talloc_strndup(mem_ctx, *s, p - *s); + if (!ret) { + errno = ENOMEM; + } + + *s = p; + + return ret; +} + + +/* + find a matching close brace in a string +*/ +static const char *match_brace(const char *s) +{ + unsigned int count = 0; + while (*s && (count != 0 || *s != ')')) { + if (*s == '(') { + count++; + } + if (*s == ')') { + count--; + } + s++; + } + if (! *s) { + return NULL; + } + return s; +} + +static struct ldb_parse_tree *ldb_parse_filter(TALLOC_CTX *mem_ctx, + const char **s); + +/* + <simple> ::= <attributetype> <filtertype> <attributevalue> +*/ +static struct ldb_parse_tree *ldb_parse_simple(TALLOC_CTX *mem_ctx, + const char *s) +{ + char *eq, *val, *l; + struct ldb_parse_tree *ret; + + l = ldb_parse_lex(mem_ctx, &s, LDB_ALL_SEP); + if (!l) { + return NULL; + } + + if (strchr("()&|=", *l)) + return NULL; + + eq = ldb_parse_lex(mem_ctx, &s, LDB_ALL_SEP); + if (!eq || strcmp(eq, "=") != 0) + return NULL; + + val = ldb_parse_lex(mem_ctx, &s, ")"); + if (val && strchr("()&|", *val)) + return NULL; + + ret = talloc(mem_ctx, sizeof(*ret)); + if (!ret) { + errno = ENOMEM; + return NULL; + } + + ret->operation = LDB_OP_SIMPLE; + ret->u.simple.attr = l; + ret->u.simple.value.data = val; + ret->u.simple.value.length = val?strlen(val):0; + + return ret; +} + + +/* + parse a filterlist + <and> ::= '&' <filterlist> + <or> ::= '|' <filterlist> + <filterlist> ::= <filter> | <filter> <filterlist> +*/ +static struct ldb_parse_tree *ldb_parse_filterlist(TALLOC_CTX *mem_ctx, + enum ldb_parse_op op, + const char *s) +{ + struct ldb_parse_tree *ret, *next; + + ret = talloc(mem_ctx, sizeof(*ret)); + + if (!ret) { + errno = ENOMEM; + return NULL; + } + + ret->operation = op; + ret->u.list.num_elements = 1; + ret->u.list.elements = talloc(mem_ctx, sizeof(*ret->u.list.elements)); + if (!ret->u.list.elements) { + errno = ENOMEM; + return NULL; + } + + ret->u.list.elements[0] = ldb_parse_filter(mem_ctx, &s); + if (!ret->u.list.elements[0]) { + return NULL; + } + + while (isspace(*s)) s++; + + while (*s && (next = ldb_parse_filter(mem_ctx, &s))) { + struct ldb_parse_tree **e; + e = talloc_realloc(mem_ctx, ret->u.list.elements, + sizeof(struct ldb_parse_tree) * + (ret->u.list.num_elements+1)); + if (!e) { + errno = ENOMEM; + return NULL; + } + ret->u.list.elements = e; + ret->u.list.elements[ret->u.list.num_elements] = next; + ret->u.list.num_elements++; + while (isspace(*s)) s++; + } + + return ret; +} + + +/* + <not> ::= '!' <filter> +*/ +static struct ldb_parse_tree *ldb_parse_not(TALLOC_CTX *mem_ctx, const char *s) +{ + struct ldb_parse_tree *ret; + + ret = talloc(mem_ctx, sizeof(*ret)); + if (!ret) { + errno = ENOMEM; + return NULL; + } + + ret->operation = LDB_OP_NOT; + ret->u.not.child = ldb_parse_filter(mem_ctx, &s); + if (!ret->u.not.child) + return NULL; + + return ret; +} + +/* + parse a filtercomp + <filtercomp> ::= <and> | <or> | <not> | <simple> +*/ +static struct ldb_parse_tree *ldb_parse_filtercomp(TALLOC_CTX *mem_ctx, + const char *s) +{ + while (isspace(*s)) s++; + + switch (*s) { + case '&': + return ldb_parse_filterlist(mem_ctx, LDB_OP_AND, s+1); + + case '|': + return ldb_parse_filterlist(mem_ctx, LDB_OP_OR, s+1); + + case '!': + return ldb_parse_not(mem_ctx, s+1); + + case '(': + case ')': + return NULL; + } + + return ldb_parse_simple(mem_ctx, s); +} + + +/* + <filter> ::= '(' <filtercomp> ')' +*/ +static struct ldb_parse_tree *ldb_parse_filter(TALLOC_CTX *mem_ctx, + const char **s) +{ + char *l, *s2; + const char *p, *p2; + struct ldb_parse_tree *ret; + + l = ldb_parse_lex(mem_ctx, s, LDB_ALL_SEP); + if (!l) { + return NULL; + } + + if (strcmp(l, "(") != 0) { + return NULL; + } + + p = match_brace(*s); + if (!p) { + return NULL; + } + p2 = p + 1; + + s2 = talloc_strndup(mem_ctx, *s, p - *s); + if (!s2) { + errno = ENOMEM; + return NULL; + } + + ret = ldb_parse_filtercomp(mem_ctx, s2); + + *s = p2; + + return ret; +} + +/* + main parser entry point. Takes a search string and returns a parse tree + + expression ::= <simple> | <filter> +*/ +static struct ldb_parse_tree *ldb_parse_tree(TALLOC_CTX *mem_ctx, const char *s) +{ + while (isspace(*s)) s++; + + if (*s == '(') { + return ldb_parse_filter(mem_ctx, &s); + } + + return ldb_parse_simple(mem_ctx, s); +} + +static BOOL ldap_push_filter(ASN1_DATA *data, struct ldb_parse_tree *tree) +{ + switch (tree->operation) { + case LDB_OP_SIMPLE: { + if ((tree->u.simple.value.length == 1) && + (((char *)(tree->u.simple.value.data))[0] == '*')) { + /* Just a presence test */ + asn1_push_tag(data, 0x87); + asn1_write(data, tree->u.simple.attr, + strlen(tree->u.simple.attr)); + asn1_pop_tag(data); + return !data->has_error; + } + + /* Equality is all we currently do... */ + asn1_push_tag(data, 0xa3); + asn1_write_OctetString(data, tree->u.simple.attr, + strlen(tree->u.simple.attr)); + asn1_write_OctetString(data, tree->u.simple.value.data, + tree->u.simple.value.length); + asn1_pop_tag(data); + break; + } + + case LDB_OP_AND: { + int i; + + asn1_push_tag(data, 0xa0); + for (i=0; i<tree->u.list.num_elements; i++) { + ldap_push_filter(data, tree->u.list.elements[i]); + } + asn1_pop_tag(data); + break; + } + + case LDB_OP_OR: { + int i; + + asn1_push_tag(data, 0xa1); + for (i=0; i<tree->u.list.num_elements; i++) { + ldap_push_filter(data, tree->u.list.elements[i]); + } + asn1_pop_tag(data); + break; + } + default: + return False; + } + return !data->has_error; +} + +/**************************************************************************** + * + * LDIF parser + * + * Shamelessly stolen and adapted from Samba 4. + * + ***************************************************************************/ + +/* + pull a ldif chunk, which is defined as a piece of data ending in \n\n or EOF + this routine removes any RFC2849 continuations and comments + + caller frees +*/ +static char *next_chunk(TALLOC_CTX *mem_ctx, + int (*fgetc_fn)(void *), void *private_data) +{ + size_t alloc_size=0, chunk_size = 0; + char *chunk = NULL; + int c; + int in_comment = 0; + + while ((c = fgetc_fn(private_data)) != EOF) { + if (chunk_size+1 >= alloc_size) { + char *c2; + alloc_size += 1024; + c2 = talloc_realloc(mem_ctx, chunk, alloc_size); + if (!c2) { + errno = ENOMEM; + return NULL; + } + chunk = c2; + } + + if (in_comment) { + if (c == '\n') { + in_comment = 0; + } + continue; + } + + /* handle continuation lines - see RFC2849 */ + if (c == ' ' && chunk_size > 1 && + chunk[chunk_size-1] == '\n') { + chunk_size--; + continue; + } + + /* chunks are terminated by a double line-feed */ + if (c == '\n' && chunk_size > 0 && + chunk[chunk_size-1] == '\n') { + chunk[chunk_size-1] = 0; + return chunk; + } + + if (c == '#' && + (chunk_size == 0 || chunk[chunk_size-1] == '\n')) { + in_comment = 1; + continue; + } + + /* ignore leading blank lines */ + if (chunk_size == 0 && c == '\n') { + continue; + } + + chunk[chunk_size++] = c; + } + + if (chunk) { + chunk[chunk_size] = 0; + } + + return chunk; +} + +/* simple ldif attribute parser */ +static int next_attr(char **s, const char **attr, struct ldb_val *value) +{ + char *p; + int base64_encoded = 0; + + if (strncmp(*s, "-\n", 2) == 0) { + value->length = 0; + *attr = "-"; + *s += 2; + return 0; + } + + p = strchr(*s, ':'); + if (!p) { + return -1; + } + + *p++ = 0; + + if (*p == ':') { + base64_encoded = 1; + p++; + } + + *attr = *s; + + while (isspace(*p)) { + p++; + } + + value->data = p; + + p = strchr(p, '\n'); + + if (!p) { + value->length = strlen((char *)value->data); + *s = ((char *)value->data) + value->length; + } else { + value->length = p - (char *)value->data; + *s = p+1; + *p = 0; + } + + if (base64_encoded) { + DATA_BLOB blob = base64_decode_data_blob(value->data); + memcpy(value->data, blob.data, blob.length); + value->length = blob.length; + ((char *)value->data)[value->length] = '\0'; + } + + return 0; +} + +static BOOL add_value_to_attrib(TALLOC_CTX *mem_ctx, struct ldb_val *value, + struct ldap_attribute *attrib) +{ + attrib->values = talloc_realloc(mem_ctx, attrib->values, + sizeof(*attrib->values) * + (attrib->num_values+1)); + if (attrib->values == NULL) + return False; + + attrib->values[attrib->num_values] = + data_blob_talloc(mem_ctx, value->data, value->length); + attrib->num_values += 1; + return True; +} + +static BOOL fill_add_attributes(struct ldap_message *msg, char **chunk) +{ + struct ldap_AddRequest *r = &msg->r.AddRequest; + const char *attr_name; + struct ldb_val value; + + r->num_attributes = 0; + r->attributes = NULL; + + while (next_attr(chunk, &attr_name, &value) == 0) { + int i; + struct ldap_attribute *attrib = NULL; + + for (i=0; i<r->num_attributes; i++) { + if (strequal(r->attributes[i].name, attr_name)) { + attrib = &r->attributes[i]; + break; + } + } + + if (attrib == NULL) { + r->attributes = talloc_realloc(msg->mem_ctx, + r->attributes, + sizeof(*r->attributes) * + (r->num_attributes+1)); + if (r->attributes == NULL) + return False; + + attrib = &(r->attributes[r->num_attributes]); + r->num_attributes += 1; + ZERO_STRUCTP(attrib); + attrib->name = talloc_strdup(msg->mem_ctx, + attr_name); + } + + if (!add_value_to_attrib(msg->mem_ctx, &value, attrib)) + return False; + } + return True; +} + +static 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, + sizeof(**mods) * ((*num_mods)+1)); + + if (*mods == NULL) + return False; + + (*mods)[*num_mods] = *mod; + *num_mods += 1; + return True; +} + +static BOOL fill_mods(struct ldap_message *msg, char **chunk) +{ + struct ldap_ModifyRequest *r = &msg->r.ModifyRequest; + const char *attr_name; + struct ldb_val value; + + r->num_mods = 0; + r->mods = NULL; + + while (next_attr(chunk, &attr_name, &value) == 0) { + + struct ldap_mod mod; + mod.type = LDAP_MODIFY_NONE; + + mod.attrib.name = talloc_strdup(msg->mem_ctx, value.data); + + if (strequal(attr_name, "add")) + mod.type = LDAP_MOD_ADD; + + if (strequal(attr_name, "delete")) + mod.type = LDAP_MOD_DELETE; + + if (strequal(attr_name, "replace")) + mod.type = LDAP_MOD_REPLACE; + + if (mod.type == LDAP_MODIFY_NONE) { + DEBUG(2, ("ldif modification type %s unsupported\n", + attr_name)); + return False; + } + + mod.attrib.num_values = 0; + mod.attrib.values = NULL; + + while (next_attr(chunk, &attr_name, &value) == 0) { + if (strequal(attr_name, "-")) + break; + if (!strequal(attr_name, mod.attrib.name)) { + DEBUG(3, ("attrib name %s does not " + "match %s\n", attr_name, + mod.attrib.name)); + return False; + } + if (!add_value_to_attrib(msg->mem_ctx, &value, + &mod.attrib)) { + DEBUG(3, ("Could not add value\n")); + return False; + } + } + + if (!add_mod_to_array_talloc(msg->mem_ctx, &mod, &r->mods, + &r->num_mods)) + return False; + } + + return True; +} + +/* + read from a LDIF source, creating a ldap_message +*/ +static struct ldap_message *ldif_read(int (*fgetc_fn)(void *), + void *private_data) +{ + struct ldap_message *msg; + const char *attr=NULL; + const char *dn; + char *chunk=NULL, *s; + struct ldb_val value; + + value.data = NULL; + + msg = new_ldap_message(); + if (msg == NULL) + return NULL; + + chunk = next_chunk(msg->mem_ctx, fgetc_fn, private_data); + if (!chunk) { + goto failed; + } + + s = chunk; + + if (next_attr(&s, &attr, &value) != 0) { + goto failed; + } + + /* first line must be a dn */ + if (!strequal(attr, "dn")) { + DEBUG(5, ("Error: First line of ldif must be a dn not '%s'\n", + attr)); + goto failed; + } + + dn = talloc_strdup(msg->mem_ctx, value.data); + + if (next_attr(&s, &attr, &value) != 0) { + goto failed; + } + + if (!strequal(attr, "changetype")) { + DEBUG(5, ("Error: Second line of ldif must be a changetype " + "not '%s'\n", attr)); + goto failed; + } + + if (strequal(value.data, "delete")) { + msg->type = LDAP_TAG_DelRequest; + msg->r.DelRequest.dn = dn; + return msg; + } + + if (strequal(value.data, "add")) { + msg->type = LDAP_TAG_AddRequest; + + msg->r.AddRequest.dn = dn; + + if (!fill_add_attributes(msg, &s)) + goto failed; + + return msg; + } + + if (strequal(value.data, "modify")) { + msg->type = LDAP_TAG_ModifyRequest; + + msg->r.ModifyRequest.dn = dn; + + if (!fill_mods(msg, &s)) + goto failed; + + return msg; + } + + DEBUG(3, ("changetype %s not supported\n", (char *)value.data)); + +failed: + destroy_ldap_message(msg); + return NULL; +} + +/* + a wrapper around ldif_read() for reading from const char* +*/ +struct ldif_read_string_state { + const char *s; +}; + +static int fgetc_string(void *private_data) +{ + struct ldif_read_string_state *state = private_data; + if (state->s[0] != 0) { + return *state->s++; + } + return EOF; +} + +struct ldap_message *ldap_ldif2msg(const char *s) +{ + struct ldif_read_string_state state; + state.s = s; + return ldif_read(fgetc_string, &state); +} + +static void ldap_encode_response(enum ldap_request_tag tag, + struct ldap_Result *result, + ASN1_DATA *data) +{ + asn1_push_tag(data, ASN1_APPLICATION(tag)); + 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 != NULL) + asn1_write_OctetString(data, result->referral, + strlen(result->referral)); + asn1_pop_tag(data); +} + +BOOL ldap_encode(struct ldap_message *msg, DATA_BLOB *result) +{ + ASN1_DATA data; + int i, j; + + ZERO_STRUCT(data); + 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(LDAP_TAG_BindRequest)); + 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, r->mechanism | 0x80); + 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, r->mechanism | 0xa0); + asn1_write_OctetString(&data, r->creds.SASL.mechanism, + strlen(r->creds.SASL.mechanism)); + asn1_write_OctetString(&data, r->creds.SASL.creds.data, + r->creds.SASL.creds.length); + asn1_pop_tag(&data); + break; + default: + return False; + } + + asn1_pop_tag(&data); + asn1_pop_tag(&data); + break; + } + case LDAP_TAG_BindResponse: { + struct ldap_BindResponse *r = &msg->r.BindResponse; + ldap_encode_response(msg->type, &r->response, &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(LDAP_TAG_SearchRequest)); + 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_BOOLEAN2(&data, r->attributesonly); + + { + TALLOC_CTX *mem_ctx = talloc_init("ldb_parse_tree"); + struct ldb_parse_tree *tree; + + if (mem_ctx == NULL) + return False; + + tree = ldb_parse_tree(mem_ctx, r->filter); + + if (tree == NULL) + return False; + + ldap_push_filter(&data, tree); + + talloc_destroy(mem_ctx); + } + + 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(LDAP_TAG_SearchResultEntry)); + 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 ldap_attribute *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_SearchResultDone *r = &msg->r.SearchResultDone; + ldap_encode_response(msg->type, r, &data); + break; + } + case LDAP_TAG_ModifyRequest: { + struct ldap_ModifyRequest *r = &msg->r.ModifyRequest; + asn1_push_tag(&data, ASN1_APPLICATION(LDAP_TAG_ModifyRequest)); + 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 ldap_attribute *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; + ldap_encode_response(msg->type, r, &data); + break; + } + case LDAP_TAG_AddRequest: { + struct ldap_AddRequest *r = &msg->r.AddRequest; + asn1_push_tag(&data, ASN1_APPLICATION(LDAP_TAG_AddRequest)); + 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 ldap_attribute *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; + ldap_encode_response(msg->type, r, &data); + break; + } + case LDAP_TAG_DelRequest: { + struct ldap_DelRequest *r = &msg->r.DelRequest; + asn1_push_tag(&data, + ASN1_APPLICATION_SIMPLE(LDAP_TAG_DelRequest)); + asn1_write(&data, r->dn, strlen(r->dn)); + asn1_pop_tag(&data); + break; + } + case LDAP_TAG_DelResponse: { + struct ldap_Result *r = &msg->r.DelResponse; + ldap_encode_response(msg->type, r, &data); + break; + } + case LDAP_TAG_ModifyDNRequest: { + struct ldap_ModifyDNRequest *r = &msg->r.ModifyDNRequest; + asn1_push_tag(&data, + ASN1_APPLICATION(LDAP_TAG_ModifyDNRequest)); + asn1_write_OctetString(&data, r->dn, strlen(r->dn)); + asn1_write_OctetString(&data, r->newrdn, strlen(r->newrdn)); + asn1_write_BOOLEAN2(&data, r->deleteolddn); + if (r->newsuperior != NULL) { + 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; */ + break; + } + case LDAP_TAG_CompareRequest: { + struct ldap_CompareRequest *r = &msg->r.CompareRequest; + asn1_push_tag(&data, + ASN1_APPLICATION(LDAP_TAG_CompareRequest)); + 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, + strlen(r->value)); + asn1_pop_tag(&data); + asn1_pop_tag(&data); + break; + } + case LDAP_TAG_CompareResponse: { +/* struct ldap_Result *r = &msg->r.CompareResponse; */ + break; + } + case LDAP_TAG_AbandonRequest: { + struct ldap_AbandonRequest *r = &msg->r.AbandonRequest; + asn1_push_tag(&data, + ASN1_APPLICATION_SIMPLE(LDAP_TAG_AbandonRequest)); + asn1_write_Integer(&data, r->messageid); + asn1_pop_tag(&data); + break; + } + case LDAP_TAG_SearchResultReference: { +/* struct ldap_SearchResRef *r = &msg->r.SearchResultReference; */ + break; + } + case LDAP_TAG_ExtendedRequest: { + struct ldap_ExtendedRequest *r = &msg->r.ExtendedRequest; + asn1_push_tag(&data, ASN1_APPLICATION(LDAP_TAG_ExtendedRequest)); + asn1_push_tag(&data, ASN1_CONTEXT_SIMPLE(0)); + asn1_write(&data, r->oid, strlen(r->oid)); + asn1_pop_tag(&data); + 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; + ldap_encode_response(msg->type, &r->response, &data); + break; + } + default: + return False; + } + + asn1_pop_tag(&data); + *result = data_blob(data.data, data.length); + asn1_free(&data); + return True; +} + +static const char *blob2string_talloc(TALLOC_CTX *mem_ctx, + DATA_BLOB blob) +{ + char *result = talloc(mem_ctx, 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, + ASN1_DATA *data, + const char **result) +{ + DATA_BLOB string; + if (!asn1_read_OctetString(data, &string)) + return False; + *result = blob2string_talloc(mem_ctx, string); + data_blob_free(&string); + return True; +} + +static void ldap_decode_response(TALLOC_CTX *mem_ctx, + ASN1_DATA *data, + enum ldap_request_tag tag, + struct ldap_Result *result) +{ + asn1_start_tag(data, ASN1_APPLICATION(tag)); + 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_OCTET_STRING)) + asn1_read_OctetString_talloc(mem_ctx, data, &result->referral); + else + result->referral = NULL; + asn1_end_tag(data); +} + +static BOOL add_attrib_to_array_talloc(TALLOC_CTX *mem_ctx, + const struct ldap_attribute *attrib, + struct ldap_attribute **attribs, + int *num_attribs) +{ + *attribs = talloc_realloc(mem_ctx, *attribs, + sizeof(**attribs) * (*num_attribs+1)); + + if (*attribs == NULL) + return False; + + (*attribs)[*num_attribs] = *attrib; + *num_attribs += 1; + return True; +} + +static BOOL ldap_decode_filter(TALLOC_CTX *mem_ctx, ASN1_DATA *data, + char **filter) +{ + uint8 filter_tag, tag_desc; + + if (!asn1_peek_uint8(data, &filter_tag)) + return False; + + tag_desc = filter_tag; + filter_tag &= 0x1f; /* strip off the asn1 stuff */ + tag_desc &= 0xe0; + + switch(filter_tag) { + case 0: { + /* AND of one or more filters */ + if (tag_desc != 0xa0) /* context compount */ + return False; + + asn1_start_tag(data, ASN1_CONTEXT(0)); + + *filter = talloc_strdup(mem_ctx, "(&"); + if (*filter == NULL) + return False; + + while (asn1_tag_remaining(data) > 0) { + char *subfilter; + if (!ldap_decode_filter(mem_ctx, data, &subfilter)) + return False; + *filter = talloc_asprintf(mem_ctx, "%s%s", *filter, + subfilter); + if (*filter == NULL) + return False; + } + asn1_end_tag(data); + + *filter = talloc_asprintf(mem_ctx, "%s)", *filter); + break; + } + case 1: { + /* OR of one or more filters */ + if (tag_desc != 0xa0) /* context compount */ + return False; + + asn1_start_tag(data, ASN1_CONTEXT(1)); + + *filter = talloc_strdup(mem_ctx, "(|"); + if (*filter == NULL) + return False; + + while (asn1_tag_remaining(data) > 0) { + char *subfilter; + if (!ldap_decode_filter(mem_ctx, data, &subfilter)) + return False; + *filter = talloc_asprintf(mem_ctx, "%s%s", *filter, + subfilter); + if (*filter == NULL) + return False; + } + + asn1_end_tag(data); + + *filter = talloc_asprintf(mem_ctx, "%s)", *filter); + break; + } + case 3: { + /* equalityMatch */ + const char *attrib, *value; + if (tag_desc != 0xa0) /* context compound */ + return False; + asn1_start_tag(data, ASN1_CONTEXT(3)); + asn1_read_OctetString_talloc(mem_ctx, data, &attrib); + asn1_read_OctetString_talloc(mem_ctx, data, &value); + asn1_end_tag(data); + if ((data->has_error) || (attrib == NULL) || (value == NULL)) + return False; + *filter = talloc_asprintf(mem_ctx, "(%s=%s)", attrib, value); + break; + } + case 7: { + /* Normal presence, "attribute=*" */ + int attr_len; + char *attr_name; + if (tag_desc != 0x80) /* context simple */ + return False; + if (!asn1_start_tag(data, ASN1_CONTEXT_SIMPLE(7))) + return False; + attr_len = asn1_tag_remaining(data); + attr_name = malloc(attr_len+1); + if (attr_name == NULL) + return False; + asn1_read(data, attr_name, attr_len); + attr_name[attr_len] = '\0'; + *filter = talloc_asprintf(mem_ctx, "(%s=*)", attr_name); + SAFE_FREE(attr_name); + asn1_end_tag(data); + break; + } + default: + return False; + } + if (*filter == NULL) + return False; + return True; +} + +static void ldap_decode_attrib(TALLOC_CTX *mem_ctx, ASN1_DATA *data, + struct ldap_attribute *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; + struct ldb_val value; + asn1_read_OctetString(data, &blob); + value.data = blob.data; + value.length = blob.length; + add_value_to_attrib(mem_ctx, &value, attrib); + data_blob_free(&blob); + } + asn1_end_tag(data); + asn1_end_tag(data); + +} + +static void ldap_decode_attribs(TALLOC_CTX *mem_ctx, ASN1_DATA *data, + struct ldap_attribute **attributes, + int *num_attributes) +{ + asn1_start_tag(data, ASN1_SEQUENCE(0)); + while (asn1_peek_tag(data, ASN1_SEQUENCE(0))) { + struct ldap_attribute 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); +} + +BOOL ldap_decode(ASN1_DATA *data, struct ldap_message *msg) +{ + uint8 tag; + + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_Integer(data, &msg->messageid); + + if (!asn1_peek_uint8(data, &tag)) + return False; + + switch(tag) { + + case ASN1_APPLICATION(LDAP_TAG_BindRequest): { + struct ldap_BindRequest *r = &msg->r.BindRequest; + msg->type = LDAP_TAG_BindRequest; + asn1_start_tag(data, ASN1_APPLICATION(LDAP_TAG_BindRequest)); + asn1_read_Integer(data, &r->version); + asn1_read_OctetString_talloc(msg->mem_ctx, data, &r->dn); + if (asn1_peek_tag(data, 0x80)) { + int pwlen; + r->creds.password = ""; + /* Mechanism 0 (SIMPLE) */ + asn1_start_tag(data, 0x80); + pwlen = asn1_tag_remaining(data); + if (pwlen != 0) { + char *pw = talloc(msg->mem_ctx, pwlen+1); + asn1_read(data, pw, pwlen); + pw[pwlen] = '\0'; + r->creds.password = pw; + } + asn1_end_tag(data); + } + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_BindResponse): { + struct ldap_BindResponse *r = &msg->r.BindResponse; + msg->type = LDAP_TAG_BindResponse; + ldap_decode_response(msg->mem_ctx, + data, LDAP_TAG_BindResponse, + &r->response); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_UnbindRequest): { + msg->type = LDAP_TAG_UnbindRequest; + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchRequest): { + struct ldap_SearchRequest *r = &msg->r.SearchRequest; + msg->type = LDAP_TAG_SearchRequest; + asn1_start_tag(data, ASN1_APPLICATION(LDAP_TAG_SearchRequest)); + asn1_read_OctetString_talloc(msg->mem_ctx, 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_BOOLEAN2(data, &r->attributesonly); + + /* Maybe create a TALLOC_CTX for the filter? This can waste + * quite a bit of memory recursing down. */ + ldap_decode_filter(msg->mem_ctx, data, &r->filter); + + 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->mem_ctx, data, + &attr)) + return False; + if (!add_string_to_array(msg->mem_ctx, attr, + &r->attributes, + &r->num_attributes)) + return False; + } + + 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, + ASN1_APPLICATION(LDAP_TAG_SearchResultEntry)); + asn1_read_OctetString_talloc(msg->mem_ctx, data, &r->dn); + ldap_decode_attribs(msg->mem_ctx, data, &r->attributes, + &r->num_attributes); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchResultDone): { + struct ldap_SearchResultDone *r = &msg->r.SearchResultDone; + msg->type = LDAP_TAG_SearchResultDone; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_SearchResultDone, r); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_SearchResultReference): { +/* struct ldap_SearchResRef *r = &msg->r.SearchResultReference; */ + msg->type = LDAP_TAG_SearchResultReference; + 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->mem_ctx, 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; + ZERO_STRUCT(mod); + asn1_start_tag(data, ASN1_SEQUENCE(0)); + asn1_read_enumerated(data, &mod.type); + ldap_decode_attrib(msg->mem_ctx, data, &mod.attrib); + asn1_end_tag(data); + if (!add_mod_to_array_talloc(msg->mem_ctx, &mod, + &r->mods, &r->num_mods)) + break; + } + + asn1_end_tag(data); + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyResponse): { + struct ldap_ModifyResponse *r = &msg->r.ModifyResponse; + msg->type = LDAP_TAG_ModifyResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_ModifyResponse, r); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_AddRequest): { + struct ldap_AddRequest *r = &msg->r.AddRequest; + msg->type = LDAP_TAG_AddRequest; + asn1_start_tag(data, ASN1_APPLICATION(LDAP_TAG_AddRequest)); + asn1_read_OctetString_talloc(msg->mem_ctx, data, &r->dn); + + r->attributes = NULL; + r->num_attributes = 0; + ldap_decode_attribs(msg->mem_ctx, data, &r->attributes, + &r->num_attributes); + + asn1_end_tag(data); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_AddResponse): { + struct ldap_AddResponse *r = &msg->r.AddResponse; + msg->type = LDAP_TAG_AddResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_AddResponse, r); + 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); + dn = talloc(msg->mem_ctx, 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_DelResponse *r = &msg->r.DelResponse; + msg->type = LDAP_TAG_DelResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_DelResponse, r); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyDNRequest): { +/* struct ldap_ModifyDNRequest *r = &msg->r.ModifyDNRequest; */ + msg->type = LDAP_TAG_ModifyDNRequest; + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ModifyDNResponse): { + struct ldap_ModifyDNResponse *r = &msg->r.ModifyDNResponse; + msg->type = LDAP_TAG_ModifyDNResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_ModifyDNResponse, r); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_CompareRequest): { +/* struct ldap_CompareRequest *r = &msg->r.CompareRequest; */ + msg->type = LDAP_TAG_CompareRequest; + break; + } + + case ASN1_APPLICATION(LDAP_TAG_CompareResponse): { + struct ldap_CompareResponse *r = &msg->r.CompareResponse; + msg->type = LDAP_TAG_CompareResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_CompareResponse, r); + break; + } + + case ASN1_APPLICATION(LDAP_TAG_AbandonRequest): { +/* struct ldap_AbandonRequest *r = &msg->r.AbandonRequest; */ + msg->type = LDAP_TAG_AbandonRequest; + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ExtendedRequest): { +/* struct ldap_ExtendedRequest *r = &msg->r.ExtendedRequest; */ + msg->type = LDAP_TAG_ExtendedRequest; + break; + } + + case ASN1_APPLICATION(LDAP_TAG_ExtendedResponse): { + struct ldap_ExtendedResponse *r = &msg->r.ExtendedResponse; + msg->type = LDAP_TAG_ExtendedResponse; + ldap_decode_response(msg->mem_ctx, data, + LDAP_TAG_ExtendedResponse, &r->response); + /* I have to come across an operation that actually sends + * something back to really see what's going on. The currently + * needed pwdchange does not send anything back. */ + r->name = NULL; + r->value.data = NULL; + r->value.length = 0; + break; + } + + } + + asn1_end_tag(data); + return ((!data->has_error) && (data->nesting == NULL)); +} + +BOOL ldap_parse_basic_url(TALLOC_CTX *mem_ctx, const char *url, + char **host, uint16 *port, BOOL *ldaps) +{ + int tmp_port = 0; + fstring protocol; + fstring tmp_host; + const char *p = url; + + /* skip leading "URL:" (if any) */ + if ( strnequal( p, "URL:", 4 ) ) { + p += 4; + } + + /* Paranoia check */ + SMB_ASSERT(sizeof(protocol)>10 && sizeof(tmp_host)>254); + + sscanf(p, "%10[^:]://%254[^:/]:%d", protocol, tmp_host, &tmp_port); + + if (strequal(protocol, "ldap")) { + *port = 389; + *ldaps = False; + } else if (strequal(protocol, "ldaps")) { + *port = 636; + *ldaps = True; + } else { + DEBUG(0, ("unrecognised protocol (%s)!\n", protocol)); + return False; + } + + if (tmp_port != 0) + *port = tmp_port; + + *host = talloc_strdup(mem_ctx, tmp_host); + + return (*host != NULL); +} + +struct ldap_connection *new_ldap_connection(void) +{ + TALLOC_CTX *mem_ctx = talloc_init("ldap_connection"); + struct ldap_connection *result; + + if (mem_ctx == NULL) + return NULL; + + result = talloc(mem_ctx, sizeof(*result)); + + if (result == NULL) + return NULL; + + result->mem_ctx = mem_ctx; + result->next_msgid = 1; + result->outstanding = NULL; + result->searchid = 0; + result->search_entries = NULL; + return result; +} + +BOOL ldap_connect(struct ldap_connection *conn, const char *url) +{ + struct hostent *hp; + struct in_addr ip; + + if (!ldap_parse_basic_url(conn->mem_ctx, url, &conn->host, + &conn->port, &conn->ldaps)) + return False; + + hp = sys_gethostbyname(conn->host); + + if ((hp == NULL) || (hp->h_addr == NULL)) + return False; + + putip((char *)&ip, (char *)hp->h_addr); + + conn->sock = open_socket_out(SOCK_STREAM, &ip, conn->port, 10000); + + return (conn->sock >= 0); +} + +BOOL ldap_set_simple_creds(struct ldap_connection *conn, + const char *dn, const char *password) +{ + conn->auth_dn = talloc_strdup(conn->mem_ctx, dn); + conn->simple_pw = talloc_strdup(conn->mem_ctx, password); + + return ((conn->auth_dn != NULL) && (conn->simple_pw != NULL)); +} + +struct ldap_message *new_ldap_message(void) +{ + TALLOC_CTX *mem_ctx = talloc_init("ldap_message"); + struct ldap_message *result; + + if (mem_ctx == NULL) + return NULL; + + result = talloc(mem_ctx, sizeof(*result)); + + if (result == NULL) + return NULL; + + result->mem_ctx = mem_ctx; + return result; +} + +void destroy_ldap_message(struct ldap_message *msg) +{ + if (msg != NULL) + talloc_destroy(msg->mem_ctx); +} + +BOOL ldap_send_msg(struct ldap_connection *conn, struct ldap_message *msg, + const struct timeval *endtime) +{ + DATA_BLOB request; + BOOL result; + struct ldap_queue_entry *entry; + + msg->messageid = conn->next_msgid++; + + if (!ldap_encode(msg, &request)) + return False; + + result = (write_data_until(conn->sock, request.data, request.length, + endtime) == request.length); + + data_blob_free(&request); + + if (!result) + return result; + + /* abandon and unbind don't expect results */ + + if ((msg->type == LDAP_TAG_AbandonRequest) || + (msg->type == LDAP_TAG_UnbindRequest)) + return True; + + entry = malloc(sizeof(*entry)); + + if (entry == NULL) + return False; + + entry->msgid = msg->messageid; + entry->msg = NULL; + DLIST_ADD(conn->outstanding, entry); + + return True; +} + +BOOL ldap_receive_msg(struct ldap_connection *conn, struct ldap_message *msg, + const struct timeval *endtime) +{ + struct asn1_data data; + BOOL result; + + if (!asn1_read_sequence_until(conn->sock, &data, endtime)) + return False; + + result = ldap_decode(&data, msg); + + asn1_free(&data); + return result; +} + +static struct ldap_message *recv_from_queue(struct ldap_connection *conn, + int msgid) +{ + struct ldap_queue_entry *e; + + for (e = conn->outstanding; e != NULL; e = e->next) { + + if (e->msgid == msgid) { + struct ldap_message *result = e->msg; + DLIST_REMOVE(conn->outstanding, e); + SAFE_FREE(e); + return result; + } + } + + return NULL; +} + +static void add_search_entry(struct ldap_connection *conn, + struct ldap_message *msg) +{ + struct ldap_queue_entry *e = malloc(sizeof *e); + struct ldap_queue_entry *tmp; + + if (e == NULL) + return; + + e->msg = msg; + DLIST_ADD_END(conn->search_entries, e, tmp); + return; +} + +static void fill_outstanding_request(struct ldap_connection *conn, + struct ldap_message *msg) +{ + struct ldap_queue_entry *e; + + for (e = conn->outstanding; e != NULL; e = e->next) { + if (e->msgid == msg->messageid) { + e->msg = msg; + return; + } + } + + /* This reply has not been expected, destroy the incoming msg */ + destroy_ldap_message(msg); + return; +} + +struct ldap_message *ldap_receive(struct ldap_connection *conn, int msgid, + const struct timeval *endtime) +{ + struct ldap_message *result = recv_from_queue(conn, msgid); + + if (result != NULL) + return result; + + while (True) { + struct asn1_data data; + result = new_ldap_message(); + BOOL res; + + if (!asn1_read_sequence_until(conn->sock, &data, endtime)) + return NULL; + + res = ldap_decode(&data, result); + asn1_free(&data); + + if (!res) + return NULL; + + if (result->messageid == msgid) + return result; + + if (result->type == LDAP_TAG_SearchResultEntry) { + add_search_entry(conn, result); + } else { + fill_outstanding_request(conn, result); + } + } + + return NULL; +} + +struct ldap_message *ldap_transaction(struct ldap_connection *conn, + struct ldap_message *request) +{ + if (!ldap_send_msg(conn, request, NULL)) + return False; + + return ldap_receive(conn, request->messageid, NULL); +} + +BOOL ldap_setup_connection(struct ldap_connection *conn, + const char *url) +{ + struct ldap_message *msg = new_ldap_message(); + struct ldap_message *response; + BOOL result; + + if (msg == NULL) + return False; + + if (!ldap_connect(conn, url)) { + destroy_ldap_message(msg); + return False; + } + + msg->messageid = conn->next_msgid++; + msg->type = LDAP_TAG_BindRequest; + msg->r.BindRequest.version = 3; + msg->r.BindRequest.dn = conn->auth_dn; + msg->r.BindRequest.mechanism = LDAP_AUTH_MECH_SIMPLE; + msg->r.BindRequest.creds.password = conn->simple_pw; + + if ((response = ldap_transaction(conn, msg)) == NULL) + return False; + + result = (response->r.BindResponse.response.resultcode == 0); + + destroy_ldap_message(msg); + destroy_ldap_message(response); + return result; +} + +static BOOL ldap_abandon_message(struct ldap_connection *conn, int msgid, + const struct timeval *endtime) +{ + struct ldap_message *msg = new_ldap_message(); + BOOL result; + + if (msg == NULL) + return False; + + msg->type = LDAP_TAG_AbandonRequest; + msg->r.AbandonRequest.messageid = msgid; + + result = ldap_send_msg(conn, msg, endtime); + destroy_ldap_message(msg); + return result; +} + +struct ldap_message *new_ldap_search_message(const char *base, + enum ldap_scope scope, + char *filter, + int num_attributes, + const char **attributes) +{ + struct ldap_message *res = new_ldap_message(); + + if (res == NULL) + return NULL; + + res->type = LDAP_TAG_SearchRequest; + res->r.SearchRequest.basedn = base; + res->r.SearchRequest.scope = scope; + res->r.SearchRequest.deref = LDAP_DEREFERENCE_NEVER; + res->r.SearchRequest.timelimit = 0; + res->r.SearchRequest.sizelimit = 0; + res->r.SearchRequest.attributesonly = False; + res->r.SearchRequest.filter = filter; + res->r.SearchRequest.num_attributes = num_attributes; + res->r.SearchRequest.attributes = attributes; + return res; +} + +struct ldap_message *new_ldap_simple_bind_msg(const char *dn, const char *pw) +{ + struct ldap_message *res = new_ldap_message(); + + if (res == NULL) + return NULL; + + res->type = LDAP_TAG_BindRequest; + res->r.BindRequest.version = 3; + res->r.BindRequest.dn = talloc_strdup(res->mem_ctx, dn); + res->r.BindRequest.mechanism = LDAP_AUTH_MECH_SIMPLE; + res->r.BindRequest.creds.password = talloc_strdup(res->mem_ctx, pw); + return res; +} + +BOOL ldap_setsearchent(struct ldap_connection *conn, struct ldap_message *msg, + const struct timeval *endtime) +{ + if ((conn->searchid != 0) && + (!ldap_abandon_message(conn, conn->searchid, endtime))) + return False; + + conn->searchid = conn->next_msgid; + return ldap_send_msg(conn, msg, endtime); +} + +struct ldap_message *ldap_getsearchent(struct ldap_connection *conn, + const struct timeval *endtime) +{ + struct ldap_message *result; + + if (conn->search_entries != NULL) { + struct ldap_queue_entry *e = conn->search_entries; + + result = e->msg; + DLIST_REMOVE(conn->search_entries, e); + SAFE_FREE(e); + return result; + } + + result = ldap_receive(conn, conn->searchid, endtime); + + if (result->type == LDAP_TAG_SearchResultEntry) + return result; + + if (result->type == LDAP_TAG_SearchResultDone) { + /* TODO: Handle Paged Results */ + destroy_ldap_message(result); + return NULL; + } + + /* TODO: Handle Search References here */ + return NULL; +} + +void ldap_endsearchent(struct ldap_connection *conn, + const struct timeval *endtime) +{ + struct ldap_queue_entry *e; + + e = conn->search_entries; + + while (e != NULL) { + struct ldap_queue_entry *next = e->next; + DLIST_REMOVE(conn->search_entries, e); + SAFE_FREE(e); + e = next; + } +} + +struct ldap_message *ldap_searchone(struct ldap_connection *conn, + struct ldap_message *msg, + const struct timeval *endtime) +{ + struct ldap_message *res1, *res2 = NULL; + if (!ldap_setsearchent(conn, msg, endtime)) + return NULL; + + res1 = ldap_getsearchent(conn, endtime); + + if (res1 != NULL) + res2 = ldap_getsearchent(conn, endtime); + + ldap_endsearchent(conn, endtime); + + if (res1 == NULL) + return NULL; + + if (res2 != NULL) { + /* More than one entry */ + destroy_ldap_message(res1); + destroy_ldap_message(res2); + return NULL; + } + + return res1; +} + +BOOL ldap_find_single_value(struct ldap_message *msg, const char *attr, + DATA_BLOB *value) +{ + int i; + struct ldap_SearchResEntry *r = &msg->r.SearchResultEntry; + + if (msg->type != LDAP_TAG_SearchResultEntry) + return False; + + for (i=0; i<r->num_attributes; i++) { + if (strequal(attr, r->attributes[i].name)) { + if (r->attributes[i].num_values != 1) + return False; + + *value = r->attributes[i].values[0]; + return True; + } + } + return False; +} + +BOOL ldap_find_single_string(struct ldap_message *msg, const char *attr, + TALLOC_CTX *mem_ctx, char **value) +{ + DATA_BLOB blob; + + if (!ldap_find_single_value(msg, attr, &blob)) + return False; + + *value = talloc(mem_ctx, blob.length+1); + + if (*value == NULL) + return False; + + memcpy(*value, blob.data, blob.length); + (*value)[blob.length] = '\0'; + return True; +} + +BOOL ldap_find_single_int(struct ldap_message *msg, const char *attr, + int *value) +{ + DATA_BLOB blob; + char *val; + int errno_save; + BOOL res; + + if (!ldap_find_single_value(msg, attr, &blob)) + return False; + + val = malloc(blob.length+1); + if (val == NULL) + return False; + + memcpy(val, blob.data, blob.length); + val[blob.length] = '\0'; + + errno_save = errno; + errno = 0; + + *value = strtol(val, NULL, 10); + + res = (errno == 0); + + free(val); + errno = errno_save; + + return res; +} + +int ldap_error(struct ldap_connection *conn) +{ + return 0; +} + +NTSTATUS ldap2nterror(int ldaperror) +{ + return NT_STATUS_OK; +} diff --git a/source4/libcli/ldap/ldap.h b/source4/libcli/ldap/ldap.h new file mode 100644 index 0000000000..96c1b82ca3 --- /dev/null +++ b/source4/libcli/ldap/ldap.h @@ -0,0 +1,246 @@ +/* + 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 2 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _SMB_LDAP_H +#define _SMB_LDAP_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 +}; + +struct ldap_Result { + int resultcode; + const char *dn; + const char *errormessage; + const char *referral; +}; + +struct ldap_attribute { + const char *name; + int num_values; + DATA_BLOB *values; +}; + +struct ldap_BindRequest { + int version; + const char *dn; + enum ldap_auth_mechanism mechanism; + union { + const char *password; + struct { + const char *mechanism; + DATA_BLOB creds; + } SASL; + } creds; +}; + +struct ldap_BindResponse { + struct ldap_Result response; + union { + DATA_BLOB credentials; + } SASL_Credentials; +}; + +struct ldap_UnbindRequest { +}; + +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 timelimit; + uint32 sizelimit; + BOOL attributesonly; + char *filter; + int num_attributes; + const char **attributes; +}; + +struct ldap_SearchResEntry { + const char *dn; + int num_attributes; + struct ldap_attribute *attributes; +}; + +struct ldap_SearchResRef { + int num_referrals; + const char **referrals; +}; + +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 ldap_attribute attrib; +}; + +struct ldap_ModifyRequest { + const char *dn; + int num_mods; + struct ldap_mod *mods; +}; + +struct ldap_AddRequest { + const char *dn; + int num_attributes; + struct ldap_attribute *attributes; +}; + +struct ldap_DelRequest { + const char *dn; +}; + +struct ldap_ModifyDNRequest { + const char *dn; + const char *newrdn; + BOOL deleteolddn; + const char *newsuperior; +}; + +struct ldap_CompareRequest { + const char *dn; + const char *attribute; + const char *value; +}; + +struct ldap_AbandonRequest { + uint32 messageid; +}; + +struct ldap_ExtendedRequest { + const char *oid; + DATA_BLOB value; +}; + +struct ldap_ExtendedResponse { + struct ldap_Result response; + const char *name; + DATA_BLOB value; +}; + +union ldap_Request { + 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_Control { + const char *oid; + BOOL critical; + DATA_BLOB value; +}; + +struct ldap_message { + TALLOC_CTX *mem_ctx; + uint32 messageid; + uint8 type; + union ldap_Request r; + int num_controls; + struct ldap_Control *controls; +}; + +struct ldap_queue_entry { + struct ldap_queue_entry *next, *prev; + int msgid; + struct ldap_message *msg; +}; + +struct ldap_connection { + TALLOC_CTX *mem_ctx; + int sock; + int next_msgid; + char *host; + uint16 port; + BOOL ldaps; + + const char *auth_dn; + const char *simple_pw; + + /* Current outstanding search entry */ + int searchid; + + /* List for incoming search entries */ + struct ldap_queue_entry *search_entries; + + /* Outstanding LDAP requests that have not yet been replied to */ + struct ldap_queue_entry *outstanding; +}; + +#endif diff --git a/source4/libcli/util/asn1.c b/source4/libcli/util/asn1.c index dcafb261ee..6ddce7882c 100644 --- a/source4/libcli/util/asn1.c +++ b/source4/libcli/util/asn1.c @@ -184,6 +184,14 @@ BOOL asn1_write_BOOLEAN2(ASN1_DATA *data, BOOL v) return !data->has_error; } +BOOL asn1_read_BOOLEAN2(ASN1_DATA *data, BOOL *v) +{ + asn1_start_tag(data, ASN1_BOOLEAN); + asn1_read_uint8(data, (uint8 *)v); + asn1_end_tag(data); + return !data->has_error; +} + /* check a BOOLEAN */ BOOL asn1_check_BOOLEAN(ASN1_DATA *data, BOOL v) { @@ -216,51 +224,52 @@ BOOL asn1_load(ASN1_DATA *data, DATA_BLOB blob) return True; } -/* read from a ASN1 buffer, advancing the buffer pointer */ -BOOL asn1_read(ASN1_DATA *data, void *p, int len) +/* Peek into an ASN1 buffer, not advancing the pointer */ +BOOL asn1_peek(ASN1_DATA *data, void *p, int len) { - if (len < 0 || data->ofs + len < data->ofs || data->ofs + len < len) { - data->has_error = True; + if (len < 0 || data->ofs + len < data->ofs || data->ofs + len < len) return False; - } - if (data->ofs + len > data->length) { - data->has_error = True; + if (data->ofs + len > data->length) return False; - } + memcpy(p, data->data + data->ofs, len); - data->ofs += len; return True; } -/* read a uint8_t from a ASN1 buffer */ -BOOL asn1_read_uint8(ASN1_DATA *data, uint8_t *v) -{ - return asn1_read(data, v, 1); -} - /* read from a ASN1 buffer, advancing the buffer pointer */ -BOOL asn1_peek(ASN1_DATA *data, void *p, int len) +BOOL asn1_read(ASN1_DATA *data, void *p, int len) { - if (len < 0 || data->ofs + len < data->ofs || data->ofs + len < len) { + if (!asn1_peek(data, p, len)) { data->has_error = True; return False; } - if (data->ofs + len > data->length) { - data->has_error = True; - return False; - } - memcpy(p, data->data + data->ofs, len); + data->ofs += len; return True; } /* read a uint8_t from a ASN1 buffer */ +BOOL asn1_read_uint8(ASN1_DATA *data, uint8_t *v) +{ + return asn1_read(data, v, 1); +} + BOOL asn1_peek_uint8(ASN1_DATA *data, uint8_t *v) { return asn1_peek(data, v, 1); } +BOOL asn1_peek_tag(ASN1_DATA *data, uint8_t tag) +{ + uint8_t b; + + if (!asn1_peek(data, &b, sizeof(b))) + return False; + + return (b == tag); +} + /* start reading a nested asn1 structure */ BOOL asn1_start_tag(ASN1_DATA *data, uint8_t tag) { @@ -304,6 +313,89 @@ BOOL asn1_start_tag(ASN1_DATA *data, uint8_t tag) return !data->has_error; } +#if 0 +static BOOL read_one_uint8(int sock, uint8_t *result, ASN1_DATA *data, + const struct timeval *endtime) +{ + if (read_data_until(sock, result, 1, endtime) != 1) + return False; + + return asn1_write(data, result, 1); +} + +/* Read a complete ASN sequence (ie LDAP result) from a socket */ +BOOL asn1_read_sequence_until(int sock, ASN1_DATA *data, + const struct timeval *endtime) +{ + uint8_t b; + size_t len; + char *buf; + + ZERO_STRUCTP(data); + + if (!read_one_uint8(sock, &b, data, endtime)) + return False; + + if (b != 0x30) { + data->has_error = True; + return False; + } + + if (!read_one_uint8(sock, &b, data, endtime)) + return False; + + if (b & 0x80) { + int n = b & 0x7f; + if (!read_one_uint8(sock, &b, data, endtime)) + return False; + len = b; + while (n > 1) { + if (!read_one_uint8(sock, &b, data, endtime)) + return False; + len = (len<<8) | b; + n--; + } + } else { + len = b; + } + + buf = malloc(len); + if (buf == NULL) + return False; + + if (read_data_until(sock, buf, len, endtime) != len) + return False; + + if (!asn1_write(data, buf, len)) + return False; + + free(buf); + + data->ofs = 0; + + return True; +} +#endif + +/* Get the length to be expected in buf */ +BOOL asn1_object_length(uint8_t *buf, size_t buf_length, + uint8_t tag, size_t *result) +{ + ASN1_DATA data; + + /* Fake the asn1_load to avoid the memdup, this is just to be able to + * re-use the length-reading in asn1_start_tag */ + ZERO_STRUCT(data); + data.data = buf; + data.length = buf_length; + if (!asn1_start_tag(&data, tag)) + return False; + *result = asn1_tag_remaining(&data)+data.ofs; + /* We can't use asn1_end_tag here, as we did not consume the complete + * tag, so asn1_end_tag would flag an error and not free nesting */ + free(data.nesting); + return True; +} /* stop reading a tag */ BOOL asn1_end_tag(ASN1_DATA *data) @@ -342,7 +434,7 @@ int asn1_tag_remaining(ASN1_DATA *data) BOOL asn1_read_OID(ASN1_DATA *data, char **OID) { uint8_t b; - char *oid = NULL; + char *tmp_oid = NULL; TALLOC_CTX *mem_ctx = talloc_init("asn1_read_OID"); if (!mem_ctx) { return False; @@ -351,8 +443,8 @@ BOOL asn1_read_OID(ASN1_DATA *data, char **OID) if (!asn1_start_tag(data, ASN1_OID)) return False; asn1_read_uint8(data, &b); - oid = talloc_asprintf(mem_ctx, "%u", b/40); - oid = talloc_asprintf_append(mem_ctx, oid, " %u", b%40); + tmp_oid = talloc_asprintf(mem_ctx, "%u", b/40); + tmp_oid = talloc_asprintf_append(mem_ctx, tmp_oid, " %u", b%40); while (!data->has_error && asn1_tag_remaining(data) > 0) { uint_t v = 0; @@ -360,12 +452,12 @@ BOOL asn1_read_OID(ASN1_DATA *data, char **OID) asn1_read_uint8(data, &b); v = (v<<7) | (b&0x7f); } while (!data->has_error && b & 0x80); - oid = talloc_asprintf_append(mem_ctx, oid, " %u", v); + tmp_oid = talloc_asprintf_append(mem_ctx, tmp_oid, " %u", v); } asn1_end_tag(data); - *OID = strdup(oid); + *OID = strdup(tmp_oid); talloc_destroy(mem_ctx); return (*OID && !data->has_error); @@ -439,6 +531,20 @@ BOOL asn1_read_Integer(ASN1_DATA *data, int *i) } +/* read an interger */ +BOOL asn1_read_enumerated(ASN1_DATA *data, int *v) +{ + *v = 0; + + if (!asn1_start_tag(data, ASN1_ENUMERATED)) return False; + while (asn1_tag_remaining(data)>0) { + uint8_t b; + asn1_read_uint8(data, &b); + *v = (*v << 8) + b; + } + return asn1_end_tag(data); +} + /* check a enumarted value is correct */ BOOL asn1_check_enumerated(ASN1_DATA *data, int v) { |