/* * @brief Decrypt an attribute value. * * Returns a decrypted copy of the value, the original value is left intact. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully decrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context the will own the memory allocated * @param ldb ldb context, to allow logging. * @param val The ldb value to decrypt, not altered or freed * @param data The context data for this module. * * @return The decrypted ldb_val, or data_blob_null if there was an error. */ static struct ldb_val decrypt_value(int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, const struct ldb_val val, const struct es_data *data) { struct ldb_val dec; struct EncryptedSecret es; struct PlaintextSecret ps = { data_blob_null}; int rc; TALLOC_CTX *frame = talloc_stackframe(); rc = ndr_pull_struct_blob(&val, frame, &es, (ndr_pull_flags_fn_t) ndr_pull_EncryptedSecret); if(!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_asprintf_errstring(ldb, "Error(%d) unpacking encrypted secret, " "data possibly corrupted or altered\n", rc); *err = LDB_ERR_OPERATIONS_ERROR; TALLOC_FREE(frame); return data_blob_null; } if (!check_header(&es)) { /* * Header is invalid so can't be an encrypted value */ ldb_set_errstring(ldb, "Invalid EncryptedSecrets header\n"); *err = LDB_ERR_OPERATIONS_ERROR; return data_blob_null; } #ifdef BUILD_WITH_GNUTLS_AEAD gnutls_decrypt_aead(err, frame, ldb, &es, &ps, data); #elif defined BUILD_WITH_SAMBA_AES_GCM samba_decrypt_aead(err, frame, ldb, &es, &ps, data); #endif if (*err != LDB_SUCCESS) { TALLOC_FREE(frame); return data_blob_null; } dec = data_blob_talloc(ctx, ps.cleartext.data, ps.cleartext.length); if (dec.data == NULL) { TALLOC_FREE(frame); ldb_set_errstring(ldb, "Out of memory, copying value\n"); *err = LDB_ERR_OPERATIONS_ERROR; return data_blob_null; } TALLOC_FREE(frame); return dec; }
/* * @brief Encrypt all the values on an ldb_message_element * * Returns a copy of the original attribute with all values encrypted * by encrypt_value(), the original attribute is left intact. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully encrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context the will own the memory allocated * for the new ldb_message_element. * @param ldb ldb context, to allow logging. * @param el The ldb_message_elemen to encrypt, not altered or freed * @param data The context data for this module. * * @return Pointer encrypted lsb_message_element, will be NULL if there was * an error. */ static struct ldb_message_element *encrypt_element( int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, const struct ldb_message_element *el, const struct es_data *data) { struct ldb_message_element* enc; int i; enc = talloc_zero(ctx, struct ldb_message_element); if (enc == NULL) { ldb_set_errstring(ldb, "Out of memory, allocating ldb_message_" "element\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } enc->flags = el->flags; enc->num_values = el->num_values; enc->values = talloc_array(enc, struct ldb_val, enc->num_values); if (enc->values == NULL) { TALLOC_FREE(enc); ldb_set_errstring(ldb, "Out of memory, allocating values array\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } enc->name = talloc_strdup(enc, el->name); if (enc->name == NULL) { TALLOC_FREE(enc); ldb_set_errstring(ldb, "Out of memory, copying element name\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } for (i = 0; i < el->num_values; i++) { enc->values[i] = encrypt_value( err, enc->values, ldb, el->values[i], data); if (*err != LDB_SUCCESS) { TALLOC_FREE(enc); return NULL; } } return enc; }
/* * @brief Decrypt all the encrypted values on an ldb_message_element * * Returns a copy of the original attribute with all values decrypted by * decrypt_value(), the original attribute is left intact. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully encrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context the will own the memory allocated * for the new ldb_message_element. * @param ldb ldb context, to allow logging. * @param el The ldb_message_elemen to decrypt, not altered or freed * @param data The context data for this module. * * @return Pointer decrypted lsb_message_element, will be NULL if there was * an error. */ static struct ldb_message_element *decrypt_element( int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, struct ldb_message_element* el, struct es_data *data) { int i; struct ldb_message_element* dec = talloc_zero(ctx, struct ldb_message_element); *err = LDB_SUCCESS; if (dec == NULL) { ldb_set_errstring(ldb, "Out of memory, allocating " "ldb_message_element\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } dec->num_values = el->num_values; dec->values = talloc_array(dec, struct ldb_val, dec->num_values); if (dec->values == NULL) { TALLOC_FREE(dec); ldb_set_errstring(ldb, "Out of memory, allocating values array\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } dec->name = talloc_strdup(dec, el->name); if (dec->name == NULL) { TALLOC_FREE(dec); ldb_set_errstring(ldb, "Out of memory, copying element name\n"); *err = LDB_ERR_OPERATIONS_ERROR; return NULL; } for (i = 0; i < el->num_values; i++) { dec->values[i] = decrypt_value(err, el->values, ldb, el->values[i], data); if (*err != LDB_SUCCESS) { TALLOC_FREE(dec); return NULL; } } return dec; }
/* delete a record */ static int lldb_delete(struct lldb_context *lldb_ac) { struct ldb_context *ldb; struct lldb_private *lldb = lldb_ac->lldb; struct ldb_module *module = lldb_ac->module; struct ldb_request *req = lldb_ac->req; char *dnstr; int ret; ldb = ldb_module_get_ctx(module); ldb_request_set_state(req, LDB_ASYNC_PENDING); dnstr = ldb_dn_alloc_linearized(lldb_ac, req->op.del.dn); ret = ldap_delete_ext(lldb->ldap, dnstr, NULL, NULL, &lldb_ac->msgid); if (ret != LDAP_SUCCESS) { ldb_set_errstring(ldb, ldap_err2string(ret)); } return lldb_ldap_to_ldb(ret); }
/* modify a record */ static int lldb_modify(struct lldb_context *lldb_ac) { struct ldb_context *ldb; struct lldb_private *lldb = lldb_ac->lldb; struct ldb_module *module = lldb_ac->module; struct ldb_request *req = lldb_ac->req; LDAPMod **mods; char *dn; int ret; ldb = ldb_module_get_ctx(module); ldb_request_set_state(req, LDB_ASYNC_PENDING); mods = lldb_msg_to_mods(lldb_ac, req->op.mod.message, 1); if (mods == NULL) { return LDB_ERR_OPERATIONS_ERROR; } dn = ldb_dn_alloc_linearized(lldb_ac, req->op.mod.message->dn); if (dn == NULL) { return LDB_ERR_OPERATIONS_ERROR; } ret = ldap_modify_ext(lldb->ldap, dn, mods, NULL, NULL, &lldb_ac->msgid); if (ret != LDAP_SUCCESS) { ldb_set_errstring(ldb, ldap_err2string(ret)); } return lldb_ldap_to_ldb(ret); }
/* check special dn's have valid attributes currently only @ATTRIBUTES is checked */ static int ltdb_check_special_dn(struct ldb_module *module, const struct ldb_message *msg) { struct ldb_context *ldb = ldb_module_get_ctx(module); unsigned int i, j; if (! ldb_dn_is_special(msg->dn) || ! ldb_dn_check_special(msg->dn, LTDB_ATTRIBUTES)) { return LDB_SUCCESS; } /* we have @ATTRIBUTES, let's check attributes are fine */ /* should we check that we deny multivalued attributes ? */ for (i = 0; i < msg->num_elements; i++) { if (ldb_attr_cmp(msg->elements[i].name, "distinguishedName") == 0) continue; for (j = 0; j < msg->elements[i].num_values; j++) { if (ltdb_check_at_attributes_values(&msg->elements[i].values[j]) != 0) { ldb_set_errstring(ldb, "Invalid attribute value in an @ATTRIBUTES entry"); return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; } } } return LDB_SUCCESS; }
/* we've made a modification to a dn - possibly reindex and update sequence number */ static int ltdb_modified(struct ldb_module *module, struct ldb_dn *dn) { int ret = LDB_SUCCESS; struct ltdb_private *ltdb = talloc_get_type(ldb_module_get_private(module), struct ltdb_private); /* only allow modifies inside a transaction, otherwise the * ldb is unsafe */ if (ltdb->in_transaction == 0) { ldb_set_errstring(ldb_module_get_ctx(module), "ltdb modify without transaction"); return LDB_ERR_OPERATIONS_ERROR; } if (ldb_dn_is_special(dn) && (ldb_dn_check_special(dn, LTDB_INDEXLIST) || ldb_dn_check_special(dn, LTDB_ATTRIBUTES)) ) { ret = ltdb_reindex(module); } /* If the modify was to a normal record, or any special except @BASEINFO, update the seq number */ if (ret == LDB_SUCCESS && !(ldb_dn_is_special(dn) && ldb_dn_check_special(dn, LTDB_BASEINFO)) ) { ret = ltdb_increase_sequence_number(module); } /* If the modify was to @OPTIONS, reload the cache */ if (ret == LDB_SUCCESS && ldb_dn_is_special(dn) && (ldb_dn_check_special(dn, LTDB_OPTIONS)) ) { ret = ltdb_cache_reload(module); } return ret; }
/* search the domain related to the provided dn allocate a new RID for the domain return the new sid string */ static int samldb_get_new_sid(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn *obj_dn, struct dom_sid **sid) { const char * const attrs[2] = { "objectSid", NULL }; struct ldb_result *res = NULL; struct ldb_dn *dom_dn; int ret; struct dom_sid *dom_sid; /* get the domain component part of the provided dn */ dom_dn = samldb_search_domain(module, mem_ctx, obj_dn); if (dom_dn == NULL) { ldb_asprintf_errstring(module->ldb, "Invalid dn (%s) not child of a domain object!\n", ldb_dn_get_linearized(obj_dn)); return LDB_ERR_CONSTRAINT_VIOLATION; } /* find the domain sid */ ret = ldb_search(module->ldb, dom_dn, LDB_SCOPE_BASE, "objectSid=*", attrs, &res); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(module->ldb, "samldb_get_new_sid: error retrieving domain sid from %s: %s!\n", ldb_dn_get_linearized(dom_dn), ldb_errstring(module->ldb)); talloc_free(res); return ret; } if (res->count != 1) { ldb_asprintf_errstring(module->ldb, "samldb_get_new_sid: error retrieving domain sid from %s: not found!\n", ldb_dn_get_linearized(dom_dn)); talloc_free(res); return LDB_ERR_CONSTRAINT_VIOLATION; } dom_sid = samdb_result_dom_sid(res, res->msgs[0], "objectSid"); if (dom_sid == NULL) { ldb_set_errstring(module->ldb, "samldb_get_new_sid: error parsing domain sid!\n"); talloc_free(res); return LDB_ERR_CONSTRAINT_VIOLATION; } /* allocate a new Rid for the domain */ ret = samldb_allocate_next_rid(module, mem_ctx, dom_dn, dom_sid, sid); if (ret != 0) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to increment nextRid of %s: %s\n", ldb_dn_get_linearized(dom_dn), ldb_errstring(module->ldb)); talloc_free(res); return ret; } talloc_free(res); return ret; }
/* * @brief Get the directory containing the key files. * * @param ctx talloc memory context that will own the return value * @param ldb ldb context, to allow logging * * @return zero terminated string, the directory containing the key file * allocated on ctx. * */ static const char* get_key_directory(TALLOC_CTX *ctx, struct ldb_context *ldb) { const char *sam_ldb_path = NULL; const char *private_dir = NULL; char *p = NULL; /* * Work out where *our* key file is. It must be in * the same directory as sam.ldb */ sam_ldb_path = ldb_get_opaque(ldb, "ldb_url"); if (sam_ldb_path == NULL) { ldb_set_errstring(ldb, "Unable to get ldb_url\n"); return NULL; } if (strncmp("tdb://", sam_ldb_path, 6) == 0) { sam_ldb_path += 6; } private_dir = talloc_strdup(ctx, sam_ldb_path); if (private_dir == NULL) { ldb_set_errstring(ldb, "Out of memory building encrypted " "secrets key\n"); return NULL; } p = strrchr(private_dir, '/'); if (p != NULL) { *p = '\0'; } else { private_dir = talloc_strdup(ctx, "."); } return private_dir; }
/* rename a record */ static int lldb_rename(struct lldb_context *lldb_ac) { struct ldb_context *ldb; struct lldb_private *lldb = lldb_ac->lldb; struct ldb_module *module = lldb_ac->module; struct ldb_request *req = lldb_ac->req; const char *rdn_name; const struct ldb_val *rdn_val; char *old_dn; char *newrdn; char *parentdn; int ret; ldb = ldb_module_get_ctx(module); ldb_request_set_state(req, LDB_ASYNC_PENDING); old_dn = ldb_dn_alloc_linearized(lldb_ac, req->op.rename.olddn); if (old_dn == NULL) { return LDB_ERR_OPERATIONS_ERROR; } rdn_name = ldb_dn_get_rdn_name(req->op.rename.newdn); rdn_val = ldb_dn_get_rdn_val(req->op.rename.newdn); if ((rdn_name != NULL) && (rdn_val != NULL)) { newrdn = talloc_asprintf(lldb_ac, "%s=%s", rdn_name, rdn_val->length > 0 ? ldb_dn_escape_value(lldb, *rdn_val) : ""); } else { newrdn = talloc_strdup(lldb_ac, ""); } if (!newrdn) { return LDB_ERR_OPERATIONS_ERROR; } parentdn = ldb_dn_alloc_linearized(lldb_ac, ldb_dn_get_parent(lldb_ac, req->op.rename.newdn)); if (!parentdn) { return LDB_ERR_OPERATIONS_ERROR; } ret = ldap_rename(lldb->ldap, old_dn, newrdn, parentdn, 1, NULL, NULL, &lldb_ac->msgid); if (ret != LDAP_SUCCESS) { ldb_set_errstring(ldb, ldap_err2string(ret)); } return lldb_ldap_to_ldb(ret); }
/* log a message, and set the ldb error string to the same message */ void ldb_debug_set(struct ldb_context *ldb, enum ldb_debug_level level, const char *fmt, ...) { va_list ap; char *msg; va_start(ap, fmt); msg = talloc_vasprintf(ldb, fmt, ap); va_end(ap); if (msg != NULL) { ldb_set_errstring(ldb, msg); ldb_debug(ldb, level, "%s", msg); } talloc_free(msg); }
/* * @brief Create an new EncryptedSecret owned by the supplied talloc context. * * Create a new encrypted secret and initialise the header. * * @param ldb ldb context, to allow logging. * @param ctx The talloc memory context that will own the new EncryptedSecret * * @return pointer to the new encrypted secret, or NULL if there was an error */ static struct EncryptedSecret *makeEncryptedSecret(struct ldb_context *ldb, TALLOC_CTX *ctx) { struct EncryptedSecret *es = NULL; es = talloc_zero_size(ctx, sizeof(struct EncryptedSecret)); if (es == NULL) { ldb_set_errstring(ldb, "Out of memory, allocating " "struct EncryptedSecret\n"); return NULL; } es->header.magic = ENCRYPTED_SECRET_MAGIC_VALUE; es->header.version = SECRET_ATTRIBUTE_VERSION; es->header.algorithm = SECRET_ENCRYPTION_ALGORITHM; es->header.flags = 0; return es; }
/* Create a generic request context. */ struct map_context *map_init_context(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct map_context *ac; ldb = ldb_module_get_ctx(module); ac = talloc_zero(req, struct map_context); if (ac == NULL) { ldb_set_errstring(ldb, "Out of Memory"); return NULL; } ac->module = module; ac->req = req; return ac; }
/* deny instancetype modification */ static int instancetype_mod(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_message_element *el; /* do not manipulate our control entries */ if (ldb_dn_is_special(req->op.mod.message->dn)) { return ldb_next_request(module, req); } ldb_debug(ldb, LDB_DEBUG_TRACE, "instancetype_mod\n"); el = ldb_msg_find_element(req->op.mod.message, "instanceType"); if (el != NULL) { /* Except to allow dbcheck to fix things, this must never be modified */ if (!ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) { ldb_set_errstring(ldb, "instancetype: the 'instanceType' attribute can never be changed!"); return LDB_ERR_CONSTRAINT_VIOLATION; } } return ldb_next_request(module, req); }
static int inject_extended_dn_out(struct ldb_reply *ares, struct ldb_context *ldb, int type, bool remove_guid, bool remove_sid) { int ret; const DATA_BLOB *guid_blob; const DATA_BLOB *sid_blob; guid_blob = ldb_msg_find_ldb_val(ares->message, "objectGUID"); sid_blob = ldb_msg_find_ldb_val(ares->message, "objectSID"); if (!guid_blob) { ldb_set_errstring(ldb, "Did not find objectGUID to inject into extended DN"); return LDB_ERR_OPERATIONS_ERROR; } ret = ldb_dn_set_extended_component(ares->message->dn, "GUID", guid_blob); if (ret != LDB_SUCCESS) { return ret; } if (sid_blob) { ret = ldb_dn_set_extended_component(ares->message->dn, "SID", sid_blob); if (ret != LDB_SUCCESS) { return ret; } } if (remove_guid) { ldb_msg_remove_attr(ares->message, "objectGUID"); } if (sid_blob && remove_sid) { ldb_msg_remove_attr(ares->message, "objectSID"); } return LDB_SUCCESS; }
/* * @brief Allocate and populate a data blob with a PlaintextSecret structure. * * Allocate a new data blob and populate it with a serialised PlaintextSecret, * containing the ldb_val * * @param ctx The talloc memory context that will own the allocated memory. * @param ldb ldb context, to allow logging. * @param val The ldb value to serialise. * * @return The populated data blob or data_blob_null if there was an error. */ static DATA_BLOB makePlainText(TALLOC_CTX *ctx, struct ldb_context *ldb, const struct ldb_val val) { struct PlaintextSecret ps = { .cleartext = data_blob_null}; DATA_BLOB pt = data_blob_null; int rc; ps.cleartext.length = val.length; ps.cleartext.data = val.data; rc = ndr_push_struct_blob(&pt, ctx, &ps, (ndr_push_flags_fn_t) ndr_push_PlaintextSecret); if (!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_set_errstring(ldb, "Unable to ndr push PlaintextSecret\n"); return data_blob_null; } return pt; }
static int samba_dsdb_init(struct ldb_module *module) { struct ldb_context *ldb = ldb_module_get_ctx(module); int ret, len, i; TALLOC_CTX *tmp_ctx = talloc_new(module); struct ldb_result *res; struct ldb_message *rootdse_msg = NULL, *partition_msg; struct ldb_dn *samba_dsdb_dn, *partition_dn; struct ldb_module *backend_module, *module_chain; const char **final_module_list, **reverse_module_list; /* Add modules to the list to activate them by default beware often order is important Some Known ordering constraints: - rootdse must be first, as it makes redirects from "" -> cn=rootdse - extended_dn_in must be before objectclass.c, as it resolves the DN - objectclass must be before password_hash and samldb since these LDB modules require the expanded "objectClass" list - objectclass must be before descriptor and acl, as both assume that objectClass values are sorted - objectclass_attrs must be behind operational in order to see all attributes (the operational module protects and therefore suppresses per default some important ones) - partition must be last - each partition has its own module list then The list is presented here as a set of declarations to show the stack visually - the code below then handles the creation of the list based on the parameters loaded from the database. */ static const char *modules_list1[] = {"resolve_oids", "rootdse", "schema_load", "lazy_commit", "dirsync", "paged_results", "ranged_results", "anr", "server_sort", "asq", "extended_dn_store", NULL }; /* extended_dn_in or extended_dn_in_openldap goes here */ static const char *modules_list1a[] = {"objectclass", "descriptor", "acl", "aclread", "samldb", "password_hash", "operational", "instancetype", "objectclass_attrs", NULL }; const char **link_modules; static const char *fedora_ds_modules[] = { "rdn_name", NULL }; static const char *openldap_modules[] = { NULL }; static const char *tdb_modules_list[] = { "rdn_name", "subtree_delete", "repl_meta_data", "subtree_rename", "linked_attributes", NULL}; const char *extended_dn_module; const char *extended_dn_module_ldb = "extended_dn_out_ldb"; const char *extended_dn_module_fds = "extended_dn_out_fds"; const char *extended_dn_module_openldap = "extended_dn_out_openldap"; const char *extended_dn_in_module = "extended_dn_in"; static const char *modules_list2[] = {"show_deleted", "new_partition", "partition", NULL }; const char **backend_modules; static const char *fedora_ds_backend_modules[] = { "nsuniqueid", "paged_searches", "simple_dn", NULL }; static const char *openldap_backend_modules[] = { "entryuuid", "simple_dn", NULL }; static const char *samba_dsdb_attrs[] = { "backendType", NULL }; static const char *partition_attrs[] = { "ldapBackend", NULL }; const char *backendType, *backendUrl; bool use_sasl_external = false; if (!tmp_ctx) { return ldb_oom(ldb); } ret = ldb_register_samba_handlers(ldb); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return ret; } samba_dsdb_dn = ldb_dn_new(tmp_ctx, ldb, "@SAMBA_DSDB"); if (!samba_dsdb_dn) { talloc_free(tmp_ctx); return ldb_oom(ldb); } partition_dn = ldb_dn_new(tmp_ctx, ldb, DSDB_PARTITION_DN); if (!partition_dn) { talloc_free(tmp_ctx); return ldb_oom(ldb); } #define CHECK_LDB_RET(check_ret) \ do { \ if (check_ret != LDB_SUCCESS) { \ talloc_free(tmp_ctx); \ return check_ret; \ } \ } while (0) ret = dsdb_module_search_dn(module, tmp_ctx, &res, samba_dsdb_dn, samba_dsdb_attrs, DSDB_FLAG_NEXT_MODULE, NULL); if (ret == LDB_ERR_NO_SUCH_OBJECT) { backendType = "ldb"; } else if (ret == LDB_SUCCESS) { backendType = ldb_msg_find_attr_as_string(res->msgs[0], "backendType", "ldb"); } else { talloc_free(tmp_ctx); return ret; } backend_modules = NULL; if (strcasecmp(backendType, "ldb") == 0) { extended_dn_module = extended_dn_module_ldb; link_modules = tdb_modules_list; } else { struct cli_credentials *cred; bool is_ldapi = false; ret = dsdb_module_search_dn(module, tmp_ctx, &res, partition_dn, partition_attrs, DSDB_FLAG_NEXT_MODULE, NULL); if (ret == LDB_SUCCESS) { backendUrl = ldb_msg_find_attr_as_string(res->msgs[0], "ldapBackend", "ldapi://"); if (!strncasecmp(backendUrl, "ldapi://", sizeof("ldapi://")-1)) { is_ldapi = true; } } else if (ret != LDB_ERR_NO_SUCH_OBJECT) { talloc_free(tmp_ctx); return ret; } if (strcasecmp(backendType, "fedora-ds") == 0) { link_modules = fedora_ds_modules; backend_modules = fedora_ds_backend_modules; extended_dn_module = extended_dn_module_fds; } else if (strcasecmp(backendType, "openldap") == 0) { link_modules = openldap_modules; backend_modules = openldap_backend_modules; extended_dn_module = extended_dn_module_openldap; extended_dn_in_module = "extended_dn_in_openldap"; if (is_ldapi) { use_sasl_external = true; } } else { return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, "invalid backend type"); } ret = ldb_set_opaque(ldb, "readOnlySchema", (void*)1); if (ret != LDB_SUCCESS) { ldb_set_errstring(ldb, "Failed to set readOnlySchema opaque"); } cred = ldb_get_opaque(ldb, "credentials"); if (!cred || !cli_credentials_authentication_requested(cred)) { ret = set_ldap_credentials(ldb, use_sasl_external); if (ret != LDB_SUCCESS) { return ret; } } } #define CHECK_MODULE_LIST \ do { \ if (!final_module_list) { \ talloc_free(tmp_ctx); \ return ldb_oom(ldb); \ } \ } while (0) final_module_list = str_list_copy_const(tmp_ctx, modules_list1); CHECK_MODULE_LIST; final_module_list = str_list_add_const(final_module_list, extended_dn_in_module); CHECK_MODULE_LIST; final_module_list = str_list_append_const(final_module_list, modules_list1a); CHECK_MODULE_LIST; final_module_list = str_list_append_const(final_module_list, link_modules); CHECK_MODULE_LIST; final_module_list = str_list_add_const(final_module_list, extended_dn_module); CHECK_MODULE_LIST; final_module_list = str_list_append_const(final_module_list, modules_list2); CHECK_MODULE_LIST; ret = read_at_rootdse_record(ldb, module, tmp_ctx, &rootdse_msg, NULL); CHECK_LDB_RET(ret); partition_msg = ldb_msg_new(tmp_ctx); partition_msg->dn = ldb_dn_new(partition_msg, ldb, "@" DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME); ret = prepare_modules_line(ldb, tmp_ctx, rootdse_msg, partition_msg, "schemaNamingContext", "schema_data", backend_modules); CHECK_LDB_RET(ret); ret = prepare_modules_line(ldb, tmp_ctx, rootdse_msg, partition_msg, NULL, NULL, backend_modules); CHECK_LDB_RET(ret); ret = ldb_set_opaque(ldb, DSDB_OPAQUE_PARTITION_MODULE_MSG_OPAQUE_NAME, partition_msg); CHECK_LDB_RET(ret); talloc_steal(ldb, partition_msg); /* Now prepare the module chain. Oddly, we must give it to ldb_load_modules_list in REVERSE */ for (len = 0; final_module_list[len]; len++) { /* noop */}; reverse_module_list = talloc_array(tmp_ctx, const char *, len+1); if (!reverse_module_list) { talloc_free(tmp_ctx); return ldb_oom(ldb); } for (i=0; i < len; i++) { reverse_module_list[i] = final_module_list[(len - 1) - i]; } reverse_module_list[i] = NULL; /* The backend (at least until the partitions module * reconfigures things) is the next module in the currently * loaded chain */ backend_module = ldb_module_next(module); ret = ldb_module_load_list(ldb, reverse_module_list, backend_module, &module_chain); CHECK_LDB_RET(ret); talloc_free(tmp_ctx); /* Set this as the 'next' module, so that we effectivly append it to module chain */ ldb_module_set_next(module, module_chain); return ldb_next_init(module); }
static int rdn_name_add(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct ldb_request *down_req; struct rename_context *ac; struct ldb_message *msg; struct ldb_message_element *attribute; const struct ldb_schema_attribute *a; const char *rdn_name; struct ldb_val rdn_val; int i, ret; ldb = ldb_module_get_ctx(module); /* do not manipulate our control entries */ if (ldb_dn_is_special(req->op.add.message->dn)) { return ldb_next_request(module, req); } ac = talloc_zero(req, struct rename_context); if (ac == NULL) { return LDB_ERR_OPERATIONS_ERROR; } ac->module = module; ac->req = req; msg = ldb_msg_copy_shallow(req, req->op.add.message); if (msg == NULL) { return LDB_ERR_OPERATIONS_ERROR; } rdn_name = ldb_dn_get_rdn_name(msg->dn); if (rdn_name == NULL) { return LDB_ERR_OPERATIONS_ERROR; } rdn_val = ldb_val_dup(msg, ldb_dn_get_rdn_val(msg->dn)); /* Perhaps someone above us tried to set this? */ if ((attribute = rdn_name_find_attribute(msg, "name")) != NULL ) { attribute->num_values = 0; } if (ldb_msg_add_value(msg, "name", &rdn_val, NULL) != 0) { return LDB_ERR_OPERATIONS_ERROR; } attribute = rdn_name_find_attribute(msg, rdn_name); if (!attribute) { if (ldb_msg_add_value(msg, rdn_name, &rdn_val, NULL) != 0) { return LDB_ERR_OPERATIONS_ERROR; } } else { a = ldb_schema_attribute_by_name(ldb, rdn_name); for (i = 0; i < attribute->num_values; i++) { ret = a->syntax->comparison_fn(ldb, msg, &rdn_val, &attribute->values[i]); if (ret == 0) { /* overwrite so it matches in case */ attribute->values[i] = rdn_val; break; } } if (i == attribute->num_values) { char *rdn_errstring = talloc_asprintf(ac, "RDN mismatch on %s: %s (%.*s) should match one of:", ldb_dn_get_linearized(msg->dn), rdn_name, (int)rdn_val.length, (const char *)rdn_val.data); for (i = 0; i < attribute->num_values; i++) { rdn_errstring = talloc_asprintf_append( rdn_errstring, " (%.*s)", (int)attribute->values[i].length, (const char *)attribute->values[i].data); } ldb_set_errstring(ldb, rdn_errstring); /* Match AD's error here */ return LDB_ERR_INVALID_DN_SYNTAX; } } ret = ldb_build_add_req(&down_req, ldb, req, msg, req->controls, ac, rdn_name_add_callback, req); if (ret != LDB_SUCCESS) { return ret; } talloc_steal(down_req, msg); /* go on with the call chain */ return ldb_next_request(module, down_req); }
static int ltdb_handle_request(struct ldb_module *module, struct ldb_request *req) { struct ldb_control *control_permissive; struct ldb_context *ldb; struct tevent_context *ev; struct ltdb_context *ac; struct tevent_timer *te; struct timeval tv; unsigned int i; ldb = ldb_module_get_ctx(module); control_permissive = ldb_request_get_control(req, LDB_CONTROL_PERMISSIVE_MODIFY_OID); for (i = 0; req->controls && req->controls[i]; i++) { if (req->controls[i]->critical && req->controls[i] != control_permissive) { ldb_asprintf_errstring(ldb, "Unsupported critical extension %s", req->controls[i]->oid); return LDB_ERR_UNSUPPORTED_CRITICAL_EXTENSION; } } if (req->starttime == 0 || req->timeout == 0) { ldb_set_errstring(ldb, "Invalid timeout settings"); return LDB_ERR_TIME_LIMIT_EXCEEDED; } ev = ldb_get_event_context(ldb); ac = talloc_zero(ldb, struct ltdb_context); if (ac == NULL) { ldb_oom(ldb); return LDB_ERR_OPERATIONS_ERROR; } ac->module = module; ac->req = req; tv.tv_sec = 0; tv.tv_usec = 0; te = tevent_add_timer(ev, ac, tv, ltdb_callback, ac); if (NULL == te) { talloc_free(ac); return LDB_ERR_OPERATIONS_ERROR; } if (req->timeout > 0) { tv.tv_sec = req->starttime + req->timeout; tv.tv_usec = 0; ac->timeout_event = tevent_add_timer(ev, ac, tv, ltdb_timeout, ac); if (NULL == ac->timeout_event) { talloc_free(ac); return LDB_ERR_OPERATIONS_ERROR; } } /* set a spy so that we do not try to use the request context * if it is freed before ltdb_callback fires */ ac->spy = talloc(req, struct ltdb_req_spy); if (NULL == ac->spy) { talloc_free(ac); return LDB_ERR_OPERATIONS_ERROR; } ac->spy->ctx = ac; talloc_set_destructor((TALLOC_CTX *)ac->spy, ltdb_request_destructor); return LDB_SUCCESS; }
static int samldb_fill_foreignSecurityPrincipal_object(struct ldb_module *module, const struct ldb_message *msg, struct ldb_message **ret_msg) { struct ldb_message *msg2; const char *rdn_name; struct dom_sid *dom_sid; struct dom_sid *sid; const char *dom_attrs[] = { "name", NULL }; struct ldb_message **dom_msgs; const char *errstr; int ret; TALLOC_CTX *mem_ctx = talloc_new(msg); if (!mem_ctx) { return LDB_ERR_OPERATIONS_ERROR; } /* build the new msg */ msg2 = ldb_msg_copy(mem_ctx, msg); if (!msg2) { ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_foreignSecurityPrincpal_object: ldb_msg_copy failed!\n"); talloc_free(mem_ctx); return LDB_ERR_OPERATIONS_ERROR; } ret = samdb_copy_template(module->ldb, msg2, "(&(CN=TemplateForeignSecurityPrincipal)(objectclass=foreignSecurityPrincipalTemplate))", &errstr); if (ret != 0) { ldb_asprintf_errstring(module->ldb, "samldb_fill_foreignSecurityPrincipal_object: " "Error copying template: %s", errstr); talloc_free(mem_ctx); return ret; } rdn_name = ldb_dn_get_rdn_name(msg2->dn); if (strcasecmp(rdn_name, "cn") != 0) { ldb_asprintf_errstring(module->ldb, "Bad RDN (%s=) for ForeignSecurityPrincipal, should be CN=!", rdn_name); talloc_free(mem_ctx); return LDB_ERR_CONSTRAINT_VIOLATION; } /* Slightly different for the foreign sids. We don't want * domain SIDs ending up there, it would cause all sorts of * pain */ sid = dom_sid_parse_talloc(msg2, (const char *)ldb_dn_get_rdn_val(msg2->dn)->data); if (!sid) { ldb_set_errstring(module->ldb, "No valid found SID in ForeignSecurityPrincipal CN!"); talloc_free(mem_ctx); return LDB_ERR_CONSTRAINT_VIOLATION; } if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) { talloc_free(sid); return LDB_ERR_OPERATIONS_ERROR; } dom_sid = dom_sid_dup(mem_ctx, sid); if (!dom_sid) { talloc_free(mem_ctx); return LDB_ERR_OPERATIONS_ERROR; } /* get the domain component part of the provided SID */ dom_sid->num_auths--; /* find the domain DN */ ret = gendb_search(module->ldb, mem_ctx, NULL, &dom_msgs, dom_attrs, "(&(objectSid=%s)(objectclass=domain))", ldap_encode_ndr_dom_sid(mem_ctx, dom_sid)); if (ret >= 1) { /* We don't really like the idea of foreign sids that are not foreign, but it happens */ const char *name = samdb_result_string(dom_msgs[0], "name", NULL); ldb_debug(module->ldb, LDB_DEBUG_TRACE, "NOTE (strange but valid): Adding foreign SID record with SID %s, but this domian (%s) is already in the database", dom_sid_string(mem_ctx, sid), name); } else if (ret == -1) { ldb_asprintf_errstring(module->ldb, "samldb_fill_foreignSecurityPrincipal_object: error searching for a domain with this sid: %s\n", dom_sid_string(mem_ctx, dom_sid)); talloc_free(dom_msgs); return LDB_ERR_OPERATIONS_ERROR; } /* This isn't an operation on a domain we know about, so just * check for the SID, looking for duplicates via the common * code */ ret = samldb_notice_sid(module, msg2, sid); if (ret == 0) { talloc_steal(msg, msg2); *ret_msg = msg2; } return ret; }
/* construct the token groups for SAM objects from a message */ static int construct_token_groups(struct ldb_module *module, struct ldb_message *msg, enum ldb_scope scope, struct ldb_request *parent) { struct ldb_context *ldb = ldb_module_get_ctx(module);; TALLOC_CTX *tmp_ctx = talloc_new(msg); unsigned int i; int ret; const char *filter; NTSTATUS status; struct dom_sid *primary_group_sid; const char *primary_group_string; const char *primary_group_dn; DATA_BLOB primary_group_blob; struct dom_sid *account_sid; const char *account_sid_string; const char *account_sid_dn; DATA_BLOB account_sid_blob; struct dom_sid *groupSIDs = NULL; unsigned int num_groupSIDs = 0; struct dom_sid *domain_sid; if (scope != LDB_SCOPE_BASE) { ldb_set_errstring(ldb, "Cannot provide tokenGroups attribute, this is not a BASE search"); return LDB_ERR_OPERATIONS_ERROR; } /* If it's not a user, it won't have a primaryGroupID */ if (ldb_msg_find_element(msg, "primaryGroupID") == NULL) { talloc_free(tmp_ctx); return LDB_SUCCESS; } /* Ensure it has an objectSID too */ account_sid = samdb_result_dom_sid(tmp_ctx, msg, "objectSid"); if (account_sid == NULL) { talloc_free(tmp_ctx); return LDB_SUCCESS; } status = dom_sid_split_rid(tmp_ctx, account_sid, &domain_sid, NULL); if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER)) { talloc_free(tmp_ctx); return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX; } else if (!NT_STATUS_IS_OK(status)) { talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; } primary_group_sid = dom_sid_add_rid(tmp_ctx, domain_sid, ldb_msg_find_attr_as_uint(msg, "primaryGroupID", ~0)); if (!primary_group_sid) { talloc_free(tmp_ctx); return ldb_oom(ldb); } /* only return security groups */ filter = talloc_asprintf(tmp_ctx, "(&(objectClass=group)(groupType:1.2.840.113556.1.4.803:=%u))", GROUP_TYPE_SECURITY_ENABLED); if (!filter) { talloc_free(tmp_ctx); return ldb_oom(ldb); } primary_group_string = dom_sid_string(tmp_ctx, primary_group_sid); if (!primary_group_string) { talloc_free(tmp_ctx); return ldb_oom(ldb); } primary_group_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", primary_group_string); if (!primary_group_dn) { talloc_free(tmp_ctx); return ldb_oom(ldb); } primary_group_blob = data_blob_string_const(primary_group_dn); account_sid_string = dom_sid_string(tmp_ctx, account_sid); if (!account_sid_string) { talloc_free(tmp_ctx); return ldb_oom(ldb); } account_sid_dn = talloc_asprintf(tmp_ctx, "<SID=%s>", account_sid_string); if (!account_sid_dn) { talloc_free(tmp_ctx); return ldb_oom(ldb); } account_sid_blob = data_blob_string_const(account_sid_dn); status = dsdb_expand_nested_groups(ldb, &account_sid_blob, true, /* We don't want to add the object's SID itself, it's not returend in this attribute */ filter, tmp_ctx, &groupSIDs, &num_groupSIDs); if (!NT_STATUS_IS_OK(status)) { ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s", account_sid_string, nt_errstr(status)); talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; } /* Expands the primary group - this function takes in * memberOf-like values, so we fake one up with the * <SID=S-...> format of DN and then let it expand * them, as long as they meet the filter - so only * domain groups, not builtin groups */ status = dsdb_expand_nested_groups(ldb, &primary_group_blob, false, filter, tmp_ctx, &groupSIDs, &num_groupSIDs); if (!NT_STATUS_IS_OK(status)) { ldb_asprintf_errstring(ldb, "Failed to construct tokenGroups: expanding groups of SID %s failed: %s", account_sid_string, nt_errstr(status)); talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; } for (i=0; i < num_groupSIDs; i++) { ret = samdb_msg_add_dom_sid(ldb, msg, msg, "tokenGroups", &groupSIDs[i]); if (ret) { talloc_free(tmp_ctx); return ret; } } return LDB_SUCCESS; }
/* add_record: add instancetype attribute */ static int instancetype_add(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_request *down_req; struct ldb_message *msg; struct ldb_message_element *el; uint32_t instanceType; int ret; /* do not manipulate our control entries */ if (ldb_dn_is_special(req->op.add.message->dn)) { return ldb_next_request(module, req); } ldb_debug(ldb, LDB_DEBUG_TRACE, "instancetype_add\n"); el = ldb_msg_find_element(req->op.add.message, "instanceType"); if (el != NULL) { if (el->num_values != 1) { ldb_set_errstring(ldb, "instancetype: the 'instanceType' attribute is single-valued!"); return LDB_ERR_UNWILLING_TO_PERFORM; } instanceType = ldb_msg_find_attr_as_uint(req->op.add.message, "instanceType", 0); if (!(instanceType & INSTANCE_TYPE_IS_NC_HEAD)) { /* * If we have no NC add operation (no TYPE_IS_NC_HEAD) * then "instanceType" can only be "0" or "TYPE_WRITE". */ if ((instanceType != 0) && ((instanceType & INSTANCE_TYPE_WRITE) == 0)) { ldb_set_errstring(ldb, "instancetype: if TYPE_IS_NC_HEAD wasn't set, then only TYPE_WRITE or 0 are allowed!"); return LDB_ERR_UNWILLING_TO_PERFORM; } } else { /* * If we have a NC add operation then we need also the * "TYPE_WRITE" flag in order to succeed, * unless this NC is not instantiated */ if (!(instanceType & INSTANCE_TYPE_UNINSTANT) && !(instanceType & INSTANCE_TYPE_WRITE)) { ldb_set_errstring(ldb, "instancetype: if TYPE_IS_NC_HEAD was set, then also TYPE_WRITE is requested!"); return LDB_ERR_UNWILLING_TO_PERFORM; } } /* we did only tests, so proceed with the original request */ return ldb_next_request(module, req); } /* we have to copy the message as the caller might have it as a const */ msg = ldb_msg_copy_shallow(req, req->op.add.message); if (msg == NULL) { return ldb_oom(ldb); } /* * TODO: calculate correct instance type */ instanceType = INSTANCE_TYPE_WRITE; ret = samdb_msg_add_uint(ldb, msg, msg, "instanceType", instanceType); if (ret != LDB_SUCCESS) { return ret; } ret = ldb_build_add_req(&down_req, ldb, req, msg, req->controls, req, dsdb_next_callback, req); LDB_REQ_SET_LOCATION(down_req); if (ret != LDB_SUCCESS) { return ret; } /* go on with the call chain */ return ldb_next_request(module, down_req); }
/* * Helper function converts a data blob to a gnutls_datum_t. * Note that this does not copy the data. * So the returned value should be treated as read only. * And that changes to the length of the underlying DATA_BLOB * will not be reflected in the returned object. * */ static const gnutls_datum_t convert_from_data_blob(DATA_BLOB blob) { const gnutls_datum_t datum = { .size = blob.length, .data = blob.data, }; return datum; } /* * @brief Get the gnutls algorithm needed to decrypt the EncryptedSecret * * @param ldb ldb context, to allow logging. * @param es the encrypted secret * * @return The gnutls algoritm number, or 0 if there is no match. * */ static int gnutls_get_algorithm(struct ldb_context *ldb, struct EncryptedSecret *es) { switch (es->header.algorithm) { case ENC_SECRET_AES_128_AEAD: return GNUTLS_CIPHER_AES_128_GCM; default: ldb_asprintf_errstring(ldb, "Unsupported encryption algorithm %d\n", es->header.algorithm); return 0; } } /* * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully encrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context the will own the memory allocated * @param ldb ldb context, to allow logging. * @param val The ldb value to encrypt, not altered or freed * @param data The context data for this module. * * @return The encrypted ldb_val, or data_blob_null if there was an error. */ static struct ldb_val gnutls_encrypt_aead(int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, const struct ldb_val val, const struct es_data *data) { struct EncryptedSecret *es = NULL; struct ldb_val enc = data_blob_null; DATA_BLOB pt = data_blob_null; gnutls_aead_cipher_hd_t cipher_hnd; int rc; TALLOC_CTX *frame = talloc_stackframe(); es = makeEncryptedSecret(ldb, frame); if (es == NULL) { goto error_exit; } pt = makePlainText(frame, ldb, val); if (pt.length == 0) { goto error_exit; } /* * Set the encryption key and initialize the encryption handle. */ { const size_t key_size = gnutls_cipher_get_key_size( data->encryption_algorithm); gnutls_datum_t cipher_key; DATA_BLOB key_blob = get_key(data); if (key_blob.length != key_size) { ldb_asprintf_errstring(ldb, "Invalid EncryptedSecrets key " "size, expected %zu bytes and " "it is %zu bytes\n", key_size, key_blob.length); goto error_exit; } cipher_key = convert_from_data_blob(key_blob); rc = gnutls_aead_cipher_init(&cipher_hnd, data->encryption_algorithm, &cipher_key); if (rc !=0) { ldb_asprintf_errstring(ldb, "gnutls_aead_cipher_init failed " "%s - %s\n", gnutls_strerror_name(rc), gnutls_strerror(rc)); goto error_exit; } } /* * Set the initialisation vector */ { unsigned iv_size = gnutls_cipher_get_iv_size( data->encryption_algorithm); uint8_t *iv; iv = talloc_zero_size(frame, iv_size); if (iv == NULL) { ldb_set_errstring(ldb, "Out of memory allocating IV\n"); goto error_exit_handle; } rc = gnutls_rnd(GNUTLS_RND_NONCE, iv, iv_size); if (rc !=0) { ldb_asprintf_errstring(ldb, "gnutls_rnd failed %s - %s\n", gnutls_strerror_name(rc), gnutls_strerror(rc)); goto error_exit_handle; } es->iv.length = iv_size; es->iv.data = iv; } /* * Encrypt the value. */ { const unsigned block_size = gnutls_cipher_get_block_size( data->encryption_algorithm); const unsigned tag_size = gnutls_cipher_get_tag_size( data->encryption_algorithm); const size_t ed_size = round_to_block_size( block_size, sizeof(struct PlaintextSecret) + val.length); const size_t en_size = ed_size + tag_size; uint8_t *ct = talloc_zero_size(frame, en_size); size_t el = en_size; if (ct == NULL) { ldb_set_errstring(ldb, "Out of memory allocation cipher " "text\n"); goto error_exit_handle; } rc = gnutls_aead_cipher_encrypt( cipher_hnd, es->iv.data, es->iv.length, &es->header, sizeof(struct EncryptedSecretHeader), tag_size, pt.data, pt.length, ct, &el); if (rc !=0) { ldb_asprintf_errstring(ldb, "gnutls_aead_cipher_encrypt '" "failed %s - %s\n", gnutls_strerror_name(rc), gnutls_strerror(rc)); *err = LDB_ERR_OPERATIONS_ERROR; return data_blob_null; } es->encrypted.length = el; es->encrypted.data = ct; gnutls_aead_cipher_deinit(cipher_hnd); } rc = ndr_push_struct_blob(&enc, ctx, es, (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); if (!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_set_errstring(ldb, "Unable to ndr push EncryptedSecret\n"); goto error_exit; } TALLOC_FREE(frame); return enc; error_exit_handle: gnutls_aead_cipher_deinit(cipher_hnd); error_exit: *err = LDB_ERR_OPERATIONS_ERROR; TALLOC_FREE(frame); return data_blob_null; } /* * @brief Decrypt data encrypted using an aead algorithm. * * Decrypt the data in ed and insert it into ev. The data was encrypted * with one of the gnutls aead compatable algorithms. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully decrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx The talloc context that will own the PlaintextSecret * @param ldb ldb context, to allow logging. * @param ev The value to be updated with the decrypted data. * @param ed The data to decrypt. * @param data The context data for this module. * * @return ev is updated with the unencrypted data. */ static void gnutls_decrypt_aead(int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, struct EncryptedSecret *es, struct PlaintextSecret *ps, const struct es_data *data) { gnutls_aead_cipher_hd_t cipher_hnd; DATA_BLOB pt = data_blob_null; const unsigned tag_size = gnutls_cipher_get_tag_size(es->header.algorithm); int rc; /* * Get the encryption key and initialise the encryption handle */ { gnutls_datum_t cipher_key; DATA_BLOB key_blob; const int algorithm = gnutls_get_algorithm(ldb, es); const size_t key_size = gnutls_cipher_get_key_size(algorithm); key_blob = get_key(data); if (algorithm == 0) { goto error_exit; } if (key_blob.length != key_size) { ldb_asprintf_errstring(ldb, "Invalid EncryptedSecrets key " "size, expected %zu bytes and " "it is %zu bytes\n", key_size, key_blob.length); goto error_exit; } cipher_key = convert_from_data_blob(key_blob); rc = gnutls_aead_cipher_init( &cipher_hnd, algorithm, &cipher_key); if (rc != 0) { ldb_asprintf_errstring(ldb, "gnutls_aead_cipher_init failed " "%s - %s\n", gnutls_strerror_name(rc), gnutls_strerror(rc)); goto error_exit; } } /* * Decrypt and validate the encrypted value */ pt.length = es->encrypted.length; pt.data = talloc_zero_size(ctx, es->encrypted.length); if (pt.data == NULL) { ldb_set_errstring(ldb, "Out of memory allocating plain text\n"); goto error_exit_handle; } rc = gnutls_aead_cipher_decrypt(cipher_hnd, es->iv.data, es->iv.length, &es->header, sizeof(struct EncryptedSecretHeader), tag_size, es->encrypted.data, es->encrypted.length, pt.data, &pt.length); if (rc != 0) { /* * Typically this will indicate that the data has been * corrupted i.e. the tag comparison has failed. * At the moment gnutls does not provide a separate * error code to indicate this */ ldb_asprintf_errstring(ldb, "gnutls_aead_cipher_decrypt failed " "%s - %s. Data possibly corrupted or " "altered\n", gnutls_strerror_name(rc), gnutls_strerror(rc)); goto error_exit_handle; } gnutls_aead_cipher_deinit(cipher_hnd); rc = ndr_pull_struct_blob(&pt, ctx, ps, (ndr_pull_flags_fn_t) ndr_pull_PlaintextSecret); if(!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_asprintf_errstring(ldb, "Error(%d) unpacking decrypted data, " "data possibly corrupted or altered\n", rc); goto error_exit; } return; error_exit_handle: gnutls_aead_cipher_deinit(cipher_hnd); error_exit: *err = LDB_ERR_OPERATIONS_ERROR; return; }
/* * @brief Decrypt data encrypted using an aead algorithm. * * Decrypt the data in ed and insert it into ev. The data was encrypted * with the samba aes gcm implementation. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully decrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context that will own the memory allocated * @param ldb ldb context, to allow logging. * @param ev The value to be updated with the decrypted data. * @param ed The data to decrypt. * @param data The context data for this module. * * @return ev is updated with the unencrypted data. */ static void samba_decrypt_aead(int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, struct EncryptedSecret *es, struct PlaintextSecret *ps, const struct es_data *data) { struct aes_gcm_128_context cctx; DATA_BLOB pt = data_blob_null; DATA_BLOB key_blob = data_blob_null; uint8_t sig[AES_BLOCK_SIZE] = {0, }; int rc; int cmp; TALLOC_CTX *frame = talloc_stackframe(); /* * Set the encryption key */ key_blob = get_key(data); if (key_blob.length != AES_BLOCK_SIZE) { ldb_asprintf_errstring(ldb, "Invalid EncryptedSecrets key size, " "expected %u bytes and is %zu bytes\n", AES_BLOCK_SIZE, key_blob.length); goto error_exit; } if (es->iv.length < AES_GCM_128_IV_SIZE) { ldb_asprintf_errstring(ldb, "Invalid EncryptedSecrets iv size, " "expected %u bytes and is %zu bytes\n", AES_GCM_128_IV_SIZE, es->iv.length); goto error_exit; } if (es->encrypted.length < AES_BLOCK_SIZE) { ldb_asprintf_errstring(ldb, "Invalid EncryptedData size, " "expected %u bytes and is %zu bytes\n", AES_BLOCK_SIZE, es->encrypted.length); goto error_exit; } pt.length = es->encrypted.length - AES_BLOCK_SIZE; pt.data = talloc_zero_size(ctx, pt.length); if (pt.data == NULL) { ldb_set_errstring(ldb, "Out of memory allocating space for " "plain text\n"); goto error_exit; } memcpy(pt.data, es->encrypted.data, pt.length); aes_gcm_128_init(&cctx, key_blob.data, es->iv.data); aes_gcm_128_updateA(&cctx, (uint8_t *)&es->header, sizeof(struct EncryptedSecretHeader)); aes_gcm_128_updateC(&cctx, pt.data, pt.length); aes_gcm_128_crypt(&cctx, pt.data, pt.length); aes_gcm_128_digest(&cctx, sig); /* * Check the authentication tag */ cmp = memcmp(&es->encrypted.data[pt.length], sig, AES_BLOCK_SIZE); if (cmp != 0) { ldb_set_errstring(ldb, "Tag does not match, " "data corrupted or altered\n"); goto error_exit; } rc = ndr_pull_struct_blob(&pt, ctx, ps, (ndr_pull_flags_fn_t) ndr_pull_PlaintextSecret); if(!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_asprintf_errstring(ldb, "Error(%d) unpacking decrypted data, " "data possibly corrupted or altered\n", rc); goto error_exit; } TALLOC_FREE(frame); return; error_exit: *err = LDB_ERR_OPERATIONS_ERROR; TALLOC_FREE(frame); return; }
/* * @brief Encrypt an ldb value using an aead algorithm. * * This function uses the samba internal implementation to perform the encryption. However * the encrypted data and tag are stored in a manner compatible with gnutls, * so the gnutls aead functions can be used to decrypt and verify the data. * * @param err Pointer to an error code, set to: * LDB_SUCESS If the value was successfully encrypted * LDB_ERR_OPERATIONS_ERROR If there was an error. * * @param ctx Talloc memory context the will own the memory allocated * @param ldb ldb context, to allow logging. * @param val The ldb value to encrypt, not altered or freed * @param data The context data for this module. * * @return The encrypted ldb_val, or data_blob_null if there was an error. */ static struct ldb_val samba_encrypt_aead(int *err, TALLOC_CTX *ctx, struct ldb_context *ldb, const struct ldb_val val, const struct es_data *data) { struct aes_gcm_128_context cctx; struct EncryptedSecret *es = NULL; DATA_BLOB pt = data_blob_null; struct ldb_val enc = data_blob_null; DATA_BLOB key_blob = data_blob_null; int rc; TALLOC_CTX *frame = talloc_stackframe(); es = makeEncryptedSecret(ldb, frame); if (es == NULL) { goto error_exit; } pt = makePlainText(frame, ldb, val); if (pt.length == 0) { goto error_exit; } /* * Set the encryption key */ key_blob = get_key(data); if (key_blob.length != AES_BLOCK_SIZE) { ldb_asprintf_errstring(ldb, "Invalid EncryptedSecrets key size, " "expected %u bytes and is %zu bytes\n", AES_BLOCK_SIZE, key_blob.length); goto error_exit; } /* * Set the initialisation vector */ { uint8_t *iv = talloc_zero_size(frame, AES_GCM_128_IV_SIZE); if (iv == NULL) { ldb_set_errstring(ldb, "Out of memory allocating iv\n"); goto error_exit; } generate_random_buffer(iv, AES_GCM_128_IV_SIZE); es->iv.length = AES_GCM_128_IV_SIZE; es->iv.data = iv; } /* * Encrypt the value, and append the GCM digest to the encrypted * data so that it can be decrypted and validated by the * gnutls aead decryption routines. */ { uint8_t *ct = talloc_zero_size(frame, pt.length + AES_BLOCK_SIZE); if (ct == NULL) { ldb_oom(ldb); goto error_exit; } memcpy(ct, pt.data, pt.length); es->encrypted.length = pt.length + AES_BLOCK_SIZE; es->encrypted.data = ct; } aes_gcm_128_init(&cctx, key_blob.data, es->iv.data); aes_gcm_128_updateA(&cctx, (uint8_t *)&es->header, sizeof(struct EncryptedSecretHeader)); aes_gcm_128_crypt(&cctx, es->encrypted.data, pt.length); aes_gcm_128_updateC(&cctx, es->encrypted.data, pt.length); aes_gcm_128_digest(&cctx, &es->encrypted.data[pt.length]); rc = ndr_push_struct_blob(&enc, ctx, es, (ndr_push_flags_fn_t) ndr_push_EncryptedSecret); if (!NDR_ERR_CODE_IS_SUCCESS(rc)) { ldb_set_errstring(ldb, "Unable to ndr push EncryptedSecret\n"); goto error_exit; } TALLOC_FREE(frame); return enc; error_exit: *err = LDB_ERR_OPERATIONS_ERROR; TALLOC_FREE(frame); return data_blob_null; }
/* search for matching records */ static int lldb_search(struct lldb_context *lldb_ac) { struct ldb_context *ldb; struct lldb_private *lldb = lldb_ac->lldb; struct ldb_module *module = lldb_ac->module; struct ldb_request *req = lldb_ac->req; struct timeval tv; int ldap_scope; char *search_base; char *expression; int ret; ldb = ldb_module_get_ctx(module); if (!req->callback || !req->context) { ldb_set_errstring(ldb, "Async interface called with NULL callback function or NULL context"); return LDB_ERR_OPERATIONS_ERROR; } if (req->op.search.tree == NULL) { ldb_set_errstring(ldb, "Invalid expression parse tree"); return LDB_ERR_OPERATIONS_ERROR; } if (req->controls != NULL) { ldb_debug(ldb, LDB_DEBUG_WARNING, "Controls are not yet supported by ldb_ldap backend!"); } ldb_request_set_state(req, LDB_ASYNC_PENDING); search_base = ldb_dn_alloc_linearized(lldb_ac, req->op.search.base); if (req->op.search.base == NULL) { search_base = talloc_strdup(lldb_ac, ""); } if (search_base == NULL) { return LDB_ERR_OPERATIONS_ERROR; } expression = ldb_filter_from_tree(lldb_ac, req->op.search.tree); if (expression == NULL) { return LDB_ERR_OPERATIONS_ERROR; } switch (req->op.search.scope) { case LDB_SCOPE_BASE: ldap_scope = LDAP_SCOPE_BASE; break; case LDB_SCOPE_ONELEVEL: ldap_scope = LDAP_SCOPE_ONELEVEL; break; default: ldap_scope = LDAP_SCOPE_SUBTREE; break; } tv.tv_sec = req->timeout; tv.tv_usec = 0; ret = ldap_search_ext(lldb->ldap, search_base, ldap_scope, expression, discard_const_p(char *, req->op.search.attrs), 0, NULL, NULL, &tv, LDAP_NO_LIMIT, &lldb_ac->msgid); if (ret != LDAP_SUCCESS) { ldb_set_errstring(ldb, ldap_err2string(ret)); } return lldb_ldap_to_ldb(ret); }
/* return false if the request is still in progress * return true if the request is completed */ static bool lldb_parse_result(struct lldb_context *ac, LDAPMessage *result) { struct ldb_context *ldb; struct lldb_private *lldb = ac->lldb; LDAPControl **serverctrlsp = NULL; char **referralsp = NULL; char *matcheddnp = NULL; char *errmsgp = NULL; LDAPMessage *msg; int type; struct ldb_message *ldbmsg; char *referral; bool callback_failed; bool request_done; bool lret; unsigned int i; int ret; ldb = ldb_module_get_ctx(ac->module); type = ldap_msgtype(result); callback_failed = false; request_done = false; switch (type) { case LDAP_RES_SEARCH_ENTRY: msg = ldap_first_entry(lldb->ldap, result); if (msg != NULL) { BerElement *berptr = NULL; char *attr, *dn; ldbmsg = ldb_msg_new(ac); if (!ldbmsg) { ldb_oom(ldb); ret = LDB_ERR_OPERATIONS_ERROR; break; } dn = ldap_get_dn(lldb->ldap, msg); if (!dn) { ldb_oom(ldb); talloc_free(ldbmsg); ret = LDB_ERR_OPERATIONS_ERROR; break; } ldbmsg->dn = ldb_dn_new(ldbmsg, ldb, dn); if ( ! ldb_dn_validate(ldbmsg->dn)) { ldb_asprintf_errstring(ldb, "Invalid DN '%s' in reply", dn); talloc_free(ldbmsg); ret = LDB_ERR_OPERATIONS_ERROR; ldap_memfree(dn); break; } ldap_memfree(dn); ldbmsg->num_elements = 0; ldbmsg->elements = NULL; /* loop over all attributes */ for (attr=ldap_first_attribute(lldb->ldap, msg, &berptr); attr; attr=ldap_next_attribute(lldb->ldap, msg, berptr)) { struct berval **bval; bval = ldap_get_values_len(lldb->ldap, msg, attr); if (bval) { lldb_add_msg_attr(ldb, ldbmsg, attr, bval); ldap_value_free_len(bval); } } if (berptr) ber_free(berptr, 0); ret = ldb_module_send_entry(ac->req, ldbmsg, NULL /* controls not yet supported */); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "entry send failed: %s", ldb_errstring(ldb)); callback_failed = true; } } else { ret = LDB_ERR_OPERATIONS_ERROR; } break; case LDAP_RES_SEARCH_REFERENCE: ret = ldap_parse_reference(lldb->ldap, result, &referralsp, &serverctrlsp, 0); if (ret != LDAP_SUCCESS) { ldb_asprintf_errstring(ldb, "ldap reference parse error: %s : %s", ldap_err2string(ret), errmsgp); ret = LDB_ERR_OPERATIONS_ERROR; break; } if (referralsp == NULL) { ldb_asprintf_errstring(ldb, "empty ldap referrals list"); ret = LDB_ERR_PROTOCOL_ERROR; break; } for (i = 0; referralsp[i]; i++) { referral = talloc_strdup(ac, referralsp[i]); ret = ldb_module_send_referral(ac->req, referral); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "referral send failed: %s", ldb_errstring(ldb)); callback_failed = true; break; } } break; case LDAP_RES_SEARCH_RESULT: case LDAP_RES_MODIFY: case LDAP_RES_ADD: case LDAP_RES_DELETE: case LDAP_RES_MODDN: if (ldap_parse_result(lldb->ldap, result, &ret, &matcheddnp, &errmsgp, &referralsp, &serverctrlsp, 0) != LDAP_SUCCESS) { ret = LDB_ERR_OPERATIONS_ERROR; } if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "ldap parse error for type %d: %s : %s", type, ldap_err2string(ret), errmsgp); break; } if (serverctrlsp != NULL) { /* FIXME: transform the LDAPControl list into an ldb_control one */ ac->controls = NULL; } request_done = true; break; default: ldb_asprintf_errstring(ldb, "unknown ldap return type: %d", type); ret = LDB_ERR_PROTOCOL_ERROR; break; } if (ret != LDB_SUCCESS) { /* if the callback failed the caller will have freed the * request. Just return and don't try to use it */ if (callback_failed) { /* tell lldb_wait to remove the request from the * queue */ lret = true; goto free_and_return; } request_done = true; } if (request_done) { lldb_request_done(ac, ac->controls, ret); lret = true; goto free_and_return; } lret = false; free_and_return: if (matcheddnp) ldap_memfree(matcheddnp); if (errmsgp && *errmsgp) { ldb_set_errstring(ldb, errmsgp); } if (errmsgp) { ldap_memfree(errmsgp); } if (referralsp) ldap_value_free(referralsp); if (serverctrlsp) ldap_controls_free(serverctrlsp); ldap_msgfree(result); return lret; }
static int lldb_handle_request(struct ldb_module *module, struct ldb_request *req) { struct ldb_context *ldb; struct lldb_private *lldb; struct lldb_context *ac; struct tevent_context *ev; struct tevent_timer *te; struct timeval tv; int ret; lldb = talloc_get_type(ldb_module_get_private(module), struct lldb_private); ldb = ldb_module_get_ctx(module); if (req->starttime == 0 || req->timeout == 0) { ldb_set_errstring(ldb, "Invalid timeout settings"); return LDB_ERR_TIME_LIMIT_EXCEEDED; } ev = ldb_get_event_context(ldb); if (NULL == ev) { return LDB_ERR_OPERATIONS_ERROR; } ac = talloc_zero(ldb, struct lldb_context); if (ac == NULL) { ldb_set_errstring(ldb, "Out of Memory"); return LDB_ERR_OPERATIONS_ERROR; } ac->module = module; ac->req = req; ac->lldb = lldb; ac->msgid = 0; if (lldb_dn_is_special(req)) { tv.tv_sec = 0; tv.tv_usec = 0; te = tevent_add_timer(ev, ac, tv, lldb_auto_done_callback, ac); if (NULL == te) { return LDB_ERR_OPERATIONS_ERROR; } return LDB_SUCCESS; } switch (ac->req->operation) { case LDB_SEARCH: ret = lldb_search(ac); break; case LDB_ADD: ret = lldb_add(ac); break; case LDB_MODIFY: ret = lldb_modify(ac); break; case LDB_DELETE: ret = lldb_delete(ac); break; case LDB_RENAME: ret = lldb_rename(ac); break; default: /* no other op supported */ ret = LDB_ERR_PROTOCOL_ERROR; break; } if (ret != LDB_SUCCESS) { lldb_request_done(ac, NULL, ret); return ret; } tv.tv_sec = 0; tv.tv_usec = 0; te = tevent_add_timer(ev, ac, tv, lldb_callback, ac); if (NULL == te) { return LDB_ERR_OPERATIONS_ERROR; } tv.tv_sec = req->starttime + req->timeout; tv.tv_usec = 0; te = tevent_add_timer(ev, ac, tv, lldb_timeout, ac); if (NULL == te) { return LDB_ERR_OPERATIONS_ERROR; } return LDB_SUCCESS; }
/* Store the DN of a single search result in context. */ static int map_search_self_callback(struct ldb_request *req, struct ldb_reply *ares) { struct ldb_context *ldb; struct map_context *ac; int ret; ac = talloc_get_type(req->context, struct map_context); ldb = ldb_module_get_ctx(ac->module); if (!ares) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } if (ares->error != LDB_SUCCESS) { return ldb_module_done(ac->req, ares->controls, ares->response, ares->error); } /* We are interested only in the single reply */ switch(ares->type) { case LDB_REPLY_ENTRY: /* We have already found a remote DN */ if (ac->local_dn) { ldb_set_errstring(ldb, "Too many results!"); return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } /* Store local DN */ ac->local_dn = talloc_steal(ac, ares->message->dn); break; case LDB_REPLY_DONE: switch (ac->req->operation) { case LDB_MODIFY: ret = map_modify_do_local(ac); break; case LDB_DELETE: ret = map_delete_do_local(ac); break; case LDB_RENAME: ret = map_rename_do_local(ac); break; default: /* if we get here we have definitely a problem */ ret = LDB_ERR_OPERATIONS_ERROR; } if (ret != LDB_SUCCESS) { return ldb_module_done(ac->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR); } default: /* ignore referrals */ break; } talloc_free(ares); return LDB_SUCCESS; }