wmi-1.3.16 from opsview.com
This commit is contained in:
@@ -0,0 +1,990 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
endpoint server for the drsuapi pipe
|
||||
DsCrackNames()
|
||||
|
||||
Copyright (C) Stefan Metzmacher 2004
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 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 "librpc/gen_ndr/drsuapi.h"
|
||||
#include "rpc_server/common/common.h"
|
||||
#include "lib/ldb/include/ldb_errors.h"
|
||||
#include "system/kerberos.h"
|
||||
#include "auth/kerberos/kerberos.h"
|
||||
#include "libcli/ldap/ldap.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "librpc/gen_ndr/ndr_misc.h"
|
||||
#include "auth/auth.h"
|
||||
#include "db_wrap.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
|
||||
static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
|
||||
struct ldb_dn *name_dn, const char *name,
|
||||
const char *domain_filter, const char *result_filter,
|
||||
struct drsuapi_DsNameInfo1 *info1);
|
||||
static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx,
|
||||
uint32_t format_offered, uint32_t format_desired,
|
||||
struct ldb_dn *name_dn, const char *name,
|
||||
struct drsuapi_DsNameInfo1 *info1);
|
||||
|
||||
static enum drsuapi_DsNameStatus LDB_lookup_spn_alias(krb5_context context, struct ldb_context *ldb_ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const char *alias_from,
|
||||
char **alias_to)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct ldb_result *res;
|
||||
struct ldb_message_element *spnmappings;
|
||||
TALLOC_CTX *tmp_ctx;
|
||||
struct ldb_dn *service_dn;
|
||||
char *service_dn_str;
|
||||
|
||||
const char *directory_attrs[] = {
|
||||
"sPNMappings",
|
||||
NULL
|
||||
};
|
||||
|
||||
tmp_ctx = talloc_new(mem_ctx);
|
||||
if (!tmp_ctx) {
|
||||
return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
}
|
||||
|
||||
service_dn = ldb_dn_new(tmp_ctx, ldb_ctx, "CN=Directory Service,CN=Windows NT,CN=Services,CN=Configuration");
|
||||
if ( ! ldb_dn_add_base(service_dn, samdb_base_dn(ldb_ctx))) {
|
||||
return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
}
|
||||
service_dn_str = ldb_dn_alloc_linearized(tmp_ctx, service_dn);
|
||||
if ( ! service_dn_str) {
|
||||
return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
}
|
||||
|
||||
ret = ldb_search(ldb_ctx, service_dn, LDB_SCOPE_BASE, "(objectClass=nTDSService)",
|
||||
directory_attrs, &res);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
DEBUG(1, ("ldb_search: dn: %s not found: %s", service_dn_str, ldb_errstring(ldb_ctx)));
|
||||
return DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
} else if (res->count != 1) {
|
||||
talloc_free(res);
|
||||
DEBUG(1, ("ldb_search: dn: %s found %d times!", service_dn_str, res->count));
|
||||
return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
}
|
||||
talloc_steal(tmp_ctx, res);
|
||||
|
||||
spnmappings = ldb_msg_find_element(res->msgs[0], "sPNMappings");
|
||||
if (!spnmappings || spnmappings->num_values == 0) {
|
||||
DEBUG(1, ("ldb_search: dn: %s no sPNMappings attribute", service_dn_str));
|
||||
talloc_free(tmp_ctx);
|
||||
return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
for (i = 0; i < spnmappings->num_values; i++) {
|
||||
char *mapping, *p, *str;
|
||||
mapping = talloc_strdup(tmp_ctx,
|
||||
(const char *)spnmappings->values[i].data);
|
||||
if (!mapping) {
|
||||
DEBUG(1, ("LDB_lookup_spn_alias: ldb_search: dn: %s did not have an sPNMapping\n", service_dn_str));
|
||||
talloc_free(tmp_ctx);
|
||||
return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* C string manipulation sucks */
|
||||
|
||||
p = strchr(mapping, '=');
|
||||
if (!p) {
|
||||
DEBUG(1, ("ldb_search: dn: %s sPNMapping malformed: %s\n",
|
||||
service_dn_str, mapping));
|
||||
talloc_free(tmp_ctx);
|
||||
return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
}
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
do {
|
||||
str = p;
|
||||
p = strchr(p, ',');
|
||||
if (p) {
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
}
|
||||
if (strcasecmp(str, alias_from) == 0) {
|
||||
*alias_to = mapping;
|
||||
talloc_steal(mem_ctx, mapping);
|
||||
talloc_free(tmp_ctx);
|
||||
return DRSUAPI_DS_NAME_STATUS_OK;
|
||||
}
|
||||
} while (p);
|
||||
}
|
||||
DEBUG(4, ("LDB_lookup_spn_alias: no alias for service %s applicable\n", alias_from));
|
||||
talloc_free(tmp_ctx);
|
||||
return DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* When cracking a ServicePrincipalName, many services may be served
|
||||
* by the host/ servicePrincipalName. The incoming query is for cifs/
|
||||
* but we translate it here, and search on host/. This is done after
|
||||
* the cifs/ entry has been searched for, making this a fallback */
|
||||
|
||||
static WERROR DsCrackNameSPNAlias(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
|
||||
const char *name, struct drsuapi_DsNameInfo1 *info1)
|
||||
{
|
||||
WERROR wret;
|
||||
krb5_error_code ret;
|
||||
krb5_principal principal;
|
||||
const char *service;
|
||||
char *new_service;
|
||||
char *new_princ;
|
||||
enum drsuapi_DsNameStatus namestatus;
|
||||
|
||||
/* parse principal */
|
||||
ret = krb5_parse_name_flags(smb_krb5_context->krb5_context,
|
||||
name, KRB5_PRINCIPAL_PARSE_NO_REALM, &principal);
|
||||
if (ret) {
|
||||
DEBUG(2, ("Could not parse principal: %s: %s",
|
||||
name, smb_get_krb5_error_message(smb_krb5_context->krb5_context,
|
||||
ret, mem_ctx)));
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
/* grab cifs/, http/ etc */
|
||||
|
||||
/* This is checked for in callers, but be safe */
|
||||
if (principal->name.name_string.len < 2) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
service = principal->name.name_string.val[0];
|
||||
|
||||
/* MAP it */
|
||||
namestatus = LDB_lookup_spn_alias(smb_krb5_context->krb5_context,
|
||||
sam_ctx, mem_ctx,
|
||||
service, &new_service);
|
||||
|
||||
if (namestatus != DRSUAPI_DS_NAME_STATUS_OK) {
|
||||
info1->status = namestatus;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
/* ooh, very nasty playing around in the Principal... */
|
||||
free(principal->name.name_string.val[0]);
|
||||
principal->name.name_string.val[0] = strdup(new_service);
|
||||
if (!principal->name.name_string.val[0]) {
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
/* reform principal */
|
||||
ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM, &new_princ);
|
||||
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
|
||||
if (ret) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
wret = DsCrackNameOneName(sam_ctx, mem_ctx, format_flags, format_offered, format_desired,
|
||||
new_princ, info1);
|
||||
free(new_princ);
|
||||
return wret;
|
||||
}
|
||||
|
||||
/* Subcase of CrackNames, for the userPrincipalName */
|
||||
|
||||
static WERROR DsCrackNameUPN(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
|
||||
const char *name, struct drsuapi_DsNameInfo1 *info1)
|
||||
{
|
||||
WERROR status;
|
||||
const char *domain_filter = NULL;
|
||||
const char *result_filter = NULL;
|
||||
krb5_error_code ret;
|
||||
krb5_principal principal;
|
||||
char **realm;
|
||||
char *unparsed_name_short;
|
||||
|
||||
/* Prevent recursion */
|
||||
if (!name) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
|
||||
KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal);
|
||||
if (ret) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
domain_filter = NULL;
|
||||
realm = krb5_princ_realm(smb_krb5_context->krb5_context, principal);
|
||||
domain_filter = talloc_asprintf(mem_ctx,
|
||||
"(&(&(|(&(dnsRoot=%s)(nETBIOSName=*))(nETBIOSName=%s))(objectclass=crossRef))(ncName=*))",
|
||||
ldb_binary_encode_string(mem_ctx, *realm),
|
||||
ldb_binary_encode_string(mem_ctx, *realm));
|
||||
ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM, &unparsed_name_short);
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
|
||||
if (ret) {
|
||||
free(unparsed_name_short);
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
/* This may need to be extended for more userPrincipalName variations */
|
||||
result_filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(samAccountName=%s))",
|
||||
ldb_binary_encode_string(mem_ctx, unparsed_name_short));
|
||||
if (!result_filter || !domain_filter) {
|
||||
free(unparsed_name_short);
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
status = DsCrackNameOneFilter(sam_ctx, mem_ctx,
|
||||
smb_krb5_context,
|
||||
format_flags, format_offered, format_desired,
|
||||
NULL, unparsed_name_short, domain_filter, result_filter,
|
||||
info1);
|
||||
free(unparsed_name_short);
|
||||
return status;
|
||||
}
|
||||
|
||||
/* Crack a single 'name', from format_offered into format_desired, returning the result in info1 */
|
||||
|
||||
WERROR DsCrackNameOneName(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
|
||||
uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
|
||||
const char *name, struct drsuapi_DsNameInfo1 *info1)
|
||||
{
|
||||
krb5_error_code ret;
|
||||
const char *domain_filter = NULL;
|
||||
const char *result_filter = NULL;
|
||||
struct ldb_dn *name_dn = NULL;
|
||||
|
||||
struct smb_krb5_context *smb_krb5_context;
|
||||
ret = smb_krb5_init_context(mem_ctx, &smb_krb5_context);
|
||||
|
||||
if (ret) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
info1->dns_domain_name = NULL;
|
||||
info1->result_name = NULL;
|
||||
|
||||
if (!name) {
|
||||
return WERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
/* TODO: - fill the correct names in all cases!
|
||||
* - handle format_flags
|
||||
*/
|
||||
|
||||
/* here we need to set the domain_filter and/or the result_filter */
|
||||
switch (format_offered) {
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL: {
|
||||
char *str;
|
||||
|
||||
str = talloc_strdup(mem_ctx, name);
|
||||
W_ERROR_HAVE_NO_MEMORY(str);
|
||||
|
||||
if (strlen(str) == 0 || str[strlen(str)-1] != '/') {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
str[strlen(str)-1] = '\0';
|
||||
|
||||
domain_filter = talloc_asprintf(mem_ctx,
|
||||
"(&(&(&(dnsRoot=%s)(objectclass=crossRef)))(nETBIOSName=*)(ncName=*))",
|
||||
ldb_binary_encode_string(mem_ctx, str));
|
||||
W_ERROR_HAVE_NO_MEMORY(domain_filter);
|
||||
|
||||
break;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: {
|
||||
char *p;
|
||||
char *domain;
|
||||
const char *account = NULL;
|
||||
|
||||
domain = talloc_strdup(mem_ctx, name);
|
||||
W_ERROR_HAVE_NO_MEMORY(domain);
|
||||
|
||||
p = strchr(domain, '\\');
|
||||
if (!p) {
|
||||
/* invalid input format */
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
p[0] = '\0';
|
||||
|
||||
if (p[1]) {
|
||||
account = &p[1];
|
||||
}
|
||||
|
||||
domain_filter = talloc_asprintf(mem_ctx,
|
||||
"(&(&(nETBIOSName=%s)(objectclass=crossRef))(ncName=*))",
|
||||
ldb_binary_encode_string(mem_ctx, domain));
|
||||
W_ERROR_HAVE_NO_MEMORY(domain_filter);
|
||||
if (account) {
|
||||
result_filter = talloc_asprintf(mem_ctx, "(sAMAccountName=%s)",
|
||||
ldb_binary_encode_string(mem_ctx, account));
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
}
|
||||
|
||||
talloc_free(domain);
|
||||
break;
|
||||
}
|
||||
|
||||
/* A LDAP DN as a string */
|
||||
case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: {
|
||||
domain_filter = NULL;
|
||||
name_dn = ldb_dn_new(mem_ctx, sam_ctx, name);
|
||||
if (! ldb_dn_validate(name_dn)) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* A GUID as a string */
|
||||
case DRSUAPI_DS_NAME_FORMAT_GUID: {
|
||||
struct GUID guid;
|
||||
char *ldap_guid;
|
||||
NTSTATUS nt_status;
|
||||
domain_filter = NULL;
|
||||
|
||||
nt_status = GUID_from_string(name, &guid);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
ldap_guid = ldap_encode_ndr_GUID(mem_ctx, &guid);
|
||||
if (!ldap_guid) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
result_filter = talloc_asprintf(mem_ctx, "(objectGUID=%s)",
|
||||
ldap_guid);
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
break;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_DISPLAY: {
|
||||
domain_filter = NULL;
|
||||
|
||||
result_filter = talloc_asprintf(mem_ctx, "(|(displayName=%s)(samAccountName=%s))",
|
||||
ldb_binary_encode_string(mem_ctx, name),
|
||||
ldb_binary_encode_string(mem_ctx, name));
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
break;
|
||||
}
|
||||
|
||||
/* A S-1234-5678 style string */
|
||||
case DRSUAPI_DS_NAME_FORMAT_SID_OR_SID_HISTORY: {
|
||||
struct dom_sid *sid = dom_sid_parse_talloc(mem_ctx, name);
|
||||
char *ldap_sid;
|
||||
|
||||
domain_filter = NULL;
|
||||
if (!sid) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
ldap_sid = ldap_encode_ndr_dom_sid(mem_ctx,
|
||||
sid);
|
||||
if (!ldap_sid) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
result_filter = talloc_asprintf(mem_ctx, "(objectSid=%s)",
|
||||
ldap_sid);
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
break;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL: {
|
||||
krb5_principal principal;
|
||||
char *unparsed_name;
|
||||
ret = krb5_parse_name(smb_krb5_context->krb5_context, name, &principal);
|
||||
if (ret) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
domain_filter = NULL;
|
||||
|
||||
ret = krb5_unparse_name(smb_krb5_context->krb5_context, principal, &unparsed_name);
|
||||
if (ret) {
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
result_filter = talloc_asprintf(mem_ctx, "(&(objectClass=user)(userPrincipalName=%s))",
|
||||
ldb_binary_encode_string(mem_ctx, unparsed_name));
|
||||
|
||||
free(unparsed_name);
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
break;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL: {
|
||||
krb5_principal principal;
|
||||
char *unparsed_name_short;
|
||||
char *service;
|
||||
ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
|
||||
KRB5_PRINCIPAL_PARSE_NO_REALM, &principal);
|
||||
if (ret) {
|
||||
/* perhaps it's a principal with a realm, so return the right 'domain only' response */
|
||||
char **realm;
|
||||
ret = krb5_parse_name_flags(smb_krb5_context->krb5_context, name,
|
||||
KRB5_PRINCIPAL_PARSE_MUST_REALM, &principal);
|
||||
if (ret) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
/* This isn't an allocation assignemnt, so it is free'ed with the krb5_free_principal */
|
||||
realm = krb5_princ_realm(smb_krb5_context->krb5_context, principal);
|
||||
|
||||
info1->dns_domain_name = talloc_strdup(info1, *realm);
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
|
||||
W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name);
|
||||
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
|
||||
return WERR_OK;
|
||||
|
||||
} else if (principal->name.name_string.len < 2) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
domain_filter = NULL;
|
||||
|
||||
ret = krb5_unparse_name_flags(smb_krb5_context->krb5_context, principal,
|
||||
KRB5_PRINCIPAL_UNPARSE_NO_REALM, &unparsed_name_short);
|
||||
if (ret) {
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
service = principal->name.name_string.val[0];
|
||||
if ((principal->name.name_string.len == 2) && (strcasecmp(service, "host") == 0)) {
|
||||
/* the 'cn' attribute is just the leading part of the name */
|
||||
char *computer_name;
|
||||
computer_name = talloc_strndup(mem_ctx, principal->name.name_string.val[1],
|
||||
strcspn(principal->name.name_string.val[1], "."));
|
||||
if (computer_name == NULL) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
result_filter = talloc_asprintf(mem_ctx, "(|(&(servicePrincipalName=%s)(objectClass=user))(&(cn=%s)(objectClass=computer)))",
|
||||
ldb_binary_encode_string(mem_ctx, unparsed_name_short),
|
||||
ldb_binary_encode_string(mem_ctx, computer_name));
|
||||
} else {
|
||||
result_filter = talloc_asprintf(mem_ctx, "(&(servicePrincipalName=%s)(objectClass=user))",
|
||||
ldb_binary_encode_string(mem_ctx, unparsed_name_short));
|
||||
}
|
||||
krb5_free_principal(smb_krb5_context->krb5_context, principal);
|
||||
free(unparsed_name_short);
|
||||
W_ERROR_HAVE_NO_MEMORY(result_filter);
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (format_flags & DRSUAPI_DS_NAME_FLAG_SYNTACTICAL_ONLY) {
|
||||
return DsCrackNameOneSyntactical(mem_ctx, format_offered, format_desired,
|
||||
name_dn, name, info1);
|
||||
}
|
||||
|
||||
return DsCrackNameOneFilter(sam_ctx, mem_ctx,
|
||||
smb_krb5_context,
|
||||
format_flags, format_offered, format_desired,
|
||||
name_dn, name,
|
||||
domain_filter, result_filter,
|
||||
info1);
|
||||
}
|
||||
|
||||
/* Subcase of CrackNames. It is possible to translate a LDAP-style DN
|
||||
* (FQDN_1779) into a canoical name without actually searching the
|
||||
* database */
|
||||
|
||||
static WERROR DsCrackNameOneSyntactical(TALLOC_CTX *mem_ctx,
|
||||
uint32_t format_offered, uint32_t format_desired,
|
||||
struct ldb_dn *name_dn, const char *name,
|
||||
struct drsuapi_DsNameInfo1 *info1)
|
||||
{
|
||||
char *cracked;
|
||||
if (format_offered != DRSUAPI_DS_NAME_FORMAT_FQDN_1779) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
switch (format_desired) {
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
|
||||
cracked = ldb_dn_canonical_string(mem_ctx, name_dn);
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
|
||||
cracked = ldb_dn_canonical_ex_string(mem_ctx, name_dn);
|
||||
break;
|
||||
default:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NO_SYNTACTICAL_MAPPING;
|
||||
return WERR_OK;
|
||||
}
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
info1->result_name = cracked;
|
||||
if (!cracked) {
|
||||
return WERR_NOMEM;
|
||||
}
|
||||
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
/* Given a filter for the domain, and one for the result, perform the
|
||||
* ldb search. The format offered and desired flags change the
|
||||
* behaviours, including what attributes to return.
|
||||
*
|
||||
* The smb_krb5_context is required because we use the krb5 libs for principal parsing
|
||||
*/
|
||||
|
||||
static WERROR DsCrackNameOneFilter(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx,
|
||||
struct smb_krb5_context *smb_krb5_context,
|
||||
uint32_t format_flags, uint32_t format_offered, uint32_t format_desired,
|
||||
struct ldb_dn *name_dn, const char *name,
|
||||
const char *domain_filter, const char *result_filter,
|
||||
struct drsuapi_DsNameInfo1 *info1)
|
||||
{
|
||||
int ldb_ret;
|
||||
struct ldb_message **domain_res = NULL;
|
||||
const char * const *domain_attrs;
|
||||
const char * const *result_attrs;
|
||||
struct ldb_message **result_res = NULL;
|
||||
struct ldb_dn *result_basedn;
|
||||
struct ldb_dn *partitions_basedn = samdb_partitions_dn(sam_ctx, mem_ctx);
|
||||
|
||||
const char * const _domain_attrs_1779[] = { "ncName", "dnsRoot", NULL};
|
||||
const char * const _result_attrs_null[] = { NULL };
|
||||
|
||||
const char * const _domain_attrs_canonical[] = { "ncName", "dnsRoot", NULL};
|
||||
const char * const _result_attrs_canonical[] = { "canonicalName", NULL };
|
||||
|
||||
const char * const _domain_attrs_nt4[] = { "ncName", "dnsRoot", "nETBIOSName", NULL};
|
||||
const char * const _result_attrs_nt4[] = { "sAMAccountName", "objectSid", NULL};
|
||||
|
||||
const char * const _domain_attrs_guid[] = { "ncName", "dnsRoot", NULL};
|
||||
const char * const _result_attrs_guid[] = { "objectGUID", NULL};
|
||||
|
||||
const char * const _domain_attrs_display[] = { "ncName", "dnsRoot", NULL};
|
||||
const char * const _result_attrs_display[] = { "displayName", "samAccountName", NULL};
|
||||
|
||||
/* here we need to set the attrs lists for domain and result lookups */
|
||||
switch (format_desired) {
|
||||
case DRSUAPI_DS_NAME_FORMAT_FQDN_1779:
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX:
|
||||
domain_attrs = _domain_attrs_1779;
|
||||
result_attrs = _result_attrs_null;
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL:
|
||||
domain_attrs = _domain_attrs_canonical;
|
||||
result_attrs = _result_attrs_canonical;
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT:
|
||||
domain_attrs = _domain_attrs_nt4;
|
||||
result_attrs = _result_attrs_nt4;
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_FORMAT_GUID:
|
||||
domain_attrs = _domain_attrs_guid;
|
||||
result_attrs = _result_attrs_guid;
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_FORMAT_DISPLAY:
|
||||
domain_attrs = _domain_attrs_display;
|
||||
result_attrs = _result_attrs_display;
|
||||
break;
|
||||
default:
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
if (domain_filter) {
|
||||
/* if we have a domain_filter look it up and set the result_basedn and the dns_domain_name */
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &domain_res, domain_attrs,
|
||||
"%s", domain_filter);
|
||||
} else {
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &domain_res, domain_attrs,
|
||||
"(ncName=%s)", ldb_dn_get_linearized(samdb_base_dn(sam_ctx)));
|
||||
}
|
||||
|
||||
switch (ldb_ret) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
case -1:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
return WERR_OK;
|
||||
default:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
info1->dns_domain_name = samdb_result_string(domain_res[0], "dnsRoot", NULL);
|
||||
W_ERROR_HAVE_NO_MEMORY(info1->dns_domain_name);
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY;
|
||||
|
||||
if (result_filter) {
|
||||
result_basedn = samdb_result_dn(sam_ctx, mem_ctx, domain_res[0], "ncName", NULL);
|
||||
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, result_basedn, &result_res,
|
||||
result_attrs, "%s", result_filter);
|
||||
} else if (format_offered == DRSUAPI_DS_NAME_FORMAT_FQDN_1779) {
|
||||
ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res,
|
||||
result_attrs);
|
||||
} else {
|
||||
name_dn = samdb_result_dn(sam_ctx, mem_ctx, domain_res[0], "ncName", NULL);
|
||||
ldb_ret = gendb_search_dn(sam_ctx, mem_ctx, name_dn, &result_res,
|
||||
result_attrs);
|
||||
}
|
||||
|
||||
switch (ldb_ret) {
|
||||
case 1:
|
||||
break;
|
||||
case 0:
|
||||
switch (format_offered) {
|
||||
case DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL:
|
||||
return DsCrackNameSPNAlias(sam_ctx, mem_ctx,
|
||||
smb_krb5_context,
|
||||
format_flags, format_offered, format_desired,
|
||||
name, info1);
|
||||
|
||||
case DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL:
|
||||
return DsCrackNameUPN(sam_ctx, mem_ctx, smb_krb5_context,
|
||||
format_flags, format_offered, format_desired,
|
||||
name, info1);
|
||||
}
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
case -1:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR;
|
||||
return WERR_OK;
|
||||
default:
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
/* here we can use result_res[0] and domain_res[0] */
|
||||
switch (format_desired) {
|
||||
case DRSUAPI_DS_NAME_FORMAT_FQDN_1779: {
|
||||
info1->result_name = ldb_dn_alloc_linearized(mem_ctx, result_res[0]->dn);
|
||||
W_ERROR_HAVE_NO_MEMORY(info1->result_name);
|
||||
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
return WERR_OK;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL: {
|
||||
info1->result_name = samdb_result_string(result_res[0], "canonicalName", NULL);
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
return WERR_OK;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX: {
|
||||
/* Not in the virtual ldb attribute */
|
||||
return DsCrackNameOneSyntactical(mem_ctx,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
DRSUAPI_DS_NAME_FORMAT_CANONICAL_EX,
|
||||
result_res[0]->dn, name, info1);
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT: {
|
||||
const struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, result_res[0], "objectSid");
|
||||
const char *_acc = "", *_dom = "";
|
||||
|
||||
if (!sid || (sid->num_auths < 4) || (sid->num_auths > 5)) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NO_MAPPING;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
if (sid->num_auths == 4) {
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &domain_res, domain_attrs,
|
||||
"(ncName=%s)", ldb_dn_get_linearized(result_res[0]->dn));
|
||||
if (ldb_ret != 1) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
_dom = samdb_result_string(domain_res[0], "nETBIOSName", NULL);
|
||||
W_ERROR_HAVE_NO_MEMORY(_dom);
|
||||
|
||||
} else if (sid->num_auths == 5) {
|
||||
const char *attrs[] = { NULL };
|
||||
struct ldb_message **domain_res2;
|
||||
struct dom_sid *dom_sid = dom_sid_dup(mem_ctx, sid);
|
||||
if (!dom_sid) {
|
||||
return WERR_OK;
|
||||
}
|
||||
dom_sid->num_auths--;
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, NULL, &domain_res, attrs,
|
||||
"(&(objectSid=%s)(objectClass=domain))", ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
|
||||
if (ldb_ret != 1) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
ldb_ret = gendb_search(sam_ctx, mem_ctx, partitions_basedn, &domain_res2, domain_attrs,
|
||||
"(ncName=%s)", ldb_dn_get_linearized(domain_res[0]->dn));
|
||||
if (ldb_ret != 1) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
_dom = samdb_result_string(domain_res2[0], "nETBIOSName", NULL);
|
||||
W_ERROR_HAVE_NO_MEMORY(_dom);
|
||||
|
||||
_acc = samdb_result_string(result_res[0], "sAMAccountName", NULL);
|
||||
W_ERROR_HAVE_NO_MEMORY(_acc);
|
||||
}
|
||||
|
||||
info1->result_name = talloc_asprintf(mem_ctx, "%s\\%s", _dom, _acc);
|
||||
W_ERROR_HAVE_NO_MEMORY(info1->result_name);
|
||||
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
return WERR_OK;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_GUID: {
|
||||
struct GUID guid;
|
||||
|
||||
guid = samdb_result_guid(result_res[0], "objectGUID");
|
||||
|
||||
info1->result_name = GUID_string2(mem_ctx, &guid);
|
||||
W_ERROR_HAVE_NO_MEMORY(info1->result_name);
|
||||
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
return WERR_OK;
|
||||
}
|
||||
case DRSUAPI_DS_NAME_FORMAT_DISPLAY: {
|
||||
info1->result_name = samdb_result_string(result_res[0], "displayName", NULL);
|
||||
if (!info1->result_name) {
|
||||
info1->result_name = samdb_result_string(result_res[0], "sAMAccountName", NULL);
|
||||
}
|
||||
if (!info1->result_name) {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_NOT_FOUND;
|
||||
} else {
|
||||
info1->status = DRSUAPI_DS_NAME_STATUS_OK;
|
||||
}
|
||||
return WERR_OK;
|
||||
}
|
||||
default:
|
||||
return WERR_OK;
|
||||
}
|
||||
|
||||
return WERR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
/* Given a user Principal Name (such as foo@bar.com),
|
||||
* return the user and domain DNs. This is used in the KDC to then
|
||||
* return the Keys and evaluate policy */
|
||||
|
||||
NTSTATUS crack_user_principal_name(struct ldb_context *sam_ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const char *user_principal_name,
|
||||
struct ldb_dn **user_dn,
|
||||
struct ldb_dn **domain_dn)
|
||||
{
|
||||
WERROR werr;
|
||||
struct drsuapi_DsNameInfo1 info1;
|
||||
werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
|
||||
DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
user_principal_name,
|
||||
&info1);
|
||||
if (!W_ERROR_IS_OK(werr)) {
|
||||
return werror_to_ntstatus(werr);
|
||||
}
|
||||
switch (info1.status) {
|
||||
case DRSUAPI_DS_NAME_STATUS_OK:
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
|
||||
case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
*user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
|
||||
|
||||
if (domain_dn) {
|
||||
werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
|
||||
DRSUAPI_DS_NAME_FORMAT_CANONICAL,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
talloc_asprintf(mem_ctx, "%s/",
|
||||
info1.dns_domain_name),
|
||||
&info1);
|
||||
if (!W_ERROR_IS_OK(werr)) {
|
||||
return werror_to_ntstatus(werr);
|
||||
}
|
||||
switch (info1.status) {
|
||||
case DRSUAPI_DS_NAME_STATUS_OK:
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
|
||||
case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
*domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
|
||||
}
|
||||
|
||||
/* Given a Service Principal Name (such as host/foo.bar.com@BAR.COM),
|
||||
* return the user and domain DNs. This is used in the KDC to then
|
||||
* return the Keys and evaluate policy */
|
||||
|
||||
NTSTATUS crack_service_principal_name(struct ldb_context *sam_ctx,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
const char *service_principal_name,
|
||||
struct ldb_dn **user_dn,
|
||||
struct ldb_dn **domain_dn)
|
||||
{
|
||||
WERROR werr;
|
||||
struct drsuapi_DsNameInfo1 info1;
|
||||
werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
|
||||
DRSUAPI_DS_NAME_FORMAT_SERVICE_PRINCIPAL,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
service_principal_name,
|
||||
&info1);
|
||||
if (!W_ERROR_IS_OK(werr)) {
|
||||
return werror_to_ntstatus(werr);
|
||||
}
|
||||
switch (info1.status) {
|
||||
case DRSUAPI_DS_NAME_STATUS_OK:
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
|
||||
case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
*user_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
|
||||
|
||||
if (domain_dn) {
|
||||
werr = DsCrackNameOneName(sam_ctx, mem_ctx, 0,
|
||||
DRSUAPI_DS_NAME_FORMAT_CANONICAL,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
talloc_asprintf(mem_ctx, "%s/",
|
||||
info1.dns_domain_name),
|
||||
&info1);
|
||||
if (!W_ERROR_IS_OK(werr)) {
|
||||
return werror_to_ntstatus(werr);
|
||||
}
|
||||
switch (info1.status) {
|
||||
case DRSUAPI_DS_NAME_STATUS_OK:
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
|
||||
case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
*domain_dn = ldb_dn_new(mem_ctx, sam_ctx, info1.result_name);
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
|
||||
}
|
||||
|
||||
NTSTATUS crack_dn_to_nt4_name(TALLOC_CTX *mem_ctx,
|
||||
const char *dn,
|
||||
const char **nt4_domain, const char **nt4_account)
|
||||
{
|
||||
WERROR werr;
|
||||
struct drsuapi_DsNameInfo1 info1;
|
||||
struct ldb_context *ldb;
|
||||
char *p;
|
||||
|
||||
/* Handle anonymous bind */
|
||||
if (!dn || !*dn) {
|
||||
*nt4_domain = "";
|
||||
*nt4_account = "";
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
ldb = samdb_connect(mem_ctx, system_session(mem_ctx));
|
||||
if (ldb == NULL) {
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
werr = DsCrackNameOneName(ldb, mem_ctx, 0,
|
||||
DRSUAPI_DS_NAME_FORMAT_FQDN_1779,
|
||||
DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT,
|
||||
dn,
|
||||
&info1);
|
||||
if (!W_ERROR_IS_OK(werr)) {
|
||||
return werror_to_ntstatus(werr);
|
||||
}
|
||||
switch (info1.status) {
|
||||
case DRSUAPI_DS_NAME_STATUS_OK:
|
||||
break;
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_FOUND:
|
||||
case DRSUAPI_DS_NAME_STATUS_DOMAIN_ONLY:
|
||||
case DRSUAPI_DS_NAME_STATUS_NOT_UNIQUE:
|
||||
return NT_STATUS_NO_SUCH_USER;
|
||||
case DRSUAPI_DS_NAME_STATUS_RESOLVE_ERROR:
|
||||
default:
|
||||
return NT_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
*nt4_domain = talloc_strdup(mem_ctx, info1.result_name);
|
||||
|
||||
p = strchr(*nt4_domain, '\\');
|
||||
if (!p) {
|
||||
return NT_STATUS_INVALID_PARAMETER;
|
||||
}
|
||||
p[0] = '\0';
|
||||
|
||||
if (p[1]) {
|
||||
*nt4_account = talloc_strdup(mem_ctx, &p[1]);
|
||||
}
|
||||
|
||||
if (!*nt4_account || !*nt4_domain) {
|
||||
return NT_STATUS_NO_MEMORY;
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
################################################
|
||||
# Start MODULE ldb_objectguid
|
||||
[MODULE::ldb_objectguid]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = objectguid_module_init
|
||||
OBJ_FILES = \
|
||||
objectguid.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
LIBNDR NDR_MISC
|
||||
# End MODULE ldb_objectguid
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_samldb
|
||||
[MODULE::ldb_samldb]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = samldb_module_init
|
||||
OBJ_FILES = \
|
||||
samldb.o
|
||||
#
|
||||
# End MODULE ldb_samldb
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_samba3sam
|
||||
[MODULE::ldb_samba3sam]
|
||||
SUBSYSTEM = ldb
|
||||
INIT_FUNCTION = ldb_samba3sam_module_init
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC ldb_map
|
||||
OBJ_FILES = \
|
||||
samba3sam.o
|
||||
#
|
||||
# End MODULE ldb_samldb
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_entryUUID
|
||||
[MODULE::ldb_entryUUID]
|
||||
SUBSYSTEM = ldb
|
||||
INIT_FUNCTION = ldb_entryUUID_module_init
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
ENABLE = YES
|
||||
OBJ_FILES = \
|
||||
entryUUID.o
|
||||
#
|
||||
# End MODULE ldb_entryUUID
|
||||
################################################
|
||||
|
||||
# ################################################
|
||||
# # Start MODULE ldb_proxy
|
||||
# [MODULE::ldb_proxy]
|
||||
# SUBSYSTEM = ldb
|
||||
# INIT_FUNCTION = proxy_module_init
|
||||
# OBJ_FILES = \
|
||||
# proxy.o
|
||||
#
|
||||
# # End MODULE ldb_proxy
|
||||
# ################################################
|
||||
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_rootdse
|
||||
[MODULE::ldb_rootdse]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = rootdse_module_init
|
||||
OBJ_FILES = \
|
||||
rootdse.o
|
||||
#
|
||||
# End MODULE ldb_rootdse
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_password_hash
|
||||
[MODULE::ldb_password_hash]
|
||||
SUBSYSTEM = ldb
|
||||
INIT_FUNCTION = password_hash_module_init
|
||||
OBJ_FILES = password_hash.o
|
||||
PUBLIC_DEPENDENCIES = HEIMDAL_KRB5
|
||||
PRIVATE_DEPENDENCIES = HEIMDAL_HDB_KEYS LIBTALLOC
|
||||
#
|
||||
# End MODULE ldb_password_hash
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_local_password
|
||||
[MODULE::ldb_local_password]
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
SUBSYSTEM = ldb
|
||||
INIT_FUNCTION = local_password_module_init
|
||||
OBJ_FILES = local_password.o
|
||||
#
|
||||
# End MODULE ldb_local_password
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_kludge_acl
|
||||
[MODULE::ldb_kludge_acl]
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
SUBSYSTEM = ldb
|
||||
INIT_FUNCTION = ldb_kludge_acl_init
|
||||
OBJ_FILES = \
|
||||
kludge_acl.o
|
||||
PUBLIC_DEPENDENCIES = \
|
||||
LIBSECURITY
|
||||
#
|
||||
# End MODULE ldb_kludge_acl
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_extended_dn
|
||||
[MODULE::ldb_extended_dn]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = ldb_extended_dn_init
|
||||
OBJ_FILES = \
|
||||
extended_dn.o
|
||||
#
|
||||
# End MODULE ldb_extended_dn
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_partition
|
||||
[MODULE::ldb_partition]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = ldb_partition_init
|
||||
OBJ_FILES = \
|
||||
partition.o
|
||||
#
|
||||
# End MODULE ldb_partition
|
||||
################################################
|
||||
|
||||
################################################
|
||||
# Start MODULE ldb_schema
|
||||
[MODULE::ldb_schema]
|
||||
SUBSYSTEM = ldb
|
||||
PRIVATE_DEPENDENCIES = LIBTALLOC
|
||||
INIT_FUNCTION = ldb_schema_init
|
||||
OBJ_FILES = \
|
||||
schema.o schema_syntax.o
|
||||
#
|
||||
# End MODULE ldb_schema
|
||||
################################################
|
||||
|
||||
@@ -0,0 +1,692 @@
|
||||
/*
|
||||
ldb database module
|
||||
|
||||
LDAP semantics mapping module
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2005
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
This module relies on ldb_map to do all the real work, but performs
|
||||
some of the trivial mappings between AD semantics and that provided
|
||||
by OpenLDAP and similar servers.
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/modules/ldb_map.h"
|
||||
|
||||
#include "librpc/gen_ndr/ndr_misc.h"
|
||||
#include "librpc/ndr/libndr.h"
|
||||
|
||||
struct entryUUID_private {
|
||||
struct ldb_result *objectclass_res;
|
||||
struct ldb_dn **base_dns;
|
||||
};
|
||||
|
||||
static struct ldb_val encode_guid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct GUID guid;
|
||||
NTSTATUS status = GUID_from_string((char *)val->data, &guid);
|
||||
struct ldb_val out = data_blob(NULL, 0);
|
||||
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return out;
|
||||
}
|
||||
status = ndr_push_struct_blob(&out, ctx, &guid,
|
||||
(ndr_push_flags_fn_t)ndr_push_GUID);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return out;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static struct ldb_val guid_always_string(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct GUID *guid;
|
||||
NTSTATUS status;
|
||||
struct ldb_val out = data_blob(NULL, 0);
|
||||
if (val->length >= 32 && val->data[val->length] == '\0') {
|
||||
ldb_handler_copy(module->ldb, ctx, val, &out);
|
||||
} else {
|
||||
guid = talloc(ctx, struct GUID);
|
||||
if (guid == NULL) {
|
||||
return out;
|
||||
}
|
||||
status = ndr_pull_struct_blob(val, guid, guid,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_GUID);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(guid);
|
||||
return out;
|
||||
}
|
||||
out = data_blob_string_const(GUID_string(ctx, guid));
|
||||
talloc_free(guid);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/* The backend holds binary sids, so just copy them back */
|
||||
static struct ldb_val val_copy(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out = data_blob(NULL, 0);
|
||||
ldb_handler_copy(module->ldb, ctx, val, &out);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/* Ensure we always convert sids into binary, so the backend doesn't have to know about both forms */
|
||||
static struct ldb_val sid_always_binary(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out = data_blob(NULL, 0);
|
||||
const struct ldb_attrib_handler *handler = ldb_attrib_handler(module->ldb, "objectSid");
|
||||
|
||||
if (handler->canonicalise_fn(module->ldb, ctx, val, &out) != LDB_SUCCESS) {
|
||||
return data_blob(NULL, 0);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static struct ldb_val objectCategory_always_dn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
int i;
|
||||
struct map_private *map_private;
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
struct ldb_result *list;
|
||||
|
||||
if (ldb_dn_validate(ldb_dn_new(ctx, module->ldb, (const char *)val->data))) {
|
||||
return *val;
|
||||
}
|
||||
map_private = talloc_get_type(module->private_data, struct map_private);
|
||||
|
||||
entryUUID_private = talloc_get_type(map_private->caller_private, struct entryUUID_private);
|
||||
list = entryUUID_private->objectclass_res;
|
||||
|
||||
for (i=0; list && (i < list->count); i++) {
|
||||
if (ldb_attr_cmp((const char *)val->data, ldb_msg_find_attr_as_string(list->msgs[i], "lDAPDisplayName", NULL)) == 0) {
|
||||
char *dn = ldb_dn_alloc_linearized(ctx, list->msgs[i]->dn);
|
||||
return data_blob_string_const(dn);
|
||||
}
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
static struct ldb_val class_to_oid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
int i;
|
||||
struct map_private *map_private;
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
struct ldb_result *list;
|
||||
|
||||
map_private = talloc_get_type(module->private_data, struct map_private);
|
||||
|
||||
entryUUID_private = talloc_get_type(map_private->caller_private, struct entryUUID_private);
|
||||
list = entryUUID_private->objectclass_res;
|
||||
|
||||
for (i=0; list && (i < list->count); i++) {
|
||||
if (ldb_attr_cmp((const char *)val->data, ldb_msg_find_attr_as_string(list->msgs[i], "lDAPDisplayName", NULL)) == 0) {
|
||||
const char *oid = ldb_msg_find_attr_as_string(list->msgs[i], "governsID", NULL);
|
||||
return data_blob_string_const(oid);
|
||||
}
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
static struct ldb_val class_from_oid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
int i;
|
||||
struct map_private *map_private;
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
struct ldb_result *list;
|
||||
|
||||
map_private = talloc_get_type(module->private_data, struct map_private);
|
||||
|
||||
entryUUID_private = talloc_get_type(map_private->caller_private, struct entryUUID_private);
|
||||
list = entryUUID_private->objectclass_res;
|
||||
|
||||
for (i=0; list && (i < list->count); i++) {
|
||||
if (ldb_attr_cmp((const char *)val->data, ldb_msg_find_attr_as_string(list->msgs[i], "governsID", NULL)) == 0) {
|
||||
const char *oc = ldb_msg_find_attr_as_string(list->msgs[i], "lDAPDisplayName", NULL);
|
||||
return data_blob_string_const(oc);
|
||||
}
|
||||
}
|
||||
return *val;
|
||||
}
|
||||
|
||||
|
||||
static struct ldb_val normalise_to_signed32(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
long long int signed_ll = strtoll((const char *)val->data, NULL, 10);
|
||||
if (signed_ll >= 0x80000000LL) {
|
||||
union {
|
||||
int32_t signed_int;
|
||||
uint32_t unsigned_int;
|
||||
} u = {
|
||||
.unsigned_int = strtoul((const char *)val->data, NULL, 10)
|
||||
};
|
||||
|
||||
struct ldb_val out = data_blob_string_const(talloc_asprintf(ctx, "%d", u.signed_int));
|
||||
return out;
|
||||
}
|
||||
return val_copy(module, ctx, val);
|
||||
}
|
||||
|
||||
static struct ldb_val usn_to_entryCSN(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out;
|
||||
unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
|
||||
time_t t = (usn >> 24);
|
||||
out = data_blob_string_const(talloc_asprintf(ctx, "%s#%06x#00#000000", ldb_timestring(ctx, t), (unsigned int)(usn & 0xFFFFFF)));
|
||||
return out;
|
||||
}
|
||||
|
||||
static unsigned long long entryCSN_to_usn_int(TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
char *entryCSN = talloc_strdup(ctx, (const char *)val->data);
|
||||
char *mod_per_sec;
|
||||
time_t t;
|
||||
unsigned long long usn;
|
||||
char *p;
|
||||
if (!entryCSN) {
|
||||
return 0;
|
||||
}
|
||||
p = strchr(entryCSN, '#');
|
||||
if (!p) {
|
||||
return 0;
|
||||
}
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
mod_per_sec = p;
|
||||
|
||||
p = strchr(p, '#');
|
||||
if (!p) {
|
||||
return 0;
|
||||
}
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
|
||||
usn = strtol(mod_per_sec, NULL, 16);
|
||||
|
||||
t = ldb_string_to_time(entryCSN);
|
||||
|
||||
usn = usn | ((unsigned long long)t <<24);
|
||||
return usn;
|
||||
}
|
||||
|
||||
static struct ldb_val entryCSN_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out;
|
||||
unsigned long long usn = entryCSN_to_usn_int(ctx, val);
|
||||
out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
|
||||
return out;
|
||||
}
|
||||
|
||||
static struct ldb_val usn_to_timestamp(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out;
|
||||
unsigned long long usn = strtoull((const char *)val->data, NULL, 10);
|
||||
time_t t = (usn >> 24);
|
||||
out = data_blob_string_const(ldb_timestring(ctx, t));
|
||||
return out;
|
||||
}
|
||||
|
||||
static struct ldb_val timestamp_to_usn(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct ldb_val out;
|
||||
time_t t;
|
||||
unsigned long long usn;
|
||||
|
||||
t = ldb_string_to_time((const char *)val->data);
|
||||
|
||||
usn = ((unsigned long long)t <<24);
|
||||
|
||||
out = data_blob_string_const(talloc_asprintf(ctx, "%lld", usn));
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
const struct ldb_map_attribute entryUUID_attributes[] =
|
||||
{
|
||||
/* objectGUID */
|
||||
{
|
||||
.local_name = "objectGUID",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "entryUUID",
|
||||
.convert_local = guid_always_string,
|
||||
.convert_remote = encode_guid,
|
||||
},
|
||||
},
|
||||
},
|
||||
/* objectSid */
|
||||
{
|
||||
.local_name = "objectSid",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "objectSid",
|
||||
.convert_local = sid_always_binary,
|
||||
.convert_remote = val_copy,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = "whenCreated",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "createTimestamp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "whenChanged",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "modifyTimestamp"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "sambaPassword",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "userPassword"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "allowedChildClassesEffective",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "allowedChildClassesEffective",
|
||||
.convert_local = class_to_oid,
|
||||
.convert_remote = class_from_oid,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = "objectCategory",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "objectCategory",
|
||||
.convert_local = objectCategory_always_dn,
|
||||
.convert_remote = val_copy,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = "distinguishedName",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "entryDN"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "groupType",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "groupType",
|
||||
.convert_local = normalise_to_signed32,
|
||||
.convert_remote = val_copy,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "sAMAccountType",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "sAMAccountType",
|
||||
.convert_local = normalise_to_signed32,
|
||||
.convert_remote = val_copy,
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
.local_name = "usnChanged",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "entryCSN",
|
||||
.convert_local = usn_to_entryCSN,
|
||||
.convert_remote = entryCSN_to_usn
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = "usnCreated",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "createTimestamp",
|
||||
.convert_local = usn_to_timestamp,
|
||||
.convert_remote = timestamp_to_usn,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = "*",
|
||||
.type = MAP_KEEP,
|
||||
},
|
||||
{
|
||||
.local_name = NULL,
|
||||
}
|
||||
};
|
||||
|
||||
/* These things do not show up in wildcard searches in OpenLDAP, but
|
||||
* we need them to show up in the AD-like view */
|
||||
const char * const wildcard_attributes[] = {
|
||||
"objectGUID",
|
||||
"whenCreated",
|
||||
"whenChanged",
|
||||
"usnCreated",
|
||||
"usnChanged",
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct ldb_dn *find_schema_dn(struct ldb_context *ldb, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
const char *rootdse_attrs[] = {"schemaNamingContext", NULL};
|
||||
struct ldb_dn *schema_dn;
|
||||
struct ldb_dn *basedn = ldb_dn_new(mem_ctx, ldb, NULL);
|
||||
struct ldb_result *rootdse_res;
|
||||
int ldb_ret;
|
||||
if (!basedn) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Search for rootdse */
|
||||
ldb_ret = ldb_search(ldb, basedn, LDB_SCOPE_BASE, NULL, rootdse_attrs, &rootdse_res);
|
||||
if (ldb_ret != LDB_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
talloc_steal(mem_ctx, rootdse_res);
|
||||
|
||||
if (rootdse_res->count != 1) {
|
||||
ldb_asprintf_errstring(ldb, "Failed to find rootDSE: count %d", rootdse_res->count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Locate schema */
|
||||
schema_dn = ldb_msg_find_attr_as_dn(ldb, mem_ctx, rootdse_res->msgs[0], "schemaNamingContext");
|
||||
if (!schema_dn) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
talloc_free(rootdse_res);
|
||||
return schema_dn;
|
||||
}
|
||||
|
||||
static int fetch_objectclass_schema(struct ldb_context *ldb, struct ldb_dn *schemadn,
|
||||
TALLOC_CTX *mem_ctx,
|
||||
struct ldb_result **objectclass_res)
|
||||
{
|
||||
TALLOC_CTX *local_ctx = talloc_new(mem_ctx);
|
||||
int ret;
|
||||
const char *attrs[] = {
|
||||
"lDAPDisplayName",
|
||||
"governsID",
|
||||
NULL
|
||||
};
|
||||
|
||||
if (!local_ctx) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Downlaod schema */
|
||||
ret = ldb_search(ldb, schemadn, LDB_SCOPE_SUBTREE,
|
||||
"objectClass=classSchema",
|
||||
attrs, objectclass_res);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
talloc_steal(mem_ctx, objectclass_res);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int get_remote_rootdse(struct ldb_context *ldb, void *context,
|
||||
struct ldb_reply *ares)
|
||||
{
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
entryUUID_private = talloc_get_type(context,
|
||||
struct entryUUID_private);
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
int i;
|
||||
struct ldb_message_element *el = ldb_msg_find_element(ares->message, "namingContexts");
|
||||
entryUUID_private->base_dns = talloc_realloc(entryUUID_private, entryUUID_private->base_dns, struct ldb_dn *,
|
||||
el->num_values + 1);
|
||||
for (i=0; i < el->num_values; i++) {
|
||||
if (!entryUUID_private->base_dns) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
entryUUID_private->base_dns[i] = ldb_dn_new(entryUUID_private->base_dns, ldb, (const char *)el->values[i].data);
|
||||
if ( ! ldb_dn_validate(entryUUID_private->base_dns[i])) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
entryUUID_private->base_dns[i] = NULL;
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int find_base_dns(struct ldb_module *module,
|
||||
struct entryUUID_private *entryUUID_private)
|
||||
{
|
||||
int ret;
|
||||
struct ldb_request *req;
|
||||
const char *naming_context_attr[] = {
|
||||
"namingContexts",
|
||||
NULL
|
||||
};
|
||||
req = talloc(entryUUID_private, struct ldb_request);
|
||||
if (req == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
req->operation = LDB_SEARCH;
|
||||
req->op.search.base = ldb_dn_new(req, module->ldb, NULL);
|
||||
req->op.search.scope = LDB_SCOPE_BASE;
|
||||
|
||||
req->op.search.tree = ldb_parse_tree(req, "objectClass=*");
|
||||
if (req->op.search.tree == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Unable to parse search expression");
|
||||
talloc_free(req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
req->op.search.attrs = naming_context_attr;
|
||||
req->controls = NULL;
|
||||
req->context = entryUUID_private;
|
||||
req->callback = get_remote_rootdse;
|
||||
ldb_set_timeout(module->ldb, req, 0); /* use default timeout */
|
||||
|
||||
ret = ldb_next_request(module, req);
|
||||
|
||||
if (ret == LDB_SUCCESS) {
|
||||
ret = ldb_wait(req->handle, LDB_WAIT_ALL);
|
||||
}
|
||||
|
||||
talloc_free(req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* the context init function */
|
||||
static int entryUUID_init(struct ldb_module *module)
|
||||
{
|
||||
int ret;
|
||||
struct map_private *map_private;
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
struct ldb_dn *schema_dn;
|
||||
|
||||
ret = ldb_map_init(module, entryUUID_attributes, NULL, wildcard_attributes, NULL);
|
||||
if (ret != LDB_SUCCESS)
|
||||
return ret;
|
||||
|
||||
map_private = talloc_get_type(module->private_data, struct map_private);
|
||||
|
||||
entryUUID_private = talloc_zero(map_private, struct entryUUID_private);
|
||||
map_private->caller_private = entryUUID_private;
|
||||
|
||||
schema_dn = find_schema_dn(module->ldb, map_private);
|
||||
if (!schema_dn) {
|
||||
/* Perhaps no schema yet */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
ret = fetch_objectclass_schema(module->ldb, schema_dn, entryUUID_private,
|
||||
&entryUUID_private->objectclass_res);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_asprintf_errstring(module->ldb, "Failed to fetch objectClass schema elements: %s\n", ldb_errstring(module->ldb));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = find_base_dns(module, entryUUID_private);
|
||||
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static int get_seq(struct ldb_context *ldb, void *context,
|
||||
struct ldb_reply *ares)
|
||||
{
|
||||
unsigned long long *max_seq = context;
|
||||
unsigned long long seq;
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
struct ldb_message_element *el = ldb_msg_find_element(ares->message, "contextCSN");
|
||||
if (el) {
|
||||
seq = entryCSN_to_usn_int(ares, &el->values[0]);
|
||||
*max_seq = MAX(seq, *max_seq);
|
||||
}
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int entryUUID_sequence_number(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
int i, ret;
|
||||
struct map_private *map_private;
|
||||
struct entryUUID_private *entryUUID_private;
|
||||
unsigned long long max_seq = 0;
|
||||
struct ldb_request *search_req;
|
||||
map_private = talloc_get_type(module->private_data, struct map_private);
|
||||
|
||||
entryUUID_private = talloc_get_type(map_private->caller_private, struct entryUUID_private);
|
||||
|
||||
/* Search the baseDNs for a sequence number */
|
||||
for (i=0; entryUUID_private &&
|
||||
entryUUID_private->base_dns &&
|
||||
entryUUID_private->base_dns[i];
|
||||
i++) {
|
||||
static const char *contextCSN_attr[] = {
|
||||
"contextCSN", NULL
|
||||
};
|
||||
search_req = talloc(req, struct ldb_request);
|
||||
if (search_req == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
search_req->operation = LDB_SEARCH;
|
||||
search_req->op.search.base = entryUUID_private->base_dns[i];
|
||||
search_req->op.search.scope = LDB_SCOPE_BASE;
|
||||
|
||||
search_req->op.search.tree = ldb_parse_tree(search_req, "objectClass=*");
|
||||
if (search_req->op.search.tree == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Unable to parse search expression");
|
||||
talloc_free(search_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
search_req->op.search.attrs = contextCSN_attr;
|
||||
search_req->controls = NULL;
|
||||
search_req->context = &max_seq;
|
||||
search_req->callback = get_seq;
|
||||
ldb_set_timeout(module->ldb, search_req, 0); /* use default timeout */
|
||||
|
||||
ret = ldb_next_request(module, search_req);
|
||||
|
||||
if (ret == LDB_SUCCESS) {
|
||||
ret = ldb_wait(search_req->handle, LDB_WAIT_ALL);
|
||||
}
|
||||
|
||||
talloc_free(search_req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
switch (req->op.seq_num.type) {
|
||||
case LDB_SEQ_HIGHEST_SEQ:
|
||||
req->op.seq_num.seq_num = max_seq;
|
||||
break;
|
||||
case LDB_SEQ_NEXT:
|
||||
req->op.seq_num.seq_num = max_seq;
|
||||
req->op.seq_num.seq_num++;
|
||||
break;
|
||||
case LDB_SEQ_HIGHEST_TIMESTAMP:
|
||||
{
|
||||
req->op.seq_num.seq_num = (max_seq >> 24);
|
||||
break;
|
||||
}
|
||||
}
|
||||
req->op.seq_num.flags = 0;
|
||||
req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
|
||||
req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static struct ldb_module_ops entryUUID_ops = {
|
||||
.name = "entryUUID",
|
||||
.init_context = entryUUID_init,
|
||||
.sequence_number = entryUUID_sequence_number
|
||||
};
|
||||
|
||||
/* the init function */
|
||||
int ldb_entryUUID_module_init(void)
|
||||
{
|
||||
struct ldb_module_ops ops = ldb_map_get_ops();
|
||||
entryUUID_ops.add = ops.add;
|
||||
entryUUID_ops.modify = ops.modify;
|
||||
entryUUID_ops.del = ops.del;
|
||||
entryUUID_ops.rename = ops.rename;
|
||||
entryUUID_ops.search = ops.search;
|
||||
entryUUID_ops.wait = ops.wait;
|
||||
return ldb_register_module(&entryUUID_ops);
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
ldb database library
|
||||
|
||||
Copyright (C) Simo Sorce 2005
|
||||
|
||||
** NOTE! The following LGPL license applies to the ldb
|
||||
** library. This does NOT imply that all of Samba is released
|
||||
** under the LGPL
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb extended dn control module
|
||||
*
|
||||
* Description: this module builds a special dn
|
||||
*
|
||||
* Author: Simo Sorce
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "librpc/gen_ndr/ndr_misc.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "libcli/security/security.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
static BOOL is_attr_in_list(const char * const * attrs, const char *attr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; attrs[i]; i++) {
|
||||
if (strcasecmp(attrs[i], attr) == 0)
|
||||
return True;
|
||||
}
|
||||
|
||||
return False;
|
||||
}
|
||||
|
||||
static char **copy_attrs(void *mem_ctx, const char * const * attrs)
|
||||
{
|
||||
char **new;
|
||||
int i, num;
|
||||
|
||||
for (num = 0; attrs[num]; num++);
|
||||
|
||||
new = talloc_array(mem_ctx, char *, num + 1);
|
||||
if (!new) return NULL;
|
||||
|
||||
for(i = 0; i < num; i++) {
|
||||
new[i] = talloc_strdup(new, attrs[i]);
|
||||
if (!new[i]) {
|
||||
talloc_free(new);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
new[i] = NULL;
|
||||
|
||||
return new;
|
||||
}
|
||||
|
||||
static BOOL add_attrs(void *mem_ctx, char ***attrs, const char *attr)
|
||||
{
|
||||
char **new;
|
||||
int num;
|
||||
|
||||
for (num = 0; (*attrs)[num]; num++);
|
||||
|
||||
new = talloc_realloc(mem_ctx, *attrs, char *, num + 2);
|
||||
if (!new) return False;
|
||||
|
||||
*attrs = new;
|
||||
|
||||
new[num] = talloc_strdup(new, attr);
|
||||
if (!new[num]) return False;
|
||||
|
||||
new[num + 1] = NULL;
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
static BOOL inject_extended_dn(struct ldb_message *msg,
|
||||
struct ldb_context *ldb,
|
||||
int type,
|
||||
BOOL remove_guid,
|
||||
BOOL remove_sid)
|
||||
{
|
||||
const struct ldb_val *val;
|
||||
struct GUID guid;
|
||||
struct dom_sid *sid;
|
||||
char *object_guid;
|
||||
char *object_sid;
|
||||
char *new_dn;
|
||||
|
||||
/* retrieve object_guid */
|
||||
guid = samdb_result_guid(msg, "objectGUID");
|
||||
object_guid = GUID_string(msg, &guid);
|
||||
if (!object_guid)
|
||||
return False;
|
||||
|
||||
if (remove_guid)
|
||||
ldb_msg_remove_attr(msg, "objectGUID");
|
||||
|
||||
/* retrieve object_sid */
|
||||
object_sid = NULL;
|
||||
sid = samdb_result_dom_sid(msg, msg, "objectSID");
|
||||
if (sid) {
|
||||
object_sid = dom_sid_string(msg, sid);
|
||||
if (!object_sid)
|
||||
return False;
|
||||
|
||||
if (remove_sid)
|
||||
ldb_msg_remove_attr(msg, "objectSID");
|
||||
}
|
||||
|
||||
/* TODO: handle type */
|
||||
switch (type) {
|
||||
case 0:
|
||||
case 1:
|
||||
if (object_sid) {
|
||||
new_dn = talloc_asprintf(msg, "<GUID=%s>;<SID=%s>;%s",
|
||||
object_guid, object_sid,
|
||||
ldb_dn_get_linearized(msg->dn));
|
||||
} else {
|
||||
new_dn = talloc_asprintf(msg, "<GUID=%s>;%s",
|
||||
object_guid,
|
||||
ldb_dn_get_linearized(msg->dn));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return False;
|
||||
}
|
||||
|
||||
if (!new_dn)
|
||||
return False;
|
||||
|
||||
msg->dn = ldb_dn_new(msg, ldb, new_dn);
|
||||
if (! ldb_dn_validate(msg->dn))
|
||||
return False;
|
||||
|
||||
val = ldb_msg_find_ldb_val(msg, "distinguishedName");
|
||||
if (val) {
|
||||
ldb_msg_remove_attr(msg, "distinguishedName");
|
||||
if (ldb_msg_add_steal_string(msg, "distinguishedName", new_dn))
|
||||
return False;
|
||||
}
|
||||
|
||||
return True;
|
||||
}
|
||||
|
||||
/* search */
|
||||
struct extended_context {
|
||||
|
||||
struct ldb_module *module;
|
||||
void *up_context;
|
||||
int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
|
||||
|
||||
const char * const *attrs;
|
||||
BOOL remove_guid;
|
||||
BOOL remove_sid;
|
||||
int extended_type;
|
||||
};
|
||||
|
||||
static int extended_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct extended_context *ac;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct extended_context);
|
||||
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
/* for each record returned post-process to add any derived
|
||||
attributes that have been asked for */
|
||||
if (!inject_extended_dn(ares->message, ldb, ac->extended_type, ac->remove_guid, ac->remove_sid)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return ac->up_callback(ldb, ac->up_context, ares);
|
||||
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
static int extended_search(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_control *control;
|
||||
struct ldb_extended_dn_control *extended_ctrl;
|
||||
struct ldb_control **saved_controls;
|
||||
struct extended_context *ac;
|
||||
struct ldb_request *down_req;
|
||||
char **new_attrs;
|
||||
int ret;
|
||||
|
||||
/* check if there's an extended dn control */
|
||||
control = get_control_from_list(req->controls, LDB_CONTROL_EXTENDED_DN_OID);
|
||||
if (control == NULL) {
|
||||
/* not found go on */
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
extended_ctrl = talloc_get_type(control->data, struct ldb_extended_dn_control);
|
||||
if (!extended_ctrl) {
|
||||
return LDB_ERR_PROTOCOL_ERROR;
|
||||
}
|
||||
|
||||
ac = talloc(req, struct extended_context);
|
||||
if (ac == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac->module = module;
|
||||
ac->up_context = req->context;
|
||||
ac->up_callback = req->callback;
|
||||
ac->attrs = req->op.search.attrs;
|
||||
ac->remove_guid = False;
|
||||
ac->remove_sid = False;
|
||||
ac->extended_type = extended_ctrl->type;
|
||||
|
||||
down_req = talloc_zero(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
down_req->operation = req->operation;
|
||||
down_req->op.search.base = req->op.search.base;
|
||||
down_req->op.search.scope = req->op.search.scope;
|
||||
down_req->op.search.tree = req->op.search.tree;
|
||||
|
||||
/* check if attrs only is specified, in that case check wether we need to modify them */
|
||||
if (req->op.search.attrs) {
|
||||
if (! is_attr_in_list(req->op.search.attrs, "objectGUID")) {
|
||||
ac->remove_guid = True;
|
||||
}
|
||||
if (! is_attr_in_list(req->op.search.attrs, "objectSID")) {
|
||||
ac->remove_sid = True;
|
||||
}
|
||||
if (ac->remove_guid || ac->remove_sid) {
|
||||
new_attrs = copy_attrs(down_req, req->op.search.attrs);
|
||||
if (new_attrs == NULL)
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
|
||||
if (ac->remove_guid) {
|
||||
if (!add_attrs(down_req, &new_attrs, "objectGUID"))
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
if (ac->remove_sid) {
|
||||
if (!add_attrs(down_req, &new_attrs, "objectSID"))
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
down_req->op.search.attrs = (const char * const *)new_attrs;
|
||||
}
|
||||
}
|
||||
|
||||
down_req->controls = req->controls;
|
||||
|
||||
/* save it locally and remove it from the list */
|
||||
/* we do not need to replace them later as we
|
||||
* are keeping the original req intact */
|
||||
if (!save_controls(control, down_req, &saved_controls)) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
down_req->context = ac;
|
||||
down_req->callback = extended_callback;
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* perform the search */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int extended_init(struct ldb_module *module)
|
||||
{
|
||||
struct ldb_request *req;
|
||||
int ret;
|
||||
|
||||
req = talloc(module, struct ldb_request);
|
||||
if (req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
req->operation = LDB_REQ_REGISTER_CONTROL;
|
||||
req->op.reg_control.oid = LDB_CONTROL_EXTENDED_DN_OID;
|
||||
req->controls = NULL;
|
||||
|
||||
ret = ldb_request(module->ldb, req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_ERROR, "extended_dn: Unable to register control with rootdse!\n");
|
||||
talloc_free(req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
talloc_free(req);
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops extended_dn_ops = {
|
||||
.name = "extended_dn",
|
||||
.search = extended_search,
|
||||
.init_context = extended_init
|
||||
};
|
||||
|
||||
int ldb_extended_dn_init(void)
|
||||
{
|
||||
return ldb_register_module(&extended_dn_ops);
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
ldb database library
|
||||
|
||||
Copyright (C) Andrew Bartlett 2005
|
||||
Copyright (C) Simo Sorce 2006
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb kludge ACL module
|
||||
*
|
||||
* Description: Simple module to enforce a simple form of access
|
||||
* control, sufficient for securing a default Samba4
|
||||
* installation.
|
||||
*
|
||||
* Author: Andrew Bartlett
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "auth/auth.h"
|
||||
#include "libcli/security/security.h"
|
||||
|
||||
/* Kludge ACL rules:
|
||||
*
|
||||
* - System can read passwords
|
||||
* - Administrators can write anything
|
||||
* - Users can read anything that is not a password
|
||||
*
|
||||
*/
|
||||
|
||||
enum user_is {
|
||||
ANONYMOUS,
|
||||
USER,
|
||||
ADMINISTRATOR,
|
||||
SYSTEM
|
||||
};
|
||||
|
||||
struct kludge_private_data {
|
||||
const char **password_attrs;
|
||||
};
|
||||
|
||||
static enum user_is what_is_user(struct ldb_module *module)
|
||||
{
|
||||
struct auth_session_info *session_info
|
||||
= ldb_get_opaque(module->ldb, "sessionInfo");
|
||||
if (!session_info) {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
if (security_token_is_system(session_info->security_token)) {
|
||||
return SYSTEM;
|
||||
}
|
||||
|
||||
if (security_token_is_anonymous(session_info->security_token)) {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
if (security_token_has_builtin_administrators(session_info->security_token)) {
|
||||
return ADMINISTRATOR;
|
||||
}
|
||||
|
||||
if (security_token_has_nt_authenticated_users(session_info->security_token)) {
|
||||
return USER;
|
||||
}
|
||||
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
static const char *user_name(TALLOC_CTX *mem_ctx, struct ldb_module *module)
|
||||
{
|
||||
struct auth_session_info *session_info
|
||||
= ldb_get_opaque(module->ldb, "sessionInfo");
|
||||
if (!session_info) {
|
||||
return "UNKNOWN (NULL)";
|
||||
}
|
||||
|
||||
return talloc_asprintf(mem_ctx, "%s\\%s",
|
||||
session_info->server_info->domain_name,
|
||||
session_info->server_info->account_name);
|
||||
}
|
||||
|
||||
/* search */
|
||||
struct kludge_acl_context {
|
||||
|
||||
struct ldb_module *module;
|
||||
void *up_context;
|
||||
int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
|
||||
|
||||
enum user_is user_type;
|
||||
};
|
||||
|
||||
static int kludge_acl_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct kludge_acl_context *ac;
|
||||
struct kludge_private_data *data;
|
||||
int i;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct kludge_acl_context);
|
||||
data = talloc_get_type(ac->module->private_data, struct kludge_private_data);
|
||||
|
||||
if (ares->type == LDB_REPLY_ENTRY
|
||||
&& data->password_attrs) /* if we are not initialized just get through */
|
||||
{
|
||||
switch (ac->user_type) {
|
||||
case SYSTEM:
|
||||
case ADMINISTRATOR:
|
||||
break;
|
||||
default:
|
||||
/* remove password attributes */
|
||||
for (i = 0; data->password_attrs[i]; i++) {
|
||||
ldb_msg_remove_attr(ares->message, data->password_attrs[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ac->up_callback(ldb, ac->up_context, ares);
|
||||
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
static int kludge_acl_search(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct kludge_acl_context *ac;
|
||||
struct ldb_request *down_req;
|
||||
int ret;
|
||||
|
||||
req->handle = NULL;
|
||||
|
||||
ac = talloc(req, struct kludge_acl_context);
|
||||
if (ac == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac->module = module;
|
||||
ac->up_context = req->context;
|
||||
ac->up_callback = req->callback;
|
||||
ac->user_type = what_is_user(module);
|
||||
|
||||
down_req = talloc_zero(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
down_req->operation = req->operation;
|
||||
down_req->op.search.base = req->op.search.base;
|
||||
down_req->op.search.scope = req->op.search.scope;
|
||||
down_req->op.search.tree = req->op.search.tree;
|
||||
down_req->op.search.attrs = req->op.search.attrs;
|
||||
|
||||
down_req->controls = req->controls;
|
||||
|
||||
down_req->context = ac;
|
||||
down_req->callback = kludge_acl_callback;
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* perform the search */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ANY change type */
|
||||
static int kludge_acl_change(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
enum user_is user_type = what_is_user(module);
|
||||
switch (user_type) {
|
||||
case SYSTEM:
|
||||
case ADMINISTRATOR:
|
||||
return ldb_next_request(module, req);
|
||||
default:
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"kludge_acl_change: "
|
||||
"attempted database modify not permitted. "
|
||||
"User %s is not SYSTEM or an administrator",
|
||||
user_name(req, module));
|
||||
return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
|
||||
}
|
||||
}
|
||||
|
||||
static int kludge_acl_init(struct ldb_module *module)
|
||||
{
|
||||
int ret, i;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(module);
|
||||
static const char *attrs[] = { "passwordAttribute", NULL };
|
||||
struct ldb_result *res;
|
||||
struct ldb_message *msg;
|
||||
struct ldb_message_element *password_attributes;
|
||||
|
||||
struct kludge_private_data *data;
|
||||
|
||||
data = talloc(module, struct kludge_private_data);
|
||||
if (data == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
data->password_attrs = NULL;
|
||||
module->private_data = data;
|
||||
|
||||
if (!mem_ctx) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@KLUDGEACL"),
|
||||
LDB_SCOPE_BASE,
|
||||
NULL, attrs,
|
||||
&res);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
talloc_steal(mem_ctx, res);
|
||||
if (res->count == 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (res->count > 1) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
msg = res->msgs[0];
|
||||
|
||||
password_attributes = ldb_msg_find_element(msg, "passwordAttribute");
|
||||
if (!password_attributes) {
|
||||
goto done;
|
||||
}
|
||||
data->password_attrs = talloc_array(data, const char *, password_attributes->num_values + 1);
|
||||
if (!data->password_attrs) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
for (i=0; i < password_attributes->num_values; i++) {
|
||||
data->password_attrs[i] = (const char *)password_attributes->values[i].data;
|
||||
talloc_steal(data->password_attrs, password_attributes->values[i].data);
|
||||
}
|
||||
data->password_attrs[i] = NULL;
|
||||
|
||||
done:
|
||||
talloc_free(mem_ctx);
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops kludge_acl_ops = {
|
||||
.name = "kludge_acl",
|
||||
.search = kludge_acl_search,
|
||||
.add = kludge_acl_change,
|
||||
.modify = kludge_acl_change,
|
||||
.del = kludge_acl_change,
|
||||
.rename = kludge_acl_change,
|
||||
.init_context = kludge_acl_init
|
||||
};
|
||||
|
||||
int ldb_kludge_acl_init(void)
|
||||
{
|
||||
return ldb_register_module(&kludge_acl_ops);
|
||||
}
|
||||
@@ -0,0 +1,878 @@
|
||||
/*
|
||||
ldb database module
|
||||
|
||||
Copyright (C) Simo Sorce 2004-2006
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2006
|
||||
Copyright (C) Andrew Tridgell 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb local_password module
|
||||
*
|
||||
* Description: correctly update hash values based on changes to sambaPassword and friends
|
||||
*
|
||||
* Author: Andrew Bartlett
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "libcli/ldap/ldap.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "librpc/ndr/libndr.h"
|
||||
#include "dsdb/samdb/ldb_modules/password_modules.h"
|
||||
|
||||
#define PASSWORD_GUID_ATTR "masterGUID"
|
||||
|
||||
/* This module maintains a local password database, seperate from the main LDAP server.
|
||||
|
||||
This allows the password database to be syncronised in a multi-master
|
||||
fashion, seperate to the more difficult concerns of the main
|
||||
database. (With passwords, the last writer always wins)
|
||||
|
||||
Each incoming add/modify is split into a remote, and a local request, done in that order.
|
||||
|
||||
We maintain a list of attributes that are kept locally:
|
||||
*/
|
||||
|
||||
static const char * const password_attrs[] = {
|
||||
"sambaPassword",
|
||||
"krb5Key",
|
||||
"ntPwdHash",
|
||||
"lmPwdHash",
|
||||
"sambaLMPwdHistory",
|
||||
"sambaNTPwdHistory",
|
||||
"msDS-KeyVersionNumber",
|
||||
"pwdLastSet"
|
||||
};
|
||||
|
||||
/* And we merge them back into search requests when asked to do so */
|
||||
|
||||
struct lpdb_context {
|
||||
|
||||
enum lpdb_type {LPDB_ADD, LPDB_MOD, LPDB_SEARCH} type;
|
||||
enum lpdb_step {LPDB_ADD_REMOTE, LPDB_MOD_REMOTE, LPDB_MOD_SEARCH_SELF, LPDB_LOCAL, LPDB_SEARCH_REMOTE} step;
|
||||
|
||||
struct ldb_module *module;
|
||||
struct ldb_request *orig_req;
|
||||
struct ldb_request *remote_req;
|
||||
struct ldb_request *search_req;
|
||||
struct ldb_request *local_req;
|
||||
|
||||
struct ldb_message *local_message;
|
||||
|
||||
BOOL added_objectGUID;
|
||||
BOOL added_objectClass;
|
||||
|
||||
struct ldb_reply *search_res;
|
||||
};
|
||||
|
||||
struct lpdb_local_search_context {
|
||||
struct lpdb_context *ac;
|
||||
struct ldb_reply *remote_res;
|
||||
struct ldb_reply *local_res;
|
||||
};
|
||||
|
||||
static struct ldb_handle *lpdb_init_handle(struct ldb_request *req, struct ldb_module *module, enum lpdb_type type)
|
||||
{
|
||||
struct lpdb_context *ac;
|
||||
struct ldb_handle *h;
|
||||
|
||||
h = talloc_zero(req, struct ldb_handle);
|
||||
if (h == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
h->module = module;
|
||||
|
||||
ac = talloc_zero(h, struct lpdb_context);
|
||||
if (ac == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
talloc_free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
h->private_data = (void *)ac;
|
||||
|
||||
h->state = LDB_ASYNC_INIT;
|
||||
h->status = LDB_SUCCESS;
|
||||
|
||||
ac->type = type;
|
||||
ac->module = module;
|
||||
ac->orig_req = req;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/* Add a record, splitting password attributes from the user's main
|
||||
* record */
|
||||
|
||||
static int local_password_add(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_handle *h;
|
||||
struct lpdb_context *ac;
|
||||
struct ldb_message *remote_message;
|
||||
struct ldb_message *local_message;
|
||||
struct GUID objectGUID;
|
||||
int i;
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_add\n");
|
||||
|
||||
if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* If the caller is manipulating the local passwords directly, let them pass */
|
||||
if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
|
||||
req->op.add.message->dn) == 0) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
|
||||
if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* It didn't match any of our password attributes, go on */
|
||||
if (i == ARRAY_SIZE(password_attrs)) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* TODO: remove this when sambaPassword will be in schema */
|
||||
if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"Cannot relocate a password on entry: %s, does not have objectClass 'person'",
|
||||
ldb_dn_get_linearized(req->op.add.message->dn));
|
||||
return LDB_ERR_OBJECT_CLASS_VIOLATION;
|
||||
}
|
||||
|
||||
/* From here, we assume we have password attributes to split off */
|
||||
h = lpdb_init_handle(req, module, LPDB_ADD);
|
||||
if (!h) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
ac->orig_req = req;
|
||||
|
||||
ac->remote_req = talloc(ac, struct ldb_request);
|
||||
if (ac->remote_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*(ac->remote_req) = *(ac->orig_req);
|
||||
|
||||
remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.add.message);
|
||||
if (remote_message == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Remove any password attributes from the remote message */
|
||||
for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
|
||||
ldb_msg_remove_attr(remote_message, password_attrs[i]);
|
||||
}
|
||||
|
||||
ac->remote_req->op.add.message = remote_message;
|
||||
|
||||
ac->remote_req->context = NULL;
|
||||
ac->remote_req->callback = NULL;
|
||||
|
||||
ac->local_req = talloc(ac, struct ldb_request);
|
||||
if (ac->local_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*(ac->local_req) = *(ac->orig_req);
|
||||
local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.add.message);
|
||||
if (local_message == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Remove anything seen in the remote message from the local
|
||||
* message (leaving only password attributes) */
|
||||
for (i=0;i<ac->remote_req->op.add.message->num_elements;i++) {
|
||||
ldb_msg_remove_attr(local_message, ac->remote_req->op.add.message->elements[i].name);
|
||||
}
|
||||
|
||||
/* We must have an objectGUID already, or we don't know where
|
||||
* to add the password. This may be changed to an 'add and
|
||||
* search', to allow the directory to create the objectGUID */
|
||||
if (ldb_msg_find_ldb_val(ac->orig_req->op.add.message, "objectGUID") == NULL) {
|
||||
ldb_set_errstring(module->ldb,
|
||||
"no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
/* Find the objectGUID to use as the key */
|
||||
objectGUID = samdb_result_guid(ac->orig_req->op.add.message, "objectGUID");
|
||||
|
||||
local_message->dn = ldb_dn_new(local_message, module->ldb, LOCAL_BASE);
|
||||
ldb_dn_add_child_fmt(local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(local_message, &objectGUID));
|
||||
|
||||
ac->local_req->op.add.message = local_message;
|
||||
|
||||
ac->local_req->context = NULL;
|
||||
ac->local_req->callback = NULL;
|
||||
|
||||
ac->step = LPDB_ADD_REMOTE;
|
||||
|
||||
/* Return our own handle do deal with this call */
|
||||
req->handle = h;
|
||||
|
||||
return ldb_next_request(module, ac->remote_req);
|
||||
}
|
||||
|
||||
/* After adding the remote entry, add the local one */
|
||||
static int local_password_add_local(struct ldb_handle *h) {
|
||||
|
||||
struct lpdb_context *ac;
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
h->state = LDB_ASYNC_INIT;
|
||||
h->status = LDB_SUCCESS;
|
||||
|
||||
ac->step = LPDB_LOCAL;
|
||||
|
||||
ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
|
||||
|
||||
/* perform the local add */
|
||||
return ldb_next_request(ac->module, ac->local_req);
|
||||
}
|
||||
|
||||
static int local_password_mod_search_self(struct ldb_handle *h);
|
||||
|
||||
static int local_password_modify(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_handle *h;
|
||||
struct lpdb_context *ac;
|
||||
struct ldb_message *remote_message;
|
||||
struct ldb_message *local_message;
|
||||
int i;
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_modify\n");
|
||||
|
||||
if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* If the caller is manipulating the local passwords directly, let them pass */
|
||||
if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
|
||||
req->op.mod.message->dn) == 0) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
|
||||
if (ldb_msg_find_element(req->op.add.message, password_attrs[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* It didn't match any of our password attributes, then we have nothing to do here */
|
||||
if (i == ARRAY_SIZE(password_attrs)) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* From here, we assume we have password attributes to split off */
|
||||
h = lpdb_init_handle(req, module, LPDB_MOD);
|
||||
if (!h) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
ac->orig_req = req;
|
||||
|
||||
ac->remote_req = talloc(ac, struct ldb_request);
|
||||
if (ac->remote_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*(ac->remote_req) = *(ac->orig_req);
|
||||
remote_message = ldb_msg_copy_shallow(ac->remote_req, ac->orig_req->op.mod.message);
|
||||
if (remote_message == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Remove any password attributes from the remote message */
|
||||
for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
|
||||
ldb_msg_remove_attr(remote_message, password_attrs[i]);
|
||||
}
|
||||
|
||||
ac->remote_req->op.mod.message = remote_message;
|
||||
|
||||
ac->remote_req->context = NULL;
|
||||
ac->remote_req->callback = NULL;
|
||||
|
||||
ac->local_req = talloc(ac, struct ldb_request);
|
||||
if (ac->local_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*(ac->local_req) = *(ac->orig_req);
|
||||
local_message = ldb_msg_copy_shallow(ac->local_req, ac->orig_req->op.mod.message);
|
||||
if (local_message == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Remove anything seen in the remote message from the local
|
||||
* message (leaving only password attributes) */
|
||||
for (i=0;i<ac->remote_req->op.mod.message->num_elements;i++) {
|
||||
ldb_msg_remove_attr(local_message, ac->remote_req->op.mod.message->elements[i].name);
|
||||
}
|
||||
|
||||
ac->local_req->op.mod.message = local_message;
|
||||
ac->local_message = local_message;
|
||||
|
||||
ac->local_req->context = NULL;
|
||||
ac->local_req->callback = NULL;
|
||||
|
||||
ac->step = LPDB_MOD_REMOTE;
|
||||
|
||||
/* Return our own handle do deal with this call */
|
||||
req->handle = h;
|
||||
|
||||
return ldb_next_request(module, ac->remote_req);
|
||||
}
|
||||
|
||||
/* Called when we search for our oen entry. Stores the one entry we
|
||||
* expect (as it is a base search) on the context pointer */
|
||||
static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct lpdb_context *ac;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct lpdb_context);
|
||||
|
||||
/* we are interested only in the single reply (base search) we receive here */
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
if (ac->search_res != NULL) {
|
||||
ldb_set_errstring(ldb, "Too many results");
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac->search_res = talloc_steal(ac, ares);
|
||||
} else {
|
||||
talloc_free(ares);
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* On a modify, we don't have the objectGUID handy, so we need to
|
||||
* search our DN for it */
|
||||
static int local_password_mod_search_self(struct ldb_handle *h) {
|
||||
|
||||
struct lpdb_context *ac;
|
||||
static const char * const attrs[] = { "objectGUID", "objectClass", NULL };
|
||||
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
/* prepare the search operation */
|
||||
ac->search_req = talloc_zero(ac, struct ldb_request);
|
||||
if (ac->search_req == NULL) {
|
||||
ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac->search_req->operation = LDB_SEARCH;
|
||||
ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
|
||||
ac->search_req->op.search.scope = LDB_SCOPE_BASE;
|
||||
ac->search_req->op.search.tree = ldb_parse_tree(ac->orig_req, NULL);
|
||||
if (ac->search_req->op.search.tree == NULL) {
|
||||
ldb_set_errstring(ac->module->ldb, "Invalid search filter");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ac->search_req->op.search.attrs = attrs;
|
||||
ac->search_req->controls = NULL;
|
||||
ac->search_req->context = ac;
|
||||
ac->search_req->callback = get_self_callback;
|
||||
ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->search_req);
|
||||
|
||||
ac->step = LPDB_MOD_SEARCH_SELF;
|
||||
|
||||
return ldb_next_request(ac->module, ac->search_req);
|
||||
}
|
||||
|
||||
/* After we find out the objectGUID for the entry, modify the local
|
||||
* password database as required */
|
||||
static int local_password_mod_local(struct ldb_handle *h) {
|
||||
|
||||
struct lpdb_context *ac;
|
||||
struct GUID objectGUID;
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
/* if it is not an entry of type person this is an error */
|
||||
/* TODO: remove this when sambaPassword will be in schema */
|
||||
if (!ac->search_res) {
|
||||
ldb_asprintf_errstring(ac->module->ldb,
|
||||
"entry just modified (%s) not found!",
|
||||
ldb_dn_get_linearized(ac->remote_req->op.mod.message->dn));
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
if (!ldb_msg_check_string_attribute(ac->search_res->message, "objectClass", "person")) {
|
||||
/* Not relevent to us */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
if (ldb_msg_find_ldb_val(ac->search_res->message, "objectGUID") == NULL) {
|
||||
ldb_set_errstring(ac->module->ldb,
|
||||
"no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
|
||||
return LDB_ERR_OBJECT_CLASS_VIOLATION;
|
||||
}
|
||||
|
||||
objectGUID = samdb_result_guid(ac->search_res->message, "objectGUID");
|
||||
|
||||
ac->local_message->dn = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
|
||||
ldb_dn_add_child_fmt(ac->local_message->dn, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID));
|
||||
|
||||
h->state = LDB_ASYNC_INIT;
|
||||
h->status = LDB_SUCCESS;
|
||||
|
||||
ac->step = LPDB_LOCAL;
|
||||
|
||||
ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, ac->local_req);
|
||||
|
||||
/* perform the local update */
|
||||
return ldb_next_request(ac->module, ac->local_req);
|
||||
}
|
||||
|
||||
|
||||
static int lpdb_local_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct lpdb_local_search_context *local_context;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
local_context = talloc_get_type(context, struct lpdb_local_search_context);
|
||||
|
||||
/* we are interested only in the single reply (base search) we receive here */
|
||||
switch (ares->type) {
|
||||
case LDB_REPLY_ENTRY:
|
||||
{
|
||||
int i;
|
||||
if (local_context->local_res != NULL) {
|
||||
ldb_set_errstring(ldb, "Too many results to base search for password entry!");
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
local_context->local_res = ares;
|
||||
|
||||
/* Make sure never to return the internal key attribute to the caller */
|
||||
ldb_msg_remove_attr(ares->message, PASSWORD_GUID_ATTR);
|
||||
|
||||
talloc_steal(local_context->remote_res->message->elements, ares->message->elements);
|
||||
for (i=0; i < ares->message->num_elements; i++) {
|
||||
struct ldb_message_element *el;
|
||||
|
||||
el = ldb_msg_find_element(local_context->remote_res->message,
|
||||
ares->message->elements[i].name);
|
||||
if (!el) {
|
||||
if (ldb_msg_add_empty(local_context->remote_res->message,
|
||||
ares->message->elements[i].name, 0, &el) != LDB_SUCCESS) {
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
*el = ares->message->elements[i];
|
||||
}
|
||||
}
|
||||
return local_context->ac->orig_req->callback(ldb,
|
||||
local_context->ac->orig_req->context,
|
||||
local_context->remote_res);
|
||||
}
|
||||
case LDB_REPLY_DONE:
|
||||
{
|
||||
/* Fire off the callback if there was no local entry, so we get the rest returned */
|
||||
if (local_context->local_res == NULL) {
|
||||
return local_context->ac->orig_req->callback(ldb,
|
||||
local_context->ac->orig_req->context,
|
||||
local_context->remote_res);
|
||||
}
|
||||
return LDB_SUCCESS;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
talloc_free(ares);
|
||||
ldb_set_errstring(ldb, "Unexpected result type in base search for password entry!");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* For each entry returned in a remote search, do a local base search,
|
||||
* based on the objectGUID we asked for as an additional attribute */
|
||||
static int lpdb_remote_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct lpdb_context *ac;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct lpdb_context);
|
||||
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
struct ldb_request *req;
|
||||
struct lpdb_local_search_context *local_context;
|
||||
struct GUID objectGUID;
|
||||
|
||||
/* No point searching further if it's not a 'person' entry */
|
||||
if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
|
||||
|
||||
/* Make sure to remove anything we added */
|
||||
if (ac->added_objectGUID) {
|
||||
ldb_msg_remove_attr(ares->message, "objectGUID");
|
||||
}
|
||||
|
||||
if (ac->added_objectClass) {
|
||||
ldb_msg_remove_attr(ares->message, "objectClass");
|
||||
}
|
||||
|
||||
return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
|
||||
}
|
||||
|
||||
if (ldb_msg_find_ldb_val(ares->message, "objectGUID") == NULL) {
|
||||
ldb_set_errstring(ac->module->ldb,
|
||||
"no objectGUID found in search: local_password module must be configured below objectGUID module!\n");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
objectGUID = samdb_result_guid(ares->message, "objectGUID");
|
||||
|
||||
if (ac->added_objectGUID) {
|
||||
ldb_msg_remove_attr(ares->message, "objectGUID");
|
||||
}
|
||||
|
||||
if (ac->added_objectClass) {
|
||||
ldb_msg_remove_attr(ares->message, "objectClass");
|
||||
}
|
||||
|
||||
req = talloc_zero(ac, struct ldb_request);
|
||||
if (!req) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
local_context = talloc(ac, struct lpdb_local_search_context);
|
||||
if (!local_context) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
local_context->ac = ac;
|
||||
local_context->remote_res = ares;
|
||||
local_context->local_res = NULL;
|
||||
|
||||
req->op.search.base = ldb_dn_new(ac, ac->module->ldb, LOCAL_BASE);
|
||||
if ( ! ldb_dn_add_child_fmt(req->op.search.base, PASSWORD_GUID_ATTR "=%s", GUID_string(ac, &objectGUID))) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
req->operation = LDB_SEARCH;
|
||||
req->op.search.scope = LDB_SCOPE_BASE;
|
||||
req->op.search.tree = ldb_parse_tree(req, NULL);
|
||||
if (req->op.search.tree == NULL) {
|
||||
ldb_set_errstring(ac->module->ldb, "Out of Memory");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
req->op.search.attrs = ac->orig_req->op.search.attrs;
|
||||
req->controls = NULL;
|
||||
req->context = ac;
|
||||
req->callback = get_self_callback;
|
||||
|
||||
ldb_set_timeout_from_prev_req(ac->module->ldb, ac->orig_req, req);
|
||||
|
||||
req->context = local_context;
|
||||
req->callback = lpdb_local_search_callback;
|
||||
|
||||
return ldb_next_request(ac->module, req);
|
||||
} else {
|
||||
return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
|
||||
}
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Search for passwords and other attributes. The passwords are
|
||||
* local, but the other attributes are remote, and we need to glue the
|
||||
* two search spaces back togeather */
|
||||
|
||||
static int local_password_search(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_handle *h;
|
||||
struct lpdb_context *ac;
|
||||
int i;
|
||||
int ret;
|
||||
const char * const *search_attrs = NULL;
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "local_password_search\n");
|
||||
|
||||
if (ldb_dn_is_special(req->op.search.base)) { /* do not manipulate our control entries */
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* If the caller is searching for the local passwords directly, let them pass */
|
||||
if (ldb_dn_compare_base(ldb_dn_new(req, module->ldb, LOCAL_BASE),
|
||||
req->op.search.base) == 0) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
if (req->op.search.attrs && (!ldb_attr_in_list(req->op.search.attrs, "*"))) {
|
||||
for (i=0; i < ARRAY_SIZE(password_attrs); i++) {
|
||||
if (ldb_attr_in_list(req->op.search.attrs, password_attrs[i])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* It didn't match any of our password attributes, go on */
|
||||
if (i == ARRAY_SIZE(password_attrs)) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
}
|
||||
|
||||
h = lpdb_init_handle(req, module, LPDB_SEARCH);
|
||||
if (!h) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(h->private_data, struct lpdb_context);
|
||||
|
||||
ac->orig_req = req;
|
||||
|
||||
ac->remote_req = talloc(ac, struct ldb_request);
|
||||
if (ac->remote_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Remote search is for all attributes: if the remote LDAP server has these attributes, then it overrides the local database */
|
||||
*(ac->remote_req) = *(ac->orig_req);
|
||||
|
||||
/* Return our own handle do deal with this call */
|
||||
ac->remote_req->handle = h;
|
||||
|
||||
ac->remote_req->context = ac;
|
||||
ac->remote_req->callback = lpdb_remote_search_callback;
|
||||
|
||||
if (req->op.search.attrs && !ldb_attr_in_list(req->op.search.attrs, "*")) {
|
||||
if (!ldb_attr_in_list(req->op.search.attrs, "objectGUID")) {
|
||||
search_attrs = ldb_attr_list_copy_add(req, req->op.search.attrs, "objectGUID");
|
||||
ac->added_objectGUID = True;
|
||||
if (!search_attrs) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
} else {
|
||||
search_attrs = req->op.search.attrs;
|
||||
}
|
||||
if (!ldb_attr_in_list(search_attrs, "objectClass")) {
|
||||
search_attrs = ldb_attr_list_copy_add(req, search_attrs, "objectClass");
|
||||
ac->added_objectClass = True;
|
||||
if (!search_attrs) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
search_attrs = req->op.search.attrs;
|
||||
}
|
||||
|
||||
ac->remote_req->op.search.attrs = search_attrs;
|
||||
|
||||
ldb_set_timeout_from_prev_req(module->ldb, ac->orig_req, ac->remote_req);
|
||||
|
||||
h->state = LDB_ASYNC_INIT;
|
||||
h->status = LDB_SUCCESS;
|
||||
|
||||
ac->step = LPDB_SEARCH_REMOTE;
|
||||
|
||||
/* perform the search */
|
||||
ret = ldb_next_request(module, ac->remote_req);
|
||||
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = ac->remote_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpdb_wait(struct ldb_handle *handle) {
|
||||
struct lpdb_context *ac;
|
||||
int ret;
|
||||
|
||||
if (!handle || !handle->private_data) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
if (handle->state == LDB_ASYNC_DONE) {
|
||||
return handle->status;
|
||||
}
|
||||
|
||||
handle->state = LDB_ASYNC_PENDING;
|
||||
handle->status = LDB_SUCCESS;
|
||||
|
||||
ac = talloc_get_type(handle->private_data, struct lpdb_context);
|
||||
|
||||
switch (ac->step) {
|
||||
case LPDB_ADD_REMOTE:
|
||||
ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->remote_req->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->remote_req->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* original request done, go on */
|
||||
return local_password_add_local(handle);
|
||||
|
||||
case LPDB_MOD_REMOTE:
|
||||
ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->remote_req->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->remote_req->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* original request done, go on */
|
||||
return local_password_mod_search_self(handle);
|
||||
|
||||
case LPDB_MOD_SEARCH_SELF:
|
||||
ret = ldb_wait(ac->search_req->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->search_req->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->search_req->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->search_req->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* original request done, go on */
|
||||
return local_password_mod_local(handle);
|
||||
|
||||
case LPDB_LOCAL:
|
||||
ret = ldb_wait(ac->local_req->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->local_req->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->local_req->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->local_req->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LPDB_SEARCH_REMOTE:
|
||||
ret = ldb_wait(ac->remote_req->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->remote_req->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->remote_req->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->remote_req->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = LDB_SUCCESS;
|
||||
|
||||
done:
|
||||
handle->state = LDB_ASYNC_DONE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lpdb_wait_all(struct ldb_handle *handle) {
|
||||
|
||||
int ret;
|
||||
|
||||
while (handle->state != LDB_ASYNC_DONE) {
|
||||
ret = lpdb_wait(handle);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return handle->status;
|
||||
}
|
||||
|
||||
static int local_password_wait(struct ldb_handle *handle, enum ldb_wait_type type)
|
||||
{
|
||||
if (type == LDB_WAIT_ALL) {
|
||||
return lpdb_wait_all(handle);
|
||||
} else {
|
||||
return lpdb_wait(handle);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops local_password_ops = {
|
||||
.name = "local_password",
|
||||
.add = local_password_add,
|
||||
.modify = local_password_modify,
|
||||
.search = local_password_search,
|
||||
.wait = local_password_wait
|
||||
};
|
||||
|
||||
|
||||
int local_password_module_init(void)
|
||||
{
|
||||
return ldb_register_module(&local_password_ops);
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
ldb database library
|
||||
|
||||
Copyright (C) Simo Sorce 2004-2006
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
Copyright (C) Andrew Tridgell 2005
|
||||
|
||||
** NOTE! The following LGPL license applies to the ldb
|
||||
** library. This does NOT imply that all of Samba is released
|
||||
** under the LGPL
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb objectguid module
|
||||
*
|
||||
* Description: add a unique objectGUID onto every new record
|
||||
*
|
||||
* Author: Simo Sorce
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/includes.h"
|
||||
#include "librpc/gen_ndr/ndr_misc.h"
|
||||
|
||||
static struct ldb_message_element *objectguid_find_attribute(const struct ldb_message *msg, const char *name)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < msg->num_elements; i++) {
|
||||
if (ldb_attr_cmp(name, msg->elements[i].name) == 0) {
|
||||
return &msg->elements[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
add a time element to a record
|
||||
*/
|
||||
static int add_time_element(struct ldb_message *msg, const char *attr, time_t t)
|
||||
{
|
||||
struct ldb_message_element *el;
|
||||
char *s;
|
||||
|
||||
if (ldb_msg_find_element(msg, attr) != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s = ldb_timestring(msg, t);
|
||||
if (s == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ldb_msg_add_string(msg, attr, s) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
el = ldb_msg_find_element(msg, attr);
|
||||
/* always set as replace. This works because on add ops, the flag
|
||||
is ignored */
|
||||
el->flags = LDB_FLAG_MOD_REPLACE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
add a uint64_t element to a record
|
||||
*/
|
||||
static int add_uint64_element(struct ldb_message *msg, const char *attr, uint64_t v)
|
||||
{
|
||||
struct ldb_message_element *el;
|
||||
|
||||
if (ldb_msg_find_element(msg, attr) != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ldb_msg_add_fmt(msg, attr, "%llu", (unsigned long long)v) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
el = ldb_msg_find_element(msg, attr);
|
||||
/* always set as replace. This works because on add ops, the flag
|
||||
is ignored */
|
||||
el->flags = LDB_FLAG_MOD_REPLACE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* add_record: add objectGUID attribute */
|
||||
static int objectguid_add(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_request *down_req;
|
||||
struct ldb_message_element *attribute;
|
||||
struct ldb_message *msg;
|
||||
struct ldb_val v;
|
||||
struct GUID guid;
|
||||
uint64_t seq_num;
|
||||
NTSTATUS nt_status;
|
||||
int ret;
|
||||
time_t t = time(NULL);
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
|
||||
|
||||
/* do not manipulate our control entries */
|
||||
if (ldb_dn_is_special(req->op.add.message->dn)) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
if ((attribute = objectguid_find_attribute(req->op.add.message, "objectGUID")) != NULL ) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
down_req = talloc(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*down_req = *req;
|
||||
|
||||
/* we have to copy the message as the caller might have it as a const */
|
||||
down_req->op.add.message = msg = ldb_msg_copy_shallow(down_req, req->op.add.message);
|
||||
if (msg == NULL) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* a new GUID */
|
||||
guid = GUID_random();
|
||||
|
||||
nt_status = ndr_push_struct_blob(&v, msg, &guid,
|
||||
(ndr_push_flags_fn_t)ndr_push_GUID);
|
||||
if (!NT_STATUS_IS_OK(nt_status)) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ret = ldb_msg_add_value(msg, "objectGUID", &v, NULL);
|
||||
if (ret) {
|
||||
talloc_free(down_req);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (add_time_element(msg, "whenCreated", t) != 0 ||
|
||||
add_time_element(msg, "whenChanged", t) != 0) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Get a sequence number from the backend */
|
||||
ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
|
||||
if (ret == LDB_SUCCESS) {
|
||||
if (add_uint64_element(msg, "uSNCreated", seq_num) != 0 ||
|
||||
add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* go on with the call chain */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* modify_record: update timestamps */
|
||||
static int objectguid_modify(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct ldb_request *down_req;
|
||||
struct ldb_message *msg;
|
||||
int ret;
|
||||
time_t t = time(NULL);
|
||||
uint64_t seq_num;
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "objectguid_add_record\n");
|
||||
|
||||
/* do not manipulate our control entries */
|
||||
if (ldb_dn_is_special(req->op.add.message->dn)) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
down_req = talloc(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*down_req = *req;
|
||||
|
||||
/* we have to copy the message as the caller might have it as a const */
|
||||
down_req->op.mod.message = msg = ldb_msg_copy_shallow(down_req, req->op.mod.message);
|
||||
if (msg == NULL) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
if (add_time_element(msg, "whenChanged", t) != 0) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* Get a sequence number from the backend */
|
||||
ret = ldb_sequence_number(module->ldb, LDB_SEQ_NEXT, &seq_num);
|
||||
if (ret == LDB_SUCCESS) {
|
||||
if (add_uint64_element(msg, "uSNChanged", seq_num) != 0) {
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* go on with the call chain */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops objectguid_ops = {
|
||||
.name = "objectguid",
|
||||
.add = objectguid_add,
|
||||
.modify = objectguid_modify,
|
||||
};
|
||||
|
||||
|
||||
int objectguid_module_init(void)
|
||||
{
|
||||
return ldb_register_module(&objectguid_ops);
|
||||
}
|
||||
@@ -0,0 +1,891 @@
|
||||
/*
|
||||
Partitions ldb module
|
||||
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006
|
||||
|
||||
* NOTICE: this module is NOT released under the GNU LGPL license as
|
||||
* other ldb code. This module is release under the GNU GPL v2 or
|
||||
* later license.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb partitions module
|
||||
*
|
||||
* Description: Implement LDAP partitions
|
||||
*
|
||||
* Author: Andrew Bartlett
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/includes.h"
|
||||
|
||||
struct partition {
|
||||
struct ldb_module *module;
|
||||
const char *backend;
|
||||
struct ldb_dn *dn;
|
||||
};
|
||||
struct partition_private_data {
|
||||
struct partition **partitions;
|
||||
struct ldb_dn **replicate;
|
||||
};
|
||||
|
||||
struct partition_context {
|
||||
struct ldb_module *module;
|
||||
struct ldb_request *orig_req;
|
||||
|
||||
struct ldb_request **down_req;
|
||||
int num_requests;
|
||||
int finished_requests;
|
||||
};
|
||||
|
||||
static struct ldb_handle *partition_init_handle(struct ldb_request *req, struct ldb_module *module)
|
||||
{
|
||||
struct partition_context *ac;
|
||||
struct ldb_handle *h;
|
||||
|
||||
h = talloc_zero(req, struct ldb_handle);
|
||||
if (h == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
h->module = module;
|
||||
|
||||
ac = talloc_zero(h, struct partition_context);
|
||||
if (ac == NULL) {
|
||||
ldb_set_errstring(module->ldb, "Out of Memory");
|
||||
talloc_free(h);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
h->private_data = (void *)ac;
|
||||
|
||||
ac->module = module;
|
||||
ac->orig_req = req;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
struct ldb_module *make_module_for_next_request(TALLOC_CTX *mem_ctx,
|
||||
struct ldb_context *ldb,
|
||||
struct ldb_module *module)
|
||||
{
|
||||
struct ldb_module *current;
|
||||
static const struct ldb_module_ops ops; /* zero */
|
||||
current = talloc_zero(mem_ctx, struct ldb_module);
|
||||
if (current == NULL) {
|
||||
return module;
|
||||
}
|
||||
|
||||
current->ldb = ldb;
|
||||
current->ops = &ops;
|
||||
current->prev = NULL;
|
||||
current->next = module;
|
||||
return current;
|
||||
}
|
||||
|
||||
struct ldb_module *find_backend(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
|
||||
{
|
||||
int i;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
if (ldb_dn_compare_base(data->partitions[i]->dn, dn) == 0) {
|
||||
return make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
|
||||
}
|
||||
}
|
||||
|
||||
return module;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
fire the caller's callback for every entry, but only send 'done' once.
|
||||
*/
|
||||
static int partition_search_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct partition_context *ac;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "partition_search_callback: NULL Context or Result in 'search' callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct partition_context);
|
||||
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
|
||||
} else {
|
||||
ac->finished_requests++;
|
||||
if (ac->finished_requests == ac->num_requests) {
|
||||
return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
|
||||
} else {
|
||||
talloc_free(ares);
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
}
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
only fire the 'last' callback, and only for START-TLS for now
|
||||
*/
|
||||
static int partition_other_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct partition_context *ac;
|
||||
|
||||
if (!context) {
|
||||
ldb_set_errstring(ldb, "partition_other_callback: NULL Context in 'other' callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct partition_context);
|
||||
|
||||
if (!ac->orig_req->callback) {
|
||||
talloc_free(ares);
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
if (!ares
|
||||
|| (ares->type == LDB_REPLY_EXTENDED
|
||||
&& strcmp(ares->response->oid, LDB_EXTENDED_START_TLS_OID))) {
|
||||
ac->finished_requests++;
|
||||
if (ac->finished_requests == ac->num_requests) {
|
||||
return ac->orig_req->callback(ldb, ac->orig_req->context, ares);
|
||||
}
|
||||
talloc_free(ares);
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
ldb_set_errstring(ldb, "partition_other_callback: Unknown reply type, only supports START_TLS");
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
|
||||
static int partition_send_request(struct partition_context *ac, struct ldb_module *partition,
|
||||
struct ldb_dn *partition_base_dn)
|
||||
{
|
||||
int ret;
|
||||
struct ldb_module *next = make_module_for_next_request(ac->module, ac->module->ldb, partition);
|
||||
struct ldb_request *req;
|
||||
ac->down_req = talloc_realloc(ac, ac->down_req,
|
||||
struct ldb_request *, ac->num_requests + 1);
|
||||
if (!ac->down_req) {
|
||||
ldb_set_errstring(ac->module->ldb, "Out of Memory");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
req = ac->down_req[ac->num_requests] = talloc(ac, struct ldb_request);
|
||||
if (req == NULL) {
|
||||
ldb_set_errstring(ac->module->ldb, "Out of Memory");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*ac->down_req[ac->num_requests] = *ac->orig_req; /* copy the request */
|
||||
|
||||
if (req->operation == LDB_SEARCH) {
|
||||
/* If the search is for 'more' than this partition,
|
||||
* then change the basedn, so a remote LDAP server
|
||||
* doesn't object */
|
||||
if (ldb_dn_compare_base(partition_base_dn, req->op.search.base) != 0) {
|
||||
req->op.search.base = partition_base_dn;
|
||||
}
|
||||
req->callback = partition_search_callback;
|
||||
req->context = ac;
|
||||
} else {
|
||||
req->callback = partition_other_callback;
|
||||
req->context = ac;
|
||||
}
|
||||
|
||||
/* Spray off search requests to all backends */
|
||||
ret = ldb_next_request(next, req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ac->num_requests++;
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* Send a request down to all the partitions */
|
||||
static int partition_send_all(struct ldb_module *module,
|
||||
struct partition_context *ac, struct ldb_request *req)
|
||||
{
|
||||
int i;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
int ret = partition_send_request(ac, module->next, NULL);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
ret = partition_send_request(ac, data->partitions[i]->module, data->partitions[i]->dn);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* Figure out which backend a request needs to be aimed at. Some
|
||||
* requests must be replicated to all backends */
|
||||
static int partition_replicate(struct ldb_module *module, struct ldb_request *req, struct ldb_dn *dn)
|
||||
{
|
||||
int i;
|
||||
struct ldb_module *backend;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
|
||||
/* Is this a special DN, we need to replicate to every backend? */
|
||||
for (i=0; data->replicate && data->replicate[i]; i++) {
|
||||
if (ldb_dn_compare(data->replicate[i],
|
||||
dn) == 0) {
|
||||
struct ldb_handle *h;
|
||||
struct partition_context *ac;
|
||||
|
||||
h = partition_init_handle(req, module);
|
||||
if (!h) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
/* return our own handle to deal with this call */
|
||||
req->handle = h;
|
||||
|
||||
ac = talloc_get_type(h->private_data, struct partition_context);
|
||||
|
||||
return partition_send_all(module, ac, req);
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, we need to find the backend to fire it to */
|
||||
|
||||
/* Find backend */
|
||||
backend = find_backend(module, req, dn);
|
||||
|
||||
/* issue request */
|
||||
return ldb_next_request(backend, req);
|
||||
|
||||
}
|
||||
|
||||
/* search */
|
||||
static int partition_search(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
/* Find backend */
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
/* issue request */
|
||||
|
||||
/* (later) consider if we should be searching multiple
|
||||
* partitions (for 'invisible' partition behaviour */
|
||||
if (ldb_get_opaque(module->ldb, "global_catalog")) {
|
||||
int ret, i;
|
||||
struct ldb_handle *h;
|
||||
struct partition_context *ac;
|
||||
|
||||
h = partition_init_handle(req, module);
|
||||
if (!h) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
/* return our own handle to deal with this call */
|
||||
req->handle = h;
|
||||
|
||||
ac = talloc_get_type(h->private_data, struct partition_context);
|
||||
|
||||
/* Search from the base DN */
|
||||
if (!req->op.search.base || ldb_dn_is_null(req->op.search.base)) {
|
||||
return partition_send_all(module, ac, req);
|
||||
}
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
/* Find all partitions under the search base */
|
||||
if (ldb_dn_compare_base(req->op.search.base, data->partitions[i]->dn) == 0) {
|
||||
ret = partition_send_request(ac, data->partitions[i]->module, data->partitions[i]->dn);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Perhaps we didn't match any partitions. Try the main partition, only */
|
||||
if (ac->num_requests == 0) {
|
||||
talloc_free(h);
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
} else {
|
||||
struct ldb_module *backend = find_backend(module, req, req->op.search.base);
|
||||
|
||||
return ldb_next_request(backend, req);
|
||||
}
|
||||
}
|
||||
|
||||
/* add */
|
||||
static int partition_add(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
return partition_replicate(module, req, req->op.add.message->dn);
|
||||
}
|
||||
|
||||
/* modify */
|
||||
static int partition_modify(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
return partition_replicate(module, req, req->op.mod.message->dn);
|
||||
}
|
||||
|
||||
/* delete */
|
||||
static int partition_delete(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
return partition_replicate(module, req, req->op.del.dn);
|
||||
}
|
||||
|
||||
/* rename */
|
||||
static int partition_rename(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
/* Find backend */
|
||||
struct ldb_module *backend = find_backend(module, req, req->op.rename.olddn);
|
||||
struct ldb_module *backend2 = find_backend(module, req, req->op.rename.newdn);
|
||||
|
||||
if (backend->next != backend2->next) {
|
||||
return LDB_ERR_AFFECTS_MULTIPLE_DSAS;
|
||||
}
|
||||
|
||||
return partition_replicate(module, req, req->op.rename.olddn);
|
||||
}
|
||||
|
||||
/* start a transaction */
|
||||
static int partition_start_trans(struct ldb_module *module)
|
||||
{
|
||||
int i, ret;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
ret = ldb_next_start_trans(module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
|
||||
|
||||
ret = ldb_next_start_trans(next);
|
||||
talloc_free(next);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
/* Back it out, if it fails on one */
|
||||
for (i--; i >= 0; i--) {
|
||||
next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
|
||||
ldb_next_del_trans(next);
|
||||
talloc_free(next);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* end a transaction */
|
||||
static int partition_end_trans(struct ldb_module *module)
|
||||
{
|
||||
int i, ret, ret2 = LDB_SUCCESS;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
ret = ldb_next_end_trans(module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
|
||||
|
||||
ret = ldb_next_end_trans(next);
|
||||
talloc_free(next);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ret2 = ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
/* Back it out, if it fails on one */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
|
||||
ldb_next_del_trans(next);
|
||||
talloc_free(next);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* delete a transaction */
|
||||
static int partition_del_trans(struct ldb_module *module)
|
||||
{
|
||||
int i, ret, ret2 = LDB_SUCCESS;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
ret = ldb_next_del_trans(module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ret2 = ret;
|
||||
}
|
||||
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(module, module->ldb, data->partitions[i]->module);
|
||||
|
||||
ret = ldb_next_del_trans(next);
|
||||
talloc_free(next);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ret2 = ret;
|
||||
}
|
||||
}
|
||||
return ret2;
|
||||
}
|
||||
|
||||
static int partition_sequence_number(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
int i, ret;
|
||||
uint64_t seq_number = 0;
|
||||
uint64_t timestamp_sequence = 0;
|
||||
uint64_t timestamp = 0;
|
||||
struct partition_private_data *data = talloc_get_type(module->private_data,
|
||||
struct partition_private_data);
|
||||
|
||||
switch (req->op.seq_num.type) {
|
||||
case LDB_SEQ_NEXT:
|
||||
case LDB_SEQ_HIGHEST_SEQ:
|
||||
ret = ldb_next_request(module, req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
|
||||
timestamp_sequence = req->op.seq_num.seq_num;
|
||||
} else {
|
||||
seq_number = seq_number + req->op.seq_num.seq_num;
|
||||
}
|
||||
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
|
||||
|
||||
ret = ldb_next_request(next, req);
|
||||
talloc_free(next);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
if (req->op.seq_num.flags & LDB_SEQ_TIMESTAMP_SEQUENCE) {
|
||||
timestamp_sequence = MAX(timestamp_sequence, req->op.seq_num.seq_num);
|
||||
} else {
|
||||
seq_number = seq_number + req->op.seq_num.seq_num;
|
||||
}
|
||||
}
|
||||
/* fall though */
|
||||
case LDB_SEQ_HIGHEST_TIMESTAMP:
|
||||
{
|
||||
struct ldb_request *date_req = talloc(req, struct ldb_request);
|
||||
if (!date_req) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
*date_req = *req;
|
||||
date_req->op.seq_num.flags = LDB_SEQ_HIGHEST_TIMESTAMP;
|
||||
|
||||
ret = ldb_next_request(module, date_req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
timestamp = date_req->op.seq_num.seq_num;
|
||||
|
||||
/* Look at base DN */
|
||||
/* Figure out which partition it is under */
|
||||
/* Skip the lot if 'data' isn't here yet (initialistion) */
|
||||
for (i=0; data && data->partitions && data->partitions[i]; i++) {
|
||||
struct ldb_module *next = make_module_for_next_request(req, module->ldb, data->partitions[i]->module);
|
||||
|
||||
ret = ldb_next_request(next, date_req);
|
||||
talloc_free(next);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
timestamp = MAX(timestamp, date_req->op.seq_num.seq_num);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (req->op.seq_num.flags) {
|
||||
case LDB_SEQ_NEXT:
|
||||
case LDB_SEQ_HIGHEST_SEQ:
|
||||
|
||||
req->op.seq_num.flags = 0;
|
||||
|
||||
/* Has someone above set a timebase sequence? */
|
||||
if (timestamp_sequence) {
|
||||
req->op.seq_num.seq_num = (((unsigned long long)timestamp << 24) | (seq_number & 0xFFFFFF));
|
||||
} else {
|
||||
req->op.seq_num.seq_num = seq_number;
|
||||
}
|
||||
|
||||
if (timestamp_sequence > req->op.seq_num.seq_num) {
|
||||
req->op.seq_num.seq_num = timestamp_sequence;
|
||||
req->op.seq_num.flags |= LDB_SEQ_TIMESTAMP_SEQUENCE;
|
||||
}
|
||||
|
||||
req->op.seq_num.flags |= LDB_SEQ_GLOBAL_SEQUENCE;
|
||||
break;
|
||||
case LDB_SEQ_HIGHEST_TIMESTAMP:
|
||||
req->op.seq_num.seq_num = timestamp;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (req->op.seq_num.flags) {
|
||||
case LDB_SEQ_NEXT:
|
||||
req->op.seq_num.seq_num++;
|
||||
}
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int sort_compare(void *void1,
|
||||
void *void2, void *opaque)
|
||||
{
|
||||
struct partition **pp1 = void1;
|
||||
struct partition **pp2 = void2;
|
||||
struct partition *partition1 = talloc_get_type(*pp1, struct partition);
|
||||
struct partition *partition2 = talloc_get_type(*pp2, struct partition);
|
||||
|
||||
return ldb_dn_compare(partition1->dn, partition2->dn);
|
||||
}
|
||||
|
||||
static int partition_init(struct ldb_module *module)
|
||||
{
|
||||
int ret, i;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(module);
|
||||
static const char *attrs[] = { "partition", "replicateEntries", "modules", NULL };
|
||||
struct ldb_result *res;
|
||||
struct ldb_message *msg;
|
||||
struct ldb_message_element *partition_attributes;
|
||||
struct ldb_message_element *replicate_attributes;
|
||||
struct ldb_message_element *modules_attributes;
|
||||
|
||||
struct partition_private_data *data;
|
||||
|
||||
if (!mem_ctx) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
data = talloc(mem_ctx, struct partition_private_data);
|
||||
if (data == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ret = ldb_search(module->ldb, ldb_dn_new(mem_ctx, module->ldb, "@PARTITION"),
|
||||
LDB_SCOPE_BASE,
|
||||
NULL, attrs,
|
||||
&res);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
talloc_steal(mem_ctx, res);
|
||||
if (res->count == 0) {
|
||||
talloc_free(mem_ctx);
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
if (res->count > 1) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
msg = res->msgs[0];
|
||||
|
||||
partition_attributes = ldb_msg_find_element(msg, "partition");
|
||||
if (!partition_attributes) {
|
||||
ldb_set_errstring(module->ldb, "partition_init: no partitions specified");
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
data->partitions = talloc_array(data, struct partition *, partition_attributes->num_values + 1);
|
||||
if (!data->partitions) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
for (i=0; i < partition_attributes->num_values; i++) {
|
||||
char *base = talloc_strdup(data->partitions, (char *)partition_attributes->values[i].data);
|
||||
char *p = strchr(base, ':');
|
||||
if (!p) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid form for partition record (missing ':'): %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
if (!p[0]) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid form for partition record (missing backend database): %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
data->partitions[i] = talloc(data->partitions, struct partition);
|
||||
if (!data->partitions[i]) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
data->partitions[i]->dn = ldb_dn_new(data->partitions[i], module->ldb, base);
|
||||
if (!data->partitions[i]->dn) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: invalid DN in partition record: %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
data->partitions[i]->backend = private_path(data->partitions[i], p);
|
||||
ret = ldb_connect_backend(module->ldb, data->partitions[i]->backend, NULL, &data->partitions[i]->module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
data->partitions[i] = NULL;
|
||||
|
||||
/* sort these into order, most to least specific */
|
||||
ldb_qsort(data->partitions, partition_attributes->num_values, sizeof(*data->partitions),
|
||||
module->ldb, sort_compare);
|
||||
|
||||
for (i=0; data->partitions[i]; i++) {
|
||||
struct ldb_request *req;
|
||||
req = talloc_zero(mem_ctx, struct ldb_request);
|
||||
if (req == NULL) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Out of memory!\n");
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
req->operation = LDB_REQ_REGISTER_PARTITION;
|
||||
req->op.reg_partition.dn = data->partitions[i]->dn;
|
||||
|
||||
ret = ldb_request(module->ldb, req);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_ERROR, "partition: Unable to register partition with rootdse!\n");
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OTHER;
|
||||
}
|
||||
talloc_free(req);
|
||||
}
|
||||
|
||||
replicate_attributes = ldb_msg_find_element(msg, "replicateEntries");
|
||||
if (!replicate_attributes) {
|
||||
data->replicate = NULL;
|
||||
} else {
|
||||
data->replicate = talloc_array(data, struct ldb_dn *, replicate_attributes->num_values + 1);
|
||||
if (!data->replicate) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
for (i=0; i < replicate_attributes->num_values; i++) {
|
||||
data->replicate[i] = ldb_dn_new(data->replicate, module->ldb, (const char *)replicate_attributes->values[i].data);
|
||||
if (!ldb_dn_validate(data->replicate[i])) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid DN in partition replicate record: %s",
|
||||
replicate_attributes->values[i].data);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
}
|
||||
data->replicate[i] = NULL;
|
||||
}
|
||||
|
||||
/* Make the private data available to any searches the modules may trigger in initialisation */
|
||||
module->private_data = data;
|
||||
talloc_steal(module, data);
|
||||
|
||||
modules_attributes = ldb_msg_find_element(msg, "modules");
|
||||
if (modules_attributes) {
|
||||
for (i=0; i < modules_attributes->num_values; i++) {
|
||||
struct ldb_dn *base_dn;
|
||||
int partition_idx;
|
||||
struct partition *partition = NULL;
|
||||
const char **modules = NULL;
|
||||
|
||||
char *base = talloc_strdup(data->partitions, (char *)modules_attributes->values[i].data);
|
||||
char *p = strchr(base, ':');
|
||||
if (!p) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid form for partition module record (missing ':'): %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
p[0] = '\0';
|
||||
p++;
|
||||
if (!p[0]) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid form for partition module record (missing backend database): %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
modules = ldb_modules_list_from_string(module->ldb, mem_ctx,
|
||||
p);
|
||||
|
||||
base_dn = ldb_dn_new(mem_ctx, module->ldb, base);
|
||||
if (!ldb_dn_validate(base_dn)) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
for (partition_idx = 0; data->partitions[partition_idx]; partition_idx++) {
|
||||
if (ldb_dn_compare(data->partitions[partition_idx]->dn, base_dn) == 0) {
|
||||
partition = data->partitions[partition_idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!partition) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"invalid form for partition module record (no such partition): %s", base);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
ret = ldb_load_modules_list(module->ldb, modules, partition->module, &partition->module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"loading backend for %s failed: %s",
|
||||
base, ldb_errstring(module->ldb));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
ret = ldb_init_module_chain(module->ldb, partition->module);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"partition_init: "
|
||||
"initialising backend for %s failed: %s",
|
||||
base, ldb_errstring(module->ldb));
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static int partition_wait_none(struct ldb_handle *handle) {
|
||||
struct partition_context *ac;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
if (!handle || !handle->private_data) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
if (handle->state == LDB_ASYNC_DONE) {
|
||||
return handle->status;
|
||||
}
|
||||
|
||||
handle->state = LDB_ASYNC_PENDING;
|
||||
handle->status = LDB_SUCCESS;
|
||||
|
||||
ac = talloc_get_type(handle->private_data, struct partition_context);
|
||||
|
||||
for (i=0; i < ac->num_requests; i++) {
|
||||
ret = ldb_wait(ac->down_req[i]->handle, LDB_WAIT_NONE);
|
||||
|
||||
if (ret != LDB_SUCCESS) {
|
||||
handle->status = ret;
|
||||
goto done;
|
||||
}
|
||||
if (ac->down_req[i]->handle->status != LDB_SUCCESS) {
|
||||
handle->status = ac->down_req[i]->handle->status;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ac->down_req[i]->handle->state != LDB_ASYNC_DONE) {
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
ret = LDB_SUCCESS;
|
||||
|
||||
done:
|
||||
handle->state = LDB_ASYNC_DONE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int partition_wait_all(struct ldb_handle *handle) {
|
||||
|
||||
int ret;
|
||||
|
||||
while (handle->state != LDB_ASYNC_DONE) {
|
||||
ret = partition_wait_none(handle);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return handle->status;
|
||||
}
|
||||
|
||||
static int partition_wait(struct ldb_handle *handle, enum ldb_wait_type type)
|
||||
{
|
||||
if (type == LDB_WAIT_ALL) {
|
||||
return partition_wait_all(handle);
|
||||
} else {
|
||||
return partition_wait_none(handle);
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops partition_ops = {
|
||||
.name = "partition",
|
||||
.init_context = partition_init,
|
||||
.search = partition_search,
|
||||
.add = partition_add,
|
||||
.modify = partition_modify,
|
||||
.del = partition_delete,
|
||||
.rename = partition_rename,
|
||||
.start_transaction = partition_start_trans,
|
||||
.end_transaction = partition_end_trans,
|
||||
.del_transaction = partition_del_trans,
|
||||
.sequence_number = partition_sequence_number,
|
||||
.wait = partition_wait
|
||||
};
|
||||
|
||||
int ldb_partition_init(void)
|
||||
{
|
||||
return ldb_register_module(&partition_ops);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,3 @@
|
||||
/* We store these passwords under this base DN: */
|
||||
|
||||
#define LOCAL_BASE "cn=Passwords"
|
||||
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
samdb proxy module
|
||||
|
||||
Copyright (C) Andrew Tridgell 2005
|
||||
|
||||
** NOTE! The following LGPL license applies to the ldb
|
||||
** library. This does NOT imply that all of Samba is released
|
||||
** under the LGPL
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
/*
|
||||
ldb proxy module. At startup this looks for a record like this:
|
||||
|
||||
dn=@PROXYINFO
|
||||
url=destination url
|
||||
olddn = basedn to proxy in upstream server
|
||||
newdn = basedn in local server
|
||||
username = username to connect to upstream
|
||||
password = password for upstream
|
||||
|
||||
NOTE: this module is a complete hack at this stage. I am committing it just
|
||||
so others can know how I am investigating mmc support
|
||||
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "auth/credentials/credentials.h"
|
||||
|
||||
struct proxy_data {
|
||||
struct ldb_context *upstream;
|
||||
struct ldb_dn *olddn;
|
||||
struct ldb_dn *newdn;
|
||||
const char **oldstr;
|
||||
const char **newstr;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
load the @PROXYINFO record
|
||||
*/
|
||||
static int load_proxy_info(struct ldb_module *module)
|
||||
{
|
||||
struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
|
||||
struct ldb_dn *dn;
|
||||
struct ldb_result *res = NULL;
|
||||
int ret;
|
||||
const char *olddn, *newdn, *url, *username, *password, *oldstr, *newstr;
|
||||
struct cli_credentials *creds;
|
||||
|
||||
|
||||
/* see if we have already loaded it */
|
||||
if (proxy->upstream != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
dn = ldb_dn_new(proxy, module->ldb, "@PROXYINFO");
|
||||
if (dn == NULL) {
|
||||
goto failed;
|
||||
}
|
||||
ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, NULL, NULL, &res);
|
||||
talloc_free(dn);
|
||||
if (ret != LDB_SUCCESS || res->count != 1) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Can't find @PROXYINFO\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
url = ldb_msg_find_attr_as_string(res->msgs[0], "url", NULL);
|
||||
olddn = ldb_msg_find_attr_as_string(res->msgs[0], "olddn", NULL);
|
||||
newdn = ldb_msg_find_attr_as_string(res->msgs[0], "newdn", NULL);
|
||||
username = ldb_msg_find_attr_as_string(res->msgs[0], "username", NULL);
|
||||
password = ldb_msg_find_attr_as_string(res->msgs[0], "password", NULL);
|
||||
oldstr = ldb_msg_find_attr_as_string(res->msgs[0], "oldstr", NULL);
|
||||
newstr = ldb_msg_find_attr_as_string(res->msgs[0], "newstr", NULL);
|
||||
|
||||
if (url == NULL || olddn == NULL || newdn == NULL || username == NULL || password == NULL) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Need url, olddn, newdn, oldstr, newstr, username and password in @PROXYINFO\n");
|
||||
goto failed;
|
||||
}
|
||||
|
||||
proxy->olddn = ldb_dn_new(proxy, module->ldb, olddn);
|
||||
if (proxy->olddn == NULL) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode olddn '%s'\n", olddn);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
proxy->newdn = ldb_dn_new(proxy, module->ldb, newdn);
|
||||
if (proxy->newdn == NULL) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "Failed to explode newdn '%s'\n", newdn);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
proxy->upstream = ldb_init(proxy);
|
||||
if (proxy->upstream == NULL) {
|
||||
ldb_oom(module->ldb);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
proxy->oldstr = str_list_make(proxy, oldstr, ", ");
|
||||
if (proxy->oldstr == NULL) {
|
||||
ldb_oom(module->ldb);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
proxy->newstr = str_list_make(proxy, newstr, ", ");
|
||||
if (proxy->newstr == NULL) {
|
||||
ldb_oom(module->ldb);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
/* setup credentials for connection */
|
||||
creds = cli_credentials_init(proxy->upstream);
|
||||
if (creds == NULL) {
|
||||
ldb_oom(module->ldb);
|
||||
goto failed;
|
||||
}
|
||||
cli_credentials_guess(creds);
|
||||
cli_credentials_set_username(creds, username, CRED_SPECIFIED);
|
||||
cli_credentials_set_password(creds, password, CRED_SPECIFIED);
|
||||
|
||||
ldb_set_opaque(proxy->upstream, "credentials", creds);
|
||||
|
||||
ret = ldb_connect(proxy->upstream, url, 0, NULL);
|
||||
if (ret != 0) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxy failed to connect to %s\n", url);
|
||||
goto failed;
|
||||
}
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy connected to %s\n", url);
|
||||
|
||||
talloc_free(res);
|
||||
|
||||
return 0;
|
||||
|
||||
failed:
|
||||
talloc_free(res);
|
||||
talloc_free(proxy->olddn);
|
||||
talloc_free(proxy->newdn);
|
||||
talloc_free(proxy->upstream);
|
||||
proxy->upstream = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
convert a binary blob
|
||||
*/
|
||||
static void proxy_convert_blob(TALLOC_CTX *mem_ctx, struct ldb_val *v,
|
||||
const char *oldstr, const char *newstr)
|
||||
{
|
||||
int len1, len2, len3;
|
||||
uint8_t *olddata = v->data;
|
||||
char *p = strcasestr((char *)v->data, oldstr);
|
||||
|
||||
len1 = (p - (char *)v->data);
|
||||
len2 = strlen(newstr);
|
||||
len3 = v->length - (p+strlen(oldstr) - (char *)v->data);
|
||||
v->length = len1+len2+len3;
|
||||
v->data = talloc_size(mem_ctx, v->length);
|
||||
memcpy(v->data, olddata, len1);
|
||||
memcpy(v->data+len1, newstr, len2);
|
||||
memcpy(v->data+len1+len2, olddata + len1 + strlen(oldstr), len3);
|
||||
}
|
||||
|
||||
/*
|
||||
convert a returned value
|
||||
*/
|
||||
static void proxy_convert_value(struct ldb_module *module, struct ldb_message *msg, struct ldb_val *v)
|
||||
{
|
||||
struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
|
||||
int i;
|
||||
for (i=0;proxy->oldstr[i];i++) {
|
||||
char *p = strcasestr((char *)v->data, proxy->oldstr[i]);
|
||||
if (p == NULL) continue;
|
||||
proxy_convert_blob(msg, v, proxy->oldstr[i], proxy->newstr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
convert a returned value
|
||||
*/
|
||||
static struct ldb_parse_tree *proxy_convert_tree(struct ldb_module *module,
|
||||
struct ldb_parse_tree *tree)
|
||||
{
|
||||
struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
|
||||
int i;
|
||||
char *expression = ldb_filter_from_tree(module, tree);
|
||||
for (i=0;proxy->newstr[i];i++) {
|
||||
struct ldb_val v;
|
||||
char *p = strcasestr(expression, proxy->newstr[i]);
|
||||
if (p == NULL) continue;
|
||||
v.data = (uint8_t *)expression;
|
||||
v.length = strlen(expression)+1;
|
||||
proxy_convert_blob(module, &v, proxy->newstr[i], proxy->oldstr[i]);
|
||||
return ldb_parse_tree(module, (const char *)v.data);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
convert a returned record
|
||||
*/
|
||||
static void proxy_convert_record(struct ldb_module *module, struct ldb_message *msg)
|
||||
{
|
||||
struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
|
||||
int attr, v;
|
||||
|
||||
/* fix the message DN */
|
||||
if (ldb_dn_compare_base(module->ldb, proxy->olddn, msg->dn) == 0) {
|
||||
ldb_dn_remove_base_components(msg->dn, ldb_dn_get_comp_num(proxy->olddn));
|
||||
ldb_dn_add_base(msg->dn, proxy->newdn);
|
||||
}
|
||||
|
||||
/* fix any attributes */
|
||||
for (attr=0;attr<msg->num_elements;attr++) {
|
||||
for (v=0;v<msg->elements[attr].num_values;v++) {
|
||||
proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
|
||||
}
|
||||
}
|
||||
|
||||
/* fix any DN components */
|
||||
for (attr=0;attr<msg->num_elements;attr++) {
|
||||
for (v=0;v<msg->elements[attr].num_values;v++) {
|
||||
proxy_convert_value(module, msg, &msg->elements[attr].values[v]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* search */
|
||||
static int proxy_search_bytree(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct proxy_data *proxy = talloc_get_type(module->private_data, struct proxy_data);
|
||||
struct ldb_request *newreq;
|
||||
struct ldb_dn *base;
|
||||
int ret, i;
|
||||
|
||||
if (req->op.search.base == NULL ||
|
||||
(req->op.search.base->comp_num == 1 &&
|
||||
req->op.search.base->components[0].name[0] == '@')) {
|
||||
goto passthru;
|
||||
}
|
||||
|
||||
if (load_proxy_info(module) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* see if the dn is within olddn */
|
||||
if (ldb_dn_compare_base(module->ldb, proxy->newdn, req->op.search.base) != 0) {
|
||||
goto passthru;
|
||||
}
|
||||
|
||||
newreq = talloc(module, struct ldb_request);
|
||||
if (newreq == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
newreq->op.search.tree = proxy_convert_tree(module, req->op.search.tree);
|
||||
|
||||
/* convert the basedn of this search */
|
||||
base = ldb_dn_copy(proxy, req->op.search.base);
|
||||
if (base == NULL) {
|
||||
talloc_free(newreq);
|
||||
goto failed;
|
||||
}
|
||||
ldb_dn_remove_base_components(base, ldb_dn_get_comp_num(proxy->newdn));
|
||||
ldb_dn_add_base(base, proxy->olddn);
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "proxying: '%s' with dn '%s' \n",
|
||||
ldb_filter_from_tree(proxy, newreq->op.search.tree), ldb_dn_get_linearized(newreq->op.search.base));
|
||||
for (i = 0; req->op.search.attrs && req->op.search.attrs[i]; i++) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "attr: '%s'\n", req->op.search.attrs[i]);
|
||||
}
|
||||
|
||||
newreq->op.search.base = base;
|
||||
newreq->op.search.scope = req->op.search.scope;
|
||||
newreq->op.search.attrs = req->op.search.attrs;
|
||||
newreq->op.search.res = req->op.search.res;
|
||||
newreq->controls = req->controls;
|
||||
ret = ldb_request(proxy->upstream, newreq);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
ldb_set_errstring(module->ldb, ldb_errstring(proxy->upstream));
|
||||
talloc_free(newreq);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < newreq->op.search.res->count; i++) {
|
||||
printf("# record %d\n", i+1);
|
||||
|
||||
proxy_convert_record(module, newreq->op.search.res->msgs[i]);
|
||||
}
|
||||
|
||||
talloc_free(newreq);
|
||||
return ret;
|
||||
|
||||
failed:
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "proxy failed for %s\n",
|
||||
ldb_dn_get_linearized(req->op.search.base));
|
||||
|
||||
passthru:
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
static int proxy_request(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
switch (req->operation) {
|
||||
|
||||
case LDB_REQ_SEARCH:
|
||||
return proxy_search_bytree(module, req);
|
||||
|
||||
default:
|
||||
return ldb_next_request(module, req);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops proxy_ops = {
|
||||
.name = "proxy",
|
||||
.request = proxy_request
|
||||
};
|
||||
|
||||
int proxy_module_init(void)
|
||||
{
|
||||
return ldb_register_module(&proxy_ops);
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
rootDSE ldb module
|
||||
|
||||
Copyright (C) Andrew Tridgell 2005
|
||||
Copyright (C) Simo Sorce 2005
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 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 "lib/ldb/include/ldb.h"
|
||||
#include "lib/ldb/include/ldb_errors.h"
|
||||
#include "lib/ldb/include/ldb_private.h"
|
||||
#include "system/time.h"
|
||||
|
||||
struct private_data {
|
||||
int num_controls;
|
||||
char **controls;
|
||||
int num_partitions;
|
||||
struct ldb_dn **partitions;
|
||||
};
|
||||
|
||||
/*
|
||||
return 1 if a specific attribute has been requested
|
||||
*/
|
||||
static int do_attribute(const char * const *attrs, const char *name)
|
||||
{
|
||||
return attrs == NULL ||
|
||||
ldb_attr_in_list(attrs, name) ||
|
||||
ldb_attr_in_list(attrs, "*");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
add dynamically generated attributes to rootDSE result
|
||||
*/
|
||||
static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_message *msg, const char * const *attrs)
|
||||
{
|
||||
struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
|
||||
char **server_sasl;
|
||||
|
||||
msg->dn = ldb_dn_new(msg, module->ldb, NULL);
|
||||
|
||||
/* don't return the distinduishedName, cn and name attributes */
|
||||
ldb_msg_remove_attr(msg, "distinguishedName");
|
||||
ldb_msg_remove_attr(msg, "cn");
|
||||
ldb_msg_remove_attr(msg, "name");
|
||||
|
||||
if (do_attribute(attrs, "currentTime")) {
|
||||
if (ldb_msg_add_steal_string(msg, "currentTime",
|
||||
ldb_timestring(msg, time(NULL))) != 0) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_attribute(attrs, "supportedControl")) {
|
||||
int i;
|
||||
for (i = 0; i < priv->num_controls; i++) {
|
||||
char *control = talloc_strdup(msg, priv->controls[i]);
|
||||
if (!control) {
|
||||
goto failed;
|
||||
}
|
||||
if (ldb_msg_add_steal_string(msg, "supportedControl",
|
||||
control) != 0) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (do_attribute(attrs, "namingContexts")) {
|
||||
int i;
|
||||
for (i = 0; i < priv->num_partitions; i++) {
|
||||
struct ldb_dn *dn = priv->partitions[i];
|
||||
if (ldb_msg_add_steal_string(msg, "namingContexts",
|
||||
ldb_dn_alloc_linearized(msg, dn)) != 0) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
server_sasl = talloc_get_type(ldb_get_opaque(module->ldb, "supportedSASLMechanims"),
|
||||
char *);
|
||||
if (server_sasl && do_attribute(attrs, "supportedSASLMechanisms")) {
|
||||
int i;
|
||||
for (i = 0; server_sasl && server_sasl[i]; i++) {
|
||||
char *sasl_name = talloc_strdup(msg, server_sasl[i]);
|
||||
if (!sasl_name) {
|
||||
goto failed;
|
||||
}
|
||||
if (ldb_msg_add_steal_string(msg, "supportedSASLMechanisms",
|
||||
sasl_name) != 0) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (do_attribute(attrs, "highestCommittedUSN")) {
|
||||
uint64_t seq_num;
|
||||
int ret = ldb_sequence_number(module->ldb, LDB_SEQ_HIGHEST_SEQ, &seq_num);
|
||||
if (ret == LDB_SUCCESS) {
|
||||
if (ldb_msg_add_fmt(msg, "highestCommittedUSN",
|
||||
"%llu", (unsigned long long)seq_num) != 0) {
|
||||
goto failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: lots more dynamic attributes should be added here */
|
||||
|
||||
return LDB_SUCCESS;
|
||||
|
||||
failed:
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
handle search requests
|
||||
*/
|
||||
|
||||
struct rootdse_context {
|
||||
struct ldb_module *module;
|
||||
void *up_context;
|
||||
int (*up_callback)(struct ldb_context *, void *, struct ldb_reply *);
|
||||
|
||||
const char * const * attrs;
|
||||
};
|
||||
|
||||
static int rootdse_callback(struct ldb_context *ldb, void *context, struct ldb_reply *ares)
|
||||
{
|
||||
struct rootdse_context *ac;
|
||||
|
||||
if (!context || !ares) {
|
||||
ldb_set_errstring(ldb, "NULL Context or Result in callback");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ac = talloc_get_type(context, struct rootdse_context);
|
||||
|
||||
if (ares->type == LDB_REPLY_ENTRY) {
|
||||
/*
|
||||
* if the client explicit asks for the 'netlogon' attribute
|
||||
* the reply_entry needs to be skipped
|
||||
*/
|
||||
if (ac->attrs && ldb_attr_in_list(ac->attrs, "netlogon")) {
|
||||
talloc_free(ares);
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* for each record returned post-process to add any dynamic
|
||||
attributes that have been asked for */
|
||||
if (rootdse_add_dynamic(ac->module, ares->message, ac->attrs) != LDB_SUCCESS) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
return ac->up_callback(ldb, ac->up_context, ares);
|
||||
|
||||
error:
|
||||
talloc_free(ares);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
static int rootdse_search(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct rootdse_context *ac;
|
||||
struct ldb_request *down_req;
|
||||
int ret;
|
||||
|
||||
/* see if its for the rootDSE */
|
||||
if (req->op.search.scope != LDB_SCOPE_BASE ||
|
||||
( ! ldb_dn_is_null(req->op.search.base))) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
ac = talloc(req, struct rootdse_context);
|
||||
if (ac == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ac->module = module;
|
||||
ac->up_context = req->context;
|
||||
ac->up_callback = req->callback;
|
||||
ac->attrs = req->op.search.attrs;
|
||||
|
||||
down_req = talloc_zero(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
down_req->operation = req->operation;
|
||||
/* in our db we store the rootDSE with a DN of cn=rootDSE */
|
||||
down_req->op.search.base = ldb_dn_new(down_req, module->ldb, "cn=rootDSE");
|
||||
down_req->op.search.scope = LDB_SCOPE_BASE;
|
||||
down_req->op.search.tree = ldb_parse_tree(down_req, NULL);
|
||||
if (down_req->op.search.base == NULL || down_req->op.search.tree == NULL) {
|
||||
ldb_oom(module->ldb);
|
||||
talloc_free(down_req);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
down_req->op.search.attrs = req->op.search.attrs;
|
||||
down_req->controls = req->controls;
|
||||
|
||||
down_req->context = ac;
|
||||
down_req->callback = rootdse_callback;
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* perform the search */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
|
||||
char **list;
|
||||
|
||||
list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1);
|
||||
if (!list) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
list[priv->num_controls] = talloc_strdup(list, req->op.reg_control.oid);
|
||||
if (!list[priv->num_controls]) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
priv->num_controls += 1;
|
||||
priv->controls = list;
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int rootdse_register_partition(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
|
||||
struct ldb_dn **list;
|
||||
|
||||
list = talloc_realloc(priv, priv->partitions, struct ldb_dn *, priv->num_partitions + 1);
|
||||
if (!list) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
list[priv->num_partitions] = ldb_dn_copy(list, req->op.reg_partition.dn);
|
||||
if (!list[priv->num_partitions]) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
priv->num_partitions += 1;
|
||||
priv->partitions = list;
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static int rootdse_request(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
switch (req->operation) {
|
||||
|
||||
case LDB_REQ_REGISTER_CONTROL:
|
||||
return rootdse_register_control(module, req);
|
||||
case LDB_REQ_REGISTER_PARTITION:
|
||||
return rootdse_register_partition(module, req);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
static int rootdse_init(struct ldb_module *module)
|
||||
{
|
||||
struct private_data *data;
|
||||
|
||||
data = talloc(module, struct private_data);
|
||||
if (data == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
data->num_controls = 0;
|
||||
data->controls = NULL;
|
||||
data->num_partitions = 0;
|
||||
data->partitions = NULL;
|
||||
module->private_data = data;
|
||||
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops rootdse_ops = {
|
||||
.name = "rootdse",
|
||||
.init_context = rootdse_init,
|
||||
.search = rootdse_search,
|
||||
.request = rootdse_request
|
||||
};
|
||||
|
||||
int rootdse_module_init(void)
|
||||
{
|
||||
return ldb_register_module(&rootdse_ops);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,897 @@
|
||||
/*
|
||||
ldb database library - Samba3 SAM compatibility backend
|
||||
|
||||
Copyright (C) Jelmer Vernooij 2005
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_private.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "ldb/modules/ldb_map.h"
|
||||
#include "system/passwd.h"
|
||||
|
||||
#include "librpc/gen_ndr/ndr_security.h"
|
||||
#include "librpc/ndr/libndr.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "libcli/security/proto.h"
|
||||
|
||||
/*
|
||||
* sambaSID -> member (dn!)
|
||||
* sambaSIDList -> member (dn!)
|
||||
* sambaDomainName -> name
|
||||
* sambaTrustPassword
|
||||
* sambaUnixIdPool
|
||||
* sambaIdmapEntry
|
||||
* sambaAccountPolicy
|
||||
* sambaSidEntry
|
||||
* sambaAcctFlags -> systemFlags ?
|
||||
* sambaPasswordHistory -> ntPwdHistory*/
|
||||
|
||||
/* Not necessary:
|
||||
* sambaConfig
|
||||
* sambaShare
|
||||
* sambaConfigOption
|
||||
* sambaNextGroupRid
|
||||
* sambaNextUserRid
|
||||
* sambaAlgorithmicRidBase
|
||||
*/
|
||||
|
||||
/* Not in Samba4:
|
||||
* sambaKickoffTime
|
||||
* sambaPwdCanChange
|
||||
* sambaPwdMustChange
|
||||
* sambaHomePath
|
||||
* sambaHomeDrive
|
||||
* sambaLogonScript
|
||||
* sambaProfilePath
|
||||
* sambaUserWorkstations
|
||||
* sambaMungedDial
|
||||
* sambaLogonHours */
|
||||
|
||||
/* In Samba4 but not in Samba3:
|
||||
*/
|
||||
|
||||
/* From a sambaPrimaryGroupSID, generate a primaryGroupID (integer) attribute */
|
||||
static struct ldb_message_element *generate_primaryGroupID(struct ldb_module *module, TALLOC_CTX *ctx, const char *local_attr, const struct ldb_message *remote)
|
||||
{
|
||||
struct ldb_message_element *el;
|
||||
const char *sid = ldb_msg_find_attr_as_string(remote, "sambaPrimaryGroupSID", NULL);
|
||||
const char *p;
|
||||
|
||||
if (!sid)
|
||||
return NULL;
|
||||
|
||||
p = strrchr(sid, '-');
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
el = talloc_zero(ctx, struct ldb_message_element);
|
||||
el->name = talloc_strdup(ctx, "primaryGroupID");
|
||||
el->num_values = 1;
|
||||
el->values = talloc_array(ctx, struct ldb_val, 1);
|
||||
el->values[0].data = (uint8_t *)talloc_strdup(el->values, p+1);
|
||||
el->values[0].length = strlen((char *)el->values[0].data);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
static void generate_sambaPrimaryGroupSID(struct ldb_module *module, const char *local_attr, const struct ldb_message *local, struct ldb_message *remote_mp, struct ldb_message *remote_fb)
|
||||
{
|
||||
const struct ldb_val *sidval;
|
||||
char *sidstring;
|
||||
struct dom_sid *sid;
|
||||
NTSTATUS status;
|
||||
|
||||
/* We need the domain, so we get it from the objectSid that we hope is here... */
|
||||
sidval = ldb_msg_find_ldb_val(local, "objectSid");
|
||||
|
||||
if (!sidval)
|
||||
return; /* Sorry, no SID today.. */
|
||||
|
||||
sid = talloc(remote_mp, struct dom_sid);
|
||||
if (sid == NULL) {
|
||||
return;
|
||||
}
|
||||
status = ndr_pull_struct_blob(sidval, sid, sid, (ndr_pull_flags_fn_t)ndr_pull_dom_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(sid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ldb_msg_find_ldb_val(local, "primaryGroupID"))
|
||||
return; /* Sorry, no SID today.. */
|
||||
|
||||
sid->num_auths--;
|
||||
|
||||
sidstring = dom_sid_string(remote_mp, sid);
|
||||
talloc_free(sid);
|
||||
ldb_msg_add_fmt(remote_mp, "sambaPrimaryGroupSID", "%s-%d", sidstring, ldb_msg_find_attr_as_uint(local, "primaryGroupID", 0));
|
||||
talloc_free(sidstring);
|
||||
}
|
||||
|
||||
static struct ldb_val convert_uid_samaccount(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
return ldb_val_dup(ctx, val);
|
||||
}
|
||||
|
||||
static struct ldb_val lookup_homedir(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
struct ldb_val retval;
|
||||
|
||||
pwd = getpwnam((char *)val->data);
|
||||
|
||||
if (!pwd) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_WARNING, "Unable to lookup '%s' in passwd", (char *)val->data);
|
||||
return *talloc_zero(ctx, struct ldb_val);
|
||||
}
|
||||
|
||||
retval.data = (uint8_t *)talloc_strdup(ctx, pwd->pw_dir);
|
||||
retval.length = strlen((char *)retval.data);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct ldb_val lookup_gid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
struct ldb_val retval;
|
||||
|
||||
pwd = getpwnam((char *)val->data);
|
||||
|
||||
if (!pwd) {
|
||||
return *talloc_zero(ctx, struct ldb_val);
|
||||
}
|
||||
|
||||
retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_gid);
|
||||
retval.length = strlen((char *)retval.data);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct ldb_val lookup_uid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct passwd *pwd;
|
||||
struct ldb_val retval;
|
||||
|
||||
pwd = getpwnam((char *)val->data);
|
||||
|
||||
if (!pwd) {
|
||||
return *talloc_zero(ctx, struct ldb_val);
|
||||
}
|
||||
|
||||
retval.data = (uint8_t *)talloc_asprintf(ctx, "%ld", (unsigned long)pwd->pw_uid);
|
||||
retval.length = strlen((char *)retval.data);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static struct ldb_val encode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct dom_sid *sid = dom_sid_parse_talloc(ctx, (char *)val->data);
|
||||
struct ldb_val *out = talloc_zero(ctx, struct ldb_val);
|
||||
NTSTATUS status;
|
||||
|
||||
if (sid == NULL) {
|
||||
return *out;
|
||||
}
|
||||
status = ndr_push_struct_blob(out, ctx, sid,
|
||||
(ndr_push_flags_fn_t)ndr_push_dom_sid);
|
||||
talloc_free(sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return *out;
|
||||
}
|
||||
|
||||
return *out;
|
||||
}
|
||||
|
||||
static struct ldb_val decode_sid(struct ldb_module *module, TALLOC_CTX *ctx, const struct ldb_val *val)
|
||||
{
|
||||
struct dom_sid *sid;
|
||||
NTSTATUS status;
|
||||
struct ldb_val *out = talloc_zero(ctx, struct ldb_val);
|
||||
|
||||
sid = talloc(ctx, struct dom_sid);
|
||||
if (sid == NULL) {
|
||||
return *out;
|
||||
}
|
||||
status = ndr_pull_struct_blob(val, sid, sid,
|
||||
(ndr_pull_flags_fn_t)ndr_pull_dom_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(sid);
|
||||
return *out;
|
||||
}
|
||||
out->data = (uint8_t *)dom_sid_string(ctx, sid);
|
||||
talloc_free(sid);
|
||||
if (out->data == NULL) {
|
||||
return *out;
|
||||
}
|
||||
out->length = strlen((const char *)out->data);
|
||||
|
||||
return *out;
|
||||
}
|
||||
|
||||
const struct ldb_map_objectclass samba3_objectclasses[] = {
|
||||
{
|
||||
.local_name = "user",
|
||||
.remote_name = "posixAccount",
|
||||
.base_classes = { "top", NULL },
|
||||
.musts = { "cn", "uid", "uidNumber", "gidNumber", "homeDirectory", NULL },
|
||||
.mays = { "userPassword", "loginShell", "gecos", "description", NULL },
|
||||
},
|
||||
{
|
||||
.local_name = "group",
|
||||
.remote_name = "posixGroup",
|
||||
.base_classes = { "top", NULL },
|
||||
.musts = { "cn", "gidNumber", NULL },
|
||||
.mays = { "userPassword", "memberUid", "description", NULL },
|
||||
},
|
||||
{
|
||||
.local_name = "group",
|
||||
.remote_name = "sambaGroupMapping",
|
||||
.base_classes = { "top", "posixGroup", NULL },
|
||||
.musts = { "gidNumber", "sambaSID", "sambaGroupType", NULL },
|
||||
.mays = { "displayName", "description", "sambaSIDList", NULL },
|
||||
},
|
||||
{
|
||||
.local_name = "user",
|
||||
.remote_name = "sambaSAMAccount",
|
||||
.base_classes = { "top", "posixAccount", NULL },
|
||||
.musts = { "uid", "sambaSID", NULL },
|
||||
.mays = { "cn", "sambaLMPassword", "sambaNTPassword",
|
||||
"sambaPwdLastSet", "sambaLogonTime", "sambaLogoffTime",
|
||||
"sambaKickoffTime", "sambaPwdCanChange", "sambaPwdMustChange",
|
||||
"sambaAcctFlags", "displayName", "sambaHomePath", "sambaHomeDrive",
|
||||
"sambaLogonScript", "sambaProfilePath", "description", "sambaUserWorkstations",
|
||||
"sambaPrimaryGroupSID", "sambaDomainName", "sambaMungedDial",
|
||||
"sambaBadPasswordCount", "sambaBadPasswordTime",
|
||||
"sambaPasswordHistory", "sambaLogonHours", NULL }
|
||||
|
||||
},
|
||||
{
|
||||
.local_name = "domain",
|
||||
.remote_name = "sambaDomain",
|
||||
.base_classes = { "top", NULL },
|
||||
.musts = { "sambaDomainName", "sambaSID", NULL },
|
||||
.mays = { "sambaNextRid", "sambaNextGroupRid", "sambaNextUserRid", "sambaAlgorithmicRidBase", NULL },
|
||||
},
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
const struct ldb_map_attribute samba3_attributes[] =
|
||||
{
|
||||
/* sambaNextRid -> nextRid */
|
||||
{
|
||||
.local_name = "nextRid",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaNextRid",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaBadPasswordTime -> badPasswordtime*/
|
||||
{
|
||||
.local_name = "badPasswordTime",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaBadPasswordTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaLMPassword -> lmPwdHash*/
|
||||
{
|
||||
.local_name = "lmPwdHash",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaLMPassword",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaGroupType -> groupType */
|
||||
{
|
||||
.local_name = "groupType",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaGroupType",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaNTPassword -> ntPwdHash*/
|
||||
{
|
||||
.local_name = "ntPwdHash",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaNTPassword",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaPrimaryGroupSID -> primaryGroupID */
|
||||
{
|
||||
.local_name = "primaryGroupID",
|
||||
.type = MAP_GENERATE,
|
||||
.u = {
|
||||
.generate = {
|
||||
.remote_names = { "sambaPrimaryGroupSID", NULL },
|
||||
.generate_local = generate_primaryGroupID,
|
||||
.generate_remote = generate_sambaPrimaryGroupSID,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaBadPasswordCount -> badPwdCount */
|
||||
{
|
||||
.local_name = "badPwdCount",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaBadPasswordCount",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaLogonTime -> lastLogon*/
|
||||
{
|
||||
.local_name = "lastLogon",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaLogonTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaLogoffTime -> lastLogoff*/
|
||||
{
|
||||
.local_name = "lastLogoff",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaLogoffTime",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* uid -> unixName */
|
||||
{
|
||||
.local_name = "unixName",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "uid",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* displayName -> name */
|
||||
{
|
||||
.local_name = "name",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "displayName",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* cn */
|
||||
{
|
||||
.local_name = "cn",
|
||||
.type = MAP_KEEP,
|
||||
},
|
||||
|
||||
/* sAMAccountName -> cn */
|
||||
{
|
||||
.local_name = "sAMAccountName",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "uid",
|
||||
.convert_remote = convert_uid_samaccount,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* objectCategory */
|
||||
{
|
||||
.local_name = "objectCategory",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* objectGUID */
|
||||
{
|
||||
.local_name = "objectGUID",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* objectVersion */
|
||||
{
|
||||
.local_name = "objectVersion",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* codePage */
|
||||
{
|
||||
.local_name = "codePage",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* dNSHostName */
|
||||
{
|
||||
.local_name = "dNSHostName",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
|
||||
/* dnsDomain */
|
||||
{
|
||||
.local_name = "dnsDomain",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* dnsRoot */
|
||||
{
|
||||
.local_name = "dnsRoot",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* countryCode */
|
||||
{
|
||||
.local_name = "countryCode",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* nTMixedDomain */
|
||||
{
|
||||
.local_name = "nTMixedDomain",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* operatingSystem */
|
||||
{
|
||||
.local_name = "operatingSystem",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* operatingSystemVersion */
|
||||
{
|
||||
.local_name = "operatingSystemVersion",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
|
||||
/* servicePrincipalName */
|
||||
{
|
||||
.local_name = "servicePrincipalName",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* msDS-Behavior-Version */
|
||||
{
|
||||
.local_name = "msDS-Behavior-Version",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* msDS-KeyVersionNumber */
|
||||
{
|
||||
.local_name = "msDS-KeyVersionNumber",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* msDs-masteredBy */
|
||||
{
|
||||
.local_name = "msDs-masteredBy",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* ou */
|
||||
{
|
||||
.local_name = "ou",
|
||||
.type = MAP_KEEP,
|
||||
},
|
||||
|
||||
/* dc */
|
||||
{
|
||||
.local_name = "dc",
|
||||
.type = MAP_KEEP,
|
||||
},
|
||||
|
||||
/* description */
|
||||
{
|
||||
.local_name = "description",
|
||||
.type = MAP_KEEP,
|
||||
},
|
||||
|
||||
/* sambaSID -> objectSid*/
|
||||
{
|
||||
.local_name = "objectSid",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "sambaSID",
|
||||
.convert_local = decode_sid,
|
||||
.convert_remote = encode_sid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* sambaPwdLastSet -> pwdLastSet */
|
||||
{
|
||||
.local_name = "pwdLastSet",
|
||||
.type = MAP_RENAME,
|
||||
.u = {
|
||||
.rename = {
|
||||
.remote_name = "sambaPwdLastSet",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* accountExpires */
|
||||
{
|
||||
.local_name = "accountExpires",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* adminCount */
|
||||
{
|
||||
.local_name = "adminCount",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* canonicalName */
|
||||
{
|
||||
.local_name = "canonicalName",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* createTimestamp */
|
||||
{
|
||||
.local_name = "createTimestamp",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* creationTime */
|
||||
{
|
||||
.local_name = "creationTime",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* dMDLocation */
|
||||
{
|
||||
.local_name = "dMDLocation",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* fSMORoleOwner */
|
||||
{
|
||||
.local_name = "fSMORoleOwner",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* forceLogoff */
|
||||
{
|
||||
.local_name = "forceLogoff",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* instanceType */
|
||||
{
|
||||
.local_name = "instanceType",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* invocationId */
|
||||
{
|
||||
.local_name = "invocationId",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* isCriticalSystemObject */
|
||||
{
|
||||
.local_name = "isCriticalSystemObject",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* localPolicyFlags */
|
||||
{
|
||||
.local_name = "localPolicyFlags",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* lockOutObservationWindow */
|
||||
{
|
||||
.local_name = "lockOutObservationWindow",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* lockoutDuration */
|
||||
{
|
||||
.local_name = "lockoutDuration",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* lockoutThreshold */
|
||||
{
|
||||
.local_name = "lockoutThreshold",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* logonCount */
|
||||
{
|
||||
.local_name = "logonCount",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* masteredBy */
|
||||
{
|
||||
.local_name = "masteredBy",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* maxPwdAge */
|
||||
{
|
||||
.local_name = "maxPwdAge",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* member */
|
||||
{
|
||||
.local_name = "member",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* memberOf */
|
||||
{
|
||||
.local_name = "memberOf",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* minPwdAge */
|
||||
{
|
||||
.local_name = "minPwdAge",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* minPwdLength */
|
||||
{
|
||||
.local_name = "minPwdLength",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* modifiedCount */
|
||||
{
|
||||
.local_name = "modifiedCount",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* modifiedCountAtLastProm */
|
||||
{
|
||||
.local_name = "modifiedCountAtLastProm",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* modifyTimestamp */
|
||||
{
|
||||
.local_name = "modifyTimestamp",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* nCName */
|
||||
{
|
||||
.local_name = "nCName",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* nETBIOSName */
|
||||
{
|
||||
.local_name = "nETBIOSName",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* oEMInformation */
|
||||
{
|
||||
.local_name = "oEMInformation",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* privilege */
|
||||
{
|
||||
.local_name = "privilege",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* pwdHistoryLength */
|
||||
{
|
||||
.local_name = "pwdHistoryLength",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* pwdProperties */
|
||||
{
|
||||
.local_name = "pwdProperties",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* rIDAvailablePool */
|
||||
{
|
||||
.local_name = "rIDAvailablePool",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* revision */
|
||||
{
|
||||
.local_name = "revision",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* ridManagerReference */
|
||||
{
|
||||
.local_name = "ridManagerReference",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* sAMAccountType */
|
||||
{
|
||||
.local_name = "sAMAccountType",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* sPNMappings */
|
||||
{
|
||||
.local_name = "sPNMappings",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* serverReference */
|
||||
{
|
||||
.local_name = "serverReference",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* serverState */
|
||||
{
|
||||
.local_name = "serverState",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* showInAdvancedViewOnly */
|
||||
{
|
||||
.local_name = "showInAdvancedViewOnly",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* subRefs */
|
||||
{
|
||||
.local_name = "subRefs",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* systemFlags */
|
||||
{
|
||||
.local_name = "systemFlags",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* uASCompat */
|
||||
{
|
||||
.local_name = "uASCompat",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* uSNChanged */
|
||||
{
|
||||
.local_name = "uSNChanged",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* uSNCreated */
|
||||
{
|
||||
.local_name = "uSNCreated",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* sambaPassword */
|
||||
{
|
||||
.local_name = "sambaPassword",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* userAccountControl */
|
||||
{
|
||||
.local_name = "userAccountControl",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* whenChanged */
|
||||
{
|
||||
.local_name = "whenChanged",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* whenCreated */
|
||||
{
|
||||
.local_name = "whenCreated",
|
||||
.type = MAP_IGNORE,
|
||||
},
|
||||
|
||||
/* uidNumber */
|
||||
{
|
||||
.local_name = "unixName",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "uidNumber",
|
||||
.convert_local = lookup_uid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* gidNumber. Perhaps make into generate so we can distinguish between
|
||||
* groups and accounts? */
|
||||
{
|
||||
.local_name = "unixName",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "gidNumber",
|
||||
.convert_local = lookup_gid,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
/* homeDirectory */
|
||||
{
|
||||
.local_name = "unixName",
|
||||
.type = MAP_CONVERT,
|
||||
.u = {
|
||||
.convert = {
|
||||
.remote_name = "homeDirectory",
|
||||
.convert_local = lookup_homedir,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
.local_name = NULL,
|
||||
}
|
||||
};
|
||||
|
||||
/* the context init function */
|
||||
static int samba3sam_init(struct ldb_module *module)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = ldb_map_init(module, samba3_attributes, samba3_objectclasses, NULL, "samba3sam");
|
||||
if (ret != LDB_SUCCESS)
|
||||
return ret;
|
||||
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static struct ldb_module_ops samba3sam_ops = {
|
||||
.name = "samba3sam",
|
||||
.init_context = samba3sam_init,
|
||||
};
|
||||
|
||||
/* the init function */
|
||||
int ldb_samba3sam_module_init(void)
|
||||
{
|
||||
struct ldb_module_ops ops = ldb_map_get_ops();
|
||||
samba3sam_ops.add = ops.add;
|
||||
samba3sam_ops.modify = ops.modify;
|
||||
samba3sam_ops.del = ops.del;
|
||||
samba3sam_ops.rename = ops.rename;
|
||||
samba3sam_ops.search = ops.search;
|
||||
samba3sam_ops.wait = ops.wait;
|
||||
|
||||
return ldb_register_module(&samba3sam_ops);
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
/*
|
||||
SAM ldb module
|
||||
|
||||
Copyright (C) Simo Sorce 2004
|
||||
Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
|
||||
|
||||
* NOTICE: this module is NOT released under the GNU LGPL license as
|
||||
* other ldb code. This module is release under the GNU GPL v2 or
|
||||
* later license.
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb samldb module
|
||||
*
|
||||
* Description: add embedded user/group creation functionality
|
||||
*
|
||||
* Author: Simo Sorce
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "libcli/ldap/ldap.h"
|
||||
#include "lib/ldb/include/ldb_errors.h"
|
||||
#include "lib/ldb/include/ldb_private.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "librpc/gen_ndr/ndr_security.h"
|
||||
#include "db_wrap.h"
|
||||
|
||||
int samldb_notice_sid(struct ldb_module *module,
|
||||
TALLOC_CTX *mem_ctx, const struct dom_sid *sid);
|
||||
|
||||
static BOOL samldb_msg_add_sid(struct ldb_module *module, struct ldb_message *msg, const char *name, const struct dom_sid *sid)
|
||||
{
|
||||
struct ldb_val v;
|
||||
NTSTATUS status;
|
||||
status = ndr_push_struct_blob(&v, msg, sid,
|
||||
(ndr_push_flags_fn_t)ndr_push_dom_sid);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
return -1;
|
||||
}
|
||||
return (ldb_msg_add_value(msg, name, &v, NULL) == 0);
|
||||
}
|
||||
|
||||
/*
|
||||
allocate a new id, attempting to do it atomically
|
||||
return 0 on failure, the id on success
|
||||
*/
|
||||
static int samldb_set_next_rid(struct ldb_context *ldb, TALLOC_CTX *mem_ctx,
|
||||
struct ldb_dn *dn, uint32_t old_id, uint32_t new_id)
|
||||
{
|
||||
struct ldb_message msg;
|
||||
int ret;
|
||||
struct ldb_val vals[2];
|
||||
struct ldb_message_element els[2];
|
||||
|
||||
if (new_id == 0) {
|
||||
/* out of IDs ! */
|
||||
ldb_debug(ldb, LDB_DEBUG_FATAL, "Are we out of valid IDs ?\n");
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
/* we do a delete and add as a single operation. That prevents
|
||||
a race, in case we are not actually on a transaction db */
|
||||
ZERO_STRUCT(msg);
|
||||
msg.dn = ldb_dn_copy(mem_ctx, dn);
|
||||
if (!msg.dn) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
msg.num_elements = 2;
|
||||
msg.elements = els;
|
||||
|
||||
els[0].num_values = 1;
|
||||
els[0].values = &vals[0];
|
||||
els[0].flags = LDB_FLAG_MOD_DELETE;
|
||||
els[0].name = talloc_strdup(mem_ctx, "nextRid");
|
||||
if (!els[0].name) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
els[1].num_values = 1;
|
||||
els[1].values = &vals[1];
|
||||
els[1].flags = LDB_FLAG_MOD_ADD;
|
||||
els[1].name = els[0].name;
|
||||
|
||||
vals[0].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", old_id);
|
||||
if (!vals[0].data) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
vals[0].length = strlen((char *)vals[0].data);
|
||||
|
||||
vals[1].data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", new_id);
|
||||
if (!vals[1].data) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
vals[1].length = strlen((char *)vals[1].data);
|
||||
|
||||
ret = ldb_modify(ldb, &msg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
allocate a new id, attempting to do it atomically
|
||||
return 0 on failure, the id on success
|
||||
*/
|
||||
static int samldb_find_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
|
||||
struct ldb_dn *dn, uint32_t *old_rid)
|
||||
{
|
||||
const char * const attrs[2] = { "nextRid", NULL };
|
||||
struct ldb_result *res = NULL;
|
||||
int ret;
|
||||
const char *str;
|
||||
|
||||
ret = ldb_search(module->ldb, dn, LDB_SCOPE_BASE, "nextRid=*", attrs, &res);
|
||||
if (ret != LDB_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
if (res->count != 1) {
|
||||
talloc_free(res);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
str = ldb_msg_find_attr_as_string(res->msgs[0], "nextRid", NULL);
|
||||
if (str == NULL) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"attribute nextRid not found in %s\n",
|
||||
ldb_dn_get_linearized(dn));
|
||||
talloc_free(res);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*old_rid = strtol(str, NULL, 0);
|
||||
talloc_free(res);
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int samldb_allocate_next_rid(struct ldb_module *module, TALLOC_CTX *mem_ctx,
|
||||
struct ldb_dn *dn, const struct dom_sid *dom_sid,
|
||||
struct dom_sid **new_sid)
|
||||
{
|
||||
struct dom_sid *obj_sid;
|
||||
uint32_t old_rid;
|
||||
int ret;
|
||||
|
||||
ret = samldb_find_next_rid(module, mem_ctx, dn, &old_rid);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* return the new object sid */
|
||||
obj_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid);
|
||||
|
||||
*new_sid = dom_sid_add_rid(mem_ctx, dom_sid, old_rid + 1);
|
||||
if (!*new_sid) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ret = samldb_notice_sid(module, mem_ctx, *new_sid);
|
||||
if (ret != 0) {
|
||||
/* gah, there are conflicting sids.
|
||||
* This is a critical situation it means that someone messed up with
|
||||
* the DB and nextRid is not returning free RIDs, report an error
|
||||
* and refuse to create any user until the problem is fixed */
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"Critical Error: unconsistent DB, unable to retireve an unique RID to generate a new SID: %s",
|
||||
ldb_errstring(module->ldb));
|
||||
return ret;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Find a domain object in the parents of a particular DN. */
|
||||
static struct ldb_dn *samldb_search_domain(struct ldb_module *module, TALLOC_CTX *mem_ctx, struct ldb_dn *dn)
|
||||
{
|
||||
TALLOC_CTX *local_ctx;
|
||||
struct ldb_dn *sdn;
|
||||
struct ldb_result *res = NULL;
|
||||
int ret = 0;
|
||||
const char *attrs[] = { NULL };
|
||||
|
||||
local_ctx = talloc_new(mem_ctx);
|
||||
if (local_ctx == NULL) return NULL;
|
||||
|
||||
sdn = ldb_dn_copy(local_ctx, dn);
|
||||
do {
|
||||
ret = ldb_search(module->ldb, sdn, LDB_SCOPE_BASE,
|
||||
"(|(objectClass=domain)(objectClass=builtinDomain))", attrs, &res);
|
||||
if (ret == LDB_SUCCESS) {
|
||||
talloc_steal(local_ctx, res);
|
||||
if (res->count == 1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while ((sdn = ldb_dn_get_parent(local_ctx, sdn)));
|
||||
|
||||
if (ret != LDB_SUCCESS || res->count != 1) {
|
||||
talloc_free(local_ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
talloc_steal(mem_ctx, sdn);
|
||||
talloc_free(local_ctx);
|
||||
|
||||
return sdn;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/* If we are adding new users/groups, we need to update the nextRid
|
||||
* attribute to be 'above' all incoming users RIDs. This tries to
|
||||
* avoid clashes in future */
|
||||
|
||||
int samldb_notice_sid(struct ldb_module *module,
|
||||
TALLOC_CTX *mem_ctx, const struct dom_sid *sid)
|
||||
{
|
||||
int ret;
|
||||
struct ldb_dn *dom_dn;
|
||||
struct dom_sid *dom_sid;
|
||||
const char *attrs[] = { NULL };
|
||||
struct ldb_result *dom_res;
|
||||
struct ldb_result *res;
|
||||
uint32_t old_rid;
|
||||
|
||||
/* find if this SID already exists */
|
||||
ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &res,
|
||||
NULL, LDB_SCOPE_SUBTREE, attrs,
|
||||
"(objectSid=%s)", ldap_encode_ndr_dom_sid(mem_ctx, sid));
|
||||
if (ret == LDB_SUCCESS) {
|
||||
if (res->count > 0) {
|
||||
talloc_free(res);
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"Attempt to add record with SID %s rejected,"
|
||||
" because this SID is already in the database",
|
||||
dom_sid_string(mem_ctx, sid));
|
||||
/* We have a duplicate SID, we must reject the add */
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
talloc_free(res);
|
||||
} else {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"samldb_notice_sid: error searching to see if sid %s is in use: %s\n",
|
||||
dom_sid_string(mem_ctx, sid),
|
||||
ldb_errstring(module->ldb));
|
||||
return ret;
|
||||
}
|
||||
|
||||
dom_sid = dom_sid_dup(mem_ctx, sid);
|
||||
if (!dom_sid) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
/* get the domain component part of the provided SID */
|
||||
dom_sid->num_auths--;
|
||||
|
||||
/* find the domain DN */
|
||||
ret = ldb_search_exp_fmt(module->ldb, mem_ctx, &dom_res,
|
||||
NULL, LDB_SCOPE_SUBTREE, attrs,
|
||||
"(&(objectSid=%s)(objectclass=domain))",
|
||||
ldap_encode_ndr_dom_sid(mem_ctx, dom_sid));
|
||||
if (ret == LDB_SUCCESS) {
|
||||
if (dom_res->count == 0) {
|
||||
talloc_free(dom_res);
|
||||
/* This isn't an operation on a domain we know about, so nothing to update */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
if (dom_res->count > 1) {
|
||||
talloc_free(dom_res);
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"samldb_notice_sid: error retrieving domain from sid: duplicate (found %d) domain: %s!\n",
|
||||
dom_res->count, dom_sid_string(dom_res, dom_sid));
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
} else {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"samldb_notice_sid: error retrieving domain from sid: %s: %s\n",
|
||||
dom_sid_string(dom_res, dom_sid),
|
||||
ldb_errstring(module->ldb));
|
||||
return ret;
|
||||
}
|
||||
|
||||
dom_dn = dom_res->msgs[0]->dn;
|
||||
|
||||
ret = samldb_find_next_rid(module, mem_ctx,
|
||||
dom_dn, &old_rid);
|
||||
if (ret) {
|
||||
talloc_free(dom_res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (old_rid <= sid->sub_auths[sid->num_auths - 1]) {
|
||||
ret = samldb_set_next_rid(module->ldb, mem_ctx, dom_dn, old_rid,
|
||||
sid->sub_auths[sid->num_auths - 1] + 1);
|
||||
}
|
||||
talloc_free(dom_res);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int samldb_handle_sid(struct ldb_module *module,
|
||||
TALLOC_CTX *mem_ctx, struct ldb_message *msg2)
|
||||
{
|
||||
int ret;
|
||||
|
||||
struct dom_sid *sid = samdb_result_dom_sid(mem_ctx, msg2, "objectSid");
|
||||
if (sid == NULL) {
|
||||
ret = samldb_get_new_sid(module, msg2, msg2->dn, &sid);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ( ! samldb_msg_add_sid(module, msg2, "objectSid", sid)) {
|
||||
talloc_free(sid);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
talloc_free(sid);
|
||||
ret = LDB_SUCCESS;
|
||||
} else {
|
||||
ret = samldb_notice_sid(module, msg2, sid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *samldb_generate_samAccountName(struct ldb_module *module, TALLOC_CTX *mem_ctx)
|
||||
{
|
||||
char *name;
|
||||
const char *attrs[] = { NULL };
|
||||
struct ldb_message **msgs;
|
||||
int ret;
|
||||
|
||||
/* Format: $000000-000000000000 */
|
||||
|
||||
do {
|
||||
name = talloc_asprintf(mem_ctx, "$%.6X-%.6X%.6X", (unsigned int)random(), (unsigned int)random(), (unsigned int)random());
|
||||
/* TODO: Figure out exactly what this is meant to conflict with */
|
||||
ret = gendb_search(module->ldb,
|
||||
mem_ctx, NULL, &msgs, attrs,
|
||||
"samAccountName=%s",
|
||||
ldb_binary_encode_string(mem_ctx, name));
|
||||
if (ret == 0) {
|
||||
/* Great. There are no conflicting users/groups/etc */
|
||||
return name;
|
||||
} else if (ret == -1) {
|
||||
/* Bugger, there is a problem, and we don't know what it is until gendb_search improves */
|
||||
return NULL;
|
||||
} else {
|
||||
talloc_free(name);
|
||||
/* gah, there are conflicting sids, lets move around the loop again... */
|
||||
}
|
||||
} while (1);
|
||||
}
|
||||
|
||||
static int samldb_fill_group_object(struct ldb_module *module, const struct ldb_message *msg,
|
||||
struct ldb_message **ret_msg)
|
||||
{
|
||||
int ret;
|
||||
const char *name;
|
||||
struct ldb_message *msg2;
|
||||
const char *rdn_name;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(msg);
|
||||
const char *errstr;
|
||||
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_group_object: ldb_msg_copy failed!\n");
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
ret = samdb_copy_template(module->ldb, msg2,
|
||||
"(&(CN=TemplateGroup)(objectclass=groupTemplate))",
|
||||
&errstr);
|
||||
if (ret != 0) {
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rdn_name = ldb_dn_get_rdn_name(msg2->dn);
|
||||
|
||||
if (strcasecmp(rdn_name, "cn") != 0) {
|
||||
ldb_debug(module->ldb, LDB_DEBUG_FATAL, "samldb_fill_group_object: Bad RDN (%s) for group!\n", rdn_name);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
/* Generate a random name, if no samAccountName was supplied */
|
||||
if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
|
||||
name = samldb_generate_samAccountName(module, mem_ctx);
|
||||
if (!name) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Manage SID allocation, conflicts etc */
|
||||
ret = samldb_handle_sid(module, mem_ctx, msg2);
|
||||
|
||||
if (ret == LDB_SUCCESS) {
|
||||
talloc_steal(msg, msg2);
|
||||
*ret_msg = msg2;
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int samldb_fill_user_or_computer_object(struct ldb_module *module, const struct ldb_message *msg,
|
||||
struct ldb_message **ret_msg)
|
||||
{
|
||||
int ret;
|
||||
char *name;
|
||||
struct ldb_message *msg2;
|
||||
const char *rdn_name;
|
||||
TALLOC_CTX *mem_ctx = talloc_new(msg);
|
||||
const char *errstr;
|
||||
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_group_object: ldb_msg_copy failed!\n");
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
if (samdb_find_attribute(module->ldb, msg, "objectclass", "computer") != NULL) {
|
||||
|
||||
ret = samdb_copy_template(module->ldb, msg2,
|
||||
"(&(CN=TemplateComputer)(objectclass=userTemplate))",
|
||||
&errstr);
|
||||
if (ret) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"samldb_fill_user_or_computer_object: "
|
||||
"Error copying computer template: %s",
|
||||
errstr);
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* readd user and then computer objectclasses */
|
||||
ret = samdb_find_or_add_value(module->ldb, msg2, "objectclass", "user");
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
ret = samdb_find_or_add_value(module->ldb, msg2, "objectclass", "computer");
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} else {
|
||||
ret = samdb_copy_template(module->ldb, msg2,
|
||||
"(&(CN=TemplateUser)(objectclass=userTemplate))",
|
||||
&errstr);
|
||||
if (ret) {
|
||||
ldb_asprintf_errstring(module->ldb,
|
||||
"samldb_fill_user_or_computer_object: Error copying user template: %s\n",
|
||||
errstr);
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
/* readd user objectclass */
|
||||
ret = samdb_find_or_add_value(module->ldb, msg2, "objectclass", "user");
|
||||
if (ret) {
|
||||
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 user/computer, should be CN=!\n", rdn_name);
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_CONSTRAINT_VIOLATION;
|
||||
}
|
||||
|
||||
if (ldb_msg_find_element(msg2, "samAccountName") == NULL) {
|
||||
name = samldb_generate_samAccountName(module, mem_ctx);
|
||||
if (!name) {
|
||||
talloc_free(mem_ctx);
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
ret = samdb_find_or_add_attribute(module->ldb, msg2, "sAMAccountName", name);
|
||||
if (ret) {
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: useraccountcontrol: setting value 0 gives 0x200 for users
|
||||
*/
|
||||
|
||||
/* Manage SID allocation, conflicts etc */
|
||||
ret = samldb_handle_sid(module, mem_ctx, msg2);
|
||||
|
||||
/* TODO: objectCategory, userAccountControl, badPwdCount, codePage, countryCode, badPasswordTime, lastLogoff, lastLogon, pwdLastSet, primaryGroupID, accountExpires, logonCount */
|
||||
|
||||
if (ret == 0) {
|
||||
*ret_msg = msg2;
|
||||
talloc_steal(msg, msg2);
|
||||
}
|
||||
talloc_free(mem_ctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* add_record */
|
||||
|
||||
/*
|
||||
* FIXME
|
||||
*
|
||||
* Actually this module is not async at all as it does a number of sync searches
|
||||
* in the process. It still to be decided how to deal with it properly so it is
|
||||
* left SYNC for now until we think of a good solution.
|
||||
*/
|
||||
|
||||
static int samldb_add(struct ldb_module *module, struct ldb_request *req)
|
||||
{
|
||||
const struct ldb_message *msg = req->op.add.message;
|
||||
struct ldb_message *msg2 = NULL;
|
||||
struct ldb_request *down_req;
|
||||
int ret;
|
||||
|
||||
ldb_debug(module->ldb, LDB_DEBUG_TRACE, "samldb_add_record\n");
|
||||
|
||||
if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
/* is user or computer? */
|
||||
if ((samdb_find_attribute(module->ldb, msg, "objectclass", "user") != NULL) ||
|
||||
(samdb_find_attribute(module->ldb, msg, "objectclass", "computer") != NULL)) {
|
||||
/* add all relevant missing objects */
|
||||
ret = samldb_fill_user_or_computer_object(module, msg, &msg2);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* is group? add all relevant missing objects */
|
||||
if ( ! msg2 ) {
|
||||
if (samdb_find_attribute(module->ldb, msg, "objectclass", "group") != NULL) {
|
||||
ret = samldb_fill_group_object(module, msg, &msg2);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* perhaps a foreignSecurityPrincipal? */
|
||||
if ( ! msg2 ) {
|
||||
if (samdb_find_attribute(module->ldb, msg, "objectclass", "foreignSecurityPrincipal") != NULL) {
|
||||
ret = samldb_fill_foreignSecurityPrincipal_object(module, msg, &msg2);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msg2 == NULL) {
|
||||
return ldb_next_request(module, req);
|
||||
}
|
||||
|
||||
down_req = talloc(req, struct ldb_request);
|
||||
if (down_req == NULL) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
*down_req = *req;
|
||||
|
||||
down_req->op.add.message = talloc_steal(down_req, msg2);
|
||||
|
||||
ldb_set_timeout_from_prev_req(module->ldb, req, down_req);
|
||||
|
||||
/* go on with the call chain */
|
||||
ret = ldb_next_request(module, down_req);
|
||||
|
||||
/* do not free down_req as the call results may be linked to it,
|
||||
* it will be freed when the upper level request get freed */
|
||||
if (ret == LDB_SUCCESS) {
|
||||
req->handle = down_req->handle;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int samldb_init(struct ldb_module *module)
|
||||
{
|
||||
return ldb_next_init(module);
|
||||
}
|
||||
|
||||
static const struct ldb_module_ops samldb_ops = {
|
||||
.name = "samldb",
|
||||
.init_context = samldb_init,
|
||||
.add = samldb_add,
|
||||
};
|
||||
|
||||
|
||||
int samldb_module_init(void)
|
||||
{
|
||||
return ldb_register_module(&samldb_ops);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
ldb database library
|
||||
|
||||
Copyright (C) Simo Sorce 2004-2006
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb schema module
|
||||
*
|
||||
* Description: add schema syntax functionality
|
||||
*
|
||||
* Author: Simo Sorce
|
||||
*
|
||||
* License: GNU GPL v2 or Later
|
||||
*/
|
||||
|
||||
#include "includes.h"
|
||||
#include "ldb/include/ldb.h"
|
||||
#include "ldb/include/ldb_errors.h"
|
||||
#include "schema_syntax.h"
|
||||
|
||||
int map_schema_syntax(uint32_t om_syntax, const char *attr_syntax, const struct ldb_val *om_class, enum schema_internal_syntax *syntax)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = LDB_SUCCESS;
|
||||
|
||||
switch(om_syntax) {
|
||||
case 1:
|
||||
*syntax = SCHEMA_AS_BOOLEAN;
|
||||
break;
|
||||
case 2:
|
||||
*syntax = SCHEMA_AS_INTEGER;
|
||||
break;
|
||||
case 4:
|
||||
if (strcmp(attr_syntax, "2.5.5.10") == 0) {
|
||||
*syntax = SCHEMA_AS_OCTET_STRING;
|
||||
break;
|
||||
}
|
||||
if (strcmp(attr_syntax, "2.5.5.17") == 0) {
|
||||
*syntax = SCHEMA_AS_SID;
|
||||
break;
|
||||
}
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
break;
|
||||
case 6:
|
||||
*syntax = SCHEMA_AS_OID;
|
||||
break;
|
||||
case 10:
|
||||
*syntax = SCHEMA_AS_ENUMERATION;
|
||||
break;
|
||||
case 18:
|
||||
*syntax = SCHEMA_AS_NUMERIC_STRING;
|
||||
break;
|
||||
case 19:
|
||||
*syntax = SCHEMA_AS_PRINTABLE_STRING;
|
||||
break;
|
||||
case 20:
|
||||
*syntax = SCHEMA_AS_CASE_IGNORE_STRING;
|
||||
break;
|
||||
case 22:
|
||||
*syntax = SCHEMA_AS_IA5_STRING;
|
||||
break;
|
||||
case 23:
|
||||
*syntax = SCHEMA_AS_UTC_TIME;
|
||||
break;
|
||||
case 24:
|
||||
*syntax = SCHEMA_AS_GENERALIZED_TIME;
|
||||
break;
|
||||
case 27:
|
||||
*syntax = SCHEMA_AS_CASE_SENSITIVE_STRING;
|
||||
break;
|
||||
case 64:
|
||||
*syntax = SCHEMA_AS_DIRECTORY_STRING;
|
||||
break;
|
||||
case 65:
|
||||
*syntax = SCHEMA_AS_LARGE_INTEGER;
|
||||
break;
|
||||
case 66:
|
||||
*syntax = SCHEMA_AS_OBJECT_SECURITY_DESCRIPTOR;
|
||||
break;
|
||||
case 127:
|
||||
if (!om_class) {
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x4a\x00", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_DN;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0b", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_DN_BINARY;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x56\x06\x01\x02\x05\x0b\x1d\x00\x00\x00", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_OR_NAME;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x06", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_REPLICA_LINK;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x5c\x00", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_PRESENTATION_ADDRESS;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x2b\x0c\x02\x87\x73\x1c\x00\x85\x3e\x00", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_ACCESS_POINT;
|
||||
break;
|
||||
}
|
||||
if (memcmp(om_class->data, "\x2a\x86\x48\x86\xf7\x14\x01\x01\x01\x0c", MIN(om_class->length, 10)) == 0) {
|
||||
*syntax = SCHEMA_AS_DN_STRING;
|
||||
break;
|
||||
}
|
||||
/* not found will error in default: */
|
||||
default:
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int schema_validate_boolean(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
|
||||
if ((strncmp("TRUE", (const char *)val->data, val->length) != 0) &&
|
||||
(strncmp("FALSE", (const char *)val->data, val->length) != 0)) {
|
||||
return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_integer(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
int value;
|
||||
char *endptr;
|
||||
|
||||
errno = 0;
|
||||
value = strtol((const char *)val->data, &endptr, 0);
|
||||
if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if (endptr[0] != '\0') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((min > INT_MIN) && (value < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((max < INT_MAX) && (value > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_binary_blob(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* is there anythign we should check in a binary blob ? */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_sid(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate binary form of objectSid */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_oid(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
if (strspn((const char *)val->data, "0123456789.") != val->length)
|
||||
return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_numeric_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
if (strspn((const char *)val->data, "0123456789") != val->length)
|
||||
return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_printable_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what constitutes the printable character set */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_teletext_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what constitutes the teletext character set */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_ia5_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what constitutes the IA5 character set */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_utc_time(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate syntax of UTC Time string */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_generalized_time(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate syntax of Generalized Time string */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* NOTE: not a single attribute has this syntax in the basic w2k3 schema */
|
||||
static int schema_validate_sensitive_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what constitutes a "case sensitive string" */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_unicode_string(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate utf8 string */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_large_integer(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate large integer/interval */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_object_sd(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: validate object Security Descriptor */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
struct ldb_dn *dn;
|
||||
int ret = LDB_SUCCESS;
|
||||
|
||||
dn = ldb_dn_new(ldb, ldb, (const char *)val->data);
|
||||
if ( ! ldb_dn_validate(dn)) {
|
||||
ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
}
|
||||
|
||||
talloc_free(dn);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int schema_validate_binary_plus_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
int ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
TALLOC_CTX *memctx;
|
||||
struct ldb_dn *dn;
|
||||
char *str, *p;
|
||||
char *endptr;
|
||||
int num;
|
||||
|
||||
memctx = talloc_new(NULL);
|
||||
if (!memctx) return LDB_ERR_OPERATIONS_ERROR;
|
||||
|
||||
str = talloc_strdup(memctx, (const char *)val->data);
|
||||
if (!str) {
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
if (strncasecmp(str, "B:", 2) != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* point at the number of chars in the string */
|
||||
str = strchr(&str[2], ':');
|
||||
if (!str) {
|
||||
goto done;
|
||||
}
|
||||
str++;
|
||||
|
||||
errno = 0;
|
||||
num = strtol(str, &endptr, 0);
|
||||
if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if (endptr[0] != ':') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((min > INT_MIN) && (num < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((max < INT_MAX) && (num > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
|
||||
/* point at the string */
|
||||
str = strchr(str, ':');
|
||||
if (!str) {
|
||||
goto done;
|
||||
}
|
||||
str++;
|
||||
|
||||
/* terminate the string */
|
||||
p = strchr(str, ':');
|
||||
if (!p) {
|
||||
goto done;
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
if (strlen(str) != 2*num) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
str = p + 1;
|
||||
|
||||
dn = ldb_dn_new(memctx, ldb, str);
|
||||
if (ldb_dn_validate(dn)) {
|
||||
ret = LDB_SUCCESS;
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(memctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int schema_validate_x400_or_name(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what is the syntax of an X400 OR NAME */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_presentation_address(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what is the syntax of a presentation address */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
static int schema_validate_x400_access_point(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
/* TODO: find out what is the syntax of an X400 Access Point */
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
/* NOTE: seem there isn't a single attribute defined like this in the base w2k3 schema */
|
||||
static int schema_validate_string_plus_dn(struct ldb_context *ldb, struct ldb_val *val, int min, int max)
|
||||
{
|
||||
int ret = LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
TALLOC_CTX *memctx;
|
||||
struct ldb_dn *dn;
|
||||
char *str, *p;
|
||||
char *endptr;
|
||||
int num;
|
||||
|
||||
memctx = talloc_new(NULL);
|
||||
if (!memctx) return LDB_ERR_OPERATIONS_ERROR;
|
||||
|
||||
str = talloc_strdup(memctx, (const char *)val->data);
|
||||
if (!str) {
|
||||
ret = LDB_ERR_OPERATIONS_ERROR;
|
||||
goto done;
|
||||
}
|
||||
if (strncasecmp(str, "S:", 2) != 0) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* point at the number of chars in the string */
|
||||
str = strchr(&str[2], ':');
|
||||
if (!str) {
|
||||
goto done;
|
||||
}
|
||||
str++;
|
||||
|
||||
errno = 0;
|
||||
num = strtol(str, &endptr, 0);
|
||||
if (errno) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if (endptr[0] != ':') return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((min > INT_MIN) && (num < min)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
if ((max < INT_MAX) && (num > max)) return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
|
||||
/* point at the string */
|
||||
str = strchr(str, ':');
|
||||
if (!str) {
|
||||
goto done;
|
||||
}
|
||||
str++;
|
||||
|
||||
/* terminate the string */
|
||||
p = strchr(str, ':');
|
||||
if (!p) {
|
||||
goto done;
|
||||
}
|
||||
*p = '\0';
|
||||
|
||||
if (strlen(str) != num) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
str = p + 1;
|
||||
|
||||
dn = ldb_dn_new(memctx, ldb, str);
|
||||
if (ldb_dn_validate(dn)) {
|
||||
ret = LDB_SUCCESS;
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(memctx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct schema_syntax_validator {
|
||||
enum schema_internal_syntax type;
|
||||
int (*validate)(struct ldb_context *ldb, struct ldb_val *, int, int);
|
||||
};
|
||||
|
||||
struct schema_syntax_validator schema_syntax_validators[] = {
|
||||
{ SCHEMA_AS_BOOLEAN, schema_validate_boolean },
|
||||
{ SCHEMA_AS_INTEGER, schema_validate_integer },
|
||||
{ SCHEMA_AS_OCTET_STRING, schema_validate_binary_blob },
|
||||
{ SCHEMA_AS_SID, schema_validate_sid },
|
||||
{ SCHEMA_AS_OID, schema_validate_oid },
|
||||
{ SCHEMA_AS_ENUMERATION, schema_validate_integer },
|
||||
{ SCHEMA_AS_NUMERIC_STRING, schema_validate_numeric_string },
|
||||
{ SCHEMA_AS_PRINTABLE_STRING, schema_validate_printable_string },
|
||||
{ SCHEMA_AS_CASE_IGNORE_STRING, schema_validate_teletext_string },
|
||||
{ SCHEMA_AS_IA5_STRING, schema_validate_ia5_string },
|
||||
{ SCHEMA_AS_UTC_TIME, schema_validate_utc_time },
|
||||
{ SCHEMA_AS_GENERALIZED_TIME, schema_validate_generalized_time },
|
||||
{ SCHEMA_AS_CASE_SENSITIVE_STRING, schema_validate_sensitive_string },
|
||||
{ SCHEMA_AS_DIRECTORY_STRING, schema_validate_unicode_string },
|
||||
{ SCHEMA_AS_LARGE_INTEGER, schema_validate_large_integer },
|
||||
{ SCHEMA_AS_OBJECT_SECURITY_DESCRIPTOR, schema_validate_object_sd },
|
||||
{ SCHEMA_AS_DN, schema_validate_dn },
|
||||
{ SCHEMA_AS_DN_BINARY, schema_validate_binary_plus_dn },
|
||||
{ SCHEMA_AS_OR_NAME, schema_validate_x400_or_name },
|
||||
{ SCHEMA_AS_REPLICA_LINK, schema_validate_binary_blob },
|
||||
{ SCHEMA_AS_PRESENTATION_ADDRESS, schema_validate_presentation_address }, /* see rfc1278 ? */
|
||||
{ SCHEMA_AS_ACCESS_POINT, schema_validate_x400_access_point },
|
||||
{ SCHEMA_AS_DN_STRING, schema_validate_string_plus_dn },
|
||||
{ -1, NULL }
|
||||
};
|
||||
|
||||
int schema_validate(struct ldb_context *ldb,
|
||||
struct ldb_message_element *el,
|
||||
enum schema_internal_syntax type,
|
||||
bool single, int min, int max)
|
||||
{
|
||||
struct schema_syntax_validator *v;
|
||||
int i, ret;
|
||||
|
||||
if (single && (el->num_values > 1)) {
|
||||
return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
|
||||
}
|
||||
|
||||
for (i = 0; schema_syntax_validators[i].type != 0; i++) {
|
||||
if (schema_syntax_validators[i].type == type)
|
||||
break;
|
||||
}
|
||||
if (schema_syntax_validators[i].type == 0) {
|
||||
return LDB_ERR_OPERATIONS_ERROR;
|
||||
}
|
||||
v = &schema_syntax_validators[i];
|
||||
|
||||
for (i = 0; i < el->num_values; i++) {
|
||||
ret = v->validate(ldb, &el->values[i], min, max);
|
||||
}
|
||||
|
||||
return LDB_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
ldb database library
|
||||
|
||||
Copyright (C) Simo Sorce 2004-2006
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Name: ldb
|
||||
*
|
||||
* Component: ldb schema module
|
||||
*
|
||||
* Description: add schema syntax functionality
|
||||
*
|
||||
* Author: Simo Sorce
|
||||
*
|
||||
* License: GNU GPL v2 or Later
|
||||
*/
|
||||
|
||||
|
||||
/* Syntax-Table
|
||||
|
||||
see ldap_server/devdocs/AD-syntaxes.txt
|
||||
*/
|
||||
|
||||
enum schema_internal_syntax {
|
||||
SCHEMA_AS_BOOLEAN = 1,
|
||||
SCHEMA_AS_INTEGER = 2,
|
||||
SCHEMA_AS_OCTET_STRING = 3,
|
||||
SCHEMA_AS_SID = 4,
|
||||
SCHEMA_AS_OID = 5,
|
||||
SCHEMA_AS_ENUMERATION = 6,
|
||||
SCHEMA_AS_NUMERIC_STRING = 7,
|
||||
SCHEMA_AS_PRINTABLE_STRING = 8,
|
||||
SCHEMA_AS_CASE_IGNORE_STRING = 9,
|
||||
SCHEMA_AS_IA5_STRING = 10,
|
||||
SCHEMA_AS_UTC_TIME = 11,
|
||||
SCHEMA_AS_GENERALIZED_TIME = 12,
|
||||
SCHEMA_AS_CASE_SENSITIVE_STRING = 13,
|
||||
SCHEMA_AS_DIRECTORY_STRING = 14,
|
||||
SCHEMA_AS_LARGE_INTEGER = 15,
|
||||
SCHEMA_AS_OBJECT_SECURITY_DESCRIPTOR = 16,
|
||||
SCHEMA_AS_DN = 17,
|
||||
SCHEMA_AS_DN_BINARY = 18,
|
||||
SCHEMA_AS_OR_NAME = 19,
|
||||
SCHEMA_AS_REPLICA_LINK = 20,
|
||||
SCHEMA_AS_PRESENTATION_ADDRESS = 21,
|
||||
SCHEMA_AS_ACCESS_POINT = 22,
|
||||
SCHEMA_AS_DN_STRING = 23
|
||||
};
|
||||
|
||||
int map_schema_syntax(uint32_t om_syntax,
|
||||
const char *attr_syntax,
|
||||
const struct ldb_val *om_class,
|
||||
enum schema_internal_syntax *syntax);
|
||||
|
||||
int schema_validate(struct ldb_context *ldb,
|
||||
struct ldb_message_element *el,
|
||||
enum schema_internal_syntax type,
|
||||
bool single, int min, int max);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
interface functions for the sam database
|
||||
|
||||
Copyright (C) Andrew Tridgell 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 __SAMDB_H__
|
||||
#define __SAMDB_H__
|
||||
|
||||
struct auth_session_info;
|
||||
struct drsuapi_DsNameInfo1;
|
||||
|
||||
#include "librpc/gen_ndr/security.h"
|
||||
#include "lib/ldb/include/ldb.h"
|
||||
#include "librpc/gen_ndr/samr.h"
|
||||
#include "dsdb/samdb/samdb_proto.h"
|
||||
|
||||
#endif /* __SAMDB_H__ */
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Unix SMB/CIFS implementation.
|
||||
|
||||
manipulate privilege records in samdb
|
||||
|
||||
Copyright (C) Andrew Tridgell 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 "libcli/ldap/ldap.h"
|
||||
#include "dsdb/samdb/samdb.h"
|
||||
#include "auth/auth.h"
|
||||
#include "libcli/security/security.h"
|
||||
#include "db_wrap.h"
|
||||
|
||||
/*
|
||||
add privilege bits for one sid to a security_token
|
||||
*/
|
||||
static NTSTATUS samdb_privilege_setup_sid(void *samctx, TALLOC_CTX *mem_ctx,
|
||||
struct security_token *token,
|
||||
const struct dom_sid *sid)
|
||||
{
|
||||
const char * const attrs[] = { "privilege", NULL };
|
||||
struct ldb_message **res = NULL;
|
||||
struct ldb_message_element *el;
|
||||
int ret, i;
|
||||
char *sidstr;
|
||||
|
||||
sidstr = ldap_encode_ndr_dom_sid(mem_ctx, sid);
|
||||
NT_STATUS_HAVE_NO_MEMORY(sidstr);
|
||||
|
||||
ret = gendb_search(samctx, mem_ctx, NULL, &res, attrs, "objectSid=%s", sidstr);
|
||||
talloc_free(sidstr);
|
||||
if (ret != 1) {
|
||||
/* not an error to not match */
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
el = ldb_msg_find_element(res[0], "privilege");
|
||||
if (el == NULL) {
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
for (i=0;i<el->num_values;i++) {
|
||||
const char *priv_str = (const char *)el->values[i].data;
|
||||
enum sec_privilege privilege = sec_privilege_id(priv_str);
|
||||
if (privilege == -1) {
|
||||
DEBUG(1,("Unknown privilege '%s' in samdb\n",
|
||||
priv_str));
|
||||
continue;
|
||||
}
|
||||
security_token_set_privilege(token, privilege);
|
||||
}
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
setup the privilege mask for this security token based on our
|
||||
local SAM
|
||||
*/
|
||||
_PUBLIC_ NTSTATUS samdb_privilege_setup(struct security_token *token)
|
||||
{
|
||||
void *samctx;
|
||||
TALLOC_CTX *mem_ctx;
|
||||
int i;
|
||||
NTSTATUS status;
|
||||
|
||||
/* Shortcuts to prevent recursion and avoid lookups */
|
||||
if (security_token_is_system(token)) {
|
||||
token->privilege_mask = ~0;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
if (security_token_is_anonymous(token)) {
|
||||
token->privilege_mask = 0;
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
|
||||
mem_ctx = talloc_new(token);
|
||||
samctx = samdb_connect(mem_ctx, system_session(mem_ctx));
|
||||
if (samctx == NULL) {
|
||||
talloc_free(mem_ctx);
|
||||
return NT_STATUS_INTERNAL_DB_CORRUPTION;
|
||||
}
|
||||
|
||||
token->privilege_mask = 0;
|
||||
|
||||
for (i=0;i<token->num_sids;i++) {
|
||||
status = samdb_privilege_setup_sid(samctx, mem_ctx,
|
||||
token, token->sids[i]);
|
||||
if (!NT_STATUS_IS_OK(status)) {
|
||||
talloc_free(mem_ctx);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(mem_ctx);
|
||||
|
||||
return NT_STATUS_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user