diff options
Diffstat (limited to 'source4/rpc_server')
44 files changed, 39451 insertions, 0 deletions
diff --git a/source4/rpc_server/backupkey/dcesrv_backupkey.c b/source4/rpc_server/backupkey/dcesrv_backupkey.c new file mode 100644 index 0000000..7c4b9de --- /dev/null +++ b/source4/rpc_server/backupkey/dcesrv_backupkey.c @@ -0,0 +1,1854 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the backupkey interface + + Copyright (C) Matthieu Patou <mat@samba.org> 2010 + Copyright (C) Andreas Schneider <asn@samba.org> 2015 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "librpc/gen_ndr/ndr_backupkey.h" +#include "dsdb/common/util.h" +#include "dsdb/samdb/samdb.h" +#include "lib/ldb/include/ldb_errors.h" +#include "../lib/util/util_ldb.h" +#include "param/param.h" +#include "auth/session.h" +#include "system/network.h" + +#include "../lib/tsocket/tsocket.h" +#include "../libcli/security/security.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libds/common/roles.h" + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <gnutls/crypto.h> +#include <gnutls/abstract.h> + +#include "lib/crypto/gnutls_helpers.h" + +#undef strncasecmp + +#define DCESRV_INTERFACE_BACKUPKEY_BIND(context, iface) \ + dcesrv_interface_backupkey_bind(context, iface) +static NTSTATUS dcesrv_interface_backupkey_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_require_privacy(context, iface); +} + +static NTSTATUS set_lsa_secret(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *name, + const DATA_BLOB *lsa_secret) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct ldb_message *msg; + struct ldb_result *res; + struct ldb_dn *system_dn = NULL; + struct ldb_val val; + int ret; + char *name2; + struct timeval now = timeval_current(); + NTTIME nt_now = timeval_to_nttime(&now); + const char *attrs[] = { + NULL + }; + + msg = ldb_msg_new(frame); + if (msg == NULL) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + + /* + * This function is a lot like dcesrv_lsa_CreateSecret + * in the rpc_server/lsa directory + * The reason why we duplicate the effort here is that: + * * we want to keep the former function static + * * we want to avoid the burden of doing LSA calls + * when we can just manipulate the secrets directly + * * taillor the function to the particular needs of backup protocol + */ + + system_dn = samdb_system_container_dn(ldb, frame); + if (system_dn == NULL) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + + name2 = talloc_asprintf(msg, "%s Secret", name); + if (name2 == NULL) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_search(ldb, mem_ctx, &res, system_dn, LDB_SCOPE_SUBTREE, attrs, + "(&(cn=%s)(objectclass=secret))", + ldb_binary_encode_string(mem_ctx, name2)); + + if (ret != LDB_SUCCESS || res->count != 0 ) { + DEBUG(2, ("Secret %s already exists !\n", name2)); + talloc_free(frame); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + /* + * We don't care about previous value as we are + * here only if the key didn't exists before + */ + + msg->dn = ldb_dn_copy(frame, system_dn); + if (msg->dn == NULL) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + if (!ldb_dn_add_child_fmt(msg->dn, "cn=%s", name2)) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "cn", name2); + if (ret != LDB_SUCCESS) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + ret = ldb_msg_add_string(msg, "objectClass", "secret"); + if (ret != LDB_SUCCESS) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + ret = samdb_msg_add_uint64(ldb, frame, msg, "priorSetTime", nt_now); + if (ret != LDB_SUCCESS) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + val.data = lsa_secret->data; + val.length = lsa_secret->length; + ret = ldb_msg_add_value(msg, "currentValue", &val, NULL); + if (ret != LDB_SUCCESS) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + ret = samdb_msg_add_uint64(ldb, frame, msg, "lastSetTime", nt_now); + if (ret != LDB_SUCCESS) { + talloc_free(frame); + return NT_STATUS_NO_MEMORY; + } + + /* + * create the secret with DSDB_MODIFY_RELAX + * otherwise dsdb/samdb/ldb_modules/objectclass.c forbid + * the create of LSA secret object + */ + ret = dsdb_add(ldb, msg, DSDB_MODIFY_RELAX); + if (ret != LDB_SUCCESS) { + DEBUG(2,("Failed to create secret record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(ldb))); + talloc_free(frame); + return NT_STATUS_ACCESS_DENIED; + } + + talloc_free(frame); + return NT_STATUS_OK; +} + +/* This function is pretty much like dcesrv_lsa_QuerySecret */ +static NTSTATUS get_lsa_secret(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb, + const char *name, + DATA_BLOB *lsa_secret) +{ + TALLOC_CTX *tmp_mem; + struct ldb_result *res; + struct ldb_dn *system_dn = NULL; + const struct ldb_val *val; + uint8_t *data; + const char *attrs[] = { + "currentValue", + NULL + }; + int ret; + + lsa_secret->data = NULL; + lsa_secret->length = 0; + + tmp_mem = talloc_new(mem_ctx); + if (tmp_mem == NULL) { + return NT_STATUS_NO_MEMORY; + } + + system_dn = samdb_system_container_dn(ldb, tmp_mem); + if (system_dn == NULL) { + talloc_free(tmp_mem); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_search(ldb, mem_ctx, &res, system_dn, LDB_SCOPE_SUBTREE, attrs, + "(&(cn=%s Secret)(objectclass=secret))", + ldb_binary_encode_string(tmp_mem, name)); + + if (ret != LDB_SUCCESS) { + talloc_free(tmp_mem); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if (res->count == 0) { + talloc_free(tmp_mem); + return NT_STATUS_RESOURCE_NAME_NOT_FOUND; + } + if (res->count > 1) { + DEBUG(2, ("Secret %s collision\n", name)); + talloc_free(tmp_mem); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + val = ldb_msg_find_ldb_val(res->msgs[0], "currentValue"); + if (val == NULL) { + /* + * The secret object is here but we don't have the secret value + * The most common case is a RODC + */ + *lsa_secret = data_blob_null; + talloc_free(tmp_mem); + return NT_STATUS_OK; + } + + data = val->data; + lsa_secret->data = talloc_move(mem_ctx, &data); + lsa_secret->length = val->length; + + talloc_free(tmp_mem); + return NT_STATUS_OK; +} + +static int reverse_and_get_bignum(TALLOC_CTX *mem_ctx, + DATA_BLOB blob, + gnutls_datum_t *datum) +{ + uint32_t i; + + datum->data = talloc_array(mem_ctx, uint8_t, blob.length); + if (datum->data == NULL) { + return -1; + } + + for(i = 0; i < blob.length; i++) { + datum->data[i] = blob.data[blob.length - i - 1]; + } + datum->size = blob.length; + + return 0; +} + +static NTSTATUS get_pk_from_raw_keypair_params(TALLOC_CTX *ctx, + struct bkrp_exported_RSA_key_pair *keypair, + gnutls_privkey_t *pk) +{ + gnutls_x509_privkey_t x509_privkey = NULL; + gnutls_privkey_t privkey = NULL; + gnutls_datum_t m, e, d, p, q, u, e1, e2; + int rc; + + rc = reverse_and_get_bignum(ctx, keypair->modulus, &m); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + rc = reverse_and_get_bignum(ctx, keypair->public_exponent, &e); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + rc = reverse_and_get_bignum(ctx, keypair->private_exponent, &d); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + rc = reverse_and_get_bignum(ctx, keypair->prime1, &p); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + rc = reverse_and_get_bignum(ctx, keypair->prime2, &q); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + rc = reverse_and_get_bignum(ctx, keypair->coefficient, &u); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + rc = reverse_and_get_bignum(ctx, keypair->exponent1, &e1); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + rc = reverse_and_get_bignum(ctx, keypair->exponent2, &e2); + if (rc != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + rc = gnutls_x509_privkey_init(&x509_privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_init failed - %s\n", + gnutls_strerror(rc)); + return NT_STATUS_INTERNAL_ERROR; + } + + rc = gnutls_x509_privkey_import_rsa_raw2(x509_privkey, + &m, + &e, + &d, + &p, + &q, + &u, + &e1, + &e2); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_import_rsa_raw2 failed - %s\n", + gnutls_strerror(rc)); + return NT_STATUS_INTERNAL_ERROR; + } + + rc = gnutls_privkey_init(&privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_privkey_init failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_privkey); + return NT_STATUS_INTERNAL_ERROR; + } + + rc = gnutls_privkey_import_x509(privkey, + x509_privkey, + GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_privkey_import_x509 failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_privkey); + return NT_STATUS_INTERNAL_ERROR; + } + + *pk = privkey; + + return NT_STATUS_OK; +} + +static WERROR get_and_verify_access_check(TALLOC_CTX *sub_ctx, + uint32_t version, + uint8_t *key_and_iv, + uint8_t *access_check, + uint32_t access_check_len, + struct auth_session_info *session_info) +{ + struct bkrp_access_check_v2 uncrypted_accesscheckv2; + struct bkrp_access_check_v3 uncrypted_accesscheckv3; + gnutls_cipher_hd_t cipher_handle = { 0 }; + gnutls_cipher_algorithm_t cipher_algo; + DATA_BLOB blob_us; + enum ndr_err_code ndr_err; + gnutls_datum_t key; + gnutls_datum_t iv; + + struct dom_sid *access_sid = NULL; + struct dom_sid *caller_sid = NULL; + int rc; + + switch (version) { + case 2: + cipher_algo = GNUTLS_CIPHER_3DES_CBC; + break; + case 3: + cipher_algo = GNUTLS_CIPHER_AES_256_CBC; + break; + default: + return WERR_INVALID_DATA; + } + + key.data = key_and_iv; + key.size = gnutls_cipher_get_key_size(cipher_algo); + + iv.data = key_and_iv + key.size; + iv.size = gnutls_cipher_get_iv_size(cipher_algo); + + /* Allocate data structure for the plaintext */ + blob_us = data_blob_talloc_zero(sub_ctx, access_check_len); + if (blob_us.data == NULL) { + return WERR_INVALID_DATA; + } + + rc = gnutls_cipher_init(&cipher_handle, + cipher_algo, + &key, + &iv); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return WERR_INVALID_DATA; + } + + rc = gnutls_cipher_decrypt2(cipher_handle, + access_check, + access_check_len, + blob_us.data, + blob_us.length); + gnutls_cipher_deinit(cipher_handle); + if (rc < 0) { + DBG_ERR("gnutls_cipher_decrypt2 failed: %s\n", + gnutls_strerror(rc)); + return WERR_INVALID_DATA; + } + + switch (version) { + case 2: + { + uint32_t hash_size = 20; + uint8_t hash[hash_size]; + gnutls_hash_hd_t dig_ctx; + + ndr_err = ndr_pull_struct_blob(&blob_us, sub_ctx, &uncrypted_accesscheckv2, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_access_check_v2); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* Unable to unmarshall */ + return WERR_INVALID_DATA; + } + if (uncrypted_accesscheckv2.magic != 0x1) { + /* wrong magic */ + return WERR_INVALID_DATA; + } + + gnutls_hash_init(&dig_ctx, GNUTLS_DIG_SHA1); + gnutls_hash(dig_ctx, + blob_us.data, + blob_us.length - hash_size); + gnutls_hash_deinit(dig_ctx, hash); + /* + * We free it after the sha1 calculation because blob.data + * point to the same area + */ + + if (!mem_equal_const_time(hash, uncrypted_accesscheckv2.hash, hash_size)) { + DEBUG(2, ("Wrong hash value in the access check in backup key remote protocol\n")); + return WERR_INVALID_DATA; + } + access_sid = &(uncrypted_accesscheckv2.sid); + break; + } + case 3: + { + uint32_t hash_size = 64; + uint8_t hash[hash_size]; + gnutls_hash_hd_t dig_ctx; + + ndr_err = ndr_pull_struct_blob(&blob_us, sub_ctx, &uncrypted_accesscheckv3, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_access_check_v3); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* Unable to unmarshall */ + return WERR_INVALID_DATA; + } + if (uncrypted_accesscheckv3.magic != 0x1) { + /* wrong magic */ + return WERR_INVALID_DATA; + } + + gnutls_hash_init(&dig_ctx, GNUTLS_DIG_SHA512); + gnutls_hash(dig_ctx, + blob_us.data, + blob_us.length - hash_size); + gnutls_hash_deinit(dig_ctx, hash); + + /* + * We free it after the sha1 calculation because blob.data + * point to the same area + */ + + if (!mem_equal_const_time(hash, uncrypted_accesscheckv3.hash, hash_size)) { + DEBUG(2, ("Wrong hash value in the access check in backup key remote protocol\n")); + return WERR_INVALID_DATA; + } + access_sid = &(uncrypted_accesscheckv3.sid); + break; + } + default: + /* Never reached normally as we filtered at the switch / case level */ + return WERR_INVALID_DATA; + } + + caller_sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + if (!dom_sid_equal(caller_sid, access_sid)) { + return WERR_INVALID_ACCESS; + } + return WERR_OK; +} + +/* + * We have some data, such as saved website or IMAP passwords that the + * client has in profile on-disk. This needs to be decrypted. This + * version gives the server the data over the network (protected by + * the X.509 certificate and public key encryption, and asks that it + * be decrypted returned for short-term use, protected only by the + * negotiated transport encryption. + * + * The data is NOT stored in the LSA, but a X.509 certificate, public + * and private keys used to encrypt the data will be stored. There is + * only one active encryption key pair and certificate per domain, it + * is pointed at with G$BCKUPKEY_PREFERRED in the LSA secrets store. + * + * The potentially multiple valid decrypting key pairs are in turn + * stored in the LSA secrets store as G$BCKUPKEY_keyGuidString. + * + */ +static WERROR bkrp_client_wrap_decrypt_data(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct bkrp_BackupKey *r, + struct ldb_context *ldb_ctx) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct bkrp_client_side_wrapped uncrypt_request; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + char *guid_string; + char *cert_secret_name; + DATA_BLOB lsa_secret; + DATA_BLOB *uncrypted_data = NULL; + NTSTATUS status; + uint32_t requested_version; + + blob.data = r->in.data_in; + blob.length = r->in.data_in_len; + + if (r->in.data_in_len < 4 || r->in.data_in == NULL) { + return WERR_INVALID_PARAMETER; + } + + /* + * We check for the version here, so we can actually print the + * message as we are unlikely to parse it with NDR. + */ + requested_version = IVAL(r->in.data_in, 0); + if ((requested_version != BACKUPKEY_CLIENT_WRAP_VERSION2) + && (requested_version != BACKUPKEY_CLIENT_WRAP_VERSION3)) { + DEBUG(1, ("Request for unknown BackupKey sub-protocol %d\n", requested_version)); + return WERR_INVALID_PARAMETER; + } + + ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &uncrypt_request, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_client_side_wrapped); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + if ((uncrypt_request.version != BACKUPKEY_CLIENT_WRAP_VERSION2) + && (uncrypt_request.version != BACKUPKEY_CLIENT_WRAP_VERSION3)) { + DEBUG(1, ("Request for unknown BackupKey sub-protocol %d\n", uncrypt_request.version)); + return WERR_INVALID_PARAMETER; + } + + guid_string = GUID_string(mem_ctx, &uncrypt_request.guid); + if (guid_string == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + cert_secret_name = talloc_asprintf(mem_ctx, + "BCKUPKEY_%s", + guid_string); + if (cert_secret_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = get_lsa_secret(mem_ctx, + ldb_ctx, + cert_secret_name, + &lsa_secret); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Error while fetching secret %s\n", cert_secret_name)); + return WERR_INVALID_DATA; + } else if (lsa_secret.length == 0) { + /* we do not have the real secret attribute, like if we are an RODC */ + return WERR_INVALID_PARAMETER; + } else { + struct bkrp_exported_RSA_key_pair keypair; + gnutls_privkey_t privkey = NULL; + gnutls_datum_t reversed_secret; + gnutls_datum_t uncrypted_secret; + uint32_t i; + DATA_BLOB blob_us; + WERROR werr; + int rc; + + ndr_err = ndr_pull_struct_blob(&lsa_secret, mem_ctx, &keypair, (ndr_pull_flags_fn_t)ndr_pull_bkrp_exported_RSA_key_pair); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(2, ("Unable to parse the ndr encoded cert in key %s\n", cert_secret_name)); + return WERR_FILE_NOT_FOUND; + } + + status = get_pk_from_raw_keypair_params(mem_ctx, + &keypair, + &privkey); + if (!NT_STATUS_IS_OK(status)) { + return WERR_INTERNAL_ERROR; + } + + reversed_secret.data = talloc_array(mem_ctx, uint8_t, + uncrypt_request.encrypted_secret_len); + if (reversed_secret.data == NULL) { + gnutls_privkey_deinit(privkey); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* The secret has to be reversed ... */ + for(i=0; i< uncrypt_request.encrypted_secret_len; i++) { + uint8_t *reversed = (uint8_t *)reversed_secret.data; + uint8_t *uncrypt = uncrypt_request.encrypted_secret; + reversed[i] = uncrypt[uncrypt_request.encrypted_secret_len - 1 - i]; + } + reversed_secret.size = uncrypt_request.encrypted_secret_len; + + /* + * Let's try to decrypt the secret now that + * we have the private key ... + */ + rc = gnutls_privkey_decrypt_data(privkey, + 0, + &reversed_secret, + &uncrypted_secret); + gnutls_privkey_deinit(privkey); + if (rc != GNUTLS_E_SUCCESS) { + /* We are not able to decrypt the secret, looks like something is wrong */ + return WERR_INVALID_PARAMETER; + } + blob_us.data = uncrypted_secret.data; + blob_us.length = uncrypted_secret.size; + + if (uncrypt_request.version == 2) { + struct bkrp_encrypted_secret_v2 uncrypted_secretv2; + + ndr_err = ndr_pull_struct_blob(&blob_us, mem_ctx, &uncrypted_secretv2, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_encrypted_secret_v2); + gnutls_free(uncrypted_secret.data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* Unable to unmarshall */ + return WERR_INVALID_DATA; + } + if (uncrypted_secretv2.magic != 0x20) { + /* wrong magic */ + return WERR_INVALID_DATA; + } + + werr = get_and_verify_access_check(mem_ctx, 2, + uncrypted_secretv2.payload_key, + uncrypt_request.access_check, + uncrypt_request.access_check_len, + session_info); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + uncrypted_data = talloc(mem_ctx, DATA_BLOB); + if (uncrypted_data == NULL) { + return WERR_INVALID_DATA; + } + + uncrypted_data->data = uncrypted_secretv2.secret; + uncrypted_data->length = uncrypted_secretv2.secret_len; + } + if (uncrypt_request.version == 3) { + struct bkrp_encrypted_secret_v3 uncrypted_secretv3; + + ndr_err = ndr_pull_struct_blob(&blob_us, mem_ctx, &uncrypted_secretv3, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_encrypted_secret_v3); + gnutls_free(uncrypted_secret.data); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* Unable to unmarshall */ + return WERR_INVALID_DATA; + } + + if (uncrypted_secretv3.magic1 != 0x30 || + uncrypted_secretv3.magic2 != 0x6610 || + uncrypted_secretv3.magic3 != 0x800e) { + /* wrong magic */ + return WERR_INVALID_DATA; + } + + /* + * Confirm that the caller is permitted to + * read this particular data. Because one key + * pair is used per domain, the caller could + * have stolen the profile data on-disk and + * would otherwise be able to read the + * passwords. + */ + + werr = get_and_verify_access_check(mem_ctx, 3, + uncrypted_secretv3.payload_key, + uncrypt_request.access_check, + uncrypt_request.access_check_len, + session_info); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + uncrypted_data = talloc(mem_ctx, DATA_BLOB); + if (uncrypted_data == NULL) { + return WERR_INVALID_DATA; + } + + uncrypted_data->data = uncrypted_secretv3.secret; + uncrypted_data->length = uncrypted_secretv3.secret_len; + } + + /* + * Yeah if we are here all looks pretty good: + * - hash is ok + * - user sid is the same as the one in access check + * - we were able to decrypt the whole stuff + */ + } + + if (uncrypted_data->data == NULL) { + return WERR_INVALID_DATA; + } + + /* There is a magic value a the beginning of the data + * we can use an adhoc structure but as the + * parent structure is just an array of bytes it a lot of work + * work just prepending 4 bytes + */ + *(r->out.data_out) = talloc_zero_array(mem_ctx, uint8_t, uncrypted_data->length + 4); + W_ERROR_HAVE_NO_MEMORY(*(r->out.data_out)); + memcpy(4+*(r->out.data_out), uncrypted_data->data, uncrypted_data->length); + *(r->out.data_out_len) = uncrypted_data->length + 4; + + return WERR_OK; +} + +static DATA_BLOB *reverse_and_get_blob(TALLOC_CTX *mem_ctx, + gnutls_datum_t *datum) +{ + DATA_BLOB *blob; + size_t i; + + blob = talloc(mem_ctx, DATA_BLOB); + if (blob == NULL) { + return NULL; + } + + blob->length = datum->size; + if (datum->data[0] == '\0') { + /* The datum has a leading byte zero, skip it */ + blob->length = datum->size - 1; + } + blob->data = talloc_zero_array(mem_ctx, uint8_t, blob->length); + if (blob->data == NULL) { + talloc_free(blob); + return NULL; + } + + for (i = 0; i < blob->length; i++) { + blob->data[i] = datum->data[datum->size - i - 1]; + } + + return blob; +} + +static WERROR create_privkey_rsa(gnutls_privkey_t *pk) +{ + int bits = 2048; + gnutls_x509_privkey_t x509_privkey = NULL; + gnutls_privkey_t privkey = NULL; + int rc; + + rc = gnutls_x509_privkey_init(&x509_privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_init failed - %s\n", + gnutls_strerror(rc)); + return WERR_INTERNAL_ERROR; + } + + rc = gnutls_x509_privkey_generate(x509_privkey, + GNUTLS_PK_RSA, + bits, + 0); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_generate failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_privkey); + return WERR_INTERNAL_ERROR; + } + + rc = gnutls_privkey_init(&privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_privkey_init failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_privkey); + return WERR_INTERNAL_ERROR; + } + + rc = gnutls_privkey_import_x509(privkey, + x509_privkey, + GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_privkey_import_x509 failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_privkey); + return WERR_INTERNAL_ERROR; + } + + *pk = privkey; + + return WERR_OK; +} + +static WERROR self_sign_cert(TALLOC_CTX *mem_ctx, + time_t lifetime, + const char *dn, + gnutls_privkey_t issuer_privkey, + gnutls_x509_crt_t *certificate, + DATA_BLOB *guidblob) +{ + gnutls_datum_t unique_id; + gnutls_datum_t serial_number; + gnutls_x509_crt_t issuer_cert; + gnutls_x509_privkey_t x509_issuer_privkey; + time_t activation = time(NULL); + time_t expiry = activation + lifetime; + const char *error_string; + uint8_t *reversed; + size_t i; + int rc; + + unique_id.size = guidblob->length; + unique_id.data = talloc_memdup(mem_ctx, + guidblob->data, + guidblob->length); + if (unique_id.data == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + reversed = talloc_array(mem_ctx, uint8_t, guidblob->length); + if (reversed == NULL) { + talloc_free(unique_id.data); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Native AD generates certificates with serialnumber in reversed notation */ + for (i = 0; i < guidblob->length; i++) { + uint8_t *uncrypt = guidblob->data; + reversed[i] = uncrypt[guidblob->length - i - 1]; + } + serial_number.size = guidblob->length; + serial_number.data = reversed; + + /* Create certificate to sign */ + rc = gnutls_x509_crt_init(&issuer_cert); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_init failed - %s\n", + gnutls_strerror(rc)); + return WERR_NOT_ENOUGH_MEMORY; + } + + rc = gnutls_x509_crt_set_dn(issuer_cert, dn, &error_string); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_dn failed - %s (%s)\n", + gnutls_strerror(rc), + error_string); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_issuer_dn(issuer_cert, dn, &error_string); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_issuer_dn failed - %s (%s)\n", + gnutls_strerror(rc), + error_string); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + /* Get x509 privkey for subjectPublicKeyInfo */ + rc = gnutls_x509_privkey_init(&x509_issuer_privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_init failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_privkey_export_x509(issuer_privkey, + &x509_issuer_privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_privkey_init failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_privkey_deinit(x509_issuer_privkey); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + /* Set subjectPublicKeyInfo */ + rc = gnutls_x509_crt_set_key(issuer_cert, x509_issuer_privkey); + gnutls_x509_privkey_deinit(x509_issuer_privkey); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_pubkey failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_activation_time(issuer_cert, activation); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_activation_time failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_expiration_time(issuer_cert, expiry); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_expiration_time failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_version(issuer_cert, 3); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_version failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_subject_unique_id(issuer_cert, + unique_id.data, + unique_id.size); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_subject_key_id failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_issuer_unique_id(issuer_cert, + unique_id.data, + unique_id.size); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_issuer_unique_id failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_set_serial(issuer_cert, + serial_number.data, + serial_number.size); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_set_serial failed - %s\n", + gnutls_strerror(rc)); + gnutls_x509_crt_deinit(issuer_cert); + return WERR_INVALID_PARAMETER; + } + + rc = gnutls_x509_crt_privkey_sign(issuer_cert, + issuer_cert, + issuer_privkey, + GNUTLS_DIG_SHA1, + 0); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_privkey_sign failed - %s\n", + gnutls_strerror(rc)); + return WERR_INVALID_PARAMETER; + } + + *certificate = issuer_cert; + + return WERR_OK; +} + +/* Return an error when we fail to generate a certificate */ +static WERROR generate_bkrp_cert(TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *dce_call, + struct ldb_context *ldb_ctx, + const char *dn) +{ + WERROR werr; + gnutls_privkey_t issuer_privkey = NULL; + gnutls_x509_crt_t cert = NULL; + gnutls_datum_t cert_blob; + gnutls_datum_t m, e, d, p, q, u, e1, e2; + DATA_BLOB blob; + DATA_BLOB blobkeypair; + DATA_BLOB *tmp; + bool ok = true; + struct GUID guid = GUID_random(); + NTSTATUS status; + char *secret_name; + struct bkrp_exported_RSA_key_pair keypair; + enum ndr_err_code ndr_err; + time_t nb_seconds_validity = 3600 * 24 * 365; + int rc; + + DEBUG(6, ("Trying to generate a certificate\n")); + werr = create_privkey_rsa(&issuer_privkey); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + status = GUID_to_ndr_blob(&guid, mem_ctx, &blob); + if (!NT_STATUS_IS_OK(status)) { + gnutls_privkey_deinit(issuer_privkey); + return WERR_INVALID_DATA; + } + + werr = self_sign_cert(mem_ctx, + nb_seconds_validity, + dn, + issuer_privkey, + &cert, + &blob); + if (!W_ERROR_IS_OK(werr)) { + gnutls_privkey_deinit(issuer_privkey); + return WERR_INVALID_DATA; + } + + rc = gnutls_x509_crt_export2(cert, GNUTLS_X509_FMT_DER, &cert_blob); + if (rc != GNUTLS_E_SUCCESS) { + DBG_ERR("gnutls_x509_crt_export2 failed - %s\n", + gnutls_strerror(rc)); + gnutls_privkey_deinit(issuer_privkey); + gnutls_x509_crt_deinit(cert); + return WERR_INVALID_DATA; + } + + keypair.cert.length = cert_blob.size; + keypair.cert.data = talloc_memdup(mem_ctx, cert_blob.data, cert_blob.size); + gnutls_x509_crt_deinit(cert); + gnutls_free(cert_blob.data); + if (keypair.cert.data == NULL) { + gnutls_privkey_deinit(issuer_privkey); + return WERR_NOT_ENOUGH_MEMORY; + } + + rc = gnutls_privkey_export_rsa_raw(issuer_privkey, + &m, + &e, + &d, + &p, + &q, + &u, + &e1, + &e2); + if (rc != GNUTLS_E_SUCCESS) { + gnutls_privkey_deinit(issuer_privkey); + return WERR_INVALID_DATA; + } + + /* + * Heimdal's bignum are big endian and the + * structure expect it to be in little endian + * so we reverse the buffer to make it work + */ + tmp = reverse_and_get_blob(mem_ctx, &e); + if (tmp == NULL) { + ok = false; + } else { + SMB_ASSERT(tmp->length <= 4); + keypair.public_exponent = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &d); + if (tmp == NULL) { + ok = false; + } else { + keypair.private_exponent = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &m); + if (tmp == NULL) { + ok = false; + } else { + keypair.modulus = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &p); + if (tmp == NULL) { + ok = false; + } else { + keypair.prime1 = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &q); + if (tmp == NULL) { + ok = false; + } else { + keypair.prime2 = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &e1); + if (tmp == NULL) { + ok = false; + } else { + keypair.exponent1 = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &e2); + if (tmp == NULL) { + ok = false; + } else { + keypair.exponent2 = *tmp; + } + + tmp = reverse_and_get_blob(mem_ctx, &u); + if (tmp == NULL) { + ok = false; + } else { + keypair.coefficient = *tmp; + } + + /* One of the keypair allocation was wrong */ + if (ok == false) { + gnutls_privkey_deinit(issuer_privkey); + return WERR_INVALID_DATA; + } + + keypair.certificate_len = keypair.cert.length; + ndr_err = ndr_push_struct_blob(&blobkeypair, + mem_ctx, + &keypair, + (ndr_push_flags_fn_t)ndr_push_bkrp_exported_RSA_key_pair); + gnutls_privkey_deinit(issuer_privkey); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_DATA; + } + + secret_name = talloc_asprintf(mem_ctx, "BCKUPKEY_%s", GUID_string(mem_ctx, &guid)); + if (secret_name == NULL) { + return WERR_OUTOFMEMORY; + } + + status = set_lsa_secret(mem_ctx, ldb_ctx, secret_name, &blobkeypair); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Failed to save the secret %s\n", secret_name)); + } + talloc_free(secret_name); + + GUID_to_ndr_blob(&guid, mem_ctx, &blob); + status = set_lsa_secret(mem_ctx, ldb_ctx, "BCKUPKEY_PREFERRED", &blob); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Failed to save the secret BCKUPKEY_PREFERRED\n")); + } + + return WERR_OK; +} + +static WERROR bkrp_retrieve_client_wrap_key(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct bkrp_BackupKey *r, struct ldb_context *ldb_ctx) +{ + struct GUID guid; + char *guid_string; + DATA_BLOB lsa_secret; + enum ndr_err_code ndr_err; + NTSTATUS status; + + /* + * here we basicaly need to return our certificate + * search for lsa secret BCKUPKEY_PREFERRED first + */ + + status = get_lsa_secret(mem_ctx, + ldb_ctx, + "BCKUPKEY_PREFERRED", + &lsa_secret); + if (NT_STATUS_EQUAL(status, NT_STATUS_RESOURCE_NAME_NOT_FOUND)) { + /* Ok we can be in this case if there was no certs */ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + char *dn = talloc_asprintf(mem_ctx, "CN=%s", + lpcfg_realm(lp_ctx)); + + WERROR werr = generate_bkrp_cert(mem_ctx, dce_call, ldb_ctx, dn); + if (!W_ERROR_IS_OK(werr)) { + return WERR_INVALID_PARAMETER; + } + status = get_lsa_secret(mem_ctx, + ldb_ctx, + "BCKUPKEY_PREFERRED", + &lsa_secret); + + if (!NT_STATUS_IS_OK(status)) { + /* Ok we really don't manage to get this certs ...*/ + DEBUG(2, ("Unable to locate BCKUPKEY_PREFERRED after cert generation\n")); + return WERR_FILE_NOT_FOUND; + } + } else if (!NT_STATUS_IS_OK(status)) { + return WERR_INTERNAL_ERROR; + } + + if (lsa_secret.length == 0) { + DEBUG(1, ("No secret in BCKUPKEY_PREFERRED, are we an undetected RODC?\n")); + return WERR_INTERNAL_ERROR; + } else { + char *cert_secret_name; + + status = GUID_from_ndr_blob(&lsa_secret, &guid); + if (!NT_STATUS_IS_OK(status)) { + return WERR_FILE_NOT_FOUND; + } + + guid_string = GUID_string(mem_ctx, &guid); + if (guid_string == NULL) { + /* We return file not found because the client + * expect this error + */ + return WERR_FILE_NOT_FOUND; + } + + cert_secret_name = talloc_asprintf(mem_ctx, + "BCKUPKEY_%s", + guid_string); + status = get_lsa_secret(mem_ctx, + ldb_ctx, + cert_secret_name, + &lsa_secret); + if (!NT_STATUS_IS_OK(status)) { + return WERR_FILE_NOT_FOUND; + } + + if (lsa_secret.length != 0) { + struct bkrp_exported_RSA_key_pair keypair; + ndr_err = ndr_pull_struct_blob(&lsa_secret, mem_ctx, &keypair, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_exported_RSA_key_pair); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_FILE_NOT_FOUND; + } + *(r->out.data_out_len) = keypair.cert.length; + *(r->out.data_out) = talloc_memdup(mem_ctx, keypair.cert.data, keypair.cert.length); + W_ERROR_HAVE_NO_MEMORY(*(r->out.data_out)); + return WERR_OK; + } else { + DEBUG(1, ("No or broken secret called %s\n", cert_secret_name)); + return WERR_INTERNAL_ERROR; + } + } + + return WERR_NOT_SUPPORTED; +} + +static WERROR generate_bkrp_server_wrap_key(TALLOC_CTX *ctx, struct ldb_context *ldb_ctx) +{ + struct GUID guid = GUID_random(); + enum ndr_err_code ndr_err; + DATA_BLOB blob_wrap_key, guid_blob; + struct bkrp_dc_serverwrap_key wrap_key; + NTSTATUS status; + char *secret_name; + TALLOC_CTX *frame = talloc_stackframe(); + + generate_random_buffer(wrap_key.key, sizeof(wrap_key.key)); + + ndr_err = ndr_push_struct_blob(&blob_wrap_key, ctx, &wrap_key, (ndr_push_flags_fn_t)ndr_push_bkrp_dc_serverwrap_key); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return WERR_INVALID_DATA; + } + + secret_name = talloc_asprintf(frame, "BCKUPKEY_%s", GUID_string(ctx, &guid)); + if (secret_name == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + + status = set_lsa_secret(frame, ldb_ctx, secret_name, &blob_wrap_key); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Failed to save the secret %s\n", secret_name)); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + status = GUID_to_ndr_blob(&guid, frame, &guid_blob); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Failed to save the secret %s\n", secret_name)); + TALLOC_FREE(frame); + } + + status = set_lsa_secret(frame, ldb_ctx, "BCKUPKEY_P", &guid_blob); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2, ("Failed to save the secret %s\n", secret_name)); + TALLOC_FREE(frame); + return WERR_INTERNAL_ERROR; + } + + TALLOC_FREE(frame); + + return WERR_OK; +} + +/* + * Find the specified decryption keys from the LSA secrets store as + * G$BCKUPKEY_keyGuidString. + */ + +static WERROR bkrp_do_retrieve_server_wrap_key(TALLOC_CTX *mem_ctx, struct ldb_context *ldb_ctx, + struct bkrp_dc_serverwrap_key *server_key, + struct GUID *guid) +{ + NTSTATUS status; + DATA_BLOB lsa_secret; + char *secret_name; + char *guid_string; + enum ndr_err_code ndr_err; + + guid_string = GUID_string(mem_ctx, guid); + if (guid_string == NULL) { + /* We return file not found because the client + * expect this error + */ + return WERR_FILE_NOT_FOUND; + } + + secret_name = talloc_asprintf(mem_ctx, "BCKUPKEY_%s", guid_string); + if (secret_name == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + status = get_lsa_secret(mem_ctx, ldb_ctx, secret_name, &lsa_secret); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Error while fetching secret %s\n", secret_name)); + return WERR_INVALID_DATA; + } + if (lsa_secret.length == 0) { + /* RODC case, we do not have secrets locally */ + DEBUG(1, ("Unable to fetch value for secret %s, are we an undetected RODC?\n", + secret_name)); + return WERR_INTERNAL_ERROR; + } + ndr_err = ndr_pull_struct_blob(&lsa_secret, mem_ctx, server_key, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_dc_serverwrap_key); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(2, ("Unable to parse the ndr encoded server wrap key %s\n", secret_name)); + return WERR_INVALID_DATA; + } + + return WERR_OK; +} + +/* + * Find the current, preferred ServerWrap Key by looking at + * G$BCKUPKEY_P in the LSA secrets store. + * + * Then find the current decryption keys from the LSA secrets store as + * G$BCKUPKEY_keyGuidString. + */ + +static WERROR bkrp_do_retrieve_default_server_wrap_key(TALLOC_CTX *mem_ctx, + struct ldb_context *ldb_ctx, + struct bkrp_dc_serverwrap_key *server_key, + struct GUID *returned_guid) +{ + NTSTATUS status; + DATA_BLOB guid_binary; + + status = get_lsa_secret(mem_ctx, ldb_ctx, "BCKUPKEY_P", &guid_binary); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10, ("Error while fetching secret BCKUPKEY_P to find current GUID\n")); + return WERR_FILE_NOT_FOUND; + } else if (guid_binary.length == 0) { + /* RODC case, we do not have secrets locally */ + DEBUG(1, ("Unable to fetch value for secret BCKUPKEY_P, are we an undetected RODC?\n")); + return WERR_INTERNAL_ERROR; + } + + status = GUID_from_ndr_blob(&guid_binary, returned_guid); + if (!NT_STATUS_IS_OK(status)) { + return WERR_FILE_NOT_FOUND; + } + + return bkrp_do_retrieve_server_wrap_key(mem_ctx, ldb_ctx, + server_key, returned_guid); +} + +static WERROR bkrp_server_wrap_decrypt_data(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct bkrp_BackupKey *r ,struct ldb_context *ldb_ctx) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + WERROR werr; + struct bkrp_server_side_wrapped decrypt_request; + DATA_BLOB sid_blob, encrypted_blob; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + struct bkrp_dc_serverwrap_key server_key; + struct bkrp_rc4encryptedpayload rc4payload; + struct dom_sid *caller_sid; + uint8_t symkey[20]; /* SHA-1 hash len */ + uint8_t mackey[20]; /* SHA-1 hash len */ + uint8_t mac[20]; /* SHA-1 hash len */ + gnutls_hmac_hd_t hmac_hnd; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t cipher_key; + int rc; + + blob.data = r->in.data_in; + blob.length = r->in.data_in_len; + + if (r->in.data_in_len == 0 || r->in.data_in == NULL) { + return WERR_INVALID_PARAMETER; + } + + ndr_err = ndr_pull_struct_blob_all(&blob, mem_ctx, &decrypt_request, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_server_side_wrapped); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + if (decrypt_request.magic != BACKUPKEY_SERVER_WRAP_VERSION) { + return WERR_INVALID_PARAMETER; + } + + werr = bkrp_do_retrieve_server_wrap_key(mem_ctx, ldb_ctx, &server_key, + &decrypt_request.guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + dump_data_pw("server_key: \n", server_key.key, sizeof(server_key.key)); + + dump_data_pw("r2: \n", decrypt_request.r2, sizeof(decrypt_request.r2)); + + /* + * This is *not* the leading 64 bytes, as indicated in MS-BKRP 3.1.4.1.1 + * BACKUPKEY_BACKUP_GUID, it really is the whole key + */ + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + server_key.key, + sizeof(server_key.key)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + rc = gnutls_hmac(hmac_hnd, + decrypt_request.r2, + sizeof(decrypt_request.r2)); + + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + gnutls_hmac_output(hmac_hnd, symkey); + dump_data_pw("symkey: \n", symkey, sizeof(symkey)); + + /* rc4 decrypt sid and secret using sym key */ + cipher_key.data = symkey; + cipher_key.size = sizeof(symkey); + + encrypted_blob = data_blob_const(decrypt_request.rc4encryptedpayload, + decrypt_request.ciphertext_length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &cipher_key, + NULL); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + rc = gnutls_cipher_encrypt2(cipher_hnd, + encrypted_blob.data, + encrypted_blob.length, + encrypted_blob.data, + encrypted_blob.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + ndr_err = ndr_pull_struct_blob_all(&encrypted_blob, mem_ctx, &rc4payload, + (ndr_pull_flags_fn_t)ndr_pull_bkrp_rc4encryptedpayload); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + + if (decrypt_request.payload_length != rc4payload.secret_data.length) { + return WERR_INVALID_PARAMETER; + } + + dump_data_pw("r3: \n", rc4payload.r3, sizeof(rc4payload.r3)); + + /* + * This is *not* the leading 64 bytes, as indicated in MS-BKRP 3.1.4.1.1 + * BACKUPKEY_BACKUP_GUID, it really is the whole key + */ + rc = gnutls_hmac(hmac_hnd, + rc4payload.r3, + sizeof(rc4payload.r3)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + gnutls_hmac_deinit(hmac_hnd, mackey); + + dump_data_pw("mackey: \n", mackey, sizeof(mackey)); + + ndr_err = ndr_push_struct_blob(&sid_blob, mem_ctx, &rc4payload.sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_ERROR; + } + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + mackey, + sizeof(mackey)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + /* SID field */ + rc = gnutls_hmac(hmac_hnd, + sid_blob.data, + sid_blob.length); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + /* Secret field */ + rc = gnutls_hmac(hmac_hnd, + rc4payload.secret_data.data, + rc4payload.secret_data.length); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + gnutls_hmac_deinit(hmac_hnd, mac); + dump_data_pw("mac: \n", mac, sizeof(mac)); + dump_data_pw("rc4payload.mac: \n", rc4payload.mac, sizeof(rc4payload.mac)); + + if (!mem_equal_const_time(mac, rc4payload.mac, sizeof(mac))) { + return WERR_INVALID_ACCESS; + } + + caller_sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + if (!dom_sid_equal(&rc4payload.sid, caller_sid)) { + return WERR_INVALID_ACCESS; + } + + *(r->out.data_out) = rc4payload.secret_data.data; + *(r->out.data_out_len) = rc4payload.secret_data.length; + + return WERR_OK; +} + +/* + * For BACKUPKEY_RESTORE_GUID we need to check the first 4 bytes to + * determine what type of restore is wanted. + * + * See MS-BKRP 3.1.4.1.4 BACKUPKEY_RESTORE_GUID point 1. + */ + +static WERROR bkrp_generic_decrypt_data(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct bkrp_BackupKey *r, struct ldb_context *ldb_ctx) +{ + if (r->in.data_in_len < 4 || r->in.data_in == NULL) { + return WERR_INVALID_PARAMETER; + } + + if (IVAL(r->in.data_in, 0) == BACKUPKEY_SERVER_WRAP_VERSION) { + return bkrp_server_wrap_decrypt_data(dce_call, mem_ctx, r, ldb_ctx); + } + + return bkrp_client_wrap_decrypt_data(dce_call, mem_ctx, r, ldb_ctx); +} + +/* + * We have some data, such as saved website or IMAP passwords that the + * client would like to put into the profile on-disk. This needs to + * be encrypted. This version gives the server the data over the + * network (protected only by the negotiated transport encryption), + * and asks that it be encrypted and returned for long-term storage. + * + * The data is NOT stored in the LSA, but a key to encrypt the data + * will be stored. There is only one active encryption key per domain, + * it is pointed at with G$BCKUPKEY_P in the LSA secrets store. + * + * The potentially multiple valid decryptiong keys (and the encryption + * key) are in turn stored in the LSA secrets store as + * G$BCKUPKEY_keyGuidString. + * + */ + +static WERROR bkrp_server_wrap_encrypt_data(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct bkrp_BackupKey *r ,struct ldb_context *ldb_ctx) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + DATA_BLOB sid_blob, encrypted_blob, server_wrapped_blob; + WERROR werr; + struct dom_sid *caller_sid; + uint8_t symkey[20]; /* SHA-1 hash len */ + uint8_t mackey[20]; /* SHA-1 hash len */ + struct bkrp_rc4encryptedpayload rc4payload; + gnutls_hmac_hd_t hmac_hnd; + struct bkrp_dc_serverwrap_key server_key; + enum ndr_err_code ndr_err; + struct bkrp_server_side_wrapped server_side_wrapped; + struct GUID guid; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t cipher_key; + int rc; + + if (r->in.data_in_len == 0 || r->in.data_in == NULL) { + return WERR_INVALID_PARAMETER; + } + + werr = bkrp_do_retrieve_default_server_wrap_key(mem_ctx, + ldb_ctx, &server_key, + &guid); + + if (!W_ERROR_IS_OK(werr)) { + if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) { + /* Generate the server wrap key since one wasn't found */ + werr = generate_bkrp_server_wrap_key(mem_ctx, + ldb_ctx); + if (!W_ERROR_IS_OK(werr)) { + return WERR_INVALID_PARAMETER; + } + werr = bkrp_do_retrieve_default_server_wrap_key(mem_ctx, + ldb_ctx, + &server_key, + &guid); + + if (W_ERROR_EQUAL(werr, WERR_FILE_NOT_FOUND)) { + /* Ok we really don't manage to get this secret ...*/ + return WERR_FILE_NOT_FOUND; + } + } else { + /* In theory we should NEVER reach this point as it + should only appear in a rodc server */ + /* we do not have the real secret attribute */ + return WERR_INVALID_PARAMETER; + } + } + + caller_sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + dump_data_pw("server_key: \n", server_key.key, sizeof(server_key.key)); + + /* + * This is the key derivation step, so that the HMAC and RC4 + * operations over the user-supplied data are not able to + * disclose the master key. By using random data, the symkey + * and mackey values are unique for this operation, and + * discovering these (by reversing the RC4 over the + * attacker-controlled data) does not return something able to + * be used to decyrpt the encrypted data of other users + */ + generate_random_buffer(server_side_wrapped.r2, sizeof(server_side_wrapped.r2)); + + dump_data_pw("r2: \n", server_side_wrapped.r2, sizeof(server_side_wrapped.r2)); + + generate_random_buffer(rc4payload.r3, sizeof(rc4payload.r3)); + + dump_data_pw("r3: \n", rc4payload.r3, sizeof(rc4payload.r3)); + + + /* + * This is *not* the leading 64 bytes, as indicated in MS-BKRP 3.1.4.1.1 + * BACKUPKEY_BACKUP_GUID, it really is the whole key + */ + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + server_key.key, + sizeof(server_key.key)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + rc = gnutls_hmac(hmac_hnd, + server_side_wrapped.r2, + sizeof(server_side_wrapped.r2)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + gnutls_hmac_output(hmac_hnd, symkey); + dump_data_pw("symkey: \n", symkey, sizeof(symkey)); + + /* + * This is *not* the leading 64 bytes, as indicated in MS-BKRP 3.1.4.1.1 + * BACKUPKEY_BACKUP_GUID, it really is the whole key + */ + rc = gnutls_hmac(hmac_hnd, + rc4payload.r3, + sizeof(rc4payload.r3)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + gnutls_hmac_deinit(hmac_hnd, mackey); + dump_data_pw("mackey: \n", mackey, sizeof(mackey)); + + ndr_err = ndr_push_struct_blob(&sid_blob, mem_ctx, caller_sid, + (ndr_push_flags_fn_t)ndr_push_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_ERROR; + } + + rc4payload.secret_data.data = r->in.data_in; + rc4payload.secret_data.length = r->in.data_in_len; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA1, + mackey, + sizeof(mackey)); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + /* SID field */ + rc = gnutls_hmac(hmac_hnd, + sid_blob.data, + sid_blob.length); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + /* Secret field */ + rc = gnutls_hmac(hmac_hnd, + rc4payload.secret_data.data, + rc4payload.secret_data.length); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + gnutls_hmac_deinit(hmac_hnd, rc4payload.mac); + dump_data_pw("rc4payload.mac: \n", rc4payload.mac, sizeof(rc4payload.mac)); + + rc4payload.sid = *caller_sid; + + ndr_err = ndr_push_struct_blob(&encrypted_blob, mem_ctx, &rc4payload, + (ndr_push_flags_fn_t)ndr_push_bkrp_rc4encryptedpayload); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_ERROR; + } + + /* rc4 encrypt sid and secret using sym key */ + cipher_key.data = symkey; + cipher_key.size = sizeof(symkey); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &cipher_key, + NULL); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + rc = gnutls_cipher_encrypt2(cipher_hnd, + encrypted_blob.data, + encrypted_blob.length, + encrypted_blob.data, + encrypted_blob.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc != GNUTLS_E_SUCCESS) { + return gnutls_error_to_werror(rc, WERR_INTERNAL_ERROR); + } + + /* create server wrap structure */ + + server_side_wrapped.payload_length = rc4payload.secret_data.length; + server_side_wrapped.ciphertext_length = encrypted_blob.length; + server_side_wrapped.guid = guid; + server_side_wrapped.rc4encryptedpayload = encrypted_blob.data; + + ndr_err = ndr_push_struct_blob(&server_wrapped_blob, mem_ctx, &server_side_wrapped, + (ndr_push_flags_fn_t)ndr_push_bkrp_server_side_wrapped); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_ERROR; + } + + *(r->out.data_out) = server_wrapped_blob.data; + *(r->out.data_out_len) = server_wrapped_blob.length; + + return WERR_OK; +} + +static WERROR dcesrv_bkrp_BackupKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, struct bkrp_BackupKey *r) +{ + WERROR error = WERR_INVALID_PARAMETER; + struct ldb_context *ldb_ctx; + bool is_rodc; + const char *addr = "unknown"; + /* At which level we start to add more debug of what is done in the protocol */ + const int debuglevel = 4; + + if (DEBUGLVL(debuglevel)) { + const struct tsocket_address *remote_address; + remote_address = dcesrv_connection_get_remote_address(dce_call->conn); + if (tsocket_address_is_inet(remote_address, "ip")) { + addr = tsocket_address_inet_addr_string(remote_address, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(addr); + } + } + + if (lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx) != ROLE_ACTIVE_DIRECTORY_DC) { + return WERR_NOT_SUPPORTED; + } + + /* + * Save the current remote session details so they can used by the + * audit logging module. This allows the audit logging to report the + * remote users details, rather than the system users details. + */ + ldb_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + + if (samdb_rodc(ldb_ctx, &is_rodc) != LDB_SUCCESS) { + talloc_unlink(mem_ctx, ldb_ctx); + return WERR_INVALID_PARAMETER; + } + + if (!is_rodc) { + if(strncasecmp(GUID_string(mem_ctx, r->in.guidActionAgent), + BACKUPKEY_RESTORE_GUID, strlen(BACKUPKEY_RESTORE_GUID)) == 0) { + DEBUG(debuglevel, ("Client %s requested to decrypt a wrapped secret\n", addr)); + error = bkrp_generic_decrypt_data(dce_call, mem_ctx, r, ldb_ctx); + } + + if (strncasecmp(GUID_string(mem_ctx, r->in.guidActionAgent), + BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID, strlen(BACKUPKEY_RETRIEVE_BACKUP_KEY_GUID)) == 0) { + DEBUG(debuglevel, ("Client %s requested certificate for client wrapped secret\n", addr)); + error = bkrp_retrieve_client_wrap_key(dce_call, mem_ctx, r, ldb_ctx); + } + + if (strncasecmp(GUID_string(mem_ctx, r->in.guidActionAgent), + BACKUPKEY_RESTORE_GUID_WIN2K, strlen(BACKUPKEY_RESTORE_GUID_WIN2K)) == 0) { + DEBUG(debuglevel, ("Client %s requested to decrypt a server side wrapped secret\n", addr)); + error = bkrp_server_wrap_decrypt_data(dce_call, mem_ctx, r, ldb_ctx); + } + + if (strncasecmp(GUID_string(mem_ctx, r->in.guidActionAgent), + BACKUPKEY_BACKUP_GUID, strlen(BACKUPKEY_BACKUP_GUID)) == 0) { + DEBUG(debuglevel, ("Client %s requested a server wrapped secret\n", addr)); + error = bkrp_server_wrap_encrypt_data(dce_call, mem_ctx, r, ldb_ctx); + } + } + /*else: I am a RODC so I don't handle backup key protocol */ + + talloc_unlink(mem_ctx, ldb_ctx); + return error; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_backupkey_s.c" diff --git a/source4/rpc_server/browser/dcesrv_browser.c b/source4/rpc_server/browser/dcesrv_browser.c new file mode 100644 index 0000000..797eb86 --- /dev/null +++ b/source4/rpc_server/browser/dcesrv_browser.c @@ -0,0 +1,169 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the browser pipe + + Copyright (C) Stefan Metzmacher 2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_browser.h" + + +/* + BrowserrServerEnum +*/ +static void dcesrv_BrowserrServerEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrServerEnum *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrDebugCall +*/ +static void dcesrv_BrowserrDebugCall(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrDebugCall *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrQueryOtherDomains +*/ +static WERROR dcesrv_BrowserrQueryOtherDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrQueryOtherDomains *r) +{ + struct BrowserrSrvInfo100Ctr *ctr100; + + switch (r->in.info->level) { + case 100: + if (!r->in.info->info.info100) { + return WERR_INVALID_PARAMETER; + } + + ctr100 = talloc(mem_ctx, struct BrowserrSrvInfo100Ctr); + W_ERROR_HAVE_NO_MEMORY(ctr100); + + ctr100->entries_read = 0; + ctr100->entries = talloc_zero_array(ctr100, struct srvsvc_NetSrvInfo100, + ctr100->entries_read); + W_ERROR_HAVE_NO_MEMORY(ctr100->entries); + + r->out.info->info.info100 = ctr100; + *r->out.total_entries = ctr100->entries_read; + return WERR_OK; + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + BrowserrResetNetlogonState +*/ +static void dcesrv_BrowserrResetNetlogonState(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrResetNetlogonState *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrDebugTrace +*/ +static void dcesrv_BrowserrDebugTrace(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrDebugTrace *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrQueryStatistics +*/ +static void dcesrv_BrowserrQueryStatistics(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrQueryStatistics *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserResetStatistics +*/ +static void dcesrv_BrowserResetStatistics(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserResetStatistics *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + NetrBrowserStatisticsClear +*/ +static void dcesrv_NetrBrowserStatisticsClear(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct NetrBrowserStatisticsClear *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + NetrBrowserStatisticsGet +*/ +static void dcesrv_NetrBrowserStatisticsGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct NetrBrowserStatisticsGet *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrSetNetlogonState +*/ +static void dcesrv_BrowserrSetNetlogonState(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrSetNetlogonState *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrQueryEmulatedDomains +*/ +static void dcesrv_BrowserrQueryEmulatedDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrQueryEmulatedDomains *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + BrowserrServerEnumEx +*/ +static void dcesrv_BrowserrServerEnumEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct BrowserrServerEnumEx *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_browser_s.c" diff --git a/source4/rpc_server/common/common.h b/source4/rpc_server/common/common.h new file mode 100644 index 0000000..b57ddf2 --- /dev/null +++ b/source4/rpc_server/common/common.h @@ -0,0 +1,44 @@ +/* + Unix SMB/CIFS implementation. + + common macros for the dcerpc server interfaces + + Copyright (C) Stefan (metze) Metzmacher 2004 + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef _DCERPC_SERVER_COMMON_H_ +#define _DCERPC_SERVER_COMMON_H_ + +struct share_config; +struct dcesrv_connection; +struct dcesrv_context; +struct dcesrv_context; +struct dcesrv_call_state; +struct ndr_interface_table; +struct ncacn_packet; +struct auth_session_info; + +struct dcerpc_server_info { + const char *domain_name; + uint32_t version_major; + uint32_t version_minor; + uint32_t version_build; +}; + +#include "rpc_server/common/proto.h" + +#endif /* _DCERPC_SERVER_COMMON_H_ */ diff --git a/source4/rpc_server/common/forward.c b/source4/rpc_server/common/forward.c new file mode 100644 index 0000000..4ae8c1b --- /dev/null +++ b/source4/rpc_server/common/forward.c @@ -0,0 +1,134 @@ +/* + Unix SMB/CIFS implementation. + + forwarding of RPC calls to other tasks + + Copyright (C) Andrew Tridgell 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "rpc_server/common/common.h" +#include "messaging/irpc.h" +#include "auth/auth.h" + + +struct dcesrv_forward_state { + const char *opname; + struct dcesrv_call_state *dce_call; +}; + +/* + called when the forwarded rpc request is finished + */ +static void dcesrv_irpc_forward_callback(struct tevent_req *subreq) +{ + struct dcesrv_forward_state *st = + tevent_req_callback_data(subreq, + struct dcesrv_forward_state); + const char *opname = st->opname; + NTSTATUS status; + + status = dcerpc_binding_handle_call_recv(subreq); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("IRPC callback failed for %s - %s\n", + opname, nt_errstr(status))); + st->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + } + status = dcesrv_reply(st->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s_handler: dcesrv_reply() failed - %s\n", + opname, nt_errstr(status))); + } +} + + + +/** + * Forward a RPC call using IRPC to another task + */ +void dcesrv_irpc_forward_rpc_call(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + void *r, uint32_t callid, + const struct ndr_interface_table *ndr_table, + const char *dest_task, const char *opname, + uint32_t timeout) +{ + struct dcesrv_forward_state *st; + struct dcerpc_binding_handle *binding_handle; + struct tevent_req *subreq; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + st = talloc(mem_ctx, struct dcesrv_forward_state); + if (st == NULL) { + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return; + } + + st->dce_call = dce_call; + st->opname = opname; + + /* if the caller has said they can't support async calls + then fail the call */ + if (!(dce_call->state_flags & DCESRV_CALL_STATE_FLAG_MAY_ASYNC)) { + /* we're not allowed to reply async */ + DEBUG(0,("%s: Not available synchronously\n", dest_task)); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return; + } + + binding_handle = irpc_binding_handle_by_name(st, + imsg_ctx, + dest_task, + ndr_table); + if (binding_handle == NULL) { + DEBUG(0,("%s: Failed to forward request to %s task\n", + opname, dest_task)); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return; + } + + /* reset timeout for the handle */ + dcerpc_binding_handle_set_timeout(binding_handle, timeout); + + /* add security token to the handle*/ + irpc_binding_handle_add_security_token(binding_handle, + session_info->security_token); + + /* forward the call */ + subreq = dcerpc_binding_handle_call_send(st, dce_call->event_ctx, + binding_handle, + NULL, ndr_table, + callid, + dce_call, r); + if (subreq == NULL) { + DEBUG(0,("%s: Failed to forward request to %s task\n", + opname, dest_task)); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return; + } + + /* mark the request as replied async */ + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + + /* setup the callback */ + tevent_req_set_callback(subreq, dcesrv_irpc_forward_callback, st); +} diff --git a/source4/rpc_server/common/loadparm.c b/source4/rpc_server/common/loadparm.c new file mode 100644 index 0000000..174063e --- /dev/null +++ b/source4/rpc_server/common/loadparm.c @@ -0,0 +1,45 @@ +/* + Unix SMB/CIFS implementation. + DCERPC server info param function + Moved into rpc_server/common to break dependencies to rpc_server from param + Copyright (C) Karl Auer 1993-1998 + + Largely re-written by Andrew Tridgell, September 1994 + + Copyright (C) Simo Sorce 2001 + Copyright (C) Alexander Bokovoy 2002 + Copyright (C) Stefan (metze) Metzmacher 2002 + Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003. + Copyright (C) James Myers 2003 <myersjj@samba.org> + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/param/param.h" +#include "rpc_server/common/common.h" + +_PUBLIC_ struct dcerpc_server_info *lpcfg_dcerpc_server_info(TALLOC_CTX *mem_ctx, struct loadparm_context *lp_ctx) +{ + struct dcerpc_server_info *ret = talloc_zero(mem_ctx, struct dcerpc_server_info); + + ret->domain_name = talloc_reference(mem_ctx, lpcfg_workgroup(lp_ctx)); + ret->version_major = lpcfg_parm_int(lp_ctx, NULL, "server_info", "version_major", 5); + ret->version_minor = lpcfg_parm_int(lp_ctx, NULL, "server_info", "version_minor", 2); + ret->version_build = lpcfg_parm_int(lp_ctx, NULL, "server_info", "version_build", 3790); + + return ret; +} + diff --git a/source4/rpc_server/common/server_info.c b/source4/rpc_server/common/server_info.c new file mode 100644 index 0000000..34228c3 --- /dev/null +++ b/source4/rpc_server/common/server_info.c @@ -0,0 +1,319 @@ +/* + Unix SMB/CIFS implementation. + + common server info functions + + Copyright (C) Stefan (metze) Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/srvsvc.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "auth/auth.h" +#include "param/param.h" +#include "rpc_server/common/common.h" +#include "libds/common/roles.h" +#include "auth/auth_util.h" +#include "lib/tsocket/tsocket.h" + +/* + Here are common server info functions used by some dcerpc server interfaces +*/ + +/* This hardcoded value should go into a ldb database! */ +enum srvsvc_PlatformId dcesrv_common_get_platform_id(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + enum srvsvc_PlatformId id; + + id = lpcfg_parm_int(dce_ctx->lp_ctx, NULL, "server_info", "platform_id", PLATFORM_ID_NT); + + return id; +} + +const char *dcesrv_common_get_server_name(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, const char *server_unc) +{ + const char *p = server_unc; + + /* if there's no string return our NETBIOS name */ + if (!p) { + return talloc_strdup(mem_ctx, lpcfg_netbios_name(dce_ctx->lp_ctx)); + } + + /* if there're '\\\\' in front remove them otherwise just pass the string */ + if (p[0] == '\\' && p[1] == '\\') { + p += 2; + } + + return talloc_strdup(mem_ctx, p); +} + + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_server_type(TALLOC_CTX *mem_ctx, struct tevent_context *event_ctx, struct dcesrv_context *dce_ctx) +{ + int default_server_announce = 0; + default_server_announce |= SV_TYPE_WORKSTATION; + default_server_announce |= SV_TYPE_SERVER; + default_server_announce |= SV_TYPE_SERVER_UNIX; + + default_server_announce |= SV_TYPE_SERVER_NT; + default_server_announce |= SV_TYPE_NT; + + switch (lpcfg_server_role(dce_ctx->lp_ctx)) { + case ROLE_DOMAIN_MEMBER: + default_server_announce |= SV_TYPE_DOMAIN_MEMBER; + break; + case ROLE_ACTIVE_DIRECTORY_DC: + { + struct ldb_context *samctx; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + break; + } + /* open main ldb */ + samctx = samdb_connect( + tmp_ctx, + event_ctx, + dce_ctx->lp_ctx, + anonymous_session(tmp_ctx, dce_ctx->lp_ctx), + NULL, + 0); + if (samctx == NULL) { + DEBUG(2,("Unable to open samdb in determining server announce flags\n")); + } else { + /* Determine if we are the pdc */ + bool is_pdc = samdb_is_pdc(samctx); + if (is_pdc) { + default_server_announce |= SV_TYPE_DOMAIN_CTRL; + } else { + default_server_announce |= SV_TYPE_DOMAIN_BAKCTRL; + } + } + /* Close it */ + talloc_free(tmp_ctx); + break; + } + case ROLE_STANDALONE: + default: + break; + } + if (lpcfg_time_server(dce_ctx->lp_ctx)) + default_server_announce |= SV_TYPE_TIME_SOURCE; + + if (lpcfg_host_msdfs(dce_ctx->lp_ctx)) + default_server_announce |= SV_TYPE_DFS_SERVER; + + +#if 0 + { + /* TODO: announce us as print server when we are a print server */ + bool is_print_server = false; + if (is_print_server) { + default_server_announce |= SV_TYPE_PRINTQ_SERVER; + } + } +#endif + return default_server_announce; +} + +/* This hardcoded value should go into a ldb database! */ +const char *dcesrv_common_get_lan_root(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return talloc_strdup(mem_ctx, ""); +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_users(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return -1; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_disc(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return 15; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_hidden(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return 0; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_announce(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return 240; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_anndelta(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return 3000; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_licenses(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return 0; +} + +/* This hardcoded value should go into a ldb database! */ +const char *dcesrv_common_get_userpath(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx) +{ + return talloc_strdup(mem_ctx, "c:\\"); +} + +#define INVALID_SHARE_NAME_CHARS " \"*+,./:;<=>?[\\]|" + +bool dcesrv_common_validate_share_name(TALLOC_CTX *mem_ctx, const char *share_name) +{ + if (strpbrk(share_name, INVALID_SHARE_NAME_CHARS)) { + return false; + } + + return true; +} + +/* + * call_session_info is session info for samdb. call_audit_session_info is for + * auditing and may be NULL. + */ +struct ldb_context *dcesrv_samdb_connect_session_info( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *dce_call, + const struct auth_session_info *call_session_info, + const struct auth_session_info *call_audit_session_info) +{ + struct ldb_context *samdb = NULL; + struct auth_session_info *user_session_info = NULL; + struct auth_session_info *audit_session_info = NULL; + struct tsocket_address *remote_address = NULL; + + user_session_info = copy_session_info(mem_ctx, call_session_info); + if (user_session_info == NULL) { + return NULL; + } + + if (call_audit_session_info != NULL) { + audit_session_info = copy_session_info(mem_ctx, call_audit_session_info); + if (audit_session_info == NULL) { + talloc_free(user_session_info); + return NULL; + } + } + + if (dce_call->conn->remote_address != NULL) { + remote_address = tsocket_address_copy(dce_call->conn->remote_address, + user_session_info); + if (remote_address == NULL) { + TALLOC_FREE(audit_session_info); + talloc_free(user_session_info); + return NULL; + } + } + + /* + * We need to make sure every argument + * stays arround for the lifetime of 'samdb', + * typically it is allocated on the scope of + * an assoc group, so we can't reference dce_call->conn, + * as the assoc group may stay when the current connection + * gets disconnected. + * + * The following are global per process: + * - dce_call->conn->dce_ctx->lp_ctx + * - dce_call->event_ctx + * - system_session + * + * We make a copy of: + * - dce_call->conn->remote_address + * - dce_call->auth_state->session_info + */ + samdb = samdb_connect( + mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + user_session_info, + remote_address, + 0); + if (samdb == NULL) { + TALLOC_FREE(audit_session_info); + talloc_free(user_session_info); + return NULL; + } + talloc_move(samdb, &user_session_info); + + if (audit_session_info != NULL) { + int ret; + + talloc_steal(samdb, audit_session_info); + + ret = ldb_set_opaque(samdb, + DSDB_NETWORK_SESSION_INFO, + audit_session_info); + if (ret != LDB_SUCCESS) { + talloc_free(samdb); + return NULL; + } + } + + return samdb; +} + +/* + * Open an ldb connection under the system session and save the remote users + * session details in a ldb_opaque. This will allow the audit logging to + * log the original session for operations performed in the system session. + * + * Access checks are required by the caller! + */ +struct ldb_context *dcesrv_samdb_connect_as_system( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *dce_call) +{ + const struct auth_session_info *system_session_info = NULL; + const struct auth_session_info *call_session_info = NULL; + + system_session_info = system_session(dce_call->conn->dce_ctx->lp_ctx); + if (system_session_info == NULL) { + return NULL; + } + + call_session_info = dcesrv_call_session_info(dce_call); + + return dcesrv_samdb_connect_session_info(mem_ctx, dce_call, + system_session_info, call_session_info); +} + +/* + * Open an ldb connection under the remote users session details. + * + * Access checks are done at the ldb level. + */ +struct ldb_context *dcesrv_samdb_connect_as_user( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *dce_call) +{ + const struct auth_session_info *call_session_info = NULL; + + call_session_info = dcesrv_call_session_info(dce_call); + + return dcesrv_samdb_connect_session_info(mem_ctx, dce_call, + call_session_info, NULL); +} diff --git a/source4/rpc_server/common/share_info.c b/source4/rpc_server/common/share_info.c new file mode 100644 index 0000000..d7ed5ee --- /dev/null +++ b/source4/rpc_server/common/share_info.c @@ -0,0 +1,123 @@ +/* + Unix SMB/CIFS implementation. + + common share info functions + + Copyright (C) Stefan (metze) Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "param/share.h" +#include "librpc/gen_ndr/srvsvc.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/share.h" + +#undef strcasecmp + +/* + Here are common server info functions used by some dcerpc server interfaces +*/ + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_share_permissions(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + return 0; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_share_current_users(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + return 1; +} + +/* This hardcoded value should go into a ldb database! */ +enum srvsvc_ShareType dcesrv_common_get_share_type(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + /* for disk share 0x00000000 + * for print share 0x00000001 + * for IPC$ share 0x00000003 + * + * administrative shares: + * ADMIN$, IPC$, C$, D$, E$ ... are type |= 0x80000000 + * this ones are hidden in NetShareEnum, but shown in NetShareEnumAll + */ + enum srvsvc_ShareType share_type = 0; + char *sharetype; + + if (!share_bool_option(scfg, SHARE_BROWSEABLE, SHARE_BROWSEABLE_DEFAULT)) { + share_type |= STYPE_HIDDEN; + } + + sharetype = share_string_option(mem_ctx, scfg, SHARE_TYPE, SHARE_TYPE_DEFAULT); + if (sharetype && strcasecmp(sharetype, "IPC") == 0) { + share_type |= STYPE_IPC; + TALLOC_FREE(sharetype); + return share_type; + } + + if (sharetype && strcasecmp(sharetype, "PRINTER") == 0) { + share_type |= STYPE_PRINTQ; + TALLOC_FREE(sharetype); + return share_type; + } + + TALLOC_FREE(sharetype); + share_type |= STYPE_DISKTREE; + + return share_type; +} + +/* This hardcoded value should go into a ldb database! */ +const char *dcesrv_common_get_share_path(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + char *sharetype; + char *p; + char *path; + + sharetype = share_string_option(mem_ctx, scfg, SHARE_TYPE, SHARE_TYPE_DEFAULT); + + if (sharetype && strcasecmp(sharetype, "IPC") == 0) { + TALLOC_FREE(sharetype); + return talloc_strdup(mem_ctx, ""); + } + + TALLOC_FREE(sharetype); + + p = share_string_option(mem_ctx, scfg, SHARE_PATH, ""); + if (!p) { + return NULL; + } + if (p[0] == '\0') { + return p; + } + all_string_sub(p, "/", "\\", 0); + + path = talloc_asprintf(mem_ctx, "C:%s", p); + TALLOC_FREE(p); + return path; +} + +/* This hardcoded value should go into a ldb database! */ +uint32_t dcesrv_common_get_share_dfs_flags(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + return 0; +} + +/* This hardcoded value should go into a ldb database! */ +struct security_descriptor *dcesrv_common_get_security_descriptor(TALLOC_CTX *mem_ctx, struct dcesrv_context *dce_ctx, struct share_config *scfg) +{ + return NULL; +} diff --git a/source4/rpc_server/dcerpc_server.c b/source4/rpc_server/dcerpc_server.c new file mode 100644 index 0000000..c896b35 --- /dev/null +++ b/source4/rpc_server/dcerpc_server.c @@ -0,0 +1,715 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc core code + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Stefan (metze) Metzmacher 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/credentials/credentials.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/dcerpc_server_proto.h" +#include "param/param.h" +#include "samba/service_stream.h" +#include "lib/tsocket/tsocket.h" +#include "lib/socket/socket.h" +#include "samba/process_model.h" +#include "lib/util/samba_modules.h" +#include "lib/util/tevent_ntstatus.h" + +/* + take a reference to an existing association group + */ +static struct dcesrv_assoc_group *dcesrv_assoc_group_reference(struct dcesrv_connection *conn, + uint32_t id) +{ + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group; + void *id_ptr = NULL; + + /* find an association group given a assoc_group_id */ + id_ptr = idr_find(conn->dce_ctx->assoc_groups_idr, id); + if (id_ptr == NULL) { + DBG_NOTICE("Failed to find assoc_group 0x%08x\n", id); + return NULL; + } + assoc_group = talloc_get_type_abort(id_ptr, struct dcesrv_assoc_group); + + if (assoc_group->transport != transport) { + const char *at = + derpc_transport_string_by_transport( + assoc_group->transport); + const char *ct = + derpc_transport_string_by_transport( + transport); + + DBG_NOTICE("assoc_group 0x%08x (transport %s) " + "is not available on transport %s", + id, at, ct); + return NULL; + } + + return talloc_reference(conn, assoc_group); +} + +static int dcesrv_assoc_group_destructor(struct dcesrv_assoc_group *assoc_group) +{ + int ret; + ret = idr_remove(assoc_group->dce_ctx->assoc_groups_idr, assoc_group->id); + if (ret != 0) { + DEBUG(0,(__location__ ": Failed to remove assoc_group 0x%08x\n", + assoc_group->id)); + } + return 0; +} + +/* + allocate a new association group + */ +static struct dcesrv_assoc_group *dcesrv_assoc_group_new(struct dcesrv_connection *conn) +{ + struct dcesrv_context *dce_ctx = conn->dce_ctx; + const struct dcesrv_endpoint *endpoint = conn->endpoint; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(endpoint->ep_description); + struct dcesrv_assoc_group *assoc_group; + int id; + + assoc_group = talloc_zero(conn, struct dcesrv_assoc_group); + if (assoc_group == NULL) { + return NULL; + } + + id = idr_get_new_random(dce_ctx->assoc_groups_idr, assoc_group, UINT16_MAX); + if (id == -1) { + talloc_free(assoc_group); + DEBUG(0,(__location__ ": Out of association groups!\n")); + return NULL; + } + + assoc_group->transport = transport; + assoc_group->id = id; + assoc_group->dce_ctx = dce_ctx; + + talloc_set_destructor(assoc_group, dcesrv_assoc_group_destructor); + + return assoc_group; +} + +NTSTATUS dcesrv_assoc_group_find_s4( + struct dcesrv_call_state *call, + void *private_data) +{ + /* + if provided, check the assoc_group is valid + */ + if (call->pkt.u.bind.assoc_group_id != 0) { + call->conn->assoc_group = + dcesrv_assoc_group_reference(call->conn, + call->pkt.u.bind.assoc_group_id); + } else { + call->conn->assoc_group = dcesrv_assoc_group_new(call->conn); + } + + /* + * The NETLOGON server does not use handles and so + * there is no need to support association groups, but + * we need to give back a number regardless. + * + * We have to do this when it is not run as a single process, + * because then it can't see the other valid association + * groups. We handle this genericly for all endpoints not + * running in single process mode. + * + * We know which endpoint we are on even before checking the + * iface UUID, so for simplicity we enforce the same policy + * for all interfaces on the endpoint. + * + * This means that where NETLOGON + * shares an endpoint (such as ncalrpc or if 'lsa over + * netlogon' is set) we will still check association groups. + * + */ + + if (call->conn->assoc_group == NULL && + !call->conn->endpoint->use_single_process) { + call->conn->assoc_group + = dcesrv_assoc_group_new(call->conn); + } + + if (call->conn->assoc_group == NULL) { + /* TODO Return correct status */ + return NT_STATUS_UNSUCCESSFUL; + } + + return NT_STATUS_OK; +} + +void dcerpc_server_init(struct loadparm_context *lp_ctx) +{ + static bool initialized; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); + STATIC_dcerpc_server_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_dcerpc_server_MODULES }; + init_module_fn *shared_init; + + if (initialized) { + return; + } + initialized = true; + + shared_init = load_samba_modules(NULL, "dcerpc_server"); + + run_init_functions(NULL, static_init); + run_init_functions(NULL, shared_init); + + talloc_free(shared_init); +} + +struct dcesrv_socket_context { + const struct dcesrv_endpoint *endpoint; + struct dcesrv_context *dcesrv_ctx; +}; + +static void dcesrv_sock_accept(struct stream_connection *srv_conn) +{ + NTSTATUS status; + struct dcesrv_socket_context *dcesrv_sock = + talloc_get_type(srv_conn->private_data, struct dcesrv_socket_context); + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dcesrv_sock->endpoint->ep_description); + struct dcesrv_connection *dcesrv_conn = NULL; + int ret; + struct loadparm_context *lp_ctx = dcesrv_sock->dcesrv_ctx->lp_ctx; + + dcesrv_cleanup_broken_connections(dcesrv_sock->dcesrv_ctx); + + if (!srv_conn->session_info) { + status = auth_anonymous_session_info(srv_conn, + lp_ctx, + &srv_conn->session_info); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcesrv_sock_accept: auth_anonymous_session_info failed: %s\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + } + + /* + * This fills in dcesrv_conn->endpoint with the endpoint + * associated with the socket. From this point on we know + * which (group of) services we are handling, but not the + * specific interface. + */ + + status = dcesrv_endpoint_connect(dcesrv_sock->dcesrv_ctx, + srv_conn, + dcesrv_sock->endpoint, + srv_conn->session_info, + srv_conn->event.ctx, + DCESRV_CALL_STATE_FLAG_MAY_ASYNC, + &dcesrv_conn); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcesrv_sock_accept: dcesrv_endpoint_connect failed: %s\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + + dcesrv_conn->transport.private_data = srv_conn; + dcesrv_conn->transport.report_output_data = dcesrv_sock_report_output_data; + dcesrv_conn->transport.terminate_connection = dcesrv_transport_terminate_connection_s4; + + TALLOC_FREE(srv_conn->event.fde); + + dcesrv_conn->send_queue = tevent_queue_create(dcesrv_conn, "dcesrv send queue"); + if (!dcesrv_conn->send_queue) { + status = NT_STATUS_NO_MEMORY; + DEBUG(0,("dcesrv_sock_accept: tevent_queue_create(%s)\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + + if (transport == NCACN_NP) { + dcesrv_conn->stream = talloc_move(dcesrv_conn, + &srv_conn->tstream); + } else { + ret = tstream_bsd_existing_socket(dcesrv_conn, + socket_get_fd(srv_conn->socket), + &dcesrv_conn->stream); + if (ret == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(0, ("dcesrv_sock_accept: " + "failed to setup tstream: %s\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + socket_set_flags(srv_conn->socket, SOCKET_FLAG_NOCLOSE); + } + + dcesrv_conn->local_address = srv_conn->local_address; + dcesrv_conn->remote_address = srv_conn->remote_address; + + if (transport == NCALRPC) { + uid_t uid; + gid_t gid; + int sock_fd; + + sock_fd = socket_get_fd(srv_conn->socket); + if (sock_fd == -1) { + stream_terminate_connection( + srv_conn, "socket_get_fd failed\n"); + return; + } + + ret = getpeereid(sock_fd, &uid, &gid); + if (ret == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(0, ("dcesrv_sock_accept: " + "getpeereid() failed for NCALRPC: %s\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + if (uid == dcesrv_conn->dce_ctx->initial_euid) { + struct tsocket_address *r = NULL; + + ret = tsocket_address_unix_from_path(dcesrv_conn, + AS_SYSTEM_MAGIC_PATH_TOKEN, + &r); + if (ret == -1) { + status = map_nt_error_from_unix_common(errno); + DEBUG(0, ("dcesrv_sock_accept: " + "tsocket_address_unix_from_path() failed for NCALRPC: %s\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + dcesrv_conn->remote_address = r; + } + } + + srv_conn->private_data = dcesrv_conn; + + status = dcesrv_connection_loop_start(dcesrv_conn); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcesrv_sock_accept: dcerpc_read_fragment_buffer_send(%s)\n", + nt_errstr(status))); + stream_terminate_connection(srv_conn, nt_errstr(status)); + return; + } + + return; +} + +static void dcesrv_sock_recv(struct stream_connection *conn, uint16_t flags) +{ + struct dcesrv_connection *dce_conn = talloc_get_type(conn->private_data, + struct dcesrv_connection); + dcesrv_terminate_connection(dce_conn, "dcesrv_sock_recv triggered"); +} + +static void dcesrv_sock_send(struct stream_connection *conn, uint16_t flags) +{ + struct dcesrv_connection *dce_conn = talloc_get_type(conn->private_data, + struct dcesrv_connection); + dcesrv_terminate_connection(dce_conn, "dcesrv_sock_send triggered"); +} + + +static const struct stream_server_ops dcesrv_stream_ops = { + .name = "rpc", + .accept_connection = dcesrv_sock_accept, + .recv_handler = dcesrv_sock_recv, + .send_handler = dcesrv_sock_send, +}; + +static NTSTATUS dcesrv_add_ep_unix(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context) +{ + struct dcesrv_socket_context *dcesrv_sock; + uint16_t port = 1; + NTSTATUS status; + const char *endpoint; + + dcesrv_sock = talloc_zero(event_ctx, struct dcesrv_socket_context); + NT_STATUS_HAVE_NO_MEMORY(dcesrv_sock); + + /* remember the endpoint of this socket */ + dcesrv_sock->endpoint = e; + dcesrv_sock->dcesrv_ctx = talloc_reference(dcesrv_sock, dce_ctx); + + endpoint = dcerpc_binding_get_string_option(e->ep_description, "endpoint"); + + status = stream_setup_socket(dcesrv_sock, event_ctx, lp_ctx, + model_ops, &dcesrv_stream_ops, + "unix", endpoint, &port, + lpcfg_socket_options(lp_ctx), + dcesrv_sock, process_context); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("service_setup_stream_socket(path=%s) failed - %s\n", + endpoint, nt_errstr(status))); + } + + return status; +} + +static NTSTATUS dcesrv_add_ep_ncalrpc(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context) +{ + struct dcesrv_socket_context *dcesrv_sock; + uint16_t port = 1; + char *full_path; + NTSTATUS status; + const char *endpoint; + + endpoint = dcerpc_binding_get_string_option(e->ep_description, "endpoint"); + + if (endpoint == NULL) { + /* + * No identifier specified: use DEFAULT. + * + * TODO: DO NOT hardcode this value anywhere else. Rather, specify + * no endpoint and let the epmapper worry about it. + */ + endpoint = "DEFAULT"; + status = dcerpc_binding_set_string_option(e->ep_description, + "endpoint", + endpoint); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcerpc_binding_set_string_option() failed - %s\n", + nt_errstr(status))); + return status; + } + } + + full_path = talloc_asprintf(dce_ctx, "%s/%s", lpcfg_ncalrpc_dir(lp_ctx), + endpoint); + + dcesrv_sock = talloc_zero(event_ctx, struct dcesrv_socket_context); + NT_STATUS_HAVE_NO_MEMORY(dcesrv_sock); + + /* remember the endpoint of this socket */ + dcesrv_sock->endpoint = e; + dcesrv_sock->dcesrv_ctx = talloc_reference(dcesrv_sock, dce_ctx); + + status = stream_setup_socket(dcesrv_sock, event_ctx, lp_ctx, + model_ops, &dcesrv_stream_ops, + "unix", full_path, &port, + lpcfg_socket_options(lp_ctx), + dcesrv_sock, process_context); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("service_setup_stream_socket(identifier=%s,path=%s) failed - %s\n", + endpoint, full_path, nt_errstr(status))); + } + return status; +} + +static NTSTATUS dcesrv_add_ep_np(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context) +{ + struct dcesrv_socket_context *dcesrv_sock; + NTSTATUS status; + const char *endpoint; + + endpoint = dcerpc_binding_get_string_option(e->ep_description, "endpoint"); + if (endpoint == NULL) { + DEBUG(0, ("Endpoint mandatory for named pipes\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + dcesrv_sock = talloc_zero(event_ctx, struct dcesrv_socket_context); + NT_STATUS_HAVE_NO_MEMORY(dcesrv_sock); + + /* remember the endpoint of this socket */ + dcesrv_sock->endpoint = e; + dcesrv_sock->dcesrv_ctx = talloc_reference(dcesrv_sock, dce_ctx); + + status = tstream_setup_named_pipe(dce_ctx, event_ctx, lp_ctx, + model_ops, &dcesrv_stream_ops, + endpoint, + dcesrv_sock, process_context); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("stream_setup_named_pipe(pipe=%s) failed - %s\n", + endpoint, nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +/* + add a socket address to the list of events, one event per dcerpc endpoint +*/ +static NTSTATUS add_socket_rpc_tcp_iface(struct dcesrv_context *dce_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + const char *address, + void *process_context) +{ + struct dcesrv_socket_context *dcesrv_sock; + uint16_t port = 0; + NTSTATUS status; + const char *endpoint; + char port_str[6]; + + endpoint = dcerpc_binding_get_string_option(e->ep_description, "endpoint"); + if (endpoint != NULL) { + port = atoi(endpoint); + } + + dcesrv_sock = talloc_zero(event_ctx, struct dcesrv_socket_context); + NT_STATUS_HAVE_NO_MEMORY(dcesrv_sock); + + /* remember the endpoint of this socket */ + dcesrv_sock->endpoint = e; + dcesrv_sock->dcesrv_ctx = talloc_reference(dcesrv_sock, dce_ctx); + + status = stream_setup_socket(dcesrv_sock, event_ctx, dce_ctx->lp_ctx, + model_ops, &dcesrv_stream_ops, + "ip", address, &port, + lpcfg_socket_options(dce_ctx->lp_ctx), + dcesrv_sock, process_context); + if (!NT_STATUS_IS_OK(status)) { + struct dcesrv_if_list *iface; + DEBUG(0,("service_setup_stream_socket(address=%s,port=%u) for ", + address, port)); + for (iface = e->interface_list; iface; iface = iface->next) { + DEBUGADD(0, ("%s ", iface->iface->name)); + } + DEBUGADD(0, ("failed - %s\n", + nt_errstr(status))); + return status; + } + + snprintf(port_str, sizeof(port_str), "%u", port); + + status = dcerpc_binding_set_string_option(e->ep_description, + "endpoint", port_str); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcerpc_binding_set_string_option(endpoint, %s) failed - %s\n", + port_str, nt_errstr(status))); + return status; + } else { + struct dcesrv_if_list *iface; + DEBUG(4,("Successfully listening on ncacn_ip_tcp endpoint [%s]:[%s] for ", + address, port_str)); + for (iface = e->interface_list; iface; iface = iface->next) { + DEBUGADD(4, ("%s ", iface->iface->name)); + } + DEBUGADD(4, ("\n")); + } + + return NT_STATUS_OK; +} + +#include "lib/socket/netif.h" /* Included here to work around the fact that socket_wrapper redefines bind() */ + +static NTSTATUS dcesrv_add_ep_tcp(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context) +{ + NTSTATUS status; + + /* Add TCP/IP sockets */ + if (lpcfg_interfaces(lp_ctx) && lpcfg_bind_interfaces_only(lp_ctx)) { + int num_interfaces; + int i; + struct interface *ifaces; + + load_interface_list(dce_ctx, lp_ctx, &ifaces); + + num_interfaces = iface_list_count(ifaces); + for(i = 0; i < num_interfaces; i++) { + const char *address = iface_list_n_ip(ifaces, i); + status = add_socket_rpc_tcp_iface(dce_ctx, e, event_ctx, + model_ops, address, + process_context); + NT_STATUS_NOT_OK_RETURN(status); + } + } else { + char **wcard; + size_t i; + size_t num_binds = 0; + wcard = iface_list_wildcard(dce_ctx); + NT_STATUS_HAVE_NO_MEMORY(wcard); + for (i=0; wcard[i]; i++) { + status = add_socket_rpc_tcp_iface(dce_ctx, e, event_ctx, + model_ops, wcard[i], + process_context); + if (NT_STATUS_IS_OK(status)) { + num_binds++; + } + } + talloc_free(wcard); + if (num_binds == 0) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + } + + return NT_STATUS_OK; +} + +NTSTATUS dcesrv_add_ep(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(e->ep_description); + + switch (transport) { + case NCACN_UNIX_STREAM: + return dcesrv_add_ep_unix(dce_ctx, lp_ctx, e, event_ctx, + model_ops, process_context); + + case NCALRPC: + return dcesrv_add_ep_ncalrpc(dce_ctx, lp_ctx, e, event_ctx, + model_ops, process_context); + + case NCACN_IP_TCP: + return dcesrv_add_ep_tcp(dce_ctx, lp_ctx, e, event_ctx, + model_ops, process_context); + + case NCACN_NP: + return dcesrv_add_ep_np(dce_ctx, lp_ctx, e, event_ctx, + model_ops, process_context); + + default: + return NT_STATUS_NOT_SUPPORTED; + } +} + +_PUBLIC_ struct imessaging_context *dcesrv_imessaging_context( + struct dcesrv_connection *conn) +{ + struct stream_connection *srv_conn = + talloc_get_type_abort(conn->transport.private_data, + struct stream_connection); + return srv_conn->msg_ctx; +} + +_PUBLIC_ struct server_id dcesrv_server_id(struct dcesrv_connection *conn) +{ + struct stream_connection *srv_conn = + talloc_get_type_abort(conn->transport.private_data, + struct stream_connection); + return srv_conn->server_id; +} + +void log_successful_dcesrv_authz_event( + struct dcesrv_call_state *call, + void *private_data) +{ + struct dcesrv_auth *auth = call->auth_state; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(call->conn->endpoint->ep_description); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(call->conn); + const char *auth_type = derpc_transport_string_by_transport(transport); + const char *transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE; + + if (transport == NCACN_NP) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB; + } + + /* + * Log the authorization to this RPC interface. This + * covered ncacn_np pass-through auth, and anonymous + * DCE/RPC (eg epmapper, netlogon etc) + */ + log_successful_authz_event(imsg_ctx, + call->conn->dce_ctx->lp_ctx, + call->conn->remote_address, + call->conn->local_address, + "DCE/RPC", + auth_type, + transport_protection, + auth->session_info); + + auth->auth_audited = true; +} + +NTSTATUS dcesrv_gensec_prepare( + TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *call, + struct gensec_security **out, + void *private_data) +{ + struct cli_credentials *server_creds = NULL; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(call->conn); + bool ok; + + server_creds = cli_credentials_init_server(call->auth_state, + call->conn->dce_ctx->lp_ctx); + if (server_creds == NULL) { + DEBUG(1, ("Failed to init server credentials\n")); + return NT_STATUS_NO_MEMORY; + } + /* This is required for ncalrpc_as_system. */ + ok = cli_credentials_set_kerberos_state(server_creds, + CRED_USE_KERBEROS_DESIRED, + CRED_SPECIFIED); + if (!ok) { + DBG_WARNING("Failed to set kerberos state\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + return samba_server_gensec_start(mem_ctx, + call->event_ctx, + imsg_ctx, + call->conn->dce_ctx->lp_ctx, + server_creds, + NULL, + out); +} + +void dcesrv_transport_terminate_connection_s4(struct dcesrv_connection *dce_conn, + const char *reason) +{ + struct stream_connection *srv_conn = + talloc_get_type_abort(dce_conn->transport.private_data, + struct stream_connection); + stream_terminate_connection(srv_conn, reason); +} diff --git a/source4/rpc_server/dcerpc_server.h b/source4/rpc_server/dcerpc_server.h new file mode 100644 index 0000000..529c604 --- /dev/null +++ b/source4/rpc_server/dcerpc_server.h @@ -0,0 +1,41 @@ +/* + Unix SMB/CIFS implementation. + + server side dcerpc defines + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Stefan (metze) Metzmacher 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef SAMBA_DCERPC_SERVER_H +#define SAMBA_DCERPC_SERVER_H + +#include "librpc/rpc/dcesrv_core.h" + +struct model_ops; + +NTSTATUS dcesrv_add_ep(struct dcesrv_context *dce_ctx, + struct loadparm_context *lp_ctx, + struct dcesrv_endpoint *e, + struct tevent_context *event_ctx, + const struct model_ops *model_ops, + void *process_context); + +_PUBLIC_ struct imessaging_context *dcesrv_imessaging_context( + struct dcesrv_connection *conn); +_PUBLIC_ struct server_id dcesrv_server_id(struct dcesrv_connection *conn); + +#endif /* SAMBA_DCERPC_SERVER_H */ diff --git a/source4/rpc_server/dcerpc_server.pc.in b/source4/rpc_server/dcerpc_server.pc.in new file mode 100644 index 0000000..0ab833a --- /dev/null +++ b/source4/rpc_server/dcerpc_server.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +modulesdir=@modulesdir@/dcerpc_server + +Name: dcerpc_server +Description: DCE/RPC server library +Requires: dcerpc +Version: @PACKAGE_VERSION@ +Libs: @LIB_RPATH@ -L${libdir} -ldcerpc-server +Cflags: -I${includedir} -DHAVE_IMMEDIATE_STRUCTURES=1 diff --git a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c new file mode 100644 index 0000000..ea4d86d --- /dev/null +++ b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c @@ -0,0 +1,2431 @@ +/* + Unix SMB/CIFS implementation. + + DNS Server + + Copyright (C) Amitay Isaacs 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "talloc.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "dsdb/samdb/samdb.h" +#include "lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_dnsserver.h" +#include "dns_server/dnsserver_common.h" +#include "dnsserver.h" + +#undef strcasecmp + +#define DCESRV_INTERFACE_DNSSERVER_BIND(context, iface) \ + dcesrv_interface_dnsserver_bind(context, iface) +static NTSTATUS dcesrv_interface_dnsserver_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_require_integrity(context, iface); +} + +#define DNSSERVER_STATE_MAGIC 0xc9657ab4 +struct dnsserver_state { + struct loadparm_context *lp_ctx; + struct ldb_context *samdb; + struct dnsserver_partition *partitions; + struct dnsserver_zone *zones; + int zones_count; + struct dnsserver_serverinfo *serverinfo; +}; + + +/* Utility functions */ + +static void dnsserver_reload_zones(struct dnsserver_state *dsstate) +{ + struct dnsserver_partition *p; + struct dnsserver_zone *zones, *z, *znext, *zmatch; + struct dnsserver_zone *old_list, *new_list; + + old_list = dsstate->zones; + new_list = NULL; + + for (p = dsstate->partitions; p; p = p->next) { + zones = dnsserver_db_enumerate_zones(dsstate, dsstate->samdb, p); + if (zones == NULL) { + continue; + } + for (z = zones; z; ) { + znext = z->next; + zmatch = dnsserver_find_zone(old_list, z->name); + if (zmatch == NULL) { + /* Missing zone */ + z->zoneinfo = dnsserver_init_zoneinfo(z, dsstate->serverinfo); + if (z->zoneinfo == NULL) { + continue; + } + DLIST_ADD_END(new_list, z); + p->zones_count++; + dsstate->zones_count++; + } else { + /* Existing zone */ + talloc_free(z); + DLIST_REMOVE(old_list, zmatch); + DLIST_ADD_END(new_list, zmatch); + } + z = znext; + } + } + + if (new_list == NULL) { + return; + } + + /* Deleted zones */ + for (z = old_list; z; ) { + znext = z->next; + z->partition->zones_count--; + dsstate->zones_count--; + talloc_free(z); + z = znext; + } + + dsstate->zones = new_list; +} + + +static struct dnsserver_state *dnsserver_connect(struct dcesrv_call_state *dce_call) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *zones, *z, *znext; + struct dnsserver_partition *partitions, *p; + NTSTATUS status; + + dsstate = dcesrv_iface_state_find_conn(dce_call, + DNSSERVER_STATE_MAGIC, + struct dnsserver_state); + if (dsstate != NULL) { + return dsstate; + } + + dsstate = talloc_zero(dce_call, struct dnsserver_state); + if (dsstate == NULL) { + return NULL; + } + + dsstate->lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + + dsstate->samdb = dcesrv_samdb_connect_as_user(dsstate, dce_call); + if (dsstate->samdb == NULL) { + DEBUG(0,("dnsserver: Failed to open samdb")); + goto failed; + } + + /* Initialize server info */ + dsstate->serverinfo = dnsserver_init_serverinfo(dsstate, + dsstate->lp_ctx, + dsstate->samdb); + if (dsstate->serverinfo == NULL) { + goto failed; + } + + /* Search for DNS partitions */ + partitions = dnsserver_db_enumerate_partitions(dsstate, dsstate->serverinfo, dsstate->samdb); + if (partitions == NULL) { + goto failed; + } + dsstate->partitions = partitions; + + /* Search for DNS zones */ + for (p = partitions; p; p = p->next) { + zones = dnsserver_db_enumerate_zones(dsstate, dsstate->samdb, p); + if (zones == NULL) { + goto failed; + } + for (z = zones; z; ) { + znext = z->next; + if (dnsserver_find_zone(dsstate->zones, z->name) == NULL) { + z->zoneinfo = dnsserver_init_zoneinfo(z, dsstate->serverinfo); + if (z->zoneinfo == NULL) { + goto failed; + } + DLIST_ADD_END(dsstate->zones, z); + p->zones_count++; + dsstate->zones_count++; + } else { + /* Ignore duplicate zone */ + DEBUG(3,("dnsserver: Ignoring duplicate zone '%s' from '%s'", + z->name, ldb_dn_get_linearized(z->zone_dn))); + } + z = znext; + } + } + + status = dcesrv_iface_state_store_conn(dce_call, + DNSSERVER_STATE_MAGIC, + dsstate); + if (!NT_STATUS_IS_OK(status)) { + goto failed; + } + + return dsstate; + +failed: + talloc_free(dsstate); + dsstate = NULL; + return NULL; +} + + +/* dnsserver query functions */ + +/* [MS-DNSP].pdf Section 3.1.1.1 DNS Server Configuration Information */ +static WERROR dnsserver_query_server(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID *typeid, + union DNSSRV_RPC_UNION *r) +{ + uint8_t is_integer, is_addresses, is_string, is_wstring, is_stringlist; + uint32_t answer_integer; + struct IP4_ARRAY *answer_iparray; + struct DNS_ADDR_ARRAY *answer_addrarray; + char *answer_string; + struct DNS_RPC_UTF8_STRING_LIST *answer_stringlist; + struct dnsserver_serverinfo *serverinfo; + + serverinfo = dsstate->serverinfo; + + if (strcasecmp(operation, "ServerInfo") == 0) { + if (client_version == DNS_CLIENT_VERSION_W2K) { + *typeid = DNSSRV_TYPEID_SERVER_INFO_W2K; + r->ServerInfoW2K = talloc_zero(mem_ctx, struct DNS_RPC_SERVER_INFO_W2K); + + r->ServerInfoW2K->dwVersion = serverinfo->dwVersion; + r->ServerInfoW2K->fBootMethod = serverinfo->fBootMethod; + r->ServerInfoW2K->fAdminConfigured = serverinfo->fAdminConfigured; + r->ServerInfoW2K->fAllowUpdate = serverinfo->fAllowUpdate; + r->ServerInfoW2K->fDsAvailable = serverinfo->fDsAvailable; + r->ServerInfoW2K->pszServerName = talloc_strdup(mem_ctx, serverinfo->pszServerName); + r->ServerInfoW2K->pszDsContainer = talloc_strdup(mem_ctx, serverinfo->pszDsContainer); + r->ServerInfoW2K->aipServerAddrs = dns_addr_array_to_ip4_array(mem_ctx, + serverinfo->aipServerAddrs); + r->ServerInfoW2K->aipListenAddrs = dns_addr_array_to_ip4_array(mem_ctx, + serverinfo->aipListenAddrs); + r->ServerInfoW2K->aipForwarders = ip4_array_copy(mem_ctx, serverinfo->aipForwarders); + r->ServerInfoW2K->dwLogLevel = serverinfo->dwLogLevel; + r->ServerInfoW2K->dwDebugLevel = serverinfo->dwDebugLevel; + r->ServerInfoW2K->dwForwardTimeout = serverinfo->dwForwardTimeout; + r->ServerInfoW2K->dwRpcProtocol = serverinfo->dwRpcProtocol; + r->ServerInfoW2K->dwNameCheckFlag = serverinfo->dwNameCheckFlag; + r->ServerInfoW2K->cAddressAnswerLimit = serverinfo->cAddressAnswerLimit; + r->ServerInfoW2K->dwRecursionRetry = serverinfo->dwRecursionRetry; + r->ServerInfoW2K->dwRecursionTimeout = serverinfo->dwRecursionTimeout; + r->ServerInfoW2K->dwMaxCacheTtl = serverinfo->dwMaxCacheTtl; + r->ServerInfoW2K->dwDsPollingInterval = serverinfo->dwDsPollingInterval; + r->ServerInfoW2K->dwScavengingInterval = serverinfo->dwScavengingInterval; + r->ServerInfoW2K->dwDefaultRefreshInterval = serverinfo->dwDefaultRefreshInterval; + r->ServerInfoW2K->dwDefaultNoRefreshInterval = serverinfo->dwDefaultNoRefreshInterval; + r->ServerInfoW2K->fAutoReverseZones = serverinfo->fAutoReverseZones; + r->ServerInfoW2K->fAutoCacheUpdate = serverinfo->fAutoCacheUpdate; + r->ServerInfoW2K->fRecurseAfterForwarding = serverinfo->fRecurseAfterForwarding; + r->ServerInfoW2K->fForwardDelegations = serverinfo->fForwardDelegations; + r->ServerInfoW2K->fNoRecursion = serverinfo->fNoRecursion; + r->ServerInfoW2K->fSecureResponses = serverinfo->fSecureResponses; + r->ServerInfoW2K->fRoundRobin = serverinfo->fRoundRobin; + r->ServerInfoW2K->fLocalNetPriority = serverinfo->fLocalNetPriority; + r->ServerInfoW2K->fBindSecondaries = serverinfo->fBindSecondaries; + r->ServerInfoW2K->fWriteAuthorityNs = serverinfo->fWriteAuthorityNs; + r->ServerInfoW2K->fStrictFileParsing = serverinfo->fStrictFileParsing; + r->ServerInfoW2K->fLooseWildcarding = serverinfo->fLooseWildcarding; + r->ServerInfoW2K->fDefaultAgingState = serverinfo->fDefaultAgingState; + + } else if (client_version == DNS_CLIENT_VERSION_DOTNET) { + *typeid = DNSSRV_TYPEID_SERVER_INFO_DOTNET; + r->ServerInfoDotNet = talloc_zero(mem_ctx, struct DNS_RPC_SERVER_INFO_DOTNET); + + r->ServerInfoDotNet->dwRpcStructureVersion = 0x01; + r->ServerInfoDotNet->dwVersion = serverinfo->dwVersion; + r->ServerInfoDotNet->fBootMethod = serverinfo->fBootMethod; + r->ServerInfoDotNet->fAdminConfigured = serverinfo->fAdminConfigured; + r->ServerInfoDotNet->fAllowUpdate = serverinfo->fAllowUpdate; + r->ServerInfoDotNet->fDsAvailable = serverinfo->fDsAvailable; + r->ServerInfoDotNet->pszServerName = talloc_strdup(mem_ctx, serverinfo->pszServerName); + r->ServerInfoDotNet->pszDsContainer = talloc_strdup(mem_ctx, serverinfo->pszDsContainer); + r->ServerInfoDotNet->aipServerAddrs = dns_addr_array_to_ip4_array(mem_ctx, + serverinfo->aipServerAddrs); + r->ServerInfoDotNet->aipListenAddrs = dns_addr_array_to_ip4_array(mem_ctx, + serverinfo->aipListenAddrs); + r->ServerInfoDotNet->aipForwarders = ip4_array_copy(mem_ctx, serverinfo->aipForwarders); + r->ServerInfoDotNet->aipLogFilter = ip4_array_copy(mem_ctx, serverinfo->aipLogFilter); + r->ServerInfoDotNet->pwszLogFilePath = talloc_strdup(mem_ctx, serverinfo->pwszLogFilePath); + r->ServerInfoDotNet->pszDomainName = talloc_strdup(mem_ctx, serverinfo->pszDomainName); + r->ServerInfoDotNet->pszForestName = talloc_strdup(mem_ctx, serverinfo->pszForestName); + r->ServerInfoDotNet->pszDomainDirectoryPartition = talloc_strdup(mem_ctx, serverinfo->pszDomainDirectoryPartition); + r->ServerInfoDotNet->pszForestDirectoryPartition = talloc_strdup(mem_ctx, serverinfo->pszForestDirectoryPartition); + r->ServerInfoDotNet->dwLogLevel = serverinfo->dwLogLevel; + r->ServerInfoDotNet->dwDebugLevel = serverinfo->dwDebugLevel; + r->ServerInfoDotNet->dwForwardTimeout = serverinfo->dwForwardTimeout; + r->ServerInfoDotNet->dwRpcProtocol = serverinfo->dwRpcProtocol; + r->ServerInfoDotNet->dwNameCheckFlag = serverinfo->dwNameCheckFlag; + r->ServerInfoDotNet->cAddressAnswerLimit = serverinfo->cAddressAnswerLimit; + r->ServerInfoDotNet->dwRecursionRetry = serverinfo->dwRecursionRetry; + r->ServerInfoDotNet->dwRecursionTimeout = serverinfo->dwRecursionTimeout; + r->ServerInfoDotNet->dwMaxCacheTtl = serverinfo->dwMaxCacheTtl; + r->ServerInfoDotNet->dwDsPollingInterval = serverinfo->dwDsPollingInterval; + r->ServerInfoDotNet->dwLocalNetPriorityNetMask = serverinfo->dwLocalNetPriorityNetMask; + r->ServerInfoDotNet->dwScavengingInterval = serverinfo->dwScavengingInterval; + r->ServerInfoDotNet->dwDefaultRefreshInterval = serverinfo->dwDefaultRefreshInterval; + r->ServerInfoDotNet->dwDefaultNoRefreshInterval = serverinfo->dwDefaultNoRefreshInterval; + r->ServerInfoDotNet->dwLastScavengeTime = serverinfo->dwLastScavengeTime; + r->ServerInfoDotNet->dwEventLogLevel = serverinfo->dwEventLogLevel; + r->ServerInfoDotNet->dwLogFileMaxSize = serverinfo->dwLogFileMaxSize; + r->ServerInfoDotNet->dwDsForestVersion = serverinfo->dwDsForestVersion; + r->ServerInfoDotNet->dwDsDomainVersion = serverinfo->dwDsDomainVersion; + r->ServerInfoDotNet->dwDsDsaVersion = serverinfo->dwDsDsaVersion; + r->ServerInfoDotNet->fAutoReverseZones = serverinfo->fAutoReverseZones; + r->ServerInfoDotNet->fAutoCacheUpdate = serverinfo->fAutoCacheUpdate; + r->ServerInfoDotNet->fRecurseAfterForwarding = serverinfo->fRecurseAfterForwarding; + r->ServerInfoDotNet->fForwardDelegations = serverinfo->fForwardDelegations; + r->ServerInfoDotNet->fNoRecursion = serverinfo->fNoRecursion; + r->ServerInfoDotNet->fSecureResponses = serverinfo->fSecureResponses; + r->ServerInfoDotNet->fRoundRobin = serverinfo->fRoundRobin; + r->ServerInfoDotNet->fLocalNetPriority = serverinfo->fLocalNetPriority; + r->ServerInfoDotNet->fBindSecondaries = serverinfo->fBindSecondaries; + r->ServerInfoDotNet->fWriteAuthorityNs = serverinfo->fWriteAuthorityNs; + r->ServerInfoDotNet->fStrictFileParsing = serverinfo->fStrictFileParsing; + r->ServerInfoDotNet->fLooseWildcarding = serverinfo->fLooseWildcarding; + r->ServerInfoDotNet->fDefaultAgingState = serverinfo->fDefaultAgingState; + + } else if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + *typeid = DNSSRV_TYPEID_SERVER_INFO; + r->ServerInfo = talloc_zero(mem_ctx, struct DNS_RPC_SERVER_INFO_LONGHORN); + + r->ServerInfo->dwRpcStructureVersion = 0x02; + r->ServerInfo->dwVersion = serverinfo->dwVersion; + r->ServerInfo->fBootMethod = serverinfo->fBootMethod; + r->ServerInfo->fAdminConfigured = serverinfo->fAdminConfigured; + r->ServerInfo->fAllowUpdate = serverinfo->fAllowUpdate; + r->ServerInfo->fDsAvailable = serverinfo->fDsAvailable; + r->ServerInfo->pszServerName = talloc_strdup(mem_ctx, serverinfo->pszServerName); + r->ServerInfo->pszDsContainer = talloc_strdup(mem_ctx, serverinfo->pszDsContainer); + r->ServerInfo->aipServerAddrs = serverinfo->aipServerAddrs; + r->ServerInfo->aipListenAddrs = serverinfo->aipListenAddrs; + r->ServerInfo->aipForwarders = ip4_array_to_dns_addr_array(mem_ctx, serverinfo->aipForwarders); + r->ServerInfo->aipLogFilter = ip4_array_to_dns_addr_array(mem_ctx, serverinfo->aipLogFilter); + r->ServerInfo->pwszLogFilePath = talloc_strdup(mem_ctx, serverinfo->pwszLogFilePath); + r->ServerInfo->pszDomainName = talloc_strdup(mem_ctx, serverinfo->pszDomainName); + r->ServerInfo->pszForestName = talloc_strdup(mem_ctx, serverinfo->pszForestName); + r->ServerInfo->pszDomainDirectoryPartition = talloc_strdup(mem_ctx, serverinfo->pszDomainDirectoryPartition); + r->ServerInfo->pszForestDirectoryPartition = talloc_strdup(mem_ctx, serverinfo->pszForestDirectoryPartition); + r->ServerInfo->dwLogLevel = serverinfo->dwLogLevel; + r->ServerInfo->dwDebugLevel = serverinfo->dwDebugLevel; + r->ServerInfo->dwForwardTimeout = serverinfo->dwForwardTimeout; + r->ServerInfo->dwRpcProtocol = serverinfo->dwRpcProtocol; + r->ServerInfo->dwNameCheckFlag = serverinfo->dwNameCheckFlag; + r->ServerInfo->cAddressAnswerLimit = serverinfo->cAddressAnswerLimit; + r->ServerInfo->dwRecursionRetry = serverinfo->dwRecursionRetry; + r->ServerInfo->dwRecursionTimeout = serverinfo->dwRecursionTimeout; + r->ServerInfo->dwMaxCacheTtl = serverinfo->dwMaxCacheTtl; + r->ServerInfo->dwDsPollingInterval = serverinfo->dwDsPollingInterval; + r->ServerInfo->dwLocalNetPriorityNetMask = serverinfo->dwLocalNetPriorityNetMask; + r->ServerInfo->dwScavengingInterval = serverinfo->dwScavengingInterval; + r->ServerInfo->dwDefaultRefreshInterval = serverinfo->dwDefaultRefreshInterval; + r->ServerInfo->dwDefaultNoRefreshInterval = serverinfo->dwDefaultNoRefreshInterval; + r->ServerInfo->dwLastScavengeTime = serverinfo->dwLastScavengeTime; + r->ServerInfo->dwEventLogLevel = serverinfo->dwEventLogLevel; + r->ServerInfo->dwLogFileMaxSize = serverinfo->dwLogFileMaxSize; + r->ServerInfo->dwDsForestVersion = serverinfo->dwDsForestVersion; + r->ServerInfo->dwDsDomainVersion = serverinfo->dwDsDomainVersion; + r->ServerInfo->dwDsDsaVersion = serverinfo->dwDsDsaVersion; + r->ServerInfo->fReadOnlyDC = serverinfo->fReadOnlyDC; + r->ServerInfo->fAutoReverseZones = serverinfo->fAutoReverseZones; + r->ServerInfo->fAutoCacheUpdate = serverinfo->fAutoCacheUpdate; + r->ServerInfo->fRecurseAfterForwarding = serverinfo->fRecurseAfterForwarding; + r->ServerInfo->fForwardDelegations = serverinfo->fForwardDelegations; + r->ServerInfo->fNoRecursion = serverinfo->fNoRecursion; + r->ServerInfo->fSecureResponses = serverinfo->fSecureResponses; + r->ServerInfo->fRoundRobin = serverinfo->fRoundRobin; + r->ServerInfo->fLocalNetPriority = serverinfo->fLocalNetPriority; + r->ServerInfo->fBindSecondaries = serverinfo->fBindSecondaries; + r->ServerInfo->fWriteAuthorityNs = serverinfo->fWriteAuthorityNs; + r->ServerInfo->fStrictFileParsing = serverinfo->fStrictFileParsing; + r->ServerInfo->fLooseWildcarding = serverinfo->fLooseWildcarding; + r->ServerInfo->fDefaultAgingState = serverinfo->fDefaultAgingState; + } + return WERR_OK; + } + + is_integer = 0; + answer_integer = 0; + + if (strcasecmp(operation, "AddressAnswerLimit") == 0) { + answer_integer = serverinfo->cAddressAnswerLimit; + is_integer = 1; + } else if (strcasecmp(operation, "AdminConfigured") == 0) { + answer_integer = serverinfo->fAdminConfigured; + is_integer = 1; + } else if (strcasecmp(operation, "AllowCNAMEAtNS") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "AllowUpdate") == 0) { + answer_integer = serverinfo->fAllowUpdate; + is_integer = 1; + } else if (strcasecmp(operation, "AutoCacheUpdate") == 0) { + answer_integer = serverinfo->fAutoCacheUpdate; + is_integer = 1; + } else if (strcasecmp(operation, "AutoConfigFileZones") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "BindSecondaries") == 0) { + answer_integer = serverinfo->fBindSecondaries; + is_integer = 1; + } else if (strcasecmp(operation, "BootMethod") == 0) { + answer_integer = serverinfo->fBootMethod; + is_integer = 1; + } else if (strcasecmp(operation, "DebugLevel") == 0) { + answer_integer = serverinfo->dwDebugLevel; + is_integer = 1; + } else if (strcasecmp(operation, "DefaultAgingState") == 0) { + answer_integer = serverinfo->fDefaultAgingState; + is_integer = 1; + } else if (strcasecmp(operation, "DefaultNoRefreshInterval") == 0) { + answer_integer = serverinfo->dwDefaultNoRefreshInterval; + is_integer = 1; + } else if (strcasecmp(operation, "DefaultRefreshInterval") == 0) { + answer_integer = serverinfo->dwDefaultRefreshInterval; + is_integer = 1; + } else if (strcasecmp(operation, "DeleteOutsideGlue") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DisjointNets") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DsLazyUpdateInterval") == 0) { + answer_integer = 3; /* seconds */ + is_integer = 1; + } else if (strcasecmp(operation, "DsPollingInterval") == 0) { + answer_integer = serverinfo->dwDsPollingInterval; + is_integer = 1; + } else if (strcasecmp(operation, "DsTombstoneInterval") == 0) { + answer_integer = 0x00127500; /* 14 days */ + is_integer = 1; + } else if (strcasecmp(operation, "EnableRegistryBoot") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EventLogLevel") == 0) { + answer_integer = serverinfo->dwEventLogLevel; + is_integer = 1; + } else if (strcasecmp(operation, "ForceSoaSerial") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForceSaoRetry") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForceSoaRefresh") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForceSoaMinimumTtl") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForwardDelegations") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "ForwardingTimeout") == 0) { + answer_integer = serverinfo->dwForwardTimeout; + is_integer = 1; + } else if (strcasecmp(operation, "IsSlave") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "LocalNetPriority") == 0) { + answer_integer = serverinfo->fLocalNetPriority; + is_integer = 1; + } else if (strcasecmp(operation, "LogFileMaxSize") == 0) { + answer_integer = serverinfo->dwLogFileMaxSize; + is_integer = 1; + } else if (strcasecmp(operation, "LogLevel") == 0) { + answer_integer = serverinfo->dwLogLevel; + is_integer = 1; + } else if (strcasecmp(operation, "LooseWildcarding") == 0) { + answer_integer = serverinfo->fLooseWildcarding; + is_integer = 1; + } else if (strcasecmp(operation, "MaxCacheTtl") == 0) { + answer_integer = serverinfo->dwMaxCacheTtl; + is_integer = 1; + } else if (strcasecmp(operation, "MaxNegativeCacheTtl") == 0) { + answer_integer = 0x00000384; /* 15 minutes */ + is_integer = 1; + } else if (strcasecmp(operation, "NameCheckFlag") == 0) { + answer_integer = serverinfo->dwNameCheckFlag; + is_integer = 1; + } else if (strcasecmp(operation, "NoRecursion") == 0) { + answer_integer = serverinfo->fNoRecursion; + is_integer = 1; + } else if (strcasecmp(operation, "NoUpdateDelegations") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "PublishAutonet") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "QuietRecvFaultInterval") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "QuietRecvLogInterval") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "RecursionRetry") == 0) { + answer_integer = serverinfo->dwRecursionRetry; + is_integer = 1; + } else if (strcasecmp(operation, "RecursionTimeout") == 0) { + answer_integer = serverinfo->dwRecursionTimeout; + is_integer = 1; + } else if (strcasecmp(operation, "ReloadException") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "RoundRobin") == 0) { + answer_integer = serverinfo->fRoundRobin; + is_integer = 1; + } else if (strcasecmp(operation, "RpcProtocol") == 0) { + answer_integer = serverinfo->dwRpcProtocol; + is_integer = 1; + } else if (strcasecmp(operation, "SecureResponses") == 0) { + answer_integer = serverinfo->fSecureResponses; + is_integer = 1; + } else if (strcasecmp(operation, "SendPort") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ScavengingInterval") == 0) { + answer_integer = serverinfo->dwScavengingInterval; + is_integer = 1; + } else if (strcasecmp(operation, "SocketPoolSize") == 0) { + answer_integer = 0x000009C4; + is_integer = 1; + } else if (strcasecmp(operation, "StrictFileParsing") == 0) { + answer_integer = serverinfo->fStrictFileParsing; + is_integer = 1; + } else if (strcasecmp(operation, "SyncDnsZoneSerial") == 0) { + answer_integer = 2; /* ZONE_SERIAL_SYNC_XFER */ + is_integer = 1; + } else if (strcasecmp(operation, "UpdateOptions") == 0) { + answer_integer = 0x0000030F; /* DNS_DEFAULT_UPDATE_OPTIONS */ + is_integer = 1; + } else if (strcasecmp(operation, "UseSystemEvengLog") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "Version") == 0) { + answer_integer = serverinfo->dwVersion; + is_integer = 1; + } else if (strcasecmp(operation, "XfrConnectTimeout") == 0) { + answer_integer = 0x0000001E; + is_integer = 1; + } else if (strcasecmp(operation, "WriteAuthorityNs") == 0) { + answer_integer = serverinfo->fWriteAuthorityNs; + is_integer = 1; + } else if (strcasecmp(operation, "AdditionalRecursionTimeout") == 0) { + answer_integer = 0x00000004; + is_integer = 1; + } else if (strcasecmp(operation, "AppendMsZoneTransferFlag") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "AutoCreateDelegations") == 0) { + answer_integer = 0; /* DNS_ACD_DONT_CREATE */ + is_integer = 1; + } else if (strcasecmp(operation, "BreakOnAscFailure") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "CacheEmptyAuthResponses") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DirectoryPartitionAutoEnlistInterval") == 0) { + answer_integer = 0x00015180; /* 1 day */ + is_integer = 1; + } else if (strcasecmp(operation, "DisableAutoReverseZones") == 0) { + answer_integer = ~serverinfo->fAutoReverseZones; + is_integer = 1; + } else if (strcasecmp(operation, "EDnsCacheTimeout") == 0) { + answer_integer = 0x00000384; /* 15 minutes */ + is_integer = 1; + } else if (strcasecmp(operation, "EnableDirectoryPartitions") == 0) { + answer_integer = serverinfo->fDsAvailable; + is_integer = 1; + } else if (strcasecmp(operation, "EnableDnsSec") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableEDnsProbes") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableEDnsReception") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableIPv6") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableIQueryResponseGeneration") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableSendErrorSuppression") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableUpdateForwarding") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableWinsR") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForceDsaBehaviorVersion") == 0) { + answer_integer = serverinfo->dwDsDsaVersion; + is_integer = 1; + } else if (strcasecmp(operation, "ForceDomainBehaviorVersion") == 0) { + answer_integer = serverinfo->dwDsDsaVersion; + is_integer = 1; + } else if (strcasecmp(operation, "ForceForestBehaviorVersion") == 0) { + answer_integer = serverinfo->dwDsDsaVersion; + is_integer = 1; + } else if (strcasecmp(operation, "HeapDebug") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "LameDelegationTtl") == 0) { + answer_integer = 0; /* seconds */ + is_integer = 1; + } else if (strcasecmp(operation, "LocalNetPriorityNetMask") == 0) { + answer_integer = serverinfo->dwLocalNetPriorityNetMask; + is_integer = 1; + } else if (strcasecmp(operation, "MaxCacheSize") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "MaxResourceRecordsInNonSecureUpdate") == 0) { + answer_integer = 0x0000001E; + is_integer = 1; + } else if (strcasecmp(operation, "OperationsLogLevel") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "OperationsLogLevel2") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "MaximumUdpPacketSize") == 0) { + answer_integer = 0x00004000; /* maximum possible */ + is_integer = 1; + } else if (strcasecmp(operation, "RecurseToInternetRootMask") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "SelfTest") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "SilentlyIgnoreCNameUpdateConflicts") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "TcpReceivePacketSize") == 0) { + answer_integer = 0x00010000; + is_integer = 1; + } else if (strcasecmp(operation, "XfrThrottleMultiplier") == 0) { + answer_integer = 0x0000000A; + is_integer = 1; + } else if (strcasecmp(operation, "AllowMsdcsLookupRetry") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "AllowReadOnlyZoneTransfer") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DsBackGroundLoadPaused") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DsMinimumBackgroundLoadThreads") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DsRemoteReplicationDelay") == 0) { + answer_integer = 0x0000001E; /* 30 seconds */ + is_integer = 1; + } else if (strcasecmp(operation, "EnableDuplicateQuerySuppresion") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableGlobalNamesSupport") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "EnableVersionQuery") == 0) { + answer_integer = 1; /* DNS_VERSION_QUERY_FULL */ + is_integer = 1; + } else if (strcasecmp(operation, "EnableRsoForRodc") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "ForceRODCMode") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesAlwaysQuerySrv") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesBlockUpdates") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesEnableEDnsProbes") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesPreferAAAA") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesQueryOrder") == 0) { + answer_integer = 1; + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesSendTimeout") == 0) { + answer_integer = 3; /* seconds */ + is_integer = 1; + } else if (strcasecmp(operation, "GlobalNamesServerQueryInterval") == 0) { + answer_integer = 0x00005460; /* 6 hours */ + is_integer = 1; + } else if (strcasecmp(operation, "RemoteIPv4RankBoost") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "RemoteIPv6RankBoost") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "MaximumRodcRsoAttemptsPerCycle") == 0) { + answer_integer = 0x00000064; + is_integer = 1; + } else if (strcasecmp(operation, "MaximumRodcRsoQueueLength") == 0) { + answer_integer = 0x0000012C; + is_integer = 1; + } else if (strcasecmp(operation, "EnableGlobalQueryBlockList") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "OpenACLOnProxyUpdates") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "CacheLockingPercent") == 0) { + answer_integer = 0x00000064; + is_integer = 1; + } + + if (is_integer == 1) { + *typeid = DNSSRV_TYPEID_DWORD; + r->Dword = answer_integer; + return WERR_OK; + } + + is_addresses = 0; + + if (strcasecmp(operation, "Forwarders") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, serverinfo->aipForwarders); + } else { + answer_iparray = ip4_array_copy(mem_ctx, serverinfo->aipForwarders); + } + is_addresses = 1; + } else if (strcasecmp(operation, "ListenAddresses") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = serverinfo->aipListenAddrs; + } else { + answer_iparray = dns_addr_array_to_ip4_array(mem_ctx, serverinfo->aipListenAddrs); + } + is_addresses = 1; + } else if (strcasecmp(operation, "BreakOnReceiveFrom") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = NULL; + } else { + answer_iparray = NULL; + } + is_addresses = 1; + } else if (strcasecmp(operation, "BreakOnUpdateFrom") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = NULL; + } else { + answer_iparray = NULL; + } + is_addresses = 1; + } else if (strcasecmp(operation, "LogIPFilterList") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, serverinfo->aipLogFilter); + } else { + answer_iparray = ip4_array_copy(mem_ctx, serverinfo->aipLogFilter); + } + is_addresses = 1; + } + + if (is_addresses == 1) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + *typeid = DNSSRV_TYPEID_ADDRARRAY; + r->AddrArray = answer_addrarray; + } else { + *typeid = DNSSRV_TYPEID_IPARRAY; + r->IpArray = answer_iparray; + } + return WERR_OK; + } + + is_string = is_wstring = 0; + + if (strcasecmp(operation, "DomainDirectoryPartitionBaseName") == 0) { + answer_string = talloc_strdup(mem_ctx, "DomainDnsZones"); + if (! answer_string) { + return WERR_OUTOFMEMORY; + } + is_string = 1; + } else if (strcasecmp(operation, "ForestDirectoryPartitionBaseName") == 0) { + answer_string = talloc_strdup(mem_ctx, "ForestDnsZones"); + if (! answer_string) { + return WERR_OUTOFMEMORY; + } + is_string = 1; + } else if (strcasecmp(operation, "LogFilePath") == 0) { + answer_string = talloc_strdup(mem_ctx, serverinfo->pwszLogFilePath); + is_wstring = 1; + } else if (strcasecmp(operation, "ServerLevelPluginDll") == 0) { + answer_string = NULL; + is_wstring = 1; + } else if (strcasecmp(operation, "DsBackgroundPauseName") == 0) { + answer_string = NULL; + is_string = 1; + } else if (strcasecmp(operation, "DsNotRoundRobinTypes") == 0) { + answer_string = NULL; + is_string = 1; + } + + if (is_string == 1) { + *typeid = DNSSRV_TYPEID_LPSTR; + r->String = answer_string; + return WERR_OK; + } else if (is_wstring == 1) { + *typeid = DNSSRV_TYPEID_LPWSTR; + r->WideString = answer_string; + return WERR_OK; + } + + is_stringlist = 0; + + if (strcasecmp(operation, "GlobalQueryBlockList") == 0) { + answer_stringlist = NULL; + is_stringlist = 1; + } else if (strcasecmp(operation, "SocketPoolExcludedPortRanges") == 0) { + answer_stringlist = NULL; + is_stringlist = 1; + } + + if (is_stringlist == 1) { + *typeid = DNSSRV_TYPEID_UTF8_STRING_LIST; + r->Utf8StringList = answer_stringlist; + return WERR_OK; + } + + DEBUG(0,("dnsserver: Invalid server operation %s", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; +} + +/* [MS-DNSP].pdf Section 3.1.1.2 Zone Configuration Information */ +static WERROR dnsserver_query_zone(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID *typeid, + union DNSSRV_RPC_UNION *r) +{ + uint8_t is_integer, is_addresses, is_string; + uint32_t answer_integer = 0; + struct IP4_ARRAY *answer_iparray; + struct DNS_ADDR_ARRAY *answer_addrarray; + char *answer_string; + struct dnsserver_zoneinfo *zoneinfo; + + zoneinfo = z->zoneinfo; + + if (strcasecmp(operation, "Zone") == 0) { + if (client_version == DNS_CLIENT_VERSION_W2K) { + *typeid = DNSSRV_TYPEID_ZONE_W2K; + r->ZoneW2K = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_W2K); + + r->ZoneW2K->pszZoneName = talloc_strdup(mem_ctx, z->name); + r->ZoneW2K->Flags = zoneinfo->Flags; + r->ZoneW2K->ZoneType = zoneinfo->dwZoneType; + r->ZoneW2K->Version = zoneinfo->Version; + } else { + *typeid = DNSSRV_TYPEID_ZONE; + r->Zone = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_DOTNET); + + r->Zone->dwRpcStructureVersion = 0x01; + r->Zone->pszZoneName = talloc_strdup(mem_ctx, z->name); + r->Zone->Flags = zoneinfo->Flags; + r->Zone->ZoneType = zoneinfo->dwZoneType; + r->Zone->Version = zoneinfo->Version; + r->Zone->dwDpFlags = z->partition->dwDpFlags; + r->Zone->pszDpFqdn = talloc_strdup(mem_ctx, z->partition->pszDpFqdn); + } + return WERR_OK; + } + + if (strcasecmp(operation, "ZoneInfo") == 0) { + if (client_version == DNS_CLIENT_VERSION_W2K) { + *typeid = DNSSRV_TYPEID_ZONE_INFO_W2K; + r->ZoneInfoW2K = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_INFO_W2K); + + r->ZoneInfoW2K->pszZoneName = talloc_strdup(mem_ctx, z->name); + r->ZoneInfoW2K->dwZoneType = zoneinfo->dwZoneType; + r->ZoneInfoW2K->fReverse = zoneinfo->fReverse; + r->ZoneInfoW2K->fAllowUpdate = zoneinfo->fAllowUpdate; + r->ZoneInfoW2K->fPaused = zoneinfo->fPaused; + r->ZoneInfoW2K->fShutdown = zoneinfo->fShutdown; + r->ZoneInfoW2K->fAutoCreated = zoneinfo->fAutoCreated; + r->ZoneInfoW2K->fUseDatabase = zoneinfo->fUseDatabase; + r->ZoneInfoW2K->pszDataFile = talloc_strdup(mem_ctx, zoneinfo->pszDataFile); + r->ZoneInfoW2K->aipMasters = ip4_array_copy(mem_ctx, zoneinfo->aipMasters); + r->ZoneInfoW2K->fSecureSecondaries = zoneinfo->fSecureSecondaries; + r->ZoneInfoW2K->fNotifyLevel = zoneinfo->fNotifyLevel; + r->ZoneInfoW2K->aipSecondaries = ip4_array_copy(mem_ctx, zoneinfo->aipSecondaries); + r->ZoneInfoW2K->aipNotify = ip4_array_copy(mem_ctx, zoneinfo->aipNotify); + r->ZoneInfoW2K->fUseWins = zoneinfo->fUseWins; + r->ZoneInfoW2K->fUseNbstat = zoneinfo->fUseNbstat; + r->ZoneInfoW2K->fAging = zoneinfo->fAging; + r->ZoneInfoW2K->dwNoRefreshInterval = zoneinfo->dwNoRefreshInterval; + r->ZoneInfoW2K->dwRefreshInterval = zoneinfo->dwRefreshInterval; + r->ZoneInfoW2K->dwAvailForScavengeTime = zoneinfo->dwAvailForScavengeTime; + r->ZoneInfoW2K->aipScavengeServers = ip4_array_copy(mem_ctx, zoneinfo->aipScavengeServers); + + } else if (client_version == DNS_CLIENT_VERSION_DOTNET) { + *typeid = DNSSRV_TYPEID_ZONE_INFO_DOTNET; + r->ZoneInfoDotNet = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_INFO_DOTNET); + + r->ZoneInfoDotNet->dwRpcStructureVersion = 0x01; + r->ZoneInfoDotNet->pszZoneName = talloc_strdup(mem_ctx, z->name); + r->ZoneInfoDotNet->dwZoneType = zoneinfo->dwZoneType; + r->ZoneInfoDotNet->fReverse = zoneinfo->fReverse; + r->ZoneInfoDotNet->fAllowUpdate = zoneinfo->fAllowUpdate; + r->ZoneInfoDotNet->fPaused = zoneinfo->fPaused; + r->ZoneInfoDotNet->fShutdown = zoneinfo->fShutdown; + r->ZoneInfoDotNet->fAutoCreated = zoneinfo->fAutoCreated; + r->ZoneInfoDotNet->fUseDatabase = zoneinfo->fUseDatabase; + r->ZoneInfoDotNet->pszDataFile = talloc_strdup(mem_ctx, zoneinfo->pszDataFile); + r->ZoneInfoDotNet->aipMasters = ip4_array_copy(mem_ctx, zoneinfo->aipMasters); + r->ZoneInfoDotNet->fSecureSecondaries = zoneinfo->fSecureSecondaries; + r->ZoneInfoDotNet->fNotifyLevel = zoneinfo->fNotifyLevel; + r->ZoneInfoDotNet->aipSecondaries = ip4_array_copy(mem_ctx, zoneinfo->aipSecondaries); + r->ZoneInfoDotNet->aipNotify = ip4_array_copy(mem_ctx, zoneinfo->aipNotify); + r->ZoneInfoDotNet->fUseWins = zoneinfo->fUseWins; + r->ZoneInfoDotNet->fUseNbstat = zoneinfo->fUseNbstat; + r->ZoneInfoDotNet->fAging = zoneinfo->fAging; + r->ZoneInfoDotNet->dwNoRefreshInterval = zoneinfo->dwNoRefreshInterval; + r->ZoneInfoDotNet->dwRefreshInterval = zoneinfo->dwRefreshInterval; + r->ZoneInfoDotNet->dwAvailForScavengeTime = zoneinfo->dwAvailForScavengeTime; + r->ZoneInfoDotNet->aipScavengeServers = ip4_array_copy(mem_ctx, zoneinfo->aipScavengeServers); + r->ZoneInfoDotNet->dwForwarderTimeout = zoneinfo->dwForwarderTimeout; + r->ZoneInfoDotNet->fForwarderSlave = zoneinfo->fForwarderSlave; + r->ZoneInfoDotNet->aipLocalMasters = ip4_array_copy(mem_ctx, zoneinfo->aipLocalMasters); + r->ZoneInfoDotNet->dwDpFlags = z->partition->dwDpFlags; + r->ZoneInfoDotNet->pszDpFqdn = talloc_strdup(mem_ctx, z->partition->pszDpFqdn); + r->ZoneInfoDotNet->pwszZoneDn = talloc_strdup(mem_ctx, zoneinfo->pwszZoneDn); + r->ZoneInfoDotNet->dwLastSuccessfulSoaCheck = zoneinfo->dwLastSuccessfulSoaCheck; + r->ZoneInfoDotNet->dwLastSuccessfulXfr = zoneinfo->dwLastSuccessfulXfr; + + } else { + *typeid = DNSSRV_TYPEID_ZONE_INFO; + r->ZoneInfo = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_INFO_LONGHORN); + + r->ZoneInfo->dwRpcStructureVersion = 0x02; + r->ZoneInfo->pszZoneName = talloc_strdup(mem_ctx, z->name); + r->ZoneInfo->dwZoneType = zoneinfo->dwZoneType; + r->ZoneInfo->fReverse = zoneinfo->fReverse; + r->ZoneInfo->fAllowUpdate = zoneinfo->fAllowUpdate; + r->ZoneInfo->fPaused = zoneinfo->fPaused; + r->ZoneInfo->fShutdown = zoneinfo->fShutdown; + r->ZoneInfo->fAutoCreated = zoneinfo->fAutoCreated; + r->ZoneInfo->fUseDatabase = zoneinfo->fUseDatabase; + r->ZoneInfo->pszDataFile = talloc_strdup(mem_ctx, zoneinfo->pszDataFile); + r->ZoneInfo->aipMasters = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipMasters); + r->ZoneInfo->fSecureSecondaries = zoneinfo->fSecureSecondaries; + r->ZoneInfo->fNotifyLevel = zoneinfo->fNotifyLevel; + r->ZoneInfo->aipSecondaries = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipSecondaries); + r->ZoneInfo->aipNotify = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipNotify); + r->ZoneInfo->fUseWins = zoneinfo->fUseWins; + r->ZoneInfo->fUseNbstat = zoneinfo->fUseNbstat; + r->ZoneInfo->fAging = zoneinfo->fAging; + r->ZoneInfo->dwNoRefreshInterval = zoneinfo->dwNoRefreshInterval; + r->ZoneInfo->dwRefreshInterval = zoneinfo->dwRefreshInterval; + r->ZoneInfo->dwAvailForScavengeTime = zoneinfo->dwAvailForScavengeTime; + r->ZoneInfo->aipScavengeServers = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipScavengeServers); + r->ZoneInfo->dwForwarderTimeout = zoneinfo->dwForwarderTimeout; + r->ZoneInfo->fForwarderSlave = zoneinfo->fForwarderSlave; + r->ZoneInfo->aipLocalMasters = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipLocalMasters); + r->ZoneInfo->dwDpFlags = z->partition->dwDpFlags; + r->ZoneInfo->pszDpFqdn = talloc_strdup(mem_ctx, z->partition->pszDpFqdn); + r->ZoneInfo->pwszZoneDn = talloc_strdup(mem_ctx, zoneinfo->pwszZoneDn); + r->ZoneInfo->dwLastSuccessfulSoaCheck = zoneinfo->dwLastSuccessfulSoaCheck; + r->ZoneInfo->dwLastSuccessfulXfr = zoneinfo->dwLastSuccessfulXfr; + + r->ZoneInfo->fQueuedForBackgroundLoad = zoneinfo->fQueuedForBackgroundLoad; + r->ZoneInfo->fBackgroundLoadInProgress = zoneinfo->fBackgroundLoadInProgress; + r->ZoneInfo->fReadOnlyZone = zoneinfo->fReadOnlyZone; + r->ZoneInfo->dwLastXfrAttempt = zoneinfo->dwLastXfrAttempt; + r->ZoneInfo->dwLastXfrResult = zoneinfo->dwLastXfrResult; + } + + return WERR_OK; + } + + is_integer = 0; + + if (strcasecmp(operation, "AllowUpdate") == 0) { + answer_integer = zoneinfo->fAllowUpdate; + is_integer = 1; + } else if (strcasecmp(operation, "Secured") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "DsIntegrated") == 0) { + answer_integer = zoneinfo->fUseDatabase; + is_integer = 1; + } else if (strcasecmp(operation, "LogUpdates") == 0) { + answer_integer = 0; + is_integer = 1; + } else if (strcasecmp(operation, "NoRefreshInterval") == 0) { + answer_integer = zoneinfo->dwNoRefreshInterval; + is_integer = 1; + } else if (strcasecmp(operation, "NotifyLevel") == 0) { + answer_integer = zoneinfo->fNotifyLevel; + is_integer = 1; + } else if (strcasecmp(operation, "RefreshInterval") == 0) { + answer_integer = zoneinfo->dwRefreshInterval; + is_integer = 1; + } else if (strcasecmp(operation, "SecureSecondaries") == 0) { + answer_integer = zoneinfo->fSecureSecondaries; + is_integer = 1; + } else if (strcasecmp(operation, "Type") == 0) { + answer_integer = zoneinfo->dwZoneType; + is_integer = 1; + } else if (strcasecmp(operation, "Aging") == 0) { + answer_integer = zoneinfo->fAging; + is_integer = 1; + } else if (strcasecmp(operation, "ForwarderSlave") == 0) { + answer_integer = zoneinfo->fForwarderSlave; + is_integer = 1; + } else if (strcasecmp(operation, "ForwarderTimeout") == 0) { + answer_integer = zoneinfo->dwForwarderTimeout; + is_integer = 1; + } else if (strcasecmp(operation, "Unicode") == 0) { + answer_integer = 0; + is_integer = 1; + } + + if (is_integer == 1) { + *typeid = DNSSRV_TYPEID_DWORD; + r->Dword = answer_integer; + return WERR_OK; + } + + is_addresses = 0; + + if (strcasecmp(operation, "AllowNSRecordsAutoCreation") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = NULL; + } else { + answer_iparray = NULL; + } + is_addresses = 1; + } else if (strcasecmp(operation, "ScavengeServers") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipScavengeServers); + } else { + answer_iparray = ip4_array_copy(mem_ctx, zoneinfo->aipScavengeServers); + } + is_addresses = 1; + } else if (strcasecmp(operation, "MasterServers") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipMasters); + } else { + answer_iparray = ip4_array_copy(mem_ctx, zoneinfo->aipMasters); + } + is_addresses = 1; + } else if (strcasecmp(operation, "LocalMasterServers") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipLocalMasters); + } else { + answer_iparray = ip4_array_copy(mem_ctx, zoneinfo->aipLocalMasters); + } + is_addresses = 1; + } else if (strcasecmp(operation, "NotifyServers") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipNotify); + } else { + answer_iparray = ip4_array_copy(mem_ctx, zoneinfo->aipNotify); + } + is_addresses = 1; + } else if (strcasecmp(operation, "SecondaryServers") == 0) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + answer_addrarray = ip4_array_to_dns_addr_array(mem_ctx, zoneinfo->aipSecondaries); + } else { + answer_iparray = ip4_array_copy(mem_ctx, zoneinfo->aipSecondaries); + } + is_addresses = 1; + } + + if (is_addresses == 1) { + if (client_version == DNS_CLIENT_VERSION_LONGHORN) { + *typeid = DNSSRV_TYPEID_ADDRARRAY; + r->AddrArray = answer_addrarray; + } else { + *typeid = DNSSRV_TYPEID_IPARRAY; + r->IpArray = answer_iparray; + } + return WERR_OK; + } + + is_string = 0; + + if (strcasecmp(operation, "DatabaseFile") == 0) { + answer_string = talloc_strdup(mem_ctx, zoneinfo->pszDataFile); + is_string = 1; + } else if (strcasecmp(operation, "ApplicationDirectoryPartition") == 0) { + answer_string = talloc_strdup(mem_ctx, z->partition->pszDpFqdn); + is_string = 1; + } else if (strcasecmp(operation, "BreakOnNameUpdate") == 0) { + answer_string = NULL; + is_string = 1; + } + + if (is_string == 1) { + *typeid = DNSSRV_TYPEID_LPSTR; + r->String = answer_string; + return WERR_OK; + } + + DEBUG(0,("dnsserver: Invalid zone operation %s", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; + +} + +/* dnsserver operation functions */ + +/* [MS-DNSP].pdf Section 3.1.1.1 DNS Server Configuration Information */ +static WERROR dnsserver_operate_server(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID typeid, + union DNSSRV_RPC_UNION *r) +{ + bool valid_operation = false; + + if (strcasecmp(operation, "ResetDwordProperty") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "Restart") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ClearDebugLog") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ClearCache") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "WriteDirtyZones") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ZoneCreate") == 0) { + struct dnsserver_zone *z, *z2; + WERROR status; + size_t len; + const char *name; + z = talloc_zero(mem_ctx, struct dnsserver_zone); + W_ERROR_HAVE_NO_MEMORY(z); + z->partition = talloc_zero(z, struct dnsserver_partition); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(z->partition, z); + z->zoneinfo = talloc_zero(z, struct dnsserver_zoneinfo); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(z->zoneinfo, z); + + if (typeid == DNSSRV_TYPEID_ZONE_CREATE_W2K) { + name = r->ZoneCreateW2K->pszZoneName; + z->zoneinfo->dwZoneType = r->ZoneCreateW2K->dwZoneType; + z->zoneinfo->fAllowUpdate = r->ZoneCreateW2K->fAllowUpdate; + z->zoneinfo->fAging = r->ZoneCreateW2K->fAging; + z->zoneinfo->Flags = r->ZoneCreateW2K->dwFlags; + } else if (typeid == DNSSRV_TYPEID_ZONE_CREATE_DOTNET) { + name = r->ZoneCreateDotNet->pszZoneName; + z->zoneinfo->dwZoneType = r->ZoneCreateDotNet->dwZoneType; + z->zoneinfo->fAllowUpdate = r->ZoneCreateDotNet->fAllowUpdate; + z->zoneinfo->fAging = r->ZoneCreateDotNet->fAging; + z->zoneinfo->Flags = r->ZoneCreateDotNet->dwFlags; + z->partition->dwDpFlags = r->ZoneCreateDotNet->dwDpFlags; + } else if (typeid == DNSSRV_TYPEID_ZONE_CREATE) { + name = r->ZoneCreate->pszZoneName; + z->zoneinfo->dwZoneType = r->ZoneCreate->dwZoneType; + z->zoneinfo->fAllowUpdate = r->ZoneCreate->fAllowUpdate; + z->zoneinfo->fAging = r->ZoneCreate->fAging; + z->zoneinfo->Flags = r->ZoneCreate->dwFlags; + z->partition->dwDpFlags = r->ZoneCreate->dwDpFlags; + } else { + talloc_free(z); + return WERR_DNS_ERROR_INVALID_PROPERTY; + } + + len = strlen(name); + if (name[len-1] == '.') { + len -= 1; + } + z->name = talloc_strndup(z, name, len); + if (z->name == NULL) { + talloc_free(z); + return WERR_NOT_ENOUGH_MEMORY; + } + + z2 = dnsserver_find_zone(dsstate->zones, z->name); + if (z2 != NULL) { + talloc_free(z); + return WERR_DNS_ERROR_ZONE_ALREADY_EXISTS; + } + + status = dnsserver_db_create_zone(dsstate->samdb, dsstate->partitions, z, + dsstate->lp_ctx); + talloc_free(z); + + if (W_ERROR_IS_OK(status)) { + dnsserver_reload_zones(dsstate); + } + return status; + } else if (strcasecmp(operation, "ClearStatistics") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "EnlistDirectoryPartition") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "StartScavenging") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "AbortScavenging") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "AutoConfigure") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ExportSettings") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "PrepareForDemotion") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "PrepareForUninstall") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteNode") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteRecord") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "WriteBackFile") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ListenAddresses") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "Forwarders") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "LogFilePath") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "LogIpFilterList") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ForestDirectoryPartitionBaseName") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DomainDirectoryPartitionBaseName") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "GlobalQueryBlockList") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "BreakOnReceiveFrom") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "BreakOnUpdateFrom") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ServerLevelPluginDll") == 0) { + valid_operation = true; + } + + if (valid_operation) { + DEBUG(0, ("dnsserver: server operation '%s' not implemented", operation)); + return WERR_CALL_NOT_IMPLEMENTED; + } + + DEBUG(0, ("dnsserver: invalid server operation '%s'", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; +} + +static WERROR dnsserver_complex_operate_server(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID typeid_in, + union DNSSRV_RPC_UNION *rin, + enum DNS_RPC_TYPEID *typeid_out, + union DNSSRV_RPC_UNION *rout) +{ + int valid_operation = 0; + struct dnsserver_zone *z, **zlist; + size_t zcount; + bool found1, found2, found3, found4; + size_t i; + + if (strcasecmp(operation, "QueryDwordProperty") == 0) { + if (typeid_in == DNSSRV_TYPEID_LPSTR) { + return dnsserver_query_server(dsstate, mem_ctx, + rin->String, + client_version, + typeid_out, + rout); + } + } else if (strcasecmp(operation, "EnumZones") == 0) { + if (typeid_in != DNSSRV_TYPEID_DWORD) { + return WERR_DNS_ERROR_INVALID_PROPERTY; + } + + zcount = 0; + zlist = talloc_zero_array(mem_ctx, struct dnsserver_zone *, 0); + for (z = dsstate->zones; z; z = z->next) { + + /* Match the flags in groups + * + * Group1 : PRIMARY, SECONDARY, CACHE, AUTO + * Group2 : FORWARD, REVERSE, FORWARDER, STUB + * Group3 : DS, NON_DS, DOMAIN_DP, FOREST_DP + * Group4 : CUSTOM_DP, LEGACY_DP + */ + + /* Group 1 */ + found1 = false; + if (rin->Dword & 0x0000000f) { + if (rin->Dword & DNS_ZONE_REQUEST_PRIMARY) { + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_PRIMARY) { + found1 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_SECONDARY) { + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_SECONDARY) { + found1 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_CACHE) { + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_CACHE) { + found1 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_AUTO) { + if (z->zoneinfo->fAutoCreated + || z->partition->dwDpFlags & DNS_DP_AUTOCREATED) { + found1 = true; + } + } + } else { + found1 = true; + } + + /* Group 2 */ + found2 = false; + if (rin->Dword & 0x000000f0) { + if (rin->Dword & DNS_ZONE_REQUEST_FORWARD) { + if (!(z->zoneinfo->fReverse)) { + found2 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_REVERSE) { + if (z->zoneinfo->fReverse) { + found2 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_FORWARDER) { + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_FORWARDER) { + found2 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_STUB) { + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_STUB) { + found2 = true; + } + } + } else { + found2 = true; + } + + /* Group 3 */ + found3 = false; + if (rin->Dword & 0x00000f00) { + if (rin->Dword & DNS_ZONE_REQUEST_DS) { + if (z->zoneinfo->Flags & DNS_RPC_ZONE_DSINTEGRATED) { + found3 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_NON_DS) { + if (!(z->zoneinfo->Flags & DNS_RPC_ZONE_DSINTEGRATED)) { + found3 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_DOMAIN_DP) { + if (!(z->partition->dwDpFlags & DNS_DP_DOMAIN_DEFAULT)) { + found3 = true; + } + } + if (rin->Dword & DNS_ZONE_REQUEST_FOREST_DP) { + if (!(z->partition->dwDpFlags & DNS_DP_FOREST_DEFAULT)) { + found3 = true; + } + } + } else { + found3 = true; + } + + /* Group 4 */ + if (rin->Dword & 0x0000f000) { + found4 = false; + } else { + found4 = true; + } + + if (found1 && found2 && found3 && found4) { + zlist = talloc_realloc(mem_ctx, zlist, struct dnsserver_zone *, zcount+1); + zlist[zcount] = z; + zcount++; + } + } + + if (client_version == DNS_CLIENT_VERSION_W2K) { + *typeid_out = DNSSRV_TYPEID_ZONE_LIST_W2K; + rout->ZoneListW2K = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_LIST_W2K); + + if (zcount == 0) { + rout->ZoneListW2K->dwZoneCount = 0; + rout->ZoneListW2K->ZoneArray = NULL; + + return WERR_OK; + } + + rout->ZoneListW2K->ZoneArray = talloc_zero_array(mem_ctx, struct DNS_RPC_ZONE_W2K *, zcount); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(rout->ZoneListW2K->ZoneArray, zlist); + + for (i=0; i<zcount; i++) { + rout->ZoneListW2K->ZoneArray[i] = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_W2K); + + rout->ZoneListW2K->ZoneArray[i]->pszZoneName = talloc_strdup(mem_ctx, zlist[i]->name); + rout->ZoneListW2K->ZoneArray[i]->Flags = zlist[i]->zoneinfo->Flags; + rout->ZoneListW2K->ZoneArray[i]->ZoneType = zlist[i]->zoneinfo->dwZoneType; + rout->ZoneListW2K->ZoneArray[i]->Version = zlist[i]->zoneinfo->Version; + } + rout->ZoneListW2K->dwZoneCount = zcount; + + } else { + *typeid_out = DNSSRV_TYPEID_ZONE_LIST; + rout->ZoneList = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_LIST_DOTNET); + + if (zcount == 0) { + rout->ZoneList->dwRpcStructureVersion = 1; + rout->ZoneList->dwZoneCount = 0; + rout->ZoneList->ZoneArray = NULL; + + return WERR_OK; + } + + rout->ZoneList->ZoneArray = talloc_zero_array(mem_ctx, struct DNS_RPC_ZONE_DOTNET *, zcount); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(rout->ZoneList->ZoneArray, zlist); + + for (i=0; i<zcount; i++) { + rout->ZoneList->ZoneArray[i] = talloc_zero(mem_ctx, struct DNS_RPC_ZONE_DOTNET); + + rout->ZoneList->ZoneArray[i]->dwRpcStructureVersion = 1; + rout->ZoneList->ZoneArray[i]->pszZoneName = talloc_strdup(mem_ctx, zlist[i]->name); + rout->ZoneList->ZoneArray[i]->Flags = zlist[i]->zoneinfo->Flags; + rout->ZoneList->ZoneArray[i]->ZoneType = zlist[i]->zoneinfo->dwZoneType; + rout->ZoneList->ZoneArray[i]->Version = zlist[i]->zoneinfo->Version; + rout->ZoneList->ZoneArray[i]->dwDpFlags = zlist[i]->partition->dwDpFlags; + rout->ZoneList->ZoneArray[i]->pszDpFqdn = talloc_strdup(mem_ctx, zlist[i]->partition->pszDpFqdn); + } + rout->ZoneList->dwRpcStructureVersion = 1; + rout->ZoneList->dwZoneCount = zcount; + } + talloc_free(zlist); + return WERR_OK; + } else if (strcasecmp(operation, "EnumZones2") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "EnumDirectoryPartitions") == 0) { + if (typeid_in != DNSSRV_TYPEID_DWORD) { + return WERR_DNS_ERROR_INVALID_PROPERTY; + } + + *typeid_out = DNSSRV_TYPEID_DP_LIST; + rout->DirectoryPartitionList = talloc_zero(mem_ctx, struct DNS_RPC_DP_LIST); + + if (rin->Dword != 0) { + rout->DirectoryPartitionList->dwDpCount = 0; + rout->DirectoryPartitionList->DpArray = NULL; + } else { + struct DNS_RPC_DP_ENUM **dplist; + struct dnsserver_partition *p; + int pcount = 2; + + dplist = talloc_zero_array(mem_ctx, struct DNS_RPC_DP_ENUM *, pcount); + W_ERROR_HAVE_NO_MEMORY(dplist); + + p = dsstate->partitions; + for (i=0; i<pcount; i++) { + dplist[i] = talloc_zero(dplist, struct DNS_RPC_DP_ENUM); + + dplist[i]->pszDpFqdn = talloc_strdup(mem_ctx, p->pszDpFqdn); + dplist[i]->dwFlags = p->dwDpFlags; + dplist[i]->dwZoneCount = p->zones_count; + p = p->next; + } + + rout->DirectoryPartitionList->dwDpCount = pcount; + rout->DirectoryPartitionList->DpArray = dplist; + } + return WERR_OK; + } else if (strcasecmp(operation, "DirectoryPartitionInfo") == 0) { + struct dnsserver_partition *p; + struct dnsserver_partition_info *partinfo; + struct DNS_RPC_DP_INFO *dpinfo = NULL; + + if (typeid_in != DNSSRV_TYPEID_LPSTR) { + return WERR_DNS_ERROR_INVALID_PROPERTY; + } + + *typeid_out = DNSSRV_TYPEID_DP_INFO; + + for (p = dsstate->partitions; p; p = p->next) { + if (strcasecmp(p->pszDpFqdn, rin->String) == 0) { + dpinfo = talloc_zero(mem_ctx, struct DNS_RPC_DP_INFO); + W_ERROR_HAVE_NO_MEMORY(dpinfo); + + partinfo = dnsserver_db_partition_info(mem_ctx, dsstate->samdb, p); + W_ERROR_HAVE_NO_MEMORY(partinfo); + + dpinfo->pszDpFqdn = talloc_strdup(dpinfo, p->pszDpFqdn); + dpinfo->pszDpDn = talloc_strdup(dpinfo, ldb_dn_get_linearized(p->partition_dn)); + dpinfo->pszCrDn = talloc_steal(dpinfo, partinfo->pszCrDn); + dpinfo->dwFlags = p->dwDpFlags; + dpinfo->dwZoneCount = p->zones_count; + dpinfo->dwState = partinfo->dwState; + dpinfo->dwReplicaCount = partinfo->dwReplicaCount; + if (partinfo->dwReplicaCount > 0) { + dpinfo->ReplicaArray = talloc_steal(dpinfo, + partinfo->ReplicaArray); + } else { + dpinfo->ReplicaArray = NULL; + } + break; + } + } + + if (dpinfo == NULL) { + return WERR_DNS_ERROR_DP_DOES_NOT_EXIST; + } + + rout->DirectoryPartition = dpinfo; + return WERR_OK; + } else if (strcasecmp(operation, "Statistics") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "IpValidate") == 0) { + valid_operation = true; + } + + if (valid_operation) { + DEBUG(0, ("dnsserver: server complex operation '%s' not implemented", operation)); + return WERR_CALL_NOT_IMPLEMENTED; + } + + DEBUG(0, ("dnsserver: invalid server complex operation '%s'", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; +} + +/* [MS-DNSP].pdf Section 3.1.1.2 Zone Configuration Information */ +static WERROR dnsserver_operate_zone(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + unsigned int request_filter, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID typeid, + union DNSSRV_RPC_UNION *r) +{ + bool valid_operation = false; + + if (strcasecmp(operation, "ResetDwordProperty") == 0) { + + if (typeid != DNSSRV_TYPEID_NAME_AND_PARAM) { + return WERR_DNS_ERROR_INVALID_PROPERTY; + } + + return dnsserver_db_do_reset_dword(dsstate->samdb, z, + r->NameAndParam); + + } else if (strcasecmp(operation, "ZoneTypeReset") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "PauseZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ResumeZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ReloadZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "RefreshZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ExpireZone") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "IncrementVersion") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "WriteBackFile") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteZoneFromDs") == 0) { + WERROR status; + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + status = dnsserver_db_delete_zone(dsstate->samdb, z); + if (W_ERROR_IS_OK(status)) { + dnsserver_reload_zones(dsstate); + } + return status; + } else if (strcasecmp(operation, "UpdateZoneFromDs") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ZoneExport") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ZoneChangeDirectoryPartition") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteNode") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DeleteRecordSet") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ForceAgingOnNode") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "DatabaseFile") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "MasterServers") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "LocalMasterServers") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "NotifyServers") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "SecondaryServers") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ScavengingServers") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "AllowNSRecordsAutoCreation") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "BreakOnNameUpdate") == 0) { + valid_operation = true; + } else if (strcasecmp(operation, "ApplicationDirectoryPartition") == 0) { + valid_operation = true; + } + + if (valid_operation) { + DEBUG(0, ("dnsserver: zone operation '%s' not implemented", operation)); + return WERR_CALL_NOT_IMPLEMENTED; + } + + DEBUG(0, ("dnsserver: invalid zone operation '%s'", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; +} + +static WERROR dnsserver_complex_operate_zone(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + const char *operation, + const unsigned int client_version, + enum DNS_RPC_TYPEID typeid_in, + union DNSSRV_RPC_UNION *rin, + enum DNS_RPC_TYPEID *typeid_out, + union DNSSRV_RPC_UNION *rout) +{ + if (strcasecmp(operation, "QueryDwordProperty") == 0) { + if (typeid_in == DNSSRV_TYPEID_LPSTR) { + return dnsserver_query_zone(dsstate, mem_ctx, z, + rin->String, + client_version, + typeid_out, + rout); + + } + } + + DEBUG(0,("dnsserver: Invalid zone operation %s", operation)); + return WERR_DNS_ERROR_INVALID_PROPERTY; +} + +/* dnsserver enumerate function */ + +static WERROR dnsserver_enumerate_root_records(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + unsigned int client_version, + const char *node_name, + enum dns_record_type record_type, + unsigned int select_flag, + unsigned int *buffer_length, + struct DNS_RPC_RECORDS_ARRAY **buffer) +{ + TALLOC_CTX *tmp_ctx; + struct dnsserver_zone *z; + const char * const attrs[] = { "name", "dnsRecord", NULL }; + struct ldb_result *res; + struct DNS_RPC_RECORDS_ARRAY *recs; + char **add_names; + char *rname; + int add_count; + int i, ret, len; + WERROR status; + + tmp_ctx = talloc_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + z = dnsserver_find_zone(dsstate->zones, "."); + if (z == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + ret = ldb_search(dsstate->samdb, tmp_ctx, &res, z->zone_dn, + LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=@)(!(dNSTombstoned=TRUE)))"); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_INTERNAL_DB_ERROR; + } + if (res->count == 0) { + talloc_free(tmp_ctx); + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + recs = talloc_zero(mem_ctx, struct DNS_RPC_RECORDS_ARRAY); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(recs, tmp_ctx); + + add_names = NULL; + add_count = 0; + + for (i=0; i<res->count; i++) { + status = dns_fill_records_array(tmp_ctx, NULL, record_type, + select_flag, NULL, + res->msgs[i], 0, recs, + &add_names, &add_count); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + talloc_free(res); + + /* Add any additional records */ + if (select_flag & DNS_RPC_VIEW_ADDITIONAL_DATA) { + for (i=0; i<add_count; i++) { + char *encoded_name + = ldb_binary_encode_string(tmp_ctx, + add_names[i]); + ret = ldb_search(dsstate->samdb, tmp_ctx, &res, z->zone_dn, + LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))", + encoded_name); + if (ret != LDB_SUCCESS || res->count == 0) { + talloc_free(res); + continue; + } + + len = strlen(add_names[i]); + if (add_names[i][len-1] == '.') { + rname = talloc_strdup(tmp_ctx, add_names[i]); + } else { + rname = talloc_asprintf(tmp_ctx, "%s.", add_names[i]); + } + status = dns_fill_records_array(tmp_ctx, NULL, DNS_TYPE_A, + select_flag, rname, + res->msgs[0], 0, recs, + NULL, NULL); + talloc_free(rname); + talloc_free(res); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + } + + talloc_free(tmp_ctx); + + *buffer_length = ndr_size_DNS_RPC_RECORDS_ARRAY(recs, 0); + *buffer = recs; + + return WERR_OK; +} + + +static WERROR dnsserver_enumerate_records(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + unsigned int client_version, + const char *node_name, + const char *start_child, + enum dns_record_type record_type, + unsigned int select_flag, + const char *filter_start, + const char *filter_stop, + unsigned int *buffer_length, + struct DNS_RPC_RECORDS_ARRAY **buffer) +{ + TALLOC_CTX *tmp_ctx; + char *name; + const char * const attrs[] = { "name", "dnsRecord", NULL }; + struct ldb_result *res = NULL; + struct DNS_RPC_RECORDS_ARRAY *recs = NULL; + char **add_names = NULL; + char *rname = NULL; + const char *preference_name = NULL; + int add_count = 0; + int i, ret, len; + WERROR status; + struct dns_tree *tree = NULL; + struct dns_tree *base = NULL; + struct dns_tree *node = NULL; + + tmp_ctx = talloc_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + name = dns_split_node_name(tmp_ctx, node_name, z->name); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(name, tmp_ctx); + + /* search all records under parent tree */ + if (strcasecmp(name, z->name) == 0) { + ret = ldb_search(dsstate->samdb, tmp_ctx, &res, z->zone_dn, + LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(!(dNSTombstoned=TRUE)))"); + preference_name = "@"; + } else { + char *encoded_name + = ldb_binary_encode_string(tmp_ctx, name); + ret = ldb_search(dsstate->samdb, tmp_ctx, &res, z->zone_dn, + LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(|(name=%s)(name=*.%s))(!(dNSTombstoned=TRUE)))", + encoded_name, encoded_name); + preference_name = name; + } + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return WERR_INTERNAL_DB_ERROR; + } + if (res->count == 0) { + talloc_free(tmp_ctx); + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + recs = talloc_zero(mem_ctx, struct DNS_RPC_RECORDS_ARRAY); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(recs, tmp_ctx); + + /* + * Sort the names, so that the records are in order by the child + * component below "name". + * + * A full tree sort is not required, so we pass in "name" so + * we know which level to sort, as only direct children are + * eventually returned + */ + LDB_TYPESAFE_QSORT(res->msgs, res->count, name, dns_name_compare); + + /* Build a tree of name components from dns name */ + tree = dns_build_tree(tmp_ctx, preference_name, res); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(tree, tmp_ctx); + + /* Find the parent record in the tree */ + base = tree; + while (base->level != -1) { + base = base->children[0]; + } + + /* Add the parent record with blank name */ + if (!(select_flag & DNS_RPC_VIEW_ONLY_CHILDREN)) { + status = dns_fill_records_array(tmp_ctx, z, record_type, + select_flag, NULL, + base->data, 0, + recs, &add_names, &add_count); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + + /* Add all the children records */ + if (!(select_flag & DNS_RPC_VIEW_NO_CHILDREN)) { + for (i=0; i<base->num_children; i++) { + node = base->children[i]; + + status = dns_fill_records_array(tmp_ctx, z, record_type, + select_flag, node->name, + node->data, node->num_children, + recs, &add_names, &add_count); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + } + + TALLOC_FREE(res); + TALLOC_FREE(tree); + TALLOC_FREE(name); + + /* Add any additional records */ + if (select_flag & DNS_RPC_VIEW_ADDITIONAL_DATA) { + for (i=0; i<add_count; i++) { + struct dnsserver_zone *z2 = NULL; + struct ldb_message *msg = NULL; + /* Search all the available zones for additional name */ + for (z2 = dsstate->zones; z2; z2 = z2->next) { + char *encoded_name; + name = dns_split_node_name(tmp_ctx, add_names[i], z2->name); + encoded_name + = ldb_binary_encode_string(tmp_ctx, + name); + ret = ldb_search(dsstate->samdb, tmp_ctx, &res, z2->zone_dn, + LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))", + encoded_name); + TALLOC_FREE(name); + if (ret != LDB_SUCCESS) { + continue; + } + if (res->count == 1) { + msg = res->msgs[0]; + break; + } else { + TALLOC_FREE(res); + continue; + } + } + + len = strlen(add_names[i]); + if (add_names[i][len-1] == '.') { + rname = talloc_strdup(tmp_ctx, add_names[i]); + } else { + rname = talloc_asprintf(tmp_ctx, "%s.", add_names[i]); + } + status = dns_fill_records_array(tmp_ctx, NULL, DNS_TYPE_A, + select_flag, rname, + msg, 0, recs, + NULL, NULL); + TALLOC_FREE(rname); + TALLOC_FREE(res); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + } + } + + *buffer_length = ndr_size_DNS_RPC_RECORDS_ARRAY(recs, 0); + *buffer = recs; + + return WERR_OK; +} + +/* + * Check str1 + '.' + str2 = name, for example: + * ("dc0", "example.com", "dc0.example.com") = true + */ +static bool cname_self_reference(const char* node_name, + const char* zone_name, + struct DNS_RPC_NAME name) { + size_t node_len, zone_len; + + if (node_name == NULL || zone_name == NULL) { + return false; + } + + node_len = strlen(node_name); + zone_len = strlen(zone_name); + + if (node_len == 0 || + zone_len == 0 || + (name.len != node_len + zone_len + 1)) { + return false; + } + + if (strncmp(node_name, name.str, node_len) == 0 && + name.str[node_len] == '.' && + strncmp(zone_name, name.str + node_len + 1, zone_len) == 0) { + return true; + } + + return false; +} + +/* dnsserver update function */ + +static WERROR dnsserver_update_record(struct dnsserver_state *dsstate, + TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + unsigned int client_version, + const char *node_name, + struct DNS_RPC_RECORD_BUF *add_buf, + struct DNS_RPC_RECORD_BUF *del_buf) +{ + TALLOC_CTX *tmp_ctx; + char *name; + WERROR status; + + tmp_ctx = talloc_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + /* If node_name is @ or zone name, dns record is @ */ + if (strcmp(node_name, "@") == 0 || + strcmp(node_name, ".") == 0 || + strcasecmp(node_name, z->name) == 0) { + name = talloc_strdup(tmp_ctx, "@"); + } else { + name = dns_split_node_name(tmp_ctx, node_name, z->name); + } + W_ERROR_HAVE_NO_MEMORY_AND_FREE(name, tmp_ctx); + + /* CNAMEs can't point to themselves */ + if (add_buf != NULL && add_buf->rec.wType == DNS_TYPE_CNAME) { + if (cname_self_reference(node_name, z->name, add_buf->rec.data.name)) { + return WERR_DNS_ERROR_CNAME_LOOP; + } + } + + if (add_buf != NULL) { + if (del_buf == NULL) { + /* Add record */ + status = dnsserver_db_add_record(tmp_ctx, dsstate->samdb, + z, name, + &add_buf->rec); + } else { + /* Update record */ + status = dnsserver_db_update_record(tmp_ctx, dsstate->samdb, + z, name, + &add_buf->rec, + &del_buf->rec); + } + } else { + if (del_buf == NULL) { + /* Add empty node */ + status = dnsserver_db_add_empty_node(tmp_ctx, dsstate->samdb, + z, name); + } else { + /* Delete record */ + status = dnsserver_db_delete_record(tmp_ctx, dsstate->samdb, + z, name, + &del_buf->rec); + } + } + + talloc_free(tmp_ctx); + return status; +} + + +/* dnsserver interface functions */ + +static WERROR dcesrv_DnssrvOperation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvOperation *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z = NULL; + uint32_t request_filter = 0; + WERROR ret; + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.dwContext == 0) { + if (r->in.pszZone != NULL) { + request_filter = dnsserver_zone_to_request_filter(r->in.pszZone); + } + } else { + request_filter = r->in.dwContext; + } + + if (r->in.pszZone == NULL) { + ret = dnsserver_operate_server(dsstate, mem_ctx, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->in.dwTypeId, + &r->in.pData); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + /* + * In the case that request_filter is not 0 and z is NULL, + * the request is for a multizone operation, which we do not + * yet support, so just error on NULL zone name. + */ + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_operate_zone(dsstate, mem_ctx, z, + request_filter, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->in.dwTypeId, + &r->in.pData); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvOperation, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvQuery(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvQuery *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwTypeId); + ZERO_STRUCTP(r->out.ppData); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + /* FIXME: DNS Server Configuration Access Control List */ + ret = dnsserver_query_server(dsstate, mem_ctx, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->out.pdwTypeId, + r->out.ppData); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_query_zone(dsstate, mem_ctx, z, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->out.pdwTypeId, + r->out.ppData); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvQuery, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvComplexOperation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvComplexOperation *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwTypeOut); + ZERO_STRUCTP(r->out.ppDataOut); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + /* Server operation */ + ret = dnsserver_complex_operate_server(dsstate, mem_ctx, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->in.dwTypeIn, + &r->in.pDataIn, + r->out.pdwTypeOut, + r->out.ppDataOut); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_complex_operate_zone(dsstate, mem_ctx, z, + r->in.pszOperation, + DNS_CLIENT_VERSION_W2K, + r->in.dwTypeIn, + &r->in.pDataIn, + r->out.pdwTypeOut, + r->out.ppDataOut); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvComplexOperation, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvEnumRecords(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvEnumRecords *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwBufferLength); + ZERO_STRUCTP(r->out.pBuffer); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + if (strcasecmp(r->in.pszZone, "..RootHints") == 0) { + ret = dnsserver_enumerate_root_records(dsstate, mem_ctx, + DNS_CLIENT_VERSION_W2K, + r->in.pszNodeName, + r->in.wRecordType, + r->in.fSelectFlag, + r->out.pdwBufferLength, + r->out.pBuffer); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + ret = dnsserver_enumerate_records(dsstate, mem_ctx, z, + DNS_CLIENT_VERSION_W2K, + r->in.pszNodeName, + r->in.pszStartChild, + r->in.wRecordType, + r->in.fSelectFlag, + r->in.pszFilterStart, + r->in.pszFilterStop, + r->out.pdwBufferLength, + r->out.pBuffer); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvEnumRecords, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvUpdateRecord(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvUpdateRecord *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + ret = dnsserver_update_record(dsstate, mem_ctx, z, + DNS_CLIENT_VERSION_W2K, + r->in.pszNodeName, + r->in.pAddRecord, + r->in.pDeleteRecord); + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvUpdateRecord, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvOperation2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvOperation2 *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z = NULL; + uint32_t request_filter = 0; + WERROR ret; + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.dwContext == 0) { + if (r->in.pszZone != NULL) { + request_filter = dnsserver_zone_to_request_filter(r->in.pszZone); + } + } else { + request_filter = r->in.dwContext; + } + + if (r->in.pszZone == NULL) { + ret = dnsserver_operate_server(dsstate, mem_ctx, + r->in.pszOperation, + r->in.dwClientVersion, + r->in.dwTypeId, + &r->in.pData); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + /* + * In the case that request_filter is not 0 and z is NULL, + * the request is for a multizone operation, which we do not + * yet support, so just error on NULL zone name. + */ + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_operate_zone(dsstate, mem_ctx, z, + request_filter, + r->in.pszOperation, + r->in.dwClientVersion, + r->in.dwTypeId, + &r->in.pData); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvOperation2, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvQuery2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvQuery2 *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwTypeId); + ZERO_STRUCTP(r->out.ppData); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + /* FIXME: DNS Server Configuration Access Control List */ + ret = dnsserver_query_server(dsstate, mem_ctx, + r->in.pszOperation, + r->in.dwClientVersion, + r->out.pdwTypeId, + r->out.ppData); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_query_zone(dsstate, mem_ctx, z, + r->in.pszOperation, + r->in.dwClientVersion, + r->out.pdwTypeId, + r->out.ppData); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvQuery2, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvComplexOperation2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvComplexOperation2 *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwTypeOut); + ZERO_STRUCTP(r->out.ppDataOut); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + /* Server operation */ + ret = dnsserver_complex_operate_server(dsstate, mem_ctx, + r->in.pszOperation, + r->in.dwClientVersion, + r->in.dwTypeIn, + &r->in.pDataIn, + r->out.pdwTypeOut, + r->out.ppDataOut); + } else { + + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST; + } + + ret = dnsserver_complex_operate_zone(dsstate, mem_ctx, z, + r->in.pszOperation, + r->in.dwClientVersion, + r->in.dwTypeIn, + &r->in.pDataIn, + r->out.pdwTypeOut, + r->out.ppDataOut); + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvComplexOperation2, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvEnumRecords2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvEnumRecords2 *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + ZERO_STRUCTP(r->out.pdwBufferLength); + ZERO_STRUCTP(r->out.pBuffer); + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + if (strcasecmp(r->in.pszZone, "..RootHints") == 0) { + ret = dnsserver_enumerate_root_records(dsstate, mem_ctx, + r->in.dwClientVersion, + r->in.pszNodeName, + r->in.wRecordType, + r->in.fSelectFlag, + r->out.pdwBufferLength, + r->out.pBuffer); + } else { + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + ret = dnsserver_enumerate_records(dsstate, mem_ctx, z, + r->in.dwClientVersion, + r->in.pszNodeName, + r->in.pszStartChild, + r->in.wRecordType, + r->in.fSelectFlag, + r->in.pszFilterStart, + r->in.pszFilterStop, + r->out.pdwBufferLength, + r->out.pBuffer); + + } + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvEnumRecords2, NDR_IN, r); + } + return ret; +} + +static WERROR dcesrv_DnssrvUpdateRecord2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct DnssrvUpdateRecord2 *r) +{ + struct dnsserver_state *dsstate; + struct dnsserver_zone *z; + WERROR ret; + + if ((dsstate = dnsserver_connect(dce_call)) == NULL) { + return WERR_DNS_ERROR_DS_UNAVAILABLE; + } + + if (r->in.pszZone == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + z = dnsserver_find_zone(dsstate->zones, r->in.pszZone); + if (z == NULL) { + return WERR_DNS_ERROR_NAME_DOES_NOT_EXIST; + } + + ret = dnsserver_update_record(dsstate, mem_ctx, z, + r->in.dwClientVersion, + r->in.pszNodeName, + r->in.pAddRecord, + r->in.pDeleteRecord); + + if (W_ERROR_EQUAL(ret, WERR_CALL_NOT_IMPLEMENTED)) { + NDR_PRINT_FUNCTION_DEBUG(DnssrvUpdateRecord2, NDR_IN, r); + } + return ret; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_dnsserver_s.c" diff --git a/source4/rpc_server/dnsserver/dnsdata.c b/source4/rpc_server/dnsserver/dnsdata.c new file mode 100644 index 0000000..002d9e6 --- /dev/null +++ b/source4/rpc_server/dnsserver/dnsdata.c @@ -0,0 +1,1121 @@ +/* + Unix SMB/CIFS implementation. + + DNS Server + + Copyright (C) Amitay Isaacs 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "dnsserver.h" +#include "dns_server/dnsserver_common.h" +#include "lib/replace/system/network.h" +#include "librpc/gen_ndr/ndr_dnsp.h" +#include "librpc/gen_ndr/ndr_dnsserver.h" + +#undef strcasecmp + +struct IP4_ARRAY *ip4_array_copy(TALLOC_CTX *mem_ctx, struct IP4_ARRAY *ip4) +{ + struct IP4_ARRAY *ret; + + if (!ip4) { + return NULL; + } + + ret = talloc_zero(mem_ctx, struct IP4_ARRAY); + if (!ret) { + return ret; + } + + ret->AddrCount = ip4->AddrCount; + if (ip4->AddrCount > 0) { + ret->AddrArray = talloc_zero_array(mem_ctx, unsigned int, ip4->AddrCount); + if (ret->AddrArray) { + memcpy(ret->AddrArray, ip4->AddrArray, + sizeof(unsigned int) * ip4->AddrCount); + } else { + talloc_free(ret); + return NULL; + } + } + return ret; +} + + +struct DNS_ADDR_ARRAY *ip4_array_to_dns_addr_array(TALLOC_CTX *mem_ctx, + struct IP4_ARRAY *ip4) +{ + struct DNS_ADDR_ARRAY *ret; + int i; + + if (!ip4) { + return NULL; + } + + ret = talloc_zero(mem_ctx, struct DNS_ADDR_ARRAY); + if (!ret) { + return ret; + } + + ret->MaxCount = ip4->AddrCount; + ret->AddrCount = ip4->AddrCount; + ret->Family = AF_INET; + if (ip4->AddrCount > 0) { + ret->AddrArray = talloc_zero_array(mem_ctx, struct DNS_ADDR, ip4->AddrCount); + if (ret->AddrArray) { + for (i=0; i<ip4->AddrCount; i++) { + ret->AddrArray[i].MaxSa[0] = 0x02; + ret->AddrArray[i].MaxSa[3] = 53; + memcpy(&ret->AddrArray[i].MaxSa[4], ip4->AddrArray, + sizeof(unsigned int)); + ret->AddrArray[i].DnsAddrUserDword[0] = 6; + } + + } else { + talloc_free(ret); + return NULL; + } + } + return ret; +} + +struct IP4_ARRAY *dns_addr_array_to_ip4_array(TALLOC_CTX *mem_ctx, + struct DNS_ADDR_ARRAY *ip) +{ + struct IP4_ARRAY *ret; + size_t i, count, curr; + + if (ip == NULL) { + return NULL; + } + /* We must only return IPv4 addresses. + The passed DNS_ADDR_ARRAY may contain: + - only ipv4 addresses + - only ipv6 addresses + - a mixture of both + - an empty array + */ + ret = talloc_zero(mem_ctx, struct IP4_ARRAY); + if (!ret) { + return ret; + } + if (ip->AddrCount == 0 || ip->Family == AF_INET6) { + ret->AddrCount = 0; + return ret; + } + /* Now only ipv4 addresses or a mixture are left */ + count = 0; + for (i = 0; i < ip->AddrCount; i++) { + if (ip->AddrArray[i].MaxSa[0] == 0x02) { + /* Is ipv4 */ + count++; + } + } + if (count == 0) { + /* should not happen */ + ret->AddrCount = 0; + return ret; + } + ret->AddrArray = talloc_zero_array(mem_ctx, uint32_t, count); + if (ret->AddrArray) { + curr = 0; + for (i = 0; i < ip->AddrCount; i++) { + if (ip->AddrArray[i].MaxSa[0] == 0x02) { + /* Is ipv4 */ + memcpy(&ret->AddrArray[curr], + &ip->AddrArray[i].MaxSa[4], + sizeof(uint32_t)); + curr++; + } + } + } else { + talloc_free(ret); + return NULL; + } + ret->AddrCount = curr; + return ret; +} + +struct DNS_ADDR_ARRAY *dns_addr_array_copy(TALLOC_CTX *mem_ctx, + struct DNS_ADDR_ARRAY *addr) +{ + struct DNS_ADDR_ARRAY *ret; + + if (!addr) { + return NULL; + } + + ret = talloc_zero(mem_ctx, struct DNS_ADDR_ARRAY); + if (!ret) { + return ret; + } + + ret->MaxCount = addr->MaxCount; + ret->AddrCount = addr->AddrCount; + ret->Family = addr->Family; + if (addr->AddrCount > 0) { + ret->AddrArray = talloc_zero_array(mem_ctx, struct DNS_ADDR, addr->AddrCount); + if (ret->AddrArray) { + memcpy(ret->AddrArray, addr->AddrArray, + sizeof(struct DNS_ADDR) * addr->AddrCount); + } else { + talloc_free(ret); + return NULL; + } + } + return ret; +} + + +int dns_split_name_components(TALLOC_CTX *tmp_ctx, const char *name, char ***components) +{ + char *str = NULL, *ptr, **list; + int count = 0; + + if (name == NULL) { + return 0; + } + + str = talloc_strdup(tmp_ctx, name); + if (!str) { + goto failed; + } + + list = talloc_zero_array(tmp_ctx, char *, 0); + if (!list) { + goto failed; + } + + ptr = strtok(str, "."); + while (ptr != NULL) { + count++; + list = talloc_realloc(tmp_ctx, list, char *, count); + if (!list) { + goto failed; + } + list[count-1] = talloc_strdup(tmp_ctx, ptr); + if (list[count-1] == NULL) { + goto failed; + } + ptr = strtok(NULL, "."); + } + + talloc_free(str); + + *components = list; + return count; + +failed: + TALLOC_FREE(str); + return -1; +} + + +char *dns_split_node_name(TALLOC_CTX *tmp_ctx, const char *node_name, const char *zone_name) +{ + char **nlist, **zlist; + char *prefix; + int ncount, zcount, i, match; + + /* + * If node_name is "@", return the zone_name + * If node_name is ".", return NULL + * If there is no '.' in node_name, return the node_name as is. + * + * If node_name does not have zone_name in it, return the node_name as is. + * + * If node_name has additional components as compared to zone_name + * return only the additional components as a prefix. + * + */ + if (strcmp(node_name, "@") == 0) { + prefix = talloc_strdup(tmp_ctx, zone_name); + } else if (strcmp(node_name, ".") == 0) { + prefix = NULL; + } else if (strchr(node_name, '.') == NULL) { + prefix = talloc_strdup(tmp_ctx, node_name); + } else { + zcount = dns_split_name_components(tmp_ctx, zone_name, &zlist); + ncount = dns_split_name_components(tmp_ctx, node_name, &nlist); + if (zcount < 0 || ncount < 0) { + return NULL; + } + + if (ncount < zcount) { + prefix = talloc_strdup(tmp_ctx, node_name); + } else { + match = 0; + for (i=1; i<=zcount; i++) { + if (strcasecmp(nlist[ncount-i], zlist[zcount-i]) != 0) { + break; + } + match++; + } + + if (match == ncount) { + prefix = talloc_strdup(tmp_ctx, zone_name); + } else { + prefix = talloc_strdup(tmp_ctx, nlist[0]); + if (prefix != NULL) { + for (i=1; i<ncount-match; i++) { + prefix = talloc_asprintf_append(prefix, ".%s", nlist[i]); + if (prefix == NULL) { + break; + } + } + } + } + } + + talloc_free(zlist); + talloc_free(nlist); + } + + return prefix; +} + + +void dnsp_to_dns_copy(TALLOC_CTX *mem_ctx, struct dnsp_DnssrvRpcRecord *dnsp, + struct DNS_RPC_RECORD *dns) +{ + int i, len; + + ZERO_STRUCTP(dns); + + dns->wDataLength = dnsp->wDataLength; + dns->wType = dnsp->wType; + dns->dwFlags = dnsp->rank; + dns->dwSerial = dnsp->dwSerial; + dns->dwTtlSeconds = dnsp->dwTtlSeconds; + dns->dwTimeStamp = dnsp->dwTimeStamp; + + switch (dnsp->wType) { + + case DNS_TYPE_TOMBSTONE: + dns->data.EntombedTime = dnsp->data.EntombedTime; + break; + + case DNS_TYPE_A: + dns->data.ipv4 = talloc_strdup(mem_ctx, dnsp->data.ipv4); + break; + + case DNS_TYPE_NS: + len = strlen(dnsp->data.ns); + if (dnsp->data.ns[len-1] == '.') { + dns->data.name.len = len; + dns->data.name.str = talloc_strdup(mem_ctx, dnsp->data.ns); + } else { + dns->data.name.len = len+1; + dns->data.name.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.ns); + } + break; + + case DNS_TYPE_CNAME: + len = strlen(dnsp->data.cname); + if (dnsp->data.cname[len-1] == '.') { + dns->data.name.len = len; + dns->data.name.str = talloc_strdup(mem_ctx, dnsp->data.cname); + } else { + dns->data.name.len = len+1; + dns->data.name.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.cname); + } + break; + + case DNS_TYPE_SOA: + dns->data.soa.dwSerialNo = dnsp->data.soa.serial; + dns->data.soa.dwRefresh = dnsp->data.soa.refresh; + dns->data.soa.dwRetry = dnsp->data.soa.retry; + dns->data.soa.dwExpire = dnsp->data.soa.expire; + dns->data.soa.dwMinimumTtl = dnsp->data.soa.minimum; + + len = strlen(dnsp->data.soa.mname); + if (dnsp->data.soa.mname[len-1] == '.') { + dns->data.soa.NamePrimaryServer.len = len; + dns->data.soa.NamePrimaryServer.str = talloc_strdup(mem_ctx, dnsp->data.soa.mname); + } else { + dns->data.soa.NamePrimaryServer.len = len+1; + dns->data.soa.NamePrimaryServer.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.soa.mname); + } + + len = strlen(dnsp->data.soa.rname); + if (dnsp->data.soa.rname[len-1] == '.') { + dns->data.soa.ZoneAdministratorEmail.len = len; + dns->data.soa.ZoneAdministratorEmail.str = talloc_strdup(mem_ctx, dnsp->data.soa.rname); + } else { + dns->data.soa.ZoneAdministratorEmail.len = len+1; + dns->data.soa.ZoneAdministratorEmail.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.soa.rname); + } + break; + + case DNS_TYPE_PTR: + dns->data.ptr.len = strlen(dnsp->data.ptr); + dns->data.ptr.str = talloc_strdup(mem_ctx, dnsp->data.ptr); + break; + + case DNS_TYPE_MX: + dns->data.mx.wPreference = dnsp->data.mx.wPriority; + len = strlen(dnsp->data.mx.nameTarget); + if (dnsp->data.mx.nameTarget[len-1] == '.') { + dns->data.mx.nameExchange.len = len; + dns->data.mx.nameExchange.str = talloc_strdup(mem_ctx, dnsp->data.mx.nameTarget); + } else { + dns->data.mx.nameExchange.len = len+1; + dns->data.mx.nameExchange.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.mx.nameTarget); + } + break; + + case DNS_TYPE_TXT: + dns->data.txt.count = dnsp->data.txt.count; + dns->data.txt.str = talloc_array(mem_ctx, struct DNS_RPC_NAME, dnsp->data.txt.count); + for (i=0; i<dnsp->data.txt.count; i++) { + dns->data.txt.str[i].str = talloc_strdup(mem_ctx, dnsp->data.txt.str[i]); + dns->data.txt.str[i].len = strlen(dnsp->data.txt.str[i]); + } + break; + + case DNS_TYPE_AAAA: + dns->data.ipv6 = talloc_strdup(mem_ctx, dnsp->data.ipv6); + break; + + case DNS_TYPE_SRV: + dns->data.srv.wPriority = dnsp->data.srv.wPriority; + dns->data.srv.wWeight = dnsp->data.srv.wWeight; + dns->data.srv.wPort = dnsp->data.srv.wPort; + len = strlen(dnsp->data.srv.nameTarget); + if (dnsp->data.srv.nameTarget[len-1] == '.') { + dns->data.srv.nameTarget.len = len; + dns->data.srv.nameTarget.str = talloc_strdup(mem_ctx, dnsp->data.srv.nameTarget); + } else { + dns->data.srv.nameTarget.len = len+1; + dns->data.srv.nameTarget.str = talloc_asprintf(mem_ctx, "%s.", dnsp->data.srv.nameTarget); + } + break; + + default: + memcpy(&dns->data, &dnsp->data, sizeof(union DNS_RPC_RECORD_DATA)); + DEBUG(0, ("dnsserver: Found Unhandled DNS record type=%d", dnsp->wType)); + } + +} + +WERROR dns_to_dnsp_convert(TALLOC_CTX *mem_ctx, struct DNS_RPC_RECORD *dns, + struct dnsp_DnssrvRpcRecord **out_dnsp, bool check_name) +{ + WERROR res; + int i, len; + const char *name; + char *talloc_res = NULL; + struct dnsp_DnssrvRpcRecord *dnsp = NULL; + + dnsp = talloc_zero(mem_ctx, struct dnsp_DnssrvRpcRecord); + if (dnsp == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + dnsp->wDataLength = dns->wDataLength; + dnsp->wType = dns->wType; + dnsp->version = 5; + dnsp->rank = dns->dwFlags & 0x000000FF; + dnsp->dwSerial = dns->dwSerial; + dnsp->dwTtlSeconds = dns->dwTtlSeconds; + dnsp->dwTimeStamp = dns->dwTimeStamp; + + switch (dns->wType) { + + case DNS_TYPE_TOMBSTONE: + dnsp->data.EntombedTime = dns->data.EntombedTime; + break; + + case DNS_TYPE_A: + talloc_res = talloc_strdup(mem_ctx, dns->data.ipv4); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.ipv4 = talloc_res; + break; + + case DNS_TYPE_NS: + name = dns->data.name.str; + len = dns->data.name.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.ns = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.ns = talloc_res; + } + + break; + + case DNS_TYPE_CNAME: + name = dns->data.name.str; + len = dns->data.name.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.cname = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.cname = talloc_res; + } + + break; + + case DNS_TYPE_SOA: + dnsp->data.soa.serial = dns->data.soa.dwSerialNo; + dnsp->data.soa.refresh = dns->data.soa.dwRefresh; + dnsp->data.soa.retry = dns->data.soa.dwRetry; + dnsp->data.soa.expire = dns->data.soa.dwExpire; + dnsp->data.soa.minimum = dns->data.soa.dwMinimumTtl; + + name = dns->data.soa.NamePrimaryServer.str; + len = dns->data.soa.NamePrimaryServer.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.soa.mname = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.soa.mname = talloc_res; + } + + name = dns->data.soa.ZoneAdministratorEmail.str; + len = dns->data.soa.ZoneAdministratorEmail.len; + + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.soa.rname = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.soa.rname = talloc_res; + } + + break; + + case DNS_TYPE_PTR: + name = dns->data.ptr.str; + len = dns->data.ptr.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.ptr = talloc_res; + + break; + + case DNS_TYPE_MX: + dnsp->data.mx.wPriority = dns->data.mx.wPreference; + + name = dns->data.mx.nameExchange.str; + len = dns->data.mx.nameExchange.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.mx.nameTarget = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.mx.nameTarget = talloc_res; + } + + break; + + case DNS_TYPE_TXT: + dnsp->data.txt.count = dns->data.txt.count; + dnsp->data.txt.str = talloc_array(mem_ctx, const char *, dns->data.txt.count); + for (i=0; i<dns->data.txt.count; i++) { + talloc_res = talloc_strdup(mem_ctx, dns->data.txt.str[i].str); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.txt.str[i] = talloc_res; + } + break; + + case DNS_TYPE_AAAA: + dnsp->data.ipv6 = talloc_strdup(mem_ctx, dns->data.ipv6); + break; + + case DNS_TYPE_SRV: + dnsp->data.srv.wPriority = dns->data.srv.wPriority; + dnsp->data.srv.wWeight = dns->data.srv.wWeight; + dnsp->data.srv.wPort = dns->data.srv.wPort; + + name = dns->data.srv.nameTarget.str; + len = dns->data.srv.nameTarget.len; + + if (check_name) { + res = dns_name_check(mem_ctx, len, name); + if (!W_ERROR_IS_OK(res)) { + return res; + } + } + + if (len > 0 && name[len-1] == '.') { + talloc_res = talloc_strndup(mem_ctx, name, len-1); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.srv.nameTarget = talloc_res; + } else { + talloc_res = talloc_strdup(mem_ctx, name); + if (talloc_res == NULL) { + goto fail_nomemory; + } + dnsp->data.srv.nameTarget = talloc_res; + } + + break; + + default: + memcpy(&dnsp->data, &dns->data, sizeof(union dnsRecordData)); + DEBUG(0, ("dnsserver: Found Unhandled DNS record type=%d", dns->wType)); + } + + *out_dnsp = dnsp; + return WERR_OK; + +fail_nomemory: + return WERR_NOT_ENOUGH_MEMORY; +} + +/* Intialize tree with given name as the root */ +static struct dns_tree *dns_tree_init(TALLOC_CTX *mem_ctx, const char *name, void *data) +{ + struct dns_tree *tree; + + tree = talloc_zero(mem_ctx, struct dns_tree); + if (tree == NULL) { + return NULL; + } + + tree->name = talloc_strdup(tree, name); + if (tree->name == NULL) { + talloc_free(tree); + return NULL; + } + + tree->data = data; + + return tree; +} + + +/* Add a child one level below */ +static struct dns_tree *dns_tree_add(struct dns_tree *tree, const char *name, void *data) +{ + struct dns_tree *node; + + node = talloc_zero(tree, struct dns_tree); + if (node == NULL) { + return NULL; + } + + node->name = talloc_strdup(tree, name); + if (node->name == NULL) { + talloc_free(node); + return NULL; + } + node->level = tree->level + 1; + node->num_children = 0; + node->children = NULL; + node->data = data; + + if (tree->num_children == 0) { + tree->children = talloc_zero(tree, struct dns_tree *); + } else { + tree->children = talloc_realloc(tree, tree->children, struct dns_tree *, + tree->num_children+1); + } + if (tree->children == NULL) { + talloc_free(node); + return NULL; + } + tree->children[tree->num_children] = node; + tree->num_children++; + + return node; +} + +/* Find a node that matches the name components */ +static struct dns_tree *dns_tree_find(struct dns_tree *tree, int ncount, char **nlist, int *match_count) +{ + struct dns_tree *node, *next; + int i, j, start; + + *match_count = -1; + + if (strcmp(tree->name, "@") == 0) { + start = 0; + } else { + if (strcasecmp(tree->name, nlist[ncount-1]) != 0) { + return NULL; + } + start = 1; + *match_count = 0; + } + + node = tree; + for (i=start; i<ncount; i++) { + if (node->num_children == 0) { + break; + } + next = NULL; + for (j=0; j<node->num_children; j++) { + if (strcasecmp(nlist[(ncount-1)-i], node->children[j]->name) == 0) { + next = node->children[j]; + *match_count = i; + break; + } + } + if (next == NULL) { + break; + } else { + node = next; + } + } + + return node; +} + +/* Build a 2-level tree for resulting dns names */ +struct dns_tree *dns_build_tree(TALLOC_CTX *mem_ctx, const char *name, struct ldb_result *res) +{ + struct dns_tree *root, *base, *tree, *node; + const char *ptr; + int rootcount, ncount; + char **nlist; + int i, level, match_count; + + rootcount = dns_split_name_components(mem_ctx, name, &nlist); + if (rootcount <= 0) { + return NULL; + } + + root = dns_tree_init(mem_ctx, nlist[rootcount-1], NULL); + if (root == NULL) { + talloc_free(nlist); + return NULL; + } + + tree = root; + for (i=rootcount-2; i>=0; i--) { + tree = dns_tree_add(tree, nlist[i], NULL); + if (tree == NULL) { + goto failed; + } + } + + base = tree; + + /* Add all names in the result in a tree */ + for (i=0; i<res->count; i++) { + ptr = ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL); + if (ptr == NULL) { + DBG_ERR("dnsserver: dns record has no name (%s)", + ldb_dn_get_linearized(res->msgs[i]->dn)); + goto failed; + } + + /* + * This might be the sub-domain in the zone being + * requested, or @ for the root of the zone + */ + if (strcasecmp(ptr, name) == 0) { + base->data = res->msgs[i]; + continue; + } + + ncount = dns_split_name_components(root, ptr, &nlist); + if (ncount < 0) { + goto failed; + } + + /* Find matching node */ + tree = dns_tree_find(root, ncount, nlist, &match_count); + if (tree == NULL) { + goto failed; + } + + /* If the node is on leaf, then add record data */ + if (match_count+1 == ncount) { + tree->data = res->msgs[i]; + } + + /* Add missing name components */ + for (level=match_count+1; level<ncount; level++) { + if (tree->level == rootcount+1) { + break; + } + if (level == ncount-1) { + node = dns_tree_add(tree, nlist[(ncount-1)-level], res->msgs[i]); + } else { + node = dns_tree_add(tree, nlist[(ncount-1)-level], NULL); + } + if (node == NULL) { + goto failed; + } + tree = node; + } + + talloc_free(nlist); + } + + /* Mark the base record, so it can be found easily */ + base->level = -1; + + return root; + +failed: + talloc_free(nlist); + talloc_free(root); + return NULL; +} + + +static void _dns_add_name(TALLOC_CTX *mem_ctx, const char *name, char ***add_names, int *add_count) +{ + int i; + char **ptr = *add_names; + int count = *add_count; + + for (i=0; i<count; i++) { + if (strcasecmp(ptr[i], name) == 0) { + return; + } + } + + ptr = talloc_realloc(mem_ctx, ptr, char *, count+1); + if (ptr == NULL) { + return; + } + + ptr[count] = talloc_strdup(mem_ctx, name); + if (ptr[count] == NULL) { + talloc_free(ptr); + return; + } + + *add_names = ptr; + *add_count = count+1; +} + + +static void dns_find_additional_names(TALLOC_CTX *mem_ctx, struct dnsp_DnssrvRpcRecord *rec, char ***add_names, int *add_count) +{ + if (add_names == NULL) { + return; + } + + switch (rec->wType) { + + case DNS_TYPE_NS: + _dns_add_name(mem_ctx, rec->data.ns, add_names, add_count); + break; + + case DNS_TYPE_CNAME: + _dns_add_name(mem_ctx, rec->data.cname, add_names, add_count); + break; + + case DNS_TYPE_SOA: + _dns_add_name(mem_ctx, rec->data.soa.mname, add_names, add_count); + break; + + case DNS_TYPE_MX: + _dns_add_name(mem_ctx, rec->data.mx.nameTarget, add_names, add_count); + break; + + case DNS_TYPE_SRV: + _dns_add_name(mem_ctx, rec->data.srv.nameTarget, add_names, add_count); + break; + + default: + break; + } +} + + +WERROR dns_fill_records_array(TALLOC_CTX *mem_ctx, + struct dnsserver_zone *z, + enum dns_record_type record_type, + unsigned int select_flag, + const char *branch_name, + struct ldb_message *msg, + int num_children, + struct DNS_RPC_RECORDS_ARRAY *recs, + char ***add_names, + int *add_count) +{ + struct ldb_message_element *el; + const char *ptr; + int i, j; + bool found; + + if (recs->count == 0) { + recs->rec = talloc_zero(recs, struct DNS_RPC_RECORDS); + } else { + recs->rec = talloc_realloc(recs, recs->rec, struct DNS_RPC_RECORDS, recs->count+1); + } + if (recs->rec == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + i = recs->count; + recs->rec[i].wLength = 0; + recs->rec[i].wRecordCount = 0; + recs->rec[i].dwChildCount = num_children; + recs->rec[i].dwFlags = 0; + + /* The base records returned with empty name */ + /* Children records returned with names */ + if (branch_name == NULL) { + recs->rec[i].dnsNodeName.str = talloc_strdup(recs, ""); + recs->rec[i].dnsNodeName.len = 0; + } else { + recs->rec[i].dnsNodeName.str = talloc_strdup(recs, branch_name); + recs->rec[i].dnsNodeName.len = strlen(branch_name); + } + recs->rec[i].records = talloc_zero_array(recs, struct DNS_RPC_RECORD, 0); + recs->count++; + + /* Allow empty records */ + if (msg == NULL) { + return WERR_OK; + } + + /* Do not return RR records, if the node has children */ + if (branch_name != NULL && num_children > 0) { + return WERR_OK; + } + + ptr = ldb_msg_find_attr_as_string(msg, "name", NULL); + if (ptr == NULL) { + DBG_ERR("dnsserver: dns record has no name (%s)", + ldb_dn_get_linearized(msg->dn)); + return WERR_INTERNAL_DB_ERROR; + } + + el = ldb_msg_find_element(msg, "dnsRecord"); + if (el == NULL || el->values == 0) { + return WERR_OK; + } + + /* Add RR records */ + for (j=0; j<el->num_values; j++) { + struct dnsp_DnssrvRpcRecord dnsp_rec; + struct DNS_RPC_RECORD *dns_rec; + enum ndr_err_code ndr_err; + + ndr_err = ndr_pull_struct_blob(&el->values[j], mem_ctx, &dnsp_rec, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DEBUG(0, ("dnsserver: Unable to parse dns record (%s)", ldb_dn_get_linearized(msg->dn))); + return WERR_INTERNAL_DB_ERROR; + } + + /* Match the records based on search criteria */ + if (record_type == DNS_TYPE_ALL || dnsp_rec.wType == record_type) { + found = false; + + if (select_flag & DNS_RPC_VIEW_AUTHORITY_DATA) { + if (dnsp_rec.rank == DNS_RANK_ZONE) { + found = true; + } else if (dnsp_rec.rank == DNS_RANK_NS_GLUE) { + /* + * If branch_name is NULL, we're + * explicitly asked to also return + * DNS_RANK_NS_GLUE records + */ + if (branch_name == NULL) { + found = true; + } + } + } + if (select_flag & DNS_RPC_VIEW_CACHE_DATA) { + if (dnsp_rec.rank == DNS_RANK_ZONE) { + found = true; + } + } + if (select_flag & DNS_RPC_VIEW_GLUE_DATA) { + if (dnsp_rec.rank == DNS_RANK_GLUE) { + found = true; + } + } + if (select_flag & DNS_RPC_VIEW_ROOT_HINT_DATA) { + if (dnsp_rec.rank == DNS_RANK_ROOT_HINT) { + found = true; + } + } + + if (found) { + recs->rec[i].records = talloc_realloc(recs, + recs->rec[i].records, + struct DNS_RPC_RECORD, + recs->rec[i].wRecordCount+1); + if (recs->rec[i].records == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + dns_rec = &recs->rec[i].records[recs->rec[i].wRecordCount]; + dnsp_to_dns_copy(recs, &dnsp_rec, dns_rec); + + /* Fix record flags */ + if (strcmp(ptr, "@") == 0) { + dns_rec->dwFlags |= DNS_RPC_FLAG_ZONE_ROOT; + + if (dnsp_rec.rank == DNS_RANK_ZONE) { + dns_rec->dwFlags |= DNS_RPC_FLAG_AUTH_ZONE_ROOT; + } + } + + if (dns_rec->dwFlags == DNS_RANK_NS_GLUE) { + dns_rec->dwFlags |= DNS_RPC_FLAG_ZONE_ROOT; + } + + recs->rec[i].wRecordCount++; + + dns_find_additional_names(mem_ctx, &dnsp_rec, add_names, add_count); + } + } + } + + return WERR_OK; +} + + +int dns_name_compare(struct ldb_message * const *m1, struct ldb_message * const *m2, + const char *search_name) +{ + const char *name1, *name2; + const char *ptr1, *ptr2; + + name1 = ldb_msg_find_attr_as_string(*m1, "name", NULL); + name2 = ldb_msg_find_attr_as_string(*m2, "name", NULL); + if (name1 == NULL || name2 == NULL) { + return 0; + } + + /* Compare the last components of names. + * If search_name is not NULL, compare the second last components of names */ + ptr1 = strrchr(name1, '.'); + if (ptr1 == NULL) { + ptr1 = name1; + } else { + if (search_name && strcasecmp(ptr1+1, search_name) == 0) { + ptr1--; + while (ptr1 != name1) { + ptr1--; + if (*ptr1 == '.') { + break; + } + } + } + if (*ptr1 == '.') { + ptr1 = &ptr1[1]; + } + } + + ptr2 = strrchr(name2, '.'); + if (ptr2 == NULL) { + ptr2 = name2; + } else { + if (search_name && strcasecmp(ptr2+1, search_name) == 0) { + ptr2--; + while (ptr2 != name2) { + ptr2--; + if (*ptr2 == '.') { + break; + } + } + } + if (*ptr2 == '.') { + ptr2 = &ptr2[1]; + } + } + + return strcasecmp(ptr1, ptr2); +} diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c new file mode 100644 index 0000000..bde54a0 --- /dev/null +++ b/source4/rpc_server/dnsserver/dnsdb.c @@ -0,0 +1,1272 @@ +/* + Unix SMB/CIFS implementation. + + DNS Server + + Copyright (C) Amitay Isaacs 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "dnsserver.h" +#include "lib/util/dlinklist.h" +#include "librpc/gen_ndr/ndr_dnsp.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "librpc/gen_ndr/ndr_misc.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "dsdb/common/util.h" + +#undef strcasecmp + +/* There are only 2 fixed partitions for DNS */ +struct dnsserver_partition *dnsserver_db_enumerate_partitions(TALLOC_CTX *mem_ctx, + struct dnsserver_serverinfo *serverinfo, + struct ldb_context *samdb) +{ + struct dnsserver_partition *partitions, *p; + + partitions = NULL; + + /* Domain partition */ + p = talloc_zero(mem_ctx, struct dnsserver_partition); + if (p == NULL) { + goto failed; + } + + p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszDomainDirectoryPartition); + if (p->partition_dn == NULL) { + goto failed; + } + + p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn); + p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_DOMAIN_DEFAULT | DNS_DP_ENLISTED; + p->is_forest = false; + + DLIST_ADD_END(partitions, p); + + /* Forest Partition */ + p = talloc_zero(mem_ctx, struct dnsserver_partition); + if (p == NULL) { + goto failed; + } + + p->partition_dn = ldb_dn_new(p, samdb, serverinfo->pszForestDirectoryPartition); + if (p->partition_dn == NULL) { + goto failed; + } + + p->pszDpFqdn = samdb_dn_to_dns_domain(p, p->partition_dn); + p->dwDpFlags = DNS_DP_AUTOCREATED | DNS_DP_FOREST_DEFAULT | DNS_DP_ENLISTED; + p->is_forest = true; + + DLIST_ADD_END(partitions, p); + + return partitions; + +failed: + return NULL; + +} + + +/* Search for all dnsZone records */ +struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_partition *p) +{ + TALLOC_CTX *tmp_ctx; + const char * const attrs[] = {"name", "dNSProperty", NULL}; + struct ldb_dn *dn; + struct ldb_result *res; + struct dnsserver_zone *zones, *z; + int i, j, ret; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NULL; + } + + dn = ldb_dn_copy(tmp_ctx, p->partition_dn); + if (dn == NULL) { + goto failed; + } + if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) { + goto failed; + } + + ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE, + attrs, "(objectClass=dnsZone)"); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("dnsserver: Failed to find DNS Zones in %s\n", + ldb_dn_get_linearized(dn))); + goto failed; + } + + zones = NULL; + for(i=0; i<res->count; i++) { + char *name; + struct ldb_message_element *element = NULL; + struct dnsp_DnsProperty *props = NULL; + enum ndr_err_code err; + z = talloc_zero(mem_ctx, struct dnsserver_zone); + if (z == NULL) { + goto failed; + } + + z->partition = p; + name = talloc_strdup(z, + ldb_msg_find_attr_as_string(res->msgs[i], + "name", NULL)); + if (strcmp(name, "..TrustAnchors") == 0) { + talloc_free(z); + continue; + } + if (strcmp(name, "RootDNSServers") == 0) { + talloc_free(name); + z->name = talloc_strdup(z, "."); + } else { + z->name = name; + } + z->zone_dn = talloc_steal(z, res->msgs[i]->dn); + + DLIST_ADD_END(zones, z); + DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name)); + + element = ldb_msg_find_element(res->msgs[i], "dNSProperty"); + if(element != NULL){ + props = talloc_zero_array(tmp_ctx, + struct dnsp_DnsProperty, + element->num_values); + for (j = 0; j < element->num_values; j++ ) { + err = ndr_pull_struct_blob( + &(element->values[j]), + mem_ctx, + &props[j], + (ndr_pull_flags_fn_t) + ndr_pull_dnsp_DnsProperty); + if (!NDR_ERR_CODE_IS_SUCCESS(err)){ + /* + * Per Microsoft we must + * ignore invalid data here + * and continue as a Windows + * server can put in a + * structure with an invalid + * length. + * + * We can safely fill in an + * extra empty property here + * because + * dns_zoneinfo_load_zone_property() + * just ignores + * DSPROPERTY_ZONE_EMPTY + */ + ZERO_STRUCT(props[j]); + props[j].id = DSPROPERTY_ZONE_EMPTY; + continue; + } + } + z->tmp_props = props; + z->num_props = element->num_values; + } + } + return zones; + +failed: + talloc_free(tmp_ctx); + return NULL; +} + + +/* Find DNS partition information */ +struct dnsserver_partition_info *dnsserver_db_partition_info(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_partition *p) +{ + const char * const attrs[] = { "instanceType", "msDs-masteredBy", NULL }; + const char * const attrs_none[] = { NULL }; + struct ldb_result *res; + struct ldb_message_element *el; + struct ldb_dn *dn; + struct dnsserver_partition_info *partinfo; + int i, ret, instance_type; + TALLOC_CTX *tmp_ctx; + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return NULL; + } + + partinfo = talloc_zero(mem_ctx, struct dnsserver_partition_info); + if (partinfo == NULL) { + talloc_free(tmp_ctx); + return NULL; + } + + /* Search for the active replica and state */ + ret = ldb_search(samdb, tmp_ctx, &res, p->partition_dn, LDB_SCOPE_BASE, + attrs, NULL); + if (ret != LDB_SUCCESS || res->count != 1) { + goto failed; + } + + /* Set the state of the partition */ + instance_type = ldb_msg_find_attr_as_int(res->msgs[0], "instanceType", -1); + if (instance_type == -1) { + partinfo->dwState = DNS_DP_STATE_UNKNOWN; + } else if (instance_type & INSTANCE_TYPE_NC_COMING) { + partinfo->dwState = DNS_DP_STATE_REPL_INCOMING; + } else if (instance_type & INSTANCE_TYPE_NC_GOING) { + partinfo->dwState = DNS_DP_STATE_REPL_OUTGOING; + } else { + partinfo->dwState = DNS_DP_OKAY; + } + + el = ldb_msg_find_element(res->msgs[0], "msDs-masteredBy"); + if (el == NULL) { + partinfo->dwReplicaCount = 0; + partinfo->ReplicaArray = NULL; + } else { + partinfo->dwReplicaCount = el->num_values; + partinfo->ReplicaArray = talloc_zero_array(partinfo, + struct DNS_RPC_DP_REPLICA *, + el->num_values); + if (partinfo->ReplicaArray == NULL) { + goto failed; + } + for (i=0; i<el->num_values; i++) { + partinfo->ReplicaArray[i] = talloc_zero(partinfo, + struct DNS_RPC_DP_REPLICA); + if (partinfo->ReplicaArray[i] == NULL) { + goto failed; + } + partinfo->ReplicaArray[i]->pszReplicaDn = talloc_strdup( + partinfo, + (const char *)el->values[i].data); + if (partinfo->ReplicaArray[i]->pszReplicaDn == NULL) { + goto failed; + } + } + } + talloc_free(res); + + /* Search for cross-reference object */ + dn = ldb_dn_copy(tmp_ctx, ldb_get_config_basedn(samdb)); + if (dn == NULL) { + goto failed; + } + + ret = ldb_search(samdb, tmp_ctx, &res, dn, LDB_SCOPE_DEFAULT, attrs_none, + "(nCName=%s)", ldb_dn_get_linearized(p->partition_dn)); + if (ret != LDB_SUCCESS || res->count != 1) { + goto failed; + } + partinfo->pszCrDn = talloc_strdup(partinfo, ldb_dn_get_linearized(res->msgs[0]->dn)); + if (partinfo->pszCrDn == NULL) { + goto failed; + } + talloc_free(res); + + talloc_free(tmp_ctx); + return partinfo; + +failed: + talloc_free(tmp_ctx); + talloc_free(partinfo); + return NULL; +} + + +/* Increment serial number and update timestamp */ +static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + WERROR *werr) +{ + const char * const attrs[] = { "dnsRecord", NULL }; + struct ldb_result *res; + struct dnsp_DnssrvRpcRecord rec; + struct ldb_message_element *el; + enum ndr_err_code ndr_err; + int ret, i, serial = -1; + + *werr = WERR_INTERNAL_DB_ERROR; + + ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=@))"); + if (ret != LDB_SUCCESS || res->count == 0) { + return -1; + } + + el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); + if (el == NULL) { + return -1; + } + + for (i=0; i<el->num_values; i++) { + ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + continue; + } + + if (rec.wType == DNS_TYPE_SOA) { + serial = rec.data.soa.serial + 1; + rec.dwSerial = serial; + rec.dwTimeStamp = 0; + rec.data.soa.serial = serial; + + ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, &rec, + (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + *werr = WERR_NOT_ENOUGH_MEMORY; + return -1; + } + break; + } + } + + if (serial != -1) { + el->flags = LDB_FLAG_MOD_REPLACE; + ret = ldb_modify(samdb, res->msgs[0]); + if (ret != LDB_SUCCESS) { + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + *werr = WERR_ACCESS_DENIED; + } + return -1; + } + } + + *werr = WERR_OK; + + return serial; +} + + +/* Add DNS record to the database */ +static WERROR dnsserver_db_do_add_rec(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct ldb_dn *dn, + int num_rec, + struct dnsp_DnssrvRpcRecord *rec) +{ + struct ldb_message *msg; + struct ldb_val v; + int ret; + enum ndr_err_code ndr_err; + int i; + + msg = ldb_msg_new(mem_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = dn; + ret = ldb_msg_add_string(msg, "objectClass", "dnsNode"); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (num_rec > 0 && rec) { + for (i=0; i<num_rec; i++) { + ndr_err = ndr_push_struct_blob(&v, mem_ctx, &rec[i], + (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + ret = ldb_msg_add_value(msg, "dnsRecord", &v, NULL); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + } + + ret = ldb_add(samdb, msg); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} + + +/* Add dnsNode record to the database with DNS record */ +WERROR dnsserver_db_add_empty_node(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *name) +{ + const char * const attrs[] = { "name", NULL }; + struct ldb_result *res; + struct ldb_dn *dn; + char *encoded_name = ldb_binary_encode_string(mem_ctx, name); + struct ldb_val name_val = data_blob_string_const(name); + int ret; + + ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, attrs, + "(&(objectClass=dnsNode)(name=%s))", + encoded_name); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + if (res->count > 0) { + talloc_free(res); + return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; + } + + dn = ldb_dn_copy(mem_ctx, z->zone_dn); + W_ERROR_HAVE_NO_MEMORY(dn); + + if (!ldb_dn_add_child_val(dn, "DC", name_val)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 0, NULL); +} + +static void set_record_rank(struct dnsserver_zone *z, + const char *name, + struct dnsp_DnssrvRpcRecord *rec) +{ + if (z->zoneinfo->dwZoneType == DNS_ZONE_TYPE_PRIMARY) { + if (strcmp(name, "@") != 0 && rec->wType == DNS_TYPE_NS) { + rec->rank = DNS_RANK_NS_GLUE; + } else { + rec->rank = DNS_RANK_ZONE; + } + } else if (strcmp(z->name, ".") == 0) { + rec->rank = DNS_RANK_ROOT_HINT; + } +} + + +/* Add a DNS record */ +WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *name, + struct DNS_RPC_RECORD *add_record) +{ + const char * const attrs[] = { "dnsRecord", "dNSTombstoned", NULL }; + struct ldb_result *res; + struct dnsp_DnssrvRpcRecord *rec = NULL; + struct ldb_message_element *el; + struct ldb_dn *dn; + enum ndr_err_code ndr_err; + int ret, i; + int serial; + WERROR werr; + bool was_tombstoned = false; + char *encoded_name = ldb_binary_encode_string(mem_ctx, name); + + werr = dns_to_dnsp_convert(mem_ctx, add_record, &rec, true); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + /* Set the correct rank for the record. */ + set_record_rank(z, name, rec); + + serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); + if (serial < 0) { + return werr; + } + + rec->dwSerial = serial; + rec->dwTimeStamp = 0; + + ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=%s))", + encoded_name); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + if (res->count == 0) { + dn = dnsserver_name_to_dn(mem_ctx, z, name); + W_ERROR_HAVE_NO_MEMORY(dn); + + return dnsserver_db_do_add_rec(mem_ctx, samdb, dn, 1, rec); + } + + el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); + if (el == NULL) { + ret = ldb_msg_add_empty(res->msgs[0], "dnsRecord", 0, &el); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + + was_tombstoned = ldb_msg_find_attr_as_bool(res->msgs[0], + "dNSTombstoned", false); + if (was_tombstoned) { + el->num_values = 0; + } + + for (i=0; i<el->num_values; i++) { + struct dnsp_DnssrvRpcRecord rec2; + + ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + if (dns_record_match(rec, &rec2)) { + break; + } + } + if (i < el->num_values) { + return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; + } + if (i == el->num_values) { + /* adding a new value */ + el->values = talloc_realloc(el, el->values, struct ldb_val, el->num_values+1); + W_ERROR_HAVE_NO_MEMORY(el->values); + el->num_values++; + } + + ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, rec, + (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + el->flags = LDB_FLAG_MOD_REPLACE; + + el = ldb_msg_find_element(res->msgs[0], "dNSTombstoned"); + if (el != NULL) { + el->flags = LDB_FLAG_MOD_DELETE; + } + + ret = ldb_modify(samdb, res->msgs[0]); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} + + +/* Update a DNS record */ +WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *name, + struct DNS_RPC_RECORD *add_record, + struct DNS_RPC_RECORD *del_record) +{ + const char * const attrs[] = { "dnsRecord", NULL }; + struct ldb_result *res; + struct dnsp_DnssrvRpcRecord rec2; + struct dnsp_DnssrvRpcRecord *arec = NULL, *drec = NULL; + struct ldb_message_element *el; + enum ndr_err_code ndr_err; + int ret, i; + int serial; + WERROR werr; + bool updating_ttl = false; + char *encoded_name = ldb_binary_encode_string(mem_ctx, name); + + werr = dns_to_dnsp_convert(mem_ctx, add_record, &arec, true); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + werr = dns_to_dnsp_convert(mem_ctx, del_record, &drec, true); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))", + encoded_name); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + if (res->count == 0) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + + el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); + if (el == NULL || el->num_values == 0) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + + for (i=0; i<el->num_values; i++) { + ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + if (dns_record_match(arec, &rec2)) { + break; + } + } + if (i < el->num_values) { + /* + * The record already exists, which is an error UNLESS we are + * doing an in-place update. + * + * Therefore we need to see if drec also matches, in which + * case it's OK, though we can only update dwTtlSeconds and + * reset the timestamp to zero. + */ + updating_ttl = dns_record_match(drec, &rec2); + if (! updating_ttl) { + return WERR_DNS_ERROR_RECORD_ALREADY_EXISTS; + } + /* In this case the next loop is redundant */ + } + + for (i=0; i<el->num_values; i++) { + ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + if (dns_record_match(drec, &rec2)) { + /* + * we are replacing this one with arec, which is done + * by pushing arec into el->values[i] below, after the + * various manipulations. + */ + break; + } + } + if (i == el->num_values) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + + /* + * If we're updating a SOA record, use the specified serial. + * + * Otherwise, if we are updating ttl in place (i.e., not changing + * .wType and .data on a record), we should increment the existing + * serial, and save to the SOA. + * + * Outside of those two cases, we look for the zone's SOA record and + * use its serial. + */ + if (arec->wType != DNS_TYPE_SOA) { + if (updating_ttl) { + /* + * In this case, we keep some of the old values. + */ + arec->dwSerial = rec2.dwSerial; + arec->dwReserved = rec2.dwReserved; + /* + * TODO: if the old TTL and the new TTL are + * different, the serial number is incremented. + */ + } else { + arec->dwReserved = 0; + serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); + if (serial < 0) { + return werr; + } + arec->dwSerial = serial; + } + } + + /* Set the correct rank for the record. */ + set_record_rank(z, name, arec); + /* + * Successful RPC updates *always* zero timestamp and flags and set + * version. + */ + arec->dwTimeStamp = 0; + arec->version = 5; + arec->flags = 0; + + ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, arec, + (ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + el->flags = LDB_FLAG_MOD_REPLACE; + ret = ldb_modify(samdb, res->msgs[0]); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} + + +/* Delete a DNS record */ +WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *name, + struct DNS_RPC_RECORD *del_record) +{ + const char * const attrs[] = { "dnsRecord", NULL }; + struct ldb_result *res; + struct dnsp_DnssrvRpcRecord *rec = NULL; + struct ldb_message_element *el; + enum ndr_err_code ndr_err; + int ret, i; + int serial; + WERROR werr; + + serial = dnsserver_update_soa(mem_ctx, samdb, z, &werr); + if (serial < 0) { + return werr; + } + + werr = dns_to_dnsp_convert(mem_ctx, del_record, &rec, false); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs, + "(&(objectClass=dnsNode)(name=%s))", + ldb_binary_encode_string(mem_ctx, name)); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + if (res->count == 0) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + if (res->count > 1) { + return WERR_DNS_ERROR_RCODE_SERVER_FAILURE; + } + + el = ldb_msg_find_element(res->msgs[0], "dnsRecord"); + if (el == NULL || el->num_values == 0) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + + for (i=0; i<el->num_values; i++) { + struct dnsp_DnssrvRpcRecord rec2; + + ndr_err = ndr_pull_struct_blob(&el->values[i], mem_ctx, &rec2, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + if (dns_record_match(rec, &rec2)) { + break; + } + } + if (i == el->num_values) { + return WERR_DNS_ERROR_RECORD_DOES_NOT_EXIST; + } + if (i < el->num_values-1) { + memmove(&el->values[i], &el->values[i+1], sizeof(el->values[0])*((el->num_values-1)-i)); + } + el->num_values--; + + if (el->num_values == 0) { + ret = ldb_delete(samdb, res->msgs[0]->dn); + } else { + el->flags = LDB_FLAG_MOD_REPLACE; + ret = ldb_modify(samdb, res->msgs[0]); + } + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} + + +static bool dnsserver_db_msg_add_dnsproperty(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct dnsp_DnsProperty *prop) +{ + DATA_BLOB *prop_blob; + enum ndr_err_code ndr_err; + int ret; + + prop_blob = talloc_zero(mem_ctx, DATA_BLOB); + if (prop_blob == NULL) return false; + + ndr_err = ndr_push_struct_blob(prop_blob, mem_ctx, prop, + (ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return false; + } + ret = ldb_msg_add_steal_value(msg, "dNSProperty", prop_blob); + if (ret != LDB_SUCCESS) { + return false; + } + return true; +} + +WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb, + struct dnsserver_zone *z, + struct DNS_RPC_NAME_AND_PARAM *n_p) +{ + struct ldb_message_element *element = NULL; + struct dnsp_DnsProperty *prop = NULL; + enum ndr_err_code err; + TALLOC_CTX *tmp_ctx = NULL; + const char * const attrs[] = {"dNSProperty", NULL}; + struct ldb_result *res = NULL; + int i, ret, prop_id; + + if (strcasecmp(n_p->pszNodeName, "Aging") == 0) { + z->zoneinfo->fAging = n_p->dwParam; + prop_id = DSPROPERTY_ZONE_AGING_STATE; + } else if (strcasecmp(n_p->pszNodeName, "RefreshInterval") == 0) { + z->zoneinfo->dwRefreshInterval = n_p->dwParam; + prop_id = DSPROPERTY_ZONE_REFRESH_INTERVAL; + } else if (strcasecmp(n_p->pszNodeName, "NoRefreshInterval") == 0) { + z->zoneinfo->dwNoRefreshInterval = n_p->dwParam; + prop_id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL; + } else if (strcasecmp(n_p->pszNodeName, "AllowUpdate") == 0) { + z->zoneinfo->fAllowUpdate = n_p->dwParam; + prop_id = DSPROPERTY_ZONE_ALLOW_UPDATE; + } else { + return WERR_UNKNOWN_PROPERTY; + } + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = ldb_search(samdb, tmp_ctx, &res, z->zone_dn, LDB_SCOPE_BASE, + attrs, "(objectClass=dnsZone)"); + if (ret != LDB_SUCCESS) { + DBG_ERR("dnsserver: no zone: %s\n", + ldb_dn_get_linearized(z->zone_dn)); + TALLOC_FREE(tmp_ctx); + return WERR_INTERNAL_DB_ERROR; + } + + if (res->count != 1) { + DBG_ERR("dnsserver: duplicate zone: %s\n", + ldb_dn_get_linearized(z->zone_dn)); + TALLOC_FREE(tmp_ctx); + return WERR_GEN_FAILURE; + } + + element = ldb_msg_find_element(res->msgs[0], "dNSProperty"); + if (element == NULL) { + DBG_ERR("dnsserver: zone %s has no properties.\n", + ldb_dn_get_linearized(z->zone_dn)); + TALLOC_FREE(tmp_ctx); + return WERR_INTERNAL_DB_ERROR; + } + + for (i = 0; i < element->num_values; i++) { + prop = talloc_zero(element, struct dnsp_DnsProperty); + if (prop == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + err = ndr_pull_struct_blob( + &(element->values[i]), + tmp_ctx, + prop, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty); + if (!NDR_ERR_CODE_IS_SUCCESS(err)){ + /* + * If we can't pull it then try again parsing + * it again. A Windows server in the domain + * will permit the addition of an invalidly + * formed property with a 0 length and cause a + * failure here + */ + struct dnsp_DnsProperty_short + *short_property + = talloc_zero(element, + struct dnsp_DnsProperty_short); + if (short_property == NULL) { + TALLOC_FREE(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + err = ndr_pull_struct_blob_all( + &(element->values[i]), + tmp_ctx, + short_property, + (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty_short); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + /* + * Unknown invalid data should be + * ignored and logged to match Windows + * behaviour + */ + DBG_NOTICE("dnsserver: couldn't PULL " + "dnsProperty value#%d in " + "zone %s while trying to " + "reset id %d\n", + i, + ldb_dn_get_linearized(z->zone_dn), + prop_id); + continue; + } + + /* + * Initialise the parts of the property not + * overwritten by value() in the IDL for + * re-push + */ + *prop = (struct dnsp_DnsProperty){ + .namelength = short_property->namelength, + .id = short_property->id, + .name = short_property->name + /* .data will be filled in below */ + }; + } + + if (prop->id == prop_id) { + switch (prop_id) { + case DSPROPERTY_ZONE_AGING_STATE: + prop->data.aging_enabled = n_p->dwParam; + break; + case DSPROPERTY_ZONE_NOREFRESH_INTERVAL: + prop->data.norefresh_hours = n_p->dwParam; + break; + case DSPROPERTY_ZONE_REFRESH_INTERVAL: + prop->data.refresh_hours = n_p->dwParam; + break; + case DSPROPERTY_ZONE_ALLOW_UPDATE: + prop->data.allow_update_flag = n_p->dwParam; + break; + } + + err = ndr_push_struct_blob( + &(element->values[i]), + tmp_ctx, + prop, + (ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty); + if (!NDR_ERR_CODE_IS_SUCCESS(err)){ + DBG_ERR("dnsserver: couldn't PUSH dns prop id " + "%d in zone %s\n", + prop->id, + ldb_dn_get_linearized(z->zone_dn)); + TALLOC_FREE(tmp_ctx); + return WERR_INTERNAL_DB_ERROR; + } + } + } + + element->flags = LDB_FLAG_MOD_REPLACE; + ret = ldb_modify(samdb, res->msgs[0]); + if (ret != LDB_SUCCESS) { + TALLOC_FREE(tmp_ctx); + DBG_ERR("dnsserver: Failed to modify zone %s prop %s: %s\n", + z->name, + n_p->pszNodeName, + ldb_errstring(samdb)); + return WERR_INTERNAL_DB_ERROR; + } + TALLOC_FREE(tmp_ctx); + + return WERR_OK; +} + +/* Create dnsZone record to database and set security descriptor */ +static WERROR dnsserver_db_do_create_zone(TALLOC_CTX *tmp_ctx, + struct ldb_context *samdb, + struct ldb_dn *zone_dn, + struct dnsserver_zone *z) +{ + const char * const attrs[] = { "objectSID", NULL }; + struct ldb_message *msg; + struct ldb_result *res; + struct ldb_message_element *el; + const char sddl_template[] = "D:AI(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)(A;;CC;;;AU)(A;;RPLCLORC;;;WD)(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)(A;CIID;LC;;;RU)(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)S:AI"; + char *sddl; + struct dom_sid dnsadmins_sid; + const struct dom_sid *domain_sid; + struct security_descriptor *secdesc; + struct dnsp_DnsProperty *prop; + DATA_BLOB *sd_encoded; + enum ndr_err_code ndr_err; + int ret; + + /* Get DnsAdmins SID */ + ret = ldb_search(samdb, tmp_ctx, &res, ldb_get_default_basedn(samdb), + LDB_SCOPE_DEFAULT, attrs, "(sAMAccountName=DnsAdmins)"); + if (ret != LDB_SUCCESS || res->count != 1) { + return WERR_INTERNAL_DB_ERROR; + } + + el = ldb_msg_find_element(res->msgs[0], "objectSID"); + if (el == NULL || el->num_values != 1) { + return WERR_INTERNAL_DB_ERROR; + } + + ndr_err = ndr_pull_struct_blob(&el->values[0], tmp_ctx, &dnsadmins_sid, + (ndr_pull_flags_fn_t)ndr_pull_dom_sid); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INTERNAL_DB_ERROR; + } + + /* create security descriptor with DnsAdmins GUID in sddl template */ + sddl = talloc_asprintf(tmp_ctx, sddl_template, + dom_sid_string(tmp_ctx, &dnsadmins_sid)); + if (sddl == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + talloc_free(res); + + domain_sid = samdb_domain_sid(samdb); + if (domain_sid == NULL) { + return WERR_INTERNAL_DB_ERROR; + } + + secdesc = sddl_decode(tmp_ctx, sddl, domain_sid); + if (secdesc == NULL) { + return WERR_GEN_FAILURE; + } + + msg = ldb_msg_new(tmp_ctx); + W_ERROR_HAVE_NO_MEMORY(msg); + + msg->dn = zone_dn; + ret = ldb_msg_add_string(msg, "objectClass", "dnsZone"); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + + sd_encoded = talloc_zero(tmp_ctx, DATA_BLOB); + W_ERROR_HAVE_NO_MEMORY(sd_encoded); + + ndr_err = ndr_push_struct_blob(sd_encoded, tmp_ctx, secdesc, + (ndr_push_flags_fn_t)ndr_push_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_GEN_FAILURE; + } + + ret = ldb_msg_add_steal_value(msg, "nTSecurityDescriptor", sd_encoded); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* dns zone Properties */ + prop = talloc_zero(tmp_ctx, struct dnsp_DnsProperty); + W_ERROR_HAVE_NO_MEMORY(prop); + + prop->version = 1; + + /* zone type */ + prop->id = DSPROPERTY_ZONE_TYPE; + prop->data.zone_type = z->zoneinfo->dwZoneType; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* allow update */ + prop->id = DSPROPERTY_ZONE_ALLOW_UPDATE; + prop->data.allow_update_flag = z->zoneinfo->fAllowUpdate; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* secure time */ + prop->id = DSPROPERTY_ZONE_SECURE_TIME; + prop->data.zone_secure_time = 0; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* norefresh interval */ + prop->id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL; + prop->data.norefresh_hours = 168; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* refresh interval */ + prop->id = DSPROPERTY_ZONE_REFRESH_INTERVAL; + prop->data.refresh_hours = 168; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* aging state */ + prop->id = DSPROPERTY_ZONE_AGING_STATE; + prop->data.aging_enabled = z->zoneinfo->fAging; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* aging enabled time */ + prop->id = DSPROPERTY_ZONE_AGING_ENABLED_TIME; + prop->data.next_scavenging_cycle_hours = 0; + if (!dnsserver_db_msg_add_dnsproperty(tmp_ctx, msg, prop)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + talloc_free(prop); + + ret = ldb_add(samdb, msg); + if (ret != LDB_SUCCESS) { + DEBUG(0, ("dnsserver: Failed to create zone (%s): %s\n", + z->name, ldb_errstring(samdb))); + + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + return WERR_ACCESS_DENIED; + } + + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} + + +/* Create new dnsZone record and @ record (SOA + NS) */ +WERROR dnsserver_db_create_zone(struct ldb_context *samdb, + struct dnsserver_partition *partitions, + struct dnsserver_zone *zone, + struct loadparm_context *lp_ctx) +{ + struct dnsserver_partition *p; + bool in_forest = false; + WERROR status; + struct ldb_dn *dn; + TALLOC_CTX *tmp_ctx; + struct dnsp_DnssrvRpcRecord *dns_rec; + struct dnsp_soa soa; + char *tmpstr, *server_fqdn, *soa_email; + struct ldb_val name_val = data_blob_string_const(zone->name); + + /* We only support primary zones for now */ + if (zone->zoneinfo->dwZoneType != DNS_ZONE_TYPE_PRIMARY) { + return WERR_CALL_NOT_IMPLEMENTED; + } + + /* Get the correct partition */ + if (zone->partition->dwDpFlags & DNS_DP_FOREST_DEFAULT) { + in_forest = true; + } + for (p = partitions; p; p = p->next) { + if (in_forest == p->is_forest) { + break; + } + } + if (p == NULL) { + return WERR_DNS_ERROR_DP_DOES_NOT_EXIST; + } + + tmp_ctx = talloc_new(NULL); + W_ERROR_HAVE_NO_MEMORY(tmp_ctx); + + dn = ldb_dn_copy(tmp_ctx, p->partition_dn); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(dn, tmp_ctx); + + if (!ldb_dn_add_child_fmt(dn, "CN=MicrosoftDNS")) { + talloc_free(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + if (!ldb_dn_add_child_val(dn, "DC", name_val)) { + talloc_free(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Add dnsZone record */ + status = dnsserver_db_do_create_zone(tmp_ctx, samdb, dn, zone); + if (!W_ERROR_IS_OK(status)) { + talloc_free(tmp_ctx); + return status; + } + + if (!ldb_dn_add_child_fmt(dn, "DC=@")) { + talloc_free(tmp_ctx); + return WERR_NOT_ENOUGH_MEMORY; + } + + dns_rec = talloc_zero_array(tmp_ctx, struct dnsp_DnssrvRpcRecord, 2); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(dns_rec, tmp_ctx); + + tmpstr = talloc_asprintf(tmp_ctx, "%s.%s", + lpcfg_netbios_name(lp_ctx), + lpcfg_realm(lp_ctx)); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(tmpstr, tmp_ctx); + server_fqdn = strlower_talloc(tmp_ctx, tmpstr); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(server_fqdn, tmp_ctx); + talloc_free(tmpstr); + + tmpstr = talloc_asprintf(tmp_ctx, "hostmaster.%s", + lpcfg_realm(lp_ctx)); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(tmpstr, tmp_ctx); + soa_email = strlower_talloc(tmp_ctx, tmpstr); + W_ERROR_HAVE_NO_MEMORY_AND_FREE(soa_email, tmp_ctx); + talloc_free(tmpstr); + + /* SOA Record - values same as defined in provision/sambadns.py */ + soa.serial = 1; + soa.refresh = 900; + soa.retry = 600; + soa.expire = 86400; + soa.minimum = 3600; + soa.mname = server_fqdn; + soa.rname = soa_email; + + dns_rec[0].wType = DNS_TYPE_SOA; + dns_rec[0].rank = DNS_RANK_ZONE; + dns_rec[0].dwSerial = soa.serial; + dns_rec[0].dwTtlSeconds = 3600; + dns_rec[0].dwTimeStamp = 0; + dns_rec[0].data.soa = soa; + + /* NS Record */ + dns_rec[1].wType = DNS_TYPE_NS; + dns_rec[1].rank = DNS_RANK_ZONE; + dns_rec[1].dwSerial = soa.serial; + dns_rec[1].dwTtlSeconds = 3600; + dns_rec[1].dwTimeStamp = 0; + dns_rec[1].data.ns = server_fqdn; + + /* Add @ Record */ + status = dnsserver_db_do_add_rec(tmp_ctx, samdb, dn, 2, dns_rec); + + talloc_free(tmp_ctx); + return status; +} + + +/* Delete dnsZone record and all DNS records in the zone */ +WERROR dnsserver_db_delete_zone(struct ldb_context *samdb, + struct dnsserver_zone *zone) +{ + int ret; + + ret = ldb_transaction_start(samdb); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + ret = dsdb_delete(samdb, zone->zone_dn, DSDB_TREE_DELETE); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(samdb); + + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + return WERR_ACCESS_DENIED; + } + return WERR_INTERNAL_DB_ERROR; + } + + ret = ldb_transaction_commit(samdb); + if (ret != LDB_SUCCESS) { + return WERR_INTERNAL_DB_ERROR; + } + + return WERR_OK; +} diff --git a/source4/rpc_server/dnsserver/dnsserver.h b/source4/rpc_server/dnsserver/dnsserver.h new file mode 100644 index 0000000..2e46e7c --- /dev/null +++ b/source4/rpc_server/dnsserver/dnsserver.h @@ -0,0 +1,264 @@ +/* + Unix SMB/CIFS implementation. + + DNS Server + + Copyright (C) Amitay Isaacs 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __DNSSERVER_H__ +#define __DNSSERVER_H__ + +#include "librpc/gen_ndr/dnsp.h" +#include "librpc/gen_ndr/dnsserver.h" +#include "param/param.h" +#include "ldb.h" + +struct dnsserver_serverinfo { + uint32_t dwVersion; + uint8_t fBootMethod; + uint8_t fAdminConfigured; + uint8_t fAllowUpdate; + uint8_t fDsAvailable; + + char * pszServerName; + char * pszDsContainer; + + uint32_t dwDsForestVersion; + uint32_t dwDsDomainVersion; + uint32_t dwDsDsaVersion; + uint32_t fReadOnlyDC; + char * pszDomainName; + char * pszForestName; + char * pszDomainDirectoryPartition; + char * pszForestDirectoryPartition; + + struct DNS_ADDR_ARRAY * aipServerAddrs; + struct DNS_ADDR_ARRAY * aipListenAddrs; + struct IP4_ARRAY * aipForwarders; + + struct IP4_ARRAY * aipLogFilter; + char * pwszLogFilePath; + + uint32_t dwLogLevel; + uint32_t dwDebugLevel; + uint32_t dwEventLogLevel; + uint32_t dwLogFileMaxSize; + + uint32_t dwForwardTimeout; + uint32_t dwRpcProtocol; + uint32_t dwNameCheckFlag; + uint32_t cAddressAnswerLimit; + uint32_t dwRecursionRetry; + uint32_t dwRecursionTimeout; + uint32_t dwMaxCacheTtl; + uint32_t dwDsPollingInterval; + uint32_t dwLocalNetPriorityNetMask; + + uint32_t dwScavengingInterval; + uint32_t dwDefaultRefreshInterval; + uint32_t dwDefaultNoRefreshInterval; + uint32_t dwLastScavengeTime; + + uint8_t fAutoReverseZones; + uint8_t fAutoCacheUpdate; + + uint8_t fRecurseAfterForwarding; + uint8_t fForwardDelegations; + uint8_t fNoRecursion; + uint8_t fSecureResponses; + + uint8_t fRoundRobin; + uint8_t fLocalNetPriority; + + uint8_t fBindSecondaries; + uint8_t fWriteAuthorityNs; + + uint8_t fStrictFileParsing; + uint8_t fLooseWildcarding; + uint8_t fDefaultAgingState; +}; + +struct dnsserver_zoneinfo { + uint8_t Version; + uint32_t Flags; + uint8_t dwZoneType; + uint8_t fReverse; + uint8_t fAllowUpdate; + uint8_t fPaused; + uint8_t fShutdown; + uint8_t fAutoCreated; + + uint8_t fUseDatabase; + char * pszDataFile; + + struct IP4_ARRAY * aipMasters; + + uint32_t fSecureSecondaries; + uint32_t fNotifyLevel; + struct IP4_ARRAY * aipSecondaries; + struct IP4_ARRAY * aipNotify; + + uint32_t fUseWins; + uint32_t fUseNbstat; + + uint32_t fAging; + uint32_t dwNoRefreshInterval; + uint32_t dwRefreshInterval; + uint32_t dwAvailForScavengeTime; + struct IP4_ARRAY * aipScavengeServers; + + uint32_t dwForwarderTimeout; + uint32_t fForwarderSlave; + + struct IP4_ARRAY * aipLocalMasters; + + char * pwszZoneDn; + + uint32_t dwLastSuccessfulSoaCheck; + uint32_t dwLastSuccessfulXfr; + + uint32_t fQueuedForBackgroundLoad; + uint32_t fBackgroundLoadInProgress; + uint8_t fReadOnlyZone; + + uint32_t dwLastXfrAttempt; + uint32_t dwLastXfrResult; +}; + + +struct dnsserver_partition { + struct dnsserver_partition *prev, *next; + struct ldb_dn *partition_dn; + const char *pszDpFqdn; + uint32_t dwDpFlags; + bool is_forest; + int zones_count; +}; + + +struct dnsserver_partition_info { + const char *pszCrDn; + uint32_t dwState; + uint32_t dwReplicaCount; + struct DNS_RPC_DP_REPLICA **ReplicaArray; +}; + + +struct dnsserver_zone { + struct dnsserver_zone *prev, *next; + struct dnsserver_partition *partition; + const char *name; + struct ldb_dn *zone_dn; + struct dnsserver_zoneinfo *zoneinfo; + struct dnsp_DnsProperty *tmp_props; + int32_t num_props; +}; + + +struct dns_tree { + const char *name; + int level; + unsigned int num_children; + struct dns_tree **children; + void *data; +}; + +/* Data structure manipulation functions from dnsdata.c */ + +struct IP4_ARRAY *ip4_array_copy(TALLOC_CTX *mem_ctx, struct IP4_ARRAY *ip4); +struct DNS_ADDR_ARRAY *ip4_array_to_dns_addr_array(TALLOC_CTX *mem_ctx, struct IP4_ARRAY *ip4); +struct IP4_ARRAY *dns_addr_array_to_ip4_array(TALLOC_CTX *mem_ctx, + struct DNS_ADDR_ARRAY *ip); +struct DNS_ADDR_ARRAY *dns_addr_array_copy(TALLOC_CTX *mem_ctx, struct DNS_ADDR_ARRAY *addr); + +int dns_split_name_components(TALLOC_CTX *mem_ctx, const char *name, char ***components); +char *dns_split_node_name(TALLOC_CTX *mem_ctx, const char *node_name, const char *zone_name); + +int dns_name_compare(struct ldb_message * const *m1, struct ldb_message * const *m2, + const char *search_name); +bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1, struct dnsp_DnssrvRpcRecord *rec2); + +void dnsp_to_dns_copy(TALLOC_CTX *mem_ctx, struct dnsp_DnssrvRpcRecord *dnsp, + struct DNS_RPC_RECORD *dns); +WERROR dns_to_dnsp_convert(TALLOC_CTX *mem_ctx, struct DNS_RPC_RECORD *dns, + struct dnsp_DnssrvRpcRecord **out_dnsp, + bool check_name); + +struct dns_tree *dns_build_tree(TALLOC_CTX *mem_ctx, const char *name, struct ldb_result *res); +WERROR dns_fill_records_array(TALLOC_CTX *mem_ctx, struct dnsserver_zone *z, + enum dns_record_type record_type, + unsigned int select_flag, const char *zone_name, + struct ldb_message *msg, int num_children, + struct DNS_RPC_RECORDS_ARRAY *recs, + char ***add_names, int *add_count); + + +/* Utility functions from dnsutils.c */ + +struct dnsserver_serverinfo *dnsserver_init_serverinfo(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct ldb_context *samdb); +struct dnsserver_zoneinfo *dnsserver_init_zoneinfo(struct dnsserver_zone *zone, + struct dnsserver_serverinfo *serverinfo); +struct dnsserver_zone *dnsserver_find_zone(struct dnsserver_zone *zones, + const char *zone_name); +struct ldb_dn *dnsserver_name_to_dn(TALLOC_CTX *mem_ctx, struct dnsserver_zone *z, + const char *name); +uint32_t dnsserver_zone_to_request_filter(const char *zone); + +/* Database functions from dnsdb.c */ + +struct dnsserver_partition *dnsserver_db_enumerate_partitions(TALLOC_CTX *mem_ctx, + struct dnsserver_serverinfo *serverinfo, + struct ldb_context *samdb); +struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_partition *p); +struct dnsserver_partition_info *dnsserver_db_partition_info(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_partition *p); +WERROR dnsserver_db_add_empty_node(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *node_name); +WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *node_name, + struct DNS_RPC_RECORD *add_record); +WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *node_name, + struct DNS_RPC_RECORD *add_record, + struct DNS_RPC_RECORD *del_record); +WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb, + struct dnsserver_zone *z, + struct DNS_RPC_NAME_AND_PARAM *n_p); +WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx, + struct ldb_context *samdb, + struct dnsserver_zone *z, + const char *node_name, + struct DNS_RPC_RECORD *del_record); +WERROR dnsserver_db_create_zone(struct ldb_context *samdb, + struct dnsserver_partition *partitions, + struct dnsserver_zone *z, + struct loadparm_context *lp_ctx); +WERROR dnsserver_db_delete_zone(struct ldb_context *samdb, + struct dnsserver_zone *z); + +#endif /* __DNSSERVER_H__ */ diff --git a/source4/rpc_server/dnsserver/dnsutils.c b/source4/rpc_server/dnsserver/dnsutils.c new file mode 100644 index 0000000..2c56946 --- /dev/null +++ b/source4/rpc_server/dnsserver/dnsutils.c @@ -0,0 +1,414 @@ +/* + Unix SMB/CIFS implementation. + + DNS Server + + Copyright (C) Amitay Isaacs 2011 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "dnsserver.h" +#include "rpc_server/common/common.h" +#include "dns_server/dnsserver_common.h" +#include "dsdb/samdb/samdb.h" +#include "lib/socket/netif.h" +#include "lib/util/util_net.h" +#include "dnsserver_common.h" + +#undef strcasecmp + +static struct DNS_ADDR_ARRAY *fill_dns_addr_array(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + bool listen_only) +{ + struct interface *ifaces; + int num_interfaces, i; + struct DNS_ADDR_ARRAY *dns_addr_array; + const char *ipstr; + bool have_ipv4, have_ipv6; + uint16_t family; + + have_ipv4 = have_ipv6 = false; + + if (!listen_only) { + /* + Return all interfaces from kernel + Not implemented! + */ + return NULL; + } + + /* Only the used interfaces */ + load_interface_list(mem_ctx, lp_ctx, &ifaces); + num_interfaces = iface_list_count(ifaces); + + dns_addr_array = talloc_zero(mem_ctx, struct DNS_ADDR_ARRAY); + if (dns_addr_array == NULL) { + goto nomem; + } + dns_addr_array->MaxCount = num_interfaces; + dns_addr_array->AddrCount = num_interfaces; + if (num_interfaces == 0) { + goto nomem; + } + + dns_addr_array->AddrArray = talloc_zero_array(mem_ctx, struct DNS_ADDR, + num_interfaces); + if (!dns_addr_array->AddrArray) { + TALLOC_FREE(dns_addr_array); + goto nomem; + } + + for (i = 0; i < num_interfaces; i++) { + int ret; + ipstr = iface_list_n_ip(ifaces, i); + if (is_ipaddress_v4(ipstr)) { + have_ipv4 = true; + dns_addr_array->AddrArray[i].MaxSa[0] = 0x02; + ret = inet_pton(AF_INET, ipstr, + &dns_addr_array->AddrArray[i].MaxSa[4]); + } else { + have_ipv6 = true; + dns_addr_array->AddrArray[i].MaxSa[0] = 0x17; + ret = inet_pton(AF_INET6, ipstr, + &dns_addr_array->AddrArray[i].MaxSa[8]); + } + if (ret != 1) { /*yep, 1 means success for inet_pton */ + DBG_ERR("Interface %d address (%s) is invalid\n", + i, ipstr); + goto nomem; + } + } + + if (have_ipv4 && have_ipv6) { + family = 0; /* mixed: MS-DNSP */ + } else if (have_ipv4 && !have_ipv6) { + family = AF_INET; + } else { + family = AF_INET6; + } + dns_addr_array->Family = family; + +nomem: + talloc_free(ifaces); + return dns_addr_array; +} + +struct dnsserver_serverinfo *dnsserver_init_serverinfo(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct ldb_context *samdb) +{ + struct dnsserver_serverinfo *serverinfo; + struct dcerpc_server_info *dinfo; + struct ldb_dn *domain_dn, *forest_dn; + + serverinfo = talloc_zero(mem_ctx, struct dnsserver_serverinfo); + if (serverinfo == NULL) { + return NULL; + } + + dinfo = lpcfg_dcerpc_server_info(mem_ctx, lp_ctx); + if (dinfo) { + serverinfo->dwVersion = (dinfo->version_build & 0x0000FFFF) << 16 | + (dinfo->version_minor & 0x000000FF) << 8 | + (dinfo->version_major & 0x000000FF); + talloc_free(dinfo); + } else { + serverinfo->dwVersion = 0x0ECE0205; /* build, os_minor, os_major */; + } + + serverinfo->fBootMethod = DNS_BOOT_METHOD_DIRECTORY; + serverinfo->fAdminConfigured = 0; + serverinfo->fAllowUpdate = 1; + serverinfo->fDsAvailable = 1; + + serverinfo->pszServerName = talloc_asprintf(mem_ctx, "%s.%s", + lpcfg_netbios_name(lp_ctx), + lpcfg_dnsdomain(lp_ctx)); + + domain_dn = ldb_get_default_basedn(samdb); + forest_dn = ldb_get_root_basedn(samdb); + + serverinfo->pszDsContainer = talloc_asprintf(mem_ctx, + "CN=MicrosoftDNS,DC=DomainDnsZones,%s", + ldb_dn_get_linearized(domain_dn)); + + serverinfo->dwDsForestVersion = dsdb_forest_functional_level(samdb); + serverinfo->dwDsDomainVersion = dsdb_functional_level(samdb); + serverinfo->dwDsDsaVersion = dsdb_dc_functional_level(samdb); + + serverinfo->pszDomainName = samdb_dn_to_dns_domain(mem_ctx, domain_dn); + serverinfo->pszForestName = samdb_dn_to_dns_domain(mem_ctx, forest_dn); + + serverinfo->pszDomainDirectoryPartition = talloc_asprintf(mem_ctx, + "DC=DomainDnsZones,%s", + ldb_dn_get_linearized(domain_dn)); + serverinfo->pszForestDirectoryPartition = talloc_asprintf(mem_ctx, + "DC=ForestDnsZones,%s", + ldb_dn_get_linearized(forest_dn)); + /* IP addresses on which the DNS server listens for DNS requests */ + serverinfo->aipListenAddrs = fill_dns_addr_array(mem_ctx, lp_ctx, true); + + /* All IP addresses available on the server + * Not implemented! + * Use same as listen addresses + */ + serverinfo->aipServerAddrs = serverinfo->aipListenAddrs; + + serverinfo->aipForwarders = NULL; + + serverinfo->aipLogFilter = NULL; + serverinfo->pwszLogFilePath = NULL; + + serverinfo->dwLogLevel = 0; + serverinfo->dwDebugLevel = 0; + serverinfo->dwEventLogLevel = DNS_EVENT_LOG_INFORMATION_TYPE; + serverinfo->dwLogFileMaxSize = 0; + + serverinfo->dwForwardTimeout = 3; /* seconds (default) */ + serverinfo->dwRpcProtocol = 5; + serverinfo->dwNameCheckFlag = DNS_ALLOW_MULTIBYTE_NAMES; + serverinfo->cAddressAnswerLimit = 0; + serverinfo->dwRecursionRetry = 3; /* seconds (default) */ + serverinfo->dwRecursionTimeout = 8; /* seconds (default) */ + serverinfo->dwMaxCacheTtl = 0x00015180; /* 1 day (default) */ + serverinfo->dwDsPollingInterval = 0xB4; /* 3 minutes (default) */ + serverinfo->dwLocalNetPriorityNetMask = 0x000000FF; + + serverinfo->dwScavengingInterval = lpcfg_parm_int( + lp_ctx, NULL, "dnsserver", "ScavengingInterval", 24 * 7); + serverinfo->dwDefaultRefreshInterval = lpcfg_parm_int( + lp_ctx, NULL, "dnsserver", "DefaultRefreshInterval", 24 * 3); + serverinfo->dwDefaultNoRefreshInterval = lpcfg_parm_int( + lp_ctx, NULL, "dnsserver", "DefaultNoRefreshInterval", 24 * 3); + + serverinfo->dwLastScavengeTime = 0; + + serverinfo->fAutoReverseZones = 0; + serverinfo->fAutoCacheUpdate = 0; + + serverinfo->fRecurseAfterForwarding = 0; + serverinfo->fForwardDelegations = 1; + serverinfo->fNoRecursion = 0; + serverinfo->fSecureResponses = 0; + + serverinfo->fRoundRobin = 1; + serverinfo->fLocalNetPriority = 0; + + serverinfo->fBindSecondaries = 0; + serverinfo->fWriteAuthorityNs = 0; + + serverinfo->fStrictFileParsing = 0; + serverinfo->fLooseWildcarding = 0 ; + serverinfo->fDefaultAgingState = 0; + + return serverinfo; +} + +struct dnsserver_zoneinfo *dnsserver_init_zoneinfo(struct dnsserver_zone *zone, + struct dnsserver_serverinfo *serverinfo) +{ + struct dnsserver_zoneinfo *zoneinfo; + uint32_t fReverse; + const char *revzone = "in-addr.arpa"; + const char *revzone6 = "ip6.arpa"; + int len1, len2; + unsigned int i = 0; + + zoneinfo = talloc_zero(zone, struct dnsserver_zoneinfo); + if (zoneinfo == NULL) { + return NULL; + } + + /* If the zone name ends with in-addr.arpa, it's reverse zone */ + /* If the zone name ends with ip6.arpa, it's reverse zone (IPv6) */ + fReverse = 0; + len1 = strlen(zone->name); + len2 = strlen(revzone); + if (len1 > len2 && strcasecmp(&zone->name[len1-len2], revzone) == 0) { + fReverse = 1; + } else { + len2 = strlen(revzone6); + if (len1 > len2 && strcasecmp(&zone->name[len1-len2], revzone6) == 0) { + fReverse = 1; + } + } + + zoneinfo->Version = 0x32; + zoneinfo->Flags = DNS_RPC_ZONE_DSINTEGRATED; + + if (strcmp(zone->name, ".") == 0) { + zoneinfo->dwZoneType = DNS_ZONE_TYPE_CACHE; + zoneinfo->fAllowUpdate = DNS_ZONE_UPDATE_OFF; + zoneinfo->fSecureSecondaries = DNS_ZONE_SECSECURE_NO_SECURITY; + zoneinfo->fNotifyLevel = DNS_ZONE_NOTIFY_OFF; + zoneinfo->dwNoRefreshInterval = 0; + zoneinfo->dwRefreshInterval = 0; + } else { + zoneinfo->Flags |= DNS_RPC_ZONE_UPDATE_SECURE; + zoneinfo->dwZoneType = DNS_ZONE_TYPE_PRIMARY; + zoneinfo->fAllowUpdate = DNS_ZONE_UPDATE_SECURE; + zoneinfo->fSecureSecondaries = DNS_ZONE_SECSECURE_NO_XFER; + zoneinfo->fNotifyLevel = DNS_ZONE_NOTIFY_LIST_ONLY; + zoneinfo->dwNoRefreshInterval = serverinfo->dwDefaultNoRefreshInterval; + zoneinfo->dwRefreshInterval = serverinfo->dwDefaultRefreshInterval; + } + + zoneinfo->fReverse = fReverse; + zoneinfo->fPaused = 0; + zoneinfo->fShutdown = 0; + zoneinfo->fAutoCreated = 0; + zoneinfo->fUseDatabase = 1; + zoneinfo->pszDataFile = NULL; + zoneinfo->aipMasters = NULL; + zoneinfo->aipSecondaries = NULL; + zoneinfo->aipNotify = NULL; + zoneinfo->fUseWins = 0; + zoneinfo->fUseNbstat = 0; + zoneinfo->fAging = 0; + zoneinfo->dwAvailForScavengeTime = 0; + zoneinfo->aipScavengeServers = NULL; + zoneinfo->dwForwarderTimeout = 0; + zoneinfo->fForwarderSlave = 0; + zoneinfo->aipLocalMasters = NULL; + zoneinfo->pwszZoneDn = discard_const_p(char, ldb_dn_get_linearized(zone->zone_dn)); + zoneinfo->dwLastSuccessfulSoaCheck = 0; + zoneinfo->dwLastSuccessfulXfr = 0; + zoneinfo->fQueuedForBackgroundLoad = 0; + zoneinfo->fBackgroundLoadInProgress = 0; + zoneinfo->fReadOnlyZone = 0; + zoneinfo->dwLastXfrAttempt = 0; + zoneinfo->dwLastXfrResult = 0; + + for(i=0; i<zone->num_props; i++){ + bool valid_property; + valid_property = dns_zoneinfo_load_zone_property( + zoneinfo, &zone->tmp_props[i]); + if (!valid_property) { + TALLOC_FREE(zoneinfo); + return NULL; + } + } + + return zoneinfo; +} + +struct dnsserver_zone *dnsserver_find_zone(struct dnsserver_zone *zones, const char *zone_name) +{ + struct dnsserver_zone *z = NULL; + + for (z = zones; z; z = z->next) { + if (samba_dns_name_equal(zone_name, z->name)) { + break; + } + } + + return z; +} + +struct ldb_dn *dnsserver_name_to_dn(TALLOC_CTX *mem_ctx, struct dnsserver_zone *z, const char *name) +{ + struct ldb_dn *dn; + bool ret; + struct ldb_val name_val = + data_blob_string_const(name); + + dn = ldb_dn_copy(mem_ctx, z->zone_dn); + if (dn == NULL) { + return NULL; + } + if (strcasecmp(name, z->name) == 0) { + ret = ldb_dn_add_child_fmt(dn, "DC=@"); + if (!ret) { + talloc_free(dn); + return NULL; + } + return dn; + } + + ret = ldb_dn_add_child_val(dn, + "DC", + name_val); + + if (!ret) { + talloc_free(dn); + return NULL; + } + + return dn; +} + +uint32_t dnsserver_zone_to_request_filter(const char *zone_name) +{ + uint32_t request_filter = 0; + + if (strcmp(zone_name, "..AllZones") == 0) { + request_filter = DNS_ZONE_REQUEST_PRIMARY + | DNS_ZONE_REQUEST_SECONDARY + | DNS_ZONE_REQUEST_AUTO + | DNS_ZONE_REQUEST_FORWARD + | DNS_ZONE_REQUEST_REVERSE + | DNS_ZONE_REQUEST_FORWARDER + | DNS_ZONE_REQUEST_STUB + | DNS_ZONE_REQUEST_DS + | DNS_ZONE_REQUEST_NON_DS + | DNS_ZONE_REQUEST_DOMAIN_DP + | DNS_ZONE_REQUEST_FOREST_DP + | DNS_ZONE_REQUEST_CUSTOM_DP + | DNS_ZONE_REQUEST_LEGACY_DP; + } else if (strcmp(zone_name, "..AllZonesAndCache") == 0) { + request_filter = DNS_ZONE_REQUEST_PRIMARY + | DNS_ZONE_REQUEST_SECONDARY + | DNS_ZONE_REQUEST_CACHE + | DNS_ZONE_REQUEST_AUTO + | DNS_ZONE_REQUEST_FORWARD + | DNS_ZONE_REQUEST_REVERSE + | DNS_ZONE_REQUEST_FORWARDER + | DNS_ZONE_REQUEST_STUB + | DNS_ZONE_REQUEST_DS + | DNS_ZONE_REQUEST_NON_DS + | DNS_ZONE_REQUEST_DOMAIN_DP + | DNS_ZONE_REQUEST_FOREST_DP + | DNS_ZONE_REQUEST_CUSTOM_DP + | DNS_ZONE_REQUEST_LEGACY_DP; + } else if (strcmp(zone_name, "..AllPrimaryZones") == 0) { + request_filter = DNS_ZONE_REQUEST_PRIMARY; + } else if (strcmp(zone_name, "..AllSecondaryZones") == 0) { + request_filter = DNS_ZONE_REQUEST_SECONDARY; + } else if (strcmp(zone_name, "..AllForwardZones") == 0) { + request_filter = DNS_ZONE_REQUEST_FORWARD; + } else if (strcmp(zone_name, "..AllReverseZones") == 0) { + request_filter = DNS_ZONE_REQUEST_REVERSE; + } else if (strcmp(zone_name, "..AllDsZones") == 0) { + request_filter = DNS_ZONE_REQUEST_DS; + } else if (strcmp(zone_name, "..AllNonDsZones") == 0) { + request_filter = DNS_ZONE_REQUEST_NON_DS; + } else if (strcmp(zone_name, "..AllPrimaryReverseZones") == 0) { + request_filter = DNS_ZONE_REQUEST_PRIMARY + | DNS_ZONE_REQUEST_REVERSE; + } else if (strcmp(zone_name, "..AllPrimaryForwardZones") == 0) { + request_filter = DNS_ZONE_REQUEST_PRIMARY + | DNS_ZONE_REQUEST_FORWARD; + } else if (strcmp(zone_name, "..AllSecondaryReverseZones") == 0) { + request_filter = DNS_ZONE_REQUEST_SECONDARY + | DNS_ZONE_REQUEST_REVERSE; + } else if (strcmp(zone_name, "..AllSecondaryForwardZones") == 0) { + request_filter = DNS_ZONE_REQUEST_SECONDARY + | DNS_ZONE_REQUEST_REVERSE; + } + + return request_filter; +} diff --git a/source4/rpc_server/drsuapi/addentry.c b/source4/rpc_server/drsuapi/addentry.c new file mode 100644 index 0000000..ff23c52 --- /dev/null +++ b/source4/rpc_server/drsuapi/addentry.c @@ -0,0 +1,240 @@ +/* + Unix SMB/CIFS implementation. + + implement the DsAddEntry call + + Copyright (C) Stefan Metzmacher 2009 + Copyright (C) Andrew Tridgell 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "param/param.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +/* + add special SPNs needed for DRS replication to machine accounts when + an AddEntry is done to create a nTDSDSA object + */ +static WERROR drsuapi_add_SPNs(struct drsuapi_bind_state *b_state, + struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + const struct drsuapi_DsReplicaObjectListItem *first_object) +{ + int ret; + const struct drsuapi_DsReplicaObjectListItem *obj; + const char *attrs[] = { "serverReference", "objectGUID", NULL }; + + for (obj = first_object; obj; obj=obj->next_object) { + const char *dn_string = obj->object.identifier->dn; + struct ldb_dn *dn = ldb_dn_new(mem_ctx, b_state->sam_ctx, dn_string); + struct ldb_result *res, *res2; + struct ldb_dn *ref_dn; + struct GUID ntds_guid; + struct ldb_message *msg; + struct ldb_message_element *el; + const char *ntds_guid_str; + const char *dom_string; + const char *attrs2[] = { "dNSHostName", "cn", NULL }; + const char *dNSHostName, *cn; + + DEBUG(6,(__location__ ": Adding SPNs for %s\n", + ldb_dn_get_linearized(dn))); + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res, + dn, LDB_SCOPE_BASE, attrs, + "(objectClass=ntDSDSA)"); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find dn '%s'\n", dn_string)); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (res->count < 1) { + /* we only add SPNs for nTDSDSA objects */ + continue; + } + + ref_dn = samdb_result_dn(b_state->sam_ctx, mem_ctx, res->msgs[0], "serverReference", NULL); + if (ref_dn == NULL) { + /* we only add SPNs for objects with a + serverReference */ + continue; + } + + DEBUG(6,(__location__ ": serverReference %s\n", + ldb_dn_get_linearized(ref_dn))); + + ntds_guid = samdb_result_guid(res->msgs[0], "objectGUID"); + + ntds_guid_str = GUID_string(res, &ntds_guid); + + dom_string = lpcfg_dnsdomain(dce_call->conn->dce_ctx->lp_ctx); + + /* get the dNSHostName and cn */ + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res2, + ref_dn, LDB_SCOPE_BASE, attrs2, NULL); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to find ref_dn '%s'\n", + ldb_dn_get_linearized(ref_dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + dNSHostName = ldb_msg_find_attr_as_string(res2->msgs[0], "dNSHostName", NULL); + cn = ldb_msg_find_attr_as_string(res2->msgs[0], "cn", NULL); + + /* + * construct a modify request to add the new SPNs to + * the machine account + */ + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + msg->dn = ref_dn; + ret = ldb_msg_add_empty(msg, "servicePrincipalName", + LDB_FLAG_MOD_ADD, &el); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + + + ldb_msg_add_steal_string(msg, "servicePrincipalName", + talloc_asprintf(el->values, + "E3514235-4B06-11D1-AB04-00C04FC2DCD2/%s/%s", + ntds_guid_str, dom_string)); + ldb_msg_add_steal_string(msg, "servicePrincipalName", + talloc_asprintf(el->values, "ldap/%s._msdcs.%s", + ntds_guid_str, dom_string)); + if (cn) { + ldb_msg_add_steal_string(msg, "servicePrincipalName", + talloc_asprintf(el->values, "ldap/%s", cn)); + } + if (dNSHostName) { + ldb_msg_add_steal_string(msg, "servicePrincipalName", + talloc_asprintf(el->values, "ldap/%s", dNSHostName)); + } + if (el->num_values < 2) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = dsdb_modify(b_state->sam_ctx, msg, DSDB_MODIFY_PERMISSIVE); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to add SPNs - %s\n", + ldb_errstring(b_state->sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + + return WERR_OK; +} + + + + +/* + drsuapi_DsAddEntry +*/ +WERROR dcesrv_drsuapi_DsAddEntry(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsAddEntry *r) +{ + WERROR status; + struct drsuapi_bind_state *b_state; + struct dcesrv_handle *h; + uint32_t num = 0; + struct drsuapi_DsReplicaObjectIdentifier2 *ids = NULL; + int ret; + const struct drsuapi_DsReplicaObjectListItem *first_object; + + if (DEBUGLVL(4)) { + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsAddEntry, NDR_IN, r); + } + + /* TODO: check which out level the client supports */ + + ZERO_STRUCTP(r->out.ctr); + *r->out.level_out = 3; + r->out.ctr->ctr3.err_ver = 1; + r->out.ctr->ctr3.err_data = talloc_zero(mem_ctx, union drsuapi_DsAddEntry_ErrData); + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + status = drs_security_level_check(dce_call, "DsAddEntry", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + switch (r->in.level) { + case 2: + ret = ldb_transaction_start(b_state->sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("DsAddEntry start transaction failed: %s\n", + ldb_errstring(b_state->sam_ctx)); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + + first_object = &r->in.req->req2.first_object; + + status = dsdb_origin_objects_commit(b_state->sam_ctx, + mem_ctx, + first_object, + &num, + DSDB_REPL_FLAG_ADD_NCNAME, + &ids); + if (!W_ERROR_IS_OK(status)) { + r->out.ctr->ctr3.err_data->v1.status = status; + ldb_transaction_cancel(b_state->sam_ctx); + DEBUG(0,(__location__ ": DsAddEntry failed - %s\n", win_errstr(status))); + return status; + } + + r->out.ctr->ctr3.count = num; + r->out.ctr->ctr3.objects = ids; + + break; + default: + return WERR_FOOBAR; + } + + /* if any of the added entries are nTDSDSA objects then we + * need to add the SPNs to the machine account + */ + status = drsuapi_add_SPNs(b_state, dce_call, mem_ctx, first_object); + if (!W_ERROR_IS_OK(status)) { + r->out.ctr->ctr3.err_data->v1.status = status; + ldb_transaction_cancel(b_state->sam_ctx); + DEBUG(0,(__location__ ": DsAddEntry add SPNs failed - %s\n", win_errstr(status))); + return status; + } + + ret = ldb_transaction_commit(b_state->sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": DsAddEntry commit failed: %s\n", + ldb_errstring(b_state->sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} diff --git a/source4/rpc_server/drsuapi/dcesrv_drsuapi.c b/source4/rpc_server/drsuapi/dcesrv_drsuapi.c new file mode 100644 index 0000000..8bb4fb2 --- /dev/null +++ b/source4/rpc_server/drsuapi/dcesrv_drsuapi.c @@ -0,0 +1,1070 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the drsuapi pipe + + Copyright (C) Stefan Metzmacher 2004 + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" +#include "auth/auth.h" +#include "param/param.h" +#include "lib/messaging/irpc.h" + +#undef strcasecmp +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +#define DRSUAPI_UNSUPPORTED(fname) do { \ + DEBUG(1,(__location__ ": Unsupported DRS call %s\n", #fname)); \ + if (DEBUGLVL(2)) NDR_PRINT_IN_DEBUG(fname, r); \ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); \ +} while (0) + +#define DCESRV_INTERFACE_DRSUAPI_BIND(context, iface) \ + dcesrv_interface_drsuapi_bind(context, iface) +static NTSTATUS dcesrv_interface_drsuapi_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_require_privacy(context, iface); +} + +/* + drsuapi_DsBind +*/ +static WERROR dcesrv_drsuapi_DsBind(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsBind *r) +{ + struct drsuapi_bind_state *b_state; + struct dcesrv_handle *handle; + struct drsuapi_DsBindInfoCtr *bind_info; + struct drsuapi_DsBindInfoCtr *local_info; + struct GUID site_guid, config_guid; + struct ldb_result *site_res, *config_res; + struct ldb_dn *server_site_dn, *config_dn; + static const char *site_attrs[] = { "objectGUID", NULL }; + static const char *config_attrs[] = { "objectGUID", NULL }; + struct ldb_result *ntds_res; + struct ldb_dn *ntds_dn; + static const char *ntds_attrs[] = { "ms-DS-ReplicationEpoch", NULL }; + uint32_t pid; + uint32_t repl_epoch; + uint32_t supported_extensions; + uint32_t req_length; + int ret; + WERROR werr; + + r->out.bind_info = NULL; + ZERO_STRUCTP(r->out.bind_handle); + + b_state = talloc_zero(mem_ctx, struct drsuapi_bind_state); + W_ERROR_HAVE_NO_MEMORY(b_state); + + /* if this is a DC connecting, give them system level access */ + werr = drs_security_level_check(dce_call, NULL, SECURITY_DOMAIN_CONTROLLER, NULL); + if (W_ERROR_IS_OK(werr)) { + DBG_NOTICE("doing DsBind with system_session\n"); + b_state->sam_ctx_system = dcesrv_samdb_connect_as_system(b_state, dce_call); + if (b_state->sam_ctx_system == NULL) { + return WERR_DS_UNAVAILABLE; + } + b_state->sam_ctx = b_state->sam_ctx_system; + } else { + b_state->sam_ctx = dcesrv_samdb_connect_as_user(b_state, dce_call); + if (b_state->sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + /* + * an RODC also needs system samdb access for secret + * attribute replication + */ + werr = drs_security_level_check(dce_call, NULL, SECURITY_RO_DOMAIN_CONTROLLER, + samdb_domain_sid(b_state->sam_ctx)); + if (W_ERROR_IS_OK(werr)) { + DBG_NOTICE("doing DsBind as RODC\n"); + b_state->sam_ctx_system = + dcesrv_samdb_connect_as_system(b_state, dce_call); + if (b_state->sam_ctx_system == NULL) { + return WERR_DS_UNAVAILABLE; + } + } + } + + /* + * find out the guid of our own site + */ + server_site_dn = samdb_server_site_dn(b_state->sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(server_site_dn); + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &site_res, + server_site_dn, LDB_SCOPE_BASE, site_attrs, + "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + if (site_res->count != 1) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + site_guid = samdb_result_guid(site_res->msgs[0], "objectGUID"); + + /* + * lookup the local servers Replication Epoch + */ + ntds_dn = samdb_ntds_settings_dn(b_state->sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(ntds_dn); + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &ntds_res, + ntds_dn, LDB_SCOPE_BASE, ntds_attrs, + "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + if (ntds_res->count != 1) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + repl_epoch = ldb_msg_find_attr_as_uint(ntds_res->msgs[0], + "ms-DS-ReplicationEpoch", 0); + + /* + * The "process identifier" of the client. + * According to the WSPP docs, sectin 5.35, this is + * for informational and debugging purposes only. + * The assignment is implementation specific. + */ + pid = 0; + + /* + * store the clients bind_guid + */ + if (r->in.bind_guid) { + b_state->remote_bind_guid = *r->in.bind_guid; + } + + /* + * store the clients bind_info + */ + if (r->in.bind_info) { + b_state->remote_info = r->in.bind_info; + } + + /* + * fill in our local bind info + */ + local_info = talloc_zero(mem_ctx, struct drsuapi_DsBindInfoCtr); + W_ERROR_HAVE_NO_MEMORY(local_info); + + /* + * Fill in supported extensions + */ + supported_extensions = 0; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_BASE; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ASYNC_REPLICATION; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_REMOVEAPI; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_MOVEREQ_V2; +#if 0 /* we don't support MSZIP compression (only decompression) */ + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHG_COMPRESS; +#endif + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V1; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_RESTORE_USN_OPTIMIZATION; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_KCC_EXECUTE; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRY_V2; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_LINKED_VALUE_REPLICATION; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V2; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_INSTANCE_TYPE_NOT_REQ_ON_MOD; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_CRYPTO_BIND; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_REPL_INFO; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_STRONG_ENCRYPTION; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_DCINFO_V01; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_TRANSITIVE_MEMBERSHIP; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADD_SID_HISTORY; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_POST_BETA3; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V5; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GET_MEMBERSHIPS2; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V6; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_NONDOMAIN_NCS; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V8; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V5; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V6; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_ADDENTRYREPLY_V3; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREPLY_V7; + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_VERIFY_OBJECT; +#if 0 /* we don't support XPRESS compression yet */ + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_XPRESS_COMPRESS; +#endif + supported_extensions |= DRSUAPI_SUPPORTED_EXTENSION_GETCHGREQ_V10; + + /* + * There is a chance for r->in.bind_info == NULL + * Currently we don't care, since it seems to be used nowhere else. + * But we need a request length. So use 28 as default. + */ + req_length = 28; + if (r->in.bind_info) { + req_length = r->in.bind_info->length; + } + + /* + * fill 28 or 48 info, depends on request + */ + if (req_length < 48) { + local_info->length = 28; + local_info->info.info28.supported_extensions = supported_extensions; + local_info->info.info28.site_guid = site_guid; + local_info->info.info28.pid = pid; + local_info->info.info28.repl_epoch = repl_epoch; + } else { + local_info->length = 48; + local_info->info.info48.supported_extensions = supported_extensions; + local_info->info.info48.site_guid = site_guid; + local_info->info.info48.pid = pid; + local_info->info.info48.repl_epoch = repl_epoch; + + local_info->info.info48.supported_extensions_ext = 0; + local_info->info.info48.supported_extensions_ext |= DRSUAPI_SUPPORTED_EXTENSION_LH_BETA2; + + /* + * find out the guid of our own site + */ + config_dn = ldb_get_config_basedn(b_state->sam_ctx); + W_ERROR_HAVE_NO_MEMORY(config_dn); + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &config_res, + config_dn, LDB_SCOPE_BASE, config_attrs, + "(objectClass=*)"); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + if (config_res->count != 1) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + config_guid = samdb_result_guid(config_res->msgs[0], "objectGUID"); + local_info->info.info48.config_dn_guid = config_guid; + } + + /* + * set local_info + */ + b_state->local_info = local_info; + + /* + * set bind_info + */ + bind_info = local_info; + + /* + * allocate a bind handle + */ + handle = dcesrv_handle_create(dce_call, DRSUAPI_BIND_HANDLE); + W_ERROR_HAVE_NO_MEMORY(handle); + handle->data = talloc_steal(handle, b_state); + + /* + * prepare reply + */ + r->out.bind_info = bind_info; + *r->out.bind_handle = handle->wire_handle; + + return WERR_OK; +} + + +/* + drsuapi_DsUnbind +*/ +static WERROR dcesrv_drsuapi_DsUnbind(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsUnbind *r) +{ + struct dcesrv_handle *h; + + *r->out.bind_handle = *r->in.bind_handle; + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + + talloc_free(h); + + ZERO_STRUCTP(r->out.bind_handle); + + return WERR_OK; +} + + +/* + drsuapi_DsReplicaSync +*/ +static WERROR dcesrv_drsuapi_DsReplicaSync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaSync *r) +{ + WERROR status; + uint32_t timeout; + + status = drs_security_level_check(dce_call, "DsReplicaSync", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + if (r->in.level != 1) { + DEBUG(0,("DsReplicaSync called with unsupported level %d\n", r->in.level)); + return WERR_DS_DRA_INVALID_PARAMETER; + } + + if (r->in.req->req1.options & DRSUAPI_DRS_ASYNC_OP) { + timeout = IRPC_CALL_TIMEOUT; + } else { + /* + * use Infinite time for timeout in case + * the caller made a sync call + */ + timeout = IRPC_CALL_TIMEOUT_INF; + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, + r, NDR_DRSUAPI_DSREPLICASYNC, + &ndr_table_drsuapi, + "dreplsrv", "DsReplicaSync", + timeout); + + return WERR_OK; +} + + +/* + drsuapi_DsReplicaAdd +*/ +static WERROR dcesrv_drsuapi_DsReplicaAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaAdd *r) +{ + WERROR status; + + status = drs_security_level_check(dce_call, "DsReplicaAdd", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, + r, NDR_DRSUAPI_DSREPLICAADD, + &ndr_table_drsuapi, + "dreplsrv", "DsReplicaAdd", + IRPC_CALL_TIMEOUT); + + return WERR_OK; +} + + +/* + drsuapi_DsReplicaDel +*/ +static WERROR dcesrv_drsuapi_DsReplicaDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaDel *r) +{ + WERROR status; + + status = drs_security_level_check(dce_call, "DsReplicaDel", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, + r, NDR_DRSUAPI_DSREPLICADEL, + &ndr_table_drsuapi, + "dreplsrv", "DsReplicaDel", + IRPC_CALL_TIMEOUT); + + return WERR_OK; +} + + +/* + drsuapi_DsReplicaModify +*/ +static WERROR dcesrv_drsuapi_DsReplicaMod(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaMod *r) +{ + WERROR status; + + status = drs_security_level_check(dce_call, "DsReplicaMod", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, + r, NDR_DRSUAPI_DSREPLICAMOD, + &ndr_table_drsuapi, + "dreplsrv", "DsReplicaMod", + IRPC_CALL_TIMEOUT); + + return WERR_OK; +} + + +/* + DRSUAPI_VERIFY_NAMES +*/ +static WERROR dcesrv_DRSUAPI_VERIFY_NAMES(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_VERIFY_NAMES *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_VERIFY_NAMES); +} + + +/* + drsuapi_DsGetMemberships +*/ +static WERROR dcesrv_drsuapi_DsGetMemberships(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetMemberships *r) +{ + DRSUAPI_UNSUPPORTED(drsuapi_DsGetMemberships); +} + + +/* + DRSUAPI_INTER_DOMAIN_MOVE +*/ +static WERROR dcesrv_DRSUAPI_INTER_DOMAIN_MOVE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_INTER_DOMAIN_MOVE *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_INTER_DOMAIN_MOVE); +} + + +/* + drsuapi_DsGetNT4ChangeLog +*/ +static WERROR dcesrv_drsuapi_DsGetNT4ChangeLog(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNT4ChangeLog *r) +{ + DRSUAPI_UNSUPPORTED(drsuapi_DsGetNT4ChangeLog); +} + +/* + drsuapi_DsCrackNames +*/ +static WERROR dcesrv_drsuapi_DsCrackNames(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsCrackNames *r) +{ + struct drsuapi_bind_state *b_state; + struct dcesrv_handle *h; + + *r->out.level_out = r->in.level; + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + r->out.ctr = talloc_zero(mem_ctx, union drsuapi_DsNameCtr); + W_ERROR_HAVE_NO_MEMORY(r->out.ctr); + + switch (r->in.level) { + case 1: { + switch(r->in.req->req1.format_offered){ + case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT_NAME_SANS_DOMAIN_EX: + case DRSUAPI_DS_NAME_FORMAT_NT4_ACCOUNT_NAME_SANS_DOMAIN: + case DRSUAPI_DS_NAME_FORMAT_STRING_SID_NAME: + case DRSUAPI_DS_NAME_FORMAT_ALT_SECURITY_IDENTITIES_NAME: + case DRSUAPI_DS_NAME_FORMAT_MAP_SCHEMA_GUID: + case DRSUAPI_DS_NAME_FORMAT_LIST_NCS: + case DRSUAPI_DS_NAME_FORMAT_LIST_DOMAINS: + case DRSUAPI_DS_NAME_FORMAT_LIST_GLOBAL_CATALOG_SERVERS: + case DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_WITH_DCS_IN_SITE: + case DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_FOR_DOMAIN_IN_SITE: + case DRSUAPI_DS_NAME_FORMAT_LIST_DOMAINS_IN_SITE: + case DRSUAPI_DS_NAME_FORMAT_LIST_SERVERS_IN_SITE: + case DRSUAPI_DS_NAME_FORMAT_LIST_SITES: + case DRSUAPI_DS_NAME_FORMAT_UPN_AND_ALTSECID: + case DRSUAPI_DS_NAME_FORMAT_UPN_FOR_LOGON: + DEBUG(0, ("DsCrackNames: Unsupported operation requested: %X", + r->in.req->req1.format_offered)); + return WERR_OK; + case DRSUAPI_DS_NAME_FORMAT_LIST_INFO_FOR_SERVER: + return dcesrv_drsuapi_ListInfoServer(b_state->sam_ctx, mem_ctx, &r->in.req->req1, &r->out.ctr->ctr1); + case DRSUAPI_DS_NAME_FORMAT_LIST_ROLES: + return dcesrv_drsuapi_ListRoles(b_state->sam_ctx, mem_ctx, + &r->in.req->req1, &r->out.ctr->ctr1); + default:/* format_offered is in the enum drsuapi_DsNameFormat*/ + return dcesrv_drsuapi_CrackNamesByNameFormat(b_state->sam_ctx, mem_ctx, + &r->in.req->req1, &r->out.ctr->ctr1); + } + } + } + return WERR_INVALID_LEVEL; +} + + +/* + drsuapi_DsRemoveDSServer +*/ +static WERROR dcesrv_drsuapi_DsRemoveDSServer(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsRemoveDSServer *r) +{ + struct drsuapi_bind_state *b_state; + struct dcesrv_handle *h; + struct ldb_dn *ntds_dn; + int ret; + bool ok; + WERROR status; + + *r->out.level_out = 1; + + status = drs_security_level_check(dce_call, "DsRemoveDSServer", SECURITY_DOMAIN_CONTROLLER, NULL); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + switch (r->in.level) { + case 1: + ntds_dn = ldb_dn_new(mem_ctx, b_state->sam_ctx, r->in.req->req1.server_dn); + W_ERROR_HAVE_NO_MEMORY(ntds_dn); + + ok = ldb_dn_validate(ntds_dn); + if (!ok) { + return WERR_FOOBAR; + } + + /* TODO: it's likely that we need more checks here */ + + ok = ldb_dn_add_child_fmt(ntds_dn, "CN=NTDS Settings"); + if (!ok) { + return WERR_FOOBAR; + } + + if (r->in.req->req1.commit) { + ret = dsdb_delete(b_state->sam_ctx, ntds_dn, DSDB_TREE_DELETE); + if (ret != LDB_SUCCESS) { + return WERR_FOOBAR; + } + } + + return WERR_OK; + default: + break; + } + + return WERR_FOOBAR; +} + + +/* + DRSUAPI_REMOVE_DS_DOMAIN +*/ +static WERROR dcesrv_DRSUAPI_REMOVE_DS_DOMAIN(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_REMOVE_DS_DOMAIN *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_REMOVE_DS_DOMAIN); +} + +/* Obtain the site name from a server DN */ +static const char *result_site_name(struct ldb_dn *server_dn) +{ + /* Format is cn=<NETBIOS name>,cn=Servers,cn=<site>,cn=sites.... */ + const struct ldb_val *val = ldb_dn_get_component_val(server_dn, 2); + const char *name = ldb_dn_get_component_name(server_dn, 2); + + if (!name || (ldb_attr_cmp(name, "cn") != 0)) { + /* Ensure this matches the format. This gives us a + * bit more confidence that a 'cn' value will be a + * ascii string */ + return NULL; + } + if (val) { + return (char *)val->data; + } + return NULL; +} + +/* + drsuapi_DsGetDomainControllerInfo +*/ +static WERROR dcesrv_drsuapi_DsGetDomainControllerInfo_1(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetDomainControllerInfo *r) +{ + struct ldb_dn *sites_dn; + struct ldb_result *res; + + const char *attrs_account_1[] = { "cn", "dnsHostName", NULL }; + const char *attrs_account_2[] = { "cn", "dnsHostName", "objectGUID", NULL }; + + const char *attrs_none[] = { NULL }; + + const char *attrs_site[] = { "objectGUID", NULL }; + + const char *attrs_ntds[] = { "options", "objectGUID", NULL }; + + const char *attrs_1[] = { "serverReference", "cn", "dnsHostName", NULL }; + const char *attrs_2[] = { "serverReference", "cn", "dnsHostName", "objectGUID", NULL }; + const char **attrs; + + struct drsuapi_DsGetDCInfoCtr1 *ctr1; + struct drsuapi_DsGetDCInfoCtr2 *ctr2; + struct drsuapi_DsGetDCInfoCtr3 *ctr3; + + int ret; + unsigned int i; + + *r->out.level_out = r->in.req->req1.level; + r->out.ctr = talloc_zero(mem_ctx, union drsuapi_DsGetDCInfoCtr); + W_ERROR_HAVE_NO_MEMORY(r->out.ctr); + + switch (*r->out.level_out) { + case -1: + /* this level is not like the others */ + return WERR_INVALID_LEVEL; + case 1: + attrs = attrs_1; + break; + case 2: + case 3: + attrs = attrs_2; + break; + default: + return WERR_INVALID_LEVEL; + } + + sites_dn = samdb_sites_dn(b_state->sam_ctx, mem_ctx); + if (!sites_dn) { + return WERR_DS_OBJ_NOT_FOUND; + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res, sites_dn, LDB_SCOPE_SUBTREE, attrs, + "(&(objectClass=server)(serverReference=*))"); + + if (ret) { + DEBUG(1, ("searching for servers in sites DN %s failed: %s\n", + ldb_dn_get_linearized(sites_dn), ldb_errstring(b_state->sam_ctx))); + return WERR_GEN_FAILURE; + } + + switch (*r->out.level_out) { + case 1: + ctr1 = &r->out.ctr->ctr1; + ctr1->count = res->count; + ctr1->array = talloc_zero_array(mem_ctx, + struct drsuapi_DsGetDCInfo1, + res->count); + for (i=0; i < res->count; i++) { + struct ldb_dn *domain_dn; + struct ldb_result *res_domain; + struct ldb_result *res_account; + struct ldb_dn *ntds_dn = ldb_dn_copy(mem_ctx, res->msgs[i]->dn); + + struct ldb_dn *ref_dn + = ldb_msg_find_attr_as_dn(b_state->sam_ctx, + mem_ctx, res->msgs[i], + "serverReference"); + + if (!ntds_dn || !ldb_dn_add_child_fmt(ntds_dn, "CN=NTDS Settings")) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_account, ref_dn, + LDB_SCOPE_BASE, attrs_account_1, + "(&(objectClass=computer)(userAccountControl:1.2.840.113556.1.4.803:=%u))", + UF_SERVER_TRUST_ACCOUNT); + if (ret == LDB_SUCCESS && res_account->count == 1) { + const char *errstr; + ctr1->array[i].dns_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "dNSHostName", NULL); + ctr1->array[i].netbios_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "cn", NULL); + ctr1->array[i].computer_dn + = ldb_dn_get_linearized(res_account->msgs[0]->dn); + + /* Determine if this is the PDC */ + ret = samdb_search_for_parent_domain(b_state->sam_ctx, + mem_ctx, res_account->msgs[0]->dn, + &domain_dn, &errstr); + + if (ret == LDB_SUCCESS) { + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_domain, domain_dn, + LDB_SCOPE_BASE, attrs_none, "fSMORoleOwner=%s", + ldb_dn_get_linearized(ntds_dn)); + if (ret) { + return WERR_GEN_FAILURE; + } + if (res_domain->count == 1) { + ctr1->array[i].is_pdc = true; + } + } + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for computer DN %s failed: %s\n", + ldb_dn_get_linearized(ref_dn), ldb_errstring(b_state->sam_ctx))); + } + + /* Look at server DN and extract site component */ + ctr1->array[i].site_name = result_site_name(res->msgs[i]->dn); + ctr1->array[i].server_dn = ldb_dn_get_linearized(res->msgs[i]->dn); + + + ctr1->array[i].is_enabled = true; + + } + break; + case 2: + ctr2 = &r->out.ctr->ctr2; + ctr2->count = res->count; + ctr2->array = talloc_zero_array(mem_ctx, + struct drsuapi_DsGetDCInfo2, + res->count); + for (i=0; i < res->count; i++) { + struct ldb_dn *domain_dn; + struct ldb_result *res_domain; + struct ldb_result *res_account; + struct ldb_dn *ntds_dn = ldb_dn_copy(mem_ctx, res->msgs[i]->dn); + struct ldb_result *res_ntds; + struct ldb_dn *site_dn = ldb_dn_copy(mem_ctx, res->msgs[i]->dn); + struct ldb_result *res_site; + struct ldb_dn *ref_dn + = ldb_msg_find_attr_as_dn(b_state->sam_ctx, + mem_ctx, res->msgs[i], + "serverReference"); + + if (!ntds_dn || !ldb_dn_add_child_fmt(ntds_dn, "CN=NTDS Settings")) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Format is cn=<NETBIOS name>,cn=Servers,cn=<site>,cn=sites.... */ + if (!site_dn || !ldb_dn_remove_child_components(site_dn, 2)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_ntds, ntds_dn, + LDB_SCOPE_BASE, attrs_ntds, "objectClass=nTDSDSA"); + if (ret == LDB_SUCCESS && res_ntds->count == 1) { + ctr2->array[i].is_gc + = (ldb_msg_find_attr_as_uint(res_ntds->msgs[0], "options", 0) & DS_NTDSDSA_OPT_IS_GC); + ctr2->array[i].ntds_guid + = samdb_result_guid(res_ntds->msgs[0], "objectGUID"); + ctr2->array[i].ntds_dn = ldb_dn_get_linearized(res_ntds->msgs[0]->dn); + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for NTDS DN %s failed: %s\n", + ldb_dn_get_linearized(ntds_dn), ldb_errstring(b_state->sam_ctx))); + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_site, site_dn, + LDB_SCOPE_BASE, attrs_site, "objectClass=site"); + if (ret == LDB_SUCCESS && res_site->count == 1) { + ctr2->array[i].site_guid + = samdb_result_guid(res_site->msgs[0], "objectGUID"); + ctr2->array[i].site_dn = ldb_dn_get_linearized(res_site->msgs[0]->dn); + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for site DN %s failed: %s\n", + ldb_dn_get_linearized(site_dn), ldb_errstring(b_state->sam_ctx))); + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_account, ref_dn, + LDB_SCOPE_BASE, attrs_account_2, "objectClass=computer"); + if (ret == LDB_SUCCESS && res_account->count == 1) { + const char *errstr; + ctr2->array[i].dns_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "dNSHostName", NULL); + ctr2->array[i].netbios_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "cn", NULL); + ctr2->array[i].computer_dn = ldb_dn_get_linearized(res_account->msgs[0]->dn); + ctr2->array[i].computer_guid + = samdb_result_guid(res_account->msgs[0], "objectGUID"); + + /* Determine if this is the PDC */ + ret = samdb_search_for_parent_domain(b_state->sam_ctx, + mem_ctx, res_account->msgs[0]->dn, + &domain_dn, &errstr); + + if (ret == LDB_SUCCESS) { + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_domain, domain_dn, + LDB_SCOPE_BASE, attrs_none, "fSMORoleOwner=%s", + ldb_dn_get_linearized(ntds_dn)); + if (ret == LDB_SUCCESS && res_domain->count == 1) { + ctr2->array[i].is_pdc = true; + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for domain DN %s failed: %s\n", + ldb_dn_get_linearized(domain_dn), ldb_errstring(b_state->sam_ctx))); + } + } + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for computer account DN %s failed: %s\n", + ldb_dn_get_linearized(ref_dn), ldb_errstring(b_state->sam_ctx))); + } + + /* Look at server DN and extract site component */ + ctr2->array[i].site_name = result_site_name(res->msgs[i]->dn); + ctr2->array[i].server_dn = ldb_dn_get_linearized(res->msgs[i]->dn); + ctr2->array[i].server_guid + = samdb_result_guid(res->msgs[i], "objectGUID"); + + ctr2->array[i].is_enabled = true; + + } + break; + case 3: + ctr3 = &r->out.ctr->ctr3; + ctr3->count = res->count; + ctr3->array = talloc_zero_array(mem_ctx, + struct drsuapi_DsGetDCInfo3, + res->count); + for (i=0; i<res->count; i++) { + struct ldb_dn *domain_dn; + struct ldb_result *res_domain; + struct ldb_result *res_account; + struct ldb_dn *ntds_dn = ldb_dn_copy(mem_ctx, res->msgs[i]->dn); + struct ldb_result *res_ntds; + struct ldb_dn *site_dn = ldb_dn_copy(mem_ctx, res->msgs[i]->dn); + struct ldb_result *res_site; + bool is_rodc; + struct ldb_dn *ref_dn + = ldb_msg_find_attr_as_dn(b_state->sam_ctx, + mem_ctx, res->msgs[i], + "serverReference"); + + if (!ntds_dn || !ldb_dn_add_child_fmt(ntds_dn, "CN=NTDS Settings")) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* Format is cn=<NETBIOS name>,cn=Servers,cn=<site>,cn=sites.... */ + if (!site_dn || !ldb_dn_remove_child_components(site_dn, 2)) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_ntds, ntds_dn, + LDB_SCOPE_BASE, attrs_ntds, "objectClass=nTDSDSA"); + if (ret == LDB_SUCCESS && res_ntds->count == 1) { + ctr3->array[i].is_gc + = (ldb_msg_find_attr_as_uint(res_ntds->msgs[0], "options", 0) & DS_NTDSDSA_OPT_IS_GC); + ctr3->array[i].ntds_guid + = samdb_result_guid(res_ntds->msgs[0], "objectGUID"); + ctr3->array[i].ntds_dn = ldb_dn_get_linearized(res_ntds->msgs[0]->dn); + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for NTDS DN %s failed: %s\n", + ldb_dn_get_linearized(ntds_dn), ldb_errstring(b_state->sam_ctx))); + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_site, site_dn, + LDB_SCOPE_BASE, attrs_site, "objectClass=site"); + if (ret == LDB_SUCCESS && res_site->count == 1) { + ctr3->array[i].site_guid + = samdb_result_guid(res_site->msgs[0], "objectGUID"); + ctr3->array[i].site_dn = ldb_dn_get_linearized(res_site->msgs[0]->dn); + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for site DN %s failed: %s\n", + ldb_dn_get_linearized(site_dn), ldb_errstring(b_state->sam_ctx))); + } + + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_account, ref_dn, + LDB_SCOPE_BASE, attrs_account_2, "objectClass=computer"); + if (ret == LDB_SUCCESS && res_account->count == 1) { + const char *errstr; + ctr3->array[i].dns_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "dNSHostName", NULL); + ctr3->array[i].netbios_name + = ldb_msg_find_attr_as_string(res_account->msgs[0], "cn", NULL); + ctr3->array[i].computer_dn = ldb_dn_get_linearized(res_account->msgs[0]->dn); + ctr3->array[i].computer_guid + = samdb_result_guid(res_account->msgs[0], "objectGUID"); + + /* Determine if this is the PDC */ + ret = samdb_search_for_parent_domain(b_state->sam_ctx, + mem_ctx, res_account->msgs[0]->dn, + &domain_dn, &errstr); + + if (ret == LDB_SUCCESS) { + ret = ldb_search(b_state->sam_ctx, mem_ctx, &res_domain, domain_dn, + LDB_SCOPE_BASE, attrs_none, "fSMORoleOwner=%s", + ldb_dn_get_linearized(ntds_dn)); + if (ret == LDB_SUCCESS && res_domain->count == 1) { + ctr3->array[i].is_pdc = true; + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for domain DN %s failed: %s\n", + ldb_dn_get_linearized(domain_dn), ldb_errstring(b_state->sam_ctx))); + } + } + } + if ((ret != LDB_SUCCESS) && (ret != LDB_ERR_NO_SUCH_OBJECT)) { + DEBUG(5, ("warning: searching for computer account DN %s failed: %s\n", + ldb_dn_get_linearized(ref_dn), ldb_errstring(b_state->sam_ctx))); + } + + /* Look at server DN and extract site component */ + ctr3->array[i].site_name = result_site_name(res->msgs[i]->dn); + ctr3->array[i].server_dn = ldb_dn_get_linearized(res->msgs[i]->dn); + ctr3->array[i].server_guid + = samdb_result_guid(res->msgs[i], "objectGUID"); + + ctr3->array[i].is_enabled = true; + + /* rodc? */ + ret = samdb_is_rodc(b_state->sam_ctx, &ctr3->array[i].server_guid, &is_rodc); + if (ret == LDB_SUCCESS && is_rodc) { + ctr3->array[i].is_rodc = true; + } + } + break; + default: + return WERR_INVALID_LEVEL; + } + return WERR_OK; +} + +/* + drsuapi_DsGetDomainControllerInfo +*/ +static WERROR dcesrv_drsuapi_DsGetDomainControllerInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetDomainControllerInfo *r) +{ + struct dcesrv_handle *h; + struct drsuapi_bind_state *b_state; + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + switch (r->in.level) { + case 1: + return dcesrv_drsuapi_DsGetDomainControllerInfo_1(b_state, mem_ctx, r); + } + + return WERR_INVALID_LEVEL; +} + + + +/* + drsuapi_DsExecuteKCC +*/ +static WERROR dcesrv_drsuapi_DsExecuteKCC(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsExecuteKCC *r) +{ + WERROR status; + uint32_t timeout; + status = drs_security_level_check(dce_call, "DsExecuteKCC", SECURITY_DOMAIN_CONTROLLER, NULL); + + if (!W_ERROR_IS_OK(status)) { + return status; + } + if (r->in.req->ctr1.taskID != 0) { + return WERR_INVALID_PARAMETER; + } + if (r->in.req->ctr1.flags & DRSUAPI_DS_EXECUTE_KCC_ASYNCHRONOUS_OPERATION) { + timeout = IRPC_CALL_TIMEOUT; + } else { + /* + * use Infinite time for timeout in case + * the caller made a sync call + */ + timeout = IRPC_CALL_TIMEOUT_INF; + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, r, NDR_DRSUAPI_DSEXECUTEKCC, + &ndr_table_drsuapi, "kccsrv", "DsExecuteKCC", + timeout); + DEBUG(10, ("Forwarded the call to execute the KCC\n")); + return WERR_OK; +} + + +/* + drsuapi_DsReplicaGetInfo +*/ +static WERROR dcesrv_drsuapi_DsReplicaGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaGetInfo *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + enum security_user_level level; + + if (!lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, NULL, + "drs", "disable_sec_check", false)) { + level = security_session_user_level(session_info, NULL); + if (level < SECURITY_DOMAIN_CONTROLLER) { + DEBUG(1,(__location__ ": Administrator access required for DsReplicaGetInfo\n")); + security_token_debug(DBGC_DRS_REPL, 2, + session_info->security_token); + return WERR_DS_DRA_ACCESS_DENIED; + } + } + + dcesrv_irpc_forward_rpc_call(dce_call, mem_ctx, r, NDR_DRSUAPI_DSREPLICAGETINFO, + &ndr_table_drsuapi, "kccsrv", "DsReplicaGetInfo", + IRPC_CALL_TIMEOUT); + + return WERR_OK; +} + + +/* + DRSUAPI_ADD_SID_HISTORY +*/ +static WERROR dcesrv_DRSUAPI_ADD_SID_HISTORY(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_ADD_SID_HISTORY *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_ADD_SID_HISTORY); +} + +/* + drsuapi_DsGetMemberships2 +*/ +static WERROR dcesrv_drsuapi_DsGetMemberships2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetMemberships2 *r) +{ + DRSUAPI_UNSUPPORTED(drsuapi_DsGetMemberships2); +} + +/* + DRSUAPI_REPLICA_VERIFY_OBJECTS +*/ +static WERROR dcesrv_DRSUAPI_REPLICA_VERIFY_OBJECTS(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_REPLICA_VERIFY_OBJECTS *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_REPLICA_VERIFY_OBJECTS); +} + + +/* + DRSUAPI_GET_OBJECT_EXISTENCE +*/ +static WERROR dcesrv_DRSUAPI_GET_OBJECT_EXISTENCE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct DRSUAPI_GET_OBJECT_EXISTENCE *r) +{ + DRSUAPI_UNSUPPORTED(DRSUAPI_GET_OBJECT_EXISTENCE); +} + + +/* + drsuapi_QuerySitesByCost +*/ +static WERROR dcesrv_drsuapi_QuerySitesByCost(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_QuerySitesByCost *r) +{ + DRSUAPI_UNSUPPORTED(drsuapi_QuerySitesByCost); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_drsuapi_s.c" diff --git a/source4/rpc_server/drsuapi/dcesrv_drsuapi.h b/source4/rpc_server/drsuapi/dcesrv_drsuapi.h new file mode 100644 index 0000000..18ac997 --- /dev/null +++ b/source4/rpc_server/drsuapi/dcesrv_drsuapi.h @@ -0,0 +1,84 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the drsuapi pipe + + Copyright (C) Stefan Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* + this type allows us to distinguish handle types +*/ +enum drsuapi_handle { + DRSUAPI_BIND_HANDLE, +}; + +/* + state asscoiated with a drsuapi_DsBind*() operation +*/ +struct drsuapi_bind_state { + struct ldb_context *sam_ctx; + struct ldb_context *sam_ctx_system; + struct GUID remote_bind_guid; + struct drsuapi_DsBindInfoCtr *remote_info; + struct drsuapi_DsBindInfoCtr *local_info; + struct drsuapi_getncchanges_state *getncchanges_full_repl_state; +}; + + +/* prototypes of internal functions */ +WERROR drsuapi_UpdateRefs(struct imessaging_context *msg_ctx, + struct tevent_context *event_ctx, + struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaUpdateRefsRequest1 *req); +WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaUpdateRefs *r); +WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChanges *r); +WERROR dcesrv_drsuapi_DsAddEntry(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsAddEntry *r); +WERROR dcesrv_drsuapi_DsWriteAccountSpn(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsWriteAccountSpn *r); + +char *drs_ObjectIdentifier_to_string(TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaObjectIdentifier *nc); + +int drsuapi_search_with_extended_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_res, + struct ldb_dn *basedn, + enum ldb_scope scope, + const char * const *attrs, + const char *filter); + +WERROR drs_security_level_check(struct dcesrv_call_state *dce_call, + const char* call, enum security_user_level minimum_level, + const struct dom_sid *domain_sid); + +void drsuapi_process_secret_attribute(struct drsuapi_DsReplicaAttribute *attr, + struct drsuapi_DsReplicaMetaData *meta_data); + +WERROR drs_security_access_check(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct drsuapi_DsReplicaObjectIdentifier *nc, + const char *ext_right); + +WERROR drs_security_access_check_nc_root(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct drsuapi_DsReplicaObjectIdentifier *nc, + const char *ext_right); diff --git a/source4/rpc_server/drsuapi/drsutil.c b/source4/rpc_server/drsuapi/drsutil.c new file mode 100644 index 0000000..48423bb --- /dev/null +++ b/source4/rpc_server/drsuapi/drsutil.c @@ -0,0 +1,237 @@ +/* + Unix SMB/CIFS implementation. + + useful utilities for the DRS server + + Copyright (C) Andrew Tridgell 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "param/param.h" +#include "auth/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +int drsuapi_search_with_extended_dn(struct ldb_context *ldb, + TALLOC_CTX *mem_ctx, + struct ldb_result **_res, + struct ldb_dn *basedn, + enum ldb_scope scope, + const char * const *attrs, + const char *filter) +{ + int ret; + struct ldb_request *req; + TALLOC_CTX *tmp_ctx; + struct ldb_result *res; + + tmp_ctx = talloc_new(mem_ctx); + + res = talloc_zero(tmp_ctx, struct ldb_result); + if (!res) { + return LDB_ERR_OPERATIONS_ERROR; + } + + ret = ldb_build_search_req(&req, ldb, tmp_ctx, + basedn, + scope, + filter, + attrs, + NULL, + res, + ldb_search_default_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + + ret = ldb_request_add_control(req, LDB_CONTROL_EXTENDED_DN_OID, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_RECYCLED_OID, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_request_add_control(req, LDB_CONTROL_REVEAL_INTERNALS, false, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + + ret = ldb_request(ldb, req); + if (ret == LDB_SUCCESS) { + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + } + + talloc_free(req); + *_res = talloc_steal(mem_ctx, res); + return ret; +} + +WERROR drs_security_level_check(struct dcesrv_call_state *dce_call, + const char* call, + enum security_user_level minimum_level, + const struct dom_sid *domain_sid) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + enum security_user_level level; + + if (lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, NULL, + "drs", "disable_sec_check", false)) { + return WERR_OK; + } + + level = security_session_user_level(session_info, domain_sid); + if (level < minimum_level) { + if (call) { + DEBUG(0,("%s refused for security token (level=%u)\n", + call, (unsigned)level)); + security_token_debug(DBGC_DRS_REPL, 2, session_info->security_token); + } + return WERR_DS_DRA_ACCESS_DENIED; + } + + return WERR_OK; +} + +void drsuapi_process_secret_attribute(struct drsuapi_DsReplicaAttribute *attr, + struct drsuapi_DsReplicaMetaData *meta_data) +{ + if (attr->value_ctr.num_values == 0) { + return; + } + + switch (attr->attid) { + case DRSUAPI_ATTID_dBCSPwd: + case DRSUAPI_ATTID_unicodePwd: + case DRSUAPI_ATTID_ntPwdHistory: + case DRSUAPI_ATTID_lmPwdHistory: + case DRSUAPI_ATTID_supplementalCredentials: + case DRSUAPI_ATTID_priorValue: + case DRSUAPI_ATTID_currentValue: + case DRSUAPI_ATTID_trustAuthOutgoing: + case DRSUAPI_ATTID_trustAuthIncoming: + case DRSUAPI_ATTID_initialAuthOutgoing: + case DRSUAPI_ATTID_initialAuthIncoming: + /*set value to null*/ + attr->value_ctr.num_values = 0; + talloc_free(attr->value_ctr.values); + attr->value_ctr.values = NULL; + meta_data->originating_change_time = 0; + return; + default: + return; + } +} + + +/* + check security on a DN, with logging of errors + */ +static WERROR drs_security_access_check_log(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct ldb_dn *dn, + const char *ext_right) +{ + int ret; + if (!dn) { + DEBUG(3,("drs_security_access_check: Null dn provided, access is denied for %s\n", + ext_right)); + return WERR_DS_DRA_ACCESS_DENIED; + } + ret = dsdb_check_access_on_dn(sam_ctx, + mem_ctx, + dn, + token, + SEC_ADS_CONTROL_ACCESS, + ext_right); + if (ret == LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS) { + DEBUG(3,("%s refused for security token on %s\n", + ext_right, ldb_dn_get_linearized(dn))); + security_token_debug(DBGC_DRS_REPL, 3, token); + return WERR_DS_DRA_ACCESS_DENIED; + } else if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to perform access check on %s: %s\n", ldb_dn_get_linearized(dn), ldb_strerror(ret))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + return WERR_OK; +} + + +/* + check security on a object identifier + */ +WERROR drs_security_access_check(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct drsuapi_DsReplicaObjectIdentifier *nc, + const char *ext_right) +{ + struct ldb_dn *dn; + WERROR werr; + int ret; + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, + sam_ctx, + nc, + &dn, + NULL); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_BAD_DN; + } + + werr = drs_security_access_check_log(sam_ctx, mem_ctx, token, dn, ext_right); + talloc_free(dn); + return werr; +} + +/* + check security on the NC root of a object identifier + */ +WERROR drs_security_access_check_nc_root(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct security_token *token, + struct drsuapi_DsReplicaObjectIdentifier *nc, + const char *ext_right) +{ + struct ldb_dn *nc_root; + WERROR werr; + int ret; + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, + sam_ctx, + nc, + NULL, + &nc_root); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_BAD_NC; + } + + werr = drs_security_access_check_log(sam_ctx, mem_ctx, token, nc_root, ext_right); + talloc_free(nc_root); + return werr; +} diff --git a/source4/rpc_server/drsuapi/getncchanges.c b/source4/rpc_server/drsuapi/getncchanges.c new file mode 100644 index 0000000..8864e79 --- /dev/null +++ b/source4/rpc_server/drsuapi/getncchanges.c @@ -0,0 +1,3861 @@ +/* + Unix SMB/CIFS implementation. + + implement the DSGetNCChanges call + + Copyright (C) Anatoliy Atanasov 2009 + Copyright (C) Andrew Tridgell 2009-2010 + Copyright (C) Andrew Bartlett 2010-2016 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "param/param.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" +#include "../libcli/drsuapi/drsuapi.h" +#include "lib/util/binsearch.h" +#include "lib/util/tsort.h" +#include "auth/session.h" +#include "dsdb/common/util.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "librpc/gen_ndr/ndr_misc.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +#define DRS_GUID_SIZE 16 +#define DEFAULT_MAX_OBJECTS 1000 +#define DEFAULT_MAX_LINKS 1500 + +/* + * state of a partially-completed replication cycle. This state persists + * over multiple calls to dcesrv_drsuapi_DsGetNCChanges() + */ +struct drsuapi_getncchanges_state { + struct db_context *obj_cache; + struct GUID *guids; + uint32_t num_records; + uint32_t num_processed; + struct ldb_dn *ncRoot_dn; + struct GUID ncRoot_guid; + bool is_schema_nc; + bool is_get_anc; + bool broken_samba_4_5_get_anc_emulation; + bool is_get_tgt; + bool send_nc_root_first; + uint64_t min_usn; + uint64_t max_usn; + struct drsuapi_DsReplicaHighWaterMark last_hwm; + struct ldb_dn *last_dn; + struct drsuapi_DsReplicaHighWaterMark final_hwm; + struct drsuapi_DsReplicaCursor2CtrEx *final_udv; + struct drsuapi_DsReplicaLinkedAttribute *la_list; + uint32_t la_count; + uint32_t la_idx; + + /* these are just used for debugging the replication's progress */ + uint32_t links_given; + uint32_t total_links; +}; + +/* We must keep the GUIDs in NDR form for sorting */ +struct la_for_sorting { + const struct drsuapi_DsReplicaLinkedAttribute *link; + uint8_t target_guid[DRS_GUID_SIZE]; + uint8_t source_guid[DRS_GUID_SIZE]; +}; + +/* + * stores the state for a chunk of replication data. This state information + * only exists for a single call to dcesrv_drsuapi_DsGetNCChanges() + */ +struct getncchanges_repl_chunk { + uint32_t max_objects; + uint32_t max_links; + uint32_t tgt_la_count; + bool immediate_link_sync; + time_t max_wait; + time_t start; + + /* stores the objects to be sent in this chunk */ + uint32_t object_count; + struct drsuapi_DsReplicaObjectListItemEx *object_list; + + /* the last object added to this replication chunk */ + struct drsuapi_DsReplicaObjectListItemEx *last_object; +}; + +static int drsuapi_DsReplicaHighWaterMark_cmp(const struct drsuapi_DsReplicaHighWaterMark *h1, + const struct drsuapi_DsReplicaHighWaterMark *h2) +{ + if (h1->highest_usn < h2->highest_usn) { + return -1; + } else if (h1->highest_usn > h2->highest_usn) { + return 1; + } else if (h1->tmp_highest_usn < h2->tmp_highest_usn) { + return -1; + } else if (h1->tmp_highest_usn > h2->tmp_highest_usn) { + return 1; + } else if (h1->reserved_usn < h2->reserved_usn) { + return -1; + } else if (h1->reserved_usn > h2->reserved_usn) { + return 1; + } + + return 0; +} + +/* + build a DsReplicaObjectIdentifier from a ldb msg + */ +static struct drsuapi_DsReplicaObjectIdentifier *get_object_identifier(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg) +{ + struct drsuapi_DsReplicaObjectIdentifier *identifier; + struct dom_sid *sid; + + identifier = talloc(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier); + if (identifier == NULL) { + return NULL; + } + + identifier->dn = ldb_dn_alloc_linearized(identifier, msg->dn); + identifier->guid = samdb_result_guid(msg, "objectGUID"); + + sid = samdb_result_dom_sid(identifier, msg, "objectSid"); + if (sid) { + identifier->sid = *sid; + } else { + ZERO_STRUCT(identifier->sid); + } + return identifier; +} + +static int udv_compare(const struct GUID *guid1, struct GUID guid2) +{ + return GUID_compare(guid1, &guid2); +} + +/* + see if we can filter an attribute using the uptodateness_vector + */ +static bool udv_filter(const struct drsuapi_DsReplicaCursorCtrEx *udv, + const struct GUID *originating_invocation_id, + uint64_t originating_usn) +{ + const struct drsuapi_DsReplicaCursor *c; + if (udv == NULL) return false; + BINARY_ARRAY_SEARCH(udv->cursors, udv->count, source_dsa_invocation_id, + originating_invocation_id, udv_compare, c); + if (c && originating_usn <= c->highest_usn) { + return true; + } + return false; +} + +static int uint32_t_cmp(uint32_t a1, uint32_t a2) +{ + if (a1 == a2) return 0; + return a1 > a2 ? 1 : -1; +} + +static int uint32_t_ptr_cmp(uint32_t *a1, uint32_t *a2) +{ + if (*a1 == *a2) return 0; + return *a1 > *a2 ? 1 : -1; +} + +static WERROR getncchanges_attid_remote_to_local(const struct dsdb_schema *schema, + const struct dsdb_syntax_ctx *ctx, + enum drsuapi_DsAttributeId remote_attid_as_enum, + enum drsuapi_DsAttributeId *local_attid_as_enum, + const struct dsdb_attribute **_sa) +{ + WERROR werr; + const struct dsdb_attribute *sa = NULL; + + if (ctx->pfm_remote == NULL) { + DEBUG(7, ("No prefixMap supplied, falling back to local prefixMap.\n")); + goto fail; + } + + werr = dsdb_attribute_drsuapi_remote_to_local(ctx, + remote_attid_as_enum, + local_attid_as_enum, + _sa); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(3, ("WARNING: Unable to resolve remote attid, falling back to local prefixMap.\n")); + goto fail; + } + + return werr; +fail: + + sa = dsdb_attribute_by_attributeID_id(schema, remote_attid_as_enum); + if (sa == NULL) { + return WERR_DS_DRA_SCHEMA_MISMATCH; + } else { + if (local_attid_as_enum != NULL) { + *local_attid_as_enum = sa->attributeID_id; + } + if (_sa != NULL) { + *_sa = sa; + } + return WERR_OK; + } +} + +static WERROR getncchanges_update_revealed_list(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct ldb_message **msg, + struct ldb_dn *object_dn, + const struct GUID *object_guid, + const struct dsdb_attribute *sa, + struct replPropertyMetaData1 *meta_data, + struct ldb_message *revealed_users) +{ + enum ndr_err_code ndr_err; + int ldb_err; + char *attr_str = NULL; + char *attr_hex = NULL; + DATA_BLOB attr_blob; + struct ldb_message_element *existing = NULL, *el_add = NULL, *el_del = NULL; + const char * const * secret_attributes = ldb_get_opaque(sam_ctx, "LDB_SECRET_ATTRIBUTE_LIST"); + + if (!ldb_attr_in_list(secret_attributes, + sa->lDAPDisplayName)) { + return WERR_OK; + } + + + ndr_err = ndr_push_struct_blob(&attr_blob, mem_ctx, meta_data, (ndr_push_flags_fn_t)ndr_push_replPropertyMetaData1); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + attr_hex = hex_encode_talloc(mem_ctx, attr_blob.data, attr_blob.length); + if (attr_hex == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + attr_str = talloc_asprintf(mem_ctx, "B:%zd:%s:%s", attr_blob.length*2, attr_hex, ldb_dn_get_linearized(object_dn)); + if (attr_str == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + existing = ldb_msg_find_element(revealed_users, "msDS-RevealedUsers"); + if (existing != NULL) { + /* Replace the old value (if one exists) with the current one */ + struct parsed_dn *link_dns; + struct parsed_dn *exact = NULL, *unused = NULL; + uint8_t attid[4]; + DATA_BLOB partial_meta; + + ldb_err = get_parsed_dns_trusted(mem_ctx, existing, &link_dns); + if (ldb_err != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* Construct a partial metadata blob to match on in the DB */ + SIVAL(attid, 0, sa->attributeID_id); + partial_meta.length = 4; + partial_meta.data = attid; + + /* Binary search using GUID and attribute id for uniqueness */ + ldb_err = parsed_dn_find(sam_ctx, link_dns, existing->num_values, + object_guid, object_dn, + partial_meta, 4, + &exact, &unused, + DSDB_SYNTAX_BINARY_DN, true); + + if (ldb_err != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed parsed DN find - %s\n", + ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (exact != NULL) { + /* Perform some verification of the blob */ + struct replPropertyMetaData1 existing_meta_data; + ndr_err = ndr_pull_struct_blob_all_noalloc(&exact->dsdb_dn->extra_part, + &existing_meta_data, + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaData1); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (existing_meta_data.attid == sa->attributeID_id) { + ldb_err = ldb_msg_add_empty(*msg, "msDS-RevealedUsers", LDB_FLAG_MOD_DELETE, &el_del); + if (ldb_err != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + el_del->values = talloc_array((*msg)->elements, struct ldb_val, 1); + if (el_del->values == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + el_del->values[0] = *exact->v; + el_del->num_values = 1; + } else { + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + } + + ldb_err = ldb_msg_add_empty(*msg, "msDS-RevealedUsers", LDB_FLAG_MOD_ADD, &el_add); + if (ldb_err != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + el_add->values = talloc_array((*msg)->elements, struct ldb_val, 1); + if (el_add->values == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + + } + + el_add->values[0] = data_blob_string_const(attr_str); + el_add->num_values = 1; + + return WERR_OK; +} + +/* + * This function filter attributes for build_object based on the + * uptodatenessvector and partial attribute set. + * + * Any secret attributes are forced here for REPL_SECRET, and audited at this + * point with msDS-RevealedUsers. + */ +static WERROR get_nc_changes_filter_attrs(struct drsuapi_DsReplicaObjectListItemEx *obj, + struct replPropertyMetaDataBlob md, + struct ldb_context *sam_ctx, + const struct ldb_message *msg, + const struct GUID *guid, + uint32_t *count, + uint64_t highest_usn, + const struct dsdb_attribute *rdn_sa, + struct dsdb_schema *schema, + struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector, + struct drsuapi_DsPartialAttributeSet *partial_attribute_set, + uint32_t *local_pas, + uint32_t *attids, + bool exop_secret, + struct ldb_message **revealed_list_msg, + struct ldb_message *existing_revealed_list_msg) +{ + uint32_t i, n; + WERROR werr; + for (n=i=0; i<md.ctr.ctr1.count; i++) { + const struct dsdb_attribute *sa; + bool force_attribute = false; + + /* if the attribute has not changed, and it is not the + instanceType then don't include it */ + if (md.ctr.ctr1.array[i].local_usn < highest_usn && + !exop_secret && + md.ctr.ctr1.array[i].attid != DRSUAPI_ATTID_instanceType) continue; + + /* don't include the rDN */ + if (md.ctr.ctr1.array[i].attid == rdn_sa->attributeID_id) continue; + + sa = dsdb_attribute_by_attributeID_id(schema, md.ctr.ctr1.array[i].attid); + if (!sa) { + DEBUG(0,(__location__ ": Failed to find attribute in schema for attrid %u mentioned in replPropertyMetaData of %s\n", + (unsigned int)md.ctr.ctr1.array[i].attid, + ldb_dn_get_linearized(msg->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (sa->linkID) { + struct ldb_message_element *el; + el = ldb_msg_find_element(msg, sa->lDAPDisplayName); + if (el && el->num_values && dsdb_dn_is_upgraded_link_val(&el->values[0])) { + /* don't send upgraded links inline */ + continue; + } + } + + if (exop_secret && + !dsdb_attr_in_rodc_fas(sa)) { + force_attribute = true; + DEBUG(4,("Forcing attribute %s in %s\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + werr = getncchanges_update_revealed_list(sam_ctx, obj, + revealed_list_msg, + msg->dn, guid, sa, + &md.ctr.ctr1.array[i], + existing_revealed_list_msg); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + /* filter by uptodateness_vector */ + if (md.ctr.ctr1.array[i].attid != DRSUAPI_ATTID_instanceType && + !force_attribute && + udv_filter(uptodateness_vector, + &md.ctr.ctr1.array[i].originating_invocation_id, + md.ctr.ctr1.array[i].originating_usn)) { + continue; + } + + /* filter by partial_attribute_set */ + if (partial_attribute_set && !force_attribute) { + uint32_t *result = NULL; + BINARY_ARRAY_SEARCH_V(local_pas, partial_attribute_set->num_attids, sa->attributeID_id, + uint32_t_cmp, result); + if (result == NULL) { + continue; + } + } + + obj->meta_data_ctr->meta_data[n].originating_change_time = md.ctr.ctr1.array[i].originating_change_time; + obj->meta_data_ctr->meta_data[n].version = md.ctr.ctr1.array[i].version; + obj->meta_data_ctr->meta_data[n].originating_invocation_id = md.ctr.ctr1.array[i].originating_invocation_id; + obj->meta_data_ctr->meta_data[n].originating_usn = md.ctr.ctr1.array[i].originating_usn; + attids[n] = md.ctr.ctr1.array[i].attid; + + n++; + } + + *count = n; + + return WERR_OK; +} + +/* + drsuapi_DsGetNCChanges for one object +*/ +static WERROR get_nc_changes_build_object(struct drsuapi_DsReplicaObjectListItemEx *obj, + const struct ldb_message *msg, + struct ldb_context *sam_ctx, + struct drsuapi_getncchanges_state *getnc_state, + struct dsdb_schema *schema, + DATA_BLOB *session_key, + struct drsuapi_DsGetNCChangesRequest10 *req10, + bool force_object_return, + uint32_t *local_pas, + struct ldb_dn *machine_dn, + const struct GUID *guid) +{ + const struct ldb_val *md_value; + uint32_t i, n; + struct replPropertyMetaDataBlob md; + uint32_t rid = 0; + int ldb_err; + enum ndr_err_code ndr_err; + uint32_t *attids; + const char *rdn; + const struct dsdb_attribute *rdn_sa; + uint64_t uSNChanged; + unsigned int instanceType; + struct dsdb_syntax_ctx syntax_ctx; + struct ldb_result *res = NULL; + WERROR werr; + int ret; + uint32_t replica_flags = req10->replica_flags; + struct drsuapi_DsPartialAttributeSet *partial_attribute_set = + req10->partial_attribute_set; + struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector = + req10->uptodateness_vector; + enum drsuapi_DsExtendedOperation extended_op = req10->extended_op; + bool is_schema_nc = getnc_state->is_schema_nc; + uint64_t highest_usn = getnc_state->min_usn; + + /* make dsdb sytanx context for conversions */ + dsdb_syntax_ctx_init(&syntax_ctx, sam_ctx, schema); + syntax_ctx.is_schema_nc = is_schema_nc; + + uSNChanged = ldb_msg_find_attr_as_uint64(msg, "uSNChanged", 0); + instanceType = ldb_msg_find_attr_as_uint(msg, "instanceType", 0); + if (instanceType & INSTANCE_TYPE_IS_NC_HEAD) { + obj->is_nc_prefix = true; + obj->parent_object_guid = NULL; + } else { + obj->is_nc_prefix = false; + obj->parent_object_guid = talloc(obj, struct GUID); + if (obj->parent_object_guid == NULL) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + *obj->parent_object_guid = samdb_result_guid(msg, "parentGUID"); + if (GUID_all_zero(obj->parent_object_guid)) { + DEBUG(0,(__location__ ": missing parentGUID for %s\n", + ldb_dn_get_linearized(msg->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + obj->next_object = NULL; + + md_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData"); + if (!md_value) { + /* nothing to send */ + return WERR_OK; + } + + if (instanceType & INSTANCE_TYPE_UNINSTANT) { + /* don't send uninstantiated objects */ + return WERR_OK; + } + + if (uSNChanged <= highest_usn) { + /* nothing to send */ + return WERR_OK; + } + + ndr_err = ndr_pull_struct_blob(md_value, obj, &md, + (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (md.version != 1) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + rdn = ldb_dn_get_rdn_name(msg->dn); + if (rdn == NULL) { + DEBUG(0,(__location__ ": No rDN for %s\n", ldb_dn_get_linearized(msg->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + rdn_sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn); + if (rdn_sa == NULL) { + DEBUG(0,(__location__ ": Can't find dsds_attribute for rDN %s in %s\n", + rdn, ldb_dn_get_linearized(msg->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + obj->meta_data_ctr = talloc(obj, struct drsuapi_DsReplicaMetaDataCtr); + attids = talloc_array(obj, uint32_t, md.ctr.ctr1.count); + + obj->object.identifier = get_object_identifier(obj, msg); + if (obj->object.identifier == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + dom_sid_split_rid(NULL, &obj->object.identifier->sid, NULL, &rid); + + obj->meta_data_ctr->meta_data = talloc_array(obj, struct drsuapi_DsReplicaMetaData, md.ctr.ctr1.count); + + if (extended_op == DRSUAPI_EXOP_REPL_SECRET) { + /* Get the existing revealed users for the destination */ + struct ldb_message *revealed_list_msg = NULL; + struct ldb_message *existing_revealed_list_msg = NULL; + const char *machine_attrs[] = { + "msDS-RevealedUsers", + NULL + }; + + revealed_list_msg = ldb_msg_new(sam_ctx); + if (revealed_list_msg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + revealed_list_msg->dn = machine_dn; + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction start - %s\n", + ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ldb_err = dsdb_search_dn(sam_ctx, obj, &res, machine_dn, machine_attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ldb_err != LDB_SUCCESS || res->count != 1) { + ldb_transaction_cancel(sam_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + existing_revealed_list_msg = res->msgs[0]; + + werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg, + guid, &n, highest_usn, + rdn_sa, schema, + uptodateness_vector, + partial_attribute_set, local_pas, + attids, + true, + &revealed_list_msg, + existing_revealed_list_msg); + if (!W_ERROR_IS_OK(werr)) { + ldb_transaction_cancel(sam_ctx); + return werr; + } + + if (revealed_list_msg != NULL) { + ret = ldb_modify(sam_ctx, revealed_list_msg); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to alter revealed links - %s\n", + ldb_errstring(sam_ctx))); + ldb_transaction_cancel(sam_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction commit - %s\n", + ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } else { + werr = get_nc_changes_filter_attrs(obj, md, sam_ctx, msg, guid, + &n, highest_usn, rdn_sa, + schema, uptodateness_vector, + partial_attribute_set, local_pas, + attids, + false, + NULL, + NULL); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + /* ignore it if its an empty change. Note that renames always + * change the 'name' attribute, so they won't be ignored by + * this + + * the force_object_return check is used to force an empty + * object return when we timeout in the getncchanges loop. + * This allows us to return an empty object, which keeps the + * client happy while preventing timeouts + */ + if (n == 0 || + (n == 1 && + attids[0] == DRSUAPI_ATTID_instanceType && + !force_object_return)) { + talloc_free(obj->meta_data_ctr); + obj->meta_data_ctr = NULL; + return WERR_OK; + } + + obj->meta_data_ctr->count = n; + + obj->object.flags = DRSUAPI_DS_REPLICA_OBJECT_FROM_MASTER; + obj->object.attribute_ctr.num_attributes = obj->meta_data_ctr->count; + obj->object.attribute_ctr.attributes = talloc_array(obj, struct drsuapi_DsReplicaAttribute, + obj->object.attribute_ctr.num_attributes); + if (obj->object.attribute_ctr.attributes == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* + * Note that the meta_data array and the attributes array must + * be the same size and in the same order + */ + for (i=0; i<obj->object.attribute_ctr.num_attributes; i++) { + struct ldb_message_element *el; + const struct dsdb_attribute *sa; + + sa = dsdb_attribute_by_attributeID_id(schema, attids[i]); + if (!sa) { + DEBUG(0,("Unable to find attributeID %u in schema\n", attids[i])); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + el = ldb_msg_find_element(msg, sa->lDAPDisplayName); + if (el == NULL) { + /* this happens for attributes that have been removed */ + DEBUG(5,("No element '%s' for attributeID %u in message\n", + sa->lDAPDisplayName, attids[i])); + ZERO_STRUCT(obj->object.attribute_ctr.attributes[i]); + obj->object.attribute_ctr.attributes[i].attid = + dsdb_attribute_get_attid(sa, syntax_ctx.is_schema_nc); + } else { + werr = sa->syntax->ldb_to_drsuapi(&syntax_ctx, sa, el, obj, + &obj->object.attribute_ctr.attributes[i]); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("Unable to convert %s on %s to DRS object - %s\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn), + win_errstr(werr))); + return werr; + } + /* if DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING is set + * check if attribute is secret and send a null value + */ + if (replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + drsuapi_process_secret_attribute(&obj->object.attribute_ctr.attributes[i], + &obj->meta_data_ctr->meta_data[i]); + } + /* some attributes needs to be encrypted + before being sent */ + werr = drsuapi_encrypt_attribute(obj, session_key, rid, + &obj->object.attribute_ctr.attributes[i]); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("Unable to encrypt %s on %s in DRS object - %s\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn), + win_errstr(werr))); + return werr; + } + } + if (attids[i] != obj->object.attribute_ctr.attributes[i].attid) { + DEBUG(0, ("Unable to replicate attribute %s on %s via DRS, incorrect attributeID: " + "0x%08x vs 0x%08x " + "Run dbcheck!\n", + sa->lDAPDisplayName, + ldb_dn_get_linearized(msg->dn), + attids[i], + obj->object.attribute_ctr.attributes[i].attid)); + return WERR_DS_DATABASE_ERROR; + } + } + + return WERR_OK; +} + +/* + add one linked attribute from an object to the list of linked + attributes in a getncchanges request + */ +static WERROR get_nc_changes_add_la(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + const struct dsdb_schema *schema, + const struct dsdb_attribute *sa, + const struct ldb_message *msg, + struct dsdb_dn *dsdb_dn, + struct drsuapi_DsReplicaLinkedAttribute **la_list, + uint32_t *la_count, + bool is_schema_nc) +{ + struct drsuapi_DsReplicaLinkedAttribute *la; + bool active; + NTSTATUS status; + WERROR werr; + + (*la_list) = talloc_realloc(mem_ctx, *la_list, struct drsuapi_DsReplicaLinkedAttribute, (*la_count)+1); + W_ERROR_HAVE_NO_MEMORY(*la_list); + + la = &(*la_list)[*la_count]; + + la->identifier = get_object_identifier(*la_list, msg); + W_ERROR_HAVE_NO_MEMORY(la->identifier); + + active = (dsdb_dn_rmd_flags(dsdb_dn->dn) & DSDB_RMD_FLAG_DELETED) == 0; + + if (!active) { + /* We have to check that the inactive link still point to an existing object */ + struct GUID guid; + struct ldb_dn *tdn; + int ret; + const char *v; + + v = ldb_msg_find_attr_as_string(msg, "isDeleted", "FALSE"); + if (strncmp(v, "TRUE", 4) == 0) { + /* + * Note: we skip the transmition of the deleted link even if the other part used to + * know about it because when we transmit the deletion of the object, the link will + * be deleted too due to deletion of object where link points and Windows do so. + */ + if (dsdb_functional_level(sam_ctx) >= DS_DOMAIN_FUNCTION_2008_R2) { + v = ldb_msg_find_attr_as_string(msg, "isRecycled", "FALSE"); + /* + * On Windows 2008R2 isRecycled is always present even if FL or DL are < FL 2K8R2 + * if it join an existing domain with deleted objets, it firsts impose to have a + * schema with the is-Recycled object and for all deleted objects it adds the isRecycled + * either during initial replication or after the getNCChanges. + * Behavior of samba has been changed to always have this attribute if it's present in the schema. + * + * So if FL <2K8R2 isRecycled might be here or not but we don't care, it's meaning less. + * If FL >=2K8R2 we are sure that this attribute will be here. + * For this kind of forest level we do not return the link if the object is recycled + * (isRecycled = true). + */ + if (strncmp(v, "TRUE", 4) == 0) { + DEBUG(2, (" object %s is recycled, not returning linked attribute !\n", + ldb_dn_get_linearized(msg->dn))); + return WERR_OK; + } + } else { + return WERR_OK; + } + } + status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid, "GUID"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " Unable to extract GUID in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + ret = dsdb_find_dn_by_guid(sam_ctx, mem_ctx, &guid, 0, &tdn); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(2, (" Search of guid %s returned 0 objects, skipping it !\n", + GUID_string(mem_ctx, &guid))); + return WERR_OK; + } else if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ " Search of guid %s failed with error code %d\n", + GUID_string(mem_ctx, &guid), + ret)); + return WERR_OK; + } + } + la->attid = dsdb_attribute_get_attid(sa, is_schema_nc); + la->flags = active?DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE:0; + + status = dsdb_get_extended_dn_uint32(dsdb_dn->dn, &la->meta_data.version, "RMD_VERSION"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_VERSION in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + status = dsdb_get_extended_dn_nttime(dsdb_dn->dn, &la->meta_data.originating_change_time, "RMD_CHANGETIME"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_CHANGETIME in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &la->meta_data.originating_invocation_id, "RMD_INVOCID"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_INVOCID in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + status = dsdb_get_extended_dn_uint64(dsdb_dn->dn, &la->meta_data.originating_usn, "RMD_ORIGINATING_USN"); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " No RMD_ORIGINATING_USN in linked attribute '%s' in '%s'\n", + sa->lDAPDisplayName, ldb_dn_get_linearized(msg->dn))); + return ntstatus_to_werror(status); + } + + status = dsdb_get_extended_dn_nttime(dsdb_dn->dn, &la->originating_add_time, "RMD_ADDTIME"); + if (!NT_STATUS_IS_OK(status)) { + /* this is possible for upgraded links */ + la->originating_add_time = la->meta_data.originating_change_time; + } + + werr = dsdb_dn_la_to_blob(sam_ctx, sa, schema, *la_list, dsdb_dn, &la->value.blob); + W_ERROR_NOT_OK_RETURN(werr); + + (*la_count)++; + return WERR_OK; +} + + +/* + add linked attributes from an object to the list of linked + attributes in a getncchanges request + */ +static WERROR get_nc_changes_add_links(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + bool is_schema_nc, + struct dsdb_schema *schema, + uint64_t highest_usn, + uint32_t replica_flags, + const struct ldb_message *msg, + struct drsuapi_DsReplicaLinkedAttribute **la_list, + uint32_t *la_count, + struct drsuapi_DsReplicaCursorCtrEx *uptodateness_vector) +{ + unsigned int i; + TALLOC_CTX *tmp_ctx = NULL; + uint64_t uSNChanged = ldb_msg_find_attr_as_uint64(msg, "uSNChanged", 0); + bool is_critical = ldb_msg_find_attr_as_bool(msg, "isCriticalSystemObject", false); + + if (replica_flags & DRSUAPI_DRS_CRITICAL_ONLY) { + if (!is_critical) { + return WERR_OK; + } + } + + if (uSNChanged <= highest_usn) { + return WERR_OK; + } + + tmp_ctx = talloc_new(mem_ctx); + if (tmp_ctx == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + for (i=0; i<msg->num_elements; i++) { + struct ldb_message_element *el = &msg->elements[i]; + const struct dsdb_attribute *sa; + unsigned int j; + + sa = dsdb_attribute_by_lDAPDisplayName(schema, el->name); + + if (!sa || sa->linkID == 0 || (sa->linkID & 1)) { + /* we only want forward links */ + continue; + } + + if (el->num_values && !dsdb_dn_is_upgraded_link_val(&el->values[0])) { + /* its an old style link, it will have been + * sent in the main replication data */ + continue; + } + + for (j=0; j<el->num_values; j++) { + struct dsdb_dn *dsdb_dn; + uint64_t local_usn; + uint64_t originating_usn; + NTSTATUS status, status2; + WERROR werr; + struct GUID originating_invocation_id; + + dsdb_dn = dsdb_dn_parse(tmp_ctx, sam_ctx, &el->values[j], sa->syntax->ldap_oid); + if (dsdb_dn == NULL) { + DEBUG(1,(__location__ ": Failed to parse DN for %s in %s\n", + el->name, ldb_dn_get_linearized(msg->dn))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + status = dsdb_get_extended_dn_uint64(dsdb_dn->dn, &local_usn, "RMD_LOCAL_USN"); + if (!NT_STATUS_IS_OK(status)) { + /* this can happen for attributes + given to us with old style meta + data */ + continue; + } + + if (local_usn > uSNChanged) { + DEBUG(1,(__location__ ": uSNChanged less than RMD_LOCAL_USN for %s on %s\n", + el->name, ldb_dn_get_linearized(msg->dn))); + talloc_free(tmp_ctx); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (local_usn <= highest_usn) { + continue; + } + + status = dsdb_get_extended_dn_guid(dsdb_dn->dn, + &originating_invocation_id, + "RMD_INVOCID"); + status2 = dsdb_get_extended_dn_uint64(dsdb_dn->dn, + &originating_usn, + "RMD_ORIGINATING_USN"); + + if (NT_STATUS_IS_OK(status) && NT_STATUS_IS_OK(status2)) { + if (udv_filter(uptodateness_vector, + &originating_invocation_id, + originating_usn)) { + continue; + } + } + + werr = get_nc_changes_add_la(mem_ctx, sam_ctx, schema, + sa, msg, dsdb_dn, la_list, + la_count, is_schema_nc); + if (!W_ERROR_IS_OK(werr)) { + talloc_free(tmp_ctx); + return werr; + } + } + } + + talloc_free(tmp_ctx); + return WERR_OK; +} + +/* + fill in the cursors return based on the replUpToDateVector for the ncRoot_dn + */ +static WERROR get_nc_changes_udv(struct ldb_context *sam_ctx, + struct ldb_dn *ncRoot_dn, + struct drsuapi_DsReplicaCursor2CtrEx *udv) +{ + int ret; + + udv->version = 2; + udv->reserved1 = 0; + udv->reserved2 = 0; + + ret = dsdb_load_udv_v2(sam_ctx, ncRoot_dn, udv, &udv->cursors, &udv->count); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to load UDV for %s - %s\n", + ldb_dn_get_linearized(ncRoot_dn), ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} + + +/* comparison function for linked attributes - see CompareLinks() in + * MS-DRSR section 4.1.10.5.17 */ +static int linked_attribute_compare(const struct la_for_sorting *la1, + const struct la_for_sorting *la2) +{ + int c; + c = memcmp(la1->source_guid, + la2->source_guid, sizeof(la2->source_guid)); + if (c != 0) { + return c; + } + + if (la1->link->attid != la2->link->attid) { + return la1->link->attid < la2->link->attid? -1:1; + } + + if ((la1->link->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE) != + (la2->link->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)) { + return (la1->link->flags & + DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)? 1:-1; + } + + return memcmp(la1->target_guid, + la2->target_guid, sizeof(la2->target_guid)); +} + +struct drsuapi_changed_objects { + struct ldb_dn *dn; + struct GUID guid; + uint64_t usn; +}; + + +/* + sort the objects we send by tree order (Samba 4.5 emulation) + */ +static int site_res_cmp_anc_order(struct drsuapi_changed_objects *m1, + struct drsuapi_changed_objects *m2, + struct drsuapi_getncchanges_state *getnc_state) +{ + return ldb_dn_compare(m2->dn, m1->dn); +} + +/* + sort the objects we send first by uSNChanged + */ +static int site_res_cmp_usn_order(struct drsuapi_changed_objects *m1, + struct drsuapi_changed_objects *m2, + struct drsuapi_getncchanges_state *getnc_state) +{ + if (m1->usn == m2->usn) { + return ldb_dn_compare(m2->dn, m1->dn); + } + + if (m1->usn < m2->usn) { + return -1; + } + + return 1; +} + + +/* + handle a DRSUAPI_EXOP_FSMO_RID_ALLOC call + */ +static WERROR getncchanges_rid_alloc(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct drsuapi_DsGetNCChangesCtr6 *ctr6, + struct ldb_dn **rid_manager_dn) +{ + struct ldb_dn *req_dn, *ntds_dn = NULL; + int ret; + struct ldb_context *ldb = b_state->sam_ctx; + struct ldb_result *ext_res; + struct dsdb_fsmo_extended_op *exop; + bool is_us; + + /* + steps: + - verify that the DN being asked for is the RID Manager DN + - verify that we are the RID Manager + */ + + /* work out who is the RID Manager, also return to caller */ + ret = samdb_rid_manager_dn(ldb, mem_ctx, rid_manager_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ ": Failed to find RID Manager object - %s\n", ldb_errstring(ldb))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, + ldb, + req10->naming_context, + &req_dn, + NULL); + if (ret != LDB_SUCCESS) { + DBG_ERR("RID Alloc request for invalid DN %s: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, req10->naming_context), + ldb_strerror(ret)); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; + return WERR_OK; + } + + if (ldb_dn_compare(req_dn, *rid_manager_dn) != 0) { + /* that isn't the RID Manager DN */ + DBG_ERR("RID Alloc request for wrong DN %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, + req10->naming_context)); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; + return WERR_OK; + } + + /* TODO: make sure ntds_dn is a valid nTDSDSA object */ + ret = dsdb_find_dn_by_guid(ldb, mem_ctx, &req10->destination_dsa_guid, 0, &ntds_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ ": Unable to find NTDS object for guid %s - %s\n", + GUID_string(mem_ctx, &req10->destination_dsa_guid), ldb_errstring(ldb))); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_UNKNOWN_CALLER; + return WERR_OK; + } + + /* find the DN of the RID Manager */ + ret = samdb_reference_dn_is_our_ntdsa(ldb, *rid_manager_dn, "fSMORoleOwner", &is_us); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to find fSMORoleOwner in RID Manager object\n")); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (!is_us) { + /* we're not the RID Manager - go away */ + DEBUG(0,(__location__ ": RID Alloc request when not RID Manager\n")); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; + return WERR_OK; + } + + exop = talloc(mem_ctx, struct dsdb_fsmo_extended_op); + W_ERROR_HAVE_NO_MEMORY(exop); + + exop->fsmo_info = req10->fsmo_info; + exop->destination_dsa_guid = req10->destination_dsa_guid; + + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction start - %s\n", + ldb_errstring(ldb))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = ldb_extended(ldb, DSDB_EXTENDED_ALLOCATE_RID_POOL, exop, &ext_res); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed extended allocation RID pool operation - %s\n", + ldb_errstring(ldb))); + ldb_transaction_cancel(ldb); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction commit - %s\n", + ldb_errstring(ldb))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + talloc_free(ext_res); + + DEBUG(2,("Allocated RID pool for server %s\n", + GUID_string(mem_ctx, &req10->destination_dsa_guid))); + + ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + + return WERR_OK; +} + +/* + handle a DRSUAPI_EXOP_REPL_SECRET call + */ +static WERROR getncchanges_repl_secret(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct dom_sid *user_sid, + struct drsuapi_DsGetNCChangesCtr6 *ctr6, + bool has_get_all_changes, + struct ldb_dn **machine_dn) +{ + struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req10->naming_context; + struct ldb_dn *obj_dn = NULL; + struct ldb_message *ntds_msg = NULL; + struct ldb_dn *ntds_dn = NULL, *server_dn = NULL; + struct ldb_dn *rodc_dn, *krbtgt_link_dn; + int ret; + const char *ntds_attrs[] = { NULL }; + const char *rodc_attrs[] = { "msDS-KrbTgtLink", + "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + NULL }; + const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; + struct ldb_result *rodc_res = NULL, *obj_res = NULL; + WERROR werr; + struct GUID_txt_buf guid_buf; + + DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_SECRET extended op on %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, ncRoot))); + + /* + * we need to work out if we will allow this DC to + * replicate the secrets for this object + * + * see 4.1.10.5.14 GetRevealSecretsPolicyForUser for details + * of this function + */ + + if (b_state->sam_ctx_system == NULL) { + /* this operation needs system level access */ + ctr6->extended_ret = DRSUAPI_EXOP_ERR_ACCESS_DENIED; + return WERR_DS_DRA_ACCESS_DENIED; + } + + /* + * Before we accept or deny, fetch the machine DN for the destination + * DSA GUID. + * + * If we are the RODC, we will check that this matches the SID. + */ + ret = samdb_get_ntds_obj_by_guid(mem_ctx, + b_state->sam_ctx_system, + &req10->destination_dsa_guid, + ntds_attrs, + &ntds_msg); + if (ret != LDB_SUCCESS) { + goto dest_dsa_error; + } + + ntds_dn = ntds_msg->dn; + + server_dn = ldb_dn_get_parent(mem_ctx, ntds_dn); + if (server_dn == NULL) { + goto failed; + } + + ret = samdb_reference_dn(b_state->sam_ctx_system, mem_ctx, server_dn, + "serverReference", machine_dn); + + if (ret != LDB_SUCCESS) { + goto dest_dsa_error; + } + + /* + * In MS-DRSR.pdf 5.99 IsGetNCChangesPermissionGranted + * + * The pseudo code indicate + * revealsecrets = true + * if IsRevealSecretRequest(msgIn) then + * if AccessCheckCAR(ncRoot, Ds-Replication-Get-Changes-All) = false + * then + * if (msgIn.ulExtendedOp = EXOP_REPL_SECRETS) then + * <... check if this account is ok to be replicated on this DC ...> + * <... and if not reveal secrets = no ...> + * else + * reveal secrets = false + * endif + * endif + * endif + * + * Which basically means that if you have GET_ALL_CHANGES rights (~== RWDC) + * then you can do EXOP_REPL_SECRETS + */ + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, + b_state->sam_ctx_system, + ncRoot, + &obj_dn, + NULL); + if (ret != LDB_SUCCESS) { + DBG_ERR("RevealSecretRequest for for invalid DN %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, ncRoot)); + goto failed; + } + + if (!ldb_dn_validate(obj_dn)) goto failed; + + if (has_get_all_changes) { + goto allowed; + } + + rodc_dn = ldb_dn_new_fmt(mem_ctx, b_state->sam_ctx_system, "<SID=%s>", + dom_sid_string(mem_ctx, user_sid)); + if (!ldb_dn_validate(rodc_dn)) goto failed; + + /* + * do the two searches we need + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID lists + * out of the extended DNs + */ + ret = dsdb_search_dn(b_state->sam_ctx_system, mem_ctx, &rodc_res, rodc_dn, rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS || rodc_res->count != 1) goto failed; + + ret = dsdb_search_dn(b_state->sam_ctx_system, mem_ctx, &obj_res, obj_dn, obj_attrs, 0); + if (ret != LDB_SUCCESS || obj_res->count != 1) goto failed; + + /* + * Must be an RODC account at this point, verify machine DN matches the + * SID account + */ + if (ldb_dn_compare(rodc_res->msgs[0]->dn, *machine_dn) != 0) { + goto denied; + } + + /* an RODC is allowed to get its own krbtgt account secrets */ + krbtgt_link_dn = samdb_result_dn(b_state->sam_ctx_system, mem_ctx, + rodc_res->msgs[0], "msDS-KrbTgtLink", NULL); + if (krbtgt_link_dn != NULL && + ldb_dn_compare(obj_dn, krbtgt_link_dn) == 0) { + goto allowed; + } + + werr = samdb_confirm_rodc_allowed_to_repl_to(b_state->sam_ctx_system, + user_sid, + rodc_res->msgs[0], + obj_res->msgs[0]); + + if (W_ERROR_IS_OK(werr)) { + goto allowed; + } + + /* default deny */ +denied: + DEBUG(2,(__location__ ": Denied single object with secret replication for %s by RODC %s\n", + ldb_dn_get_linearized(obj_dn), ldb_dn_get_linearized(rodc_res->msgs[0]->dn))); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_NONE; + return WERR_DS_DRA_SECRETS_DENIED; + +allowed: + DEBUG(2,(__location__ ": Allowed single object with secret replication for %s by %s %s\n", + ldb_dn_get_linearized(obj_dn), has_get_all_changes?"RWDC":"RODC", + ldb_dn_get_linearized(*machine_dn))); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + req10->highwatermark.highest_usn = 0; + return WERR_OK; + +failed: + DEBUG(2,(__location__ ": Failed single secret replication for %s by RODC %s\n", + ldb_dn_get_linearized(obj_dn), dom_sid_string(mem_ctx, user_sid))); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_NONE; + return WERR_DS_DRA_BAD_DN; + +dest_dsa_error: + DBG_WARNING("Failed secret replication for %s by RODC %s as dest_dsa_guid %s is invalid\n", + ldb_dn_get_linearized(obj_dn), + dom_sid_string(mem_ctx, user_sid), + GUID_buf_string(&req10->destination_dsa_guid, + &guid_buf)); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_NONE; + return WERR_DS_DRA_DB_ERROR; +} + +/* + handle a DRSUAPI_EXOP_REPL_OBJ call + */ +static WERROR getncchanges_repl_obj(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct dom_sid *user_sid, + struct drsuapi_DsGetNCChangesCtr6 *ctr6) +{ + struct drsuapi_DsReplicaObjectIdentifier *ncRoot = req10->naming_context; + + DEBUG(3,(__location__ ": DRSUAPI_EXOP_REPL_OBJ extended op on %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, ncRoot))); + + ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + return WERR_OK; +} + + +/* + handle DRSUAPI_EXOP_FSMO_REQ_ROLE, + DRSUAPI_EXOP_FSMO_RID_REQ_ROLE, + and DRSUAPI_EXOP_FSMO_REQ_PDC calls + */ +static WERROR getncchanges_change_master(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct drsuapi_DsGetNCChangesCtr6 *ctr6) +{ + struct ldb_dn *req_dn, *ntds_dn; + int ret; + unsigned int i; + struct ldb_context *ldb = b_state->sam_ctx; + struct ldb_message *msg; + bool is_us; + + /* + steps: + - verify that the client dn exists + - verify that we are the current master + */ + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, ldb, req10->naming_context, + &req_dn, NULL); + if (ret != LDB_SUCCESS) { + /* that is not a valid dn */ + DBG_ERR("FSMO role transfer request for invalid DN %s: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, req10->naming_context), + ldb_strerror(ret)); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; + return WERR_OK; + } + + /* find the DN of the current role owner */ + ret = samdb_reference_dn_is_our_ntdsa(ldb, req_dn, "fSMORoleOwner", &is_us); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to find fSMORoleOwner in RID Manager object\n")); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (!is_us) { + /* we're not the RID Manager or role owner - go away */ + DEBUG(0,(__location__ ": FSMO role or RID manager transfer owner request when not role owner\n")); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER; + return WERR_OK; + } + + /* change the current master */ + msg = ldb_msg_new(ldb); + W_ERROR_HAVE_NO_MEMORY(msg); + ret = drs_ObjectIdentifier_to_dn_and_nc_root(msg, ldb, req10->naming_context, + &msg->dn, NULL); + if (ret != LDB_SUCCESS) { + /* that is not a valid dn */ + DBG_ERR("FSMO role transfer request for invalid DN %s: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, req10->naming_context), + ldb_strerror(ret)); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_MISMATCH; + return WERR_OK; + } + + /* TODO: make sure ntds_dn is a valid nTDSDSA object */ + ret = dsdb_find_dn_by_guid(ldb, msg, &req10->destination_dsa_guid, 0, &ntds_dn); + if (ret != LDB_SUCCESS) { + DEBUG(0, (__location__ ": Unable to find NTDS object for guid %s - %s\n", + GUID_string(mem_ctx, &req10->destination_dsa_guid), ldb_errstring(ldb))); + talloc_free(msg); + ctr6->extended_ret = DRSUAPI_EXOP_ERR_UNKNOWN_CALLER; + return WERR_OK; + } + + ret = ldb_msg_add_string(msg, "fSMORoleOwner", ldb_dn_get_linearized(ntds_dn)); + if (ret != 0) { + talloc_free(msg); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + for (i=0;i<msg->num_elements;i++) { + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + } + + ret = ldb_transaction_start(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction start - %s\n", + ldb_errstring(ldb))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = ldb_modify(ldb, msg); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to change current owner - %s\n", + ldb_errstring(ldb))); + ldb_transaction_cancel(ldb); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = ldb_transaction_commit(ldb); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed transaction commit - %s\n", + ldb_errstring(ldb))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ctr6->extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + + return WERR_OK; +} + +/* + see if this getncchanges request includes a request to reveal secret information + */ +static WERROR dcesrv_drsuapi_is_reveal_secrets_request(struct drsuapi_bind_state *b_state, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct dsdb_schema_prefixmap *pfm_remote, + bool *is_secret_request) +{ + enum drsuapi_DsExtendedOperation exop; + uint32_t i; + struct dsdb_schema *schema; + struct dsdb_syntax_ctx syntax_ctx; + + *is_secret_request = true; + + exop = req10->extended_op; + + switch (exop) { + case DRSUAPI_EXOP_FSMO_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_REQ_PDC: + case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: + /* FSMO exops can reveal secrets */ + *is_secret_request = true; + return WERR_OK; + case DRSUAPI_EXOP_REPL_SECRET: + case DRSUAPI_EXOP_REPL_OBJ: + case DRSUAPI_EXOP_NONE: + break; + } + + if (req10->replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + *is_secret_request = false; + return WERR_OK; + } + + if (exop == DRSUAPI_EXOP_REPL_SECRET || + req10->partial_attribute_set == NULL) { + /* they want secrets */ + *is_secret_request = true; + return WERR_OK; + } + + schema = dsdb_get_schema(b_state->sam_ctx, NULL); + dsdb_syntax_ctx_init(&syntax_ctx, b_state->sam_ctx, schema); + syntax_ctx.pfm_remote = pfm_remote; + + /* check the attributes they asked for */ + for (i=0; i<req10->partial_attribute_set->num_attids; i++) { + const struct dsdb_attribute *sa; + WERROR werr = getncchanges_attid_remote_to_local(schema, + &syntax_ctx, + req10->partial_attribute_set->attids[i], + NULL, + &sa); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__": attid 0x%08X not found: %s\n", + req10->partial_attribute_set->attids[i], win_errstr(werr))); + return werr; + } + + if (!dsdb_attr_in_rodc_fas(sa)) { + *is_secret_request = true; + return WERR_OK; + } + } + + if (req10->partial_attribute_set_ex) { + /* check the extended attributes they asked for */ + for (i=0; i<req10->partial_attribute_set_ex->num_attids; i++) { + const struct dsdb_attribute *sa; + WERROR werr = getncchanges_attid_remote_to_local(schema, + &syntax_ctx, + req10->partial_attribute_set_ex->attids[i], + NULL, + &sa); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__": attid 0x%08X not found: %s\n", + req10->partial_attribute_set_ex->attids[i], win_errstr(werr))); + return werr; + } + + if (!dsdb_attr_in_rodc_fas(sa)) { + *is_secret_request = true; + return WERR_OK; + } + } + } + + *is_secret_request = false; + return WERR_OK; +} + +/* + see if this getncchanges request is only for attributes in the GC + partial attribute set + */ +static WERROR dcesrv_drsuapi_is_gc_pas_request(struct drsuapi_bind_state *b_state, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct dsdb_schema_prefixmap *pfm_remote, + bool *is_gc_pas_request) +{ + enum drsuapi_DsExtendedOperation exop; + uint32_t i; + struct dsdb_schema *schema; + struct dsdb_syntax_ctx syntax_ctx; + + exop = req10->extended_op; + + switch (exop) { + case DRSUAPI_EXOP_FSMO_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_REQ_PDC: + case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: + case DRSUAPI_EXOP_REPL_SECRET: + *is_gc_pas_request = false; + return WERR_OK; + case DRSUAPI_EXOP_REPL_OBJ: + case DRSUAPI_EXOP_NONE: + break; + } + + if (req10->partial_attribute_set == NULL) { + /* they want it all */ + *is_gc_pas_request = false; + return WERR_OK; + } + + schema = dsdb_get_schema(b_state->sam_ctx, NULL); + dsdb_syntax_ctx_init(&syntax_ctx, b_state->sam_ctx, schema); + syntax_ctx.pfm_remote = pfm_remote; + + /* check the attributes they asked for */ + for (i=0; i<req10->partial_attribute_set->num_attids; i++) { + const struct dsdb_attribute *sa; + WERROR werr = getncchanges_attid_remote_to_local(schema, + &syntax_ctx, + req10->partial_attribute_set->attids[i], + NULL, + &sa); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__": attid 0x%08X not found: %s\n", + req10->partial_attribute_set->attids[i], win_errstr(werr))); + return werr; + } + + if (!sa->isMemberOfPartialAttributeSet) { + *is_gc_pas_request = false; + return WERR_OK; + } + } + + if (req10->partial_attribute_set_ex) { + /* check the extended attributes they asked for */ + for (i=0; i<req10->partial_attribute_set_ex->num_attids; i++) { + const struct dsdb_attribute *sa; + WERROR werr = getncchanges_attid_remote_to_local(schema, + &syntax_ctx, + req10->partial_attribute_set_ex->attids[i], + NULL, + &sa); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__": attid 0x%08X not found: %s\n", + req10->partial_attribute_set_ex->attids[i], win_errstr(werr))); + return werr; + } + + if (!sa->isMemberOfPartialAttributeSet) { + *is_gc_pas_request = false; + return WERR_OK; + } + } + } + + *is_gc_pas_request = true; + return WERR_OK; +} + + +/* + map from req8 to req10 + */ +static struct drsuapi_DsGetNCChangesRequest10 * +getncchanges_map_req8(TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChangesRequest8 *req8) +{ + struct drsuapi_DsGetNCChangesRequest10 *req10 = talloc_zero(mem_ctx, + struct drsuapi_DsGetNCChangesRequest10); + if (req10 == NULL) { + return NULL; + } + + req10->destination_dsa_guid = req8->destination_dsa_guid; + req10->source_dsa_invocation_id = req8->source_dsa_invocation_id; + req10->naming_context = req8->naming_context; + req10->highwatermark = req8->highwatermark; + req10->uptodateness_vector = req8->uptodateness_vector; + req10->replica_flags = req8->replica_flags; + req10->max_object_count = req8->max_object_count; + req10->max_ndr_size = req8->max_ndr_size; + req10->extended_op = req8->extended_op; + req10->fsmo_info = req8->fsmo_info; + req10->partial_attribute_set = req8->partial_attribute_set; + req10->partial_attribute_set_ex = req8->partial_attribute_set_ex; + req10->mapping_ctr = req8->mapping_ctr; + + return req10; +} + +static const char *collect_objects_attrs[] = { "uSNChanged", + "objectGUID" , + NULL }; + +/** + * Collects object for normal replication cycle. + */ +static WERROR getncchanges_collect_objects(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_getncchanges_state *getnc_state, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct ldb_dn *search_dn, + const char *extra_filter, + struct ldb_result **search_res) +{ + int ret; + char* search_filter; + enum ldb_scope scope = LDB_SCOPE_SUBTREE; + bool critical_only = false; + + if (req10->replica_flags & DRSUAPI_DRS_CRITICAL_ONLY) { + critical_only = true; + } + + if (req10->extended_op == DRSUAPI_EXOP_REPL_OBJ || + req10->extended_op == DRSUAPI_EXOP_REPL_SECRET) { + scope = LDB_SCOPE_BASE; + critical_only = false; + } + + /* Construct response. */ + search_filter = talloc_asprintf(mem_ctx, + "(uSNChanged>=%llu)", + (unsigned long long)(getnc_state->min_usn+1)); + + if (extra_filter) { + search_filter = talloc_asprintf(mem_ctx, "(&%s(%s))", search_filter, extra_filter); + } + + if (critical_only) { + search_filter = talloc_asprintf(mem_ctx, + "(&%s(isCriticalSystemObject=TRUE))", + search_filter); + } + + if (req10->replica_flags & DRSUAPI_DRS_ASYNC_REP) { + scope = LDB_SCOPE_BASE; + } + + if (!search_dn) { + search_dn = getnc_state->ncRoot_dn; + } + + DEBUG(2,(__location__ ": getncchanges on %s using filter %s\n", + ldb_dn_get_linearized(getnc_state->ncRoot_dn), search_filter)); + ret = drsuapi_search_with_extended_dn(b_state->sam_ctx, getnc_state, search_res, + search_dn, scope, + collect_objects_attrs, + search_filter); + if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} + +/** + * Collects object for normal replication cycle. + */ +static WERROR getncchanges_collect_objects_exop(struct drsuapi_bind_state *b_state, + TALLOC_CTX *mem_ctx, + struct drsuapi_getncchanges_state *getnc_state, + struct drsuapi_DsGetNCChangesRequest10 *req10, + struct drsuapi_DsGetNCChangesCtr6 *ctr6, + struct ldb_dn *search_dn, + const char *extra_filter, + struct ldb_result **search_res) +{ + /* we have nothing to do in case of ex-op failure */ + if (ctr6->extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + + switch (req10->extended_op) { + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + { + int ret; + struct ldb_dn *ntds_dn = NULL; + struct ldb_dn *server_dn = NULL; + struct ldb_dn *machine_dn = NULL; + struct ldb_dn *rid_set_dn = NULL; + struct ldb_result *search_res2 = NULL; + struct ldb_result *search_res3 = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + /* get RID manager, RID set and server DN (in that order) */ + + /* This first search will get the RID Manager */ + ret = drsuapi_search_with_extended_dn(b_state->sam_ctx, frame, + search_res, + search_dn, LDB_SCOPE_BASE, + collect_objects_attrs, + NULL); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get RID Manager object %s - %s", + ldb_dn_get_linearized(search_dn), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if ((*search_res)->count != 1) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get RID Manager object %s - %u objects returned", + ldb_dn_get_linearized(search_dn), + (*search_res)->count)); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* Now extend it to the RID set */ + + /* Find the computer account DN for the destination + * dsa GUID specified */ + + ret = dsdb_find_dn_by_guid(b_state->sam_ctx, frame, + &req10->destination_dsa_guid, 0, + &ntds_dn); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Unable to find NTDS object for guid %s - %s\n", + GUID_string(frame, + &req10->destination_dsa_guid), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + server_dn = ldb_dn_get_parent(frame, ntds_dn); + if (!server_dn) { + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = samdb_reference_dn(b_state->sam_ctx, frame, server_dn, + "serverReference", &machine_dn); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to find serverReference in %s - %s", + ldb_dn_get_linearized(server_dn), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + ret = samdb_reference_dn(b_state->sam_ctx, frame, machine_dn, + "rIDSetReferences", &rid_set_dn); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to find rIDSetReferences in %s - %s", + ldb_dn_get_linearized(server_dn), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + + /* This first search will get the RID Manager, now get the RID set */ + ret = drsuapi_search_with_extended_dn(b_state->sam_ctx, frame, + &search_res2, + rid_set_dn, LDB_SCOPE_BASE, + collect_objects_attrs, + NULL); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get RID Set object %s - %s", + ldb_dn_get_linearized(rid_set_dn), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (search_res2->count != 1) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get RID Set object %s - %u objects returned", + ldb_dn_get_linearized(rid_set_dn), + search_res2->count)); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* Finally get the server DN */ + ret = drsuapi_search_with_extended_dn(b_state->sam_ctx, frame, + &search_res3, + machine_dn, LDB_SCOPE_BASE, + collect_objects_attrs, + NULL); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get server object %s - %s", + ldb_dn_get_linearized(server_dn), + ldb_errstring(b_state->sam_ctx))); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (search_res3->count != 1) { + DEBUG(1, ("DRSUAPI_EXOP_FSMO_RID_ALLOC: Failed to get server object %s - %u objects returned", + ldb_dn_get_linearized(server_dn), + search_res3->count)); + TALLOC_FREE(frame); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* Now extend the original search_res with these answers */ + (*search_res)->count = 3; + + (*search_res)->msgs = talloc_realloc(frame, (*search_res)->msgs, + struct ldb_message *, + (*search_res)->count); + if ((*search_res)->msgs == NULL) { + TALLOC_FREE(frame); + return WERR_NOT_ENOUGH_MEMORY; + } + + + talloc_steal(mem_ctx, *search_res); + (*search_res)->msgs[1] = + talloc_steal((*search_res)->msgs, search_res2->msgs[0]); + (*search_res)->msgs[2] = + talloc_steal((*search_res)->msgs, search_res3->msgs[0]); + + TALLOC_FREE(frame); + return WERR_OK; + } + default: + /* TODO: implement extended op specific collection + * of objects. Right now we just normal procedure + * for collecting objects */ + return getncchanges_collect_objects(b_state, + mem_ctx, + getnc_state, + req10, + search_dn, + extra_filter, + search_res); + } +} + +static void dcesrv_drsuapi_update_highwatermark(const struct ldb_message *msg, + uint64_t max_usn, + struct drsuapi_DsReplicaHighWaterMark *hwm) +{ + uint64_t uSN = ldb_msg_find_attr_as_uint64(msg, "uSNChanged", 0); + + if (uSN > max_usn) { + /* + * Only report the max_usn we had at the start + * of the replication cycle. + * + * If this object has changed lately we better + * let the destination dsa refetch the change. + * This is better than the risk of loosing some + * objects or linked attributes. + */ + return; + } + + if (uSN <= hwm->tmp_highest_usn) { + return; + } + + hwm->tmp_highest_usn = uSN; + hwm->reserved_usn = 0; +} + +/** + * Adds an object's GUID to the cache of objects already sent. + * This avoids us sending the same object multiple times when + * the GetNCChanges request uses a flag like GET_ANC. + */ +static WERROR dcesrv_drsuapi_obj_cache_add(struct db_context *obj_cache, + const struct GUID *guid) +{ + enum ndr_err_code ndr_err; + uint8_t guid_buf[DRS_GUID_SIZE] = { 0, }; + DATA_BLOB b = { + .data = guid_buf, + .length = sizeof(guid_buf), + }; + TDB_DATA key = { + .dptr = b.data, + .dsize = b.length, + }; + TDB_DATA val = { + .dptr = NULL, + .dsize = 0, + }; + NTSTATUS status; + + ndr_err = ndr_push_struct_into_fixed_blob(&b, guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + status = dbwrap_store(obj_cache, key, val, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + return WERR_OK; +} + +/** + * Checks if the object with the GUID specified already exists in the + * object cache, i.e. it's already been sent in a GetNCChanges response. + */ +static WERROR dcesrv_drsuapi_obj_cache_exists(struct db_context *obj_cache, + const struct GUID *guid) +{ + enum ndr_err_code ndr_err; + uint8_t guid_buf[DRS_GUID_SIZE] = { 0, }; + DATA_BLOB b = { + .data = guid_buf, + .length = sizeof(guid_buf), + }; + TDB_DATA key = { + .dptr = b.data, + .dsize = b.length, + }; + bool exists; + + ndr_err = ndr_push_struct_into_fixed_blob(&b, guid, + (ndr_push_flags_fn_t)ndr_push_GUID); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + exists = dbwrap_exists(obj_cache, key); + if (!exists) { + return WERR_OBJECT_NOT_FOUND; + } + + return WERR_OBJECT_NAME_EXISTS; +} + +/** + * Copies the la_list specified into a sorted array, ready to be sent in a + * GetNCChanges response. + */ +static WERROR getncchanges_get_sorted_array(const struct drsuapi_DsReplicaLinkedAttribute *la_list, + const uint32_t link_count, + struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + const struct dsdb_schema *schema, + struct la_for_sorting **ret_array) +{ + int j; + struct la_for_sorting *guid_array; + WERROR werr = WERR_OK; + + *ret_array = NULL; + guid_array = talloc_array(mem_ctx, struct la_for_sorting, link_count); + if (guid_array == NULL) { + DEBUG(0, ("Out of memory allocating %u linked attributes for sorting", link_count)); + return WERR_NOT_ENOUGH_MEMORY; + } + + for (j = 0; j < link_count; j++) { + + /* we need to get the target GUIDs to compare */ + struct dsdb_dn *dn; + const struct drsuapi_DsReplicaLinkedAttribute *la = &la_list[j]; + const struct dsdb_attribute *schema_attrib; + const struct ldb_val *target_guid; + DATA_BLOB source_guid; + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + + schema_attrib = dsdb_attribute_by_attributeID_id(schema, la->attid); + + werr = dsdb_dn_la_from_blob(sam_ctx, schema_attrib, schema, frame, la->value.blob, &dn); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Bad la blob in sort\n")); + TALLOC_FREE(frame); + return werr; + } + + /* Extract the target GUID in NDR form */ + target_guid = ldb_dn_get_extended_component(dn->dn, "GUID"); + if (target_guid == NULL + || target_guid->length != sizeof(guid_array[0].target_guid)) { + status = NT_STATUS_OBJECT_NAME_NOT_FOUND; + } else { + /* Repack the source GUID as NDR for sorting */ + status = GUID_to_ndr_blob(&la->identifier->guid, + frame, + &source_guid); + } + + if (!NT_STATUS_IS_OK(status) + || source_guid.length != sizeof(guid_array[0].source_guid)) { + DEBUG(0,(__location__ ": Bad la guid in sort\n")); + TALLOC_FREE(frame); + return ntstatus_to_werror(status); + } + + guid_array[j].link = &la_list[j]; + memcpy(guid_array[j].target_guid, target_guid->data, + sizeof(guid_array[j].target_guid)); + memcpy(guid_array[j].source_guid, source_guid.data, + sizeof(guid_array[j].source_guid)); + TALLOC_FREE(frame); + } + + TYPESAFE_QSORT(guid_array, link_count, linked_attribute_compare); + + *ret_array = guid_array; + + return werr; +} + + +/** + * Adds any ancestor/parent objects of the child_obj specified. + * This is needed when the GET_ANC flag is specified in the request. + * @param new_objs if parents are added, this gets updated to point to a chain + * of parent objects (with the parents first and the child last) + */ +static WERROR getncchanges_add_ancestors(const struct GUID *parent_object_guid, + struct ldb_dn *child_dn, + TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct drsuapi_getncchanges_state *getnc_state, + struct dsdb_schema *schema, + DATA_BLOB *session_key, + struct drsuapi_DsGetNCChangesRequest10 *req10, + uint32_t *local_pas, + struct ldb_dn *machine_dn, + struct drsuapi_DsReplicaObjectListItemEx **new_objs) +{ + int ret; + const struct GUID *next_anc_guid = NULL; + WERROR werr = WERR_OK; + static const char * const msg_attrs[] = { + "*", + "nTSecurityDescriptor", + "parentGUID", + "replPropertyMetaData", + DSDB_SECRET_ATTRIBUTES, + NULL }; + + next_anc_guid = parent_object_guid; + + while (next_anc_guid != NULL) { + struct drsuapi_DsReplicaObjectListItemEx *anc_obj = NULL; + struct ldb_message *anc_msg = NULL; + struct ldb_result *anc_res = NULL; + struct ldb_dn *anc_dn = NULL; + + /* + * For the GET_ANC case (but not the 'send NC root + * first' case), don't send an object twice. + * + * (If we've sent the object, then we've also sent all + * its parents as well) + */ + if (getnc_state->obj_cache) { + werr = dcesrv_drsuapi_obj_cache_exists(getnc_state->obj_cache, + next_anc_guid); + if (W_ERROR_EQUAL(werr, WERR_OBJECT_NAME_EXISTS)) { + return WERR_OK; + } + if (W_ERROR_IS_OK(werr)) { + return WERR_INTERNAL_ERROR; + } + if (!W_ERROR_EQUAL(werr, WERR_OBJECT_NOT_FOUND)) { + return werr; + } + } + + anc_obj = talloc_zero(mem_ctx, + struct drsuapi_DsReplicaObjectListItemEx); + if (anc_obj == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + anc_dn = ldb_dn_new_fmt(anc_obj, sam_ctx, "<GUID=%s>", + GUID_string(anc_obj, next_anc_guid)); + if (anc_dn == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = drsuapi_search_with_extended_dn(sam_ctx, anc_obj, + &anc_res, anc_dn, + LDB_SCOPE_BASE, + msg_attrs, NULL); + if (ret != LDB_SUCCESS) { + const char *anc_str = NULL; + const char *obj_str = NULL; + + anc_str = ldb_dn_get_extended_linearized(anc_obj, + anc_dn, + 1); + obj_str = ldb_dn_get_extended_linearized(anc_obj, + child_dn, + 1); + + DBG_ERR("getncchanges: failed to fetch ANC " + "DN %s for DN %s - %s\n", + anc_str, obj_str, ldb_errstring(sam_ctx)); + return WERR_DS_DRA_INCONSISTENT_DIT; + } + + anc_msg = anc_res->msgs[0]; + + werr = get_nc_changes_build_object(anc_obj, anc_msg, + sam_ctx, + getnc_state, + schema, session_key, + req10, + false, /* force_object_return */ + local_pas, + machine_dn, + next_anc_guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + /* + * Regardless of whether we actually use it or not, + * we add it to the cache so we don't look at it again + * + * The only time we are here without + * getnc_state->obj_cache is for the forced addition + * of the NC root to the start of the reply, this we + * want to add each and every call.. + */ + if (getnc_state->obj_cache) { + werr = dcesrv_drsuapi_obj_cache_add(getnc_state->obj_cache, + next_anc_guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + /* + * Any ancestors which are below the highwatermark + * or uptodateness_vector shouldn't be added, + * but we still look further up the + * tree for ones which have been changed recently. + */ + if (anc_obj->meta_data_ctr != NULL) { + + /* + * prepend the parent to the list so that the client-side + * adds the parent object before it adds the children + */ + anc_obj->next_object = *new_objs; + *new_objs = anc_obj; + } + + anc_msg = NULL; + TALLOC_FREE(anc_res); + TALLOC_FREE(anc_dn); + + /* + * We may need to resolve more parents... + */ + next_anc_guid = anc_obj->parent_object_guid; + } + return werr; +} + +/** + * Adds a list of new objects into the current chunk of replication data to send + */ +static void getncchanges_chunk_add_objects(struct getncchanges_repl_chunk *repl_chunk, + struct drsuapi_DsReplicaObjectListItemEx *obj_list) +{ + struct drsuapi_DsReplicaObjectListItemEx *obj; + + /* + * We track the last object added to the replication chunk, so just add + * the new object-list onto the end + */ + if (repl_chunk->object_list == NULL) { + repl_chunk->object_list = obj_list; + } else { + repl_chunk->last_object->next_object = obj_list; + } + + for (obj = obj_list; obj != NULL; obj = obj->next_object) { + repl_chunk->object_count += 1; + + /* + * Remember the last object in the response - we'll use this to + * link the next object(s) processed onto the existing list + */ + if (obj->next_object == NULL) { + repl_chunk->last_object = obj; + } + } +} + +/** + * Gets the object to send, packed into an RPC struct ready to send. This also + * adds the object to the object cache, and adds any ancestors (if needed). + * @param msg - DB search result for the object to add + * @param guid - GUID of the object to add + * @param ret_obj_list - returns the object ready to be sent (in a list, along + * with any ancestors that might be needed). NULL if nothing to send. + */ +static WERROR getncchanges_get_obj_to_send(const struct ldb_message *msg, + TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct drsuapi_getncchanges_state *getnc_state, + struct dsdb_schema *schema, + DATA_BLOB *session_key, + struct drsuapi_DsGetNCChangesRequest10 *req10, + bool force_object_return, + uint32_t *local_pas, + struct ldb_dn *machine_dn, + const struct GUID *guid, + struct drsuapi_DsReplicaObjectListItemEx **ret_obj_list) +{ + struct drsuapi_DsReplicaObjectListItemEx *obj; + WERROR werr; + + *ret_obj_list = NULL; + + obj = talloc_zero(mem_ctx, struct drsuapi_DsReplicaObjectListItemEx); + W_ERROR_HAVE_NO_MEMORY(obj); + + werr = get_nc_changes_build_object(obj, msg, sam_ctx, getnc_state, + schema, session_key, req10, + force_object_return, + local_pas, machine_dn, guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + /* + * The object may get filtered out by the UTDV's USN and not actually + * sent, in which case there's nothing more to do here + */ + if (obj->meta_data_ctr == NULL) { + TALLOC_FREE(obj); + return WERR_OK; + } + + if (getnc_state->obj_cache != NULL) { + werr = dcesrv_drsuapi_obj_cache_add(getnc_state->obj_cache, + guid); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + *ret_obj_list = obj; + + /* + * If required, also add any ancestors that the client may need to know + * about before it can resolve this object. These get prepended to the + * ret_obj_list so the client adds them first. + * + * We allow this to be disabled to permit testing of a + * client-side fallback for the broken behaviour in Samba 4.5 + * and earlier. + */ + if (getnc_state->is_get_anc + && !getnc_state->broken_samba_4_5_get_anc_emulation) { + werr = getncchanges_add_ancestors(obj->parent_object_guid, + msg->dn, mem_ctx, + sam_ctx, getnc_state, + schema, session_key, + req10, local_pas, + machine_dn, ret_obj_list); + } + + return werr; +} + +/** + * Returns the number of links that are waiting to be sent + */ +static uint32_t getncchanges_chunk_links_pending(struct getncchanges_repl_chunk *repl_chunk, + struct drsuapi_getncchanges_state *getnc_state) +{ + uint32_t links_to_send = 0; + + if (getnc_state->is_get_tgt) { + + /* + * when the GET_TGT flag is set, only include the linked + * attributes whose target object has already been checked + * (i.e. they're ready to send). + */ + if (repl_chunk->tgt_la_count > getnc_state->la_idx) { + links_to_send = (repl_chunk->tgt_la_count - + getnc_state->la_idx); + } + } else { + links_to_send = getnc_state->la_count - getnc_state->la_idx; + } + + return links_to_send; +} + +/** + * Returns the max number of links that will fit in the current replication chunk + */ +static uint32_t getncchanges_chunk_max_links(struct getncchanges_repl_chunk *repl_chunk) +{ + uint32_t max_links = 0; + + if (repl_chunk->max_links != DEFAULT_MAX_LINKS || + repl_chunk->max_objects != DEFAULT_MAX_OBJECTS) { + + /* + * We're using non-default settings, so don't try to adjust + * them, just trust the user has configured decent values + */ + max_links = repl_chunk->max_links; + + } else if (repl_chunk->max_links > repl_chunk->object_count) { + + /* + * This is just an approximate guess to avoid overfilling the + * replication chunk. It's the logic we've used historically. + * E.g. if we've already sent 1000 objects, then send 1000 fewer + * links. For comparison, the max that Windows seems to send is + * ~2700 links and ~250 objects (although this may vary based + * on timeouts) + */ + max_links = repl_chunk->max_links - repl_chunk->object_count; + } + + return max_links; +} + +/** + * Returns true if the current GetNCChanges() call has taken longer than its + * allotted time. This prevents the client from timing out. + */ +static bool getncchanges_chunk_timed_out(struct getncchanges_repl_chunk *repl_chunk) +{ + return (time(NULL) - repl_chunk->start > repl_chunk->max_wait); +} + +/** + * Returns true if the current chunk of replication data has reached the + * max_objects and/or max_links thresholds. + */ +static bool getncchanges_chunk_is_full(struct getncchanges_repl_chunk *repl_chunk, + struct drsuapi_getncchanges_state *getnc_state) +{ + bool chunk_full = false; + uint32_t links_to_send; + uint32_t chunk_limit; + + /* check if the current chunk is already full with objects */ + if (repl_chunk->object_count >= repl_chunk->max_objects) { + chunk_full = true; + + } else if (repl_chunk->object_count > 0 && + getncchanges_chunk_timed_out(repl_chunk)) { + + /* + * We've exceeded our allotted time building this chunk, + * and we have at least one object to send back to the client + */ + chunk_full = true; + + } else if (repl_chunk->immediate_link_sync) { + + /* check if the chunk is already full with links */ + links_to_send = getncchanges_chunk_links_pending(repl_chunk, + getnc_state); + + chunk_limit = getncchanges_chunk_max_links(repl_chunk); + + /* + * The chunk is full if we've got more links to send than will + * fit in one chunk + */ + if (links_to_send > 0 && chunk_limit <= links_to_send) { + chunk_full = true; + } + } + + return chunk_full; +} + +/** + * Goes through any new linked attributes and checks that the target object + * will be known to the client, i.e. we've already sent it in an replication + * chunk. If not, then it adds the target object to the current replication + * chunk. This is only done when the client specifies DRS_GET_TGT. + */ +static WERROR getncchanges_chunk_add_la_targets(struct getncchanges_repl_chunk *repl_chunk, + struct drsuapi_getncchanges_state *getnc_state, + uint32_t start_la_index, + TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct dsdb_schema *schema, + DATA_BLOB *session_key, + struct drsuapi_DsGetNCChangesRequest10 *req10, + uint32_t *local_pas, + struct ldb_dn *machine_dn) +{ + int ret; + uint32_t i; + uint32_t max_la_index; + uint32_t max_links; + uint32_t target_count = 0; + WERROR werr = WERR_OK; + static const char * const msg_attrs[] = { + "*", + "nTSecurityDescriptor", + "parentGUID", + "replPropertyMetaData", + DSDB_SECRET_ATTRIBUTES, + NULL }; + + /* + * A object can potentially link to thousands of targets. Only bother + * checking as many targets as will fit into the current response + */ + max_links = getncchanges_chunk_max_links(repl_chunk); + max_la_index = MIN(getnc_state->la_count, + start_la_index + max_links); + + /* loop through any linked attributes to check */ + for (i = start_la_index; + (i < max_la_index && + !getncchanges_chunk_is_full(repl_chunk, getnc_state)); + i++) { + + struct GUID target_guid; + struct drsuapi_DsReplicaObjectListItemEx *new_objs = NULL; + const struct drsuapi_DsReplicaLinkedAttribute *la; + struct ldb_result *msg_res; + struct ldb_dn *search_dn; + TALLOC_CTX *tmp_ctx; + struct dsdb_dn *dn; + const struct dsdb_attribute *schema_attrib; + NTSTATUS status; + bool same_nc; + + la = &getnc_state->la_list[i]; + tmp_ctx = talloc_new(mem_ctx); + + /* + * Track what linked attribute targets we've checked. We might + * not have time to check them all, so we should only send back + * the ones we've actually checked. + */ + repl_chunk->tgt_la_count = i + 1; + + /* get the GUID of the linked attribute's target object */ + schema_attrib = dsdb_attribute_by_attributeID_id(schema, + la->attid); + + werr = dsdb_dn_la_from_blob(sam_ctx, schema_attrib, schema, + tmp_ctx, la->value.blob, &dn); + + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Bad la blob\n")); + return werr; + } + + status = dsdb_get_extended_dn_guid(dn->dn, &target_guid, "GUID"); + + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * if the target isn't in the cache, then the client + * might not know about it, so send the target now + */ + werr = dcesrv_drsuapi_obj_cache_exists(getnc_state->obj_cache, + &target_guid); + + if (W_ERROR_EQUAL(werr, WERR_OBJECT_NAME_EXISTS)) { + + /* target already sent, nothing to do */ + TALLOC_FREE(tmp_ctx); + continue; + } + + same_nc = dsdb_objects_have_same_nc(sam_ctx, tmp_ctx, dn->dn, + getnc_state->ncRoot_dn); + + /* don't try to fetch target objects from another partition */ + if (!same_nc) { + TALLOC_FREE(tmp_ctx); + continue; + } + + search_dn = ldb_dn_new_fmt(tmp_ctx, sam_ctx, "<GUID=%s>", + GUID_string(tmp_ctx, &target_guid)); + W_ERROR_HAVE_NO_MEMORY(search_dn); + + ret = drsuapi_search_with_extended_dn(sam_ctx, tmp_ctx, + &msg_res, search_dn, + LDB_SCOPE_BASE, + msg_attrs, NULL); + + /* + * Don't fail the replication if we can't find the target. + * This could happen for a one-way linked attribute, if the + * target is deleted and then later expunged (thus, the source + * object can be left with a hanging link). Continue to send + * the the link (the client-side has already tried once with + * GET_TGT, so it should just end up ignoring it). + */ + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + DBG_WARNING("Encountered unknown link target DN %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn->dn, 1)); + TALLOC_FREE(tmp_ctx); + continue; + + } else if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to fetch link target DN %s - %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, dn->dn, 1), + ldb_errstring(sam_ctx)); + return WERR_DS_DRA_INCONSISTENT_DIT; + } + + /* + * Construct an object, ready to send (this will include + * the object's ancestors as well, if GET_ANC is set) + */ + werr = getncchanges_get_obj_to_send(msg_res->msgs[0], mem_ctx, + sam_ctx, getnc_state, + schema, session_key, req10, + false, local_pas, + machine_dn, &target_guid, + &new_objs); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (new_objs != NULL) { + target_count++; + getncchanges_chunk_add_objects(repl_chunk, new_objs); + } + TALLOC_FREE(tmp_ctx); + } + + if (target_count > 0) { + DEBUG(3, ("GET_TGT: checked %u link-attrs, added %u target objs\n", + i - start_la_index, target_count)); + } + + return WERR_OK; +} + +/** + * Creates a helper struct used for building a chunk of replication data, + * i.e. used over a single call to dcesrv_drsuapi_DsGetNCChanges(). + */ +static struct getncchanges_repl_chunk * getncchanges_chunk_new(TALLOC_CTX *mem_ctx, + struct dcesrv_call_state *dce_call, + struct drsuapi_DsGetNCChangesRequest10 *req10) +{ + struct getncchanges_repl_chunk *repl_chunk; + + repl_chunk = talloc_zero(mem_ctx, struct getncchanges_repl_chunk); + + repl_chunk->start = time(NULL); + + repl_chunk->max_objects = lpcfg_parm_int(dce_call->conn->dce_ctx->lp_ctx, NULL, + "drs", "max object sync", + DEFAULT_MAX_OBJECTS); + + /* + * The client control here only applies in normal replication, not extended + * operations, which return a fixed set, even if the caller + * sets max_object_count == 0 + */ + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + + /* + * use this to force single objects at a time, which is useful + * for working out what object is giving problems + */ + if (req10->max_object_count < repl_chunk->max_objects) { + repl_chunk->max_objects = req10->max_object_count; + } + } + + repl_chunk->max_links = + lpcfg_parm_int(dce_call->conn->dce_ctx->lp_ctx, NULL, + "drs", "max link sync", + DEFAULT_MAX_LINKS); + + repl_chunk->immediate_link_sync = + lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, NULL, + "drs", "immediate link sync", false); + + /* + * Maximum time that we can spend in a getncchanges + * in order to avoid timeout of the other part. + * 10 seconds by default. + */ + repl_chunk->max_wait = lpcfg_parm_int(dce_call->conn->dce_ctx->lp_ctx, + NULL, "drs", "max work time", 10); + + return repl_chunk; +} + +/* + drsuapi_DsGetNCChanges + + see MS-DRSR 4.1.10.5.2 for basic logic of this function +*/ +WERROR dcesrv_drsuapi_DsGetNCChanges(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsGetNCChanges *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + struct drsuapi_DsReplicaObjectIdentifier *untrusted_ncRoot; + int ret; + uint32_t i, k; + struct dsdb_schema *schema; + struct drsuapi_DsReplicaOIDMapping_Ctr *ctr; + struct getncchanges_repl_chunk *repl_chunk; + NTSTATUS status; + DATA_BLOB session_key; + WERROR werr; + struct dcesrv_handle *h; + struct drsuapi_bind_state *b_state; + struct drsuapi_getncchanges_state *getnc_state = NULL; + struct drsuapi_DsGetNCChangesRequest10 *req10; + uint32_t options; + uint32_t link_count = 0; + struct ldb_dn *search_dn = NULL; + bool am_rodc; + enum security_user_level security_level; + struct ldb_context *sam_ctx; + struct dom_sid *user_sid; + bool is_secret_request; + bool is_gc_pas_request; + struct drsuapi_changed_objects *changes; + bool has_get_all_changes = false; + struct GUID invocation_id; + static const struct drsuapi_DsReplicaLinkedAttribute no_linked_attr; + struct dsdb_schema_prefixmap *pfm_remote = NULL; + bool full = true; + uint32_t *local_pas = NULL; + struct ldb_dn *machine_dn = NULL; /* Only used for REPL SECRET EXOP */ + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + /* sam_ctx_system is not present for non-administrator users */ + sam_ctx = b_state->sam_ctx_system?b_state->sam_ctx_system:b_state->sam_ctx; + + invocation_id = *(samdb_ntds_invocation_id(sam_ctx)); + + *r->out.level_out = 6; + + r->out.ctr->ctr6.linked_attributes_count = 0; + r->out.ctr->ctr6.linked_attributes = discard_const_p(struct drsuapi_DsReplicaLinkedAttribute, &no_linked_attr); + + r->out.ctr->ctr6.object_count = 0; + r->out.ctr->ctr6.nc_object_count = 0; + r->out.ctr->ctr6.more_data = false; + r->out.ctr->ctr6.uptodateness_vector = NULL; + r->out.ctr->ctr6.source_dsa_guid = *(samdb_ntds_objectGUID(sam_ctx)); + r->out.ctr->ctr6.source_dsa_invocation_id = *(samdb_ntds_invocation_id(sam_ctx)); + r->out.ctr->ctr6.first_object = NULL; + + /* Check request revision. + */ + switch (r->in.level) { + case 8: + req10 = getncchanges_map_req8(mem_ctx, &r->in.req->req8); + if (req10 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + break; + case 10: + req10 = &r->in.req->req10; + break; + default: + DEBUG(0,(__location__ ": Request for DsGetNCChanges with unsupported level %u\n", + r->in.level)); + return WERR_REVISION_MISMATCH; + } + + repl_chunk = getncchanges_chunk_new(mem_ctx, dce_call, req10); + + if (repl_chunk == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* a RODC doesn't allow for any replication */ + ret = samdb_rodc(sam_ctx, &am_rodc); + if (ret == LDB_SUCCESS && am_rodc) { + DEBUG(0,(__location__ ": DsGetNCChanges attempt on RODC\n")); + return WERR_DS_DRA_SOURCE_DISABLED; + } + + /* + * Help our tests pass by pre-checking the + * destination_dsa_guid before the NC permissions. Info on + * valid DSA GUIDs is not sensitive so this isn't a leak + */ + switch (req10->extended_op) { + case DRSUAPI_EXOP_FSMO_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: + case DRSUAPI_EXOP_FSMO_REQ_PDC: + case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: + { + const char *attrs[] = { NULL }; + + ret = samdb_get_ntds_obj_by_guid(mem_ctx, + sam_ctx, + &req10->destination_dsa_guid, + attrs, + NULL); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + /* + * Error out with an EXOP error but success at + * the top level return value + */ + r->out.ctr->ctr6.extended_ret = DRSUAPI_EXOP_ERR_UNKNOWN_CALLER; + return WERR_OK; + } else if (ret != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + break; + } + case DRSUAPI_EXOP_REPL_SECRET: + case DRSUAPI_EXOP_REPL_OBJ: + case DRSUAPI_EXOP_NONE: + break; + } + + /* Perform access checks. */ + untrusted_ncRoot = req10->naming_context; + if (untrusted_ncRoot == NULL) { + DEBUG(0,(__location__ ": Request for DsGetNCChanges with no NC\n")); + return WERR_DS_DRA_INVALID_PARAMETER; + } + + if (samdb_ntds_options(sam_ctx, &options) != LDB_SUCCESS) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if ((options & DS_NTDSDSA_OPT_DISABLE_OUTBOUND_REPL) && + !(req10->replica_flags & DRSUAPI_DRS_SYNC_FORCED)) { + return WERR_DS_DRA_SOURCE_DISABLED; + } + + user_sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + /* + * all clients must have GUID_DRS_GET_CHANGES. This finds the + * actual NC root of the given value and checks that, allowing + * REPL_OBJ to work safely + */ + werr = drs_security_access_check_nc_root(sam_ctx, + mem_ctx, + session_info->security_token, + req10->naming_context, + GUID_DRS_GET_CHANGES); + + if (W_ERROR_EQUAL(werr, WERR_DS_DRA_BAD_NC)) { + /* + * These extended operations need a different error if + * the supplied DN can't be found + */ + switch (req10->extended_op) { + case DRSUAPI_EXOP_REPL_OBJ: + case DRSUAPI_EXOP_REPL_SECRET: + return WERR_DS_DRA_BAD_DN; + default: + return werr; + } + } + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (dsdb_functional_level(sam_ctx) >= DS_DOMAIN_FUNCTION_2008) { + full = req10->partial_attribute_set == NULL && + req10->partial_attribute_set_ex == NULL; + } else { + full = (options & DRSUAPI_DRS_WRIT_REP) != 0; + } + + werr = dsdb_schema_pfm_from_drsuapi_pfm(&req10->mapping_ctr, true, + mem_ctx, &pfm_remote, NULL); + + /* We were supplied a partial attribute set, without the prefix map! */ + if (!full && !W_ERROR_IS_OK(werr)) { + if (req10->mapping_ctr.num_mappings == 0) { + /* + * Despite the fact MS-DRSR specifies that this shouldn't + * happen, Windows RODCs will in fact not provide a prefixMap. + */ + DEBUG(5,(__location__ ": Failed to provide a remote prefixMap," + " falling back to local prefixMap\n")); + } else { + DEBUG(0,(__location__ ": Failed to decode remote prefixMap: %s\n", + win_errstr(werr))); + return werr; + } + } + + /* allowed if the GC PAS and client has + GUID_DRS_GET_FILTERED_ATTRIBUTES */ + werr = dcesrv_drsuapi_is_gc_pas_request(b_state, req10, pfm_remote, &is_gc_pas_request); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (is_gc_pas_request) { + werr = drs_security_access_check_nc_root(sam_ctx, + mem_ctx, + session_info->security_token, + req10->naming_context, + GUID_DRS_GET_FILTERED_ATTRIBUTES); + if (W_ERROR_IS_OK(werr)) { + goto allowed; + } + } + + werr = dcesrv_drsuapi_is_reveal_secrets_request(b_state, req10, + pfm_remote, + &is_secret_request); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + if (is_secret_request) { + werr = drs_security_access_check_nc_root(sam_ctx, + mem_ctx, + session_info->security_token, + req10->naming_context, + GUID_DRS_GET_ALL_CHANGES); + if (!W_ERROR_IS_OK(werr)) { + /* Only bail if this is not a EXOP_REPL_SECRET */ + if (req10->extended_op != DRSUAPI_EXOP_REPL_SECRET) { + return werr; + } + } else { + has_get_all_changes = true; + } + } + +allowed: + /* for non-administrator replications, check that they have + given the correct source_dsa_invocation_id */ + security_level = security_session_user_level(session_info, + samdb_domain_sid(sam_ctx)); + if (security_level == SECURITY_RO_DOMAIN_CONTROLLER) { + if (req10->replica_flags & DRSUAPI_DRS_WRIT_REP) { + /* we rely on this flag being unset for RODC requests */ + req10->replica_flags &= ~DRSUAPI_DRS_WRIT_REP; + } + } + + if (req10->replica_flags & DRSUAPI_DRS_FULL_SYNC_PACKET) { + /* Ignore the _in_ uptpdateness vector*/ + req10->uptodateness_vector = NULL; + } + + if (GUID_all_zero(&req10->source_dsa_invocation_id)) { + req10->source_dsa_invocation_id = invocation_id; + } + + if (!GUID_equal(&req10->source_dsa_invocation_id, &invocation_id)) { + /* + * The given highwatermark is only valid relative to the + * specified source_dsa_invocation_id. + */ + ZERO_STRUCT(req10->highwatermark); + } + + /* + * An extended operation is "special single-response cycle" + * per MS-DRSR 4.1.10.1.1 "Start and Finish" so we don't need + * to guess if this is a continuation of any long-term + * state. + * + * Otherwise, maintain (including marking as stale, which is + * what the below is for) the replication state. + * + * Note that point 5 "The server implementation MAY declare + * the supplied values ... as too stale to use." would allow + * resetting the state at almost any point, Microsoft Azure AD + * Connect will switch back and forth between a REPL_OBJ and a + * full replication, so we must not reset the statue during + * extended operations. + */ + if (req10->extended_op == DRSUAPI_EXOP_NONE && + b_state->getncchanges_full_repl_state != NULL) { + /* + * Knowing that this is not an extended operation, we + * can access (and validate) the full replication + * state + */ + getnc_state = b_state->getncchanges_full_repl_state; + } + + /* see if a previous replication has been abandoned */ + if (getnc_state != NULL) { + struct ldb_dn *new_dn; + ret = drs_ObjectIdentifier_to_dn_and_nc_root(getnc_state, + sam_ctx, + untrusted_ncRoot, + &new_dn, + NULL); + if (ret != LDB_SUCCESS) { + /* + * This can't fail as we have done this above + * implicitly but not got the DN out, but + * print a good error message regardless just + * in case. + */ + DBG_ERR("Bad DN '%s' as Naming Context for GetNCChanges: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, untrusted_ncRoot), + ldb_strerror(ret)); + return WERR_DS_DRA_INVALID_PARAMETER; + } + if (ldb_dn_compare(new_dn, getnc_state->ncRoot_dn) != 0) { + DEBUG(0,(__location__ ": DsGetNCChanges 2nd replication on different DN %s %s (last_dn %s)\n", + ldb_dn_get_linearized(new_dn), + ldb_dn_get_linearized(getnc_state->ncRoot_dn), + ldb_dn_get_linearized(getnc_state->last_dn))); + TALLOC_FREE(getnc_state); + b_state->getncchanges_full_repl_state = NULL; + } + } + + if (getnc_state != NULL) { + ret = drsuapi_DsReplicaHighWaterMark_cmp(&getnc_state->last_hwm, + &req10->highwatermark); + if (ret != 0) { + DEBUG(0,(__location__ ": DsGetNCChanges 2nd replication " + "on DN %s %s highwatermark (last_dn %s)\n", + ldb_dn_get_linearized(getnc_state->ncRoot_dn), + (ret > 0) ? "older" : "newer", + ldb_dn_get_linearized(getnc_state->last_dn))); + TALLOC_FREE(getnc_state); + b_state->getncchanges_full_repl_state = NULL; + } + } + + /* + * This is either a new replication cycle, or an extended + * operation. A new cycle is triggered above by the + * TALLOC_FREE() which sets getnc_state to NULL. + */ + if (getnc_state == NULL) { + struct ldb_result *res = NULL; + const char *attrs[] = { + "instanceType", + "objectGuID", + NULL + }; + uint32_t nc_instanceType; + struct ldb_dn *ncRoot_dn; + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, + sam_ctx, + untrusted_ncRoot, + &ncRoot_dn, + NULL); + if (ret != LDB_SUCCESS) { + DBG_ERR("Bad DN '%s' as Naming Context or EXOP DN for GetNCChanges: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, untrusted_ncRoot), + ldb_strerror(ret)); + return WERR_DS_DRA_BAD_DN; + } + + ret = dsdb_search_dn(sam_ctx, mem_ctx, &res, + ncRoot_dn, attrs, + DSDB_SEARCH_SHOW_DELETED | + DSDB_SEARCH_SHOW_RECYCLED); + if (ret != LDB_SUCCESS) { + DBG_WARNING("Failed to find ncRoot_dn %s\n", + ldb_dn_get_linearized(ncRoot_dn)); + return WERR_DS_DRA_BAD_DN; + } + nc_instanceType = ldb_msg_find_attr_as_int(res->msgs[0], + "instanceType", + 0); + + if (req10->extended_op != DRSUAPI_EXOP_NONE) { + r->out.ctr->ctr6.extended_ret = DRSUAPI_EXOP_ERR_SUCCESS; + } + + /* + * This is the first replication cycle and it is + * a good place to handle extended operations + * + * FIXME: we don't fully support extended operations yet + */ + switch (req10->extended_op) { + case DRSUAPI_EXOP_NONE: + if ((nc_instanceType & INSTANCE_TYPE_IS_NC_HEAD) == 0) { + const char *dn_str + = ldb_dn_get_linearized(ncRoot_dn); + + DBG_NOTICE("Rejecting full replication on " + "not NC %s", dn_str); + + return WERR_DS_CANT_FIND_EXPECTED_NC; + } + + break; + case DRSUAPI_EXOP_FSMO_RID_ALLOC: + werr = getncchanges_rid_alloc(b_state, mem_ctx, req10, &r->out.ctr->ctr6, &search_dn); + W_ERROR_NOT_OK_RETURN(werr); + if (r->out.ctr->ctr6.extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + break; + case DRSUAPI_EXOP_REPL_SECRET: + werr = getncchanges_repl_secret(b_state, mem_ctx, req10, + user_sid, + &r->out.ctr->ctr6, + has_get_all_changes, + &machine_dn); + r->out.result = werr; + W_ERROR_NOT_OK_RETURN(werr); + break; + case DRSUAPI_EXOP_FSMO_REQ_ROLE: + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); + W_ERROR_NOT_OK_RETURN(werr); + if (r->out.ctr->ctr6.extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + break; + case DRSUAPI_EXOP_FSMO_RID_REQ_ROLE: + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); + W_ERROR_NOT_OK_RETURN(werr); + if (r->out.ctr->ctr6.extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + break; + case DRSUAPI_EXOP_FSMO_REQ_PDC: + werr = getncchanges_change_master(b_state, mem_ctx, req10, &r->out.ctr->ctr6); + W_ERROR_NOT_OK_RETURN(werr); + if (r->out.ctr->ctr6.extended_ret != DRSUAPI_EXOP_ERR_SUCCESS) { + return WERR_OK; + } + break; + case DRSUAPI_EXOP_REPL_OBJ: + werr = getncchanges_repl_obj(b_state, mem_ctx, req10, user_sid, &r->out.ctr->ctr6); + r->out.result = werr; + W_ERROR_NOT_OK_RETURN(werr); + break; + + case DRSUAPI_EXOP_FSMO_ABANDON_ROLE: + + DEBUG(0,(__location__ ": Request for DsGetNCChanges unsupported extended op 0x%x\n", + (unsigned)req10->extended_op)); + return WERR_DS_DRA_NOT_SUPPORTED; + } + + /* + * Initialize the state, initially for the remainder + * of this call (EXOPs) + * + * An extended operation is a "special single-response + * cycle" per MS-DRSR 4.1.10.1.1 "Start and Finish" + * + */ + getnc_state = talloc_zero(mem_ctx, struct drsuapi_getncchanges_state); + if (getnc_state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + /* + * Promote the memory to being a store of + * long-term state that we will use over the + * replication cycle for full replication + * requests + * + * Store the state in a clearly named location + * for pulling back only during full + * replications + */ + b_state->getncchanges_full_repl_state + = talloc_steal(b_state, getnc_state); + } + + getnc_state->ncRoot_dn = ncRoot_dn; + talloc_steal(getnc_state, ncRoot_dn); + + getnc_state->ncRoot_guid = samdb_result_guid(res->msgs[0], + "objectGUID"); + + /* find out if we are to replicate Schema NC */ + ret = ldb_dn_compare_base(ldb_get_schema_basedn(sam_ctx), + ncRoot_dn); + getnc_state->is_schema_nc = (0 == ret); + + TALLOC_FREE(res); + } + + /* we need the session key for encrypting password attributes */ + status = dcesrv_auth_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": Failed to get session key\n")); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + /* + TODO: MS-DRSR section 4.1.10.1.1 + Work out if this is the start of a new cycle */ + + if (getnc_state->guids == NULL) { + const char *extra_filter; + struct ldb_result *search_res = NULL; + static const struct drsuapi_DsReplicaCursorCtrEx empty_udv; + const struct drsuapi_DsReplicaCursorCtrEx *udv = NULL; + + extra_filter = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "drs", "object filter"); + + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + if (req10->uptodateness_vector != NULL) { + udv = req10->uptodateness_vector; + } else { + udv = &empty_udv; + } + + getnc_state->min_usn = req10->highwatermark.highest_usn; + for (i = 0; i < udv->count; i++) { + bool match; + const struct drsuapi_DsReplicaCursor *cur = + &udv->cursors[i]; + + match = GUID_equal(&invocation_id, + &cur->source_dsa_invocation_id); + if (!match) { + continue; + } + if (cur->highest_usn > getnc_state->min_usn) { + getnc_state->min_usn = cur->highest_usn; + } + break; + } + } else { + /* We do not want REPL_SECRETS or REPL_SINGLE to return empty-handed */ + udv = &empty_udv; + getnc_state->min_usn = 0; + } + + getnc_state->max_usn = getnc_state->min_usn; + + getnc_state->final_udv = talloc_zero(getnc_state, + struct drsuapi_DsReplicaCursor2CtrEx); + if (getnc_state->final_udv == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + werr = get_nc_changes_udv(sam_ctx, getnc_state->ncRoot_dn, + getnc_state->final_udv); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + werr = getncchanges_collect_objects(b_state, mem_ctx, + getnc_state, req10, + search_dn, extra_filter, + &search_res); + } else { + werr = getncchanges_collect_objects_exop(b_state, mem_ctx, + getnc_state, req10, + &r->out.ctr->ctr6, + search_dn, extra_filter, + &search_res); + } + W_ERROR_NOT_OK_RETURN(werr); + + /* extract out the GUIDs list */ + getnc_state->num_records = search_res ? search_res->count : 0; + getnc_state->guids = talloc_array(getnc_state, struct GUID, getnc_state->num_records); + W_ERROR_HAVE_NO_MEMORY(getnc_state->guids); + + changes = talloc_array(getnc_state, + struct drsuapi_changed_objects, + getnc_state->num_records); + W_ERROR_HAVE_NO_MEMORY(changes); + + for (i=0; i<getnc_state->num_records; i++) { + changes[i].dn = search_res->msgs[i]->dn; + changes[i].guid = samdb_result_guid(search_res->msgs[i], "objectGUID"); + changes[i].usn = ldb_msg_find_attr_as_uint64(search_res->msgs[i], "uSNChanged", 0); + + if (changes[i].usn > getnc_state->max_usn) { + getnc_state->max_usn = changes[i].usn; + } + + if (req10->extended_op == DRSUAPI_EXOP_NONE && + GUID_equal(&changes[i].guid, &getnc_state->ncRoot_guid)) + { + getnc_state->send_nc_root_first = true; + } + } + + if (req10->extended_op == DRSUAPI_EXOP_NONE) { + getnc_state->is_get_anc = + ((req10->replica_flags & DRSUAPI_DRS_GET_ANC) != 0); + if (getnc_state->is_get_anc + && lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, + NULL, + "drs", + "broken_samba_4.5_get_anc_emulation", + false)) { + getnc_state->broken_samba_4_5_get_anc_emulation = true; + } + if (lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, + NULL, + "drs", + "get_tgt_support", + true)) { + getnc_state->is_get_tgt = + ((req10->more_flags & DRSUAPI_DRS_GET_TGT) != 0); + } + } + + /* RID_ALLOC returns 3 objects in a fixed order */ + if (req10->extended_op == DRSUAPI_EXOP_FSMO_RID_ALLOC) { + /* Do nothing */ + } else if (getnc_state->broken_samba_4_5_get_anc_emulation) { + LDB_TYPESAFE_QSORT(changes, + getnc_state->num_records, + getnc_state, + site_res_cmp_anc_order); + } else { + LDB_TYPESAFE_QSORT(changes, + getnc_state->num_records, + getnc_state, + site_res_cmp_usn_order); + } + + for (i=0; i < getnc_state->num_records; i++) { + getnc_state->guids[i] = changes[i].guid; + if (GUID_all_zero(&getnc_state->guids[i])) { + DEBUG(2,("getncchanges: bad objectGUID from %s\n", + ldb_dn_get_linearized(search_res->msgs[i]->dn))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + } + + getnc_state->final_hwm.tmp_highest_usn = getnc_state->max_usn; + getnc_state->final_hwm.reserved_usn = 0; + getnc_state->final_hwm.highest_usn = getnc_state->max_usn; + + talloc_free(search_res); + talloc_free(changes); + + /* + * when using GET_ANC or GET_TGT, cache the objects that have + * been already sent, to avoid sending them multiple times + */ + if (getnc_state->is_get_anc || getnc_state->is_get_tgt) { + DEBUG(3,("Using object cache, GET_ANC %u, GET_TGT %u\n", + getnc_state->is_get_anc, + getnc_state->is_get_tgt)); + + getnc_state->obj_cache = db_open_rbt(getnc_state); + if (getnc_state->obj_cache == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + } + } + + if (req10->uptodateness_vector) { + /* make sure its sorted */ + TYPESAFE_QSORT(req10->uptodateness_vector->cursors, + req10->uptodateness_vector->count, + drsuapi_DsReplicaCursor_compare); + } + + /* Prefix mapping */ + schema = dsdb_get_schema(sam_ctx, mem_ctx); + if (!schema) { + DEBUG(0,("No schema in sam_ctx\n")); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + r->out.ctr->ctr6.naming_context = talloc(mem_ctx, struct drsuapi_DsReplicaObjectIdentifier); + if (r->out.ctr->ctr6.naming_context == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + /* + * Match Windows and echo back the original values from the request, even if + * they say DummyDN for the string NC + */ + *r->out.ctr->ctr6.naming_context = *untrusted_ncRoot; + + /* find the SID if there is one */ + dsdb_find_sid_by_dn(sam_ctx, getnc_state->ncRoot_dn, &r->out.ctr->ctr6.naming_context->sid); + + /* Set GUID */ + r->out.ctr->ctr6.naming_context->guid = getnc_state->ncRoot_guid; + + dsdb_get_oid_mappings_drsuapi(schema, true, mem_ctx, &ctr); + r->out.ctr->ctr6.mapping_ctr = *ctr; + + r->out.ctr->ctr6.source_dsa_guid = *(samdb_ntds_objectGUID(sam_ctx)); + r->out.ctr->ctr6.source_dsa_invocation_id = *(samdb_ntds_invocation_id(sam_ctx)); + + r->out.ctr->ctr6.old_highwatermark = req10->highwatermark; + r->out.ctr->ctr6.new_highwatermark = req10->highwatermark; + + /* + * If the client has already set GET_TGT then we know they can handle + * receiving the linked attributes interleaved with the source objects + */ + if (getnc_state->is_get_tgt) { + repl_chunk->immediate_link_sync = true; + } + + if (req10->partial_attribute_set != NULL) { + struct dsdb_syntax_ctx syntax_ctx; + uint32_t j = 0; + + dsdb_syntax_ctx_init(&syntax_ctx, sam_ctx, schema); + syntax_ctx.pfm_remote = pfm_remote; + + local_pas = talloc_array(b_state, uint32_t, req10->partial_attribute_set->num_attids); + + for (j = 0; j < req10->partial_attribute_set->num_attids; j++) { + getncchanges_attid_remote_to_local(schema, + &syntax_ctx, + req10->partial_attribute_set->attids[j], + (enum drsuapi_DsAttributeId *)&local_pas[j], + NULL); + } + + TYPESAFE_QSORT(local_pas, + req10->partial_attribute_set->num_attids, + uint32_t_ptr_cmp); + } + + /* + * If we have the NC root in this replication, send it + * first regardless. However, don't bump the USN now, + * treat it as if it was sent early due to GET_ANC + * + * This is triggered for each call, so every page of responses + * gets the NC root as the first object, up to the point where + * it naturally occurs in the replication. + */ + + if (getnc_state->send_nc_root_first) { + struct drsuapi_DsReplicaObjectListItemEx *new_objs = NULL; + + werr = getncchanges_add_ancestors(&getnc_state->ncRoot_guid, + NULL, mem_ctx, + sam_ctx, getnc_state, + schema, &session_key, + req10, local_pas, + machine_dn, &new_objs); + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + getncchanges_chunk_add_objects(repl_chunk, new_objs); + + DEBUG(8,(__location__ ": replicating NC root %s\n", + ldb_dn_get_linearized(getnc_state->ncRoot_dn))); + } + + /* + * Check in case we're still processing the links from an object in the + * previous chunk. We want to send the links (and any targets needed) + * before moving on to the next object. + */ + if (getnc_state->is_get_tgt) { + werr = getncchanges_chunk_add_la_targets(repl_chunk, + getnc_state, + getnc_state->la_idx, + mem_ctx, sam_ctx, + schema, &session_key, + req10, local_pas, + machine_dn); + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + for (i=getnc_state->num_processed; + i<getnc_state->num_records && + !getncchanges_chunk_is_full(repl_chunk, getnc_state); + i++) { + struct drsuapi_DsReplicaObjectListItemEx *new_objs = NULL; + struct ldb_message *msg; + static const char * const msg_attrs[] = { + "*", + "nTSecurityDescriptor", + "parentGUID", + "replPropertyMetaData", + DSDB_SECRET_ATTRIBUTES, + NULL }; + struct ldb_result *msg_res; + struct ldb_dn *msg_dn; + bool obj_already_sent = false; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + uint32_t old_la_index; + + /* + * Once we get to the 'natural' place to send the NC + * root, stop sending it at the front of each reply + * and make sure to suppress sending it now + * + * We don't just 'continue' here as we must send links + * and unlike Windows we want to update the + * tmp_highest_usn + */ + + if (getnc_state->send_nc_root_first && + GUID_equal(&getnc_state->guids[i], &getnc_state->ncRoot_guid)) + { + getnc_state->send_nc_root_first = false; + obj_already_sent = true; + } + + msg_dn = ldb_dn_new_fmt(tmp_ctx, sam_ctx, "<GUID=%s>", + GUID_string(tmp_ctx, &getnc_state->guids[i])); + W_ERROR_HAVE_NO_MEMORY(msg_dn); + + /* + * by re-searching here we avoid having a lot of full + * records in memory between calls to getncchanges. + * + * We expect that we may get some objects that vanish + * (tombstone expunge) between the first and second + * check. + */ + ret = drsuapi_search_with_extended_dn(sam_ctx, tmp_ctx, &msg_res, + msg_dn, + LDB_SCOPE_BASE, msg_attrs, NULL); + if (ret != LDB_SUCCESS) { + if (ret != LDB_ERR_NO_SUCH_OBJECT) { + DEBUG(1,("getncchanges: failed to fetch DN %s - %s\n", + ldb_dn_get_extended_linearized(tmp_ctx, msg_dn, 1), + ldb_errstring(sam_ctx))); + } + TALLOC_FREE(tmp_ctx); + continue; + } + + if (msg_res->count == 0) { + DEBUG(1,("getncchanges: got LDB_SUCCESS but failed" + "to get any results in fetch of DN " + "%s (race with tombstone expunge?)\n", + ldb_dn_get_extended_linearized(tmp_ctx, + msg_dn, 1))); + TALLOC_FREE(tmp_ctx); + continue; + } + + msg = msg_res->msgs[0]; + + /* + * Check if we've already sent the object as an ancestor of + * another object. If so, we don't need to send it again + */ + if (getnc_state->obj_cache != NULL) { + werr = dcesrv_drsuapi_obj_cache_exists(getnc_state->obj_cache, + &getnc_state->guids[i]); + if (W_ERROR_EQUAL(werr, WERR_OBJECT_NAME_EXISTS)) { + obj_already_sent = true; + } + } + + if (!obj_already_sent) { + bool max_wait_reached; + + max_wait_reached = getncchanges_chunk_timed_out(repl_chunk); + + /* + * Construct an object, ready to send (this will include + * the object's ancestors as well, if needed) + */ + werr = getncchanges_get_obj_to_send(msg, mem_ctx, sam_ctx, + getnc_state, schema, + &session_key, req10, + max_wait_reached, + local_pas, machine_dn, + &getnc_state->guids[i], + &new_objs); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + old_la_index = getnc_state->la_count; + + /* + * We've reached the USN where this object naturally occurs. + * Regardless of whether we've already sent the object (as an + * ancestor), we add its links and update the HWM at this point + */ + werr = get_nc_changes_add_links(sam_ctx, getnc_state, + getnc_state->is_schema_nc, + schema, getnc_state->min_usn, + req10->replica_flags, + msg, + &getnc_state->la_list, + &getnc_state->la_count, + req10->uptodateness_vector); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + dcesrv_drsuapi_update_highwatermark(msg, + getnc_state->max_usn, + &r->out.ctr->ctr6.new_highwatermark); + + if (new_objs != NULL) { + + /* + * Add the object (and, if GET_ANC, any parents it may + * have) into the current chunk of replication data + */ + getncchanges_chunk_add_objects(repl_chunk, new_objs); + + talloc_free(getnc_state->last_dn); + /* + * talloc_steal() as we still need msg->dn to + * be a valid pointer for the log on the next + * line. + * + * msg only remains in scope for the next 25 + * lines or so anyway. + */ + getnc_state->last_dn = talloc_steal(getnc_state, msg->dn); + } + + DEBUG(8,(__location__ ": %s object %s new tmp_highest_usn=%" PRIu64 "\n", + new_objs ? "replicating" : "skipping send of", + ldb_dn_get_linearized(msg->dn), + r->out.ctr->ctr6.new_highwatermark.tmp_highest_usn)); + + getnc_state->total_links += (getnc_state->la_count - old_la_index); + + /* + * If the GET_TGT flag was set, check any new links added to + * make sure the client knows about the link target object + */ + if (getnc_state->is_get_tgt) { + werr = getncchanges_chunk_add_la_targets(repl_chunk, + getnc_state, + old_la_index, + mem_ctx, sam_ctx, + schema, &session_key, + req10, local_pas, + machine_dn); + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + } + + TALLOC_FREE(tmp_ctx); + } + + /* copy the constructed object list into the response message */ + r->out.ctr->ctr6.object_count = repl_chunk->object_count; + r->out.ctr->ctr6.first_object = repl_chunk->object_list; + + getnc_state->num_processed = i; + + if (i < getnc_state->num_records) { + r->out.ctr->ctr6.more_data = true; + } + + /* the client can us to call UpdateRefs on its behalf to + re-establish monitoring of the NC */ + if ((req10->replica_flags & (DRSUAPI_DRS_ADD_REF | DRSUAPI_DRS_REF_GCSPN)) && + !GUID_all_zero(&req10->destination_dsa_guid)) { + struct drsuapi_DsReplicaUpdateRefsRequest1 ureq; + DEBUG(3,("UpdateRefs on getncchanges for %s\n", + GUID_string(mem_ctx, &req10->destination_dsa_guid))); + + /* + * We pass the pre-validation NC root here as + * drsuapi_UpdateRefs() has to check its own input + * values due to being called from + * dcesrv_drsuapi_DsReplicaUpdateRefs() + */ + + ureq.naming_context = untrusted_ncRoot; + ureq.dest_dsa_dns_name = samdb_ntds_msdcs_dns_name(sam_ctx, mem_ctx, + &req10->destination_dsa_guid); + if (!ureq.dest_dsa_dns_name) { + return WERR_NOT_ENOUGH_MEMORY; + } + ureq.dest_dsa_guid = req10->destination_dsa_guid; + ureq.options = DRSUAPI_DRS_ADD_REF | + DRSUAPI_DRS_ASYNC_OP | + DRSUAPI_DRS_GETCHG_CHECK; + + /* we also need to pass through the + DRSUAPI_DRS_REF_GCSPN bit so that repsTo gets flagged + to send notifies using the GC SPN */ + ureq.options |= (req10->replica_flags & DRSUAPI_DRS_REF_GCSPN); + + werr = drsuapi_UpdateRefs(imsg_ctx, + dce_call->event_ctx, + b_state, + mem_ctx, + &ureq); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,(__location__ ": Failed UpdateRefs on %s for %s in DsGetNCChanges - %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, untrusted_ncRoot), + ureq.dest_dsa_dns_name, + win_errstr(werr))); + } + } + + /* + * Work out how many links we can send in this chunk. The default is to + * send all the links last, but there is a config option to send them + * immediately, in the same chunk as their source object + */ + if (!r->out.ctr->ctr6.more_data || repl_chunk->immediate_link_sync) { + link_count = getncchanges_chunk_links_pending(repl_chunk, + getnc_state); + link_count = MIN(link_count, + getncchanges_chunk_max_links(repl_chunk)); + } + + /* If we've got linked attributes to send, add them now */ + if (link_count > 0) { + struct la_for_sorting *la_sorted; + + /* + * Grab a chunk of linked attributes off the list and put them + * in sorted array, ready to send + */ + werr = getncchanges_get_sorted_array(&getnc_state->la_list[getnc_state->la_idx], + link_count, + sam_ctx, getnc_state, + schema, + &la_sorted); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + r->out.ctr->ctr6.linked_attributes_count = link_count; + r->out.ctr->ctr6.linked_attributes = talloc_array(r->out.ctr, struct drsuapi_DsReplicaLinkedAttribute, link_count); + if (r->out.ctr->ctr6.linked_attributes == NULL) { + DEBUG(0, ("Out of memory allocating %u linked attributes for output", link_count)); + return WERR_NOT_ENOUGH_MEMORY; + } + + for (k = 0; k < link_count; k++) { + r->out.ctr->ctr6.linked_attributes[k] = *la_sorted[k].link; + } + + getnc_state->la_idx += link_count; + getnc_state->links_given += link_count; + + if (getnc_state->la_idx < getnc_state->la_count) { + r->out.ctr->ctr6.more_data = true; + } else { + + /* + * We've now sent all the links seen so far, so we can + * reset la_list back to an empty list again. Note that + * the steal means the linked attribute memory gets + * freed after this RPC message is sent on the wire. + */ + talloc_steal(mem_ctx, getnc_state->la_list); + getnc_state->la_list = NULL; + getnc_state->la_idx = 0; + getnc_state->la_count = 0; + } + + TALLOC_FREE(la_sorted); + } + + if (req10->replica_flags & DRSUAPI_DRS_GET_NC_SIZE) { + /* + * TODO: This implementation is wrong + * we should find out the total number of + * objects and links in the whole naming context + * at the start of the cycle and return these + * values in each message. + * + * For now we keep our current strategy and return + * the number of objects for this cycle and the number + * of links we found so far during the cycle. + */ + r->out.ctr->ctr6.nc_object_count = getnc_state->num_records; + r->out.ctr->ctr6.nc_linked_attributes_count = getnc_state->total_links; + } + + if (req10->extended_op != DRSUAPI_EXOP_NONE) { + r->out.ctr->ctr6.uptodateness_vector = NULL; + r->out.ctr->ctr6.nc_object_count = 0; + ZERO_STRUCT(r->out.ctr->ctr6.new_highwatermark); + } else if (!r->out.ctr->ctr6.more_data) { + + /* this is the last response in the replication cycle */ + r->out.ctr->ctr6.new_highwatermark = getnc_state->final_hwm; + r->out.ctr->ctr6.uptodateness_vector = talloc_move(mem_ctx, + &getnc_state->final_udv); + + /* + * Free the state info stored for the replication cycle. Note + * that the RPC message we're sending contains links stored in + * getnc_state. mem_ctx is local to this RPC call, so the memory + * will get freed after the RPC message is sent on the wire. + * + * We must not do this for an EXOP, as that should not + * end the replication state, which is why that is + * checked first above. + */ + talloc_steal(mem_ctx, getnc_state); + b_state->getncchanges_full_repl_state = NULL; + } else { + ret = drsuapi_DsReplicaHighWaterMark_cmp(&r->out.ctr->ctr6.old_highwatermark, + &r->out.ctr->ctr6.new_highwatermark); + if (ret == 0) { + /* + * We need to make sure that we never return the + * same highwatermark within the same replication + * cycle more than once. Otherwise we cannot detect + * when the client uses an unexptected highwatermark. + * + * This is a HACK which is needed because our + * object ordering is wrong and set tmp_highest_usn + * to a value that is higher than what we already + * sent to the client (destination dsa). + */ + r->out.ctr->ctr6.new_highwatermark.reserved_usn += 1; + } + + getnc_state->last_hwm = r->out.ctr->ctr6.new_highwatermark; + } + + TALLOC_FREE(repl_chunk); + + DEBUG(r->out.ctr->ctr6.more_data?4:2, + ("DsGetNCChanges with uSNChanged >= %llu flags 0x%08x on %s gave %u objects (done %u/%u) %u links (done %u/%u (as %s))\n", + (unsigned long long)(req10->highwatermark.highest_usn+1), + req10->replica_flags, + drs_ObjectIdentifier_to_debug_string(mem_ctx, untrusted_ncRoot), + r->out.ctr->ctr6.object_count, + i, r->out.ctr->ctr6.more_data?getnc_state->num_records:i, + r->out.ctr->ctr6.linked_attributes_count, + getnc_state->links_given, getnc_state->total_links, + dom_sid_string(mem_ctx, user_sid))); + +#if 0 + if (!r->out.ctr->ctr6.more_data && req10->extended_op != DRSUAPI_EXOP_NONE) { + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsGetNCChanges, NDR_BOTH, r); + } +#endif + + return WERR_OK; +} + diff --git a/source4/rpc_server/drsuapi/updaterefs.c b/source4/rpc_server/drsuapi/updaterefs.c new file mode 100644 index 0000000..0be675f --- /dev/null +++ b/source4/rpc_server/drsuapi/updaterefs.c @@ -0,0 +1,411 @@ +/* + Unix SMB/CIFS implementation. + + implement the DRSUpdateRefs call + + Copyright (C) Andrew Tridgell 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" +#include "auth/session.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "lib/messaging/irpc.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +struct repsTo { + uint32_t count; + struct repsFromToBlob *r; +}; + +static WERROR uref_check_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, struct GUID *dest_guid, + uint32_t options) +{ + struct repsTo reps; + WERROR werr; + unsigned int i; + bool found = false; + + werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + for (i=0; i<reps.count; i++) { + if (GUID_equal(dest_guid, + &reps.r[i].ctr.ctr1.source_dsa_obj_guid)) { + found = true; + break; + } + } + + if (options & DRSUAPI_DRS_ADD_REF) { + if (found && !(options & DRSUAPI_DRS_DEL_REF)) { + return WERR_DS_DRA_REF_ALREADY_EXISTS; + } + } + + if (options & DRSUAPI_DRS_DEL_REF) { + if (!found && !(options & DRSUAPI_DRS_ADD_REF)) { + return WERR_DS_DRA_REF_NOT_FOUND; + } + } + + return WERR_OK; +} + +/* + add a replication destination for a given partition GUID + */ +static WERROR uref_add_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, struct repsFromTo1 *dest, + uint32_t options) +{ + struct repsTo reps; + WERROR werr; + unsigned int i; + + werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + for (i=0; i<reps.count; i++) { + if (GUID_equal(&dest->source_dsa_obj_guid, + &reps.r[i].ctr.ctr1.source_dsa_obj_guid)) { + if (options & DRSUAPI_DRS_GETCHG_CHECK) { + return WERR_OK; + } else { + return WERR_DS_DRA_REF_ALREADY_EXISTS; + } + } + } + + reps.r = talloc_realloc(mem_ctx, reps.r, struct repsFromToBlob, reps.count+1); + if (reps.r == NULL) { + return WERR_DS_DRA_INTERNAL_ERROR; + } + ZERO_STRUCT(reps.r[reps.count]); + reps.r[reps.count].version = 1; + reps.r[reps.count].ctr.ctr1 = *dest; + /* add the GCSPN flag if the client asked for it */ + reps.r[reps.count].ctr.ctr1.replica_flags |= (options & DRSUAPI_DRS_REF_GCSPN); + reps.count++; + + werr = dsdb_savereps(sam_ctx, mem_ctx, dn, "repsTo", reps.r, reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + return WERR_OK; +} + +/* + delete a replication destination for a given partition GUID + */ +static WERROR uref_del_dest(struct ldb_context *sam_ctx, TALLOC_CTX *mem_ctx, + struct ldb_dn *dn, struct GUID *dest_guid, + uint32_t options) +{ + struct repsTo reps; + WERROR werr; + unsigned int i; + bool found = false; + + werr = dsdb_loadreps(sam_ctx, mem_ctx, dn, "repsTo", &reps.r, &reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + for (i=0; i<reps.count; i++) { + if (GUID_equal(dest_guid, + &reps.r[i].ctr.ctr1.source_dsa_obj_guid)) { + if (i+1 < reps.count) { + memmove(&reps.r[i], &reps.r[i+1], sizeof(reps.r[i])*(reps.count-(i+1))); + } + reps.count--; + found = true; + } + } + + werr = dsdb_savereps(sam_ctx, mem_ctx, dn, "repsTo", reps.r, reps.count); + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + if (!found && + !(options & DRSUAPI_DRS_GETCHG_CHECK) && + !(options & DRSUAPI_DRS_ADD_REF)) { + return WERR_DS_DRA_REF_NOT_FOUND; + } + + return WERR_OK; +} + +struct drepl_refresh_state { + struct dreplsrv_refresh r; +}; + +/** + * @brief Update the references for the given NC and the destination DSA object + * + * This function is callable from non RPC functions (ie. getncchanges), it + * will validate the request to update reference and then will add/del a repsTo + * to the specified server referenced by its DSA GUID in the request. + * + * @param[in] msg_ctx Messaging context for sending partition + * refresh in dreplsrv + * + * @param[in] b_state A bind_state object + * + * @param[in] mem_ctx A talloc context for memory allocation + * + * @param[in] req A drsuapi_DsReplicaUpdateRefsRequest1 + * object which NC, which server and which + * action (add/delete) should be performed + * + * @return WERR_OK is success, different error + * otherwise. + */ +WERROR drsuapi_UpdateRefs(struct imessaging_context *msg_ctx, + struct tevent_context *event_ctx, + struct drsuapi_bind_state *b_state, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaUpdateRefsRequest1 *req) +{ + WERROR werr; + int ret; + struct ldb_dn *dn_normalised; + struct ldb_dn *nc_root; + struct ldb_context *sam_ctx = b_state->sam_ctx_system?b_state->sam_ctx_system:b_state->sam_ctx; + struct dcerpc_binding_handle *irpc_handle; + struct tevent_req *subreq; + struct drepl_refresh_state *state; + + + DEBUG(4,("DsReplicaUpdateRefs for host '%s' with GUID %s options 0x%08x nc=%s\n", + req->dest_dsa_dns_name, GUID_string(mem_ctx, &req->dest_dsa_guid), + req->options, + drs_ObjectIdentifier_to_debug_string(mem_ctx, req->naming_context))); + + /* + * 4.1.26.2 Server Behavior of the IDL_DRSUpdateRefs Method + * Implements the input validation checks + */ + if (GUID_all_zero(&req->dest_dsa_guid)) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + /* FIXME it seems that we should check the length of the stuff too*/ + if (req->dest_dsa_dns_name == NULL) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + if (!(req->options & (DRSUAPI_DRS_DEL_REF|DRSUAPI_DRS_ADD_REF))) { + return WERR_DS_DRA_INVALID_PARAMETER; + } + + ret = drs_ObjectIdentifier_to_dn_and_nc_root(mem_ctx, sam_ctx, req->naming_context, + &dn_normalised, &nc_root); + if (ret != LDB_SUCCESS) { + DBG_WARNING("Didn't find a nc for %s: %s\n", + drs_ObjectIdentifier_to_debug_string(mem_ctx, + req->naming_context), + ldb_errstring(sam_ctx)); + return WERR_DS_DRA_BAD_NC; + } + if (ldb_dn_compare(dn_normalised, nc_root) != 0) { + DBG_NOTICE("dn %s is not equal to %s (from %s)\n", + ldb_dn_get_linearized(dn_normalised), + ldb_dn_get_linearized(nc_root), + drs_ObjectIdentifier_to_debug_string(mem_ctx, + req->naming_context)); + return WERR_DS_DRA_BAD_NC; + } + + /* + * First check without a transaction open. + * + * This means that in the usual case, it will never open it and never + * bother to refresh the dreplsrv. + */ + werr = uref_check_dest(sam_ctx, + mem_ctx, + dn_normalised, + &req->dest_dsa_guid, + req->options); + if (W_ERROR_EQUAL(werr, WERR_DS_DRA_REF_ALREADY_EXISTS) || + W_ERROR_EQUAL(werr, WERR_DS_DRA_REF_NOT_FOUND)) { + if (req->options & DRSUAPI_DRS_GETCHG_CHECK) { + return WERR_OK; + } + return werr; + } + + if (ldb_transaction_start(sam_ctx) != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to start transaction on samdb: %s\n", + ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + if (req->options & DRSUAPI_DRS_DEL_REF) { + werr = uref_del_dest(sam_ctx, + mem_ctx, + dn_normalised, + &req->dest_dsa_guid, + req->options); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("Failed to delete repsTo for %s: %s\n", + GUID_string(mem_ctx, &req->dest_dsa_guid), + win_errstr(werr))); + goto failed; + } + } + + if (req->options & DRSUAPI_DRS_ADD_REF) { + struct repsFromTo1 dest; + struct repsFromTo1OtherInfo oi; + + ZERO_STRUCT(dest); + ZERO_STRUCT(oi); + + oi.dns_name = req->dest_dsa_dns_name; + dest.other_info = &oi; + dest.source_dsa_obj_guid = req->dest_dsa_guid; + dest.replica_flags = req->options; + + werr = uref_add_dest(sam_ctx, + mem_ctx, + dn_normalised, + &dest, + req->options); + if (!W_ERROR_IS_OK(werr)) { + DEBUG(0,("Failed to add repsTo for %s: %s\n", + GUID_string(mem_ctx, &dest.source_dsa_obj_guid), + win_errstr(werr))); + goto failed; + } + } + + if (ldb_transaction_commit(sam_ctx) != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Failed to commit transaction on samdb: %s\n", + ldb_errstring(sam_ctx))); + return WERR_DS_DRA_INTERNAL_ERROR; + } + + state = talloc_zero(mem_ctx, struct drepl_refresh_state); + if (state == NULL) { + return WERR_OK; + } + + irpc_handle = irpc_binding_handle_by_name(mem_ctx, msg_ctx, + "dreplsrv", &ndr_table_irpc); + if (irpc_handle == NULL) { + /* dreplsrv is not running yet */ + TALLOC_FREE(state); + return WERR_OK; + } + + /* + * [Taken from auth_sam_trigger_repl_secret in auth_sam.c] + * + * This seem to rely on the current IRPC implementation, + * which delivers the message in the _send function. + * + * TODO: we need a ONE_WAY IRPC handle and register + * a callback and wait for it to be triggered! + */ + subreq = dcerpc_dreplsrv_refresh_r_send(state, event_ctx, + irpc_handle, &state->r); + TALLOC_FREE(subreq); + TALLOC_FREE(state); + + return WERR_OK; + +failed: + ldb_transaction_cancel(sam_ctx); + return werr; +} + +/* + drsuapi_DsReplicaUpdateRefs +*/ +WERROR dcesrv_drsuapi_DsReplicaUpdateRefs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsReplicaUpdateRefs *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + struct dcesrv_handle *h; + struct drsuapi_bind_state *b_state; + struct drsuapi_DsReplicaUpdateRefsRequest1 *req; + WERROR werr; + int ret; + enum security_user_level security_level; + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + if (r->in.level != 1) { + DEBUG(0,("DrReplicUpdateRefs - unsupported level %u\n", r->in.level)); + return WERR_DS_DRA_INVALID_PARAMETER; + } + req = &r->in.req.req1; + werr = drs_security_access_check(b_state->sam_ctx, + mem_ctx, + session_info->security_token, + req->naming_context, + GUID_DRS_MANAGE_TOPOLOGY); + + if (!W_ERROR_IS_OK(werr)) { + return werr; + } + + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_ADMINISTRATOR) { + /* check that they are using an DSA objectGUID that they own */ + ret = dsdb_validate_dsa_guid(b_state->sam_ctx, + &req->dest_dsa_guid, + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]); + if (ret != LDB_SUCCESS) { + DEBUG(0,(__location__ ": Refusing DsReplicaUpdateRefs for sid %s with GUID %s\n", + dom_sid_string(mem_ctx, + &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]), + GUID_string(mem_ctx, &req->dest_dsa_guid))); + return WERR_DS_DRA_ACCESS_DENIED; + } + } + + werr = drsuapi_UpdateRefs(imsg_ctx, + dce_call->event_ctx, + b_state, + mem_ctx, + req); + +#if 0 + NDR_PRINT_FUNCTION_DEBUG(drsuapi_DsReplicaUpdateRefs, NDR_BOTH, r); +#endif + + return werr; +} diff --git a/source4/rpc_server/drsuapi/writespn.c b/source4/rpc_server/drsuapi/writespn.c new file mode 100644 index 0000000..9e4b533 --- /dev/null +++ b/source4/rpc_server/drsuapi/writespn.c @@ -0,0 +1,257 @@ +/* + Unix SMB/CIFS implementation. + + implement the DsWriteAccountSpn call + + Copyright (C) Stefan Metzmacher 2009 + Copyright (C) Andrew Tridgell 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "libcli/security/security.h" +#include "libcli/security/session.h" +#include "rpc_server/drsuapi/dcesrv_drsuapi.h" +#include "librpc/gen_ndr/ndr_drsuapi.h" +#include "auth/session.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_DRS_REPL + +#undef strcasecmp + +/* + check that the SPN update should be allowed as an override + via sam_ctx_system + + This is only called if the client is not a domain controller or + administrator + */ +static bool writespn_check_spn(struct drsuapi_bind_state *b_state, + struct dcesrv_call_state *dce_call, + struct ldb_dn *dn, + const char *spn) +{ + /* + * we only allow SPN updates if: + * + * 1) they are on the clients own account object + * 2) they are of the form SERVICE/dnshostname + */ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dom_sid *user_sid, *sid; + TALLOC_CTX *tmp_ctx = talloc_new(dce_call); + struct ldb_result *res; + const char *attrs[] = { "objectSID", "dNSHostName", NULL }; + int ret; + krb5_context krb_ctx; + krb5_error_code kerr; + krb5_principal principal; + const krb5_data *component; + const char *dns_name, *dnsHostName; + + /* The service principal name shouldn't be NULL */ + if (spn == NULL) { + talloc_free(tmp_ctx); + return false; + } + + /* + get the objectSid of the DN that is being modified, and + check it matches the user_sid in their token + */ + + ret = dsdb_search_dn(b_state->sam_ctx, tmp_ctx, &res, dn, attrs, + DSDB_SEARCH_ONE_ONLY); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return false; + } + + user_sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + sid = samdb_result_dom_sid(tmp_ctx, res->msgs[0], "objectSid"); + if (sid == NULL) { + talloc_free(tmp_ctx); + return false; + } + + dnsHostName = ldb_msg_find_attr_as_string(res->msgs[0], "dNSHostName", + NULL); + if (dnsHostName == NULL) { + talloc_free(tmp_ctx); + return false; + } + + if (!dom_sid_equal(sid, user_sid)) { + talloc_free(tmp_ctx); + return false; + } + + kerr = smb_krb5_init_context_basic(tmp_ctx, + dce_call->conn->dce_ctx->lp_ctx, + &krb_ctx); + if (kerr != 0) { + talloc_free(tmp_ctx); + return false; + } + + kerr = krb5_parse_name_flags(krb_ctx, spn, KRB5_PRINCIPAL_PARSE_NO_REALM, + &principal); + if (kerr != 0) { + krb5_free_context(krb_ctx); + talloc_free(tmp_ctx); + return false; + } + + if (krb5_princ_size(krb_ctx, principal) != 2) { + krb5_free_principal(krb_ctx, principal); + krb5_free_context(krb_ctx); + talloc_free(tmp_ctx); + return false; + } + + component = krb5_princ_component(krb_ctx, principal, 1); + dns_name = (const char *)component->data; + + if (strcasecmp(dns_name, dnsHostName) != 0) { + krb5_free_principal(krb_ctx, principal); + krb5_free_context(krb_ctx); + talloc_free(tmp_ctx); + return false; + } + + /* its a simple update on their own account - allow it with + * permissions override */ + krb5_free_principal(krb_ctx, principal); + krb5_free_context(krb_ctx); + talloc_free(tmp_ctx); + + return true; +} + +/* + drsuapi_DsWriteAccountSpn +*/ +WERROR dcesrv_drsuapi_DsWriteAccountSpn(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct drsuapi_DsWriteAccountSpn *r) +{ + struct drsuapi_bind_state *b_state; + struct dcesrv_handle *h; + + *r->out.level_out = r->in.level; + + DCESRV_PULL_HANDLE_WERR(h, r->in.bind_handle, DRSUAPI_BIND_HANDLE); + b_state = h->data; + + r->out.res = talloc(mem_ctx, union drsuapi_DsWriteAccountSpnResult); + W_ERROR_HAVE_NO_MEMORY(r->out.res); + + switch (r->in.level) { + case 1: { + struct drsuapi_DsWriteAccountSpnRequest1 *req; + struct ldb_message *msg; + uint32_t count; + unsigned int i; + int ret; + unsigned spn_count=0; + bool passed_checks = true; + struct ldb_context *sam_ctx; + + req = &r->in.req->req1; + count = req->count; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + msg->dn = ldb_dn_new(msg, b_state->sam_ctx, + req->object_dn); + if ( ! ldb_dn_validate(msg->dn)) { + r->out.res->res1.status = WERR_OK; + return WERR_OK; + } + + /* construct mods */ + for (i = 0; i < count; i++) { + if (!writespn_check_spn(b_state, + dce_call, + msg->dn, + req->spn_names[i].str)) { + passed_checks = false; + } + ret = ldb_msg_add_string(msg, + "servicePrincipalName", + req->spn_names[i].str); + if (ret != LDB_SUCCESS) { + return WERR_NOT_ENOUGH_MEMORY; + } + spn_count++; + } + + if (msg->num_elements == 0) { + DEBUG(2,("No SPNs need changing on %s\n", + ldb_dn_get_linearized(msg->dn))); + r->out.res->res1.status = WERR_OK; + return WERR_OK; + } + + for (i=0;i<msg->num_elements;i++) { + switch (req->operation) { + case DRSUAPI_DS_SPN_OPERATION_ADD: + msg->elements[i].flags = LDB_FLAG_MOD_ADD; + break; + case DRSUAPI_DS_SPN_OPERATION_REPLACE: + msg->elements[i].flags = LDB_FLAG_MOD_REPLACE; + break; + case DRSUAPI_DS_SPN_OPERATION_DELETE: + msg->elements[i].flags = LDB_FLAG_MOD_DELETE; + break; + } + } + + if (passed_checks && b_state->sam_ctx_system) { + sam_ctx = b_state->sam_ctx_system; + } else { + sam_ctx = b_state->sam_ctx; + } + + /* Apply to database */ + ret = dsdb_modify(sam_ctx, msg, DSDB_MODIFY_PERMISSIVE); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to modify SPNs on %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(b_state->sam_ctx))); + NDR_PRINT_IN_DEBUG( + drsuapi_DsWriteAccountSpn, r); + r->out.res->res1.status = WERR_ACCESS_DENIED; + } else { + DEBUG(2,("Modified %u SPNs on %s\n", spn_count, + ldb_dn_get_linearized(msg->dn))); + r->out.res->res1.status = WERR_OK; + } + + return WERR_OK; + } + } + + return WERR_INVALID_LEVEL; +} diff --git a/source4/rpc_server/echo/rpc_echo.c b/source4/rpc_server/echo/rpc_echo.c new file mode 100644 index 0000000..b0baf82 --- /dev/null +++ b/source4/rpc_server/echo/rpc_echo.c @@ -0,0 +1,211 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the echo pipe + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Stefan (metze) Metzmacher 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_echo.h" +#include "lib/events/events.h" + +#define DCESRV_INTERFACE_RPCECHO_BIND(context, iface) \ + dcesrv_interface_rpcecho_bind(context, iface) +static NTSTATUS dcesrv_interface_rpcecho_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_allow_connect(context, iface); +} + +static NTSTATUS dcesrv_echo_AddOne(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_AddOne *r) +{ + *r->out.out_data = r->in.in_data + 1; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_EchoData(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_EchoData *r) +{ + if (!r->in.len) { + return NT_STATUS_OK; + } + + r->out.out_data = (uint8_t *)talloc_memdup(mem_ctx, r->in.in_data, r->in.len); + if (!r->out.out_data) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_SinkData(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_SinkData *r) +{ + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_SourceData(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_SourceData *r) +{ + unsigned int i; + + r->out.data = talloc_array(mem_ctx, uint8_t, r->in.len); + if (!r->out.data) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<r->in.len;i++) { + r->out.data[i] = i; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_TestCall(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestCall *r) +{ + *r->out.s2 = talloc_strdup(mem_ctx, r->in.s1); + if (r->in.s1 && !*r->out.s2) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_TestCall2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestCall2 *r) +{ + r->out.info = talloc(mem_ctx, union echo_Info); + if (!r->out.info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: + r->out.info->info1.v = 10; + break; + case 2: + r->out.info->info2.v = 20; + break; + case 3: + r->out.info->info3.v = 30; + break; + case 4: + r->out.info->info4.v = 40; + break; + case 5: + r->out.info->info5.v1 = 50; + r->out.info->info5.v2 = 60; + break; + case 6: + r->out.info->info6.v1 = 70; + r->out.info->info6.info1.v= 80; + break; + case 7: + r->out.info->info7.v1 = 80; + r->out.info->info7.info4.v = 90; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_TestEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestEnum *r) +{ + r->out.foo2->e1 = ECHO_ENUM2; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_echo_TestSurrounding(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestSurrounding *r) +{ + if (!r->in.data) { + r->out.data = NULL; + return NT_STATUS_OK; + } + + r->out.data = talloc(mem_ctx, struct echo_Surrounding); + if (!r->out.data) { + return NT_STATUS_NO_MEMORY; + } + r->out.data->x = 2 * r->in.data->x; + r->out.data->surrounding = talloc_zero_array(mem_ctx, uint16_t, r->out.data->x); + if (!r->out.data->surrounding) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +static uint16_t dcesrv_echo_TestDoublePointer(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestDoublePointer *r) +{ + if (!*r->in.data) + return 0; + if (!**r->in.data) + return 0; + return ***r->in.data; +} + +struct echo_TestSleep_private { + struct dcesrv_call_state *dce_call; + struct echo_TestSleep *r; +}; + +static void echo_TestSleep_handler(struct tevent_context *ev, struct tevent_timer *te, + struct timeval t, void *private_data) +{ + struct echo_TestSleep_private *p = talloc_get_type(private_data, + struct echo_TestSleep_private); + struct echo_TestSleep *r = p->r; + NTSTATUS status; + + r->out.result = r->in.seconds; + + status = dcesrv_reply(p->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("echo_TestSleep_handler: dcesrv_reply() failed - %s\n", + nt_errstr(status))); + } +} + +static long dcesrv_echo_TestSleep(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct echo_TestSleep *r) +{ + struct echo_TestSleep_private *p; + + if (!(dce_call->state_flags & DCESRV_CALL_STATE_FLAG_MAY_ASYNC)) { + /* we're not allowed to reply async */ + sleep(r->in.seconds); + return r->in.seconds; + } + + /* we're allowed to reply async */ + p = talloc(mem_ctx, struct echo_TestSleep_private); + if (!p) { + return 0; + } + + p->dce_call = dce_call; + p->r = r; + + tevent_add_timer(dce_call->event_ctx, p, + timeval_add(&dce_call->time, r->in.seconds, 0), + echo_TestSleep_handler, p); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + return 0; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_echo_s.c" diff --git a/source4/rpc_server/epmapper/rpc_epmapper.c b/source4/rpc_server/epmapper/rpc_epmapper.c new file mode 100644 index 0000000..d91fc8c --- /dev/null +++ b/source4/rpc_server/epmapper/rpc_epmapper.c @@ -0,0 +1,294 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the epmapper pipe + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Jelmer Vernooij 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_epmapper.h" +#include "rpc_server/dcerpc_server.h" + +#define DCESRV_INTERFACE_EPMAPPER_BIND(context, iface) \ + dcesrv_interface_epmapper_bind(context, iface) +static NTSTATUS dcesrv_interface_epmapper_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_allow_connect(context, iface); +} + +typedef uint32_t error_status_t; + +/* handle types for this module */ +enum handle_types {HTYPE_LOOKUP}; + +/* a endpoint combined with an interface description */ +struct dcesrv_ep_iface { + const char *name; + struct epm_tower ep; +}; + +/* + build a list of all interfaces handled by all endpoint servers +*/ +static uint32_t build_ep_list(TALLOC_CTX *mem_ctx, + struct dcesrv_endpoint *endpoint_list, + struct dcesrv_ep_iface **eps) +{ + struct dcesrv_endpoint *d; + uint32_t total = 0; + NTSTATUS status; + + *eps = NULL; + + for (d=endpoint_list; d; d=d->next) { + struct dcesrv_if_list *iface; + + for (iface=d->interface_list;iface;iface=iface->next) { + struct dcerpc_binding *description; + + (*eps) = talloc_realloc(mem_ctx, + *eps, + struct dcesrv_ep_iface, + total + 1); + if (!*eps) { + return 0; + } + (*eps)[total].name = iface->iface->name; + + description = dcerpc_binding_dup(*eps, d->ep_description); + if (description == NULL) { + return 0; + } + + status = dcerpc_binding_set_abstract_syntax(description, + &iface->iface->syntax_id); + if (!NT_STATUS_IS_OK(status)) { + return 0; + } + + status = dcerpc_binding_build_tower(*eps, description, &(*eps)[total].ep); + TALLOC_FREE(description); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("Unable to build tower for %s - %s\n", + iface->iface->name, + nt_errstr(status)); + continue; + } + total++; + } + } + + return total; +} + + +static error_status_t dcesrv_epm_Insert(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct epm_Insert *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static error_status_t dcesrv_epm_Delete(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_Delete *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + implement epm_Lookup. This call is used to enumerate the interfaces + available on a rpc server +*/ +static error_status_t dcesrv_epm_Lookup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_Lookup *r) +{ + struct dcesrv_handle *h; + struct rpc_eps { + uint32_t count; + struct dcesrv_ep_iface *e; + } *eps; + uint32_t num_ents; + unsigned int i; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.entry_handle, HTYPE_LOOKUP); + + eps = h->data; + + if (!eps) { + /* this is the first call - fill the list. Subsequent calls + will feed from this list, stored in the handle */ + eps = talloc(h, struct rpc_eps); + if (!eps) { + return EPMAPPER_STATUS_NO_MEMORY; + } + h->data = eps; + + eps->count = build_ep_list(h, dce_call->conn->dce_ctx->endpoint_list, &eps->e); + } + + /* return the next N elements */ + num_ents = r->in.max_ents; + if (num_ents > eps->count) { + num_ents = eps->count; + } + + *r->out.entry_handle = h->wire_handle; + r->out.num_ents = talloc(mem_ctx, uint32_t); + *r->out.num_ents = num_ents; + + if (num_ents == 0) { + r->out.entries = NULL; + ZERO_STRUCTP(r->out.entry_handle); + talloc_free(h); + return EPMAPPER_STATUS_NO_MORE_ENTRIES; + } + + r->out.entries = talloc_array(mem_ctx, struct epm_entry_t, num_ents); + if (!r->out.entries) { + return EPMAPPER_STATUS_NO_MEMORY; + } + + for (i=0;i<num_ents;i++) { + ZERO_STRUCT(r->out.entries[i].object); + r->out.entries[i].annotation = eps->e[i].name; + r->out.entries[i].tower = talloc(mem_ctx, struct epm_twr_t); + if (!r->out.entries[i].tower) { + return EPMAPPER_STATUS_NO_MEMORY; + } + r->out.entries[i].tower->tower = eps->e[i].ep; + } + + eps->count -= num_ents; + eps->e += num_ents; + + return EPMAPPER_STATUS_OK; +} + + +/* + implement epm_Map. This is used to find the specific endpoint to talk to given + a generic protocol tower +*/ +static error_status_t dcesrv_epm_Map(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_Map *r) +{ + uint32_t count; + unsigned int i; + struct dcesrv_ep_iface *eps; + struct epm_floor *floors; + enum dcerpc_transport_t transport; + struct ndr_syntax_id ndr_syntax; + + count = build_ep_list(mem_ctx, dce_call->conn->dce_ctx->endpoint_list, &eps); + + ZERO_STRUCT(*r->out.entry_handle); + r->out.num_towers = talloc(mem_ctx, uint32_t); + if (!r->out.num_towers) { + return EPMAPPER_STATUS_NO_MEMORY; + } + *r->out.num_towers = 1; + r->out.towers = talloc(mem_ctx, struct epm_twr_p_t); + if (!r->out.towers) { + return EPMAPPER_STATUS_NO_MEMORY; + } + r->out.towers->twr = talloc(mem_ctx, struct epm_twr_t); + if (!r->out.towers->twr) { + return EPMAPPER_STATUS_NO_MEMORY; + } + + if (!r->in.map_tower || r->in.max_towers == 0 || + r->in.map_tower->tower.num_floors < 3) { + goto failed; + } + + floors = r->in.map_tower->tower.floors; + + dcerpc_floor_get_lhs_data(&r->in.map_tower->tower.floors[1], &ndr_syntax); + + if (floors[1].lhs.protocol != EPM_PROTOCOL_UUID || + !ndr_syntax_id_equal(&ndr_syntax, &ndr_transfer_syntax_ndr)) { + goto failed; + } + + transport = dcerpc_transport_by_tower(&r->in.map_tower->tower); + + if (transport == -1) { + DEBUG(2, ("Client requested unknown transport with levels: ")); + for (i = 2; i < r->in.map_tower->tower.num_floors; i++) { + DEBUG(2, ("%d, ", r->in.map_tower->tower.floors[i].lhs.protocol)); + } + DEBUG(2, ("\n")); + goto failed; + } + + for (i=0;i<count;i++) { + if ( + data_blob_cmp(&r->in.map_tower->tower.floors[0].lhs.lhs_data, + &eps[i].ep.floors[0].lhs.lhs_data) != 0 + || transport != dcerpc_transport_by_tower(&eps[i].ep)) { + continue; + } + + r->out.towers->twr->tower = eps[i].ep; + r->out.towers->twr->tower_length = 0; + return EPMAPPER_STATUS_OK; + } + + +failed: + *r->out.num_towers = 0; + r->out.towers->twr = NULL; + + return EPMAPPER_STATUS_NO_MORE_ENTRIES; +} + +static error_status_t dcesrv_epm_LookupHandleFree(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_LookupHandleFree *r) +{ + struct dcesrv_handle *h = NULL; + + r->out.entry_handle = r->in.entry_handle; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.entry_handle, HTYPE_LOOKUP); + TALLOC_FREE(h); + + ZERO_STRUCTP(r->out.entry_handle); + + return EPMAPPER_STATUS_OK; +} + +static error_status_t dcesrv_epm_InqObject(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_InqObject *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static error_status_t dcesrv_epm_MgmtDelete(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_MgmtDelete *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static error_status_t dcesrv_epm_MapAuth(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct epm_MapAuth *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_epmapper_s.c" diff --git a/source4/rpc_server/eventlog/dcesrv_eventlog6.c b/source4/rpc_server/eventlog/dcesrv_eventlog6.c new file mode 100644 index 0000000..4962984 --- /dev/null +++ b/source4/rpc_server/eventlog/dcesrv_eventlog6.c @@ -0,0 +1,331 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the eventlog6 pipe + + Copyright (C) Anatoliy Atanasov 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_eventlog6.h" +#include "rpc_server/common/common.h" + + +/* + eventlog6_EvtRpcRegisterRemoteSubscription +*/ +static WERROR dcesrv_eventlog6_EvtRpcRegisterRemoteSubscription(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRegisterRemoteSubscription *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRemoteSubscriptionNextAsync +*/ +static WERROR dcesrv_eventlog6_EvtRpcRemoteSubscriptionNextAsync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRemoteSubscriptionNextAsync *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRemoteSubscriptionNext +*/ +static WERROR dcesrv_eventlog6_EvtRpcRemoteSubscriptionNext(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRemoteSubscriptionNext *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRemoteSubscriptionWaitAsync +*/ +static WERROR dcesrv_eventlog6_EvtRpcRemoteSubscriptionWaitAsync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRemoteSubscriptionWaitAsync *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRegisterControllableOperation +*/ +static WERROR dcesrv_eventlog6_EvtRpcRegisterControllableOperation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRegisterControllableOperation *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRegisterLogQuery +*/ +static WERROR dcesrv_eventlog6_EvtRpcRegisterLogQuery(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRegisterLogQuery *r) +{ + struct dcesrv_handle *handle; + + handle = dcesrv_handle_create(dce_call, 0); + W_ERROR_HAVE_NO_MEMORY(handle); + + r->out.handle = &handle->wire_handle; + + handle = dcesrv_handle_create(dce_call, 0); + W_ERROR_HAVE_NO_MEMORY(handle); + + r->out.opControl = &handle->wire_handle; + + return WERR_OK; +} + + +/* + eventlog6_EvtRpcClearLog +*/ +static WERROR dcesrv_eventlog6_EvtRpcClearLog(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcClearLog *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcExportLog +*/ +static WERROR dcesrv_eventlog6_EvtRpcExportLog(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcExportLog *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcLocalizeExportLog +*/ +static WERROR dcesrv_eventlog6_EvtRpcLocalizeExportLog(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcLocalizeExportLog *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcMessageRender +*/ +static WERROR dcesrv_eventlog6_EvtRpcMessageRender(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcMessageRender *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcMessageRenderDefault +*/ +static WERROR dcesrv_eventlog6_EvtRpcMessageRenderDefault(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcMessageRenderDefault *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcQueryNext +*/ +static WERROR dcesrv_eventlog6_EvtRpcQueryNext(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcQueryNext *r) +{ + return WERR_OK; +} + + +/* + eventlog6_EvtRpcQuerySeek +*/ +static WERROR dcesrv_eventlog6_EvtRpcQuerySeek(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcQuerySeek *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcClose +*/ +static WERROR dcesrv_eventlog6_EvtRpcClose(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcClose *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcCancel +*/ +static WERROR dcesrv_eventlog6_EvtRpcCancel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcCancel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcAssertConfig +*/ +static WERROR dcesrv_eventlog6_EvtRpcAssertConfig(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcAssertConfig *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcRetractConfig +*/ +static WERROR dcesrv_eventlog6_EvtRpcRetractConfig(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcRetractConfig *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcOpenLogHandle +*/ +static WERROR dcesrv_eventlog6_EvtRpcOpenLogHandle(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcOpenLogHandle *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetLogFileInfo +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetLogFileInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetLogFileInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetChannelList +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetChannelList(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetChannelList *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetChannelConfig +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetChannelConfig(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetChannelConfig *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcPutChannelConfig +*/ +static WERROR dcesrv_eventlog6_EvtRpcPutChannelConfig(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcPutChannelConfig *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetPublisherList +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetPublisherList(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetPublisherList *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetPublisherListForChannel +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetPublisherListForChannel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetPublisherListForChannel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetPublisherMetadata +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetPublisherMetadata(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetPublisherMetadata *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetPublisherResourceMetadata +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetPublisherResourceMetadata(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetPublisherResourceMetadata *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetEventMetadataEnum +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetEventMetadataEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetEventMetadataEnum *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetNextEventMetadata +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetNextEventMetadata(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetNextEventMetadata *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + eventlog6_EvtRpcGetClassicLogDisplayName +*/ +static WERROR dcesrv_eventlog6_EvtRpcGetClassicLogDisplayName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct eventlog6_EvtRpcGetClassicLogDisplayName *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_eventlog6_s.c" diff --git a/source4/rpc_server/lsa/dcesrv_lsa.c b/source4/rpc_server/lsa/dcesrv_lsa.c new file mode 100644 index 0000000..575bf84 --- /dev/null +++ b/source4/rpc_server/lsa/dcesrv_lsa.c @@ -0,0 +1,4847 @@ +/* need access mask/acl implementation */ + +/* + Unix SMB/CIFS implementation. + + endpoint server for the lsarpc pipe + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2008 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "rpc_server/lsa/lsa.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "librpc/gen_ndr/ndr_drsblobs.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "lib/util/tsort.h" +#include "dsdb/common/util.h" +#include "libcli/security/session.h" +#include "libcli/lsarpc/util_lsarpc.h" +#include "lib/messaging/irpc.h" +#include "libds/common/roles.h" +#include "lib/util/smb_strtox.h" +#include "lib/param/loadparm.h" +#include "librpc/rpc/dcerpc_helper.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef strcasecmp + +#define DCESRV_INTERFACE_LSARPC_BIND(context, iface) \ + dcesrv_interface_lsarpc_bind(context, iface) +static NTSTATUS dcesrv_interface_lsarpc_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_reject_connect(context, iface); +} + +static NTSTATUS lsarpc__op_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server); +static const struct dcesrv_interface dcesrv_lsarpc_interface; + +#define NCACN_NP_PIPE_NETLOGON "ncacn_np:[\\pipe\\netlogon]" +#define NCACN_NP_PIPE_LSASS "ncacn_np:[\\pipe\\lsass]" +#define DCESRV_INTERFACE_LSARPC_NCACN_NP_SECONDARY_ENDPOINT NCACN_NP_PIPE_LSASS + +#define DCESRV_INTERFACE_LSARPC_INIT_SERVER \ + dcesrv_interface_lsarpc_init_server +static NTSTATUS dcesrv_interface_lsarpc_init_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + if (lpcfg_lsa_over_netlogon(dce_ctx->lp_ctx)) { + NTSTATUS ret = dcesrv_interface_register(dce_ctx, + NCACN_NP_PIPE_NETLOGON, + NCACN_NP_PIPE_LSASS, + &dcesrv_lsarpc_interface, NULL); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1,("lsarpc_op_init_server: failed to register endpoint '\\pipe\\netlogon'\n")); + return ret; + } + } + return lsarpc__op_init_server(dce_ctx, ep_server); +} + +/* + this type allows us to distinguish handle types +*/ + +/* + state associated with a lsa_OpenAccount() operation +*/ +struct lsa_account_state { + struct lsa_policy_state *policy; + uint32_t access_mask; + struct dom_sid *account_sid; +}; + + +/* + state associated with a lsa_OpenSecret() operation +*/ +struct lsa_secret_state { + struct lsa_policy_state *policy; + uint32_t access_mask; + struct ldb_dn *secret_dn; + struct ldb_context *sam_ldb; + bool global; +}; + +/* + state associated with a lsa_OpenTrustedDomain() operation +*/ +struct lsa_trusted_domain_state { + struct lsa_policy_state *policy; + uint32_t access_mask; + struct ldb_dn *trusted_domain_dn; + struct ldb_dn *trusted_domain_user_dn; +}; + +static bool dcesrc_lsa_valid_AccountRight(const char *right) +{ + enum sec_privilege priv_id; + uint32_t right_bit; + + priv_id = sec_privilege_id(right); + if (priv_id != SEC_PRIV_INVALID) { + return true; + } + + right_bit = sec_right_bit(right); + if (right_bit != 0) { + return true; + } + + return false; +} + +/* + this is based on the samba3 function make_lsa_object_sd() + It uses the same logic, but with samba4 helper functions + */ +static NTSTATUS dcesrv_build_lsa_sd(TALLOC_CTX *mem_ctx, + struct security_descriptor **sd, + struct dom_sid *sid, + uint32_t sid_access) +{ + NTSTATUS status; + uint32_t rid; + struct dom_sid *domain_sid, *domain_admins_sid; + const char *domain_admins_sid_str, *sidstr; + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + + status = dom_sid_split_rid(tmp_ctx, sid, &domain_sid, &rid); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(tmp_ctx); + return status; + } + + domain_admins_sid = dom_sid_add_rid(tmp_ctx, domain_sid, DOMAIN_RID_ADMINS); + if (domain_admins_sid == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + domain_admins_sid_str = dom_sid_string(tmp_ctx, domain_admins_sid); + if (domain_admins_sid_str == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + sidstr = dom_sid_string(tmp_ctx, sid); + if (sidstr == NULL) { + TALLOC_FREE(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + *sd = security_descriptor_dacl_create(mem_ctx, + 0, sidstr, NULL, + + SID_WORLD, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_EXECUTE | SEC_GENERIC_READ, 0, + + SID_BUILTIN_ADMINISTRATORS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, 0, + + SID_BUILTIN_ACCOUNT_OPERATORS, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, 0, + + domain_admins_sid_str, + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_GENERIC_ALL, 0, + + sidstr, + SEC_ACE_TYPE_ACCESS_ALLOWED, + sid_access, 0, + + NULL); + talloc_free(tmp_ctx); + + NT_STATUS_HAVE_NO_MEMORY(*sd); + + return NT_STATUS_OK; +} + + +static NTSTATUS dcesrv_lsa_EnumAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_EnumAccountRights *r); + +static NTSTATUS dcesrv_lsa_AddRemoveAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_policy_state *state, + int ldb_flag, + struct dom_sid *sid, + const struct lsa_RightSet *rights); + +/* + lsa_Close +*/ +static NTSTATUS dcesrv_lsa_Close(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_Close *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_handle *h; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + *r->out.handle = *r->in.handle; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + talloc_free(h); + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; +} + + +/* + lsa_Delete +*/ +static NTSTATUS dcesrv_lsa_Delete(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_Delete *r) +{ + return NT_STATUS_NOT_SUPPORTED; +} + + +/* + lsa_DeleteObject +*/ +static NTSTATUS dcesrv_lsa_DeleteObject(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_DeleteObject *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + if (h->wire_handle.handle_type == LSA_HANDLE_SECRET) { + struct lsa_secret_state *secret_state = h->data; + + /* Ensure user is permitted to delete this... */ + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + break; + default: + /* Users and anonymous are not allowed to delete things */ + return NT_STATUS_ACCESS_DENIED; + } + + ret = ldb_delete(secret_state->sam_ldb, + secret_state->secret_dn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INVALID_HANDLE; + } + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; + + } else if (h->wire_handle.handle_type == LSA_HANDLE_TRUSTED_DOMAIN) { + struct lsa_trusted_domain_state *trusted_domain_state = + talloc_get_type(h->data, struct lsa_trusted_domain_state); + ret = ldb_transaction_start(trusted_domain_state->policy->sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ret = ldb_delete(trusted_domain_state->policy->sam_ldb, + trusted_domain_state->trusted_domain_dn); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(trusted_domain_state->policy->sam_ldb); + return NT_STATUS_INVALID_HANDLE; + } + + if (trusted_domain_state->trusted_domain_user_dn) { + ret = ldb_delete(trusted_domain_state->policy->sam_ldb, + trusted_domain_state->trusted_domain_user_dn); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(trusted_domain_state->policy->sam_ldb); + return NT_STATUS_INVALID_HANDLE; + } + } + + ret = ldb_transaction_commit(trusted_domain_state->policy->sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; + + } else if (h->wire_handle.handle_type == LSA_HANDLE_ACCOUNT) { + struct lsa_RightSet *rights; + struct lsa_account_state *astate; + struct lsa_EnumAccountRights r2; + NTSTATUS status; + + rights = talloc(mem_ctx, struct lsa_RightSet); + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_ACCOUNT); + + astate = h->data; + + r2.in.handle = &astate->policy->handle->wire_handle; + r2.in.sid = astate->account_sid; + r2.out.rights = rights; + + /* dcesrv_lsa_EnumAccountRights takes a LSA_HANDLE_POLICY, + but we have a LSA_HANDLE_ACCOUNT here, so this call + will always fail */ + status = dcesrv_lsa_EnumAccountRights(dce_call, mem_ctx, &r2); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, astate->policy, + LDB_FLAG_MOD_DELETE, astate->account_sid, + r2.out.rights); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; + } + + return NT_STATUS_INVALID_HANDLE; +} + + +/* + lsa_EnumPrivs +*/ +static NTSTATUS dcesrv_lsa_EnumPrivs(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_EnumPrivs *r) +{ + struct dcesrv_handle *h; + uint32_t i; + enum sec_privilege priv; + const char *privname; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + i = *r->in.resume_handle; + + while (((priv = sec_privilege_from_index(i)) != SEC_PRIV_INVALID) && + r->out.privs->count < r->in.max_count) { + struct lsa_PrivEntry *e; + privname = sec_privilege_name(priv); + r->out.privs->privs = talloc_realloc(r->out.privs, + r->out.privs->privs, + struct lsa_PrivEntry, + r->out.privs->count+1); + if (r->out.privs->privs == NULL) { + return NT_STATUS_NO_MEMORY; + } + e = &r->out.privs->privs[r->out.privs->count]; + e->luid.low = priv; + e->luid.high = 0; + e->name.string = privname; + r->out.privs->count++; + i++; + } + + *r->out.resume_handle = i; + + return NT_STATUS_OK; +} + + +/* + lsa_QuerySecObj +*/ +static NTSTATUS dcesrv_lsa_QuerySecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QuerySecurity *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + const struct security_descriptor *sd = NULL; + uint32_t access_granted = 0; + struct sec_desc_buf *sdbuf = NULL; + NTSTATUS status; + struct dom_sid *sid; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + sid = &session_info->security_token->sids[PRIMARY_USER_SID_INDEX]; + + if (h->wire_handle.handle_type == LSA_HANDLE_POLICY) { + struct lsa_policy_state *pstate = h->data; + + sd = pstate->sd; + access_granted = pstate->access_mask; + + } else if (h->wire_handle.handle_type == LSA_HANDLE_ACCOUNT) { + struct lsa_account_state *astate = h->data; + struct security_descriptor *_sd = NULL; + + status = dcesrv_build_lsa_sd(mem_ctx, &_sd, sid, + LSA_ACCOUNT_ALL_ACCESS); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + sd = _sd; + access_granted = astate->access_mask; + } else { + return NT_STATUS_INVALID_HANDLE; + } + + sdbuf = talloc_zero(mem_ctx, struct sec_desc_buf); + if (sdbuf == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = security_descriptor_for_client(sdbuf, sd, r->in.sec_info, + access_granted, &sdbuf->sd); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.sdbuf = sdbuf; + + return NT_STATUS_OK; +} + + +/* + lsa_SetSecObj +*/ +static NTSTATUS dcesrv_lsa_SetSecObj(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetSecObj *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_ChangePassword +*/ +static NTSTATUS dcesrv_lsa_ChangePassword(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_ChangePassword *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* + dssetup_DsRoleGetPrimaryDomainInformation + + This is not an LSA call, but is the only call left on the DSSETUP + pipe (after the pipe was truncated), and needs lsa_get_policy_state +*/ +static WERROR dcesrv_dssetup_DsRoleGetPrimaryDomainInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleGetPrimaryDomainInformation *r) +{ + union dssetup_DsRoleInfo *info; + + info = talloc_zero(mem_ctx, union dssetup_DsRoleInfo); + W_ERROR_HAVE_NO_MEMORY(info); + + switch (r->in.level) { + case DS_ROLE_BASIC_INFORMATION: + { + enum dssetup_DsRole role = DS_ROLE_STANDALONE_SERVER; + uint32_t flags = 0; + const char *domain = NULL; + const char *dns_domain = NULL; + const char *forest = NULL; + struct GUID domain_guid; + struct lsa_policy_state *state; + + NTSTATUS status = dcesrv_lsa_get_policy_state(dce_call, mem_ctx, + 0, /* we skip access checks */ + &state); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + ZERO_STRUCT(domain_guid); + + switch (lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx)) { + case ROLE_STANDALONE: + role = DS_ROLE_STANDALONE_SERVER; + break; + case ROLE_DOMAIN_MEMBER: + role = DS_ROLE_MEMBER_SERVER; + break; + case ROLE_ACTIVE_DIRECTORY_DC: + if (samdb_is_pdc(state->sam_ldb)) { + role = DS_ROLE_PRIMARY_DC; + } else { + role = DS_ROLE_BACKUP_DC; + } + break; + } + + switch (lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx)) { + case ROLE_STANDALONE: + domain = talloc_strdup(mem_ctx, lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(domain); + break; + case ROLE_DOMAIN_MEMBER: + domain = talloc_strdup(mem_ctx, lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(domain); + /* TODO: what is with dns_domain and forest and guid? */ + break; + case ROLE_ACTIVE_DIRECTORY_DC: + flags = DS_ROLE_PRIMARY_DS_RUNNING; + + if (state->mixed_domain == 1) { + flags |= DS_ROLE_PRIMARY_DS_MIXED_MODE; + } + + domain = state->domain_name; + dns_domain = state->domain_dns; + forest = state->forest_dns; + + domain_guid = state->domain_guid; + flags |= DS_ROLE_PRIMARY_DOMAIN_GUID_PRESENT; + break; + } + + info->basic.role = role; + info->basic.flags = flags; + info->basic.domain = domain; + info->basic.dns_domain = dns_domain; + info->basic.forest = forest; + info->basic.domain_guid = domain_guid; + + r->out.info = info; + return WERR_OK; + } + case DS_ROLE_UPGRADE_STATUS: + { + info->upgrade.upgrading = DS_ROLE_NOT_UPGRADING; + info->upgrade.previous_role = DS_ROLE_PREVIOUS_UNKNOWN; + + r->out.info = info; + return WERR_OK; + } + case DS_ROLE_OP_STATUS: + { + info->opstatus.status = DS_ROLE_OP_IDLE; + + r->out.info = info; + return WERR_OK; + } + default: + return WERR_INVALID_PARAMETER; + } +} + +/* + fill in the AccountDomain info +*/ +static NTSTATUS dcesrv_lsa_info_AccountDomain(struct lsa_policy_state *state, TALLOC_CTX *mem_ctx, + struct lsa_DomainInfo *info) +{ + info->name.string = state->domain_name; + info->sid = state->domain_sid; + + return NT_STATUS_OK; +} + +/* + fill in the DNS domain info +*/ +static NTSTATUS dcesrv_lsa_info_DNS(struct lsa_policy_state *state, TALLOC_CTX *mem_ctx, + struct lsa_DnsDomainInfo *info) +{ + info->name.string = state->domain_name; + info->sid = state->domain_sid; + info->dns_domain.string = state->domain_dns; + info->dns_forest.string = state->forest_dns; + info->domain_guid = state->domain_guid; + + return NT_STATUS_OK; +} + +/* + lsa_QueryInfoPolicy2 +*/ +static NTSTATUS dcesrv_lsa_QueryInfoPolicy2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QueryInfoPolicy2 *r) +{ + struct lsa_policy_state *state; + struct dcesrv_handle *h; + union lsa_PolicyInformation *info; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + info = talloc_zero(mem_ctx, union lsa_PolicyInformation); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + *r->out.info = info; + + switch (r->in.level) { + case LSA_POLICY_INFO_AUDIT_LOG: + /* we don't need to fill in any of this */ + ZERO_STRUCT(info->audit_log); + return NT_STATUS_OK; + case LSA_POLICY_INFO_AUDIT_EVENTS: + /* we don't need to fill in any of this */ + ZERO_STRUCT(info->audit_events); + return NT_STATUS_OK; + case LSA_POLICY_INFO_PD: + /* we don't need to fill in any of this */ + ZERO_STRUCT(info->pd); + return NT_STATUS_OK; + + case LSA_POLICY_INFO_DOMAIN: + return dcesrv_lsa_info_AccountDomain(state, mem_ctx, &info->domain); + case LSA_POLICY_INFO_ACCOUNT_DOMAIN: + return dcesrv_lsa_info_AccountDomain(state, mem_ctx, &info->account_domain); + case LSA_POLICY_INFO_L_ACCOUNT_DOMAIN: + return dcesrv_lsa_info_AccountDomain(state, mem_ctx, &info->l_account_domain); + + case LSA_POLICY_INFO_ROLE: + info->role.role = LSA_ROLE_PRIMARY; + return NT_STATUS_OK; + + case LSA_POLICY_INFO_DNS: + case LSA_POLICY_INFO_DNS_INT: + return dcesrv_lsa_info_DNS(state, mem_ctx, &info->dns); + + case LSA_POLICY_INFO_REPLICA: + ZERO_STRUCT(info->replica); + return NT_STATUS_OK; + + case LSA_POLICY_INFO_QUOTA: + ZERO_STRUCT(info->quota); + return NT_STATUS_OK; + + case LSA_POLICY_INFO_MOD: + case LSA_POLICY_INFO_AUDIT_FULL_SET: + case LSA_POLICY_INFO_AUDIT_FULL_QUERY: + /* windows gives INVALID_PARAMETER */ + *r->out.info = NULL; + return NT_STATUS_INVALID_PARAMETER; + } + + *r->out.info = NULL; + return NT_STATUS_INVALID_INFO_CLASS; +} + +/* + lsa_QueryInfoPolicy +*/ +static NTSTATUS dcesrv_lsa_QueryInfoPolicy(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QueryInfoPolicy *r) +{ + struct lsa_QueryInfoPolicy2 r2; + NTSTATUS status; + + ZERO_STRUCT(r2); + + r2.in.handle = r->in.handle; + r2.in.level = r->in.level; + r2.out.info = r->out.info; + + status = dcesrv_lsa_QueryInfoPolicy2(dce_call, mem_ctx, &r2); + + return status; +} + +/* + lsa_SetInfoPolicy +*/ +static NTSTATUS dcesrv_lsa_SetInfoPolicy(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetInfoPolicy *r) +{ + /* need to support this */ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_ClearAuditLog +*/ +static NTSTATUS dcesrv_lsa_ClearAuditLog(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_ClearAuditLog *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +static const struct generic_mapping dcesrv_lsa_account_mapping = { + LSA_ACCOUNT_READ, + LSA_ACCOUNT_WRITE, + LSA_ACCOUNT_EXECUTE, + LSA_ACCOUNT_ALL_ACCESS +}; + +/* + lsa_CreateAccount + + This call does not seem to have any long-term effects, hence no database operations + + we need to talk to the MS product group to find out what this account database means! + + answer is that the lsa database is totally separate from the SAM and + ldap databases. We are going to need a separate ldb to store these + accounts. The SIDs on this account bear no relation to the SIDs in + AD +*/ +static NTSTATUS dcesrv_lsa_CreateAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CreateAccount *r) +{ + struct lsa_account_state *astate; + + struct lsa_policy_state *state; + struct dcesrv_handle *h, *ah; + + ZERO_STRUCTP(r->out.acct_handle); + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + astate = talloc(dce_call->conn, struct lsa_account_state); + if (astate == NULL) { + return NT_STATUS_NO_MEMORY; + } + + astate->account_sid = dom_sid_dup(astate, r->in.sid); + if (astate->account_sid == NULL) { + talloc_free(astate); + return NT_STATUS_NO_MEMORY; + } + + astate->policy = talloc_reference(astate, state); + astate->access_mask = r->in.access_mask; + + /* + * For now we grant all requested access. + * + * We will fail at the ldb layer later. + */ + if (astate->access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + astate->access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED; + astate->access_mask |= LSA_ACCOUNT_ALL_ACCESS; + } + se_map_generic(&astate->access_mask, &dcesrv_lsa_account_mapping); + + DEBUG(10,("%s: %s access desired[0x%08X] granted[0x%08X].\n", + __func__, dom_sid_string(mem_ctx, astate->account_sid), + (unsigned)r->in.access_mask, + (unsigned)astate->access_mask)); + + ah = dcesrv_handle_create(dce_call, LSA_HANDLE_ACCOUNT); + if (!ah) { + talloc_free(astate); + return NT_STATUS_NO_MEMORY; + } + + ah->data = talloc_steal(ah, astate); + + *r->out.acct_handle = ah->wire_handle; + + return NT_STATUS_OK; +} + + +/* + lsa_EnumAccounts +*/ +static NTSTATUS dcesrv_lsa_EnumAccounts(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_EnumAccounts *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *state; + int ret; + struct ldb_message **res; + const char * const attrs[] = { "objectSid", NULL}; + uint32_t count, i; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + /* NOTE: This call must only return accounts that have at least + one privilege set + */ + ret = gendb_search(state->pdb, mem_ctx, NULL, &res, attrs, + "(&(objectSid=*)(privilege=*))"); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (*r->in.resume_handle >= ret) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + count = ret - *r->in.resume_handle; + if (count > r->in.num_entries) { + count = r->in.num_entries; + } + + if (count == 0) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + r->out.sids->sids = talloc_array(r->out.sids, struct lsa_SidPtr, count); + if (r->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<count;i++) { + r->out.sids->sids[i].sid = + samdb_result_dom_sid(r->out.sids->sids, + res[i + *r->in.resume_handle], + "objectSid"); + NT_STATUS_HAVE_NO_MEMORY(r->out.sids->sids[i].sid); + } + + r->out.sids->num_sids = count; + *r->out.resume_handle = count + *r->in.resume_handle; + + return NT_STATUS_OK; +} + +/* This decrypts and returns Trusted Domain Auth Information Internal data */ +static NTSTATUS get_trustdom_auth_blob(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, DATA_BLOB *auth_blob, + struct trustDomainPasswords *auth_struct) +{ + DATA_BLOB session_key = data_blob(NULL, 0); + enum ndr_err_code ndr_err; + NTSTATUS nt_status; + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + int rc; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + bool encrypted; + + encrypted = + dcerpc_is_transport_encrypted(session_info); + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + DBG_ERR("Transport isn't encrypted and weak crypto disallowed!\n"); + return NT_STATUS_ACCESS_DENIED; + } + + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + if (rc < 0) { + GNUTLS_FIPS140_SET_STRICT_MODE(); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + auth_blob->data, + auth_blob->length); + gnutls_cipher_deinit(cipher_hnd); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + ndr_err = ndr_pull_struct_blob(auth_blob, mem_ctx, + auth_struct, + (ndr_pull_flags_fn_t)ndr_pull_trustDomainPasswords); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = NT_STATUS_OK; +out: + return nt_status; +} + +static NTSTATUS get_trustauth_inout_blob(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct trustAuthInOutBlob *iopw, + DATA_BLOB *trustauth_blob) +{ + enum ndr_err_code ndr_err; + + if (iopw->current.count != iopw->count) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (iopw->previous.count > iopw->current.count) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (iopw->previous.count == 0) { + /* + * If the previous credentials are not present + * we need to make a copy. + */ + iopw->previous = iopw->current; + } + + if (iopw->previous.count < iopw->current.count) { + struct AuthenticationInformationArray *c = &iopw->current; + struct AuthenticationInformationArray *p = &iopw->previous; + + /* + * The previous array needs to have the same size + * as the current one. + * + * We may have to fill with TRUST_AUTH_TYPE_NONE + * elements. + */ + p->array = talloc_realloc(mem_ctx, p->array, + struct AuthenticationInformation, + c->count); + if (p->array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + while (p->count < c->count) { + struct AuthenticationInformation *a = + &p->array[p->count++]; + + *a = (struct AuthenticationInformation) { + .LastUpdateTime = p->array[0].LastUpdateTime, + .AuthType = TRUST_AUTH_TYPE_NONE, + }; + } + } + + ndr_err = ndr_push_struct_blob(trustauth_blob, mem_ctx, + iopw, + (ndr_push_flags_fn_t)ndr_push_trustAuthInOutBlob); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return NT_STATUS_OK; +} + +static NTSTATUS add_trust_user(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ldb, + struct ldb_dn *base_dn, + const char *netbios_name, + struct trustAuthInOutBlob *in, + struct ldb_dn **user_dn) +{ + struct ldb_request *req; + struct ldb_message *msg; + struct ldb_dn *dn; + uint32_t i; + int ret; + + dn = ldb_dn_copy(mem_ctx, base_dn); + if (!dn) { + return NT_STATUS_NO_MEMORY; + } + if (!ldb_dn_add_child_fmt(dn, "cn=%s$,cn=users", netbios_name)) { + return NT_STATUS_NO_MEMORY; + } + + msg = ldb_msg_new(mem_ctx); + if (!msg) { + return NT_STATUS_NO_MEMORY; + } + msg->dn = dn; + + ret = ldb_msg_add_string(msg, "objectClass", "user"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_fmt(msg, "samAccountName", "%s$", netbios_name); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = samdb_msg_add_uint(sam_ldb, msg, msg, "userAccountControl", + UF_INTERDOMAIN_TRUST_ACCOUNT); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < in->count; i++) { + const char *attribute; + struct ldb_val v; + switch (in->current.array[i].AuthType) { + case TRUST_AUTH_TYPE_NT4OWF: + attribute = "unicodePwd"; + v.data = (uint8_t *)&in->current.array[i].AuthInfo.nt4owf.password; + v.length = 16; + break; + case TRUST_AUTH_TYPE_CLEAR: + attribute = "clearTextPassword"; + v.data = in->current.array[i].AuthInfo.clear.password; + v.length = in->current.array[i].AuthInfo.clear.size; + break; + default: + continue; + } + + ret = ldb_msg_add_value(msg, attribute, &v, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* create the trusted_domain user account */ + ret = ldb_build_add_req(&req, sam_ldb, mem_ctx, msg, NULL, NULL, + ldb_op_default_callback, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_request_add_control(req, DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID, + false, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = dsdb_autotransaction_request(sam_ldb, req); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ldb))); + + switch (ret) { + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_DOMAIN_EXISTS; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + if (user_dn) { + *user_dn = dn; + } + return NT_STATUS_OK; +} + +/* + lsa_CreateTrustedDomainEx2 +*/ +static NTSTATUS dcesrv_lsa_CreateTrustedDomain_base(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_CreateTrustedDomainEx2 *r, + int op, + struct lsa_TrustDomainInfoAuthInfo *unencrypted_auth_info) +{ + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct lsa_trusted_domain_state *trusted_domain_state; + struct dcesrv_handle *handle; + struct ldb_message **msgs, *msg; + const char *attrs[] = { + NULL + }; + const char *netbios_name; + const char *dns_name; + DATA_BLOB trustAuthIncoming, trustAuthOutgoing, auth_blob; + struct trustDomainPasswords auth_struct; + int ret; + NTSTATUS nt_status; + struct ldb_context *sam_ldb; + struct server_id *server_ids = NULL; + uint32_t num_server_ids = 0; + NTSTATUS status; + bool ok; + char *dns_encoded = NULL; + char *netbios_encoded = NULL; + char *sid_encoded = NULL; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + DCESRV_PULL_HANDLE(policy_handle, r->in.policy_handle, LSA_HANDLE_POLICY); + ZERO_STRUCTP(r->out.trustdom_handle); + + policy_state = policy_handle->data; + sam_ldb = policy_state->sam_ldb; + + netbios_name = r->in.info->netbios_name.string; + if (!netbios_name) { + return NT_STATUS_INVALID_PARAMETER; + } + + dns_name = r->in.info->domain_name.string; + if (dns_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.info->sid == NULL) { + return NT_STATUS_INVALID_SID; + } + + /* + * We expect S-1-5-21-A-B-C, but we don't + * allow S-1-5-21-0-0-0 as this is used + * for claims and compound identities. + */ + ok = dom_sid_is_valid_account_domain(r->in.info->sid); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + dns_encoded = ldb_binary_encode_string(mem_ctx, dns_name); + if (dns_encoded == NULL) { + return NT_STATUS_NO_MEMORY; + } + netbios_encoded = ldb_binary_encode_string(mem_ctx, netbios_name); + if (netbios_encoded == NULL) { + return NT_STATUS_NO_MEMORY; + } + sid_encoded = ldap_encode_ndr_dom_sid(mem_ctx, r->in.info->sid); + if (sid_encoded == NULL) { + return NT_STATUS_NO_MEMORY; + } + + trusted_domain_state = talloc_zero(mem_ctx, struct lsa_trusted_domain_state); + if (!trusted_domain_state) { + return NT_STATUS_NO_MEMORY; + } + trusted_domain_state->policy = policy_state; + + if (strcasecmp(netbios_name, "BUILTIN") == 0 + || (strcasecmp(dns_name, "BUILTIN") == 0) + || (dom_sid_in_domain(policy_state->builtin_sid, r->in.info->sid))) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcasecmp(netbios_name, policy_state->domain_name) == 0 + || strcasecmp(netbios_name, policy_state->domain_dns) == 0 + || strcasecmp(dns_name, policy_state->domain_dns) == 0 + || strcasecmp(dns_name, policy_state->domain_name) == 0 + || (dom_sid_equal(policy_state->domain_sid, r->in.info->sid))) { + return NT_STATUS_CURRENT_DOMAIN_NOT_ALLOWED; + } + + /* While this is a REF pointer, some of the functions that wrap this don't provide this */ + if (op == NDR_LSA_CREATETRUSTEDDOMAIN) { + /* No secrets are created at this time, for this function */ + auth_struct.outgoing.count = 0; + auth_struct.incoming.count = 0; + } else if (op == NDR_LSA_CREATETRUSTEDDOMAINEX2) { + auth_blob = data_blob_const(r->in.auth_info_internal->auth_blob.data, + r->in.auth_info_internal->auth_blob.size); + nt_status = get_trustdom_auth_blob(dce_call, mem_ctx, + &auth_blob, &auth_struct); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else if (op == NDR_LSA_CREATETRUSTEDDOMAINEX) { + + if (unencrypted_auth_info->incoming_count > 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* more investigation required here, do not create secrets for + * now */ + auth_struct.outgoing.count = 0; + auth_struct.incoming.count = 0; + } else { + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_struct.incoming.count) { + nt_status = get_trustauth_inout_blob(dce_call, mem_ctx, + &auth_struct.incoming, + &trustAuthIncoming); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + trustAuthIncoming = data_blob(NULL, 0); + } + + if (auth_struct.outgoing.count) { + nt_status = get_trustauth_inout_blob(dce_call, mem_ctx, + &auth_struct.outgoing, + &trustAuthOutgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + trustAuthOutgoing = data_blob(NULL, 0); + } + + ret = ldb_transaction_start(sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* search for the trusted_domain record */ + ret = gendb_search(sam_ldb, + mem_ctx, policy_state->system_dn, &msgs, attrs, + "(&(objectClass=trustedDomain)(|" + "(flatname=%s)(trustPartner=%s)" + "(flatname=%s)(trustPartner=%s)" + "(securityIdentifier=%s)))", + dns_encoded, dns_encoded, + netbios_encoded, netbios_encoded, + sid_encoded); + if (ret > 0) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + if (ret < 0) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, policy_state->system_dn); + if ( ! ldb_dn_add_child_fmt(msg->dn, "cn=%s", dns_name)) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "objectClass", "trustedDomain"); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + ret = ldb_msg_add_string(msg, "flatname", netbios_name); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "trustPartner", dns_name); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + ret = samdb_msg_add_dom_sid(sam_ldb, mem_ctx, msg, "securityIdentifier", + r->in.info->sid); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + ret = samdb_msg_add_int(sam_ldb, mem_ctx, msg, "trustType", r->in.info->trust_type); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + ret = samdb_msg_add_int(sam_ldb, mem_ctx, msg, "trustAttributes", r->in.info->trust_attributes); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + ret = samdb_msg_add_int(sam_ldb, mem_ctx, msg, "trustDirection", r->in.info->trust_direction); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY;; + } + + if (trustAuthIncoming.data) { + ret = ldb_msg_add_value(msg, "trustAuthIncoming", &trustAuthIncoming, NULL); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY; + } + } + if (trustAuthOutgoing.data) { + ret = ldb_msg_add_value(msg, "trustAuthOutgoing", &trustAuthOutgoing, NULL); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ldb); + return NT_STATUS_NO_MEMORY; + } + } + + trusted_domain_state->trusted_domain_dn = talloc_reference(trusted_domain_state, msg->dn); + + /* create the trusted_domain */ + ret = ldb_add(sam_ldb, msg); + switch (ret) { + case LDB_SUCCESS: + break; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + ldb_transaction_cancel(sam_ldb); + DEBUG(0,("Failed to create trusted domain record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ldb))); + return NT_STATUS_DOMAIN_EXISTS; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + ldb_transaction_cancel(sam_ldb); + DEBUG(0,("Failed to create trusted domain record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ldb))); + return NT_STATUS_ACCESS_DENIED; + default: + ldb_transaction_cancel(sam_ldb); + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ldb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (r->in.info->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + struct ldb_dn *user_dn; + /* Inbound trusts must also create a cn=users object to match */ + nt_status = add_trust_user(mem_ctx, sam_ldb, + policy_state->domain_dn, + netbios_name, + &auth_struct.incoming, + &user_dn); + if (!NT_STATUS_IS_OK(nt_status)) { + ldb_transaction_cancel(sam_ldb); + return nt_status; + } + + /* save the trust user dn */ + trusted_domain_state->trusted_domain_user_dn + = talloc_steal(trusted_domain_state, user_dn); + } + + ret = ldb_transaction_commit(sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* + * Notify winbindd that we have a new trust + */ + status = irpc_servers_byname(imsg_ctx, + mem_ctx, + "winbind_server", + &num_server_ids, + &server_ids); + if (NT_STATUS_IS_OK(status) && num_server_ids >= 1) { + imessaging_send(imsg_ctx, + server_ids[0], + MSG_WINBIND_RELOAD_TRUSTED_DOMAINS, + NULL); + } + TALLOC_FREE(server_ids); + + handle = dcesrv_handle_create(dce_call, LSA_HANDLE_TRUSTED_DOMAIN); + if (!handle) { + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, trusted_domain_state); + + trusted_domain_state->access_mask = r->in.access_mask; + trusted_domain_state->policy = talloc_reference(trusted_domain_state, policy_state); + + *r->out.trustdom_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + +/* + lsa_CreateTrustedDomainEx2 +*/ +static NTSTATUS dcesrv_lsa_CreateTrustedDomainEx2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_CreateTrustedDomainEx2 *r) +{ + return dcesrv_lsa_CreateTrustedDomain_base(dce_call, mem_ctx, r, NDR_LSA_CREATETRUSTEDDOMAINEX2, NULL); +} +/* + lsa_CreateTrustedDomainEx +*/ +static NTSTATUS dcesrv_lsa_CreateTrustedDomainEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_CreateTrustedDomainEx *r) +{ + struct lsa_CreateTrustedDomainEx2 r2; + + r2.in.policy_handle = r->in.policy_handle; + r2.in.info = r->in.info; + r2.out.trustdom_handle = r->out.trustdom_handle; + return dcesrv_lsa_CreateTrustedDomain_base(dce_call, mem_ctx, &r2, NDR_LSA_CREATETRUSTEDDOMAINEX, r->in.auth_info); +} + +/* + lsa_CreateTrustedDomain +*/ +static NTSTATUS dcesrv_lsa_CreateTrustedDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CreateTrustedDomain *r) +{ + struct lsa_CreateTrustedDomainEx2 r2; + + r2.in.policy_handle = r->in.policy_handle; + r2.in.info = talloc(mem_ctx, struct lsa_TrustDomainInfoInfoEx); + if (!r2.in.info) { + return NT_STATUS_NO_MEMORY; + } + + r2.in.info->domain_name = r->in.info->name; + r2.in.info->netbios_name = r->in.info->name; + r2.in.info->sid = r->in.info->sid; + r2.in.info->trust_direction = LSA_TRUST_DIRECTION_OUTBOUND; + r2.in.info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL; + r2.in.info->trust_attributes = 0; + + r2.in.access_mask = r->in.access_mask; + r2.out.trustdom_handle = r->out.trustdom_handle; + + return dcesrv_lsa_CreateTrustedDomain_base(dce_call, mem_ctx, &r2, NDR_LSA_CREATETRUSTEDDOMAIN, NULL); +} + +static NTSTATUS dcesrv_lsa_OpenTrustedDomain_common( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *tmp_mem, + struct lsa_policy_state *policy_state, + const char *filter, + uint32_t access_mask, + struct dcesrv_handle **_handle) +{ + struct lsa_trusted_domain_state *trusted_domain_state; + struct dcesrv_handle *handle; + struct ldb_message **msgs; + const char *attrs[] = { + "trustDirection", + "flatname", + NULL + }; + uint32_t direction; + int ret; + + /* TODO: perform access checks */ + + /* search for the trusted_domain record */ + ret = gendb_search(policy_state->sam_ldb, tmp_mem, + policy_state->system_dn, + &msgs, attrs, "%s", filter); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ret != 1) { + DEBUG(0,("Found %d records matching %s under %s\n", ret, + filter, + ldb_dn_get_linearized(policy_state->system_dn))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + trusted_domain_state = talloc_zero(tmp_mem, + struct lsa_trusted_domain_state); + if (!trusted_domain_state) { + return NT_STATUS_NO_MEMORY; + } + trusted_domain_state->policy = policy_state; + + trusted_domain_state->trusted_domain_dn = + talloc_steal(trusted_domain_state, msgs[0]->dn); + + direction = ldb_msg_find_attr_as_int(msgs[0], "trustDirection", 0); + if (direction & LSA_TRUST_DIRECTION_INBOUND) { + const char *flatname = ldb_msg_find_attr_as_string(msgs[0], + "flatname", NULL); + + /* search for the trusted_domain account */ + ret = gendb_search(policy_state->sam_ldb, tmp_mem, + policy_state->domain_dn, + &msgs, attrs, + "(&(samaccountname=%s$)(objectclass=user)" + "(userAccountControl:%s:=%u))", + flatname, + LDB_OID_COMPARATOR_AND, + UF_INTERDOMAIN_TRUST_ACCOUNT); + if (ret == 1) { + trusted_domain_state->trusted_domain_user_dn = + talloc_steal(trusted_domain_state, msgs[0]->dn); + } + } + + handle = dcesrv_handle_create(dce_call, LSA_HANDLE_TRUSTED_DOMAIN); + if (!handle) { + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, trusted_domain_state); + + trusted_domain_state->access_mask = access_mask; + trusted_domain_state->policy = talloc_reference(trusted_domain_state, + policy_state); + + *_handle = handle; + + return NT_STATUS_OK; +} + +/* + lsa_OpenTrustedDomain +*/ +static NTSTATUS dcesrv_lsa_OpenTrustedDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_OpenTrustedDomain *r) +{ + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct dcesrv_handle *handle; + const char *sid_string; + char *filter; + NTSTATUS status; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + ZERO_STRUCTP(r->out.trustdom_handle); + policy_state = policy_handle->data; + + sid_string = dom_sid_string(mem_ctx, r->in.sid); + if (!sid_string) { + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, + "(&(securityIdentifier=%s)" + "(objectclass=trustedDomain))", + sid_string); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = dcesrv_lsa_OpenTrustedDomain_common(dce_call, mem_ctx, + policy_state, + filter, + r->in.access_mask, + &handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.trustdom_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + lsa_OpenTrustedDomainByName +*/ +static NTSTATUS dcesrv_lsa_OpenTrustedDomainByName(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_OpenTrustedDomainByName *r) +{ + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct dcesrv_handle *handle; + char *td_name; + char *filter; + NTSTATUS status; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + ZERO_STRUCTP(r->out.trustdom_handle); + policy_state = policy_handle->data; + + if (!r->in.name.string) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* search for the trusted_domain record */ + td_name = ldb_binary_encode_string(mem_ctx, r->in.name.string); + if (td_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + filter = talloc_asprintf(mem_ctx, + "(&(|(flatname=%s)(cn=%s)(trustPartner=%s))" + "(objectclass=trustedDomain))", + td_name, td_name, td_name); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = dcesrv_lsa_OpenTrustedDomain_common(dce_call, mem_ctx, + policy_state, + filter, + r->in.access_mask, + &handle); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.trustdom_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + + +/* + lsa_SetTrustedDomainInfo +*/ +static NTSTATUS dcesrv_lsa_SetTrustedDomainInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetTrustedDomainInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* parameters 4 to 6 are optional if the dn is a dn of a TDO object, + * otherwise at least one must be provided */ +static NTSTATUS get_tdo(struct ldb_context *sam, TALLOC_CTX *mem_ctx, + struct ldb_dn *basedn, const char *dns_domain, + const char *netbios, struct dom_sid2 *sid, + struct ldb_message ***msgs) +{ + const char *attrs[] = { "flatname", "trustPartner", + "securityIdentifier", "trustDirection", + "trustType", "trustAttributes", + "trustPosixOffset", + "msDs-supportedEncryptionTypes", + "msDS-TrustForestTrustInfo", + NULL + }; + char *dns = NULL; + char *nbn = NULL; + char *filter; + int ret; + + + if (dns_domain || netbios || sid) { + filter = talloc_strdup(mem_ctx, + "(&(objectclass=trustedDomain)(|"); + } else { + filter = talloc_strdup(mem_ctx, + "(objectclass=trustedDomain)"); + } + + if (dns_domain) { + dns = ldb_binary_encode_string(mem_ctx, dns_domain); + if (!dns) { + return NT_STATUS_NO_MEMORY; + } + talloc_asprintf_addbuf(&filter, "(trustPartner=%s)", dns); + } + if (netbios) { + nbn = ldb_binary_encode_string(mem_ctx, netbios); + if (!nbn) { + return NT_STATUS_NO_MEMORY; + } + talloc_asprintf_addbuf(&filter, "(flatname=%s)", nbn); + } + if (sid) { + struct dom_sid_buf buf; + char *sidstr = dom_sid_str_buf(sid, &buf); + talloc_asprintf_addbuf( + &filter, "(securityIdentifier=%s)", sidstr); + } + if (dns_domain || netbios || sid) { + talloc_asprintf_addbuf(&filter, "))"); + } + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(sam, mem_ctx, basedn, msgs, attrs, "%s", filter); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ret != 1) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + return NT_STATUS_OK; +} + +static NTSTATUS update_uint32_t_value(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ldb, + struct ldb_message *orig, + struct ldb_message *dest, + const char *attribute, + uint32_t value, + uint32_t *orig_value) +{ + const struct ldb_val *orig_val; + uint32_t orig_uint = 0; + unsigned int flags = 0; + int ret; + int error = 0; + + orig_val = ldb_msg_find_ldb_val(orig, attribute); + if (!orig_val || !orig_val->data) { + /* add new attribute */ + flags = LDB_FLAG_MOD_ADD; + + } else { + orig_uint = smb_strtoul((const char *)orig_val->data, + NULL, + 0, + &error, + SMB_STR_STANDARD); + if (error != 0 || orig_uint != value) { + /* replace also if can't get value */ + flags = LDB_FLAG_MOD_REPLACE; + } + } + + if (flags == 0) { + /* stored value is identical, nothing to change */ + goto done; + } + + ret = samdb_msg_append_uint(sam_ldb, dest, dest, attribute, value, flags); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + +done: + if (orig_value) { + *orig_value = orig_uint; + } + return NT_STATUS_OK; +} + +static NTSTATUS update_trust_user(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ldb, + struct ldb_dn *base_dn, + bool delete_user, + const char *netbios_name, + struct trustAuthInOutBlob *in) +{ + const char *attrs[] = { "userAccountControl", NULL }; + struct ldb_message **msgs; + struct ldb_message *msg; + uint32_t uac; + uint32_t i; + int ret; + + ret = gendb_search(sam_ldb, mem_ctx, + base_dn, &msgs, attrs, + "samAccountName=%s$", netbios_name); + if (ret > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (ret == 0) { + if (delete_user) { + return NT_STATUS_OK; + } + + /* ok no existing user, add it from scratch */ + return add_trust_user(mem_ctx, sam_ldb, base_dn, + netbios_name, in, NULL); + } + + /* check user is what we are looking for */ + uac = ldb_msg_find_attr_as_uint(msgs[0], + "userAccountControl", 0); + if (!(uac & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + if (delete_user) { + ret = ldb_delete(sam_ldb, msgs[0]->dn); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + /* entry exists, just modify secret if any */ + if (in == NULL || in->count == 0) { + return NT_STATUS_OK; + } + + msg = ldb_msg_new(mem_ctx); + if (!msg) { + return NT_STATUS_NO_MEMORY; + } + msg->dn = msgs[0]->dn; + + for (i = 0; i < in->count; i++) { + const char *attribute; + struct ldb_val v; + switch (in->current.array[i].AuthType) { + case TRUST_AUTH_TYPE_NT4OWF: + attribute = "unicodePwd"; + v.data = (uint8_t *)&in->current.array[i].AuthInfo.nt4owf.password; + v.length = 16; + break; + case TRUST_AUTH_TYPE_CLEAR: + attribute = "clearTextPassword"; + v.data = in->current.array[i].AuthInfo.clear.password; + v.length = in->current.array[i].AuthInfo.clear.size; + break; + default: + continue; + } + + ret = ldb_msg_append_value(msg, attribute, &v, LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* create the trusted_domain user account */ + ret = ldb_modify(sam_ldb, msg); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to create user record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ldb))); + + switch (ret) { + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_DOMAIN_EXISTS; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + return NT_STATUS_OK; +} + + +static NTSTATUS setInfoTrustedDomain_base(struct dcesrv_call_state *dce_call, + struct lsa_policy_state *p_state, + TALLOC_CTX *mem_ctx, + struct ldb_message *dom_msg, + enum lsa_TrustDomInfoEnum level, + union lsa_TrustedDomainInfo *info) +{ + uint32_t *posix_offset = NULL; + struct lsa_TrustDomainInfoInfoEx *info_ex = NULL; + struct lsa_TrustDomainInfoAuthInfo *auth_info = NULL; + struct lsa_TrustDomainInfoAuthInfoInternal *auth_info_int = NULL; + uint32_t *enc_types = NULL; + DATA_BLOB trustAuthIncoming, trustAuthOutgoing, auth_blob; + struct trustDomainPasswords auth_struct; + struct trustAuthInOutBlob *current_passwords = NULL; + NTSTATUS nt_status; + struct ldb_message **msgs; + struct ldb_message *msg; + bool add_outgoing = false; + bool add_incoming = false; + bool del_outgoing = false; + bool del_incoming = false; + bool del_forest_info = false; + bool in_transaction = false; + int ret; + bool am_rodc; + + switch (level) { + case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET: + posix_offset = &info->posix_offset.posix_offset; + break; + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX: + info_ex = &info->info_ex; + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO: + auth_info = &info->auth_info; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO: + posix_offset = &info->full_info.posix_offset.posix_offset; + info_ex = &info->full_info.info_ex; + auth_info = &info->full_info.auth_info; + break; + case LSA_TRUSTED_DOMAIN_INFO_AUTH_INFO_INTERNAL: + auth_info_int = &info->auth_info_internal; + break; + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_INTERNAL: + posix_offset = &info->full_info_internal.posix_offset.posix_offset; + info_ex = &info->full_info_internal.info_ex; + auth_info_int = &info->full_info_internal.auth_info; + break; + case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES: + enc_types = &info->enc_types.enc_types; + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_info) { + nt_status = auth_info_2_auth_blob(mem_ctx, auth_info, + &trustAuthIncoming, + &trustAuthOutgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + if (trustAuthIncoming.data) { + /* This does the decode of some of this twice, but it is easier that way */ + nt_status = auth_info_2_trustauth_inout(mem_ctx, + auth_info->incoming_count, + auth_info->incoming_current_auth_info, + NULL, + ¤t_passwords); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + } + + /* decode auth_info_int if set */ + if (auth_info_int) { + + /* now decrypt blob */ + auth_blob = data_blob_const(auth_info_int->auth_blob.data, + auth_info_int->auth_blob.size); + + nt_status = get_trustdom_auth_blob(dce_call, mem_ctx, + &auth_blob, &auth_struct); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + if (info_ex) { + /* verify data matches */ + if (info_ex->trust_attributes & + LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + /* TODO: check what behavior level we have */ + if (strcasecmp_m(p_state->domain_dns, + p_state->forest_dns) != 0) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + } + + ret = samdb_rodc(p_state->sam_ldb, &am_rodc); + if (ret == LDB_SUCCESS && am_rodc) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + /* verify only one object matches the dns/netbios/sid + * triplet and that this is the one we already have */ + nt_status = get_tdo(p_state->sam_ldb, mem_ctx, + p_state->system_dn, + info_ex->domain_name.string, + info_ex->netbios_name.string, + info_ex->sid, &msgs); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + if (ldb_dn_compare(dom_msg->dn, msgs[0]->dn) != 0) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + talloc_free(msgs); + } + + /* TODO: should we fetch previous values from the existing entry + * and append them ? */ + if (auth_info_int && auth_struct.incoming.count) { + nt_status = get_trustauth_inout_blob(dce_call, mem_ctx, + &auth_struct.incoming, + &trustAuthIncoming); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + current_passwords = &auth_struct.incoming; + + } else { + trustAuthIncoming = data_blob(NULL, 0); + } + + if (auth_info_int && auth_struct.outgoing.count) { + nt_status = get_trustauth_inout_blob(dce_call, mem_ctx, + &auth_struct.outgoing, + &trustAuthOutgoing); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + trustAuthOutgoing = data_blob(NULL, 0); + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + msg->dn = dom_msg->dn; + + if (posix_offset) { + nt_status = update_uint32_t_value(mem_ctx, p_state->sam_ldb, + dom_msg, msg, + "trustPosixOffset", + *posix_offset, NULL); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + if (info_ex) { + uint32_t origattrs; + uint32_t changed_attrs; + uint32_t origdir; + int origtype; + + nt_status = update_uint32_t_value(mem_ctx, p_state->sam_ldb, + dom_msg, msg, + "trustDirection", + info_ex->trust_direction, + &origdir); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (info_ex->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + if (auth_info != NULL && trustAuthIncoming.length > 0) { + add_incoming = true; + } + } + if (info_ex->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) { + if (auth_info != NULL && trustAuthOutgoing.length > 0) { + add_outgoing = true; + } + } + + if ((origdir & LSA_TRUST_DIRECTION_INBOUND) && + !(info_ex->trust_direction & LSA_TRUST_DIRECTION_INBOUND)) { + del_incoming = true; + } + if ((origdir & LSA_TRUST_DIRECTION_OUTBOUND) && + !(info_ex->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND)) { + del_outgoing = true; + } + + origtype = ldb_msg_find_attr_as_int(dom_msg, "trustType", -1); + if (origtype == -1 || origtype != info_ex->trust_type) { + DEBUG(1, ("Attempted to change trust type! " + "Operation not handled\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + nt_status = update_uint32_t_value(mem_ctx, p_state->sam_ldb, + dom_msg, msg, + "trustAttributes", + info_ex->trust_attributes, + &origattrs); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* TODO: check forestFunctionality from ldb opaque */ + /* TODO: check what is set makes sense */ + + changed_attrs = origattrs ^ info_ex->trust_attributes; + if (changed_attrs & ~LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE) { + /* + * For now we only allow + * LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE to be changed. + * + * TODO: we may need to support more attribute changes + */ + DEBUG(1, ("Attempted to change trust attributes " + "(0x%08x != 0x%08x)! " + "Operation not handled yet...\n", + (unsigned)origattrs, + (unsigned)info_ex->trust_attributes)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(info_ex->trust_attributes & + LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) + { + struct ldb_message_element *orig_forest_el = NULL; + + orig_forest_el = ldb_msg_find_element(dom_msg, + "msDS-TrustForestTrustInfo"); + if (orig_forest_el != NULL) { + del_forest_info = true; + } + } + } + + if (enc_types) { + nt_status = update_uint32_t_value(mem_ctx, p_state->sam_ldb, + dom_msg, msg, + "msDS-SupportedEncryptionTypes", + *enc_types, NULL); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + if (add_incoming || del_incoming) { + if (add_incoming) { + ret = ldb_msg_append_value(msg, "trustAuthIncoming", + &trustAuthIncoming, LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + ret = ldb_msg_add_empty(msg, "trustAuthIncoming", + LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + } + if (add_outgoing || del_outgoing) { + if (add_outgoing) { + ret = ldb_msg_append_value(msg, "trustAuthOutgoing", + &trustAuthOutgoing, LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + ret = ldb_msg_add_empty(msg, "trustAuthOutgoing", + LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + } + if (del_forest_info) { + ret = ldb_msg_add_empty(msg, "msDS-TrustForestTrustInfo", + LDB_FLAG_MOD_REPLACE, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* start transaction */ + ret = ldb_transaction_start(p_state->sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + in_transaction = true; + + if (msg->num_elements) { + ret = ldb_modify(p_state->sam_ldb, msg); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to modify trusted domain record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(p_state->sam_ldb))); + nt_status = dsdb_ldb_err_to_ntstatus(ret); + goto done; + } + } + + if (add_incoming || del_incoming) { + const char *netbios_name; + + netbios_name = ldb_msg_find_attr_as_string(dom_msg, + "flatname", NULL); + if (!netbios_name) { + nt_status = NT_STATUS_INVALID_DOMAIN_STATE; + goto done; + } + + /* We use trustAuthIncoming.data to incidate that auth_struct.incoming is valid */ + nt_status = update_trust_user(mem_ctx, + p_state->sam_ldb, + p_state->domain_dn, + del_incoming, + netbios_name, + current_passwords); + if (!NT_STATUS_IS_OK(nt_status)) { + goto done; + } + } + + /* ok, all fine, commit transaction and return */ + ret = ldb_transaction_commit(p_state->sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + in_transaction = false; + + nt_status = NT_STATUS_OK; + +done: + if (in_transaction) { + ldb_transaction_cancel(p_state->sam_ldb); + } + return nt_status; +} + +/* + lsa_SetInformationTrustedDomain +*/ +static NTSTATUS dcesrv_lsa_SetInformationTrustedDomain( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_SetInformationTrustedDomain *r) +{ + struct dcesrv_handle *h; + struct lsa_trusted_domain_state *td_state; + struct ldb_message **msgs; + NTSTATUS nt_status; + + DCESRV_PULL_HANDLE(h, r->in.trustdom_handle, + LSA_HANDLE_TRUSTED_DOMAIN); + + td_state = talloc_get_type(h->data, struct lsa_trusted_domain_state); + + /* get the trusted domain object */ + nt_status = get_tdo(td_state->policy->sam_ldb, mem_ctx, + td_state->trusted_domain_dn, + NULL, NULL, NULL, &msgs); + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return nt_status; + } + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return setInfoTrustedDomain_base(dce_call, td_state->policy, mem_ctx, + msgs[0], r->in.level, r->in.info); +} + + +/* + lsa_DeleteTrustedDomain +*/ +static NTSTATUS dcesrv_lsa_DeleteTrustedDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_DeleteTrustedDomain *r) +{ + NTSTATUS status; + struct lsa_OpenTrustedDomain opn = {{0},{0}}; + struct lsa_DeleteObject del; + struct dcesrv_handle *h; + + opn.in.handle = r->in.handle; + opn.in.sid = r->in.dom_sid; + opn.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + opn.out.trustdom_handle = talloc(mem_ctx, struct policy_handle); + if (!opn.out.trustdom_handle) { + return NT_STATUS_NO_MEMORY; + } + status = dcesrv_lsa_OpenTrustedDomain(dce_call, mem_ctx, &opn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DCESRV_PULL_HANDLE(h, opn.out.trustdom_handle, DCESRV_HANDLE_ANY); + talloc_steal(mem_ctx, h); + + del.in.handle = opn.out.trustdom_handle; + del.out.handle = opn.out.trustdom_handle; + status = dcesrv_lsa_DeleteObject(dce_call, mem_ctx, &del); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_OK; +} + +static NTSTATUS fill_trust_domain_ex(TALLOC_CTX *mem_ctx, + struct ldb_message *msg, + struct lsa_TrustDomainInfoInfoEx *info_ex) +{ + info_ex->domain_name.string + = ldb_msg_find_attr_as_string(msg, "trustPartner", NULL); + info_ex->netbios_name.string + = ldb_msg_find_attr_as_string(msg, "flatname", NULL); + info_ex->sid + = samdb_result_dom_sid(mem_ctx, msg, "securityIdentifier"); + info_ex->trust_direction + = ldb_msg_find_attr_as_int(msg, "trustDirection", 0); + info_ex->trust_type + = ldb_msg_find_attr_as_int(msg, "trustType", 0); + info_ex->trust_attributes + = ldb_msg_find_attr_as_int(msg, "trustAttributes", 0); + return NT_STATUS_OK; +} + +/* + lsa_QueryTrustedDomainInfo +*/ +static NTSTATUS dcesrv_lsa_QueryTrustedDomainInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QueryTrustedDomainInfo *r) +{ + union lsa_TrustedDomainInfo *info = NULL; + struct dcesrv_handle *h; + struct lsa_trusted_domain_state *trusted_domain_state; + struct ldb_message *msg; + int ret; + struct ldb_message **res; + const char *attrs[] = { + "flatname", + "trustPartner", + "securityIdentifier", + "trustDirection", + "trustType", + "trustAttributes", + "msDs-supportedEncryptionTypes", + NULL + }; + + DCESRV_PULL_HANDLE(h, r->in.trustdom_handle, LSA_HANDLE_TRUSTED_DOMAIN); + + trusted_domain_state = talloc_get_type(h->data, struct lsa_trusted_domain_state); + + /* pull all the user attributes */ + ret = gendb_search_dn(trusted_domain_state->policy->sam_ldb, mem_ctx, + trusted_domain_state->trusted_domain_dn, &res, attrs); + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + info = talloc_zero(mem_ctx, union lsa_TrustedDomainInfo); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + *r->out.info = info; + + switch (r->in.level) { + case LSA_TRUSTED_DOMAIN_INFO_NAME: + info->name.netbios_name.string + = ldb_msg_find_attr_as_string(msg, "flatname", NULL); + break; + case LSA_TRUSTED_DOMAIN_INFO_POSIX_OFFSET: + info->posix_offset.posix_offset + = ldb_msg_find_attr_as_uint(msg, "posixOffset", 0); + break; +#if 0 /* Win2k3 doesn't implement this */ + case LSA_TRUSTED_DOMAIN_INFO_BASIC: + r->out.info->info_basic.netbios_name.string + = ldb_msg_find_attr_as_string(msg, "flatname", NULL); + r->out.info->info_basic.sid + = samdb_result_dom_sid(mem_ctx, msg, "securityIdentifier"); + break; +#endif + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX: + return fill_trust_domain_ex(mem_ctx, msg, &info->info_ex); + + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO: + ZERO_STRUCT(info->full_info); + return fill_trust_domain_ex(mem_ctx, msg, &info->full_info.info_ex); + case LSA_TRUSTED_DOMAIN_INFO_FULL_INFO_2_INTERNAL: + ZERO_STRUCT(info->full_info2_internal); + info->full_info2_internal.posix_offset.posix_offset + = ldb_msg_find_attr_as_uint(msg, "posixOffset", 0); + return fill_trust_domain_ex(mem_ctx, msg, &info->full_info2_internal.info.info_ex); + + case LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES: + info->enc_types.enc_types + = ldb_msg_find_attr_as_uint(msg, "msDs-supportedEncryptionTypes", KERB_ENCTYPE_RC4_HMAC_MD5); + break; + + case LSA_TRUSTED_DOMAIN_INFO_CONTROLLERS: + case LSA_TRUSTED_DOMAIN_INFO_INFO_EX2_INTERNAL: + /* oops, we don't want to return the info after all */ + talloc_free(info); + *r->out.info = NULL; + return NT_STATUS_INVALID_PARAMETER; + default: + /* oops, we don't want to return the info after all */ + talloc_free(info); + *r->out.info = NULL; + return NT_STATUS_INVALID_INFO_CLASS; + } + + return NT_STATUS_OK; +} + + +/* + lsa_QueryTrustedDomainInfoBySid +*/ +static NTSTATUS dcesrv_lsa_QueryTrustedDomainInfoBySid(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QueryTrustedDomainInfoBySid *r) +{ + NTSTATUS status; + struct lsa_OpenTrustedDomain opn = {{0},{0}}; + struct lsa_QueryTrustedDomainInfo query; + struct dcesrv_handle *h; + + opn.in.handle = r->in.handle; + opn.in.sid = r->in.dom_sid; + opn.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + opn.out.trustdom_handle = talloc(mem_ctx, struct policy_handle); + if (!opn.out.trustdom_handle) { + return NT_STATUS_NO_MEMORY; + } + status = dcesrv_lsa_OpenTrustedDomain(dce_call, mem_ctx, &opn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Ensure this handle goes away at the end of this call */ + DCESRV_PULL_HANDLE(h, opn.out.trustdom_handle, DCESRV_HANDLE_ANY); + talloc_steal(mem_ctx, h); + + query.in.trustdom_handle = opn.out.trustdom_handle; + query.in.level = r->in.level; + query.out.info = r->out.info; + status = dcesrv_lsa_QueryTrustedDomainInfo(dce_call, mem_ctx, &query); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/* + lsa_SetTrustedDomainInfoByName +*/ +static NTSTATUS dcesrv_lsa_SetTrustedDomainInfoByName(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_SetTrustedDomainInfoByName *r) +{ + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct ldb_message **msgs; + NTSTATUS nt_status; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + policy_state = policy_handle->data; + + /* get the trusted domain object */ + nt_status = get_tdo(policy_state->sam_ldb, mem_ctx, + policy_state->domain_dn, + r->in.trusted_domain->string, + r->in.trusted_domain->string, + NULL, &msgs); + if (!NT_STATUS_IS_OK(nt_status)) { + if (NT_STATUS_EQUAL(nt_status, + NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return nt_status; + } + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + return setInfoTrustedDomain_base(dce_call, policy_state, mem_ctx, + msgs[0], r->in.level, r->in.info); +} + +/* + lsa_QueryTrustedDomainInfoByName +*/ +static NTSTATUS dcesrv_lsa_QueryTrustedDomainInfoByName(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_QueryTrustedDomainInfoByName *r) +{ + NTSTATUS status; + struct lsa_OpenTrustedDomainByName opn = {{0},{0}}; + struct lsa_QueryTrustedDomainInfo query; + struct dcesrv_handle *h; + + opn.in.handle = r->in.handle; + opn.in.name = *r->in.trusted_domain; + opn.in.access_mask = SEC_FLAG_MAXIMUM_ALLOWED; + opn.out.trustdom_handle = talloc(mem_ctx, struct policy_handle); + if (!opn.out.trustdom_handle) { + return NT_STATUS_NO_MEMORY; + } + status = dcesrv_lsa_OpenTrustedDomainByName(dce_call, mem_ctx, &opn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Ensure this handle goes away at the end of this call */ + DCESRV_PULL_HANDLE(h, opn.out.trustdom_handle, DCESRV_HANDLE_ANY); + talloc_steal(mem_ctx, h); + + query.in.trustdom_handle = opn.out.trustdom_handle; + query.in.level = r->in.level; + query.out.info = r->out.info; + status = dcesrv_lsa_QueryTrustedDomainInfo(dce_call, mem_ctx, &query); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/* + lsa_CloseTrustedDomainEx +*/ +static NTSTATUS dcesrv_lsa_CloseTrustedDomainEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_CloseTrustedDomainEx *r) +{ + /* The result of a bad hair day from an IDL programmer? Not + * implmented in Win2k3. You should always just lsa_Close + * anyway. */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + comparison function for sorting lsa_DomainInformation array +*/ +static int compare_DomainInfo(struct lsa_DomainInfo *e1, struct lsa_DomainInfo *e2) +{ + return strcasecmp_m(e1->name.string, e2->name.string); +} + +/* + lsa_EnumTrustDom +*/ +static NTSTATUS dcesrv_lsa_EnumTrustDom(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_EnumTrustDom *r) +{ + struct dcesrv_handle *policy_handle; + struct lsa_DomainInfo *entries; + struct lsa_policy_state *policy_state; + struct ldb_message **domains; + const char *attrs[] = { + "flatname", + "securityIdentifier", + NULL + }; + + + int count, i; + + *r->out.resume_handle = 0; + + r->out.domains->domains = NULL; + r->out.domains->count = 0; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + policy_state = policy_handle->data; + + /* search for all users in this domain. This could possibly be cached and + resumed based on resume_key */ + count = gendb_search(policy_state->sam_ldb, mem_ctx, policy_state->system_dn, &domains, attrs, + "objectclass=trustedDomain"); + if (count < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* convert to lsa_TrustInformation format */ + entries = talloc_array(mem_ctx, struct lsa_DomainInfo, count); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<count;i++) { + entries[i].sid = samdb_result_dom_sid(mem_ctx, domains[i], "securityIdentifier"); + entries[i].name.string = ldb_msg_find_attr_as_string(domains[i], "flatname", NULL); + } + + /* sort the results by name */ + TYPESAFE_QSORT(entries, count, compare_DomainInfo); + + if (*r->in.resume_handle >= count) { + *r->out.resume_handle = -1; + + return NT_STATUS_NO_MORE_ENTRIES; + } + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 60 */ + r->out.domains->count = count - *r->in.resume_handle; + r->out.domains->count = MIN(r->out.domains->count, + 1+(r->in.max_size/LSA_ENUM_TRUST_DOMAIN_MULTIPLIER)); + + r->out.domains->domains = entries + *r->in.resume_handle; + + if (r->out.domains->count < count - *r->in.resume_handle) { + *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count; + return STATUS_MORE_ENTRIES; + } + + /* according to MS-LSAD 3.1.4.7.8 output resume handle MUST + * always be larger than the previous input resume handle, in + * particular when hitting the last query it is vital to set the + * resume handle correctly to avoid infinite client loops, as + * seen e.g. with Windows XP SP3 when resume handle is 0 and + * status is NT_STATUS_OK - gd */ + + *r->out.resume_handle = (uint32_t)-1; + + return NT_STATUS_OK; +} + +/* + comparison function for sorting lsa_DomainInformation array +*/ +static int compare_TrustDomainInfoInfoEx(struct lsa_TrustDomainInfoInfoEx *e1, struct lsa_TrustDomainInfoInfoEx *e2) +{ + return strcasecmp_m(e1->netbios_name.string, e2->netbios_name.string); +} + +/* + lsa_EnumTrustedDomainsEx +*/ +static NTSTATUS dcesrv_lsa_EnumTrustedDomainsEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_EnumTrustedDomainsEx *r) +{ + struct dcesrv_handle *policy_handle; + struct lsa_TrustDomainInfoInfoEx *entries; + struct lsa_policy_state *policy_state; + struct ldb_message **domains; + const char *attrs[] = { + "flatname", + "trustPartner", + "securityIdentifier", + "trustDirection", + "trustType", + "trustAttributes", + NULL + }; + NTSTATUS nt_status; + + int count, i; + + *r->out.resume_handle = 0; + + r->out.domains->domains = NULL; + r->out.domains->count = 0; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + policy_state = policy_handle->data; + + /* search for all users in this domain. This could possibly be cached and + resumed based on resume_key */ + count = gendb_search(policy_state->sam_ldb, mem_ctx, policy_state->system_dn, &domains, attrs, + "objectclass=trustedDomain"); + if (count < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* convert to lsa_DomainInformation format */ + entries = talloc_array(mem_ctx, struct lsa_TrustDomainInfoInfoEx, count); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<count;i++) { + nt_status = fill_trust_domain_ex(mem_ctx, domains[i], &entries[i]); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + /* sort the results by name */ + TYPESAFE_QSORT(entries, count, compare_TrustDomainInfoInfoEx); + + if (*r->in.resume_handle >= count) { + *r->out.resume_handle = -1; + + return NT_STATUS_NO_MORE_ENTRIES; + } + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 60 */ + r->out.domains->count = count - *r->in.resume_handle; + r->out.domains->count = MIN(r->out.domains->count, + 1+(r->in.max_size/LSA_ENUM_TRUST_DOMAIN_EX_MULTIPLIER)); + + r->out.domains->domains = entries + *r->in.resume_handle; + + if (r->out.domains->count < count - *r->in.resume_handle) { + *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count; + return STATUS_MORE_ENTRIES; + } + + *r->out.resume_handle = *r->in.resume_handle + r->out.domains->count; + + return NT_STATUS_OK; +} + + +/* + lsa_OpenAccount +*/ +static NTSTATUS dcesrv_lsa_OpenAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_OpenAccount *r) +{ + struct dcesrv_handle *h, *ah; + struct lsa_policy_state *state; + struct lsa_account_state *astate; + + ZERO_STRUCTP(r->out.acct_handle); + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + astate = talloc(dce_call->conn, struct lsa_account_state); + if (astate == NULL) { + return NT_STATUS_NO_MEMORY; + } + + astate->account_sid = dom_sid_dup(astate, r->in.sid); + if (astate->account_sid == NULL) { + talloc_free(astate); + return NT_STATUS_NO_MEMORY; + } + + astate->policy = talloc_reference(astate, state); + astate->access_mask = r->in.access_mask; + + /* + * For now we grant all requested access. + * + * We will fail at the ldb layer later. + */ + if (astate->access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + astate->access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED; + astate->access_mask |= LSA_ACCOUNT_ALL_ACCESS; + } + se_map_generic(&astate->access_mask, &dcesrv_lsa_account_mapping); + + DEBUG(10,("%s: %s access desired[0x%08X] granted[0x%08X] - success.\n", + __func__, dom_sid_string(mem_ctx, astate->account_sid), + (unsigned)r->in.access_mask, + (unsigned)astate->access_mask)); + + ah = dcesrv_handle_create(dce_call, LSA_HANDLE_ACCOUNT); + if (!ah) { + talloc_free(astate); + return NT_STATUS_NO_MEMORY; + } + + ah->data = talloc_steal(ah, astate); + + *r->out.acct_handle = ah->wire_handle; + + return NT_STATUS_OK; +} + + +/* + lsa_EnumPrivsAccount +*/ +static NTSTATUS dcesrv_lsa_EnumPrivsAccount(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_EnumPrivsAccount *r) +{ + struct dcesrv_handle *h; + struct lsa_account_state *astate; + int ret; + unsigned int i, j; + struct ldb_message **res; + const char * const attrs[] = { "privilege", NULL}; + struct ldb_message_element *el; + const char *sidstr; + struct lsa_PrivilegeSet *privs; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_ACCOUNT); + + astate = h->data; + + privs = talloc(mem_ctx, struct lsa_PrivilegeSet); + if (privs == NULL) { + return NT_STATUS_NO_MEMORY; + } + privs->count = 0; + privs->unknown = 0; + privs->set = NULL; + + *r->out.privs = privs; + + sidstr = ldap_encode_ndr_dom_sid(mem_ctx, astate->account_sid); + if (sidstr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(astate->policy->pdb, mem_ctx, NULL, &res, attrs, + "objectSid=%s", sidstr); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if (ret != 1) { + return NT_STATUS_OK; + } + + el = ldb_msg_find_element(res[0], "privilege"); + if (el == NULL || el->num_values == 0) { + return NT_STATUS_OK; + } + + privs->set = talloc_array(privs, + struct lsa_LUIDAttribute, el->num_values); + if (privs->set == NULL) { + return NT_STATUS_NO_MEMORY; + } + + j = 0; + for (i=0;i<el->num_values;i++) { + int id = sec_privilege_id((const char *)el->values[i].data); + if (id == SEC_PRIV_INVALID) { + /* Perhaps an account right, not a privilege */ + continue; + } + privs->set[j].attribute = 0; + privs->set[j].luid.low = id; + privs->set[j].luid.high = 0; + j++; + } + + privs->count = j; + + return NT_STATUS_OK; +} + +/* + lsa_EnumAccountRights +*/ +static NTSTATUS dcesrv_lsa_EnumAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_EnumAccountRights *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *state; + int ret; + unsigned int i; + struct ldb_message **res; + const char * const attrs[] = { "privilege", NULL}; + const char *sidstr; + struct ldb_message_element *el; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + sidstr = ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid); + if (sidstr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(state->pdb, mem_ctx, NULL, &res, attrs, + "(&(objectSid=%s)(privilege=*))", sidstr); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + if (ret != 1) { + DEBUG(3, ("searching for account rights for SID: %s failed: %s", + dom_sid_string(mem_ctx, r->in.sid), + ldb_errstring(state->pdb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + el = ldb_msg_find_element(res[0], "privilege"); + if (el == NULL || el->num_values == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + r->out.rights->count = el->num_values; + r->out.rights->names = talloc_array(r->out.rights, + struct lsa_StringLarge, r->out.rights->count); + if (r->out.rights->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<el->num_values;i++) { + r->out.rights->names[i].string = (const char *)el->values[i].data; + } + + return NT_STATUS_OK; +} + + + +/* + helper for lsa_AddAccountRights and lsa_RemoveAccountRights +*/ +static NTSTATUS dcesrv_lsa_AddRemoveAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_policy_state *state, + int ldb_flag, + struct dom_sid *sid, + const struct lsa_RightSet *rights) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + const char *sidstr, *sidndrstr; + struct ldb_message *msg; + struct ldb_message_element *el; + int ret; + uint32_t i; + struct lsa_EnumAccountRights r2; + char *dnstr; + + if (security_session_user_level(session_info, NULL) < + SECURITY_ADMINISTRATOR) { + DEBUG(0,("lsa_AddRemoveAccount refused for supplied security token\n")); + return NT_STATUS_ACCESS_DENIED; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sidndrstr = ldap_encode_ndr_dom_sid(msg, sid); + if (sidndrstr == NULL) { + TALLOC_FREE(msg); + return NT_STATUS_NO_MEMORY; + } + + sidstr = dom_sid_string(msg, sid); + if (sidstr == NULL) { + TALLOC_FREE(msg); + return NT_STATUS_NO_MEMORY; + } + + dnstr = talloc_asprintf(msg, "sid=%s", sidstr); + if (dnstr == NULL) { + TALLOC_FREE(msg); + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_new(msg, state->pdb, dnstr); + if (msg->dn == NULL) { + TALLOC_FREE(msg); + return NT_STATUS_NO_MEMORY; + } + + if (LDB_FLAG_MOD_TYPE(ldb_flag) == LDB_FLAG_MOD_ADD) { + NTSTATUS status; + + r2.in.handle = &state->handle->wire_handle; + r2.in.sid = sid; + r2.out.rights = talloc(mem_ctx, struct lsa_RightSet); + + status = dcesrv_lsa_EnumAccountRights(dce_call, mem_ctx, &r2); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(r2.out.rights); + } + } + + for (i=0;i<rights->count;i++) { + bool ok; + + ok = dcesrc_lsa_valid_AccountRight(rights->names[i].string); + if (!ok) { + talloc_free(msg); + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + if (LDB_FLAG_MOD_TYPE(ldb_flag) == LDB_FLAG_MOD_ADD) { + uint32_t j; + for (j=0;j<r2.out.rights->count;j++) { + if (strcasecmp_m(r2.out.rights->names[j].string, + rights->names[i].string) == 0) { + break; + } + } + if (j != r2.out.rights->count) continue; + } + + ret = ldb_msg_add_string(msg, "privilege", rights->names[i].string); + if (ret != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + } + + el = ldb_msg_find_element(msg, "privilege"); + if (!el) { + talloc_free(msg); + return NT_STATUS_OK; + } + + el->flags = ldb_flag; + + ret = ldb_modify(state->pdb, msg); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + if (samdb_msg_add_dom_sid(state->pdb, msg, msg, "objectSid", sid) != LDB_SUCCESS) { + talloc_free(msg); + return NT_STATUS_NO_MEMORY; + } + ldb_msg_add_string(msg, "comment", "added via LSA"); + ret = ldb_add(state->pdb, msg); + } + if (ret != LDB_SUCCESS) { + if (LDB_FLAG_MOD_TYPE(ldb_flag) == LDB_FLAG_MOD_DELETE && ret == LDB_ERR_NO_SUCH_ATTRIBUTE) { + talloc_free(msg); + return NT_STATUS_OK; + } + DEBUG(3, ("Could not %s attributes from %s: %s", + LDB_FLAG_MOD_TYPE(ldb_flag) == LDB_FLAG_MOD_DELETE ? "delete" : "add", + ldb_dn_get_linearized(msg->dn), ldb_errstring(state->pdb))); + talloc_free(msg); + return NT_STATUS_UNEXPECTED_IO_ERROR; + } + + talloc_free(msg); + return NT_STATUS_OK; +} + +/* + lsa_AddPrivilegesToAccount +*/ +static NTSTATUS dcesrv_lsa_AddPrivilegesToAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_AddPrivilegesToAccount *r) +{ + struct lsa_RightSet rights; + struct dcesrv_handle *h; + struct lsa_account_state *astate; + uint32_t i; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_ACCOUNT); + + astate = h->data; + + rights.count = r->in.privs->count; + rights.names = talloc_array(mem_ctx, struct lsa_StringLarge, rights.count); + if (rights.names == NULL) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<rights.count;i++) { + int id = r->in.privs->set[i].luid.low; + if (r->in.privs->set[i].luid.high) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + rights.names[i].string = sec_privilege_name(id); + if (rights.names[i].string == NULL) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + } + + return dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, astate->policy, + LDB_FLAG_MOD_ADD, astate->account_sid, + &rights); +} + + +/* + lsa_RemovePrivilegesFromAccount +*/ +static NTSTATUS dcesrv_lsa_RemovePrivilegesFromAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_RemovePrivilegesFromAccount *r) +{ + struct lsa_RightSet *rights; + struct dcesrv_handle *h; + struct lsa_account_state *astate; + uint32_t i; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_ACCOUNT); + + astate = h->data; + + rights = talloc(mem_ctx, struct lsa_RightSet); + + if (r->in.remove_all == 1 && + r->in.privs == NULL) { + struct lsa_EnumAccountRights r2; + NTSTATUS status; + + r2.in.handle = &astate->policy->handle->wire_handle; + r2.in.sid = astate->account_sid; + r2.out.rights = rights; + + status = dcesrv_lsa_EnumAccountRights(dce_call, mem_ctx, &r2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, astate->policy, + LDB_FLAG_MOD_DELETE, astate->account_sid, + r2.out.rights); + } + + if (r->in.remove_all != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + rights->count = r->in.privs->count; + rights->names = talloc_array(mem_ctx, struct lsa_StringLarge, rights->count); + if (rights->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<rights->count;i++) { + int id = r->in.privs->set[i].luid.low; + if (r->in.privs->set[i].luid.high) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + rights->names[i].string = sec_privilege_name(id); + if (rights->names[i].string == NULL) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + } + + return dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, astate->policy, + LDB_FLAG_MOD_DELETE, astate->account_sid, + rights); +} + + +/* + lsa_GetQuotasForAccount +*/ +static NTSTATUS dcesrv_lsa_GetQuotasForAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_GetQuotasForAccount *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_SetQuotasForAccount +*/ +static NTSTATUS dcesrv_lsa_SetQuotasForAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetQuotasForAccount *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_GetSystemAccessAccount +*/ +static NTSTATUS dcesrv_lsa_GetSystemAccessAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_GetSystemAccessAccount *r) +{ + struct dcesrv_handle *h; + struct lsa_account_state *astate; + int ret; + unsigned int i; + struct ldb_message **res; + const char * const attrs[] = { "privilege", NULL}; + struct ldb_message_element *el; + const char *sidstr; + + *(r->out.access_mask) = 0x00000000; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_ACCOUNT); + + astate = h->data; + + sidstr = ldap_encode_ndr_dom_sid(mem_ctx, astate->account_sid); + if (sidstr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(astate->policy->pdb, mem_ctx, NULL, &res, attrs, + "objectSid=%s", sidstr); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if (ret != 1) { + return NT_STATUS_OK; + } + + el = ldb_msg_find_element(res[0], "privilege"); + if (el == NULL || el->num_values == 0) { + return NT_STATUS_OK; + } + + for (i=0;i<el->num_values;i++) { + uint32_t right_bit = sec_right_bit((const char *)el->values[i].data); + if (right_bit == 0) { + /* Perhaps an privilege, not a right */ + continue; + } + *(r->out.access_mask) |= right_bit; + } + + return NT_STATUS_OK; +} + + +/* + lsa_SetSystemAccessAccount +*/ +static NTSTATUS dcesrv_lsa_SetSystemAccessAccount(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetSystemAccessAccount *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} +/* + lsa_CreateSecret +*/ +static NTSTATUS dcesrv_lsa_CreateSecret(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CreateSecret *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct lsa_secret_state *secret_state; + struct dcesrv_handle *handle; + struct ldb_message **msgs, *msg; + const char *attrs[] = { + NULL + }; + + const char *name; + + int ret; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + ZERO_STRUCTP(r->out.sec_handle); + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + break; + default: + /* Users and annonymous are not allowed create secrets */ + return NT_STATUS_ACCESS_DENIED; + } + + policy_state = policy_handle->data; + + if (!r->in.name.string) { + return NT_STATUS_INVALID_PARAMETER; + } + + secret_state = talloc(mem_ctx, struct lsa_secret_state); + NT_STATUS_HAVE_NO_MEMORY(secret_state); + secret_state->policy = policy_state; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (strncmp("G$", r->in.name.string, 2) == 0) { + const char *name2; + + secret_state->global = true; + + name = &r->in.name.string[2]; + if (strlen(name) == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + name2 = talloc_asprintf(mem_ctx, "%s Secret", + ldb_binary_encode_string(mem_ctx, name)); + NT_STATUS_HAVE_NO_MEMORY(name2); + + /* + * We need to connect to the database as system, as this is + * one of the rare RPC calls that must read the secrets + * (and this is denied otherwise) + * + * We also save the current remote session details so they can + * used by the audit logging module. This allows the audit + * logging to report the remote users details, rather than the + * system users details. + */ + secret_state->sam_ldb = + dcesrv_samdb_connect_as_system(secret_state, dce_call); + NT_STATUS_HAVE_NO_MEMORY(secret_state->sam_ldb); + + /* search for the secret record */ + ret = gendb_search(secret_state->sam_ldb, + mem_ctx, policy_state->system_dn, &msgs, attrs, + "(&(cn=%s)(objectclass=secret))", + name2); + if (ret > 0) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + if (ret < 0) { + DEBUG(0,("Failure searching for CN=%s: %s\n", + name2, ldb_errstring(secret_state->sam_ldb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + msg->dn = ldb_dn_copy(mem_ctx, policy_state->system_dn); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + if (!ldb_dn_add_child_fmt(msg->dn, "cn=%s", name2)) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "cn", name2); + if (ret != LDB_SUCCESS) return NT_STATUS_NO_MEMORY; + } else { + secret_state->global = false; + + name = r->in.name.string; + if (strlen(name) == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + secret_state->sam_ldb = secrets_db_connect(secret_state, + dce_call->conn->dce_ctx->lp_ctx); + NT_STATUS_HAVE_NO_MEMORY(secret_state->sam_ldb); + + /* search for the secret record */ + ret = gendb_search(secret_state->sam_ldb, mem_ctx, + ldb_dn_new(mem_ctx, secret_state->sam_ldb, "cn=LSA Secrets"), + &msgs, attrs, + "(&(cn=%s)(objectclass=secret))", + ldb_binary_encode_string(mem_ctx, name)); + if (ret > 0) { + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + if (ret < 0) { + DEBUG(0,("Failure searching for CN=%s: %s\n", + name, ldb_errstring(secret_state->sam_ldb))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + msg->dn = ldb_dn_new_fmt(mem_ctx, secret_state->sam_ldb, + "cn=%s,cn=LSA Secrets", name); + NT_STATUS_HAVE_NO_MEMORY(msg->dn); + ret = ldb_msg_add_string(msg, "cn", name); + if (ret != LDB_SUCCESS) return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(msg, "objectClass", "secret"); + if (ret != LDB_SUCCESS) return NT_STATUS_NO_MEMORY; + + secret_state->secret_dn = talloc_reference(secret_state, msg->dn); + NT_STATUS_HAVE_NO_MEMORY(secret_state->secret_dn); + + /* create the secret */ + ret = ldb_add(secret_state->sam_ldb, msg); + if (ret != LDB_SUCCESS) { + DEBUG(0,("Failed to create secret record %s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(secret_state->sam_ldb))); + return NT_STATUS_ACCESS_DENIED; + } + + handle = dcesrv_handle_create(dce_call, LSA_HANDLE_SECRET); + NT_STATUS_HAVE_NO_MEMORY(handle); + + handle->data = talloc_steal(handle, secret_state); + + secret_state->access_mask = r->in.access_mask; + secret_state->policy = talloc_reference(secret_state, policy_state); + NT_STATUS_HAVE_NO_MEMORY(secret_state->policy); + + *r->out.sec_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + lsa_OpenSecret +*/ +static NTSTATUS dcesrv_lsa_OpenSecret(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_OpenSecret *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *policy_handle; + struct lsa_policy_state *policy_state; + struct lsa_secret_state *secret_state; + struct dcesrv_handle *handle; + struct ldb_message **msgs; + const char *attrs[] = { + NULL + }; + const char *name; + int ret; + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + ZERO_STRUCTP(r->out.sec_handle); + policy_state = policy_handle->data; + + if (!r->in.name.string) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + break; + default: + /* Users and annonymous are not allowed to access secrets */ + return NT_STATUS_ACCESS_DENIED; + } + + secret_state = talloc(mem_ctx, struct lsa_secret_state); + if (!secret_state) { + return NT_STATUS_NO_MEMORY; + } + secret_state->policy = policy_state; + + if (strncmp("G$", r->in.name.string, 2) == 0) { + name = &r->in.name.string[2]; + /* + * We need to connect to the database as system, as this is + * one of the rare RPC calls that must read the secrets + * (and this is denied otherwise) + * + * We also save the current remote session details so they can + * used by the audit logging module. This allows the audit + * logging to report the remote users details, rather than the + * system users details. + */ + secret_state->sam_ldb = + dcesrv_samdb_connect_as_system(secret_state, dce_call); + NT_STATUS_HAVE_NO_MEMORY(secret_state->sam_ldb); + secret_state->global = true; + + if (strlen(name) < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* search for the secret record */ + ret = gendb_search(secret_state->sam_ldb, + mem_ctx, policy_state->system_dn, &msgs, attrs, + "(&(cn=%s Secret)(objectclass=secret))", + ldb_binary_encode_string(mem_ctx, name)); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ret != 1) { + DEBUG(0,("Found %d records matching DN %s\n", ret, + ldb_dn_get_linearized(policy_state->system_dn))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } else { + secret_state->global = false; + secret_state->sam_ldb = secrets_db_connect(secret_state, + dce_call->conn->dce_ctx->lp_ctx); + NT_STATUS_HAVE_NO_MEMORY(secret_state->sam_ldb); + + name = r->in.name.string; + if (strlen(name) < 1) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* search for the secret record */ + ret = gendb_search(secret_state->sam_ldb, mem_ctx, + ldb_dn_new(mem_ctx, secret_state->sam_ldb, "cn=LSA Secrets"), + &msgs, attrs, + "(&(cn=%s)(objectclass=secret))", + ldb_binary_encode_string(mem_ctx, name)); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ret != 1) { + DEBUG(0,("Found %d records matching CN=%s\n", + ret, ldb_binary_encode_string(mem_ctx, name))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + secret_state->secret_dn = talloc_reference(secret_state, msgs[0]->dn); + + handle = dcesrv_handle_create(dce_call, LSA_HANDLE_SECRET); + if (!handle) { + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, secret_state); + + secret_state->access_mask = r->in.access_mask; + secret_state->policy = talloc_reference(secret_state, policy_state); + + *r->out.sec_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + lsa_SetSecret +*/ +static NTSTATUS dcesrv_lsa_SetSecret(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_SetSecret *r) +{ + + struct dcesrv_handle *h; + struct lsa_secret_state *secret_state; + struct ldb_message *msg; + DATA_BLOB session_key; + DATA_BLOB crypt_secret, secret; + struct ldb_val val; + int ret; + NTSTATUS status = NT_STATUS_OK; + + struct timeval now = timeval_current(); + NTTIME nt_now = timeval_to_nttime(&now); + + DCESRV_PULL_HANDLE(h, r->in.sec_handle, LSA_HANDLE_SECRET); + + secret_state = h->data; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_reference(mem_ctx, secret_state->secret_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (r->in.old_val) { + /* Decrypt */ + crypt_secret.data = r->in.old_val->data; + crypt_secret.length = r->in.old_val->size; + + status = sess_decrypt_blob(mem_ctx, &crypt_secret, &session_key, &secret); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + val.data = secret.data; + val.length = secret.length; + + /* set value */ + if (ldb_msg_add_value(msg, "priorValue", &val, NULL) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* set old value mtime */ + if (samdb_msg_add_uint64(secret_state->sam_ldb, + mem_ctx, msg, "priorSetTime", nt_now) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + } else { + /* If the old value is not set, then migrate the + * current value to the old value */ + const struct ldb_val *old_val; + NTTIME last_set_time; + struct ldb_message **res; + const char *attrs[] = { + "currentValue", + "lastSetTime", + NULL + }; + + /* search for the secret record */ + ret = gendb_search_dn(secret_state->sam_ldb,mem_ctx, + secret_state->secret_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + if (ret != 1) { + DEBUG(0,("Found %d records matching dn=%s\n", ret, + ldb_dn_get_linearized(secret_state->secret_dn))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + old_val = ldb_msg_find_ldb_val(res[0], "currentValue"); + last_set_time = ldb_msg_find_attr_as_uint64(res[0], "lastSetTime", 0); + + if (old_val) { + /* set old value */ + if (ldb_msg_add_value(msg, "priorValue", + old_val, NULL) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + if (samdb_msg_add_delete(secret_state->sam_ldb, + mem_ctx, msg, "priorValue") != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* set old value mtime */ + if (ldb_msg_find_ldb_val(res[0], "lastSetTime")) { + if (samdb_msg_add_uint64(secret_state->sam_ldb, + mem_ctx, msg, "priorSetTime", last_set_time) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + if (samdb_msg_add_uint64(secret_state->sam_ldb, + mem_ctx, msg, "priorSetTime", nt_now) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + } + + if (r->in.new_val) { + /* Decrypt */ + crypt_secret.data = r->in.new_val->data; + crypt_secret.length = r->in.new_val->size; + + status = sess_decrypt_blob(mem_ctx, &crypt_secret, &session_key, &secret); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + val.data = secret.data; + val.length = secret.length; + + /* set value */ + if (ldb_msg_add_value(msg, "currentValue", &val, NULL) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* set new value mtime */ + if (samdb_msg_add_uint64(secret_state->sam_ldb, + mem_ctx, msg, "lastSetTime", nt_now) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + /* NULL out the NEW value */ + if (samdb_msg_add_uint64(secret_state->sam_ldb, + mem_ctx, msg, "lastSetTime", nt_now) != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + if (samdb_msg_add_delete(secret_state->sam_ldb, + mem_ctx, msg, "currentValue") != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* modify the samdb record */ + ret = dsdb_replace(secret_state->sam_ldb, msg, 0); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + + +/* + lsa_QuerySecret +*/ +static NTSTATUS dcesrv_lsa_QuerySecret(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_QuerySecret *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct lsa_secret_state *secret_state; + struct ldb_message *msg; + DATA_BLOB session_key; + DATA_BLOB crypt_secret, secret; + int ret; + struct ldb_message **res; + const char *attrs[] = { + "currentValue", + "priorValue", + "lastSetTime", + "priorSetTime", + NULL + }; + + NTSTATUS nt_status; + + DCESRV_PULL_HANDLE(h, r->in.sec_handle, LSA_HANDLE_SECRET); + + /* Ensure user is permitted to read this... */ + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + break; + default: + /* Users and annonymous are not allowed to read secrets */ + return NT_STATUS_ACCESS_DENIED; + } + + secret_state = h->data; + + /* pull all the user attributes */ + ret = gendb_search_dn(secret_state->sam_ldb, mem_ctx, + secret_state->secret_dn, &res, attrs); + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (r->in.old_val) { + const struct ldb_val *prior_val; + r->out.old_val = talloc_zero(mem_ctx, struct lsa_DATA_BUF_PTR); + if (!r->out.old_val) { + return NT_STATUS_NO_MEMORY; + } + prior_val = ldb_msg_find_ldb_val(msg, "priorValue"); + + if (prior_val && prior_val->length) { + secret.data = prior_val->data; + secret.length = prior_val->length; + + /* Encrypt */ + crypt_secret = sess_encrypt_blob(mem_ctx, &secret, &session_key); + if (!crypt_secret.length) { + return NT_STATUS_NO_MEMORY; + } + r->out.old_val->buf = talloc(mem_ctx, struct lsa_DATA_BUF); + if (!r->out.old_val->buf) { + return NT_STATUS_NO_MEMORY; + } + r->out.old_val->buf->size = crypt_secret.length; + r->out.old_val->buf->length = crypt_secret.length; + r->out.old_val->buf->data = crypt_secret.data; + } + } + + if (r->in.old_mtime) { + r->out.old_mtime = talloc(mem_ctx, NTTIME); + if (!r->out.old_mtime) { + return NT_STATUS_NO_MEMORY; + } + *r->out.old_mtime = ldb_msg_find_attr_as_uint64(msg, "priorSetTime", 0); + } + + if (r->in.new_val) { + const struct ldb_val *new_val; + r->out.new_val = talloc_zero(mem_ctx, struct lsa_DATA_BUF_PTR); + if (!r->out.new_val) { + return NT_STATUS_NO_MEMORY; + } + + new_val = ldb_msg_find_ldb_val(msg, "currentValue"); + + if (new_val && new_val->length) { + secret.data = new_val->data; + secret.length = new_val->length; + + /* Encrypt */ + crypt_secret = sess_encrypt_blob(mem_ctx, &secret, &session_key); + if (!crypt_secret.length) { + return NT_STATUS_NO_MEMORY; + } + r->out.new_val->buf = talloc(mem_ctx, struct lsa_DATA_BUF); + if (!r->out.new_val->buf) { + return NT_STATUS_NO_MEMORY; + } + r->out.new_val->buf->length = crypt_secret.length; + r->out.new_val->buf->size = crypt_secret.length; + r->out.new_val->buf->data = crypt_secret.data; + } + } + + if (r->in.new_mtime) { + r->out.new_mtime = talloc(mem_ctx, NTTIME); + if (!r->out.new_mtime) { + return NT_STATUS_NO_MEMORY; + } + *r->out.new_mtime = ldb_msg_find_attr_as_uint64(msg, "lastSetTime", 0); + } + + return NT_STATUS_OK; +} + + +/* + lsa_LookupPrivValue +*/ +static NTSTATUS dcesrv_lsa_LookupPrivValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupPrivValue *r) +{ + struct dcesrv_handle *h; + int id; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + id = sec_privilege_id(r->in.name->string); + if (id == SEC_PRIV_INVALID) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + r->out.luid->low = id; + r->out.luid->high = 0; + + return NT_STATUS_OK; +} + + +/* + lsa_LookupPrivName +*/ +static NTSTATUS dcesrv_lsa_LookupPrivName(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupPrivName *r) +{ + struct dcesrv_handle *h; + struct lsa_StringLarge *name; + const char *privname; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + if (r->in.luid->high != 0) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + privname = sec_privilege_name(r->in.luid->low); + if (privname == NULL) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + name = talloc(mem_ctx, struct lsa_StringLarge); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + name->string = privname; + + *r->out.name = name; + + return NT_STATUS_OK; +} + + +/* + lsa_LookupPrivDisplayName +*/ +static NTSTATUS dcesrv_lsa_LookupPrivDisplayName(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupPrivDisplayName *r) +{ + struct dcesrv_handle *h; + struct lsa_StringLarge *disp_name = NULL; + enum sec_privilege id; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + id = sec_privilege_id(r->in.name->string); + if (id == SEC_PRIV_INVALID) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + disp_name = talloc(mem_ctx, struct lsa_StringLarge); + if (disp_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + disp_name->string = sec_privilege_display_name(id, &r->in.language_id); + if (disp_name->string == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + *r->out.disp_name = disp_name; + *r->out.returned_language_id = 0; + + return NT_STATUS_OK; +} + + +/* + lsa_EnumAccountsWithUserRight +*/ +static NTSTATUS dcesrv_lsa_EnumAccountsWithUserRight(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_EnumAccountsWithUserRight *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *state; + int ret, i; + struct ldb_message **res; + const char * const attrs[] = { "objectSid", NULL}; + const char *privname; + bool ok; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + if (r->in.name == NULL) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + privname = r->in.name->string; + + ok = dcesrc_lsa_valid_AccountRight(privname); + if (!ok) { + return NT_STATUS_NO_SUCH_PRIVILEGE; + } + + ret = gendb_search(state->pdb, mem_ctx, NULL, &res, attrs, + "privilege=%s", privname); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if (ret == 0) { + return NT_STATUS_NO_MORE_ENTRIES; + } + + r->out.sids->sids = talloc_array(r->out.sids, struct lsa_SidPtr, ret); + if (r->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + for (i=0;i<ret;i++) { + r->out.sids->sids[i].sid = samdb_result_dom_sid(r->out.sids->sids, + res[i], "objectSid"); + NT_STATUS_HAVE_NO_MEMORY(r->out.sids->sids[i].sid); + } + r->out.sids->num_sids = ret; + + return NT_STATUS_OK; +} + + +/* + lsa_AddAccountRights +*/ +static NTSTATUS dcesrv_lsa_AddAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_AddAccountRights *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *state; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + return dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, state, + LDB_FLAG_MOD_ADD, + r->in.sid, r->in.rights); +} + + +/* + lsa_RemoveAccountRights +*/ +static NTSTATUS dcesrv_lsa_RemoveAccountRights(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_RemoveAccountRights *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *state; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + state = h->data; + + return dcesrv_lsa_AddRemoveAccountRights(dce_call, mem_ctx, state, + LDB_FLAG_MOD_DELETE, + r->in.sid, r->in.rights); +} + + +/* + lsa_StorePrivateData +*/ +static NTSTATUS dcesrv_lsa_StorePrivateData(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_StorePrivateData *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_RetrievePrivateData +*/ +static NTSTATUS dcesrv_lsa_RetrievePrivateData(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_RetrievePrivateData *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_GetUserName +*/ +static NTSTATUS dcesrv_lsa_GetUserName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_GetUserName *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS status = NT_STATUS_OK; + const char *account_name; + const char *authority_name; + struct lsa_String *_account_name; + struct lsa_String *_authority_name = NULL; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + /* this is what w2k3 does */ + r->out.account_name = r->in.account_name; + r->out.authority_name = r->in.authority_name; + + if (r->in.account_name + && *r->in.account_name + /* && *(*r->in.account_name)->string */ + ) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.authority_name + && *r->in.authority_name + /* && *(*r->in.authority_name)->string */ + ) { + return NT_STATUS_INVALID_PARAMETER; + } + + account_name = talloc_reference(mem_ctx, session_info->info->account_name); + authority_name = talloc_reference(mem_ctx, session_info->info->domain_name); + + _account_name = talloc(mem_ctx, struct lsa_String); + NT_STATUS_HAVE_NO_MEMORY(_account_name); + _account_name->string = account_name; + + if (r->in.authority_name) { + _authority_name = talloc(mem_ctx, struct lsa_String); + NT_STATUS_HAVE_NO_MEMORY(_authority_name); + _authority_name->string = authority_name; + } + + *r->out.account_name = _account_name; + if (r->out.authority_name) { + *r->out.authority_name = _authority_name; + } + + return status; +} + +/* + lsa_SetInfoPolicy2 +*/ +static NTSTATUS dcesrv_lsa_SetInfoPolicy2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_SetInfoPolicy2 *r) +{ + /* need to support these */ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void kdc_get_policy(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct smb_krb5_context *smb_krb5_context, + struct lsa_DomainInfoKerberos *k) +{ + time_t svc_tkt_lifetime; + time_t usr_tkt_lifetime; + time_t renewal_lifetime; + + /* Our KDC always re-validates the client */ + k->authentication_options = LSA_POLICY_KERBEROS_VALIDATE_CLIENT; + + lpcfg_default_kdc_policy(mem_ctx, lp_ctx, &svc_tkt_lifetime, + &usr_tkt_lifetime, &renewal_lifetime); + + unix_to_nt_time(&k->service_tkt_lifetime, svc_tkt_lifetime); + unix_to_nt_time(&k->user_tkt_lifetime, usr_tkt_lifetime); + unix_to_nt_time(&k->user_tkt_renewaltime, renewal_lifetime); +#ifdef SAMBA4_USES_HEIMDAL /* MIT lacks krb5_get_max_time_skew. + However in the parent function we basically just did a full + krb5_context init with the only purpose of getting a global + config option (the max skew), it would probably make more sense + to have a lp_ or ldb global option as the samba default */ + if (smb_krb5_context) { + unix_to_nt_time(&k->clock_skew, + krb5_get_max_time_skew(smb_krb5_context->krb5_context)); + } +#endif + k->reserved = 0; +} +/* + lsa_QueryDomainInformationPolicy +*/ +static NTSTATUS dcesrv_lsa_QueryDomainInformationPolicy(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_QueryDomainInformationPolicy *r) +{ + union lsa_DomainInformationPolicy *info; + + info = talloc_zero(r->out.info, union lsa_DomainInformationPolicy); + if (!info) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case LSA_DOMAIN_INFO_POLICY_EFS: + talloc_free(info); + *r->out.info = NULL; + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + case LSA_DOMAIN_INFO_POLICY_KERBEROS: + { + struct lsa_DomainInfoKerberos *k = &info->kerberos_info; + struct smb_krb5_context *smb_krb5_context; + int ret = smb_krb5_init_context(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + &smb_krb5_context); + if (ret != 0) { + talloc_free(info); + *r->out.info = NULL; + return NT_STATUS_INTERNAL_ERROR; + } + kdc_get_policy(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, + smb_krb5_context, + k); + talloc_free(smb_krb5_context); + *r->out.info = info; + return NT_STATUS_OK; + } + default: + talloc_free(info); + *r->out.info = NULL; + return NT_STATUS_INVALID_INFO_CLASS; + } +} + +/* + lsa_SetDomInfoPolicy +*/ +static NTSTATUS dcesrv_lsa_SetDomainInformationPolicy(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_SetDomainInformationPolicy *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* + lsa_TestCall +*/ +static NTSTATUS dcesrv_lsa_TestCall(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_TestCall *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* + lsa_CREDRWRITE +*/ +static NTSTATUS dcesrv_lsa_CREDRWRITE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRWRITE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRREAD +*/ +static NTSTATUS dcesrv_lsa_CREDRREAD(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRREAD *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRENUMERATE +*/ +static NTSTATUS dcesrv_lsa_CREDRENUMERATE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRENUMERATE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRWRITEDOMAINCREDENTIALS +*/ +static NTSTATUS dcesrv_lsa_CREDRWRITEDOMAINCREDENTIALS(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRWRITEDOMAINCREDENTIALS *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRREADDOMAINCREDENTIALS +*/ +static NTSTATUS dcesrv_lsa_CREDRREADDOMAINCREDENTIALS(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRREADDOMAINCREDENTIALS *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRDELETE +*/ +static NTSTATUS dcesrv_lsa_CREDRDELETE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRDELETE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRGETTARGETINFO +*/ +static NTSTATUS dcesrv_lsa_CREDRGETTARGETINFO(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRGETTARGETINFO *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRPROFILELOADED +*/ +static NTSTATUS dcesrv_lsa_CREDRPROFILELOADED(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRPROFILELOADED *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_CREDRGETSESSIONTYPES +*/ +static NTSTATUS dcesrv_lsa_CREDRGETSESSIONTYPES(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRGETSESSIONTYPES *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARREGISTERAUDITEVENT +*/ +static NTSTATUS dcesrv_lsa_LSARREGISTERAUDITEVENT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARREGISTERAUDITEVENT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARGENAUDITEVENT +*/ +static NTSTATUS dcesrv_lsa_LSARGENAUDITEVENT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARGENAUDITEVENT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARUNREGISTERAUDITEVENT +*/ +static NTSTATUS dcesrv_lsa_LSARUNREGISTERAUDITEVENT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARUNREGISTERAUDITEVENT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_lsaRQueryForestTrustInformation +*/ +static NTSTATUS dcesrv_lsa_lsaRQueryForestTrustInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_lsaRQueryForestTrustInformation *r) +{ + struct dcesrv_handle *h = NULL; + struct lsa_policy_state *p_state = NULL; + int forest_level = DS_DOMAIN_FUNCTION_2000; + const char * const trust_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + "msDS-TrustForestTrustInfo", + NULL + }; + struct ldb_message *trust_tdo_msg = NULL; + struct lsa_TrustDomainInfoInfoEx *trust_tdo = NULL; + struct ForestTrustInfo *trust_fti = NULL; + struct lsa_ForestTrustInformation *trust_lfti = NULL; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + p_state = h->data; + + if (strcmp(p_state->domain_dns, p_state->forest_dns)) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + forest_level = dsdb_forest_functional_level(p_state->sam_ldb); + if (forest_level < DS_DOMAIN_FUNCTION_2003) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + if (r->in.trusted_domain_name->string == NULL) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + status = dsdb_trust_search_tdo(p_state->sam_ldb, + r->in.trusted_domain_name->string, + r->in.trusted_domain_name->string, + trust_attrs, mem_ctx, &trust_tdo_msg); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dsdb_trust_parse_tdo_info(mem_ctx, trust_tdo_msg, &trust_tdo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (!(trust_tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.highest_record_type >= LSA_FOREST_TRUST_RECORD_TYPE_LAST) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_trust_parse_forest_info(mem_ctx, + trust_tdo_msg, + &trust_fti); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dsdb_trust_forest_info_to_lsa(mem_ctx, trust_fti, + &trust_lfti); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *r->out.forest_trust_info = trust_lfti; + return NT_STATUS_OK; +} + +/* + lsa_lsaRSetForestTrustInformation +*/ +static NTSTATUS dcesrv_lsa_lsaRSetForestTrustInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_lsaRSetForestTrustInformation *r) +{ + struct dcesrv_handle *h; + struct lsa_policy_state *p_state; + const char * const trust_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + "msDS-TrustForestTrustInfo", + NULL + }; + struct ldb_message *trust_tdo_msg = NULL; + struct lsa_TrustDomainInfoInfoEx *trust_tdo = NULL; + struct lsa_ForestTrustInformation *step1_lfti = NULL; + struct lsa_ForestTrustInformation *step2_lfti = NULL; + struct ForestTrustInfo *trust_fti = NULL; + struct ldb_result *trusts_res = NULL; + unsigned int i; + struct lsa_TrustDomainInfoInfoEx *xref_tdo = NULL; + struct lsa_ForestTrustInformation *xref_lfti = NULL; + struct lsa_ForestTrustCollisionInfo *c_info = NULL; + DATA_BLOB ft_blob = {}; + struct ldb_message *msg = NULL; + struct server_id *server_ids = NULL; + uint32_t num_server_ids = 0; + NTSTATUS status; + enum ndr_err_code ndr_err; + int ret; + bool in_transaction = false; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + DCESRV_PULL_HANDLE(h, r->in.handle, LSA_HANDLE_POLICY); + + p_state = h->data; + + if (strcmp(p_state->domain_dns, p_state->forest_dns)) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + if (r->in.check_only == 0) { + ret = ldb_transaction_start(p_state->sam_ldb); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + in_transaction = true; + } + + /* + * abort if we are not a PDC + * + * In future we should use a function like IsEffectiveRoleOwner() + */ + if (!samdb_is_pdc(p_state->sam_ldb)) { + status = NT_STATUS_INVALID_DOMAIN_ROLE; + goto done; + } + + if (r->in.trusted_domain_name->string == NULL) { + status = NT_STATUS_NO_SUCH_DOMAIN; + goto done; + } + + status = dsdb_trust_search_tdo(p_state->sam_ldb, + r->in.trusted_domain_name->string, + r->in.trusted_domain_name->string, + trust_attrs, mem_ctx, &trust_tdo_msg); + if (NT_STATUS_EQUAL(status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + status = NT_STATUS_NO_SUCH_DOMAIN; + goto done; + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dsdb_trust_parse_tdo_info(mem_ctx, trust_tdo_msg, &trust_tdo); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (!(trust_tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + if (r->in.highest_record_type >= LSA_FOREST_TRUST_RECORD_TYPE_LAST) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + /* + * verify and normalize the given forest trust info. + * + * Step1: doesn't reorder yet, so step1_lfti might contain + * NULL entries. This means dsdb_trust_verify_forest_info() + * can generate collision entries with the callers index. + */ + status = dsdb_trust_normalize_forest_info_step1(mem_ctx, + r->in.forest_trust_info, + &step1_lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + c_info = talloc_zero(r->out.collision_info, + struct lsa_ForestTrustCollisionInfo); + if (c_info == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + /* + * First check our own forest, then other domains/forests + */ + + status = dsdb_trust_xref_tdo_info(mem_ctx, p_state->sam_ldb, + &xref_tdo); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + status = dsdb_trust_xref_forest_info(mem_ctx, p_state->sam_ldb, + &xref_lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* + * The documentation proposed to generate + * LSA_FOREST_TRUST_COLLISION_XREF collisions. + * But Windows always uses LSA_FOREST_TRUST_COLLISION_TDO. + */ + status = dsdb_trust_verify_forest_info(xref_tdo, xref_lfti, + LSA_FOREST_TRUST_COLLISION_TDO, + c_info, step1_lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* fetch all other trusted domain objects */ + status = dsdb_trust_search_tdos(p_state->sam_ldb, + trust_tdo->domain_name.string, + trust_attrs, + mem_ctx, &trusts_res); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* + * now check against the other domains. + * and generate LSA_FOREST_TRUST_COLLISION_TDO collisions. + */ + for (i = 0; i < trusts_res->count; i++) { + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + struct ForestTrustInfo *fti = NULL; + struct lsa_ForestTrustInformation *lfti = NULL; + + status = dsdb_trust_parse_tdo_info(mem_ctx, + trusts_res->msgs[i], + &tdo); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dsdb_trust_parse_forest_info(tdo, + trusts_res->msgs[i], + &fti); + if (NT_STATUS_EQUAL(status, NT_STATUS_NOT_FOUND)) { + continue; + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dsdb_trust_forest_info_to_lsa(tdo, fti, &lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dsdb_trust_verify_forest_info(tdo, lfti, + LSA_FOREST_TRUST_COLLISION_TDO, + c_info, step1_lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + TALLOC_FREE(tdo); + } + + if (r->in.check_only != 0) { + status = NT_STATUS_OK; + goto done; + } + + /* + * not just a check, write info back + */ + + /* + * normalize the given forest trust info. + * + * Step2: adds TOP_LEVEL_NAME[_EX] in reverse order, + * followed by DOMAIN_INFO in reverse order. It also removes + * possible NULL entries from Step1. + */ + status = dsdb_trust_normalize_forest_info_step2(mem_ctx, step1_lfti, + &step2_lfti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = dsdb_trust_forest_info_from_lsa(mem_ctx, step2_lfti, + &trust_fti); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + ndr_err = ndr_push_struct_blob(&ft_blob, mem_ctx, trust_fti, + (ndr_push_flags_fn_t)ndr_push_ForestTrustInfo); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + msg->dn = ldb_dn_copy(mem_ctx, trust_tdo_msg->dn); + if (!msg->dn) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + ret = ldb_msg_append_value(msg, "msDS-TrustForestTrustInfo", + &ft_blob, LDB_FLAG_MOD_REPLACE); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + ret = ldb_modify(p_state->sam_ldb, msg); + if (ret != LDB_SUCCESS) { + status = dsdb_ldb_err_to_ntstatus(ret); + + DEBUG(0, ("Failed to store Forest Trust Info: %s\n", + ldb_errstring(p_state->sam_ldb))); + + goto done; + } + + /* ok, all fine, commit transaction and return */ + in_transaction = false; + ret = ldb_transaction_commit(p_state->sam_ldb); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + /* + * Notify winbindd that we have a acquired forest trust info + */ + status = irpc_servers_byname(imsg_ctx, + mem_ctx, + "winbind_server", + &num_server_ids, + &server_ids); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("irpc_servers_byname failed\n"); + goto done; + } + + imessaging_send(imsg_ctx, + server_ids[0], + MSG_WINBIND_RELOAD_TRUSTED_DOMAINS, + NULL); + + status = NT_STATUS_OK; + +done: + if (NT_STATUS_IS_OK(status) && c_info->count != 0) { + *r->out.collision_info = c_info; + } + + if (in_transaction) { + ldb_transaction_cancel(p_state->sam_ldb); + } + + return status; +} + +/* + lsa_CREDRRENAME +*/ +static NTSTATUS dcesrv_lsa_CREDRRENAME(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_CREDRRENAME *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* + lsa_LSAROPENPOLICYSCE +*/ +static NTSTATUS dcesrv_lsa_LSAROPENPOLICYSCE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSAROPENPOLICYSCE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARADTREGISTERSECURITYEVENTSOURCE +*/ +static NTSTATUS dcesrv_lsa_LSARADTREGISTERSECURITYEVENTSOURCE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARADTREGISTERSECURITYEVENTSOURCE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE +*/ +static NTSTATUS dcesrv_lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARADTUNREGISTERSECURITYEVENTSOURCE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + lsa_LSARADTREPORTSECURITYEVENT +*/ +static NTSTATUS dcesrv_lsa_LSARADTREPORTSECURITYEVENT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LSARADTREPORTSECURITYEVENT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_lsa_s.c" + + + +/***************************************** +NOTE! The remaining calls below were +removed in w2k3, so the DCESRV_FAULT() +replies are the correct implementation. Do +not try and fill these in with anything else +******************************************/ + +/* + dssetup_DsRoleDnsNameToFlatName +*/ +static WERROR dcesrv_dssetup_DsRoleDnsNameToFlatName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleDnsNameToFlatName *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleDcAsDc +*/ +static WERROR dcesrv_dssetup_DsRoleDcAsDc(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleDcAsDc *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleDcAsReplica +*/ +static WERROR dcesrv_dssetup_DsRoleDcAsReplica(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleDcAsReplica *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleDemoteDc +*/ +static WERROR dcesrv_dssetup_DsRoleDemoteDc(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleDemoteDc *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleGetDcOperationProgress +*/ +static WERROR dcesrv_dssetup_DsRoleGetDcOperationProgress(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleGetDcOperationProgress *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleGetDcOperationResults +*/ +static WERROR dcesrv_dssetup_DsRoleGetDcOperationResults(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleGetDcOperationResults *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleCancel +*/ +static WERROR dcesrv_dssetup_DsRoleCancel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleCancel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleServerSaveStateForUpgrade +*/ +static WERROR dcesrv_dssetup_DsRoleServerSaveStateForUpgrade(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleServerSaveStateForUpgrade *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleUpgradeDownlevelServer +*/ +static WERROR dcesrv_dssetup_DsRoleUpgradeDownlevelServer(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleUpgradeDownlevelServer *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + dssetup_DsRoleAbortDownlevelServerUpgrade +*/ +static WERROR dcesrv_dssetup_DsRoleAbortDownlevelServerUpgrade(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct dssetup_DsRoleAbortDownlevelServerUpgrade *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_dssetup_s.c" + +NTSTATUS dcerpc_server_lsa_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = dcerpc_server_dssetup_init(ctx); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + ret = dcerpc_server_lsarpc_init(ctx); + if (!NT_STATUS_IS_OK(ret)) { + return ret; + } + return ret; +} diff --git a/source4/rpc_server/lsa/lsa.h b/source4/rpc_server/lsa/lsa.h new file mode 100644 index 0000000..5c00ba5 --- /dev/null +++ b/source4/rpc_server/lsa/lsa.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the lsarpc pipe + + Copyright (C) Andrew Tridgell 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "auth/auth.h" +#include "dsdb/samdb/samdb.h" +#include "libcli/ldap/ldap_ndr.h" +#include <ldb_errors.h> +#include "libcli/security/security.h" +#include "libcli/auth/libcli_auth.h" +#include "param/secrets.h" +#include "../lib/util/util_ldb.h" +#include "librpc/gen_ndr/ndr_dssetup.h" +#include "param/param.h" + +/* + state associated with a lsa_OpenPolicy() operation +*/ +struct lsa_policy_state { + struct dcesrv_handle *handle; + struct ldb_context *sam_ldb; + struct ldb_context *pdb; + struct ldb_dn *domain_dn; + struct ldb_dn *forest_dn; + struct ldb_dn *builtin_dn; + struct ldb_dn *system_dn; + const char *domain_name; + const char *domain_dns; + const char *forest_dns; + struct dom_sid *domain_sid; + struct GUID domain_guid; + struct dom_sid *builtin_sid; + struct dom_sid *nt_authority_sid; + struct dom_sid *creator_owner_domain_sid; + struct dom_sid *world_domain_sid; + int mixed_domain; + struct security_descriptor *sd; + uint32_t access_mask; +}; + +enum lsa_handle { + LSA_HANDLE_POLICY, + LSA_HANDLE_ACCOUNT, + LSA_HANDLE_SECRET, + LSA_HANDLE_TRUSTED_DOMAIN +}; + +#include "rpc_server/lsa/proto.h" + diff --git a/source4/rpc_server/lsa/lsa_init.c b/source4/rpc_server/lsa/lsa_init.c new file mode 100644 index 0000000..1065cc3 --- /dev/null +++ b/source4/rpc_server/lsa/lsa_init.c @@ -0,0 +1,293 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the lsarpc pipe + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "rpc_server/lsa/lsa.h" + +/* + * This matches a Windows 2012R2 dc in + * a domain with function level 2012R2. + */ +#define DCESRV_LSA_POLICY_SD_SDDL \ + "O:BAG:SY" \ + "D:" \ + "(D;;0x00000800;;;AN)" \ + "(A;;0x000f1fff;;;BA)" \ + "(A;;0x00020801;;;WD)" \ + "(A;;0x00000801;;;AN)" \ + "(A;;0x00001000;;;LS)" \ + "(A;;0x00001000;;;NS)" \ + "(A;;0x00001000;;;S-1-5-17)" \ + "(A;;0x00000801;;;AC)" \ + "(A;;0x00000801;;;S-1-15-2-2)" + +static const struct generic_mapping dcesrv_lsa_policy_mapping = { + LSA_POLICY_READ, + LSA_POLICY_WRITE, + LSA_POLICY_EXECUTE, + LSA_POLICY_ALL_ACCESS +}; + +NTSTATUS dcesrv_lsa_get_policy_state(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + uint32_t access_desired, + struct lsa_policy_state **_state) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + enum security_user_level security_level; + struct lsa_policy_state *state; + struct ldb_result *dom_res; + const char *dom_attrs[] = { + "objectSid", + "objectGUID", + "nTMixedDomain", + "fSMORoleOwner", + NULL + }; + char *p; + int ret; + + state = talloc_zero(mem_ctx, struct lsa_policy_state); + if (!state) { + return NT_STATUS_NO_MEMORY; + } + + /* make sure the sam database is accessible */ + state->sam_ldb = dcesrv_samdb_connect_as_user(state, dce_call); + if (state->sam_ldb == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* and the privilege database */ + state->pdb = privilege_connect(state, dce_call->conn->dce_ctx->lp_ctx); + if (state->pdb == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* work out the domain_dn - useful for so many calls its worth + fetching here */ + state->domain_dn = ldb_get_default_basedn(state->sam_ldb); + if (!state->domain_dn) { + return NT_STATUS_NO_MEMORY; + } + + /* work out the forest root_dn - useful for so many calls its worth + fetching here */ + state->forest_dn = ldb_get_root_basedn(state->sam_ldb); + if (!state->forest_dn) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_search(state->sam_ldb, mem_ctx, &dom_res, + state->domain_dn, LDB_SCOPE_BASE, dom_attrs, NULL); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + if (dom_res->count != 1) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->domain_sid = samdb_result_dom_sid(state, dom_res->msgs[0], "objectSid"); + if (!state->domain_sid) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->domain_guid = samdb_result_guid(dom_res->msgs[0], "objectGUID"); + + state->mixed_domain = ldb_msg_find_attr_as_uint(dom_res->msgs[0], "nTMixedDomain", 0); + + talloc_free(dom_res); + + state->domain_name = lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx); + + state->domain_dns = ldb_dn_canonical_string(state, state->domain_dn); + if (!state->domain_dns) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + p = strchr(state->domain_dns, '/'); + if (p) { + *p = '\0'; + } + + state->forest_dns = ldb_dn_canonical_string(state, state->forest_dn); + if (!state->forest_dns) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + p = strchr(state->forest_dns, '/'); + if (p) { + *p = '\0'; + } + + /* work out the builtin_dn - useful for so many calls its worth + fetching here */ + state->builtin_dn = samdb_search_dn(state->sam_ldb, state, state->domain_dn, "(objectClass=builtinDomain)"); + if (!state->builtin_dn) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + /* work out the system_dn - useful for so many calls its worth + fetching here */ + state->system_dn = samdb_system_container_dn(state->sam_ldb, state); + if (state->system_dn == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->builtin_sid = dom_sid_parse_talloc(state, SID_BUILTIN); + if (!state->builtin_sid) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->nt_authority_sid = dom_sid_parse_talloc(state, SID_NT_AUTHORITY); + if (!state->nt_authority_sid) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->creator_owner_domain_sid = dom_sid_parse_talloc(state, SID_CREATOR_OWNER_DOMAIN); + if (!state->creator_owner_domain_sid) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->world_domain_sid = dom_sid_parse_talloc(state, SID_WORLD_DOMAIN); + if (!state->world_domain_sid) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + state->sd = sddl_decode(state, DCESRV_LSA_POLICY_SD_SDDL, + state->domain_sid); + if (state->sd == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->sd->dacl->revision = SECURITY_ACL_REVISION_NT4; + + se_map_generic(&access_desired, &dcesrv_lsa_policy_mapping); + security_acl_map_generic(state->sd->dacl, &dcesrv_lsa_policy_mapping); + + security_level = security_session_user_level(session_info, NULL); + if (security_level >= SECURITY_SYSTEM) { + /* + * The security descriptor doesn't allow system, + * but we want to allow system via ncalrpc as root. + */ + state->access_mask = access_desired; + if (state->access_mask & SEC_FLAG_MAXIMUM_ALLOWED) { + state->access_mask &= ~SEC_FLAG_MAXIMUM_ALLOWED; + state->access_mask |= LSA_POLICY_ALL_ACCESS; + } + } else { + NTSTATUS status; + + status = se_access_check(state->sd, + session_info->security_token, + access_desired, + &state->access_mask); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(2,("%s: access desired[0x%08X] rejected[0x%08X] - %s\n", + __func__, + (unsigned)access_desired, + (unsigned)state->access_mask, + nt_errstr(status))); + return status; + } + } + + DEBUG(10,("%s: access desired[0x%08X] granted[0x%08X] - success.\n", + __func__, + (unsigned)access_desired, + (unsigned)state->access_mask)); + + *_state = state; + + return NT_STATUS_OK; +} + +/* + lsa_OpenPolicy2 +*/ +NTSTATUS dcesrv_lsa_OpenPolicy2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_OpenPolicy2 *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + NTSTATUS status; + struct lsa_policy_state *state; + struct dcesrv_handle *handle; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + ZERO_STRUCTP(r->out.handle); + + if (r->in.attr != NULL && + r->in.attr->root_dir != NULL) { + /* MS-LSAD 3.1.4.4.1 */ + return NT_STATUS_INVALID_PARAMETER; + } + + status = dcesrv_lsa_get_policy_state(dce_call, mem_ctx, + r->in.access_mask, + &state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + handle = dcesrv_handle_create(dce_call, LSA_HANDLE_POLICY); + if (!handle) { + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, state); + + state->handle = handle; + *r->out.handle = handle->wire_handle; + + /* note that we have completely ignored the attr element of + the OpenPolicy. As far as I can tell, this is what w2k3 + does */ + + return NT_STATUS_OK; +} + +/* + lsa_OpenPolicy + a wrapper around lsa_OpenPolicy2 +*/ +NTSTATUS dcesrv_lsa_OpenPolicy(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_OpenPolicy *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct lsa_OpenPolicy2 r2; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + r2.in.system_name = NULL; + r2.in.attr = r->in.attr; + r2.in.access_mask = r->in.access_mask; + r2.out.handle = r->out.handle; + + return dcesrv_lsa_OpenPolicy2(dce_call, mem_ctx, &r2); +} + + diff --git a/source4/rpc_server/lsa/lsa_lookup.c b/source4/rpc_server/lsa/lsa_lookup.c new file mode 100644 index 0000000..61cb8a1 --- /dev/null +++ b/source4/rpc_server/lsa/lsa_lookup.c @@ -0,0 +1,2281 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the lsarpc pipe + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "rpc_server/lsa/lsa.h" +#include "libds/common/roles.h" +#include "libds/common/flag_mapping.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_lsa_c.h" + +struct dcesrv_lsa_TranslatedItem { + enum lsa_SidType type; + const struct dom_sid *sid; + const char *name; + const char *authority_name; + const struct dom_sid *authority_sid; + uint32_t flags; + uint32_t wb_idx; + bool done; + struct { + const char *domain; /* only $DOMAIN\ */ + const char *namespace; /* $NAMESPACE\ or @$NAMESPACE */ + const char *principal; /* \$PRINCIPAL or $PRIN@IPAL */ + const char *sid; /* "S-1-5-21-9000-8000-7000-6000" */ + const char *rid; /* "00001770" */ + } hints; +}; + +struct dcesrv_lsa_LookupSids_base_state; +struct dcesrv_lsa_LookupNames_base_state; + +struct dcesrv_lsa_Lookup_view { + const char *name; + NTSTATUS (*lookup_sid)(struct dcesrv_lsa_LookupSids_base_state *state, + struct dcesrv_lsa_TranslatedItem *item); + NTSTATUS (*lookup_name)(struct dcesrv_lsa_LookupNames_base_state *state, + struct dcesrv_lsa_TranslatedItem *item); +}; + +struct dcesrv_lsa_Lookup_view_table { + const char *name; + size_t count; + const struct dcesrv_lsa_Lookup_view **array; +}; + +static const struct dcesrv_lsa_Lookup_view_table *dcesrv_lsa_view_table( + enum lsa_LookupNamesLevel level); + +/* + lookup a SID for 1 name +*/ +static NTSTATUS dcesrv_lsa_lookup_name(struct lsa_policy_state *state, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const struct dom_sid *domain_sid, + struct ldb_dn *domain_dn, + const char *principal, + const struct dom_sid **p_sid, + enum lsa_SidType *p_type) +{ + const char * const attrs[] = { "objectSid", "sAMAccountType", NULL}; + struct ldb_message **res = NULL; + const char *nt4_account = NULL; + char *encoded_account = NULL; + const char *at = NULL; + NTSTATUS status; + const struct dom_sid *sid = NULL; + uint32_t atype; + enum lsa_SidType type; + bool match = false; + int ret; + + if ((principal == NULL) || (principal[0] == '\0')) { + return NT_STATUS_NONE_MAPPED; + } + + at = strchr(principal, '@'); + if (at != NULL) { + const char *nt4_domain = NULL; + + status = crack_name_to_nt4_name(mem_ctx, + state->sam_ldb, + DRSUAPI_DS_NAME_FORMAT_USER_PRINCIPAL, + principal, + &nt4_domain, + &nt4_account); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Failed to crack name %s into an NT4 name: %s\n", + principal, nt_errstr(status))); + return status; + } + + match = strequal(nt4_domain, domain_name); + if (!match) { + /* + * TODO: handle multiple domains in a forest. + */ + return NT_STATUS_NONE_MAPPED; + } + } else { + nt4_account = principal; + } + + encoded_account = ldb_binary_encode_string(mem_ctx, nt4_account); + if (encoded_account == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(state->sam_ldb, mem_ctx, domain_dn, &res, attrs, + "(&(sAMAccountName=%s)(objectSid=*))", + encoded_account); + TALLOC_FREE(encoded_account); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + if (ret == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (ret > 1) { + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + DBG_ERR("nt4_account[%s] found %d times (principal[%s]) - %s\n", + nt4_account, ret, principal, nt_errstr(status)); + return status; + } + + sid = samdb_result_dom_sid(mem_ctx, res[0], "objectSid"); + if (sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Check that this is in the domain */ + match = dom_sid_in_domain(domain_sid, sid); + if (!match) { + return NT_STATUS_NONE_MAPPED; + } + + atype = ldb_msg_find_attr_as_uint(res[0], "sAMAccountType", 0); + type = ds_atype_map(atype); + if (type == SID_NAME_UNKNOWN) { + return NT_STATUS_NONE_MAPPED; + } + + *p_sid = sid; + *p_type = type; + return NT_STATUS_OK; +} + + +/* + add to the lsa_RefDomainList for LookupSids and LookupNames +*/ +static NTSTATUS dcesrv_lsa_authority_list(const char *authority_name, + const struct dom_sid *authority_sid, + struct lsa_RefDomainList *domains, + uint32_t *sid_index) +{ + uint32_t i; + + *sid_index = UINT32_MAX; + + if (authority_name == NULL) { + return NT_STATUS_OK; + } + + /* see if we've already done this authority name */ + for (i=0;i<domains->count;i++) { + if (strcasecmp_m(authority_name, domains->domains[i].name.string) == 0) { + *sid_index = i; + return NT_STATUS_OK; + } + } + + domains->domains = talloc_realloc(domains, + domains->domains, + struct lsa_DomainInfo, + domains->count+1); + if (domains->domains == NULL) { + return NT_STATUS_NO_MEMORY; + } + domains->domains[i].name.string = talloc_strdup(domains->domains, + authority_name); + if (domains->domains[i].name.string == NULL) { + return NT_STATUS_NO_MEMORY; + } + domains->domains[i].sid = dom_sid_dup(domains->domains, + authority_sid); + if (domains->domains[i].sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + domains->count++; + domains->max_size = LSA_REF_DOMAIN_LIST_MULTIPLIER * domains->count; + *sid_index = i; + + return NT_STATUS_OK; +} + +/* + lookup a name for 1 SID +*/ +static NTSTATUS dcesrv_lsa_lookup_sid(struct lsa_policy_state *state, + TALLOC_CTX *mem_ctx, + const char *domain_name, + const struct dom_sid *domain_sid, + struct ldb_dn *domain_dn, + const struct dom_sid *sid, + const char **p_name, + enum lsa_SidType *p_type) +{ + const char * const attrs[] = { "sAMAccountName", "sAMAccountType", NULL}; + struct ldb_message **res = NULL; + char *encoded_sid = NULL; + const char *name = NULL; + uint32_t atype; + enum lsa_SidType type; + int ret; + + encoded_sid = ldap_encode_ndr_dom_sid(mem_ctx, sid); + if (encoded_sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(state->sam_ldb, mem_ctx, domain_dn, &res, attrs, + "(&(objectSid=%s)(sAMAccountName=*))", encoded_sid); + TALLOC_FREE(encoded_sid); + if (ret < 0) { + return NT_STATUS_INTERNAL_DB_ERROR; + } + if (ret == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (ret > 1) { + NTSTATUS status = NT_STATUS_INTERNAL_DB_CORRUPTION; + DBG_ERR("sid[%s] found %d times - %s\n", + dom_sid_string(mem_ctx, sid), ret, nt_errstr(status)); + return status; + } + + name = ldb_msg_find_attr_as_string(res[0], "sAMAccountName", NULL); + if (name == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + atype = ldb_msg_find_attr_as_uint(res[0], "sAMAccountType", 0); + type = ds_atype_map(atype); + if (type == SID_NAME_UNKNOWN) { + return NT_STATUS_NONE_MAPPED; + } + + *p_name = name; + *p_type = type; + return NT_STATUS_OK; +} + +struct dcesrv_lsa_LookupSids_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct lsa_policy_state *policy_state; + + struct lsa_LookupSids3 r; + + const struct dcesrv_lsa_Lookup_view_table *view_table; + struct dcesrv_lsa_TranslatedItem *items; + + struct dsdb_trust_routing_table *routing_table; + + struct { + struct dcerpc_binding_handle *irpc_handle; + struct lsa_SidArray sids; + struct lsa_RefDomainList *domains; + struct lsa_TransNameArray2 names; + uint32_t count; + NTSTATUS result; + } wb; + + struct { + struct lsa_LookupSids *l; + struct lsa_LookupSids2 *l2; + struct lsa_LookupSids3 *l3; + } _r; +}; + +static NTSTATUS dcesrv_lsa_LookupSids_base_finish( + struct dcesrv_lsa_LookupSids_base_state *state); +static void dcesrv_lsa_LookupSids_base_map( + struct dcesrv_lsa_LookupSids_base_state *state); +static void dcesrv_lsa_LookupSids_base_done(struct tevent_req *subreq); + +static NTSTATUS dcesrv_lsa_LookupSids_base_call(struct dcesrv_lsa_LookupSids_base_state *state) +{ + struct lsa_LookupSids3 *r = &state->r; + struct tevent_req *subreq = NULL; + uint32_t v; + uint32_t i; + + *r->out.domains = NULL; + r->out.names->count = 0; + r->out.names->names = NULL; + *r->out.count = 0; + + state->view_table = dcesrv_lsa_view_table(r->in.level); + if (state->view_table == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *r->out.domains = talloc_zero(r->out.domains, struct lsa_RefDomainList); + if (*r->out.domains == NULL) { + return NT_STATUS_NO_MEMORY; + } + + r->out.names->names = talloc_zero_array(r->out.names, + struct lsa_TranslatedName2, + r->in.sids->num_sids); + if (r->out.names->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->items = talloc_zero_array(state, + struct dcesrv_lsa_TranslatedItem, + r->in.sids->num_sids); + if (state->items == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<r->in.sids->num_sids;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + uint32_t rid = 0; + + if (r->in.sids->sids[i].sid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + item->type = SID_NAME_UNKNOWN; + item->sid = r->in.sids->sids[i].sid; + + item->hints.sid = dom_sid_string(state->items, item->sid); + if (item->hints.sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dom_sid_split_rid(state->items, item->sid, NULL, &rid); + item->hints.rid = talloc_asprintf(state->items, + "%08X", (unsigned)rid); + if (item->hints.rid == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + for (v=0; v < state->view_table->count; v++) { + const struct dcesrv_lsa_Lookup_view *view = + state->view_table->array[v]; + + for (i=0; i < r->in.sids->num_sids; i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + NTSTATUS status; + + if (item->done) { + continue; + } + + status = view->lookup_sid(state, item); + if (NT_STATUS_IS_OK(status)) { + item->done = true; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + status = NT_STATUS_OK; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_SOME_NOT_MAPPED)) { + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + + if (state->wb.irpc_handle == NULL) { + return dcesrv_lsa_LookupSids_base_finish(state); + } + + state->wb.sids.sids = talloc_zero_array(state, struct lsa_SidPtr, + r->in.sids->num_sids); + if (state->wb.sids.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i < r->in.sids->num_sids; i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + + if (item->done) { + continue; + } + + item->wb_idx = state->wb.sids.num_sids; + state->wb.sids.sids[item->wb_idx] = r->in.sids->sids[i]; + state->wb.sids.num_sids++; + } + + subreq = dcerpc_lsa_LookupSids3_send(state, + state->dce_call->event_ctx, + state->wb.irpc_handle, + &state->wb.sids, + &state->wb.domains, + &state->wb.names, + state->r.in.level, + &state->wb.count, + state->r.in.lookup_options, + state->r.in.client_revision); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY;; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_lsa_LookupSids_base_done, + state); + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_lsa_LookupSids_base_finish( + struct dcesrv_lsa_LookupSids_base_state *state) +{ + struct lsa_LookupSids3 *r = &state->r; + uint32_t i; + + for (i=0;i<r->in.sids->num_sids;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + NTSTATUS status; + uint32_t sid_index = UINT32_MAX; + + status = dcesrv_lsa_authority_list(item->authority_name, + item->authority_sid, + *r->out.domains, + &sid_index); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (item->name == NULL && r->in.level == LSA_LOOKUP_NAMES_ALL) { + if (sid_index == UINT32_MAX) { + item->name = item->hints.sid; + } else { + item->name = item->hints.rid; + } + } + + r->out.names->names[i].sid_type = item->type; + r->out.names->names[i].name.string = item->name; + r->out.names->names[i].sid_index = sid_index; + r->out.names->names[i].unknown = item->flags; + + r->out.names->count++; + if (item->type != SID_NAME_UNKNOWN) { + (*r->out.count)++; + } + } + + if (*r->out.count == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (*r->out.count != r->in.sids->num_sids) { + return STATUS_SOME_UNMAPPED; + } + + return NT_STATUS_OK; +} + +static void dcesrv_lsa_LookupSids_base_map( + struct dcesrv_lsa_LookupSids_base_state *state) +{ + if (state->_r.l3 != NULL) { + struct lsa_LookupSids3 *r = state->_r.l3; + + r->out.result = state->r.out.result; + return; + } + + if (state->_r.l2 != NULL) { + struct lsa_LookupSids2 *r = state->_r.l2; + + r->out.result = state->r.out.result; + return; + } + + if (state->_r.l != NULL) { + struct lsa_LookupSids *r = state->_r.l; + uint32_t i; + + r->out.result = state->r.out.result; + + SMB_ASSERT(state->r.out.names->count <= r->in.sids->num_sids); + for (i = 0; i < state->r.out.names->count; i++) { + struct lsa_TranslatedName2 *n2 = + &state->r.out.names->names[i]; + struct lsa_TranslatedName *n = + &r->out.names->names[i]; + + n->sid_type = n2->sid_type; + n->name = n2->name; + n->sid_index = n2->sid_index; + } + r->out.names->count = state->r.out.names->count; + return; + } +} + +static void dcesrv_lsa_LookupSids_base_done(struct tevent_req *subreq) +{ + struct dcesrv_lsa_LookupSids_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_lsa_LookupSids_base_state); + struct dcesrv_call_state *dce_call = state->dce_call; + NTSTATUS status; + uint32_t i; + + status = dcerpc_lsa_LookupSids3_recv(subreq, state->mem_ctx, + &state->wb.result); + TALLOC_FREE(subreq); + TALLOC_FREE(state->wb.irpc_handle); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + goto finished; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + goto finished; + } + + status = state->wb.result; + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + status = NT_STATUS_OK; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_SOME_NOT_MAPPED)) { + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + goto finished; + } + + for (i=0; i < state->r.in.sids->num_sids; i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + struct lsa_TranslatedName2 *s2 = NULL; + struct lsa_DomainInfo *d = NULL; + + if (item->done) { + continue; + } + + if (item->wb_idx >= state->wb.names.count) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + s2 = &state->wb.names.names[item->wb_idx]; + + item->type = s2->sid_type; + item->name = s2->name.string; + item->flags = s2->unknown; + + if (s2->sid_index == UINT32_MAX) { + continue; + } + + if (state->wb.domains == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + if (s2->sid_index >= state->wb.domains->count) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + d = &state->wb.domains->domains[s2->sid_index]; + + item->authority_name = d->name.string; + item->authority_sid = d->sid; + } + + status = dcesrv_lsa_LookupSids_base_finish(state); + finished: + state->r.out.result = status; + dcesrv_lsa_LookupSids_base_map(state); + + status = dcesrv_reply(dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + lsa_LookupSids2 +*/ +NTSTATUS dcesrv_lsa_LookupSids2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupSids2 *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_lsa_LookupSids_base_state *state = NULL; + struct dcesrv_handle *policy_handle = NULL; + NTSTATUS status; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + *r->out.domains = NULL; + r->out.names->count = 0; + r->out.names->names = NULL; + *r->out.count = 0; + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupSids_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->policy_state = policy_handle->data; + + state->r.in.sids = r->in.sids; + state->r.in.level = r->in.level; + state->r.in.lookup_options = r->in.lookup_options; + state->r.in.client_revision = r->in.client_revision; + state->r.in.names = r->in.names; + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.names = r->out.names; + state->r.out.count = r->out.count; + + state->_r.l2 = r; + + status = dcesrv_lsa_LookupSids_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupSids_base_map(state); + return status; +} + +/* A random hexidecimal number (honest!) */ +#define LSA_SERVER_IMPLICIT_POLICY_STATE_MAGIC 0xc0c99e00 + +/* + Ensure we're operating on an schannel connection, + and use a lsa_policy_state cache on the connection. +*/ +static NTSTATUS schannel_call_setup(struct dcesrv_call_state *dce_call, + struct lsa_policy_state **_policy_state) +{ + struct lsa_policy_state *policy_state = NULL; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + if (transport != NCACN_IP_TCP) { + /* We can't call DCESRV_FAULT() in the sub-function */ + dce_call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + /* + * We don't have policy handles on this call. So this must be restricted + * to crypto connections only. + * + * NB. gensec requires schannel connections to + * have at least DCERPC_AUTH_LEVEL_INTEGRITY. + */ + dcesrv_call_auth_info(dce_call, &auth_type, NULL); + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + /* We can't call DCESRV_FAULT() in the sub-function */ + dce_call->fault_code = DCERPC_FAULT_ACCESS_DENIED; + return NT_STATUS_ACCESS_DENIED; + } + + /* + * We don't have a policy handle on this call, so we want to + * make a policy state and cache it for the life of the + * connection, to avoid re-opening the DB each call. + */ + policy_state + = dcesrv_iface_state_find_conn(dce_call, + LSA_SERVER_IMPLICIT_POLICY_STATE_MAGIC, + struct lsa_policy_state); + + if (policy_state == NULL) { + NTSTATUS status + = dcesrv_lsa_get_policy_state(dce_call, + dce_call /* mem_ctx */, + 0, /* we skip access checks */ + &policy_state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * This will talloc_steal() policy_state onto the + * connection, which has longer lifetime than the + * immidiate caller requires + */ + status = dcesrv_iface_state_store_conn(dce_call, + LSA_SERVER_IMPLICIT_POLICY_STATE_MAGIC, + policy_state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + *_policy_state = policy_state; + return NT_STATUS_OK; +} + +/* + lsa_LookupSids3 + + Identical to LookupSids2, but doesn't take a policy handle + +*/ +NTSTATUS dcesrv_lsa_LookupSids3(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupSids3 *r) +{ + struct dcesrv_lsa_LookupSids_base_state *state = NULL; + NTSTATUS status; + + *r->out.domains = NULL; + r->out.names->count = 0; + r->out.names->names = NULL; + *r->out.count = 0; + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupSids_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * We don't have a policy handle on this call, so we want to + * make a policy state and cache it for the life of the + * connection, to avoid re-opening the DB each call. + * + * This also enforces that this is only available over + * ncacn_ip_tcp and with SCHANNEL. + * + * schannel_call_setup may also set the fault state. + * + * state->policy_state is shared between all calls on this + * connection and is moved with talloc_steal() under the + * connection, not dce_call or state. + */ + status = schannel_call_setup(dce_call, &state->policy_state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + state->r.in.sids = r->in.sids; + state->r.in.level = r->in.level; + state->r.in.lookup_options = r->in.lookup_options; + state->r.in.client_revision = r->in.client_revision; + state->r.in.names = r->in.names; + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.names = r->out.names; + state->r.out.count = r->out.count; + + state->_r.l3 = r; + + status = dcesrv_lsa_LookupSids_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupSids_base_map(state); + return status; +} + + +/* + lsa_LookupSids +*/ +NTSTATUS dcesrv_lsa_LookupSids(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LookupSids *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_lsa_LookupSids_base_state *state = NULL; + struct dcesrv_handle *policy_handle = NULL; + NTSTATUS status; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + *r->out.domains = NULL; + r->out.names->count = 0; + r->out.names->names = NULL; + *r->out.count = 0; + + r->out.names->names = talloc_zero_array(r->out.names, + struct lsa_TranslatedName, + r->in.sids->num_sids); + if (r->out.names->names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupSids_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->policy_state = policy_handle->data; + + state->r.in.sids = r->in.sids; + state->r.in.level = r->in.level; + state->r.in.lookup_options = LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES; + state->r.in.client_revision = LSA_CLIENT_REVISION_1; + state->r.in.names = talloc_zero(state, struct lsa_TransNameArray2); + if (state->r.in.names == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.names = talloc_zero(state, struct lsa_TransNameArray2); + if (state->r.out.names == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.out.count = r->out.count; + + state->_r.l = r; + + status = dcesrv_lsa_LookupSids_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupSids_base_map(state); + return status; +} + +struct dcesrv_lsa_LookupNames_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct lsa_policy_state *policy_state; + + struct lsa_LookupNames4 r; + + const struct dcesrv_lsa_Lookup_view_table *view_table; + struct dcesrv_lsa_TranslatedItem *items; + + struct dsdb_trust_routing_table *routing_table; + + struct { + struct dcerpc_binding_handle *irpc_handle; + uint32_t num_names; + struct lsa_String *names; + struct lsa_RefDomainList *domains; + struct lsa_TransSidArray3 sids; + uint32_t count; + NTSTATUS result; + } wb; + + struct { + struct lsa_LookupNames *l; + struct lsa_LookupNames2 *l2; + struct lsa_LookupNames3 *l3; + struct lsa_LookupNames4 *l4; + } _r; +}; + +static NTSTATUS dcesrv_lsa_LookupNames_base_finish( + struct dcesrv_lsa_LookupNames_base_state *state); +static void dcesrv_lsa_LookupNames_base_map( + struct dcesrv_lsa_LookupNames_base_state *state); +static void dcesrv_lsa_LookupNames_base_done(struct tevent_req *subreq); + +static NTSTATUS dcesrv_lsa_LookupNames_base_call(struct dcesrv_lsa_LookupNames_base_state *state) +{ + struct lsa_LookupNames4 *r = &state->r; + enum lsa_LookupOptions invalid_lookup_options = 0; + struct tevent_req *subreq = NULL; + uint32_t v; + uint32_t i; + + *r->out.domains = NULL; + r->out.sids->count = 0; + r->out.sids->sids = NULL; + *r->out.count = 0; + + if (r->in.level != LSA_LOOKUP_NAMES_ALL) { + invalid_lookup_options |= + LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES_LOCAL; + } + if (r->in.lookup_options & invalid_lookup_options) { + return NT_STATUS_INVALID_PARAMETER; + } + + state->view_table = dcesrv_lsa_view_table(r->in.level); + if (state->view_table == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + *r->out.domains = talloc_zero(r->out.domains, struct lsa_RefDomainList); + if (*r->out.domains == NULL) { + return NT_STATUS_NO_MEMORY; + } + + r->out.sids->sids = talloc_zero_array(r->out.sids, + struct lsa_TranslatedSid3, + r->in.num_names); + if (r->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->items = talloc_zero_array(state, + struct dcesrv_lsa_TranslatedItem, + r->in.num_names); + if (state->items == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<r->in.num_names;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + char *p = NULL; + + item->type = SID_NAME_UNKNOWN; + item->name = r->in.names[i].string; + /* + * Note: that item->name can be NULL! + * + * See test_LookupNames_NULL() in + * source4/torture/rpc/lsa.c + * + * nt4 returns NT_STATUS_NONE_MAPPED with sid_type + * SID_NAME_UNKNOWN, rid 0, and sid_index -1; + * + * w2k3/w2k8 return NT_STATUS_OK with sid_type + * SID_NAME_DOMAIN, rid -1 and sid_index 0 and BUILTIN domain + */ + if (item->name == NULL) { + continue; + } + + item->hints.principal = item->name; + p = strchr(item->name, '\\'); + if (p != NULL && p != item->name) { + item->hints.domain = talloc_strndup(state->items, + item->name, + p - item->name); + if (item->hints.domain == NULL) { + return NT_STATUS_NO_MEMORY; + } + item->hints.namespace = item->hints.domain; + p++; + if (p[0] == '\0') { + /* + * This is just 'BUILTIN\'. + */ + item->hints.principal = NULL; + } else { + item->hints.principal = p; + } + } + if (item->hints.domain == NULL) { + p = strchr(item->name, '@'); + if (p != NULL && p != item->name && p[1] != '\0') { + item->hints.namespace = p + 1; + } + } + } + + for (v=0; v < state->view_table->count; v++) { + const struct dcesrv_lsa_Lookup_view *view = + state->view_table->array[v]; + + for (i=0; i < r->in.num_names; i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + NTSTATUS status; + + if (item->done) { + continue; + } + + status = view->lookup_name(state, item); + if (NT_STATUS_IS_OK(status)) { + item->done = true; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + status = NT_STATUS_OK; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_SOME_NOT_MAPPED)) { + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + + if (state->wb.irpc_handle == NULL) { + return dcesrv_lsa_LookupNames_base_finish(state); + } + + state->wb.names = talloc_zero_array(state, struct lsa_String, + r->in.num_names); + if (state->wb.names == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<r->in.num_names;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + + if (item->done) { + continue; + } + + item->wb_idx = state->wb.num_names; + state->wb.names[item->wb_idx] = r->in.names[i]; + state->wb.num_names++; + } + + subreq = dcerpc_lsa_LookupNames4_send(state, + state->dce_call->event_ctx, + state->wb.irpc_handle, + state->wb.num_names, + state->wb.names, + &state->wb.domains, + &state->wb.sids, + state->r.in.level, + &state->wb.count, + state->r.in.lookup_options, + state->r.in.client_revision); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_lsa_LookupNames_base_done, + state); + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_lsa_LookupNames_base_finish( + struct dcesrv_lsa_LookupNames_base_state *state) +{ + struct lsa_LookupNames4 *r = &state->r; + uint32_t i; + + for (i=0;i<r->in.num_names;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + NTSTATUS status; + uint32_t sid_index = UINT32_MAX; + + status = dcesrv_lsa_authority_list(item->authority_name, + item->authority_sid, + *r->out.domains, + &sid_index); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + r->out.sids->sids[i].sid_type = item->type; + r->out.sids->sids[i].sid = discard_const_p(struct dom_sid, + item->sid); + r->out.sids->sids[i].sid_index = sid_index; + r->out.sids->sids[i].flags = item->flags; + + r->out.sids->count++; + if (item->type != SID_NAME_UNKNOWN) { + (*r->out.count)++; + } + } + + if (*r->out.count == 0) { + return NT_STATUS_NONE_MAPPED; + } + if (*r->out.count != r->in.num_names) { + return STATUS_SOME_UNMAPPED; + } + + return NT_STATUS_OK; +} + +static void dcesrv_lsa_LookupNames_base_map( + struct dcesrv_lsa_LookupNames_base_state *state) +{ + if (state->_r.l4 != NULL) { + struct lsa_LookupNames4 *r = state->_r.l4; + + r->out.result = state->r.out.result; + return; + } + + if (state->_r.l3 != NULL) { + struct lsa_LookupNames3 *r = state->_r.l3; + + r->out.result = state->r.out.result; + return; + } + + if (state->_r.l2 != NULL) { + struct lsa_LookupNames2 *r = state->_r.l2; + uint32_t i; + + r->out.result = state->r.out.result; + + SMB_ASSERT(state->r.out.sids->count <= r->in.num_names); + for (i = 0; i < state->r.out.sids->count; i++) { + const struct lsa_TranslatedSid3 *s3 = + &state->r.out.sids->sids[i]; + struct lsa_TranslatedSid2 *s2 = + &r->out.sids->sids[i]; + + s2->sid_type = s3->sid_type; + if (s3->sid_type == SID_NAME_DOMAIN) { + s2->rid = UINT32_MAX; + } else if (s3->flags & 0x00000004) { + s2->rid = UINT32_MAX; + } else if (s3->sid == NULL) { + /* + * MS-LSAT 3.1.4.7 - rid zero is considered + * equivalent to sid NULL - so we should return + * 0 rid for unmapped entries + */ + s2->rid = 0; + } else { + s2->rid = 0; + dom_sid_split_rid(NULL, s3->sid, + NULL, &s2->rid); + } + s2->sid_index = s3->sid_index; + s2->unknown = s3->flags; + } + r->out.sids->count = state->r.out.sids->count; + return; + } + + if (state->_r.l != NULL) { + struct lsa_LookupNames *r = state->_r.l; + uint32_t i; + + r->out.result = state->r.out.result; + + SMB_ASSERT(state->r.out.sids->count <= r->in.num_names); + for (i = 0; i < state->r.out.sids->count; i++) { + struct lsa_TranslatedSid3 *s3 = + &state->r.out.sids->sids[i]; + struct lsa_TranslatedSid *s = + &r->out.sids->sids[i]; + + s->sid_type = s3->sid_type; + if (s3->sid_type == SID_NAME_DOMAIN) { + s->rid = UINT32_MAX; + } else if (s3->flags & 0x00000004) { + s->rid = UINT32_MAX; + } else if (s3->sid == NULL) { + /* + * MS-LSAT 3.1.4.7 - rid zero is considered + * equivalent to sid NULL - so we should return + * 0 rid for unmapped entries + */ + s->rid = 0; + } else { + s->rid = 0; + dom_sid_split_rid(NULL, s3->sid, + NULL, &s->rid); + } + s->sid_index = s3->sid_index; + } + r->out.sids->count = state->r.out.sids->count; + return; + } +} + +static void dcesrv_lsa_LookupNames_base_done(struct tevent_req *subreq) +{ + struct dcesrv_lsa_LookupNames_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_lsa_LookupNames_base_state); + struct dcesrv_call_state *dce_call = state->dce_call; + NTSTATUS status; + uint32_t i; + + status = dcerpc_lsa_LookupNames4_recv(subreq, state->mem_ctx, + &state->wb.result); + TALLOC_FREE(subreq); + TALLOC_FREE(state->wb.irpc_handle); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + goto finished; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + goto finished; + } + + status = state->wb.result; + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + status = NT_STATUS_OK; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_SOME_NOT_MAPPED)) { + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + goto finished; + } + + for (i=0; i < state->r.in.num_names;i++) { + struct dcesrv_lsa_TranslatedItem *item = &state->items[i]; + struct lsa_TranslatedSid3 *s3 = NULL; + struct lsa_DomainInfo *d = NULL; + + if (item->done) { + continue; + } + + if (item->wb_idx >= state->wb.sids.count) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + s3 = &state->wb.sids.sids[item->wb_idx]; + + item->type = s3->sid_type; + item->sid = s3->sid; + item->flags = s3->flags; + + if (s3->sid_index == UINT32_MAX) { + continue; + } + + if (state->wb.domains == NULL) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + if (s3->sid_index >= state->wb.domains->count) { + status = NT_STATUS_INTERNAL_ERROR; + goto finished; + } + + d = &state->wb.domains->domains[s3->sid_index]; + + item->authority_name = d->name.string; + item->authority_sid = d->sid; + } + + status = dcesrv_lsa_LookupNames_base_finish(state); + finished: + state->r.out.result = status; + dcesrv_lsa_LookupNames_base_map(state); + + status = dcesrv_reply(dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + lsa_LookupNames3 +*/ +NTSTATUS dcesrv_lsa_LookupNames3(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupNames3 *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_lsa_LookupNames_base_state *state = NULL; + struct dcesrv_handle *policy_handle = NULL; + NTSTATUS status; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + *r->out.domains = NULL; + r->out.sids->count = 0; + r->out.sids->sids = NULL; + *r->out.count = 0; + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupNames_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->policy_state = policy_handle->data; + + state->r.in.num_names = r->in.num_names; + state->r.in.names = r->in.names; + state->r.in.level = r->in.level; + state->r.in.lookup_options = r->in.lookup_options; + state->r.in.client_revision = r->in.client_revision; + state->r.in.sids = r->in.sids; + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.sids = r->out.sids; + state->r.out.count = r->out.count; + + state->_r.l3 = r; + + status = dcesrv_lsa_LookupNames_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupNames_base_map(state); + return status; +} + +/* + lsa_LookupNames4 + + Identical to LookupNames3, but doesn't take a policy handle + +*/ +NTSTATUS dcesrv_lsa_LookupNames4(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LookupNames4 *r) +{ + struct dcesrv_lsa_LookupNames_base_state *state = NULL; + NTSTATUS status; + + *r->out.domains = NULL; + r->out.sids->count = 0; + r->out.sids->sids = NULL; + *r->out.count = 0; + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupNames_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + /* + * We don't have a policy handle on this call, so we want to + * make a policy state and cache it for the life of the + * connection, to avoid re-opening the DB each call. + * + * This also enforces that this is only available over + * ncacn_ip_tcp and with SCHANNEL. + * + * schannel_call_setup may also set the fault state. + * + * state->policy_state is shared between all calls on this + * connection and is moved with talloc_steal() under the + * connection, not dce_call or state. + */ + status = schannel_call_setup(dce_call, &state->policy_state); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + state->r.in.num_names = r->in.num_names; + state->r.in.names = r->in.names; + state->r.in.level = r->in.level; + state->r.in.lookup_options = r->in.lookup_options; + state->r.in.client_revision = r->in.client_revision; + state->r.in.sids = r->in.sids; + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.sids = r->out.sids; + state->r.out.count = r->out.count; + + state->_r.l4 = r; + + status = dcesrv_lsa_LookupNames_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupNames_base_map(state); + return status; +} + +/* + lsa_LookupNames2 +*/ +NTSTATUS dcesrv_lsa_LookupNames2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct lsa_LookupNames2 *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_lsa_LookupNames_base_state *state = NULL; + struct dcesrv_handle *policy_handle = NULL; + NTSTATUS status; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + *r->out.domains = NULL; + r->out.sids->count = 0; + r->out.sids->sids = NULL; + *r->out.count = 0; + + r->out.sids->sids = talloc_zero_array(r->out.sids, + struct lsa_TranslatedSid2, + r->in.num_names); + if (r->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupNames_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->policy_state = policy_handle->data; + + state->r.in.num_names = r->in.num_names; + state->r.in.names = r->in.names; + state->r.in.level = r->in.level; + /* + * MS-LSAT 3.1.4.7: + * + * The LookupOptions and ClientRevision parameters MUST be ignored. + * Message processing MUST happen as if LookupOptions is set to + * 0x00000000 and ClientRevision is set to 0x00000002. + */ + state->r.in.lookup_options = LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES; + state->r.in.client_revision = LSA_CLIENT_REVISION_2; + state->r.in.sids = talloc_zero(state, struct lsa_TransSidArray3); + if (state->r.in.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.sids = talloc_zero(state, struct lsa_TransSidArray3); + if (state->r.out.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.out.count = r->out.count; + + state->_r.l2 = r; + + status = dcesrv_lsa_LookupNames_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupNames_base_map(state); + return status; +} + +/* + lsa_LookupNames +*/ +NTSTATUS dcesrv_lsa_LookupNames(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct lsa_LookupNames *r) +{ + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + struct dcesrv_lsa_LookupNames_base_state *state = NULL; + struct dcesrv_handle *policy_handle = NULL; + NTSTATUS status; + + if (transport != NCACN_NP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + DCESRV_PULL_HANDLE(policy_handle, r->in.handle, LSA_HANDLE_POLICY); + + *r->out.domains = NULL; + r->out.sids->count = 0; + r->out.sids->sids = NULL; + *r->out.count = 0; + + r->out.sids->sids = talloc_zero_array(r->out.sids, + struct lsa_TranslatedSid, + r->in.num_names); + if (r->out.sids->sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state = talloc_zero(mem_ctx, struct dcesrv_lsa_LookupNames_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->policy_state = policy_handle->data; + + state->r.in.num_names = r->in.num_names; + state->r.in.names = r->in.names; + state->r.in.level = r->in.level; + state->r.in.lookup_options = LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES; + state->r.in.client_revision = LSA_CLIENT_REVISION_1; + state->r.in.sids = talloc_zero(state, struct lsa_TransSidArray3); + if (state->r.in.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.in.count = r->in.count; + state->r.out.domains = r->out.domains; + state->r.out.sids = talloc_zero(state, struct lsa_TransSidArray3); + if (state->r.out.sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->r.out.count = r->out.count; + + state->_r.l = r; + + status = dcesrv_lsa_LookupNames_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return status; + } + + state->r.out.result = status; + dcesrv_lsa_LookupNames_base_map(state); + return status; +} + +static NTSTATUS dcesrv_lsa_lookup_name_predefined( + struct dcesrv_lsa_LookupNames_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + NTSTATUS status; + + status = dom_sid_lookup_predefined_name(item->name, + &item->sid, + &item->type, + &item->authority_sid, + &item->authority_name); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return status; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_lsa_lookup_sid_predefined( + struct dcesrv_lsa_LookupSids_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + NTSTATUS status; + + status = dom_sid_lookup_predefined_sid(item->sid, + &item->name, + &item->type, + &item->authority_sid, + &item->authority_name); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return status; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +static const struct dcesrv_lsa_Lookup_view view_predefined = { + .name = "Predefined", + .lookup_sid = dcesrv_lsa_lookup_sid_predefined, + .lookup_name = dcesrv_lsa_lookup_name_predefined, +}; + +static NTSTATUS dcesrv_lsa_lookup_name_builtin( + struct dcesrv_lsa_LookupNames_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + struct lsa_policy_state *policy_state = state->policy_state; + NTSTATUS status; + bool is_builtin = false; + + if (item->name == NULL) { + /* + * This should not be mapped. + */ + return NT_STATUS_OK; + } + + /* + * The predefined view already handled the BUILTIN domain. + * + * Now we just need to find the principal. + * + * We only allow 'BUILTIN\something' and + * not 'something@BUILTIN. + * + * And we try out best for just 'something'. + */ + is_builtin = strequal(item->hints.domain, NAME_BUILTIN); + if (!is_builtin && item->hints.domain != NULL) { + return NT_STATUS_NONE_MAPPED; + } + + status = dcesrv_lsa_lookup_name(state->policy_state, + state->mem_ctx, + NAME_BUILTIN, + policy_state->builtin_sid, + policy_state->builtin_dn, + item->hints.principal, + &item->sid, + &item->type); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + if (!is_builtin) { + return NT_STATUS_NONE_MAPPED; + } + /* + * We know we're authoritative + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + item->authority_name = NAME_BUILTIN; + item->authority_sid = policy_state->builtin_sid; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_lsa_lookup_sid_builtin( + struct dcesrv_lsa_LookupSids_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + struct lsa_policy_state *policy_state = state->policy_state; + NTSTATUS status; + bool is_builtin = false; + + /* + * The predefined view already handled the BUILTIN domain. + * + * Now we just need to find the principal. + */ + is_builtin = dom_sid_in_domain(policy_state->builtin_sid, item->sid); + if (!is_builtin) { + return NT_STATUS_NONE_MAPPED; + } + + status = dcesrv_lsa_lookup_sid(state->policy_state, + state->mem_ctx, + NAME_BUILTIN, + policy_state->builtin_sid, + policy_state->builtin_dn, + item->sid, + &item->name, + &item->type); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + /* + * We know we're authoritative + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + item->authority_name = NAME_BUILTIN; + item->authority_sid = policy_state->builtin_sid; + return NT_STATUS_OK; +} + +static const struct dcesrv_lsa_Lookup_view view_builtin = { + .name = "Builtin", + .lookup_sid = dcesrv_lsa_lookup_sid_builtin, + .lookup_name = dcesrv_lsa_lookup_name_builtin, +}; + +static NTSTATUS dcesrv_lsa_lookup_name_account( + struct dcesrv_lsa_LookupNames_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + struct lsa_policy_state *policy_state = state->policy_state; + struct loadparm_context *lp_ctx = state->dce_call->conn->dce_ctx->lp_ctx; + struct lsa_LookupNames4 *r = &state->r; + NTSTATUS status; + int role; + bool (*is_local_match_fn)(struct loadparm_context *, const char *) = NULL; + bool is_domain = false; + bool try_lookup = false; + const char *check_domain_name = NULL; + + role = lpcfg_server_role(lp_ctx); + if (role == ROLE_ACTIVE_DIRECTORY_DC) { + is_local_match_fn = lpcfg_is_my_domain_or_realm; + } else { + is_local_match_fn = lpcfg_is_myname; + } + + if (item->name == NULL) { + /* + * This should not be mapped. + */ + return NT_STATUS_OK; + } + + if (item->hints.domain != NULL && item->hints.principal == NULL) { + /* + * This is 'DOMAIN\'. + */ + check_domain_name = item->hints.domain; + } else { + /* + * This is just 'DOMAIN'. + */ + check_domain_name = item->name; + } + is_domain = is_local_match_fn(lp_ctx, check_domain_name); + if (is_domain) { + item->type = SID_NAME_DOMAIN; + item->sid = policy_state->domain_sid; + item->authority_name = policy_state->domain_name; + item->authority_sid = policy_state->domain_sid; + return NT_STATUS_OK; + } + + if (r->in.lookup_options & LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES_LOCAL) { + if (item->hints.domain != item->hints.namespace) { + /* + * This means the client asked for an UPN, + * and it should not be mapped. + */ + return NT_STATUS_OK; + } + } + + if (item->hints.namespace != NULL) { + is_domain = is_local_match_fn(lp_ctx, item->hints.namespace); + try_lookup = is_domain; + } else { + try_lookup = true; + } + + if (!try_lookup) { + struct dcesrv_lsa_TranslatedItem tmp; + + tmp = *item; + status = dom_sid_lookup_predefined_name(item->hints.namespace, + &tmp.sid, + &tmp.type, + &tmp.authority_sid, + &tmp.authority_name); + if (NT_STATUS_IS_OK(status)) { + /* + * It should not be handled by us. + */ + return NT_STATUS_NONE_MAPPED; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + return status; + } + } + + if (!try_lookup) { + const struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + const struct lsa_ForestTrustDomainInfo *di = NULL; + + if (state->routing_table == NULL) { + status = dsdb_trust_routing_table_load(policy_state->sam_ldb, + state, + &state->routing_table); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + tdo = dsdb_trust_domain_by_name(state->routing_table, + item->hints.namespace, + &di); + if (tdo == NULL) { + /* + * The name is not resolvable at all... + */ + return NT_STATUS_OK; + } + + if (!(tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST)) { + /* + * The name is not resolvable here + */ + return NT_STATUS_NONE_MAPPED; + } + + /* + * TODO: handle multiple domains in a forest together with + * LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY + */ + is_domain = true; + try_lookup = true; + } + + if (!try_lookup) { + /* + * It should not be handled by us. + */ + return NT_STATUS_NONE_MAPPED; + } + + /* + * TODO: handle multiple domains in our forest. + */ + + status = dcesrv_lsa_lookup_name(state->policy_state, + state->mem_ctx, + policy_state->domain_name, + policy_state->domain_sid, + policy_state->domain_dn, + item->hints.principal, + &item->sid, + &item->type); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + if (!is_domain) { + return NT_STATUS_NONE_MAPPED; + } + /* + * We know we're authoritative + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + item->authority_name = policy_state->domain_name; + item->authority_sid = policy_state->domain_sid; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_lsa_lookup_sid_account( + struct dcesrv_lsa_LookupSids_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + struct lsa_policy_state *policy_state = state->policy_state; + NTSTATUS status; + bool is_domain; + + is_domain = dom_sid_equal(policy_state->domain_sid, item->sid); + if (is_domain) { + item->type = SID_NAME_DOMAIN; + item->name = policy_state->domain_name; + item->authority_name = policy_state->domain_name; + item->authority_sid = policy_state->domain_sid; + return NT_STATUS_OK; + } + is_domain = dom_sid_in_domain(policy_state->domain_sid, item->sid); + if (!is_domain) { + return NT_STATUS_NONE_MAPPED; + } + + status = dcesrv_lsa_lookup_sid(state->policy_state, + state->mem_ctx, + policy_state->domain_name, + policy_state->domain_sid, + policy_state->domain_dn, + item->sid, + &item->name, + &item->type); + if (NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + /* + * We know we're authoritative + */ + status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + item->authority_name = policy_state->domain_name; + item->authority_sid = policy_state->domain_sid; + return NT_STATUS_OK; +} + +static const struct dcesrv_lsa_Lookup_view view_account = { + .name = "Account", + .lookup_sid = dcesrv_lsa_lookup_sid_account, + .lookup_name = dcesrv_lsa_lookup_name_account, +}; + +static NTSTATUS dcesrv_lsa_lookup_name_winbind( + struct dcesrv_lsa_LookupNames_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + struct lsa_LookupNames4 *r = &state->r; + const struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + const struct lsa_ForestTrustDomainInfo *di = NULL; + NTSTATUS status; + const char *check_domain_name = NULL; + bool expect_domain = false; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(state->dce_call->conn); + + if (item->name == NULL) { + /* + * This should not be mapped. + */ + return NT_STATUS_OK; + } + + if (item->hints.domain != NULL && item->hints.principal == NULL) { + /* + * This is 'DOMAIN\'. + */ + check_domain_name = item->hints.domain; + expect_domain = true; + } else if (item->hints.namespace != NULL) { + /* + * This is 'DOMAIN\someone' + * or 'someone@DOMAIN' + */ + check_domain_name = item->hints.namespace; + } else { + /* + * This is just 'DOMAIN'. + */ + check_domain_name = item->name; + expect_domain = true; + } + + if (state->routing_table == NULL) { + struct lsa_policy_state *policy_state = state->policy_state; + + status = dsdb_trust_routing_table_load(policy_state->sam_ldb, + state, + &state->routing_table); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + tdo = dsdb_trust_domain_by_name(state->routing_table, + check_domain_name, + &di); + if (tdo == NULL) { + /* + * The name is not resolvable at all... + * + * And for now we don't send unqualified names + * to winbindd, as we don't handle them + * there yet. + * + * TODO: how should that work within + * winbindd? + */ + return NT_STATUS_OK; + } + + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + /* + * The name should have been resolved in the account view. + * + * TODO: handle multiple domains in a forest... + */ + return NT_STATUS_OK; + } + + if (expect_domain) { + const char *name = NULL; + const struct dom_sid *sid = NULL; + + name = talloc_strdup(state->mem_ctx, + di->netbios_domain_name.string); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + sid = dom_sid_dup(state->mem_ctx, + di->domain_sid); + if (sid == NULL) { + return NT_STATUS_NO_MEMORY; + } + item->type = SID_NAME_DOMAIN; + item->sid = sid; + item->authority_name = name; + item->authority_sid = sid; + return NT_STATUS_OK; + } + + if (r->in.lookup_options & LSA_LOOKUP_OPTION_SEARCH_ISOLATED_NAMES_LOCAL) { + if (item->hints.namespace == NULL) { + /* + * We should not try to resolve isolated names + * remotely. + */ + return NT_STATUS_OK; + } + } + + /* + * We know at least the domain part of the name exists. + * + * For now the rest handled within winbindd. + * + * In future we can optimize it based on + * r->in.level. + * + * We can also try to resolve SID_NAME_DOMAIN + * just based on the routing table. + */ + + if (state->wb.irpc_handle != NULL) { + /* + * already called... + */ + return NT_STATUS_NONE_MAPPED; + } + + state->wb.irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_lsarpc); + if (state->wb.irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(state->wb.irpc_handle, 60); + + return NT_STATUS_NONE_MAPPED; +} + +static NTSTATUS dcesrv_lsa_lookup_sid_winbind( + struct dcesrv_lsa_LookupSids_base_state *state, + struct dcesrv_lsa_TranslatedItem *item) +{ + const struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + const struct lsa_ForestTrustDomainInfo *di = NULL; + struct dcesrv_lsa_TranslatedItem tmp; + struct dom_sid domain_sid = {0,}; + NTSTATUS status; + bool match; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(state->dce_call->conn); + + /* + * Verify the sid is not INVALID. + */ + tmp = *item; + status = dom_sid_lookup_predefined_sid(tmp.sid, + &tmp.name, + &tmp.type, + &tmp.authority_sid, + &tmp.authority_name); + if (NT_STATUS_IS_OK(status)) { + status = NT_STATUS_NONE_MAPPED; + } + if (!NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED)) { + /* + * Typically INVALID_SID + */ + return status; + } + + if (state->routing_table == NULL) { + struct lsa_policy_state *policy_state = state->policy_state; + + status = dsdb_trust_routing_table_load(policy_state->sam_ldb, + state, + &state->routing_table); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + domain_sid = *item->sid; + if (domain_sid.num_auths == 5) { + sid_split_rid(&domain_sid, NULL); + } + + tdo = dsdb_trust_domain_by_sid(state->routing_table, + &domain_sid, &di); + if (tdo == NULL) { + /* + * The sid is not resolvable at all... + */ + return NT_STATUS_OK; + } + + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + /* + * The name should have been resolved in the account view. + * + * TODO: handle multiple domains in a forest... + */ + return NT_STATUS_OK; + } + + match = dom_sid_equal(di->domain_sid, item->sid); + if (match) { + const char *name = NULL; + + name = talloc_strdup(state->mem_ctx, + di->netbios_domain_name.string); + if (name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + item->type = SID_NAME_DOMAIN; + item->name = name; + item->authority_name = name; + item->authority_sid = item->sid; + return NT_STATUS_OK; + } + + /* + * We know at least the domain part of the sid exists. + * + * For now the rest handled within winbindd. + * + * In future we can optimize it based on + * r->in.level. + * + * We can also try to resolve SID_NAME_DOMAIN + * just based on the routing table. + */ + if (state->wb.irpc_handle != NULL) { + /* + * already called... + */ + return NT_STATUS_NONE_MAPPED; + } + + state->wb.irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_lsarpc); + if (state->wb.irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(state->wb.irpc_handle, 60); + + return NT_STATUS_NONE_MAPPED; +} + +static const struct dcesrv_lsa_Lookup_view view_winbind = { + .name = "Winbind", + .lookup_sid = dcesrv_lsa_lookup_sid_winbind, + .lookup_name = dcesrv_lsa_lookup_name_winbind, +}; + +static const struct dcesrv_lsa_Lookup_view *table_all_views[] = { + &view_predefined, + &view_builtin, + &view_account, + &view_winbind, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_all = { + .name = "LSA_LOOKUP_NAMES_ALL", + .count = ARRAY_SIZE(table_all_views), + .array = table_all_views, +}; + +static const struct dcesrv_lsa_Lookup_view *table_domains_views[] = { + &view_account, + &view_winbind, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_domains = { + .name = "LSA_LOOKUP_NAMES_DOMAINS_ONLY", + .count = ARRAY_SIZE(table_domains_views), + .array = table_domains_views, +}; + +static const struct dcesrv_lsa_Lookup_view *table_primary_views[] = { + &view_account, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_primary = { + .name = "LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY", + .count = ARRAY_SIZE(table_primary_views), + .array = table_primary_views, +}; + +static const struct dcesrv_lsa_Lookup_view *table_remote_views[] = { + &view_winbind, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_gc = { + .name = "LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY", + .count = ARRAY_SIZE(table_domains_views), + .array = table_domains_views, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_xreferral = { + .name = "LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY", + .count = ARRAY_SIZE(table_remote_views), + .array = table_remote_views, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_xresolve = { + .name = "LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2", + .count = ARRAY_SIZE(table_domains_views), + .array = table_domains_views, +}; + +static const struct dcesrv_lsa_Lookup_view_table table_rodc = { + .name = "LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC", + .count = ARRAY_SIZE(table_remote_views), + .array = table_remote_views, +}; + +static const struct dcesrv_lsa_Lookup_view_table *dcesrv_lsa_view_table( + enum lsa_LookupNamesLevel level) +{ + switch (level) { + case LSA_LOOKUP_NAMES_ALL: + return &table_all; + case LSA_LOOKUP_NAMES_DOMAINS_ONLY: + return &table_domains; + case LSA_LOOKUP_NAMES_PRIMARY_DOMAIN_ONLY: + return &table_primary; + case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY: + return &table_gc; + case LSA_LOOKUP_NAMES_FOREST_TRUSTS_ONLY: + return &table_xreferral; + case LSA_LOOKUP_NAMES_UPLEVEL_TRUSTS_ONLY2: + return &table_xresolve; + case LSA_LOOKUP_NAMES_RODC_REFERRAL_TO_FULL_DC: + return &table_rodc; + } + + return NULL; +} diff --git a/source4/rpc_server/netlogon/dcerpc_netlogon.c b/source4/rpc_server/netlogon/dcerpc_netlogon.c new file mode 100644 index 0000000..3f312f1 --- /dev/null +++ b/source4/rpc_server/netlogon/dcerpc_netlogon.c @@ -0,0 +1,4631 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the netlogon pipe + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2008 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2005 + Copyright (C) Matthias Dieter Wallnöfer 2009-2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "auth/auth.h" +#include "auth/auth_sam_reply.h" +#include "dsdb/samdb/samdb.h" +#include "../lib/util/util_ldb.h" +#include "../libcli/auth/schannel.h" +#include "libcli/security/security.h" +#include "param/param.h" +#include "lib/messaging/irpc.h" +#include "librpc/gen_ndr/ndr_irpc_c.h" +#include "../libcli/ldap/ldap_ndr.h" +#include "dsdb/samdb/ldb_modules/util.h" +#include "lib/tsocket/tsocket.h" +#include "librpc/gen_ndr/ndr_netlogon.h" +#include "librpc/gen_ndr/ndr_lsa.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "librpc/gen_ndr/ndr_irpc.h" +#include "librpc/gen_ndr/ndr_winbind.h" +#include "librpc/gen_ndr/ndr_winbind_c.h" +#include "librpc/rpc/server/netlogon/schannel_util.h" +#include "lib/socket/netif.h" +#include "lib/util/util_str_escape.h" +#include "lib/param/loadparm.h" + +#define DCESRV_INTERFACE_NETLOGON_BIND(context, iface) \ + dcesrv_interface_netlogon_bind(context, iface) + +#undef strcasecmp + +/* + * This #define allows the netlogon interface to accept invalid + * association groups, because association groups are to coordinate + * handles, and handles are not used in NETLOGON. This in turn avoids + * the need to coordinate these across multiple possible NETLOGON + * processes + */ +#define DCESRV_INTERFACE_NETLOGON_FLAGS DCESRV_INTERFACE_FLAGS_HANDLES_NOT_USED + +static NTSTATUS dcesrv_interface_netlogon_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + struct loadparm_context *lp_ctx = context->conn->dce_ctx->lp_ctx; + bool global_allow_nt4_crypto = lpcfg_allow_nt4_crypto(lp_ctx); + bool global_reject_md5_client = lpcfg_reject_md5_clients(lp_ctx); + int schannel = lpcfg_server_schannel(lp_ctx); + bool schannel_global_required = (schannel == true); + bool global_require_seal = lpcfg_server_schannel_require_seal(lp_ctx); + static bool warned_global_nt4_once = false; + static bool warned_global_md5_once = false; + static bool warned_global_schannel_once = false; + static bool warned_global_seal_once = false; + + if (global_allow_nt4_crypto && !warned_global_nt4_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023 (and others): " + "Please configure 'allow nt4 crypto = no' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_nt4_once = true; + } + + if (!global_reject_md5_client && !warned_global_md5_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023: " + "Please configure 'reject md5 clients = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_md5_once = true; + } + + if (!schannel_global_required && !warned_global_schannel_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2020-1472(ZeroLogon): " + "Please configure 'server schannel = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=14497\n"); + warned_global_schannel_once = true; + } + + if (!global_require_seal && !warned_global_seal_once) { + /* + * We want admins to notice their misconfiguration! + */ + D_ERR("CVE-2022-38023 (and others): " + "Please configure 'server schannel require seal = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_seal_once = true; + } + + return dcesrv_interface_bind_reject_connect(context, iface); +} + +static NTSTATUS dcesrv_netr_ServerReqChallenge(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerReqChallenge *r) +{ + struct netlogon_server_pipe_state *pipe_state = NULL; + NTSTATUS ntstatus; + + ZERO_STRUCTP(r->out.return_credentials); + + pipe_state = dcesrv_iface_state_find_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + TALLOC_FREE(pipe_state); + + pipe_state = talloc_zero(dce_call, + struct netlogon_server_pipe_state); + if (pipe_state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pipe_state->client_challenge = *r->in.credentials; + + netlogon_creds_random_challenge(&pipe_state->server_challenge); + + *r->out.return_credentials = pipe_state->server_challenge; + + ntstatus = dcesrv_iface_state_store_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + pipe_state); + if (!NT_STATUS_IS_OK(ntstatus)) { + return ntstatus; + } + + ntstatus = schannel_save_challenge(dce_call->conn->dce_ctx->lp_ctx, + &pipe_state->client_challenge, + &pipe_state->server_challenge, + r->in.computer_name); + if (!NT_STATUS_IS_OK(ntstatus)) { + TALLOC_FREE(pipe_state); + return ntstatus; + } + + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_netr_ServerAuthenticate3_check_downgrade( + struct dcesrv_call_state *dce_call, + struct netr_ServerAuthenticate3 *r, + struct netlogon_server_pipe_state *pipe_state, + uint32_t negotiate_flags, + const char *trust_account_in_db, + NTSTATUS orig_status) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + bool global_allow_nt4_crypto = lpcfg_allow_nt4_crypto(lp_ctx); + bool account_allow_nt4_crypto = global_allow_nt4_crypto; + const char *explicit_nt4_opt = NULL; + bool global_reject_md5_client = lpcfg_reject_md5_clients(lp_ctx); + bool account_reject_md5_client = global_reject_md5_client; + const char *explicit_md5_opt = NULL; + bool reject_des_client; + bool allow_nt4_crypto; + bool reject_md5_client; + bool need_des = true; + bool need_md5 = true; + int CVE_2022_38023_warn_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "warn_about_unused_debug_level", DBGLVL_ERR); + int CVE_2022_38023_error_level = lpcfg_parm_int(lp_ctx, NULL, + "CVE_2022_38023", "error_debug_level", DBGLVL_ERR); + + /* + * We don't use lpcfg_parm_bool(), as we + * need the explicit_opt pointer in order to + * adjust the debug messages. + */ + + if (trust_account_in_db != NULL) { + explicit_nt4_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "allow nt4 crypto", + trust_account_in_db); + } + if (explicit_nt4_opt != NULL) { + account_allow_nt4_crypto = lp_bool(explicit_nt4_opt); + } + allow_nt4_crypto = account_allow_nt4_crypto; + if (trust_account_in_db != NULL) { + explicit_md5_opt = lpcfg_get_parametric(lp_ctx, + NULL, + "server reject md5 schannel", + trust_account_in_db); + } + if (explicit_md5_opt != NULL) { + account_reject_md5_client = lp_bool(explicit_md5_opt); + } + reject_md5_client = account_reject_md5_client; + + reject_des_client = !allow_nt4_crypto; + + /* + * If weak cryto is disabled, do not announce that we support RC4. + */ + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + /* Without RC4 and DES we require AES */ + reject_des_client = true; + reject_md5_client = true; + } + + if (negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { + need_des = false; + reject_des_client = false; + } + + if (negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + need_des = false; + need_md5 = false; + reject_des_client = false; + reject_md5_client = false; + } + + if (reject_des_client || reject_md5_client) { + TALLOC_CTX *frame = talloc_stackframe(); + + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + if (CVE_2022_38023_error_level < DBGLVL_NOTICE) { + CVE_2022_38023_error_level = DBGLVL_NOTICE; + } + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: " + "client_account[%s] computer_name[%s] " + "schannel_type[%u] " + "client_negotiate_flags[0x%x] " + "%s%s%s " + "NT_STATUS_DOWNGRADE_DETECTED " + "WEAK_CRYPTO_DISALLOWED\n", + log_escape(frame, r->in.account_name), + log_escape(frame, r->in.computer_name), + r->in.secure_channel_type, + (unsigned)*r->in.negotiate_flags, + trust_account_in_db ? "real_account[" : "", + trust_account_in_db ? trust_account_in_db : "", + trust_account_in_db ? "]" : "")); + goto return_downgrade; + } + + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: " + "client_account[%s] computer_name[%s] " + "schannel_type[%u] " + "client_negotiate_flags[0x%x] " + "%s%s%s " + "NT_STATUS_DOWNGRADE_DETECTED " + "reject_des[%u] reject_md5[%u]\n", + log_escape(frame, r->in.account_name), + log_escape(frame, r->in.computer_name), + r->in.secure_channel_type, + (unsigned)*r->in.negotiate_flags, + trust_account_in_db ? "real_account[" : "", + trust_account_in_db ? trust_account_in_db : "", + trust_account_in_db ? "]" : "", + reject_des_client, + reject_md5_client)); + if (trust_account_in_db == NULL) { + goto return_downgrade; + } + + if (reject_md5_client && explicit_md5_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } + if (reject_des_client && explicit_nt4_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } + +return_downgrade: + /* + * Here we match Windows 2012 and return no flags. + */ + *r->out.negotiate_flags = 0; + TALLOC_FREE(frame); + return NT_STATUS_DOWNGRADE_DETECTED; + } + + /* + * This talloc_free is important to prevent re-use of the + * challenge. We have to delay it this far due to NETApp + * servers per: + * https://bugzilla.samba.org/show_bug.cgi?id=11291 + */ + TALLOC_FREE(pipe_state); + + /* + * At this point we must also cleanup the TDB cache + * entry, if we fail the client needs to call + * netr_ServerReqChallenge again. + * + * Note: this handles a non existing record just fine, + * the r->in.computer_name might not be the one used + * in netr_ServerReqChallenge(), but we are trying to + * just tidy up the normal case to prevent re-use. + */ + schannel_delete_challenge(dce_call->conn->dce_ctx->lp_ctx, + r->in.computer_name); + + /* + * According to Microsoft (see bugid #6099) + * Windows 7 looks at the negotiate_flags + * returned in this structure *even if the + * call fails with access denied! + */ + *r->out.negotiate_flags = negotiate_flags; + + if (!NT_STATUS_IS_OK(orig_status) || trust_account_in_db == NULL) { + return orig_status; + } + + if (global_reject_md5_client && account_reject_md5_client && explicit_md5_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = yes' not needed!?\n", + trust_account_in_db); + } else if (need_md5 && !account_reject_md5_client && explicit_md5_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "still needed for a legacy client.\n", + trust_account_in_db); + } else if (need_md5 && explicit_md5_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } else if (!account_reject_md5_client && explicit_md5_opt) { + DEBUG(CVE_2022_38023_warn_level, ( + "CVE-2022-38023: Check if option " + "'server reject md5 schannel:%s = no' not needed!?\n", + trust_account_in_db)); + } + + if (!global_allow_nt4_crypto && !account_allow_nt4_crypto && explicit_nt4_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = no' not needed!?\n", + trust_account_in_db); + } else if (need_des && account_allow_nt4_crypto && explicit_nt4_opt) { + D_INFO("CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "still needed for a legacy client.\n", + trust_account_in_db); + } else if (need_des && explicit_nt4_opt == NULL) { + DEBUG(CVE_2022_38023_error_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' " + "might be needed for a legacy client.\n", + trust_account_in_db)); + } else if (account_allow_nt4_crypto && explicit_nt4_opt) { + DEBUG(CVE_2022_38023_warn_level, ( + "CVE-2022-38023: Check if option " + "'allow nt4 crypto:%s = yes' not needed!?\n", + trust_account_in_db)); + } + + return orig_status; +} + +/* + * Do the actual processing of a netr_ServerAuthenticate3 message. + * called from dcesrv_netr_ServerAuthenticate3, which handles the logging. + */ +static NTSTATUS dcesrv_netr_ServerAuthenticate3_helper( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate3 *r, + const char **trust_account_for_search, + const char **trust_account_in_db, + struct dom_sid **sid) +{ + struct netlogon_server_pipe_state *pipe_state = NULL; + bool challenge_valid = false; + struct netlogon_server_pipe_state challenge; + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + struct samr_Password *curNtHash = NULL; + struct samr_Password *prevNtHash = NULL; + uint32_t user_account_control; + int num_records; + struct ldb_message **msgs; + NTSTATUS nt_status; + const char *attrs[] = {"unicodePwd", "userAccountControl", + "objectSid", "samAccountName", NULL}; + uint32_t server_flags = 0; + uint32_t negotiate_flags = 0; + + ZERO_STRUCTP(r->out.return_credentials); + *r->out.negotiate_flags = 0; + *r->out.rid = 0; + + pipe_state = dcesrv_iface_state_find_conn(dce_call, + NETLOGON_SERVER_PIPE_STATE_MAGIC, + struct netlogon_server_pipe_state); + if (pipe_state != NULL) { + /* + * If we had a challenge remembered on the connection + * consider this for usage. This can't be cleanup + * by other clients. + * + * This is the default code path for typical clients + * which call netr_ServerReqChallenge() and + * netr_ServerAuthenticate3() on the same dcerpc connection. + */ + challenge = *pipe_state; + + challenge_valid = true; + + } else { + NTSTATUS ntstatus; + + /* + * Fallback and try to get the challenge from + * the global cache. + * + * If too many clients are using this code path, + * they may destroy their cache entries as the + * TDB has a fixed size limited via a lossy hash + * + * The TDB used is the schannel store, which is + * initialised at startup. + * + * NOTE: The challenge is deleted from the DB as soon as it is + * fetched, to prevent reuse. + * + */ + + ntstatus = schannel_get_challenge(dce_call->conn->dce_ctx->lp_ctx, + &challenge.client_challenge, + &challenge.server_challenge, + r->in.computer_name); + + if (!NT_STATUS_IS_OK(ntstatus)) { + ZERO_STRUCT(challenge); + } else { + challenge_valid = true; + } + } + + server_flags = NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_SEND_PASSWORD_INFO_PDC | + NETLOGON_NEG_GENERIC_PASSTHROUGH | + NETLOGON_NEG_CONCURRENT_RPC | + NETLOGON_NEG_AVOID_ACCOUNT_DB_REPL | + NETLOGON_NEG_AVOID_SECURITYAUTH_DB_REPL | + NETLOGON_NEG_STRONG_KEYS | + NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION | + NETLOGON_NEG_RODC_PASSTHROUGH | + NETLOGON_NEG_SUPPORTS_AES | + NETLOGON_NEG_AUTHENTICATED_RPC_LSASS | + NETLOGON_NEG_AUTHENTICATED_RPC; + + /* + * If weak cryto is disabled, do not announce that we support RC4. + */ + if (lpcfg_weak_crypto(dce_call->conn->dce_ctx->lp_ctx) == + SAMBA_WEAK_CRYPTO_DISALLOWED) { + server_flags &= ~NETLOGON_NEG_ARCFOUR; + } + + negotiate_flags = *r->in.negotiate_flags & server_flags; + + switch (r->in.secure_channel_type) { + case SEC_CHAN_WKSTA: + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + case SEC_CHAN_BDC: + case SEC_CHAN_RODC: + break; + case SEC_CHAN_NULL: + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_PARAMETER); + default: + DEBUG(1, ("Client asked for an invalid secure channel type: %d\n", + r->in.secure_channel_type)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_PARAMETER); + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INVALID_SYSTEM_SERVICE); + } + + if (r->in.secure_channel_type == SEC_CHAN_DOMAIN || + r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) + { + struct ldb_message *tdo_msg = NULL; + const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustAttributes", + "flatName", + NULL + }; + char *encoded_name = NULL; + size_t len; + const char *flatname = NULL; + char trailer = '$'; + bool require_trailer = true; + const char *netbios = NULL; + const char *dns = NULL; + + if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + trailer = '.'; + require_trailer = false; + } + + encoded_name = ldb_binary_encode_string(mem_ctx, + r->in.account_name); + if (encoded_name == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_MEMORY); + } + + len = strlen(encoded_name); + if (len < 2) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + if (require_trailer && encoded_name[len - 1] != trailer) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + encoded_name[len - 1] = '\0'; + + if (r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + dns = encoded_name; + } else { + netbios = encoded_name; + } + + nt_status = dsdb_trust_search_tdo(sam_ctx, + netbios, dns, + tdo_attrs, mem_ctx, &tdo_msg); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + DEBUG(2, ("Client asked for a trusted domain secure channel, " + "but there's no tdo for [%s] => [%s] \n", + log_escape(mem_ctx, r->in.account_name), + encoded_name)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + nt_status); + } + + nt_status = dsdb_trust_get_incoming_passwords(tdo_msg, mem_ctx, + &curNtHash, + &prevNtHash); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_ACCOUNT_DISABLED)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + nt_status); + } + + flatname = ldb_msg_find_attr_as_string(tdo_msg, "flatName", NULL); + if (flatname == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + *trust_account_for_search = talloc_asprintf(mem_ctx, "%s$", flatname); + if (*trust_account_for_search == NULL) { + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_MEMORY); + } + } else { + *trust_account_for_search = r->in.account_name; + } + + /* pull the user attributes */ + num_records = gendb_search(sam_ctx, mem_ctx, NULL, &msgs, attrs, + "(&(sAMAccountName=%s)(objectclass=user))", + ldb_binary_encode_string(mem_ctx, + *trust_account_for_search)); + + if (num_records == 0) { + DEBUG(3,("Couldn't find user [%s] in samdb.\n", + log_escape(mem_ctx, r->in.account_name))); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_NO_TRUST_SAM_ACCOUNT); + } + + if (num_records > 1) { + DEBUG(0,("Found %d records matching user [%s]\n", + num_records, + log_escape(mem_ctx, r->in.account_name))); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INTERNAL_DB_CORRUPTION); + } + + *trust_account_in_db = ldb_msg_find_attr_as_string(msgs[0], + "samAccountName", + NULL); + if (*trust_account_in_db == NULL) { + DEBUG(0,("No samAccountName returned in record matching user [%s]\n", + r->in.account_name)); + return dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + NULL, /* trust_account_in_db */ + NT_STATUS_INTERNAL_DB_CORRUPTION); + } + + nt_status = dcesrv_netr_ServerAuthenticate3_check_downgrade( + dce_call, r, pipe_state, negotiate_flags, + *trust_account_in_db, + NT_STATUS_OK); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + user_account_control = ldb_msg_find_attr_as_uint(msgs[0], "userAccountControl", 0); + + if (user_account_control & UF_ACCOUNTDISABLE) { + DEBUG(1, ("Account [%s] is disabled\n", + log_escape(mem_ctx, r->in.account_name))); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + + if (r->in.secure_channel_type == SEC_CHAN_WKSTA) { + if (!(user_account_control & UF_WORKSTATION_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a workstation secure channel, but is not a workstation (member server) acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_DOMAIN || + r->in.secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + if (!(user_account_control & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a trusted domain secure channel, but is not a trusted domain: acb flags: 0x%x\n", user_account_control)); + + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_BDC) { + if (!(user_account_control & UF_SERVER_TRUST_ACCOUNT)) { + DEBUG(1, ("Client asked for a server secure channel, but is not a server (domain controller): acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else if (r->in.secure_channel_type == SEC_CHAN_RODC) { + if (!(user_account_control & UF_PARTIAL_SECRETS_ACCOUNT)) { + DEBUG(1, ("Client asked for a RODC secure channel, but is not a RODC: acb flags: 0x%x\n", user_account_control)); + return NT_STATUS_NO_TRUST_SAM_ACCOUNT; + } + } else { + /* we should never reach this */ + return NT_STATUS_INTERNAL_ERROR; + } + + if (!(user_account_control & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + nt_status = samdb_result_passwords_no_lockout(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + msgs[0], &curNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_ACCESS_DENIED; + } + } + + if (curNtHash == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + + if (!challenge_valid) { + DEBUG(1, ("No challenge requested by client [%s/%s], " + "cannot authenticate\n", + log_escape(mem_ctx, r->in.computer_name), + log_escape(mem_ctx, r->in.account_name))); + return NT_STATUS_ACCESS_DENIED; + } + + creds = netlogon_creds_server_init(mem_ctx, + r->in.account_name, + r->in.computer_name, + r->in.secure_channel_type, + &challenge.client_challenge, + &challenge.server_challenge, + curNtHash, + r->in.credentials, + r->out.return_credentials, + negotiate_flags); + if (creds == NULL && prevNtHash != NULL) { + /* + * We fallback to the previous password for domain trusts. + * + * Note that lpcfg_old_password_allowed_period() doesn't + * apply here. + */ + creds = netlogon_creds_server_init(mem_ctx, + r->in.account_name, + r->in.computer_name, + r->in.secure_channel_type, + &challenge.client_challenge, + &challenge.server_challenge, + prevNtHash, + r->in.credentials, + r->out.return_credentials, + negotiate_flags); + } + + if (creds == NULL) { + return NT_STATUS_ACCESS_DENIED; + } + creds->sid = samdb_result_dom_sid(creds, msgs[0], "objectSid"); + *sid = talloc_memdup(mem_ctx, creds->sid, sizeof(struct dom_sid)); + + nt_status = schannel_save_creds_state(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + creds); + if (!NT_STATUS_IS_OK(nt_status)) { + ZERO_STRUCTP(r->out.return_credentials); + return nt_status; + } + + *r->out.rid = samdb_result_rid_from_sid(mem_ctx, msgs[0], + "objectSid", 0); + + return NT_STATUS_OK; +} + +/* + * Log a netr_ServerAuthenticate3 request, and then invoke + * dcesrv_netr_ServerAuthenticate3_helper to perform the actual processing + */ +static NTSTATUS dcesrv_netr_ServerAuthenticate3( + struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate3 *r) +{ + NTSTATUS status; + struct dom_sid *sid = NULL; + const char *trust_account_for_search = NULL; + const char *trust_account_in_db = NULL; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + struct auth_usersupplied_info ui = { + .local_host = dce_call->conn->local_address, + .remote_host = dce_call->conn->remote_address, + .client = { + .account_name = r->in.account_name, + .domain_name = lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx), + }, + .service_description = "NETLOGON", + .auth_description = "ServerAuthenticate", + .netlogon_trust_account = { + .computer_name = r->in.computer_name, + .negotiate_flags = *r->in.negotiate_flags, + .secure_channel_type = r->in.secure_channel_type, + }, + }; + + status = dcesrv_netr_ServerAuthenticate3_helper(dce_call, + mem_ctx, + r, + &trust_account_for_search, + &trust_account_in_db, + &sid); + ui.netlogon_trust_account.sid = sid; + ui.netlogon_trust_account.account_name = trust_account_in_db; + ui.mapped.account_name = trust_account_for_search; + log_authentication_event( + imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + NULL, + &ui, + status, + lpcfg_workgroup(dce_call->conn->dce_ctx->lp_ctx), + trust_account_in_db, + sid); + + return status; +} +static NTSTATUS dcesrv_netr_ServerAuthenticate(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate *r) +{ + struct netr_ServerAuthenticate3 a; + uint32_t rid; + /* TODO: + * negotiate_flags is used as an [in] parameter + * so it need to be initialised. + * + * (I think ... = 0; seems wrong here --metze) + */ + uint32_t negotiate_flags_in = 0; + uint32_t negotiate_flags_out = 0; + + a.in.server_name = r->in.server_name; + a.in.account_name = r->in.account_name; + a.in.secure_channel_type = r->in.secure_channel_type; + a.in.computer_name = r->in.computer_name; + a.in.credentials = r->in.credentials; + a.in.negotiate_flags = &negotiate_flags_in; + + a.out.return_credentials = r->out.return_credentials; + a.out.rid = &rid; + a.out.negotiate_flags = &negotiate_flags_out; + + return dcesrv_netr_ServerAuthenticate3(dce_call, mem_ctx, &a); +} + +static NTSTATUS dcesrv_netr_ServerAuthenticate2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerAuthenticate2 *r) +{ + struct netr_ServerAuthenticate3 r3; + uint32_t rid = 0; + + r3.in.server_name = r->in.server_name; + r3.in.account_name = r->in.account_name; + r3.in.secure_channel_type = r->in.secure_channel_type; + r3.in.computer_name = r->in.computer_name; + r3.in.credentials = r->in.credentials; + r3.out.return_credentials = r->out.return_credentials; + r3.in.negotiate_flags = r->in.negotiate_flags; + r3.out.negotiate_flags = r->out.negotiate_flags; + r3.out.rid = &rid; + + return dcesrv_netr_ServerAuthenticate3(dce_call, mem_ctx, &r3); +} + +/* + Change the machine account password for the currently connected + client. Supplies only the NT#. +*/ + +static NTSTATUS dcesrv_netr_ServerPasswordSet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordSet *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + NTSTATUS nt_status; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + nt_status = netlogon_creds_des_decrypt(creds, r->in.new_password); + NT_STATUS_NOT_OK_RETURN(nt_status); + + /* Using the sid for the account as the key, set the password */ + nt_status = samdb_set_password_sid(sam_ctx, mem_ctx, + creds->sid, + NULL, /* Don't have version */ + NULL, /* Don't have plaintext */ + r->in.new_password, + DSDB_PASSWORD_CHECKED_AND_CORRECT, /* Password change */ + NULL, NULL); + return nt_status; +} + +/* + Change the machine account password for the currently connected + client. Supplies new plaintext. +*/ +static NTSTATUS dcesrv_netr_ServerPasswordSet2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordSet2 *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + struct NL_PASSWORD_VERSION version = {}; + const uint32_t *new_version = NULL; + NTSTATUS nt_status; + DATA_BLOB new_password = data_blob_null; + size_t confounder_len; + DATA_BLOB dec_blob = data_blob_null; + DATA_BLOB enc_blob = data_blob_null; + struct samr_CryptPassword password_buf; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + memcpy(password_buf.data, r->in.new_password->data, 512); + SIVAL(password_buf.data, 512, r->in.new_password->length); + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + nt_status = netlogon_creds_aes_decrypt(creds, + password_buf.data, + 516); + } else { + nt_status = netlogon_creds_arcfour_crypt(creds, + password_buf.data, + 516); + } + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + switch (creds->secure_channel_type) { + case SEC_CHAN_DOMAIN: + case SEC_CHAN_DNS_DOMAIN: { + uint32_t len = IVAL(password_buf.data, 512); + if (len <= 500) { + uint32_t ofs = 500 - len; + uint8_t *p; + + p = password_buf.data + ofs; + + version.ReservedField = IVAL(p, 0); + version.PasswordVersionNumber = IVAL(p, 4); + version.PasswordVersionPresent = IVAL(p, 8); + + if (version.PasswordVersionPresent == NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT) { + new_version = &version.PasswordVersionNumber; + } + }} + break; + default: + break; + } + + if (!extract_pw_from_buffer(mem_ctx, password_buf.data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the length field was encrypted, + * otherwise we are under attack. + */ + if (new_password.length == r->in.new_password->length) { + DBG_WARNING("Length[%zu] field not encrypted\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * We don't allow empty passwords for machine accounts. + */ + if (new_password.length < 2) { + DBG_WARNING("Empty password Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Make sure the confounder part of CryptPassword + * buffer was encrypted, otherwise we are under attack. + */ + confounder_len = 512 - new_password.length; + enc_blob = data_blob_const(r->in.new_password->data, confounder_len); + dec_blob = data_blob_const(password_buf.data, confounder_len); + if (confounder_len > 0 && data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Confounder buffer not encrypted Length[%zu]\n", + confounder_len); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * Check that the password part was actually encrypted, + * otherwise we are under attack. + */ + enc_blob = data_blob_const(r->in.new_password->data + confounder_len, + new_password.length); + dec_blob = data_blob_const(password_buf.data + confounder_len, + new_password.length); + if (data_blob_equal_const_time(&dec_blob, &enc_blob)) { + DBG_WARNING("Password buffer not encrypted Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* + * don't allow zero buffers + */ + if (all_zero(new_password.data, new_password.length)) { + DBG_WARNING("Password zero buffer Length[%zu]\n", + new_password.length); + return NT_STATUS_WRONG_PASSWORD; + } + + /* Using the sid for the account as the key, set the password */ + nt_status = samdb_set_password_sid(sam_ctx, mem_ctx, + creds->sid, + new_version, + &new_password, /* we have plaintext */ + NULL, + DSDB_PASSWORD_CHECKED_AND_CORRECT, /* Password change */ + NULL, NULL); + return nt_status; +} + + +/* + netr_LogonUasLogon +*/ +static WERROR dcesrv_netr_LogonUasLogon(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonUasLogon *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonUasLogoff +*/ +static WERROR dcesrv_netr_LogonUasLogoff(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonUasLogoff *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +static NTSTATUS dcesrv_netr_LogonSamLogon_check(struct dcesrv_call_state *dce_call, + const struct netr_LogonSamLogonEx *r) +{ + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + if (r->in.logon->password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + case NetlogonValidationSamInfo4: /* 6 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + if (r->in.logon->network == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo: /* 2 */ + case NetlogonValidationSamInfo2: /* 3 */ + case NetlogonValidationSamInfo4: /* 6 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + + case NetlogonGenericInformation: + if (r->in.logon->generic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (r->in.validation_level) { + /* TODO: case NetlogonValidationGenericInfo: 4 */ + case NetlogonValidationGenericInfo2: /* 5 */ + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + dcesrv_call_auth_info(dce_call, NULL, &auth_level); + + switch (r->in.validation_level) { + case NetlogonValidationSamInfo4: /* 6 */ + if (auth_level < DCERPC_AUTH_LEVEL_PRIVACY) { + return NT_STATUS_INVALID_PARAMETER; + } + break; + + default: + break; + } + + return NT_STATUS_OK; +} + +struct dcesrv_netr_LogonSamLogon_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct netlogon_creds_CredentialState *creds; + + struct netr_LogonSamLogonEx r; + + uint32_t _ignored_flags; + + struct { + struct netr_LogonSamLogon *lsl; + struct netr_LogonSamLogonWithFlags *lslwf; + struct netr_LogonSamLogonEx *lslex; + } _r; + + struct kdc_check_generic_kerberos kr; +}; + +static void dcesrv_netr_LogonSamLogon_base_auth_done(struct tevent_req *subreq); +static void dcesrv_netr_LogonSamLogon_base_krb5_done(struct tevent_req *subreq); +static void dcesrv_netr_LogonSamLogon_base_reply( + struct dcesrv_netr_LogonSamLogon_base_state *state); + +/* + netr_LogonSamLogon_base + + This version of the function allows other wrappers to say 'do not check the credentials' + + We can't do the traditional 'wrapping' format completely, as this + function must only run under schannel +*/ +static NTSTATUS dcesrv_netr_LogonSamLogon_base_call(struct dcesrv_netr_LogonSamLogon_base_state *state) +{ + struct dcesrv_call_state *dce_call = state->dce_call; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct netlogon_creds_CredentialState *creds = state->creds; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const char *workgroup = lpcfg_workgroup(lp_ctx); + struct auth4_context *auth_context = NULL; + struct auth_usersupplied_info *user_info = NULL; + NTSTATUS nt_status; + struct tevent_req *subreq = NULL; + enum dcerpc_AuthType auth_type = DCERPC_AUTH_TYPE_NONE; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + dcesrv_call_auth_info(dce_call, &auth_type, &auth_level); + + switch (dce_call->pkt.u.request.opnum) { + case NDR_NETR_LOGONSAMLOGON: + case NDR_NETR_LOGONSAMLOGONWITHFLAGS: + /* + * These already called dcesrv_netr_check_schannel() + * via dcesrv_netr_creds_server_step_check() + */ + break; + case NDR_NETR_LOGONSAMLOGONEX: + default: + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = dcesrv_netr_check_schannel(dce_call, + creds, + auth_type, + auth_level, + dce_call->pkt.u.request.opnum); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + break; + } + + *r->out.authoritative = 1; + + if (*r->in.flags & NETLOGON_SAMLOGON_FLAG_PASS_TO_FOREST_ROOT) { + /* + * Currently we're always the forest root ourself. + */ + return NT_STATUS_NO_SUCH_USER; + } + + if (*r->in.flags & NETLOGON_SAMLOGON_FLAG_PASS_CROSS_FOREST_HOP) { + /* + * Currently we don't support trusts correctly yet. + */ + return NT_STATUS_NO_SUCH_USER; + } + + user_info = talloc_zero(mem_ctx, struct auth_usersupplied_info); + NT_STATUS_HAVE_NO_MEMORY(user_info); + + user_info->service_description = "SamLogon"; + + nt_status = netlogon_creds_decrypt_samlogon_logon(creds, + r->in.logon_level, + r->in.logon); + NT_STATUS_NOT_OK_RETURN(nt_status); + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + + nt_status = auth_context_create_for_netlogon(mem_ctx, + dce_call->event_ctx, + imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + &auth_context); + NT_STATUS_NOT_OK_RETURN(nt_status); + + user_info->remote_host = dce_call->conn->remote_address; + user_info->local_host = dce_call->conn->local_address; + + user_info->netlogon_trust_account.secure_channel_type + = creds->secure_channel_type; + user_info->netlogon_trust_account.negotiate_flags + = creds->negotiate_flags; + + /* + * These two can be unrelated when the account is + * actually that of a trusted domain, so we want to + * know which DC in that trusted domain contacted + * us + */ + user_info->netlogon_trust_account.computer_name + = creds->computer_name; + user_info->netlogon_trust_account.account_name + = creds->account_name; + user_info->netlogon_trust_account.sid + = creds->sid; + + break; + default: + /* We do not need to set up the user_info in this case */ + break; + } + + switch (r->in.logon_level) { + case NetlogonInteractiveInformation: + case NetlogonServiceInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceTransitiveInformation: + user_info->auth_description = "interactive"; + + user_info->logon_parameters + = r->in.logon->password->identity_info.parameter_control; + user_info->client.account_name + = r->in.logon->password->identity_info.account_name.string; + user_info->client.domain_name + = r->in.logon->password->identity_info.domain_name.string; + user_info->workstation_name + = r->in.logon->password->identity_info.workstation.string; + user_info->flags |= USER_INFO_INTERACTIVE_LOGON; + user_info->password_state = AUTH_PASSWORD_HASH; + + user_info->password.hash.lanman = talloc(user_info, struct samr_Password); + NT_STATUS_HAVE_NO_MEMORY(user_info->password.hash.lanman); + *user_info->password.hash.lanman = r->in.logon->password->lmpassword; + + user_info->password.hash.nt = talloc(user_info, struct samr_Password); + NT_STATUS_HAVE_NO_MEMORY(user_info->password.hash.nt); + *user_info->password.hash.nt = r->in.logon->password->ntpassword; + + user_info->logon_id + = r->in.logon->password->identity_info.logon_id; + + break; + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + user_info->auth_description = "network"; + + nt_status = auth_context_set_challenge( + auth_context, + r->in.logon->network->challenge, + "netr_LogonSamLogonWithFlags"); + NT_STATUS_NOT_OK_RETURN(nt_status); + + user_info->logon_parameters + = r->in.logon->network->identity_info.parameter_control; + user_info->client.account_name + = r->in.logon->network->identity_info.account_name.string; + user_info->client.domain_name + = r->in.logon->network->identity_info.domain_name.string; + user_info->workstation_name + = r->in.logon->network->identity_info.workstation.string; + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = data_blob_talloc(mem_ctx, r->in.logon->network->lm.data, r->in.logon->network->lm.length); + user_info->password.response.nt = data_blob_talloc(mem_ctx, r->in.logon->network->nt.data, r->in.logon->network->nt.length); + + user_info->logon_id + = r->in.logon->network->identity_info.logon_id; + + nt_status = NTLMv2_RESPONSE_verify_netlogon_creds( + user_info->client.account_name, + user_info->client.domain_name, + user_info->password.response.nt, + creds, workgroup); + NT_STATUS_NOT_OK_RETURN(nt_status); + + break; + + + case NetlogonGenericInformation: + { + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + /* OK */ + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + /* OK */ + } else { + /* Using DES to verify kerberos tickets makes no sense */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcmp(r->in.logon->generic->package_name.string, "Kerberos") == 0) { + struct dcerpc_binding_handle *irpc_handle; + struct netr_GenericInfo2 *generic = talloc_zero(mem_ctx, struct netr_GenericInfo2); + NT_STATUS_HAVE_NO_MEMORY(generic); + + r->out.validation->generic = generic; + + user_info->logon_id + = r->in.logon->generic->identity_info.logon_id; + + irpc_handle = irpc_binding_handle_by_name(mem_ctx, + imsg_ctx, + "kdc_server", + &ndr_table_irpc); + if (irpc_handle == NULL) { + return NT_STATUS_NO_LOGON_SERVERS; + } + + state->kr.in.generic_request = + data_blob_const(r->in.logon->generic->data, + r->in.logon->generic->length); + + /* + * 60 seconds should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + subreq = dcerpc_kdc_check_generic_kerberos_r_send(state, + state->dce_call->event_ctx, + irpc_handle, &state->kr); + if (subreq == NULL) { + return NT_STATUS_NO_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonSamLogon_base_krb5_done, + state); + return NT_STATUS_OK; + } + + /* Until we get an implemetnation of these other packages */ + return NT_STATUS_INVALID_PARAMETER; + } + default: + return NT_STATUS_INVALID_PARAMETER; + } + + subreq = auth_check_password_send(state, state->dce_call->event_ctx, + auth_context, user_info); + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonSamLogon_base_auth_done, + state); + return NT_STATUS_OK; +} + +static void dcesrv_netr_LogonSamLogon_base_auth_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonSamLogon_base_state); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct auth_user_info_dc *user_info_dc = NULL; + struct netr_SamInfo2 *sam2 = NULL; + struct netr_SamInfo3 *sam3 = NULL; + struct netr_SamInfo6 *sam6 = NULL; + NTSTATUS nt_status; + + nt_status = auth_check_password_recv(subreq, mem_ctx, + &user_info_dc, + r->out.authoritative); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + switch (r->in.validation_level) { + case 2: + nt_status = auth_convert_user_info_dc_saminfo2(mem_ctx, + user_info_dc, + &sam2); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam2 = sam2; + break; + + case 3: + nt_status = auth_convert_user_info_dc_saminfo3(mem_ctx, + user_info_dc, + &sam3); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam3 = sam3; + break; + + case 6: + nt_status = auth_convert_user_info_dc_saminfo6(mem_ctx, + user_info_dc, + &sam6); + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = nt_status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + r->out.validation->sam6 = sam6; + break; + + default: + if (!NT_STATUS_IS_OK(nt_status)) { + r->out.result = NT_STATUS_INVALID_INFO_CLASS; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + } + + /* TODO: Describe and deal with these flags */ + *r->out.flags = 0; + + r->out.result = NT_STATUS_OK; + + dcesrv_netr_LogonSamLogon_base_reply(state); +} + +static void dcesrv_netr_LogonSamLogon_base_krb5_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonSamLogon_base_state); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_LogonSamLogonEx *r = &state->r; + struct netr_GenericInfo2 *generic = NULL; + NTSTATUS status; + + status = dcerpc_kdc_check_generic_kerberos_r_recv(subreq, mem_ctx); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + r->out.result = status; + dcesrv_netr_LogonSamLogon_base_reply(state); + return; + } + + generic = r->out.validation->generic; + generic->length = state->kr.out.generic_reply.length; + generic->data = state->kr.out.generic_reply.data; + + /* TODO: Describe and deal with these flags */ + *r->out.flags = 0; + + r->out.result = NT_STATUS_OK; + + dcesrv_netr_LogonSamLogon_base_reply(state); +} + +static void dcesrv_netr_LogonSamLogon_base_reply( + struct dcesrv_netr_LogonSamLogon_base_state *state) +{ + struct netr_LogonSamLogonEx *r = &state->r; + NTSTATUS status; + + if (NT_STATUS_IS_OK(r->out.result)) { + status = netlogon_creds_encrypt_samlogon_validation(state->creds, + r->in.validation_level, + r->out.validation); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("netlogon_creds_encrypt_samlogon_validation() " + "failed - %s\n", + nt_errstr(status)); + } + } + + if (state->_r.lslex != NULL) { + struct netr_LogonSamLogonEx *_r = state->_r.lslex; + _r->out.result = r->out.result; + } else if (state->_r.lslwf != NULL) { + struct netr_LogonSamLogonWithFlags *_r = state->_r.lslwf; + _r->out.result = r->out.result; + } else if (state->_r.lsl != NULL) { + struct netr_LogonSamLogon *_r = state->_r.lsl; + _r->out.result = r->out.result; + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR("dcesrv_reply() failed - %s\n", + nt_errstr(status)); + } +} + +static NTSTATUS dcesrv_netr_LogonSamLogonEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogonEx *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = r->in.flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = r->out.flags; + + state->_r.lslex = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = schannel_get_creds_state(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + r->in.computer_name, &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + +/* + netr_LogonSamLogonWithFlags + +*/ +static NTSTATUS dcesrv_netr_LogonSamLogonWithFlags(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogonWithFlags *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = r->in.flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = r->out.flags; + + state->_r.lslwf = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + r->out.return_authenticator = talloc_zero(mem_ctx, + struct netr_Authenticator); + if (r->out.return_authenticator == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + +/* + netr_LogonSamLogon +*/ +static NTSTATUS dcesrv_netr_LogonSamLogon(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogon *r) +{ + struct dcesrv_netr_LogonSamLogon_base_state *state; + NTSTATUS nt_status; + + *r->out.authoritative = 1; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonSamLogon_base_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_name = r->in.server_name; + state->r.in.computer_name = r->in.computer_name; + state->r.in.logon_level = r->in.logon_level; + state->r.in.logon = r->in.logon; + state->r.in.validation_level = r->in.validation_level; + state->r.in.flags = &state->_ignored_flags; + state->r.out.validation = r->out.validation; + state->r.out.authoritative = r->out.authoritative; + state->r.out.flags = &state->_ignored_flags; + + state->_r.lsl = r; + + nt_status = dcesrv_netr_LogonSamLogon_check(dce_call, &state->r); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + r->out.return_authenticator = talloc_zero(mem_ctx, + struct netr_Authenticator); + if (r->out.return_authenticator == NULL) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &state->creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dcesrv_netr_LogonSamLogon_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return nt_status; + } + + return nt_status; +} + + +/* + netr_LogonSamLogoff +*/ +static NTSTATUS dcesrv_netr_LogonSamLogoff(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonSamLogoff *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* + netr_DatabaseDeltas +*/ +static NTSTATUS dcesrv_netr_DatabaseDeltas(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseDeltas *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_DatabaseSync2 +*/ +static NTSTATUS dcesrv_netr_DatabaseSync2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseSync2 *r) +{ + /* win2k3 native mode returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_DatabaseSync +*/ +static NTSTATUS dcesrv_netr_DatabaseSync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseSync *r) +{ + struct netr_DatabaseSync2 r2; + NTSTATUS status; + + ZERO_STRUCT(r2); + + r2.in.logon_server = r->in.logon_server; + r2.in.computername = r->in.computername; + r2.in.credential = r->in.credential; + r2.in.database_id = r->in.database_id; + r2.in.restart_state = SYNCSTATE_NORMAL_STATE; + r2.in.sync_context = r->in.sync_context; + r2.out.sync_context = r->out.sync_context; + r2.out.delta_enum_array = r->out.delta_enum_array; + r2.in.preferredmaximumlength = r->in.preferredmaximumlength; + + status = dcesrv_netr_DatabaseSync2(dce_call, mem_ctx, &r2); + + return status; +} + + +/* + netr_AccountDeltas +*/ +static NTSTATUS dcesrv_netr_AccountDeltas(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_AccountDeltas *r) +{ + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_AccountSync +*/ +static NTSTATUS dcesrv_netr_AccountSync(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_AccountSync *r) +{ + /* w2k3 returns "NOT IMPLEMENTED" for this call */ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + netr_GetDcName +*/ +static WERROR dcesrv_netr_GetDcName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_GetDcName *r) +{ + const char * const attrs[] = { NULL }; + struct ldb_context *sam_ctx; + struct ldb_message **res; + struct ldb_dn *domain_dn; + int ret; + const char *dcname; + + /* + * [MS-NRPC] 3.5.5.3.4 NetrGetDCName says + * that the domainname needs to be a valid netbios domain + * name, if it is not NULL. + */ + if (r->in.domainname) { + const char *dot = strchr(r->in.domainname, '.'); + size_t len = strlen(r->in.domainname); + + if (dot || len > 15) { + return WERR_NERR_DCNOTFOUND; + } + + /* + * TODO: Should we also varify that only valid + * netbios name characters are used? + */ + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + domain_dn = samdb_domain_to_dn(sam_ctx, mem_ctx, + r->in.domainname); + if (domain_dn == NULL) { + return WERR_NO_SUCH_DOMAIN; + } + + ret = gendb_search_dn(sam_ctx, mem_ctx, + domain_dn, &res, attrs); + if (ret != 1) { + return WERR_NO_SUCH_DOMAIN; + } + + /* TODO: - return real IP address + * - check all r->in.* parameters (server_unc is ignored by w2k3!) + */ + dcname = talloc_asprintf(mem_ctx, "\\\\%s", + lpcfg_netbios_name(dce_call->conn->dce_ctx->lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(dcname); + + *r->out.dcname = dcname; + return WERR_OK; +} + +struct dcesrv_netr_LogonControl_base_state { + struct dcesrv_call_state *dce_call; + + TALLOC_CTX *mem_ctx; + + struct netr_LogonControl2Ex r; + + struct { + struct netr_LogonControl *l; + struct netr_LogonControl2 *l2; + struct netr_LogonControl2Ex *l2ex; + } _r; +}; + +static void dcesrv_netr_LogonControl_base_done(struct tevent_req *subreq); + +static WERROR dcesrv_netr_LogonControl_base_call(struct dcesrv_netr_LogonControl_base_state *state) +{ + struct loadparm_context *lp_ctx = state->dce_call->conn->dce_ctx->lp_ctx; + struct auth_session_info *session_info = + dcesrv_call_session_info(state->dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(state->dce_call->conn); + enum security_user_level security_level; + struct dcerpc_binding_handle *irpc_handle; + struct tevent_req *subreq; + bool ok; + + /* TODO: check for WERR_INVALID_COMPUTERNAME ? */ + + if (state->_r.l != NULL) { + /* + * netr_LogonControl + */ + if (state->r.in.level == 0x00000002) { + return WERR_NOT_SUPPORTED; + } else if (state->r.in.level != 0x00000001) { + return WERR_INVALID_LEVEL; + } + + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_QUERY: + case NETLOGON_CONTROL_REPLICATE: + case NETLOGON_CONTROL_SYNCHRONIZE: + case NETLOGON_CONTROL_PDC_REPLICATE: + case NETLOGON_CONTROL_BREAKPOINT: + case NETLOGON_CONTROL_BACKUP_CHANGE_LOG: + case NETLOGON_CONTROL_TRUNCATE_LOG: + break; + default: + return WERR_NOT_SUPPORTED; + } + } + + if (state->r.in.level < 0x00000001) { + return WERR_INVALID_LEVEL; + } + + if (state->r.in.level > 0x00000004) { + return WERR_INVALID_LEVEL; + } + + if (state->r.in.function_code == NETLOGON_CONTROL_QUERY) { + struct netr_NETLOGON_INFO_1 *info1 = NULL; + struct netr_NETLOGON_INFO_3 *info3 = NULL; + + switch (state->r.in.level) { + case 0x00000001: + info1 = talloc_zero(state->mem_ctx, + struct netr_NETLOGON_INFO_1); + if (info1 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->r.out.query->info1 = info1; + return WERR_OK; + + case 0x00000003: + info3 = talloc_zero(state->mem_ctx, + struct netr_NETLOGON_INFO_3); + if (info3 == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->r.out.query->info3 = info3; + return WERR_OK; + + default: + return WERR_INVALID_PARAMETER; + } + } + + /* + * Some validations are done before the access check + * and some after the access check + */ + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_ADMINISTRATOR) { + return WERR_ACCESS_DENIED; + } + + if (state->_r.l2 != NULL) { + /* + * netr_LogonControl2 + */ + if (state->r.in.level == 0x00000004) { + return WERR_INVALID_LEVEL; + } + } + + switch (state->r.in.level) { + case 0x00000001: + break; + + case 0x00000002: + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_TC_VERIFY: + break; + default: + return WERR_INVALID_PARAMETER; + } + + break; + + case 0x00000003: + break; + + case 0x00000004: + if (state->r.in.function_code != NETLOGON_CONTROL_FIND_USER) { + return WERR_INVALID_PARAMETER; + } + + break; + + default: + return WERR_INVALID_LEVEL; + } + + switch (state->r.in.function_code) { + case NETLOGON_CONTROL_REDISCOVER: + case NETLOGON_CONTROL_TC_QUERY: + case NETLOGON_CONTROL_TC_VERIFY: + if (state->r.in.level != 2) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data == NULL) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data->domain == NULL) { + return WERR_INVALID_PARAMETER; + } + + break; + + case NETLOGON_CONTROL_CHANGE_PASSWORD: + if (state->r.in.level != 1) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data == NULL) { + return WERR_INVALID_PARAMETER; + } + + if (state->r.in.data->domain == NULL) { + return WERR_INVALID_PARAMETER; + } + + ok = lpcfg_is_my_domain_or_realm(lp_ctx, + state->r.in.data->domain); + if (!ok) { + struct ldb_context *sam_ctx; + + sam_ctx = dcesrv_samdb_connect_as_system(state, + state->dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + /* + * Secrets for trusted domains can only be triggered on + * the PDC. + */ + ok = samdb_is_pdc(sam_ctx); + TALLOC_FREE(sam_ctx); + if (!ok) { + return WERR_INVALID_DOMAIN_ROLE; + } + } + + break; + default: + return WERR_NOT_SUPPORTED; + } + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + subreq = dcerpc_winbind_LogonControl_send(state, + state->dce_call->event_ctx, + irpc_handle, + state->r.in.function_code, + state->r.in.level, + state->r.in.data, + state->r.out.query); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_LogonControl_base_done, + state); + + return WERR_OK; +} + +static void dcesrv_netr_LogonControl_base_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_LogonControl_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_LogonControl_base_state); + NTSTATUS status; + + status = dcerpc_winbind_LogonControl_recv(subreq, state->mem_ctx, + &state->r.out.result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r.out.result = WERR_TIMEOUT; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + } + + if (state->_r.l2ex != NULL) { + struct netr_LogonControl2Ex *r = state->_r.l2ex; + r->out.result = state->r.out.result; + } else if (state->_r.l2 != NULL) { + struct netr_LogonControl2 *r = state->_r.l2; + r->out.result = state->r.out.result; + } else if (state->_r.l != NULL) { + struct netr_LogonControl *r = state->_r.l; + r->out.result = state->r.out.result; + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_LogonControl +*/ +static WERROR dcesrv_netr_LogonControl(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.logon_server = r->in.logon_server; + state->r.in.function_code = r->in.function_code; + state->r.in.level = r->in.level; + state->r.in.data = NULL; + state->r.out.query = r->out.query; + + state->_r.l = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +/* + netr_LogonControl2 +*/ +static WERROR dcesrv_netr_LogonControl2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl2 *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.logon_server = r->in.logon_server; + state->r.in.function_code = r->in.function_code; + state->r.in.level = r->in.level; + state->r.in.data = r->in.data; + state->r.out.query = r->out.query; + + state->_r.l2 = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +/* + netr_LogonControl2Ex +*/ +static WERROR dcesrv_netr_LogonControl2Ex(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonControl2Ex *r) +{ + struct dcesrv_netr_LogonControl_base_state *state; + WERROR werr; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_LogonControl_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r = *r; + state->_r.l2ex = r; + + werr = dcesrv_netr_LogonControl_base_call(state); + + if (dce_call->state_flags & DCESRV_CALL_STATE_FLAG_ASYNC) { + return werr; + } + + return werr; +} + +static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct netr_DomainTrustList *trusts, + uint32_t trust_flags); + +/* + netr_GetAnyDCName +*/ +static WERROR dcesrv_netr_GetAnyDCName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_GetAnyDCName *r) +{ + struct netr_DomainTrustList *trusts; + struct ldb_context *sam_ctx; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + uint32_t i; + WERROR werr; + + *r->out.dcname = NULL; + + if ((r->in.domainname == NULL) || (r->in.domainname[0] == '\0')) { + /* if the domainname parameter wasn't set assume our domain */ + r->in.domainname = lpcfg_workgroup(lp_ctx); + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + if (strcasecmp(r->in.domainname, lpcfg_workgroup(lp_ctx)) == 0) { + /* well we asked for a DC of our own domain */ + if (samdb_is_pdc(sam_ctx)) { + /* we are the PDC of the specified domain */ + return WERR_NO_SUCH_DOMAIN; + } + + *r->out.dcname = talloc_asprintf(mem_ctx, "\\%s", + lpcfg_netbios_name(lp_ctx)); + W_ERROR_HAVE_NO_MEMORY(*r->out.dcname); + + return WERR_OK; + } + + /* Okay, now we have to consider the trusted domains */ + + trusts = talloc_zero(mem_ctx, struct netr_DomainTrustList); + W_ERROR_HAVE_NO_MEMORY(trusts); + + trusts->count = 0; + + werr = fill_trusted_domains_array(mem_ctx, sam_ctx, trusts, + NETR_TRUST_FLAG_INBOUND + | NETR_TRUST_FLAG_OUTBOUND); + W_ERROR_NOT_OK_RETURN(werr); + + for (i = 0; i < trusts->count; i++) { + if (strcasecmp(r->in.domainname, trusts->array[i].netbios_name) == 0) { + /* FIXME: Here we need to find a DC for the specified + * trusted domain. */ + + /* return WERR_OK; */ + return WERR_NO_SUCH_DOMAIN; + } + } + + return WERR_NO_SUCH_DOMAIN; +} + + +/* + netr_DatabaseRedo +*/ +static NTSTATUS dcesrv_netr_DatabaseRedo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DatabaseRedo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NetrEnumerateTrustedDomains +*/ +static NTSTATUS dcesrv_netr_NetrEnumerateTrustedDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrEnumerateTrustedDomains *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonGetCapabilities +*/ +static NTSTATUS dcesrv_netr_LogonGetCapabilities(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonGetCapabilities *r) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS status; + + switch (r->in.query_level) { + case 1: + break; + case 2: + /* + * Until we know the details behind KB5028166 + * just return DCERPC_NCA_S_FAULT_INVALID_TAG + * like an unpatched Windows Server. + */ + FALL_THROUGH; + default: + /* + * There would not be a way to marshall the + * the response. Which would mean our final + * ndr_push would fail an we would return + * an RPC-level fault with DCERPC_FAULT_BAD_STUB_DATA. + * + * But it's important to match a Windows server + * especially before KB5028166, see also our bug #15418 + * Otherwise Windows client would stop talking to us. + */ + DCESRV_FAULT(DCERPC_NCA_S_FAULT_INVALID_TAG); + } + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ " Bad credentials - error\n")); + } + NT_STATUS_NOT_OK_RETURN(status); + + r->out.capabilities->server_capabilities = creds->negotiate_flags; + + return NT_STATUS_OK; +} + + +/* + netr_NETRLOGONSETSERVICEBITS +*/ +static WERROR dcesrv_netr_NETRLOGONSETSERVICEBITS(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONSETSERVICEBITS *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_LogonGetTrustRid +*/ +static WERROR dcesrv_netr_LogonGetTrustRid(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_LogonGetTrustRid *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NETRLOGONCOMPUTESERVERDIGEST +*/ +static WERROR dcesrv_netr_NETRLOGONCOMPUTESERVERDIGEST(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONCOMPUTESERVERDIGEST *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NETRLOGONCOMPUTECLIENTDIGEST +*/ +static WERROR dcesrv_netr_NETRLOGONCOMPUTECLIENTDIGEST(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONCOMPUTECLIENTDIGEST *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + + +/* + netr_DsRGetSiteName +*/ +static WERROR dcesrv_netr_DsRGetSiteName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetSiteName *r) +{ + struct ldb_context *sam_ctx; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + /* + * We assume to be a DC when we get called over NETLOGON. Hence we + * get our site name always by using "samdb_server_site_name()" + * and not "samdb_client_site_name()". + */ + *r->out.site = samdb_server_site_name(sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(*r->out.site); + + return WERR_OK; +} + + +/* + fill in a netr_OneDomainInfo from our own domain/forest +*/ +static NTSTATUS fill_our_one_domain_info(TALLOC_CTX *mem_ctx, + const struct lsa_TrustDomainInfoInfoEx *our_tdo, + struct GUID domain_guid, + struct netr_OneDomainInfo *info, + bool is_trust_list) +{ + ZERO_STRUCTP(info); + + if (is_trust_list) { + struct netr_trust_extension *te = NULL; + struct netr_trust_extension_info *tei = NULL; + + /* w2k8 only fills this on trusted domains */ + te = talloc_zero(mem_ctx, struct netr_trust_extension); + if (te == NULL) { + return NT_STATUS_NO_MEMORY; + } + tei = &te->info; + tei->flags |= NETR_TRUST_FLAG_PRIMARY; + + /* + * We're always within a native forest + */ + tei->flags |= NETR_TRUST_FLAG_IN_FOREST; + tei->flags |= NETR_TRUST_FLAG_NATIVE; + + /* For now we assume we're always the tree root */ + tei->flags |= NETR_TRUST_FLAG_TREEROOT; + tei->parent_index = 0; + + tei->trust_type = our_tdo->trust_type; + /* + * This needs to be 0 instead of our_tdo->trust_attributes + * It means LSA_TRUST_ATTRIBUTE_WITHIN_FOREST won't + * be set, while NETR_TRUST_FLAG_IN_FOREST is set above. + */ + tei->trust_attributes = 0; + + info->trust_extension.info = te; + } + + if (is_trust_list) { + info->dns_domainname.string = our_tdo->domain_name.string; + + /* MS-NRPC 3.5.4.3.9 - must be set to NULL for trust list */ + info->dns_forestname.string = NULL; + } else { + info->dns_domainname.string = talloc_asprintf(mem_ctx, "%s.", + our_tdo->domain_name.string); + if (info->dns_domainname.string == NULL) { + return NT_STATUS_NO_MEMORY; + } + + info->dns_forestname.string = info->dns_domainname.string; + } + + info->domainname.string = our_tdo->netbios_name.string; + info->domain_sid = our_tdo->sid; + info->domain_guid = domain_guid; + + return NT_STATUS_OK; +} + +/* + fill in a netr_OneDomainInfo from a trust tdo +*/ +static NTSTATUS fill_trust_one_domain_info(TALLOC_CTX *mem_ctx, + struct GUID domain_guid, + const struct lsa_TrustDomainInfoInfoEx *tdo, + struct netr_OneDomainInfo *info) +{ + struct netr_trust_extension *te = NULL; + struct netr_trust_extension_info *tei = NULL; + + ZERO_STRUCTP(info); + + /* w2k8 only fills this on trusted domains */ + te = talloc_zero(mem_ctx, struct netr_trust_extension); + if (te == NULL) { + return NT_STATUS_NO_MEMORY; + } + tei = &te->info; + + if (tdo->trust_direction & LSA_TRUST_DIRECTION_INBOUND) { + tei->flags |= NETR_TRUST_FLAG_INBOUND; + } + if (tdo->trust_direction & LSA_TRUST_DIRECTION_OUTBOUND) { + tei->flags |= NETR_TRUST_FLAG_OUTBOUND; + } + if (tdo->trust_attributes & LSA_TRUST_ATTRIBUTE_WITHIN_FOREST) { + tei->flags |= NETR_TRUST_FLAG_IN_FOREST; + } + + /* + * TODO: once we support multiple domains within our forest, + * we need to fill this correct (or let the caller do it + * for all domains marked with NETR_TRUST_FLAG_IN_FOREST). + */ + tei->parent_index = 0; + + tei->trust_type = tdo->trust_type; + tei->trust_attributes = tdo->trust_attributes; + + info->trust_extension.info = te; + + info->domainname.string = tdo->netbios_name.string; + if (tdo->trust_type != LSA_TRUST_TYPE_DOWNLEVEL) { + info->dns_domainname.string = tdo->domain_name.string; + } else { + info->dns_domainname.string = NULL; + } + info->domain_sid = tdo->sid; + info->domain_guid = domain_guid; + + /* MS-NRPC 3.5.4.3.9 - must be set to NULL for trust list */ + info->dns_forestname.string = NULL; + + return NT_STATUS_OK; +} + +/* + netr_LogonGetDomainInfo + this is called as part of the ADS domain logon procedure. + + It has an important role in convaying details about the client, such + as Operating System, Version, Service Pack etc. +*/ +static NTSTATUS dcesrv_netr_LogonGetDomainInfo(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, struct netr_LogonGetDomainInfo *r) +{ + struct netlogon_creds_CredentialState *creds; + const char * const trusts_attrs[] = { + "securityIdentifier", + "flatName", + "trustPartner", + "trustAttributes", + "trustDirection", + "trustType", + NULL + }; + const char * const attrs2[] = { "sAMAccountName", "dNSHostName", + "msDS-SupportedEncryptionTypes", NULL }; + const char *sam_account_name, *old_dns_hostname; + struct ldb_context *sam_ctx; + const struct GUID *our_domain_guid = NULL; + struct lsa_TrustDomainInfoInfoEx *our_tdo = NULL; + struct ldb_message **res1, *new_msg; + struct ldb_result *trusts_res = NULL; + struct ldb_dn *workstation_dn; + struct netr_DomainInformation *domain_info; + struct netr_LsaPolicyInformation *lsa_policy_info; + struct auth_session_info *workstation_session_info = NULL; + uint32_t default_supported_enc_types = 0xFFFFFFFF; + bool update_dns_hostname = true; + int ret, i; + NTSTATUS status; + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + char* local = NULL; + char* remote = NULL; + TALLOC_CTX *frame = talloc_stackframe(); + remote = tsocket_address_string(dce_call->conn->remote_address, + frame); + local = tsocket_address_string(dce_call->conn->local_address, + frame); + DBG_ERR(("Bad credentials - " + "computer[%s] remote[%s] local[%s]\n"), + log_escape(frame, r->in.computer_name), + remote, + local); + talloc_free(frame); + } + NT_STATUS_NOT_OK_RETURN(status); + + /* We want to avoid connecting as system. */ + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + switch (r->in.level) { + case 1: /* Domain information */ + + if (r->in.query->workstation_info == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Prepares the workstation DN */ + workstation_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "<SID=%s>", + dom_sid_string(mem_ctx, creds->sid)); + NT_STATUS_HAVE_NO_MEMORY(workstation_dn); + + /* Get the workstation's session info from the database. */ + status = authsam_get_session_info_principal(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + sam_ctx, + NULL, /* principal */ + workstation_dn, + 0, /* session_info_flags */ + &workstation_session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * Reconnect to samdb as the workstation, now that we have its + * session info. We do this so the database update can be + * attributed to the workstation account in the audit logs -- + * otherwise it might be incorrectly attributed to + * SID_NT_ANONYMOUS. + */ + sam_ctx = dcesrv_samdb_connect_session_info(mem_ctx, + dce_call, + workstation_session_info, + workstation_session_info); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* Lookup for attributes in workstation object */ + ret = gendb_search_dn(sam_ctx, mem_ctx, workstation_dn, &res1, + attrs2); + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* Gets the sam account name which is checked against the DNS + * hostname parameter. */ + sam_account_name = ldb_msg_find_attr_as_string(res1[0], + "sAMAccountName", + NULL); + if (sam_account_name == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (r->in.query->workstation_info->dns_hostname == NULL) { + update_dns_hostname = false; + } + + /* Gets the old DNS hostname */ + old_dns_hostname = ldb_msg_find_attr_as_string(res1[0], + "dNSHostName", + NULL); + + /* + * Updates the DNS hostname when the client wishes that the + * server should handle this for him + * ("NETR_WS_FLAG_HANDLES_SPN_UPDATE" not set). + * See MS-NRPC section 3.5.4.3.9 + */ + if ((r->in.query->workstation_info->workstation_flags + & NETR_WS_FLAG_HANDLES_SPN_UPDATE) != 0) { + update_dns_hostname = false; + } + + /* Gets host information and put them into our directory */ + + new_msg = ldb_msg_new(mem_ctx); + NT_STATUS_HAVE_NO_MEMORY(new_msg); + + new_msg->dn = workstation_dn; + + /* Sets the OS name */ + + if (r->in.query->workstation_info->os_name.string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + ret = ldb_msg_add_string(new_msg, "operatingSystem", + r->in.query->workstation_info->os_name.string); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* + * Sets information from "os_version". On an empty structure + * the values are cleared. + */ + if (r->in.query->workstation_info->os_version.os != NULL) { + struct netr_OsVersionInfoEx *os_version; + const char *os_version_str; + + os_version = &r->in.query->workstation_info->os_version.os->os; + + if (os_version->CSDVersion == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + os_version_str = talloc_asprintf(new_msg, "%u.%u (%u)", + os_version->MajorVersion, + os_version->MinorVersion, + os_version->BuildNumber); + NT_STATUS_HAVE_NO_MEMORY(os_version_str); + + if (strlen(os_version->CSDVersion) != 0) { + ret = ldb_msg_add_string(new_msg, + "operatingSystemServicePack", + os_version->CSDVersion); + } else { + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemServicePack"); + } + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(new_msg, + "operatingSystemVersion", + os_version_str); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } else { + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemServicePack"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = samdb_msg_add_delete(sam_ctx, mem_ctx, new_msg, + "operatingSystemVersion"); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + /* + * If the boolean "update_dns_hostname" remained true, then we + * are fine to start the update. + */ + if (update_dns_hostname) { + ret = ldb_msg_add_string(new_msg, + "dNSHostname", + r->in.query->workstation_info->dns_hostname); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + /* This manual "servicePrincipalName" generation is + * still needed! Since the update in the samldb LDB + * module does only work if the entries already exist + * which isn't always the case. */ + ret = ldb_msg_add_string(new_msg, + "servicePrincipalName", + talloc_asprintf(new_msg, "HOST/%s", + r->in.computer_name)); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_msg_add_string(new_msg, + "servicePrincipalName", + talloc_asprintf(new_msg, "HOST/%s", + r->in.query->workstation_info->dns_hostname)); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + } + + if (dsdb_replace(sam_ctx, new_msg, DSDB_FLAG_FORCE_ALLOW_VALIDATED_DNS_HOSTNAME_SPN_WRITE) != LDB_SUCCESS) { + DEBUG(3,("Impossible to update samdb: %s\n", + ldb_errstring(sam_ctx))); + } + + talloc_free(new_msg); + + /* Writes back the domain information */ + + our_domain_guid = samdb_domain_guid(sam_ctx); + if (our_domain_guid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = dsdb_trust_local_tdo_info(mem_ctx, sam_ctx, &our_tdo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dsdb_trust_search_tdos(sam_ctx, + NULL, /* exclude */ + trusts_attrs, + mem_ctx, + &trusts_res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_info = talloc(mem_ctx, struct netr_DomainInformation); + NT_STATUS_HAVE_NO_MEMORY(domain_info); + + ZERO_STRUCTP(domain_info); + + /* Informations about the local and trusted domains */ + + status = fill_our_one_domain_info(mem_ctx, + our_tdo, + *our_domain_guid, + &domain_info->primary_domain, + false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + domain_info->trusted_domain_count = trusts_res->count + 1; + domain_info->trusted_domains = talloc_zero_array(mem_ctx, + struct netr_OneDomainInfo, + domain_info->trusted_domain_count); + NT_STATUS_HAVE_NO_MEMORY(domain_info->trusted_domains); + + for (i=0; i < trusts_res->count; i++) { + struct netr_OneDomainInfo *o = + &domain_info->trusted_domains[i]; + /* we can't know the guid of trusts outside our forest */ + struct GUID trust_domain_guid = GUID_zero(); + struct lsa_TrustDomainInfoInfoEx *tdo = NULL; + + status = dsdb_trust_parse_tdo_info(mem_ctx, + trusts_res->msgs[i], + &tdo); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = fill_trust_one_domain_info(mem_ctx, + trust_domain_guid, + tdo, + o); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + status = fill_our_one_domain_info(mem_ctx, + our_tdo, + *our_domain_guid, + &domain_info->trusted_domains[i], + true); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Sets the supported encryption types */ + domain_info->supported_enc_types = ldb_msg_find_attr_as_uint(res1[0], + "msDS-SupportedEncryptionTypes", + default_supported_enc_types); + + /* Other host domain information */ + + lsa_policy_info = talloc(mem_ctx, + struct netr_LsaPolicyInformation); + NT_STATUS_HAVE_NO_MEMORY(lsa_policy_info); + ZERO_STRUCTP(lsa_policy_info); + + domain_info->lsa_policy = *lsa_policy_info; + + /* The DNS hostname is only returned back when there is a chance + * for a change. */ + if ((r->in.query->workstation_info->workstation_flags + & NETR_WS_FLAG_HANDLES_SPN_UPDATE) != 0) { + domain_info->dns_hostname.string = old_dns_hostname; + } else { + domain_info->dns_hostname.string = NULL; + } + + domain_info->workstation_flags = + r->in.query->workstation_info->workstation_flags & ( + NETR_WS_FLAG_HANDLES_SPN_UPDATE | NETR_WS_FLAG_HANDLES_INBOUND_TRUSTS); + + r->out.info->domain_info = domain_info; + break; + case 2: /* LSA policy information - not used at the moment */ + lsa_policy_info = talloc(mem_ctx, + struct netr_LsaPolicyInformation); + NT_STATUS_HAVE_NO_MEMORY(lsa_policy_info); + ZERO_STRUCTP(lsa_policy_info); + + r->out.info->lsa_policy_info = lsa_policy_info; + break; + default: + return NT_STATUS_INVALID_LEVEL; + break; + } + + return NT_STATUS_OK; +} + + +/* + netr_ServerPasswordGet +*/ +static NTSTATUS dcesrv_netr_ServerPasswordGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerPasswordGet *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +static bool sam_rodc_access_check(struct ldb_context *sam_ctx, + TALLOC_CTX *mem_ctx, + struct dom_sid *user_sid, + struct ldb_dn *obj_dn) +{ + const char *rodc_attrs[] = { "msDS-NeverRevealGroup", + "msDS-RevealOnDemandGroup", + "userAccountControl", + NULL }; + const char *obj_attrs[] = { "tokenGroups", "objectSid", "UserAccountControl", "msDS-KrbTgtLinkBL", NULL }; + struct ldb_dn *rodc_dn; + int ret; + struct ldb_result *rodc_res = NULL, *obj_res = NULL; + WERROR werr; + + rodc_dn = ldb_dn_new_fmt(mem_ctx, sam_ctx, "<SID=%s>", + dom_sid_string(mem_ctx, user_sid)); + if (!ldb_dn_validate(rodc_dn)) goto denied; + + /* + * do the two searches we need + * We need DSDB_SEARCH_SHOW_EXTENDED_DN as we get a SID list + * out of the extended DNs + */ + ret = dsdb_search_dn(sam_ctx, mem_ctx, &rodc_res, rodc_dn, rodc_attrs, + DSDB_SEARCH_SHOW_EXTENDED_DN); + if (ret != LDB_SUCCESS || rodc_res->count != 1) goto denied; + + ret = dsdb_search_dn(sam_ctx, mem_ctx, &obj_res, obj_dn, obj_attrs, 0); + if (ret != LDB_SUCCESS || obj_res->count != 1) goto denied; + + werr = samdb_confirm_rodc_allowed_to_repl_to(sam_ctx, + user_sid, + rodc_res->msgs[0], + obj_res->msgs[0]); + + if (W_ERROR_IS_OK(werr)) { + goto allowed; + } +denied: + return false; +allowed: + return true; + +} + +/* + netr_NetrLogonSendToSam +*/ +static NTSTATUS dcesrv_netr_NetrLogonSendToSam(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrLogonSendToSam *r) +{ + struct netlogon_creds_CredentialState *creds; + struct ldb_context *sam_ctx; + NTSTATUS nt_status; + DATA_BLOB decrypted_blob; + enum ndr_err_code ndr_err; + struct netr_SendToSamBase base_msg = { 0 }; + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + + NT_STATUS_NOT_OK_RETURN(nt_status); + + switch (creds->secure_channel_type) { + case SEC_CHAN_BDC: + case SEC_CHAN_RODC: + break; + case SEC_CHAN_WKSTA: + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + case SEC_CHAN_NULL: + return NT_STATUS_INVALID_PARAMETER; + default: + DEBUG(1, ("Client asked for an invalid secure channel type: %d\n", + creds->secure_channel_type)); + return NT_STATUS_INVALID_PARAMETER; + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* Buffer is meant to be 16-bit aligned */ + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + nt_status = netlogon_creds_aes_decrypt(creds, + r->in.opaque_buffer, + r->in.buffer_len); + } else { + nt_status = netlogon_creds_arcfour_crypt(creds, + r->in.opaque_buffer, + r->in.buffer_len); + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + decrypted_blob.data = r->in.opaque_buffer; + decrypted_blob.length = r->in.buffer_len; + + ndr_err = ndr_pull_struct_blob(&decrypted_blob, mem_ctx, &base_msg, + (ndr_pull_flags_fn_t)ndr_pull_netr_SendToSamBase); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + /* We only partially implement SendToSam */ + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* Now 'send' to SAM */ + switch (base_msg.message_type) { + case SendToSamResetBadPasswordCount: + { + struct ldb_message *msg = ldb_msg_new(mem_ctx); + struct ldb_dn *dn = NULL; + int ret = 0; + + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_ERROR; + } + + ret = dsdb_find_dn_by_guid(sam_ctx, + mem_ctx, + &base_msg.message.reset_bad_password.guid, + 0, + &dn); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (creds->secure_channel_type == SEC_CHAN_RODC && + !sam_rodc_access_check(sam_ctx, mem_ctx, creds->sid, dn)) { + DEBUG(1, ("Client asked to reset bad password on " + "an arbitrary user: %s\n", + ldb_dn_get_linearized(dn))); + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + msg->dn = dn; + + ret = samdb_msg_add_int(sam_ctx, mem_ctx, msg, "badPwdCount", 0); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ret = dsdb_replace(sam_ctx, msg, 0); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + return NT_STATUS_INTERNAL_ERROR; + } + + break; + } + default: + return NT_STATUS_NOT_IMPLEMENTED; + } + + return NT_STATUS_OK; +} + +struct dcesrv_netr_DsRGetDCName_base_state { + struct dcesrv_call_state *dce_call; + TALLOC_CTX *mem_ctx; + + struct netr_DsRGetDCNameEx2 r; + const char *client_site; + + struct { + struct netr_DsRGetDCName *dc; + struct netr_DsRGetDCNameEx *dcex; + struct netr_DsRGetDCNameEx2 *dcex2; + } _r; +}; + +static void dcesrv_netr_DsRGetDCName_base_done(struct tevent_req *subreq); + +/* Returns a nonzero value if multiple bits in 'val' are set. */ +static bool multiple_bits_set(uint32_t val) +{ + /* + * Subtracting one from an integer has the effect of flipping all the + * bits from the least significant bit up to and including the least + * significant '1' bit. For example, + * + * 0b101000 - 1 + * = 0b100111 + * ==== + * + * If 'val' is zero, all the bits will be flipped and thus the bitwise + * AND of 'val' with 'val - 1' will be zero. + * + * If the integer is nonzero, the least significant '1' bit will be + * ANDed with a '0' bit and so will be reset in the final result, but + * all other '1' bits will remain set. In other words, the effect of + * this expression is to mask off the least significant bit that is + * set. Therefore iff the result of 'val & (val - 1)' is non-zero, 'val' + * must contain multiple set bits. + */ + return val & (val - 1); +} + +static WERROR dcesrv_netr_DsRGetDCName_base_call(struct dcesrv_netr_DsRGetDCName_base_state *state) +{ + struct dcesrv_call_state *dce_call = state->dce_call; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + TALLOC_CTX *mem_ctx = state->mem_ctx; + struct netr_DsRGetDCNameEx2 *r = &state->r; + struct ldb_context *sam_ctx; + struct netr_DsRGetDCNameInfo *info; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const struct tsocket_address *local_address; + char *local_addr = NULL; + const struct tsocket_address *remote_address; + char *remote_addr = NULL; + const char *server_site_name; + char *guid_str; + struct netlogon_samlogon_response response; + NTSTATUS status; + const char *dc_name = NULL; + const char *domain_name = NULL; + const char *pdc_ip; + bool different_domain = true; + uint32_t valid_flags; + int dc_level; + + ZERO_STRUCTP(r->out.info); + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + local_address = dcesrv_connection_get_local_address(dce_call->conn); + if (tsocket_address_is_inet(local_address, "ip")) { + local_addr = tsocket_address_inet_addr_string(local_address, state); + W_ERROR_HAVE_NO_MEMORY(local_addr); + } + + remote_address = dcesrv_connection_get_remote_address(dce_call->conn); + if (tsocket_address_is_inet(remote_address, "ip")) { + remote_addr = tsocket_address_inet_addr_string(remote_address, state); + W_ERROR_HAVE_NO_MEMORY(remote_addr); + } + + /* "server_unc" is ignored by w2k3 */ + + /* + * With the following flags: + * DS_FORCE_REDISCOVERY (Flag A) + * DS_DIRECTORY_SERVICE_REQUIRED (Flag B) + * DS_DIRECTORY_SERVICE_PREFERRED (Flag C) + * DS_GC_SERVER_REQUIRED (Flag D) + * DS_PDC_REQUIRED (Flag E) + * DS_BACKGROUND_ONLY (Flag F) + * DS_IP_REQUIRED (Flag G) + * DS_KDC_REQUIRED (Flag H) + * DS_TIMESERV_REQUIRED (Flag I) + * DS_WRITABLE_REQUIRED (Flag J) + * DS_GOOD_TIMESERV_PREFERRED (Flag K) + * DS_AVOID_SELF (Flag L) + * DS_ONLY_LDAP_NEEDED (Flag M) + * DS_IS_FLAT_NAME (Flag N) + * DS_IS_DNS_NAME (Flag O) + * DS_TRY_NEXTCLOSEST_SITE (Flag P) + * DS_DIRECTORY_SERVICE_6_REQUIRED (Flag Q) + * DS_WEB_SERVICE_REQUIRED (Flag T) + * DS_DIRECTORY_SERVICE_8_REQUIRED (Flag U) + * DS_DIRECTORY_SERVICE_9_REQUIRED (Flag V) + * DS_DIRECTORY_SERVICE_10_REQUIRED (Flag W) + * DS_RETURN_DNS_NAME (Flag R) + * DS_RETURN_FLAT_NAME (Flag S) + * + * MS-NRPC 3.5.4.3.1 says: + * ... + * On receiving this call, the server MUST perform the following Flags + * parameter validations: + * - Flags D, E, and H MUST NOT be combined with each other. + * - Flag N MUST NOT be combined with the O flag. + * - Flag R MUST NOT be combined with the S flag. + * - Flags B, Q, U, V, and W MUST NOT be combined with each other. + * - Flag K MUST NOT be combined with any of the flags: B, C, D, E, or H. + * - Flag P MUST NOT be set when the SiteName parameter is provided. + * The server MUST return ERROR_INVALID_FLAGS for any of the previously + * mentioned conflicting combinations. + * ... + */ + + dc_level = dsdb_dc_functional_level(sam_ctx); + valid_flags = DSGETDC_VALID_FLAGS; + if (dc_level >= DS_DOMAIN_FUNCTION_2012) { + valid_flags |= DS_DIRECTORY_SERVICE_8_REQUIRED; + } + if (dc_level >= DS_DOMAIN_FUNCTION_2012_R2) { + valid_flags |= DS_DIRECTORY_SERVICE_9_REQUIRED; + } + if (dc_level >= DS_DOMAIN_FUNCTION_2016) { + valid_flags |= DS_DIRECTORY_SERVICE_10_REQUIRED; + } + if (r->in.flags & ~valid_flags) { + /* + * TODO: add tests to prove this (maybe based on the + * msDS-Behavior-Version levels of dc, domain and/or forest + */ + return WERR_INVALID_FLAGS; + } + + /* Flags D, E, and H MUST NOT be combined with each other. */ +#define _DEH (DS_GC_SERVER_REQUIRED|DS_PDC_REQUIRED|DS_KDC_REQUIRED) + if (multiple_bits_set(r->in.flags & _DEH)) { + return WERR_INVALID_FLAGS; + } + + /* Flag N MUST NOT be combined with the O flag. */ + if (r->in.flags & DS_IS_FLAT_NAME && + r->in.flags & DS_IS_DNS_NAME) { + return WERR_INVALID_FLAGS; + } + + /* Flag R MUST NOT be combined with the S flag. */ + if (r->in.flags & DS_RETURN_DNS_NAME && + r->in.flags & DS_RETURN_FLAT_NAME) { + return WERR_INVALID_FLAGS; + } + + /* Flags B, Q, U, V, and W MUST NOT be combined with each other */ +#define _BQUVW ( \ + DS_DIRECTORY_SERVICE_REQUIRED | \ + DS_DIRECTORY_SERVICE_6_REQUIRED | \ + DS_DIRECTORY_SERVICE_8_REQUIRED | \ + DS_DIRECTORY_SERVICE_9_REQUIRED | \ + DS_DIRECTORY_SERVICE_10_REQUIRED | \ +0) + if (multiple_bits_set(r->in.flags & _BQUVW)) { + return WERR_INVALID_FLAGS; + } + + /* + * Flag K MUST NOT be combined with any of the flags: + * B, C, D, E, or H. + */ + if (r->in.flags & DS_GOOD_TIMESERV_PREFERRED && + r->in.flags & + (DS_DIRECTORY_SERVICE_REQUIRED | + DS_DIRECTORY_SERVICE_PREFERRED | + DS_GC_SERVER_REQUIRED | + DS_PDC_REQUIRED | + DS_KDC_REQUIRED)) { + return WERR_INVALID_FLAGS; + } + + /* Flag P MUST NOT be set when the SiteName parameter is provided. */ + if (r->in.flags & DS_TRY_NEXTCLOSEST_SITE && + r->in.site_name) { + return WERR_INVALID_FLAGS; + } + + /* + * If we send an all-zero GUID, we should ignore it as winbind actually + * checks it with a DNS query. Windows also appears to ignore it. + */ + if (r->in.domain_guid != NULL && GUID_all_zero(r->in.domain_guid)) { + r->in.domain_guid = NULL; + } + + /* Attempt winbind search only if we suspect the domain is incorrect */ + if (r->in.domain_name != NULL && strcmp("", r->in.domain_name) != 0) { + if (r->in.flags & DS_IS_FLAT_NAME) { + if (strcasecmp_m(r->in.domain_name, + lpcfg_sam_name(lp_ctx)) == 0) { + different_domain = false; + } + } else if (r->in.flags & DS_IS_DNS_NAME) { + if (strcasecmp_m(r->in.domain_name, + lpcfg_dnsdomain(lp_ctx)) == 0) { + different_domain = false; + } + } else { + if (strcasecmp_m(r->in.domain_name, + lpcfg_sam_name(lp_ctx)) == 0 || + strcasecmp_m(r->in.domain_name, + lpcfg_dnsdomain(lp_ctx)) == 0) { + different_domain = false; + } + } + } else { + /* + * We need to be able to handle empty domain names, where we + * revert to our domain by default. + */ + different_domain = false; + } + + /* Proof server site parameter "site_name" if it was specified */ + server_site_name = samdb_server_site_name(sam_ctx, state); + W_ERROR_HAVE_NO_MEMORY(server_site_name); + if (different_domain || (r->in.site_name != NULL && + (strcasecmp_m(r->in.site_name, + server_site_name) != 0))) { + + struct dcerpc_binding_handle *irpc_handle = NULL; + struct tevent_req *subreq = NULL; + + /* + * Retrieve the client site to override the winbind response. + * + * DO NOT use Windows fallback for client site. + * In the case of multiple domains, this is plainly wrong. + * + * Note: It's possible that the client may belong to multiple + * subnets across domains. It's not clear what this would mean, + * but here we only return what this domain knows. + */ + state->client_site = samdb_client_site_name(sam_ctx, + state, + remote_addr, + NULL, + false); + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for " + "winbind_server task\n")); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + + subreq = dcerpc_wbint_DsGetDcName_send(state, + dce_call->event_ctx, + irpc_handle, + r->in.domain_name, + r->in.domain_guid, + r->in.site_name, + r->in.flags, + r->out.info); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + tevent_req_set_callback(subreq, + dcesrv_netr_DsRGetDCName_base_done, + state); + + return WERR_OK; + } + + guid_str = r->in.domain_guid != NULL ? + GUID_string(state, r->in.domain_guid) : NULL; + + status = fill_netlogon_samlogon_response(sam_ctx, mem_ctx, + r->in.domain_name, + r->in.domain_name, + NULL, guid_str, + r->in.client_account, + r->in.mask, remote_addr, + NETLOGON_NT_VERSION_5EX_WITH_IP, + lp_ctx, &response, true); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + /* + * According to MS-NRPC 2.2.1.2.1 we should set the "DS_DNS_FOREST_ROOT" + * (O) flag when the returned forest name is in DNS format. This is here + * always the case (see below). + */ + response.data.nt5_ex.server_type |= DS_DNS_FOREST_ROOT; + + if (r->in.flags & DS_RETURN_DNS_NAME) { + dc_name = response.data.nt5_ex.pdc_dns_name; + domain_name = response.data.nt5_ex.dns_domain; + /* + * According to MS-NRPC 2.2.1.2.1 we should set the + * "DS_DNS_CONTROLLER" (M) and "DS_DNS_DOMAIN" (N) flags when + * the returned information is in DNS form. + */ + response.data.nt5_ex.server_type |= + DS_DNS_CONTROLLER | DS_DNS_DOMAIN; + } else if (r->in.flags & DS_RETURN_FLAT_NAME) { + dc_name = response.data.nt5_ex.pdc_name; + domain_name = response.data.nt5_ex.domain_name; + } else { + + /* + * TODO: autodetect what we need to return + * based on the given arguments + */ + dc_name = response.data.nt5_ex.pdc_name; + domain_name = response.data.nt5_ex.domain_name; + } + + if (!dc_name || !dc_name[0]) { + return WERR_NO_SUCH_DOMAIN; + } + + if (!domain_name || !domain_name[0]) { + return WERR_NO_SUCH_DOMAIN; + } + + info = talloc(mem_ctx, struct netr_DsRGetDCNameInfo); + W_ERROR_HAVE_NO_MEMORY(info); + info->dc_unc = talloc_asprintf(mem_ctx, "%s%s", + dc_name[0] != '\\'? "\\\\":"", + talloc_strdup(mem_ctx, dc_name)); + W_ERROR_HAVE_NO_MEMORY(info->dc_unc); + + pdc_ip = local_addr; + if (pdc_ip == NULL) { + pdc_ip = "127.0.0.1"; + } + info->dc_address = talloc_asprintf(mem_ctx, "\\\\%s", pdc_ip); + W_ERROR_HAVE_NO_MEMORY(info->dc_address); + info->dc_address_type = DS_ADDRESS_TYPE_INET; + info->domain_guid = response.data.nt5_ex.domain_uuid; + info->domain_name = domain_name; + info->forest_name = response.data.nt5_ex.forest; + info->dc_flags = response.data.nt5_ex.server_type; + if (r->in.flags & DS_RETURN_DNS_NAME) { + /* As MS-NRPC.pdf in 2.2.1.2.1 the DS_DNS_CONTROLLER flag should be + * returned if we are returning info->dc_unc containing a FQDN. + * This attribute is called DomainControllerName in the specs, + * it seems that we decide to return FQDN or netbios depending on + * DS_RETURN_DNS_NAME. + */ + info->dc_flags |= DS_DNS_CONTROLLER; + } + info->dc_site_name = response.data.nt5_ex.server_site; + info->client_site_name = response.data.nt5_ex.client_site; + + *r->out.info = info; + + return WERR_OK; +} + +static void dcesrv_netr_DsRGetDCName_base_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_DsRGetDCName_base_state); + struct dcesrv_call_state *dce_call = state->dce_call; + NTSTATUS result, status; + + status = dcerpc_wbint_DsGetDcName_recv(subreq, + state->mem_ctx, + &result); + TALLOC_FREE(subreq); + + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r.out.result = WERR_TIMEOUT; + goto finished; + } + + if (!NT_STATUS_IS_OK(status)) { + DBG_ERR(__location__ ": IRPC callback failed %s\n", + nt_errstr(status)); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + if (!NT_STATUS_IS_OK(result)) { + DBG_NOTICE("DC location via winbind failed - %s\n", + nt_errstr(result)); + state->r.out.result = WERR_NO_SUCH_DOMAIN; + goto finished; + } + + if (state->r.out.info == NULL || state->r.out.info[0] == NULL) { + DBG_ERR("DC location via winbind returned no results\n"); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + if (state->r.out.info[0]->dc_unc == NULL) { + DBG_ERR("DC location via winbind returned no DC unc\n"); + state->r.out.result = WERR_GEN_FAILURE; + goto finished; + } + + /* + * Either the supplied site name is NULL (possibly via + * TRY_NEXT_CLOSEST_SITE) or the resulting site name matches + * the input match name. + * + * TODO: Currently this means that requests with NETBIOS domain + * names can fail because they do not return the site name. + */ + if (state->r.in.site_name == NULL || + strcasecmp_m("", state->r.in.site_name) == 0 || + (state->r.out.info[0]->dc_site_name != NULL && + strcasecmp_m(state->r.out.info[0]->dc_site_name, + state->r.in.site_name) == 0)) { + + state->r.out.info[0]->client_site_name = + talloc_move(state->mem_ctx, &state->client_site); + + /* + * Make sure to return our DC UNC with // prefix. + * Winbind currently doesn't send the leading slashes + * for some reason. + */ + if (strlen(state->r.out.info[0]->dc_unc) > 2 && + strncmp("\\\\", state->r.out.info[0]->dc_unc, 2) != 0) { + const char *dc_unc = NULL; + + dc_unc = talloc_asprintf(state->mem_ctx, + "\\\\%s", + state->r.out.info[0]->dc_unc); + state->r.out.info[0]->dc_unc = dc_unc; + } + + state->r.out.result = WERR_OK; + } else { + state->r.out.info = NULL; + state->r.out.result = WERR_NO_SUCH_DOMAIN; + } + +finished: + if (state->_r.dcex2 != NULL) { + struct netr_DsRGetDCNameEx2 *r = state->_r.dcex2; + r->out.result = state->r.out.result; + } else if (state->_r.dcex != NULL) { + struct netr_DsRGetDCNameEx *r = state->_r.dcex; + r->out.result = state->r.out.result; + } else if (state->_r.dc != NULL) { + struct netr_DsRGetDCName *r = state->_r.dc; + r->out.result = state->r.out.result; + } + + TALLOC_FREE(state); + status = dcesrv_reply(dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", + nt_errstr(status))); + } +} + +/* + netr_DsRGetDCNameEx2 +*/ +static WERROR dcesrv_netr_DsRGetDCNameEx2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameEx2 *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r = *r; + state->_r.dcex2 = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} + +/* + netr_DsRGetDCNameEx +*/ +static WERROR dcesrv_netr_DsRGetDCNameEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCNameEx *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_unc = r->in.server_unc; + state->r.in.client_account = NULL; + state->r.in.mask = 0; + state->r.in.domain_guid = r->in.domain_guid; + state->r.in.domain_name = r->in.domain_name; + state->r.in.site_name = r->in.site_name; + state->r.in.flags = r->in.flags; + state->r.out.info = r->out.info; + + state->_r.dcex = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} + +/* + * netr_DsRGetDCName + * + * This function is a predecessor to DsrGetDcNameEx2 according to [MS-NRPC]. + * Although it has a site-guid parameter, the documentation 3.5.4.3.3 DsrGetDcName + * insists that it be ignored. + */ +static WERROR dcesrv_netr_DsRGetDCName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRGetDCName *r) +{ + struct dcesrv_netr_DsRGetDCName_base_state *state; + + state = talloc_zero(mem_ctx, struct dcesrv_netr_DsRGetDCName_base_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + + state->r.in.server_unc = r->in.server_unc; + state->r.in.client_account = NULL; + state->r.in.mask = 0; + state->r.in.domain_name = r->in.domain_name; + state->r.in.domain_guid = r->in.domain_guid; + + state->r.in.site_name = NULL; /* this is correct, we should ignore site GUID */ + state->r.in.flags = r->in.flags; + state->r.out.info = r->out.info; + + state->_r.dc = r; + + return dcesrv_netr_DsRGetDCName_base_call(state); +} +/* + netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN +*/ +static WERROR dcesrv_netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NETRLOGONGETTIMESERVICEPARENTDOMAIN *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_NetrEnumerateTrustedDomainsEx +*/ +static WERROR dcesrv_netr_NetrEnumerateTrustedDomainsEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_NetrEnumerateTrustedDomainsEx *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + netr_DsRAddressToSitenamesExW +*/ +static WERROR dcesrv_netr_DsRAddressToSitenamesExW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRAddressToSitenamesExW *r) +{ + struct ldb_context *sam_ctx; + struct netr_DsRAddressToSitenamesExWCtr *ctr; + sa_family_t sin_family; + struct sockaddr_in *addr; +#ifdef HAVE_IPV6 + struct sockaddr_in6 *addr6; + char addr_str[INET6_ADDRSTRLEN]; +#else + char addr_str[INET_ADDRSTRLEN]; +#endif + char *subnet_name; + const char *res; + uint32_t i; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesExWCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + ctr->count = r->in.count; + ctr->sitename = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename); + ctr->subnetname = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->subnetname); + + for (i=0; i<ctr->count; i++) { + ctr->sitename[i].string = NULL; + ctr->subnetname[i].string = NULL; + + if (r->in.addresses[i].size < sizeof(sa_family_t)) { + continue; + } + /* The first two byte of the buffer are reserved for the + * "sin_family" but for now only the first one is used. */ + sin_family = r->in.addresses[i].buffer[0]; + + switch (sin_family) { + case AF_INET: + if (r->in.addresses[i].size < sizeof(struct sockaddr_in)) { + continue; + } + addr = (struct sockaddr_in *) r->in.addresses[i].buffer; + res = inet_ntop(AF_INET, &addr->sin_addr, + addr_str, sizeof(addr_str)); + break; +#ifdef HAVE_IPV6 + case AF_INET6: + if (r->in.addresses[i].size < sizeof(struct sockaddr_in6)) { + continue; + } + addr6 = (struct sockaddr_in6 *) r->in.addresses[i].buffer; + res = inet_ntop(AF_INET6, &addr6->sin6_addr, + addr_str, sizeof(addr_str)); + break; +#endif + default: + continue; + } + + if (res == NULL) { + continue; + } + + ctr->sitename[i].string = samdb_client_site_name(sam_ctx, + mem_ctx, + addr_str, + &subnet_name, + true); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename[i].string); + ctr->subnetname[i].string = subnet_name; + } + + return WERR_OK; +} + + +/* + netr_DsRAddressToSitenamesW +*/ +static WERROR dcesrv_netr_DsRAddressToSitenamesW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsRAddressToSitenamesW *r) +{ + struct netr_DsRAddressToSitenamesExW r2; + struct netr_DsRAddressToSitenamesWCtr *ctr; + uint32_t i; + WERROR werr; + + ZERO_STRUCT(r2); + + r2.in.server_name = r->in.server_name; + r2.in.count = r->in.count; + r2.in.addresses = r->in.addresses; + + r2.out.ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesExWCtr *); + W_ERROR_HAVE_NO_MEMORY(r2.out.ctr); + + ctr = talloc(mem_ctx, struct netr_DsRAddressToSitenamesWCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + ctr->count = r->in.count; + ctr->sitename = talloc_array(ctr, struct lsa_String, ctr->count); + W_ERROR_HAVE_NO_MEMORY(ctr->sitename); + + werr = dcesrv_netr_DsRAddressToSitenamesExW(dce_call, mem_ctx, &r2); + + for (i=0; i<ctr->count; i++) { + ctr->sitename[i].string = (*r2.out.ctr)->sitename[i].string; + } + + return werr; +} + + +/* + netr_DsrGetDcSiteCoverageW +*/ +static WERROR dcesrv_netr_DsrGetDcSiteCoverageW(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsrGetDcSiteCoverageW *r) +{ + struct ldb_context *sam_ctx; + struct DcSitesCtr *ctr; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_DS_UNAVAILABLE; + } + + ctr = talloc(mem_ctx, struct DcSitesCtr); + W_ERROR_HAVE_NO_MEMORY(ctr); + + *r->out.ctr = ctr; + + /* For now only return our default site */ + ctr->num_sites = 1; + ctr->sites = talloc_array(ctr, struct lsa_String, ctr->num_sites); + W_ERROR_HAVE_NO_MEMORY(ctr->sites); + ctr->sites[0].string = samdb_server_site_name(sam_ctx, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(ctr->sites[0].string); + + return WERR_OK; +} + + +static WERROR fill_trusted_domains_array(TALLOC_CTX *mem_ctx, + struct ldb_context *sam_ctx, + struct netr_DomainTrustList *trusts, + uint32_t trust_flags) +{ + struct ldb_dn *system_dn; + struct ldb_message **dom_res = NULL; + const char *trust_attrs[] = { "flatname", "trustPartner", + "securityIdentifier", "trustDirection", + "trustType", "trustAttributes", NULL }; + uint32_t n; + int i; + int ret; + + if (!(trust_flags & (NETR_TRUST_FLAG_INBOUND | + NETR_TRUST_FLAG_OUTBOUND))) { + return WERR_INVALID_FLAGS; + } + + system_dn = samdb_system_container_dn(sam_ctx, mem_ctx); + if (system_dn == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + ret = gendb_search(sam_ctx, mem_ctx, system_dn, + &dom_res, trust_attrs, + "(objectclass=trustedDomain)"); + + for (i = 0; i < ret; i++) { + unsigned int trust_dir; + uint32_t flags = 0; + + trust_dir = ldb_msg_find_attr_as_uint(dom_res[i], + "trustDirection", 0); + + if (trust_dir & LSA_TRUST_DIRECTION_INBOUND) { + flags |= NETR_TRUST_FLAG_INBOUND; + } + if (trust_dir & LSA_TRUST_DIRECTION_OUTBOUND) { + flags |= NETR_TRUST_FLAG_OUTBOUND; + } + + if (!(flags & trust_flags)) { + /* this trust direction was not requested */ + continue; + } + + n = trusts->count; + trusts->array = talloc_realloc(trusts, trusts->array, + struct netr_DomainTrust, + n + 1); + W_ERROR_HAVE_NO_MEMORY(trusts->array); + + trusts->array[n].netbios_name = talloc_steal(trusts->array, ldb_msg_find_attr_as_string(dom_res[i], "flatname", NULL)); + if (!trusts->array[n].netbios_name) { + DEBUG(0, ("DB Error, TrustedDomain entry (%s) " + "without flatname\n", + ldb_dn_get_linearized(dom_res[i]->dn))); + } + + trusts->array[n].dns_name = talloc_steal(trusts->array, ldb_msg_find_attr_as_string(dom_res[i], "trustPartner", NULL)); + + trusts->array[n].trust_flags = flags; + if ((trust_flags & NETR_TRUST_FLAG_IN_FOREST) && + !(flags & NETR_TRUST_FLAG_TREEROOT)) { + /* TODO: find if we have parent in the list */ + trusts->array[n].parent_index = 0; + } + + trusts->array[n].trust_type = + ldb_msg_find_attr_as_uint(dom_res[i], + "trustType", 0); + trusts->array[n].trust_attributes = + ldb_msg_find_attr_as_uint(dom_res[i], + "trustAttributes", 0); + + if ((trusts->array[n].trust_type == LSA_TRUST_TYPE_MIT) || + (trusts->array[n].trust_type == LSA_TRUST_TYPE_DCE)) { + struct dom_sid zero_sid; + ZERO_STRUCT(zero_sid); + trusts->array[n].sid = + dom_sid_dup(trusts, &zero_sid); + } else { + trusts->array[n].sid = + samdb_result_dom_sid(trusts, dom_res[i], + "securityIdentifier"); + } + trusts->array[n].guid = GUID_zero(); + + trusts->count = n + 1; + } + + talloc_free(dom_res); + return WERR_OK; +} + +/* + netr_DsrEnumerateDomainTrusts +*/ +static WERROR dcesrv_netr_DsrEnumerateDomainTrusts(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsrEnumerateDomainTrusts *r) +{ + struct netr_DomainTrustList *trusts; + struct ldb_context *sam_ctx; + int ret; + struct ldb_message **dom_res; + const char * const dom_attrs[] = { "objectSid", "objectGUID", NULL }; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + const char *dnsdomain = lpcfg_dnsdomain(lp_ctx); + const char *p; + WERROR werr; + + if (r->in.trust_flags & 0xFFFFFE00) { + return WERR_INVALID_FLAGS; + } + + /* TODO: turn to hard check once we are sure this is 100% correct */ + if (!r->in.server_name) { + DEBUG(3, ("Invalid domain! Expected name in domain [%s]. " + "But received NULL!\n", dnsdomain)); + } else { + p = strchr(r->in.server_name, '.'); + if (!p) { + DEBUG(3, ("Invalid domain! Expected name in domain " + "[%s]. But received [%s]!\n", + dnsdomain, r->in.server_name)); + p = r->in.server_name; + } else { + p++; + } + if (strcasecmp(p, dnsdomain)) { + DEBUG(3, ("Invalid domain! Expected name in domain " + "[%s]. But received [%s]!\n", + dnsdomain, r->in.server_name)); + } + } + + trusts = talloc_zero(mem_ctx, struct netr_DomainTrustList); + W_ERROR_HAVE_NO_MEMORY(trusts); + + trusts->count = 0; + r->out.trusts = trusts; + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_GEN_FAILURE; + } + + if ((r->in.trust_flags & NETR_TRUST_FLAG_INBOUND) || + (r->in.trust_flags & NETR_TRUST_FLAG_OUTBOUND)) { + + werr = fill_trusted_domains_array(mem_ctx, sam_ctx, + trusts, r->in.trust_flags); + W_ERROR_NOT_OK_RETURN(werr); + } + + /* NOTE: we currently are always the root of the forest */ + if (r->in.trust_flags & NETR_TRUST_FLAG_IN_FOREST) { + uint32_t n = trusts->count; + + ret = gendb_search_dn(sam_ctx, mem_ctx, NULL, + &dom_res, dom_attrs); + if (ret != 1) { + return WERR_GEN_FAILURE; + } + + trusts->count = n + 1; + trusts->array = talloc_realloc(trusts, trusts->array, + struct netr_DomainTrust, + trusts->count); + W_ERROR_HAVE_NO_MEMORY(trusts->array); + + trusts->array[n].netbios_name = lpcfg_workgroup(lp_ctx); + trusts->array[n].dns_name = lpcfg_dnsdomain(lp_ctx); + trusts->array[n].trust_flags = + NETR_TRUST_FLAG_NATIVE | + NETR_TRUST_FLAG_TREEROOT | + NETR_TRUST_FLAG_IN_FOREST | + NETR_TRUST_FLAG_PRIMARY; + /* we are always the root domain for now */ + trusts->array[n].parent_index = 0; + trusts->array[n].trust_type = LSA_TRUST_TYPE_UPLEVEL; + trusts->array[n].trust_attributes = 0; + trusts->array[n].sid = samdb_result_dom_sid(mem_ctx, + dom_res[0], + "objectSid"); + trusts->array[n].guid = samdb_result_guid(dom_res[0], + "objectGUID"); + talloc_free(dom_res); + } + + return WERR_OK; +} + + +/* + netr_DsrDeregisterDNSHostRecords +*/ +static WERROR dcesrv_netr_DsrDeregisterDNSHostRecords(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_DsrDeregisterDNSHostRecords *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerGetTrustInfo *r); + +/* + netr_ServerTrustPasswordsGet +*/ +static NTSTATUS dcesrv_netr_ServerTrustPasswordsGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerTrustPasswordsGet *r) +{ + struct netr_ServerGetTrustInfo r2 = {}; + struct netr_TrustInfo *_ti = NULL; + NTSTATUS status; + + r2.in.server_name = r->in.server_name; + r2.in.account_name = r->in.account_name; + r2.in.secure_channel_type = r->in.secure_channel_type; + r2.in.computer_name = r->in.computer_name; + r2.in.credential = r->in.credential; + + r2.out.return_authenticator = r->out.return_authenticator; + r2.out.new_owf_password = r->out.new_owf_password; + r2.out.old_owf_password = r->out.old_owf_password; + r2.out.trust_info = &_ti; + + status = dcesrv_netr_ServerGetTrustInfo(dce_call, mem_ctx, &r2); + + r->out.return_authenticator = r2.out.return_authenticator; + r->out.new_owf_password = r2.out.new_owf_password; + r->out.old_owf_password = r2.out.old_owf_password; + + return status; +} + +/* + netr_DsRGetForestTrustInformation +*/ +struct dcesrv_netr_DsRGetForestTrustInformation_state { + struct dcesrv_call_state *dce_call; + TALLOC_CTX *mem_ctx; + struct netr_DsRGetForestTrustInformation *r; +}; + +static void dcesrv_netr_DsRGetForestTrustInformation_done(struct tevent_req *subreq); + +static WERROR dcesrv_netr_DsRGetForestTrustInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsRGetForestTrustInformation *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + enum security_user_level security_level; + struct ldb_context *sam_ctx = NULL; + struct dcesrv_netr_DsRGetForestTrustInformation_state *state = NULL; + struct dcerpc_binding_handle *irpc_handle = NULL; + struct tevent_req *subreq = NULL; + struct ldb_dn *domain_dn = NULL; + struct ldb_dn *forest_dn = NULL; + int cmp; + int forest_level; + + security_level = security_session_user_level(session_info, NULL); + if (security_level < SECURITY_USER) { + return WERR_ACCESS_DENIED; + } + + if (r->in.flags & 0xFFFFFFFE) { + return WERR_INVALID_FLAGS; + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return WERR_GEN_FAILURE; + } + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return WERR_GEN_FAILURE; + } + + forest_dn = ldb_get_root_basedn(sam_ctx); + if (forest_dn == NULL) { + return WERR_GEN_FAILURE; + } + + cmp = ldb_dn_compare(domain_dn, forest_dn); + if (cmp != 0) { + return WERR_NERR_ACFNOTLOADED; + } + + forest_level = dsdb_forest_functional_level(sam_ctx); + if (forest_level < DS_DOMAIN_FUNCTION_2003) { + return WERR_INVALID_FUNCTION; + } + + if (r->in.flags & DS_GFTI_UPDATE_TDO) { + if (!samdb_is_pdc(sam_ctx)) { + return WERR_NERR_NOTPRIMARY; + } + + if (r->in.trusted_domain_name == NULL) { + return WERR_INVALID_FLAGS; + } + } + + if (r->in.trusted_domain_name == NULL) { + NTSTATUS status; + + /* + * information about our own domain + */ + status = dsdb_trust_xref_forest_info(mem_ctx, sam_ctx, + r->out.forest_trust_info); + if (!NT_STATUS_IS_OK(status)) { + return ntstatus_to_werror(status); + } + + return WERR_OK; + } + + /* + * Forward the request to winbindd + */ + + state = talloc_zero(mem_ctx, + struct dcesrv_netr_DsRGetForestTrustInformation_state); + if (state == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call = dce_call; + state->mem_ctx = mem_ctx; + state->r = r; + + irpc_handle = irpc_binding_handle_by_name(state, + imsg_ctx, + "winbind_server", + &ndr_table_winbind); + if (irpc_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for winbind_server task\n")); + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return WERR_SERVICE_NOT_FOUND; + } + + /* + * 60 seconds timeout should be enough + */ + dcerpc_binding_handle_set_timeout(irpc_handle, 60); + + subreq = dcerpc_winbind_GetForestTrustInformation_send(state, + state->dce_call->event_ctx, + irpc_handle, + r->in.trusted_domain_name, + r->in.flags, + r->out.forest_trust_info); + if (subreq == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + state->dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + tevent_req_set_callback(subreq, + dcesrv_netr_DsRGetForestTrustInformation_done, + state); + + return WERR_OK; +} + +static void dcesrv_netr_DsRGetForestTrustInformation_done(struct tevent_req *subreq) +{ + struct dcesrv_netr_DsRGetForestTrustInformation_state *state = + tevent_req_callback_data(subreq, + struct dcesrv_netr_DsRGetForestTrustInformation_state); + NTSTATUS status; + + status = dcerpc_winbind_GetForestTrustInformation_recv(subreq, + state->mem_ctx, + &state->r->out.result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) { + state->r->out.result = WERR_TIMEOUT; + } else if (!NT_STATUS_IS_OK(status)) { + state->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + DEBUG(0,(__location__ ": IRPC callback failed %s\n", + nt_errstr(status))); + } + + status = dcesrv_reply(state->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_GetForestTrustInformation +*/ +static NTSTATUS dcesrv_netr_GetForestTrustInformation(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_GetForestTrustInformation *r) +{ + struct netlogon_creds_CredentialState *creds = NULL; + struct ldb_context *sam_ctx = NULL; + struct ldb_dn *domain_dn = NULL; + struct ldb_dn *forest_dn = NULL; + int cmp; + int forest_level; + NTSTATUS status; + + status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if ((creds->secure_channel_type != SEC_CHAN_DNS_DOMAIN) && + (creds->secure_channel_type != SEC_CHAN_DOMAIN)) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + /* TODO: check r->in.server_name is our name */ + + domain_dn = ldb_get_default_basedn(sam_ctx); + if (domain_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + forest_dn = ldb_get_root_basedn(sam_ctx); + if (forest_dn == NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + cmp = ldb_dn_compare(domain_dn, forest_dn); + if (cmp != 0) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + forest_level = dsdb_forest_functional_level(sam_ctx); + if (forest_level < DS_DOMAIN_FUNCTION_2003) { + return NT_STATUS_INVALID_DOMAIN_STATE; + } + + status = dsdb_trust_xref_forest_info(mem_ctx, sam_ctx, + r->out.forest_trust_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + + +/* + netr_ServerGetTrustInfo +*/ +static NTSTATUS dcesrv_netr_ServerGetTrustInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_ServerGetTrustInfo *r) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + struct netlogon_creds_CredentialState *creds = NULL; + struct ldb_context *sam_ctx = NULL; + const char * const attrs[] = { + "unicodePwd", + "sAMAccountName", + "userAccountControl", + NULL + }; + struct ldb_message **res = NULL; + struct samr_Password *curNtHash = NULL, *prevNtHash = NULL; + NTSTATUS nt_status; + int ret; + const char *asid = NULL; + uint32_t uac = 0; + const char *aname = NULL; + struct ldb_message *tdo_msg = NULL; + const char * const tdo_attrs[] = { + "trustAuthIncoming", + "trustAttributes", + NULL + }; + struct netr_TrustInfo *trust_info = NULL; + + ZERO_STRUCTP(r->out.new_owf_password); + ZERO_STRUCTP(r->out.old_owf_password); + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + /* TODO: check r->in.server_name is our name */ + + if (strcasecmp_m(r->in.account_name, creds->account_name) != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.secure_channel_type != creds->secure_channel_type) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcasecmp_m(r->in.computer_name, creds->computer_name) != 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + asid = ldap_encode_ndr_dom_sid(mem_ctx, creds->sid); + if (asid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + ret = gendb_search(sam_ctx, mem_ctx, NULL, &res, attrs, + "(&(objectClass=user)(objectSid=%s))", + asid); + if (ret != 1) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + switch (creds->secure_channel_type) { + case SEC_CHAN_DNS_DOMAIN: + case SEC_CHAN_DOMAIN: + uac = ldb_msg_find_attr_as_uint(res[0], "userAccountControl", 0); + + if (uac & UF_ACCOUNTDISABLE) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + if (!(uac & UF_INTERDOMAIN_TRUST_ACCOUNT)) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + aname = ldb_msg_find_attr_as_string(res[0], "sAMAccountName", NULL); + if (aname == NULL) { + return NT_STATUS_ACCOUNT_DISABLED; + } + + nt_status = dsdb_trust_search_tdo_by_type(sam_ctx, + SEC_CHAN_DOMAIN, aname, + tdo_attrs, mem_ctx, &tdo_msg); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OBJECT_NAME_NOT_FOUND)) { + return NT_STATUS_ACCOUNT_DISABLED; + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + nt_status = dsdb_trust_get_incoming_passwords(tdo_msg, mem_ctx, + &curNtHash, + &prevNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + trust_info = talloc_zero(mem_ctx, struct netr_TrustInfo); + if (trust_info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + trust_info->count = 1; + trust_info->data = talloc_array(trust_info, uint32_t, + trust_info->count); + if (trust_info->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + trust_info->data[0] = ldb_msg_find_attr_as_uint(tdo_msg, + "trustAttributes", + 0); + break; + + default: + nt_status = samdb_result_passwords_no_lockout(mem_ctx, lp_ctx, + res[0], + &curNtHash); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + prevNtHash = talloc(mem_ctx, struct samr_Password); + if (prevNtHash == NULL) { + return NT_STATUS_NO_MEMORY; + } + + E_md4hash("", prevNtHash->hash); + break; + } + + if (curNtHash != NULL) { + *r->out.new_owf_password = *curNtHash; + nt_status = netlogon_creds_des_encrypt(creds, r->out.new_owf_password); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + if (prevNtHash != NULL) { + *r->out.old_owf_password = *prevNtHash; + nt_status = netlogon_creds_des_encrypt(creds, r->out.old_owf_password); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + + if (trust_info != NULL) { + *r->out.trust_info = trust_info; + } + + return NT_STATUS_OK; +} + +/* + netr_Unused47 +*/ +static NTSTATUS dcesrv_netr_Unused47(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct netr_Unused47 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +struct netr_dnsupdate_RODC_state { + struct dcesrv_call_state *dce_call; + struct netr_DsrUpdateReadOnlyServerDnsRecords *r; + struct dnsupdate_RODC *r2; +}; + +/* + called when the forwarded RODC dns update request is finished + */ +static void netr_dnsupdate_RODC_callback(struct tevent_req *subreq) +{ + struct netr_dnsupdate_RODC_state *st = + tevent_req_callback_data(subreq, + struct netr_dnsupdate_RODC_state); + NTSTATUS status; + + status = dcerpc_dnsupdate_RODC_r_recv(subreq, st->dce_call); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": IRPC callback failed %s\n", nt_errstr(status))); + st->dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + } + + st->r->out.dns_names = talloc_steal(st->dce_call, st->r2->out.dns_names); + + status = dcesrv_reply(st->dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,(__location__ ": dcesrv_reply() failed - %s\n", nt_errstr(status))); + } +} + +/* + netr_DsrUpdateReadOnlyServerDnsRecords +*/ +static NTSTATUS dcesrv_netr_DsrUpdateReadOnlyServerDnsRecords(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct netr_DsrUpdateReadOnlyServerDnsRecords *r) +{ + struct netlogon_creds_CredentialState *creds; + NTSTATUS nt_status; + struct dcerpc_binding_handle *binding_handle; + struct netr_dnsupdate_RODC_state *st; + struct tevent_req *subreq; + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + nt_status = dcesrv_netr_creds_server_step_check(dce_call, + mem_ctx, + r->in.computer_name, + r->in.credential, + r->out.return_authenticator, + &creds); + NT_STATUS_NOT_OK_RETURN(nt_status); + + if (creds->secure_channel_type != SEC_CHAN_RODC) { + return NT_STATUS_ACCESS_DENIED; + } + + st = talloc_zero(mem_ctx, struct netr_dnsupdate_RODC_state); + NT_STATUS_HAVE_NO_MEMORY(st); + + st->dce_call = dce_call; + st->r = r; + st->r2 = talloc_zero(st, struct dnsupdate_RODC); + NT_STATUS_HAVE_NO_MEMORY(st->r2); + + st->r2->in.dom_sid = creds->sid; + st->r2->in.site_name = r->in.site_name; + st->r2->in.dns_ttl = r->in.dns_ttl; + st->r2->in.dns_names = r->in.dns_names; + st->r2->out.dns_names = r->out.dns_names; + + binding_handle = irpc_binding_handle_by_name(st, + imsg_ctx, + "dnsupdate", + &ndr_table_irpc); + if (binding_handle == NULL) { + DEBUG(0,("Failed to get binding_handle for dnsupdate task\n")); + dce_call->fault_code = DCERPC_FAULT_CANT_PERFORM; + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* forward the call */ + subreq = dcerpc_dnsupdate_RODC_r_send(st, dce_call->event_ctx, + binding_handle, st->r2); + NT_STATUS_HAVE_NO_MEMORY(subreq); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + + /* setup the callback */ + tevent_req_set_callback(subreq, netr_dnsupdate_RODC_callback, st); + + return NT_STATUS_OK; +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_netlogon_s.c" diff --git a/source4/rpc_server/remote/README b/source4/rpc_server/remote/README new file mode 100644 index 0000000..0d235a9 --- /dev/null +++ b/source4/rpc_server/remote/README @@ -0,0 +1,38 @@ +This is an RPC backend that implements all operations in terms of +remote RPC operations. This may be useful in certain debugging +situations, where the traffic is encrypted, or you wish to validate +that IDL is correct before implementing full test clients, or with +windows clients. + +There are two modes of operation: Password specified and delegated +credentials. + +Password specified: +------------------- + +This uses a static username/password in the config file, example: + +[global] + dcerpc endpoint servers = remote + dcerpc_remote:binding = ncacn_np:win2003 + dcerpc_remote:username = administrator + dcerpc_remote:password = PASSWORD + dcerpc_remote:interfaces = samr, lsarpc, netlogon + +Delegated credentials: +---------------------- + +If your incoming user is authenticated with Kerberos, and the machine +account for this Samba4 proxy server is 'trusted for delegation', then +the Samba4 proxy can forward the client's credentials to the target. + +You must be joined to the domain (net join <domain> member). + +To set 'trusted for delegation' with MMC, see the checkbox in the +Computer account property page under Users and Computers. + +[global] + dcerpc endpoint servers = remote + dcerpc_remote:binding = ncacn_np:win2003 + dcerpc_remote:interfaces = samr, lsarpc, netlogon + diff --git a/source4/rpc_server/remote/dcesrv_remote.c b/source4/rpc_server/remote/dcesrv_remote.c new file mode 100644 index 0000000..a57f2a7 --- /dev/null +++ b/source4/rpc_server/remote/dcesrv_remote.c @@ -0,0 +1,528 @@ +/* + Unix SMB/CIFS implementation. + remote dcerpc operations + + Copyright (C) Stefan (metze) Metzmacher 2004 + Copyright (C) Julien Kerihuel 2008-2009 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "rpc_server/dcerpc_server.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "librpc/rpc/dcerpc.h" +#include "librpc/ndr/ndr_table.h" +#include "param/param.h" + +NTSTATUS dcerpc_server_remote_init(TALLOC_CTX *ctx); + +#define DCESRV_REMOTE_ASSOC_MAGIC 0x782f50c4 +struct dcesrv_remote_assoc { + uint32_t assoc_group_id; +}; + +#define DCESRV_REMOTE_PRIVATE_MAGIC 0x7eceafa6 +struct dcesrv_remote_private { + struct dcerpc_pipe *c_pipe; +}; + +static NTSTATUS remote_op_reply(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + return NT_STATUS_OK; +} + +static NTSTATUS remote_op_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return NT_STATUS_OK; +} + +static NTSTATUS remote_get_private(struct dcesrv_call_state *dce_call, + struct dcesrv_remote_private **_priv) +{ + const struct ndr_interface_table *table = + (const struct ndr_interface_table *)dce_call->context->iface->private_data; + struct dcesrv_remote_private *priv = NULL; + struct dcesrv_remote_assoc *assoc = NULL; + const char *binding = NULL; + const char *user, *pass, *domain; + struct cli_credentials *credentials; + bool must_free_credentials = false; + bool machine_account; + bool allow_anonymous; + struct dcerpc_binding *b; + struct composite_context *pipe_conn_req; + uint32_t flags = 0; + NTSTATUS status; + + priv = dcesrv_iface_state_find_conn(dce_call, + DCESRV_REMOTE_PRIVATE_MAGIC, + struct dcesrv_remote_private); + if (priv != NULL) { + *_priv = priv; + return NT_STATUS_OK; + } + + priv = talloc_zero(dce_call, struct dcesrv_remote_private); + if (priv == NULL) { + return NT_STATUS_NO_MEMORY; + } + + assoc = dcesrv_iface_state_find_assoc(dce_call, + DCESRV_REMOTE_ASSOC_MAGIC, + struct dcesrv_remote_assoc); + if (assoc == NULL) { + assoc = talloc_zero(dce_call, struct dcesrv_remote_assoc); + if (assoc == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + binding = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, + NULL, + "dcerpc_remote", + "binding"); + if (binding == NULL) { + DEBUG(0,("You must specify a DCE/RPC binding string\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + user = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "dcerpc_remote", "user"); + pass = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "dcerpc_remote", "password"); + domain = lpcfg_parm_string(dce_call->conn->dce_ctx->lp_ctx, NULL, "dceprc_remote", "domain"); + + machine_account = lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, + NULL, + "dcerpc_remote", + "use_machine_account", + false); + allow_anonymous = lpcfg_parm_bool(dce_call->conn->dce_ctx->lp_ctx, + NULL, + "dcerpc_remote", + "allow_anonymous_fallback", + false); + + credentials = dcesrv_call_credentials(dce_call); + + if (user && pass) { + bool ok; + + DEBUG(5, ("dcerpc_remote: RPC Proxy: Using specified account\n")); + credentials = cli_credentials_init(priv); + if (!credentials) { + return NT_STATUS_NO_MEMORY; + } + must_free_credentials = true; + + ok = cli_credentials_set_conf(credentials, + dce_call->conn->dce_ctx->lp_ctx); + if (!ok) { + return NT_STATUS_INTERNAL_ERROR; + } + + cli_credentials_set_username(credentials, user, CRED_SPECIFIED); + if (domain) { + cli_credentials_set_domain(credentials, domain, CRED_SPECIFIED); + } + cli_credentials_set_password(credentials, pass, CRED_SPECIFIED); + } else if (machine_account) { + DEBUG(5, ("dcerpc_remote: RPC Proxy: Using machine account\n")); + credentials = cli_credentials_init_server( + priv, + dce_call->conn->dce_ctx->lp_ctx); + if (!credentials) { + return NT_STATUS_NO_MEMORY; + } + must_free_credentials = true; + } else if (credentials != NULL) { + DEBUG(5, ("dcerpc_remote: RPC Proxy: Using delegated credentials\n")); + } else if (allow_anonymous) { + DEBUG(5, ("dcerpc_remote: RPC Proxy: Using anonymous\n")); + credentials = cli_credentials_init_anon(priv); + if (!credentials) { + return NT_STATUS_NO_MEMORY; + } + must_free_credentials = true; + } else { + DEBUG(1,("dcerpc_remote: RPC Proxy: You must supply binding, user and password or have delegated credentials\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse binding string to the structure */ + status = dcerpc_parse_binding(priv, binding, &b); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to parse dcerpc binding '%s'\n", binding)); + return status; + } + + /* If we already have a remote association group ID, then use that */ + if (assoc->assoc_group_id != 0) { + status = dcerpc_binding_set_assoc_group_id(b, + assoc->assoc_group_id); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("dcerpc_binding_set_assoc_group_id() - %s'\n", + nt_errstr(status))); + return status; + } + } + + status = dcerpc_binding_set_abstract_syntax(b, &table->syntax_id); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("dcerpc_binding_set_abstract_syntax() - %s'\n", + nt_errstr(status))); + return status; + } + + if (dce_call->conn->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED) { + status = dcerpc_binding_set_flags(b, DCERPC_CONCURRENT_MULTIPLEX, 0); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("dcerpc_binding_set_flags(CONC_MPX) - %s'\n", + nt_errstr(status))); + return status; + } + } + + DEBUG(3, ("Using binding %s\n", dcerpc_binding_string(dce_call->context, b))); + + pipe_conn_req = dcerpc_pipe_connect_b_send(priv, b, table, + credentials, dce_call->event_ctx, dce_call->conn->dce_ctx->lp_ctx); + status = dcerpc_pipe_connect_b_recv(pipe_conn_req, priv, &(priv->c_pipe)); + + if (must_free_credentials) { + talloc_free(credentials); + } + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (dce_call->conn->state_flags & DCESRV_CALL_STATE_FLAG_MULTIPLEXED) { + flags = dcerpc_binding_get_flags(priv->c_pipe->binding); + if (!(flags & DCERPC_CONCURRENT_MULTIPLEX)) { + DEBUG(1,("dcerpc_remote: RPC Proxy: " + "Remote server doesn't support MPX\n")); + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + } + + if (assoc->assoc_group_id == 0) { + assoc->assoc_group_id = + dcerpc_binding_get_assoc_group_id(priv->c_pipe->binding); + if (assoc->assoc_group_id == 0) { + return NT_STATUS_INVALID_NETWORK_RESPONSE; + } + + status = dcesrv_iface_state_store_assoc(dce_call, + DCESRV_REMOTE_ASSOC_MAGIC, + assoc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + status = dcesrv_iface_state_store_conn(dce_call, + DCESRV_REMOTE_PRIVATE_MAGIC, + priv); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_priv = priv; + return NT_STATUS_OK; +} + +static NTSTATUS remote_op_ndr_pull(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_pull *pull, void **r) +{ + enum ndr_err_code ndr_err; + const struct ndr_interface_table *table = (const struct ndr_interface_table *)dce_call->context->iface->private_data; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + dce_call->fault_code = 0; + + if (opnum >= table->num_calls) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + + /* + * We don't have support for calls with pipes. + */ + if (table->calls[opnum].in_pipes.num_pipes != 0) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + if (table->calls[opnum].out_pipes.num_pipes != 0) { + dce_call->fault_code = DCERPC_FAULT_OP_RNG_ERROR; + return NT_STATUS_NET_WRITE_FAULT; + } + + *r = talloc_size(mem_ctx, table->calls[opnum].struct_size); + if (!*r) { + return NT_STATUS_NO_MEMORY; + } + + /* unravel the NDR for the packet */ + ndr_err = table->calls[opnum].ndr_pull(pull, NDR_IN, *r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +static void remote_op_dispatch_done(struct tevent_req *subreq); + +struct dcesrv_remote_call { + struct dcesrv_call_state *dce_call; + struct dcesrv_remote_private *priv; +}; + +static NTSTATUS remote_op_dispatch(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, void *r) +{ + struct dcesrv_remote_call *rcall = NULL; + struct dcesrv_remote_private *priv = NULL; + uint16_t opnum = dce_call->pkt.u.request.opnum; + const struct ndr_interface_table *table = dce_call->context->iface->private_data; + const struct ndr_interface_call *call; + const char *name; + struct tevent_req *subreq; + NTSTATUS status; + + name = table->calls[opnum].name; + call = &table->calls[opnum]; + + status = remote_get_private(dce_call, &priv); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcesrv_remote: call[%s] %s\n", name, nt_errstr(status))); + return status; + } + + rcall = talloc_zero(dce_call, struct dcesrv_remote_call); + if (rcall == NULL) { + return NT_STATUS_NO_MEMORY; + } + rcall->dce_call = dce_call; + rcall->priv = priv; + + if (priv->c_pipe->conn->flags & DCERPC_DEBUG_PRINT_IN) { + ndr_print_function_debug(call->ndr_print, name, NDR_IN | NDR_SET_VALUES, r); + } + + priv->c_pipe->conn->flags |= DCERPC_NDR_REF_ALLOC; + + /* we didn't use the return code of this function as we only check the last_fault_code */ + subreq = dcerpc_binding_handle_call_send(rcall, dce_call->event_ctx, + priv->c_pipe->binding_handle, + NULL, table, + opnum, mem_ctx, r); + if (subreq == NULL) { + DEBUG(0,("dcesrv_remote: call[%s] dcerpc_binding_handle_call_send() failed!\n", name)); + return NT_STATUS_NO_MEMORY; + } + tevent_req_set_callback(subreq, remote_op_dispatch_done, rcall); + + dce_call->state_flags |= DCESRV_CALL_STATE_FLAG_ASYNC; + return NT_STATUS_OK; +} + +static void remote_op_dispatch_done(struct tevent_req *subreq) +{ + struct dcesrv_remote_call *rcall = + tevent_req_callback_data(subreq, + struct dcesrv_remote_call); + struct dcesrv_call_state *dce_call = rcall->dce_call; + struct dcesrv_remote_private *priv = rcall->priv; + uint16_t opnum = dce_call->pkt.u.request.opnum; + const struct ndr_interface_table *table = dce_call->context->iface->private_data; + const struct ndr_interface_call *call; + const char *name; + NTSTATUS status; + + name = table->calls[opnum].name; + call = &table->calls[opnum]; + + /* we didn't use the return code of this function as we only check the last_fault_code */ + status = dcerpc_binding_handle_call_recv(subreq); + TALLOC_FREE(subreq); + + dce_call->fault_code = priv->c_pipe->last_fault_code; + if (dce_call->fault_code != 0) { + DEBUG(0,("dcesrv_remote: call[%s] failed with: %s!\n", + name, dcerpc_errstr(dce_call, dce_call->fault_code))); + goto reply; + } + + if (NT_STATUS_IS_OK(status) && + (priv->c_pipe->conn->flags & DCERPC_DEBUG_PRINT_OUT)) { + ndr_print_function_debug(call->ndr_print, name, NDR_OUT, dce_call->r); + } + +reply: + status = dcesrv_reply(dce_call); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("dcesrv_remote: call[%s]: dcesrv_reply() failed - %s\n", + name, nt_errstr(status))); + } +} + +static NTSTATUS remote_op_ndr_push(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct ndr_push *push, const void *r) +{ + enum ndr_err_code ndr_err; + const struct ndr_interface_table *table = dce_call->context->iface->private_data; + uint16_t opnum = dce_call->pkt.u.request.opnum; + + /* unravel the NDR for the packet */ + ndr_err = table->calls[opnum].ndr_push(push, NDR_OUT, r); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + dce_call->fault_code = DCERPC_FAULT_NDR; + return NT_STATUS_NET_WRITE_FAULT; + } + + return NT_STATUS_OK; +} + +static NTSTATUS remote_register_one_iface(struct dcesrv_context *dce_ctx, const struct dcesrv_interface *iface) +{ + unsigned int i; + const struct ndr_interface_table *table = iface->private_data; + + for (i=0;i<table->endpoints->count;i++) { + NTSTATUS ret; + const char *name = table->endpoints->names[i]; + + ret = dcesrv_interface_register(dce_ctx, name, NULL, iface, NULL); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(1,("remote_op_init_server: failed to register endpoint '%s'\n",name)); + return ret; + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS remote_op_init_server(struct dcesrv_context *dce_ctx, const struct dcesrv_endpoint_server *ep_server) +{ + unsigned int i; + char **ifaces = str_list_make(dce_ctx, lpcfg_parm_string(dce_ctx->lp_ctx, NULL, "dcerpc_remote", "interfaces"),NULL); + + if (!ifaces) { + DEBUG(3,("remote_op_init_server: no interfaces configured\n")); + return NT_STATUS_OK; + } + + for (i=0;ifaces[i];i++) { + NTSTATUS ret; + struct dcesrv_interface iface; + + if (!ep_server->interface_by_name(&iface, ifaces[i])) { + DEBUG(0,("remote_op_init_server: failed to find interface = '%s'\n", ifaces[i])); + talloc_free(ifaces); + return NT_STATUS_UNSUCCESSFUL; + } + + ret = remote_register_one_iface(dce_ctx, &iface); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("remote_op_init_server: failed to register interface = '%s'\n", ifaces[i])); + talloc_free(ifaces); + return ret; + } + } + + talloc_free(ifaces); + return NT_STATUS_OK; +} + +static NTSTATUS remote_op_shutdown_server(struct dcesrv_context *dce_ctx, + const struct dcesrv_endpoint_server *ep_server) +{ + return NT_STATUS_OK; +} + +static bool remote_fill_interface(struct dcesrv_interface *iface, const struct ndr_interface_table *if_tabl) +{ + iface->name = if_tabl->name; + iface->syntax_id = if_tabl->syntax_id; + + iface->bind = remote_op_bind; + iface->unbind = NULL; + + iface->ndr_pull = remote_op_ndr_pull; + iface->dispatch = remote_op_dispatch; + iface->reply = remote_op_reply; + iface->ndr_push = remote_op_ndr_push; + + iface->private_data = if_tabl; + iface->flags = 0; + + return true; +} + +static bool remote_op_interface_by_uuid(struct dcesrv_interface *iface, const struct GUID *uuid, uint32_t if_version) +{ + const struct ndr_interface_list *l; + + for (l=ndr_table_list();l;l=l->next) { + if (l->table->syntax_id.if_version == if_version && + GUID_equal(&l->table->syntax_id.uuid, uuid)==0) { + return remote_fill_interface(iface, l->table); + } + } + + return false; +} + +static bool remote_op_interface_by_name(struct dcesrv_interface *iface, const char *name) +{ + const struct ndr_interface_table *tbl = ndr_table_by_name(name); + + if (tbl) + return remote_fill_interface(iface, tbl); + + return false; +} + +NTSTATUS dcerpc_server_remote_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + static const struct dcesrv_endpoint_server ep_server = { + /* fill in our name */ + .name = "remote", + + .initialized = false, + + /* fill in all the operations */ + .init_server = remote_op_init_server, + .shutdown_server = remote_op_shutdown_server, + + .interface_by_uuid = remote_op_interface_by_uuid, + .interface_by_name = remote_op_interface_by_name + }; + + /* register ourselves with the DCERPC subsystem. */ + ret = dcerpc_register_ep_server(&ep_server); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register 'remote' endpoint server!\n")); + return ret; + } + + /* We need the full DCE/RPC interface table */ + ndr_table_init(); + + return ret; +} diff --git a/source4/rpc_server/samr/dcesrv_samr.c b/source4/rpc_server/samr/dcesrv_samr.c new file mode 100644 index 0000000..b1342cb --- /dev/null +++ b/source4/rpc_server/samr/dcesrv_samr.c @@ -0,0 +1,5365 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the samr pipe + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Volker Lendecke 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Matthias Dieter Wallnöfer 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_samr.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "rpc_server/samr/dcesrv_samr.h" +#include "system/time.h" +#include <ldb.h> +#include <ldb_errors.h> +#include "../libds/common/flags.h" +#include "dsdb/samdb/samdb.h" +#include "dsdb/common/util.h" +#include "libcli/ldap/ldap_ndr.h" +#include "libcli/security/security.h" +#include "rpc_server/samr/proto.h" +#include "../lib/util/util_ldb.h" +#include "param/param.h" +#include "lib/util/tsort.h" +#include "libds/common/flag_mapping.h" + +#undef strcasecmp + +#define DCESRV_INTERFACE_SAMR_BIND(context, iface) \ + dcesrv_interface_samr_bind(context, iface) +static NTSTATUS dcesrv_interface_samr_bind(struct dcesrv_connection_context *context, + const struct dcesrv_interface *iface) +{ + return dcesrv_interface_bind_reject_connect(context, iface); +} + +/* these query macros make samr_Query[User|Group|Alias]Info a bit easier to read */ + +#define QUERY_STRING(msg, field, attr) \ + info->field.string = ldb_msg_find_attr_as_string(msg, attr, ""); +#define QUERY_UINT(msg, field, attr) \ + info->field = ldb_msg_find_attr_as_uint(msg, attr, 0); +#define QUERY_RID(msg, field, attr) \ + info->field = samdb_result_rid_from_sid(mem_ctx, msg, attr, 0); +#define QUERY_UINT64(msg, field, attr) \ + info->field = ldb_msg_find_attr_as_uint64(msg, attr, 0); +#define QUERY_APASSC(msg, field, attr) \ + info->field = samdb_result_allow_password_change(sam_ctx, mem_ctx, \ + a_state->domain_state->domain_dn, msg, attr); +#define QUERY_BPWDCT(msg, field, attr) \ + info->field = samdb_result_effective_badPwdCount(sam_ctx, mem_ctx, \ + a_state->domain_state->domain_dn, msg); +#define QUERY_LHOURS(msg, field, attr) \ + info->field = samdb_result_logon_hours(mem_ctx, msg, attr); +#define QUERY_AFLAGS(msg, field, attr) \ + info->field = samdb_result_acct_flags(msg, attr); + + +/* these are used to make the Set[User|Group]Info code easier to follow */ + +#define SET_STRING(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (r->in.info->field.string == NULL) return NT_STATUS_INVALID_PARAMETER; \ + if (r->in.info->field.string[0] == '\0') { \ + if (ldb_msg_add_empty(msg, attr, LDB_FLAG_MOD_DELETE, NULL) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + } \ + if (ldb_msg_add_string(msg, attr, r->in.info->field.string) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_UINT(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_uint(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_INT64(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_int64(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_UINT64(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +/* Set account flags, discarding flags that cannot be set with SAMR */ +#define SET_AFLAGS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_acct_flags(sam_ctx, mem_ctx, msg, attr, r->in.info->field) != 0) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_LHOURS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (samdb_msg_add_logon_hours(sam_ctx, mem_ctx, msg, attr, &r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ +} while (0) + +#define SET_PARAMETERS(msg, field, attr) do { \ + struct ldb_message_element *set_el; \ + if (r->in.info->field.length != 0) { \ + if (samdb_msg_add_parameters(sam_ctx, mem_ctx, msg, attr, &r->in.info->field) != LDB_SUCCESS) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + set_el = ldb_msg_find_element(msg, attr); \ + set_el->flags = LDB_FLAG_MOD_REPLACE; \ + } \ +} while (0) + +/* + * Clear a GUID cache + */ +static void clear_guid_cache(struct samr_guid_cache *cache) +{ + cache->handle = 0; + cache->size = 0; + TALLOC_FREE(cache->entries); +} + +/* + * initialize a GUID cache + */ +static void initialize_guid_cache(struct samr_guid_cache *cache) +{ + cache->handle = 0; + cache->size = 0; + cache->entries = NULL; +} + +static NTSTATUS load_guid_cache( + struct samr_guid_cache *cache, + struct samr_domain_state *d_state, + unsigned int ldb_cnt, + struct ldb_message **res) +{ + NTSTATUS status = NT_STATUS_OK; + unsigned int i; + TALLOC_CTX *frame = talloc_stackframe(); + + clear_guid_cache(cache); + + /* + * Store the GUID's in the cache. + */ + cache->handle = 0; + cache->size = ldb_cnt; + cache->entries = talloc_array(d_state, struct GUID, ldb_cnt); + if (cache->entries == NULL) { + clear_guid_cache(cache); + status = NT_STATUS_NO_MEMORY; + goto exit; + } + + /* + * Extract a list of the GUIDs for all the matching objects + * we cache just the GUIDS to reduce the memory overhead of + * the result cache. + */ + for (i = 0; i < ldb_cnt; i++) { + cache->entries[i] = samdb_result_guid(res[i], "objectGUID"); + } +exit: + TALLOC_FREE(frame); + return status; +} + +/* + samr_Connect + + create a connection to the SAM database +*/ +static NTSTATUS dcesrv_samr_Connect(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect *r) +{ + struct samr_connect_state *c_state; + struct dcesrv_handle *handle; + + ZERO_STRUCTP(r->out.connect_handle); + + c_state = talloc(mem_ctx, struct samr_connect_state); + if (!c_state) { + return NT_STATUS_NO_MEMORY; + } + + /* make sure the sam database is accessible */ + c_state->sam_ctx = dcesrv_samdb_connect_as_user(c_state, dce_call); + if (c_state->sam_ctx == NULL) { + talloc_free(c_state); + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_CONNECT); + if (!handle) { + talloc_free(c_state); + return NT_STATUS_NO_MEMORY; + } + + handle->data = talloc_steal(handle, c_state); + + c_state->access_mask = r->in.access_mask; + *r->out.connect_handle = handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + samr_Close +*/ +static NTSTATUS dcesrv_samr_Close(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Close *r) +{ + struct dcesrv_handle *h; + + *r->out.handle = *r->in.handle; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + talloc_free(h); + + ZERO_STRUCTP(r->out.handle); + + return NT_STATUS_OK; +} + + +/* + samr_SetSecurity +*/ +static NTSTATUS dcesrv_samr_SetSecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetSecurity *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_QuerySecurity +*/ +static NTSTATUS dcesrv_samr_QuerySecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QuerySecurity *r) +{ + struct dcesrv_handle *h; + struct sec_desc_buf *sd; + + *r->out.sdbuf = NULL; + + DCESRV_PULL_HANDLE(h, r->in.handle, DCESRV_HANDLE_ANY); + + sd = talloc(mem_ctx, struct sec_desc_buf); + if (sd == NULL) { + return NT_STATUS_NO_MEMORY; + } + + sd->sd = samdb_default_security_descriptor(mem_ctx); + + *r->out.sdbuf = sd; + + return NT_STATUS_OK; +} + + +/* + samr_Shutdown + + we refuse this operation completely. If a admin wants to shutdown samr + in Samba then they should use the samba admin tools to disable the samr pipe +*/ +static NTSTATUS dcesrv_samr_Shutdown(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Shutdown *r) +{ + return NT_STATUS_ACCESS_DENIED; +} + + +/* + samr_LookupDomain + + this maps from a domain name to a SID +*/ +static NTSTATUS dcesrv_samr_LookupDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupDomain *r) +{ + struct samr_connect_state *c_state; + struct dcesrv_handle *h; + struct dom_sid *sid; + const char * const dom_attrs[] = { "objectSid", NULL}; + struct ldb_message **dom_msgs; + int ret; + + *r->out.sid = NULL; + + DCESRV_PULL_HANDLE(h, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + c_state = h->data; + + if (r->in.domain_name->string == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (strcasecmp(r->in.domain_name->string, "BUILTIN") == 0) { + ret = gendb_search(c_state->sam_ctx, + mem_ctx, NULL, &dom_msgs, dom_attrs, + "(objectClass=builtinDomain)"); + } else if (strcasecmp_m(r->in.domain_name->string, lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx)) == 0) { + ret = gendb_search_dn(c_state->sam_ctx, + mem_ctx, ldb_get_default_basedn(c_state->sam_ctx), + &dom_msgs, dom_attrs); + } else { + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret != 1) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + sid = samdb_result_dom_sid(mem_ctx, dom_msgs[0], + "objectSid"); + + if (sid == NULL) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + + *r->out.sid = sid; + + return NT_STATUS_OK; +} + + +/* + samr_EnumDomains + + list the domains in the SAM +*/ +static NTSTATUS dcesrv_samr_EnumDomains(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomains *r) +{ + struct dcesrv_handle *h; + struct samr_SamArray *array; + uint32_t i, start_i; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + *r->out.resume_handle = 2; + + start_i = *r->in.resume_handle; + + if (start_i >= 2) { + /* search past end of list is not an error for this call */ + return NT_STATUS_OK; + } + + array = talloc(mem_ctx, struct samr_SamArray); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->count = 0; + array->entries = NULL; + + array->entries = talloc_array(mem_ctx, struct samr_SamEntry, 2 - start_i); + if (array->entries == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0;i<2-start_i;i++) { + array->entries[i].idx = start_i + i; + if (i == 0) { + array->entries[i].name.string = lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx); + } else { + array->entries[i].name.string = "BUILTIN"; + } + } + + *r->out.sam = array; + *r->out.num_entries = i; + array->count = *r->out.num_entries; + + return NT_STATUS_OK; +} + + +/* + samr_OpenDomain +*/ +static NTSTATUS dcesrv_samr_OpenDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenDomain *r) +{ + struct dcesrv_handle *h_conn, *h_domain; + struct samr_connect_state *c_state; + struct samr_domain_state *d_state; + const char * const dom_attrs[] = { "cn", NULL}; + struct ldb_message **dom_msgs; + int ret; + unsigned int i; + + ZERO_STRUCTP(r->out.domain_handle); + + DCESRV_PULL_HANDLE(h_conn, r->in.connect_handle, SAMR_HANDLE_CONNECT); + + c_state = h_conn->data; + + if (r->in.sid == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + d_state = talloc(mem_ctx, struct samr_domain_state); + if (!d_state) { + return NT_STATUS_NO_MEMORY; + } + + d_state->domain_sid = talloc_steal(d_state, r->in.sid); + + if (dom_sid_equal(d_state->domain_sid, dom_sid_parse_talloc(mem_ctx, SID_BUILTIN))) { + d_state->builtin = true; + d_state->domain_name = "BUILTIN"; + } else { + d_state->builtin = false; + d_state->domain_name = lpcfg_sam_name(dce_call->conn->dce_ctx->lp_ctx); + } + + ret = gendb_search(c_state->sam_ctx, + mem_ctx, ldb_get_default_basedn(c_state->sam_ctx), &dom_msgs, dom_attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + + if (ret == 0) { + talloc_free(d_state); + return NT_STATUS_NO_SUCH_DOMAIN; + } else if (ret > 1) { + talloc_free(d_state); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (ret == -1) { + talloc_free(d_state); + DEBUG(1, ("Failed to open domain %s: %s\n", dom_sid_string(mem_ctx, r->in.sid), ldb_errstring(c_state->sam_ctx))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + d_state->domain_dn = talloc_steal(d_state, dom_msgs[0]->dn); + d_state->role = lpcfg_server_role(dce_call->conn->dce_ctx->lp_ctx); + d_state->connect_state = talloc_reference(d_state, c_state); + d_state->sam_ctx = c_state->sam_ctx; + d_state->access_mask = r->in.access_mask; + d_state->domain_users_cached = NULL; + + d_state->lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + + for (i = 0; i < SAMR_LAST_CACHE; i++) { + initialize_guid_cache(&d_state->guid_caches[i]); + } + + h_domain = dcesrv_handle_create(dce_call, SAMR_HANDLE_DOMAIN); + if (!h_domain) { + talloc_free(d_state); + return NT_STATUS_NO_MEMORY; + } + + h_domain->data = talloc_steal(h_domain, d_state); + + *r->out.domain_handle = h_domain->wire_handle; + + return NT_STATUS_OK; +} + +/* + return DomInfo1 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo1(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo1 *info) +{ + info->min_password_length = + ldb_msg_find_attr_as_uint(dom_msgs[0], "minPwdLength", 0); + info->password_history_length = + ldb_msg_find_attr_as_uint(dom_msgs[0], "pwdHistoryLength", 0); + info->password_properties = + ldb_msg_find_attr_as_uint(dom_msgs[0], "pwdProperties", 0); + info->max_password_age = + ldb_msg_find_attr_as_int64(dom_msgs[0], "maxPwdAge", 0); + info->min_password_age = + ldb_msg_find_attr_as_int64(dom_msgs[0], "minPwdAge", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo2 +*/ +static NTSTATUS dcesrv_samr_info_DomGeneralInformation(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomGeneralInformation *info) +{ + size_t count = 0; + const enum ldb_scope scope = LDB_SCOPE_SUBTREE; + int ret = 0; + + /* MS-SAMR 2.2.4.1 - ReplicaSourceNodeName: "domainReplica" attribute */ + info->primary.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "domainReplica", + ""); + + info->force_logoff_time = ldb_msg_find_attr_as_uint64(dom_msgs[0], "forceLogoff", + 0x8000000000000000LL); + + info->oem_information.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "oEMInformation", + ""); + info->domain_name.string = state->domain_name; + + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + 0); + switch (state->role) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* This pulls the NetBIOS name from the + cn=NTDS Settings,cn=<NETBIOS name of PDC>,.... + string */ + if (samdb_is_pdc(state->sam_ctx)) { + info->role = SAMR_ROLE_DOMAIN_PDC; + } else { + info->role = SAMR_ROLE_DOMAIN_BDC; + } + break; + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + case ROLE_AUTO: + return NT_STATUS_INTERNAL_ERROR; + case ROLE_DOMAIN_MEMBER: + info->role = SAMR_ROLE_DOMAIN_MEMBER; + break; + case ROLE_STANDALONE: + info->role = SAMR_ROLE_STANDALONE; + break; + } + + /* + * Users are not meant to be in BUILTIN + * so to speed up the query we do not filter on domain_sid + */ + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + NULL, + scope, + "(objectClass=user)"); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_users = count; + + /* + * Groups are not meant to be in BUILTIN + * so to speed up the query we do not filter on domain_sid + */ + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + NULL, + scope, + "(&(objectClass=group)(|(groupType=%d)(groupType=%d)))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_groups = count; + + ret = dsdb_domain_count( + state->sam_ctx, + &count, + state->domain_dn, + state->domain_sid, + scope, + "(&(objectClass=group)(|(groupType=%d)(groupType=%d)))", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ret != LDB_SUCCESS || count > UINT32_MAX) { + goto error; + } + info->num_aliases = count; + + return NT_STATUS_OK; + +error: + if (count > UINT32_MAX) { + return NT_STATUS_INTEGER_OVERFLOW; + } + return dsdb_ldb_err_to_ntstatus(ret); + +} + +/* + return DomInfo3 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo3(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo3 *info) +{ + info->force_logoff_time = ldb_msg_find_attr_as_uint64(dom_msgs[0], "forceLogoff", + 0x8000000000000000LL); + + return NT_STATUS_OK; +} + +/* + return DomInfo4 +*/ +static NTSTATUS dcesrv_samr_info_DomOEMInformation(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomOEMInformation *info) +{ + info->oem_information.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "oEMInformation", + ""); + + return NT_STATUS_OK; +} + +/* + return DomInfo5 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo5(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo5 *info) +{ + info->domain_name.string = state->domain_name; + + return NT_STATUS_OK; +} + +/* + return DomInfo6 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo6(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo6 *info) +{ + /* MS-SAMR 2.2.4.1 - ReplicaSourceNodeName: "domainReplica" attribute */ + info->primary.string = ldb_msg_find_attr_as_string(dom_msgs[0], + "domainReplica", + ""); + + return NT_STATUS_OK; +} + +/* + return DomInfo7 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo7(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo7 *info) +{ + + switch (state->role) { + case ROLE_ACTIVE_DIRECTORY_DC: + /* This pulls the NetBIOS name from the + cn=NTDS Settings,cn=<NETBIOS name of PDC>,.... + string */ + if (samdb_is_pdc(state->sam_ctx)) { + info->role = SAMR_ROLE_DOMAIN_PDC; + } else { + info->role = SAMR_ROLE_DOMAIN_BDC; + } + break; + case ROLE_DOMAIN_PDC: + case ROLE_DOMAIN_BDC: + case ROLE_IPA_DC: + case ROLE_AUTO: + return NT_STATUS_INTERNAL_ERROR; + case ROLE_DOMAIN_MEMBER: + info->role = SAMR_ROLE_DOMAIN_MEMBER; + break; + case ROLE_STANDALONE: + info->role = SAMR_ROLE_STANDALONE; + break; + } + + return NT_STATUS_OK; +} + +/* + return DomInfo8 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo8(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo8 *info) +{ + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + time(NULL)); + + info->domain_create_time = ldb_msg_find_attr_as_uint(dom_msgs[0], "creationTime", + 0x0LL); + + return NT_STATUS_OK; +} + +/* + return DomInfo9 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo9(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo9 *info) +{ + info->domain_server_state = DOMAIN_SERVER_ENABLED; + + return NT_STATUS_OK; +} + +/* + return DomInfo11 +*/ +static NTSTATUS dcesrv_samr_info_DomGeneralInformation2(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomGeneralInformation2 *info) +{ + NTSTATUS status; + status = dcesrv_samr_info_DomGeneralInformation(state, mem_ctx, dom_msgs, &info->general); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + info->lockout_duration = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutDuration", + -18000000000LL); + info->lockout_window = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockOutObservationWindow", + -18000000000LL); + info->lockout_threshold = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutThreshold", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo12 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo12(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo12 *info) +{ + info->lockout_duration = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutDuration", + -18000000000LL); + info->lockout_window = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockOutObservationWindow", + -18000000000LL); + info->lockout_threshold = ldb_msg_find_attr_as_int64(dom_msgs[0], "lockoutThreshold", 0); + + return NT_STATUS_OK; +} + +/* + return DomInfo13 +*/ +static NTSTATUS dcesrv_samr_info_DomInfo13(struct samr_domain_state *state, + TALLOC_CTX *mem_ctx, + struct ldb_message **dom_msgs, + struct samr_DomInfo13 *info) +{ + info->sequence_num = ldb_msg_find_attr_as_uint64(dom_msgs[0], "modifiedCount", + time(NULL)); + + info->domain_create_time = ldb_msg_find_attr_as_uint(dom_msgs[0], "creationTime", + 0x0LL); + + info->modified_count_at_last_promotion = 0; + + return NT_STATUS_OK; +} + +/* + samr_QueryDomainInfo +*/ +static NTSTATUS dcesrv_samr_QueryDomainInfo(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_QueryDomainInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + union samr_DomainInfo *info; + + struct ldb_message **dom_msgs; + const char * const *attrs = NULL; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + switch (r->in.level) { + case 1: + { + static const char * const attrs2[] = { "minPwdLength", + "pwdHistoryLength", + "pwdProperties", + "maxPwdAge", + "minPwdAge", + NULL }; + attrs = attrs2; + break; + } + case 2: + { + static const char * const attrs2[] = {"forceLogoff", + "oEMInformation", + "modifiedCount", + "domainReplica", + NULL}; + attrs = attrs2; + break; + } + case 3: + { + static const char * const attrs2[] = {"forceLogoff", + NULL}; + attrs = attrs2; + break; + } + case 4: + { + static const char * const attrs2[] = {"oEMInformation", + NULL}; + attrs = attrs2; + break; + } + case 5: + { + attrs = NULL; + break; + } + case 6: + { + static const char * const attrs2[] = { "domainReplica", + NULL }; + attrs = attrs2; + break; + } + case 7: + { + attrs = NULL; + break; + } + case 8: + { + static const char * const attrs2[] = { "modifiedCount", + "creationTime", + NULL }; + attrs = attrs2; + break; + } + case 9: + { + attrs = NULL; + break; + } + case 11: + { + static const char * const attrs2[] = { "oEMInformation", + "forceLogoff", + "modifiedCount", + "lockoutDuration", + "lockOutObservationWindow", + "lockoutThreshold", + NULL}; + attrs = attrs2; + break; + } + case 12: + { + static const char * const attrs2[] = { "lockoutDuration", + "lockOutObservationWindow", + "lockoutThreshold", + NULL}; + attrs = attrs2; + break; + } + case 13: + { + static const char * const attrs2[] = { "modifiedCount", + "creationTime", + NULL }; + attrs = attrs2; + break; + } + default: + { + return NT_STATUS_INVALID_INFO_CLASS; + } + } + + /* some levels don't need a search */ + if (attrs) { + int ret; + ret = gendb_search_dn(d_state->sam_ctx, mem_ctx, + d_state->domain_dn, &dom_msgs, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_DomainInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + *r->out.info = info; + + switch (r->in.level) { + case 1: + return dcesrv_samr_info_DomInfo1(d_state, mem_ctx, dom_msgs, + &info->info1); + case 2: + return dcesrv_samr_info_DomGeneralInformation(d_state, mem_ctx, dom_msgs, + &info->general); + case 3: + return dcesrv_samr_info_DomInfo3(d_state, mem_ctx, dom_msgs, + &info->info3); + case 4: + return dcesrv_samr_info_DomOEMInformation(d_state, mem_ctx, dom_msgs, + &info->oem); + case 5: + return dcesrv_samr_info_DomInfo5(d_state, mem_ctx, dom_msgs, + &info->info5); + case 6: + return dcesrv_samr_info_DomInfo6(d_state, mem_ctx, dom_msgs, + &info->info6); + case 7: + return dcesrv_samr_info_DomInfo7(d_state, mem_ctx, dom_msgs, + &info->info7); + case 8: + return dcesrv_samr_info_DomInfo8(d_state, mem_ctx, dom_msgs, + &info->info8); + case 9: + return dcesrv_samr_info_DomInfo9(d_state, mem_ctx, dom_msgs, + &info->info9); + case 11: + return dcesrv_samr_info_DomGeneralInformation2(d_state, mem_ctx, dom_msgs, + &info->general2); + case 12: + return dcesrv_samr_info_DomInfo12(d_state, mem_ctx, dom_msgs, + &info->info12); + case 13: + return dcesrv_samr_info_DomInfo13(d_state, mem_ctx, dom_msgs, + &info->info13); + default: + return NT_STATUS_INVALID_INFO_CLASS; + } +} + + +/* + samr_SetDomainInfo +*/ +static NTSTATUS dcesrv_samr_SetDomainInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetDomainInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message *msg; + int ret; + struct ldb_context *sam_ctx; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + sam_ctx = d_state->sam_ctx; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_reference(mem_ctx, d_state->domain_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case 1: + SET_UINT (msg, info1.min_password_length, "minPwdLength"); + SET_UINT (msg, info1.password_history_length, "pwdHistoryLength"); + SET_UINT (msg, info1.password_properties, "pwdProperties"); + SET_INT64 (msg, info1.max_password_age, "maxPwdAge"); + SET_INT64 (msg, info1.min_password_age, "minPwdAge"); + break; + case 3: + SET_UINT64 (msg, info3.force_logoff_time, "forceLogoff"); + break; + case 4: + SET_STRING(msg, oem.oem_information, "oEMInformation"); + break; + + case 6: + case 7: + case 9: + /* No op, we don't know where to set these */ + return NT_STATUS_OK; + + case 12: + /* + * It is not possible to set lockout_duration < lockout_window. + * (The test is the other way around since the negative numbers + * are stored...) + * + * TODO: + * This check should be moved to the backend, i.e. to some + * ldb module under dsdb/samdb/ldb_modules/ . + * + * This constraint is documented here for the samr rpc service: + * MS-SAMR 3.1.1.6 Attribute Constraints for Originating Updates + * http://msdn.microsoft.com/en-us/library/cc245667%28PROT.10%29.aspx + * + * And here for the ldap backend: + * MS-ADTS 3.1.1.5.3.2 Constraints + * http://msdn.microsoft.com/en-us/library/cc223462(PROT.10).aspx + */ + if (r->in.info->info12.lockout_duration > + r->in.info->info12.lockout_window) + { + return NT_STATUS_INVALID_PARAMETER; + } + SET_INT64 (msg, info12.lockout_duration, "lockoutDuration"); + SET_INT64 (msg, info12.lockout_window, "lockOutObservationWindow"); + SET_INT64 (msg, info12.lockout_threshold, "lockoutThreshold"); + break; + + default: + /* many info classes are not valid for SetDomainInfo */ + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(sam_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to modify record %s: %s\n", + ldb_dn_get_linearized(d_state->domain_dn), + ldb_errstring(sam_ctx))); + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + +/* + samr_CreateDomainGroup +*/ +static NTSTATUS dcesrv_samr_CreateDomainGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateDomainGroup *r) +{ + NTSTATUS status; + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *groupname; + struct dom_sid *group_sid; + struct ldb_dn *group_dn; + struct dcesrv_handle *g_handle; + + ZERO_STRUCTP(r->out.group_handle); + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a domain group in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } + + groupname = r->in.name->string; + + if (groupname == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_domain_group(d_state->sam_ctx, mem_ctx, groupname, &group_sid, &group_dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, group_dn); + + a_state->account_name = talloc_steal(a_state, groupname); + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_GROUP); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.group_handle = g_handle->wire_handle; + *r->out.rid = group_sid->sub_auths[group_sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + comparison function for sorting SamEntry array +*/ +static int compare_SamEntry(struct samr_SamEntry *e1, struct samr_SamEntry *e2) +{ + return e1->idx - e2->idx; +} + +static int compare_msgRid(struct ldb_message **m1, struct ldb_message **m2) { + struct dom_sid *sid1 = NULL; + struct dom_sid *sid2 = NULL; + uint32_t rid1; + uint32_t rid2; + int res = 0; + NTSTATUS status; + TALLOC_CTX *frame = talloc_stackframe(); + + sid1 = samdb_result_dom_sid(frame, *m1, "objectSid"); + sid2 = samdb_result_dom_sid(frame, *m2, "objectSid"); + + /* + * If entries don't have a SID we want to sort them to the end of + * the list. + */ + if (sid1 == NULL && sid2 == NULL) { + res = 0; + goto exit; + } else if (sid2 == NULL) { + res = 1; + goto exit; + } else if (sid1 == NULL) { + res = -1; + goto exit; + } + + /* + * Get and compare the rids, if we fail to extract a rid treat it as a + * missing SID and sort to the end of the list + */ + status = dom_sid_split_rid(NULL, sid1, NULL, &rid1); + if (!NT_STATUS_IS_OK(status)) { + res = 1; + goto exit; + } + + status = dom_sid_split_rid(NULL, sid2, NULL, &rid2); + if (!NT_STATUS_IS_OK(status)) { + res = -1; + goto exit; + } + + if (rid1 == rid2) { + res = 0; + } + else if (rid1 > rid2) { + res = 1; + } + else { + res = -1; + } +exit: + TALLOC_FREE(frame); + return res; +} + +/* + samr_EnumDomainGroups +*/ +static NTSTATUS dcesrv_samr_EnumDomainGroups(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomainGroups *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message **res; + uint32_t i; + uint32_t count; + uint32_t results; + uint32_t max_entries; + uint32_t remaining_entries; + uint32_t resume_handle; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", NULL }; + const char * const cache_attrs[] = { "objectSid", "objectGUID", NULL }; + struct samr_SamArray *sam; + struct samr_guid_cache *cache = NULL; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + cache = &d_state->guid_caches[SAMR_ENUM_DOMAIN_GROUPS_CACHE]; + + /* + * If the resume_handle is zero, query the database and cache the + * matching GUID's + */ + if (*r->in.resume_handle == 0) { + NTSTATUS status; + int ldb_cnt; + clear_guid_cache(cache); + /* + * search for all domain groups in this domain. + */ + ldb_cnt = samdb_search_domain( + d_state->sam_ctx, + mem_ctx, + d_state->domain_dn, + &res, + cache_attrs, + d_state->domain_sid, + "(&(|(groupType=%d)(groupType=%d))(objectClass=group))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (ldb_cnt < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + /* + * Sort the results into RID order, while the spec states there + * is no order, Windows appears to sort the results by RID and + * so it is possible that there are clients that depend on + * this ordering + */ + TYPESAFE_QSORT(res, ldb_cnt, compare_msgRid); + + /* + * cache the sorted GUID's + */ + status = load_guid_cache(cache, d_state, ldb_cnt, res); + TALLOC_FREE(res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + cache->handle = 0; + } + + + /* + * If the resume handle is out of range we return an empty response + * and invalidate the cache. + * + * From the specification: + * Servers SHOULD validate that EnumerationContext is an expected + * value for the server's implementation. Windows does NOT validate + * the input, though the result of malformed information merely results + * in inconsistent output to the client. + */ + if (*r->in.resume_handle >= cache->size) { + clear_guid_cache(cache); + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + sam->entries = NULL; + sam->count = 0; + + *r->out.sam = sam; + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + + + /* + * Calculate the number of entries to return limit by max_size. + * Note that we use the w2k3 element size value of 54 + */ + max_entries = 1 + (r->in.max_size/SAMR_ENUM_USERS_MULTIPLIER); + remaining_entries = cache->size - *r->in.resume_handle; + results = MIN(remaining_entries, max_entries); + + /* + * Process the list of result GUID's. + * Read the details of each object and populate the Entries + * for the current level. + */ + count = 0; + resume_handle = *r->in.resume_handle; + entries = talloc_array(mem_ctx, struct samr_SamEntry, results); + if (entries == NULL) { + clear_guid_cache(cache); + return NT_STATUS_NO_MEMORY; + } + for (i = 0; i < results; i++) { + struct dom_sid *objectsid; + uint32_t rid; + struct ldb_result *rec; + const uint32_t idx = *r->in.resume_handle + i; + int ret; + NTSTATUS status; + const char *name = NULL; + resume_handle++; + /* + * Read an object from disk using the GUID as the key + * + * If the object can not be read, or it does not have a SID + * it is ignored. + * + * As a consequence of this, if all the remaining GUID's + * have been deleted an empty result will be returned. + * i.e. even if the previous call returned a non zero + * resume_handle it is possible for no results to be returned. + * + */ + ret = dsdb_search_by_dn_guid(d_state->sam_ctx, + mem_ctx, + &rec, + &cache->entries[idx], + attrs, + 0); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } else if (ret != LDB_SUCCESS) { + clear_guid_cache(cache); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + objectsid = samdb_result_dom_sid(mem_ctx, + rec->msgs[0], + "objectSID"); + if (objectsid == NULL) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID for GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + status = dom_sid_split_rid(NULL, + objectsid, + NULL, + &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID [%s] for GUID [%s] invalid\n", + dom_sid_str_buf(objectsid, &sid_buf), + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + + entries[count].idx = rid; + name = ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entries[count].name.string = talloc_strdup(entries, name); + count++; + } + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + clear_guid_cache(cache); + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries; + sam->count = count; + + *r->out.sam = sam; + *r->out.resume_handle = resume_handle; + *r->out.num_entries = count; + + /* + * Signal no more results by returning zero resume handle, + * the cache is also cleared at this point + */ + if (*r->out.resume_handle >= cache->size) { + *r->out.resume_handle = 0; + clear_guid_cache(cache); + return NT_STATUS_OK; + } + /* + * There are more results to be returned. + */ + return STATUS_MORE_ENTRIES; +} + + +/* + samr_CreateUser2 + + This call uses transactions to ensure we don't get a new conflicting + user while we are processing this, and to ensure the user either + completly exists, or does not. +*/ +static NTSTATUS dcesrv_samr_CreateUser2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateUser2 *r) +{ + NTSTATUS status; + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + struct ldb_dn *dn; + struct dom_sid *sid; + struct dcesrv_handle *u_handle; + const char *account_name; + + ZERO_STRUCTP(r->out.user_handle); + *r->out.access_granted = 0; + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a user in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } else if (r->in.acct_flags == ACB_DOMTRUST) { + /* Domain trust accounts must be created by the LSA calls */ + return NT_STATUS_ACCESS_DENIED; + } + account_name = r->in.account_name->string; + + if (account_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_user(d_state->sam_ctx, mem_ctx, account_name, r->in.acct_flags, NULL, + &sid, &dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, dn); + + a_state->account_name = talloc_steal(a_state, account_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + u_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_USER); + if (!u_handle) { + return NT_STATUS_NO_MEMORY; + } + + u_handle->data = talloc_steal(u_handle, a_state); + + *r->out.user_handle = u_handle->wire_handle; + *r->out.access_granted = 0xf07ff; /* TODO: fix access mask calculations */ + + *r->out.rid = sid->sub_auths[sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + samr_CreateUser +*/ +static NTSTATUS dcesrv_samr_CreateUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateUser *r) +{ + struct samr_CreateUser2 r2; + uint32_t access_granted = 0; + + + /* a simple wrapper around samr_CreateUser2 works nicely */ + + r2 = (struct samr_CreateUser2) { + .in.domain_handle = r->in.domain_handle, + .in.account_name = r->in.account_name, + .in.acct_flags = ACB_NORMAL, + .in.access_mask = r->in.access_mask, + .out.user_handle = r->out.user_handle, + .out.access_granted = &access_granted, + .out.rid = r->out.rid + }; + + return dcesrv_samr_CreateUser2(dce_call, mem_ctx, &r2); +} + +struct enum_dom_users_ctx { + struct samr_SamEntry *entries; + uint32_t num_entries; + uint32_t acct_flags; + struct dom_sid *domain_sid; +}; + +static int user_iterate_callback(struct ldb_request *req, + struct ldb_reply *ares); + +/* + * Iterate users and add all those that match a domain SID and pass an acct + * flags check to an array of SamEntry objects. + */ +static int user_iterate_callback(struct ldb_request *req, + struct ldb_reply *ares) +{ + struct enum_dom_users_ctx *ac =\ + talloc_get_type(req->context, struct enum_dom_users_ctx); + int ret = LDB_ERR_OPERATIONS_ERROR; + + if (!ares) { + return ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + } + if (ares->error != LDB_SUCCESS) { + return ldb_request_done(req, ares->error); + } + + switch (ares->type) { + case LDB_REPLY_ENTRY: + { + struct ldb_message *msg = ares->message; + const struct ldb_val *val; + struct samr_SamEntry *ent; + struct dom_sid objectsid; + uint32_t rid; + size_t entries_array_len = 0; + NTSTATUS status; + ssize_t sid_size; + + if (ac->acct_flags && ((samdb_result_acct_flags(msg, NULL) & + ac->acct_flags) == 0)) { + ret = LDB_SUCCESS; + break; + } + + val = ldb_msg_find_ldb_val(msg, "objectSID"); + if (val == NULL) { + DBG_WARNING("objectSID for DN %s not found\n", + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + sid_size = sid_parse(val->data, val->length, &objectsid); + if (sid_size == -1) { + struct dom_sid_buf sid_buf; + DBG_WARNING("objectsid [%s] for DN [%s] invalid\n", + dom_sid_str_buf(&objectsid, &sid_buf), + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + if (!dom_sid_in_domain(ac->domain_sid, &objectsid)) { + /* Ignore if user isn't in the domain */ + ret = LDB_SUCCESS; + break; + } + + status = dom_sid_split_rid(ares, &objectsid, NULL, &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + DBG_WARNING("Couldn't split RID from " + "SID [%s] of DN [%s]\n", + dom_sid_str_buf(&objectsid, &sid_buf), + ldb_dn_get_linearized(msg->dn)); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + + entries_array_len = talloc_array_length(ac->entries); + if (ac->num_entries >= entries_array_len) { + if (entries_array_len * 2 < entries_array_len) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + ac->entries = talloc_realloc(ac, + ac->entries, + struct samr_SamEntry, + entries_array_len * 2); + if (ac->entries == NULL) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + } + + ent = &(ac->entries[ac->num_entries++]); + val = ldb_msg_find_ldb_val(msg, "samaccountname"); + if (val == NULL) { + DBG_WARNING("samaccountname attribute not found\n"); + ret = ldb_request_done(req, LDB_ERR_OPERATIONS_ERROR); + break; + } + ent->name.string = talloc_steal(ac->entries, + (char *)val->data); + ent->idx = rid; + ret = LDB_SUCCESS; + break; + } + case LDB_REPLY_DONE: + { + if (ac->num_entries != 0 && + ac->num_entries != talloc_array_length(ac->entries)) { + ac->entries = talloc_realloc(ac, + ac->entries, + struct samr_SamEntry, + ac->num_entries); + if (ac->entries == NULL) { + ret = ldb_request_done(req, + LDB_ERR_OPERATIONS_ERROR); + break; + } + } + ret = ldb_request_done(req, LDB_SUCCESS); + break; + } + case LDB_REPLY_REFERRAL: + { + ret = LDB_SUCCESS; + break; + } + default: + /* Doesn't happen */ + ret = LDB_ERR_OPERATIONS_ERROR; + } + TALLOC_FREE(ares); + + return ret; +} + +/* + * samr_EnumDomainUsers + * The previous implementation did an initial search and stored a list of + * matching GUIDs on the connection handle's domain state, then did direct + * GUID lookups for each record in a page indexed by resume_handle. That + * approach was memory efficient, requiring only 16 bytes per record, but + * was too slow for winbind which needs this RPC call for getpwent. + * + * Now we use an iterate pattern to populate a cached list of the rids and + * names for each record. This improves runtime performance but requires + * about 200 bytes per record which will mean for a 100k database we use + * about 2MB, which is fine. The speedup achieved by this new approach is + * around 50%. + */ +static NTSTATUS dcesrv_samr_EnumDomainUsers(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_EnumDomainUsers *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + uint32_t results; + uint32_t max_entries; + uint32_t num_entries; + uint32_t remaining_entries; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", + "userAccountControl", NULL }; + struct samr_SamArray *sam; + struct ldb_request *req; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + entries = d_state->domain_users_cached; + + /* + * If the resume_handle is zero, query the database and cache the + * matching entries. + */ + if (*r->in.resume_handle == 0) { + int ret; + struct enum_dom_users_ctx *ac; + if (entries != NULL) { + talloc_free(entries); + d_state->domain_users_cached = NULL; + } + + ac = talloc(mem_ctx, struct enum_dom_users_ctx); + ac->num_entries = 0; + ac->domain_sid = d_state->domain_sid; + ac->entries = talloc_array(ac, + struct samr_SamEntry, + 100); + if (ac->entries == NULL) { + talloc_free(ac); + return NT_STATUS_NO_MEMORY; + } + ac->acct_flags = r->in.acct_flags; + + ret = ldb_build_search_req(&req, + d_state->sam_ctx, + mem_ctx, + d_state->domain_dn, + LDB_SCOPE_SUBTREE, + "(objectClass=user)", + attrs, + NULL, + ac, + user_iterate_callback, + NULL); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_request(d_state->sam_ctx, req); + if (ret != LDB_SUCCESS) { + talloc_free(ac); + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_wait(req->handle, LDB_WAIT_ALL); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + if (ac->num_entries == 0) { + DBG_WARNING("No users in domain %s", + ldb_dn_get_linearized(d_state->domain_dn)); + talloc_free(ac); + return NT_STATUS_OK; + } + + entries = talloc_steal(d_state, ac->entries); + d_state->domain_users_cached = entries; + num_entries = ac->num_entries; + talloc_free(ac); + + /* + * Sort the entries into RID order, while the spec states there + * is no order, Windows appears to sort the results by RID and + * so it is possible that there are clients that depend on + * this ordering + */ + TYPESAFE_QSORT(entries, num_entries, compare_SamEntry); + } else { + num_entries = talloc_array_length(entries); + } + + /* + * If the resume handle is out of range we return an empty response + * and invalidate the cache. + * + * From the specification: + * Servers SHOULD validate that EnumerationContext is an expected + * value for the server's implementation. Windows does NOT validate + * the input, though the result of malformed information merely results + * in inconsistent output to the client. + */ + if (*r->in.resume_handle >= num_entries) { + talloc_free(entries); + d_state->domain_users_cached = NULL; + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + sam->entries = NULL; + sam->count = 0; + + *r->out.sam = sam; + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + + /* + * Calculate the number of entries to return limit by max_size. + * Note that we use the w2k3 element size value of 54 + */ + max_entries = 1 + (r->in.max_size / SAMR_ENUM_USERS_MULTIPLIER); + remaining_entries = num_entries - *r->in.resume_handle; + results = MIN(remaining_entries, max_entries); + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + d_state->domain_users_cached = NULL; + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries + *r->in.resume_handle; + sam->count = results; + + *r->out.sam = sam; + *r->out.resume_handle = *r->in.resume_handle + results; + *r->out.num_entries = results; + + /* + * Signal no more results by returning zero resume handle, + * the cache is also cleared at this point + */ + if (*r->out.resume_handle >= num_entries) { + *r->out.resume_handle = 0; + return NT_STATUS_OK; + } + /* + * There are more results to be returned. + */ + return STATUS_MORE_ENTRIES; +} + + +/* + samr_CreateDomAlias +*/ +static NTSTATUS dcesrv_samr_CreateDomAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_CreateDomAlias *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *alias_name; + struct dom_sid *sid; + struct dcesrv_handle *a_handle; + struct ldb_dn *dn; + NTSTATUS status; + + ZERO_STRUCTP(r->out.alias_handle); + *r->out.rid = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (d_state->builtin) { + DEBUG(5, ("Cannot create a domain alias in the BUILTIN domain")); + return NT_STATUS_ACCESS_DENIED; + } + + alias_name = r->in.alias_name->string; + + if (alias_name == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + status = dsdb_add_domain_alias(d_state->sam_ctx, mem_ctx, alias_name, &sid, &dn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, dn); + + a_state->account_name = talloc_steal(a_state, alias_name); + + /* create the policy handle */ + a_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_ALIAS); + if (a_handle == NULL) + return NT_STATUS_NO_MEMORY; + + a_handle->data = talloc_steal(a_handle, a_state); + + *r->out.alias_handle = a_handle->wire_handle; + + *r->out.rid = sid->sub_auths[sid->num_auths-1]; + + return NT_STATUS_OK; +} + + +/* + samr_EnumDomainAliases +*/ +static NTSTATUS dcesrv_samr_EnumDomainAliases(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_EnumDomainAliases *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_message **res; + int i, ldb_cnt; + uint32_t first, count; + struct samr_SamEntry *entries; + const char * const attrs[] = { "objectSid", "sAMAccountName", NULL }; + struct samr_SamArray *sam; + + *r->out.resume_handle = 0; + *r->out.sam = NULL; + *r->out.num_entries = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* search for all domain aliases in this domain. This could possibly be + cached and resumed based on resume_key */ + ldb_cnt = samdb_search_domain(d_state->sam_ctx, mem_ctx, NULL, + &res, attrs, + d_state->domain_sid, + "(&(|(grouptype=%d)(grouptype=%d)))" + "(objectclass=group))", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ldb_cnt < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + /* convert to SamEntry format */ + entries = talloc_array(mem_ctx, struct samr_SamEntry, ldb_cnt); + if (!entries) { + return NT_STATUS_NO_MEMORY; + } + + count = 0; + + for (i=0;i<ldb_cnt;i++) { + struct dom_sid *alias_sid; + + alias_sid = samdb_result_dom_sid(mem_ctx, res[i], + "objectSid"); + + if (alias_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + entries[count].idx = + alias_sid->sub_auths[alias_sid->num_auths-1]; + entries[count].name.string = + ldb_msg_find_attr_as_string(res[i], "sAMAccountName", ""); + count += 1; + } + + /* sort the results by rid */ + TYPESAFE_QSORT(entries, count, compare_SamEntry); + + /* find the first entry to return */ + for (first=0; + first<count && entries[first].idx <= *r->in.resume_handle; + first++) ; + + /* return the rest, limit by max_size. Note that we + use the w2k3 element size value of 54 */ + *r->out.num_entries = count - first; + *r->out.num_entries = MIN(*r->out.num_entries, + 1+(r->in.max_size/SAMR_ENUM_USERS_MULTIPLIER)); + + sam = talloc(mem_ctx, struct samr_SamArray); + if (!sam) { + return NT_STATUS_NO_MEMORY; + } + + sam->entries = entries+first; + sam->count = *r->out.num_entries; + + *r->out.sam = sam; + + if (first == count) { + return NT_STATUS_OK; + } + + if (*r->out.num_entries < count - first) { + *r->out.resume_handle = + entries[first+*r->out.num_entries-1].idx; + return STATUS_MORE_ENTRIES; + } + + return NT_STATUS_OK; +} + + +/* + samr_GetAliasMembership +*/ +static NTSTATUS dcesrv_samr_GetAliasMembership(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetAliasMembership *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + char *filter; + const char * const attrs[] = { "objectSid", NULL }; + struct ldb_message **res; + uint32_t i; + int count = 0; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + filter = talloc_asprintf(mem_ctx, + "(&(|(grouptype=%d)(grouptype=%d))" + "(objectclass=group)(|", + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<r->in.sids->num_sids; i++) { + struct dom_sid_buf buf; + + filter = talloc_asprintf_append( + filter, + "(member=<SID=%s>)", + dom_sid_str_buf(r->in.sids->sids[i].sid, &buf)); + + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + /* Find out if we had at least one valid member SID passed - otherwise + * just skip the search. */ + if (strstr(filter, "member") != NULL) { + count = samdb_search_domain(d_state->sam_ctx, mem_ctx, NULL, + &res, attrs, d_state->domain_sid, + "%s))", filter); + if (count < 0) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + } + + r->out.rids->count = 0; + r->out.rids->ids = talloc_array(mem_ctx, uint32_t, count); + if (r->out.rids->ids == NULL) + return NT_STATUS_NO_MEMORY; + + for (i=0; i<count; i++) { + struct dom_sid *alias_sid; + + alias_sid = samdb_result_dom_sid(mem_ctx, res[i], "objectSid"); + if (alias_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + r->out.rids->ids[r->out.rids->count] = + alias_sid->sub_auths[alias_sid->num_auths-1]; + r->out.rids->count += 1; + } + + return NT_STATUS_OK; +} + + +/* + samr_LookupNames +*/ +static NTSTATUS dcesrv_samr_LookupNames(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupNames *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + uint32_t i, num_mapped; + NTSTATUS status = NT_STATUS_OK; + const char * const attrs[] = { "sAMAccountType", "objectSid", NULL }; + int count; + + ZERO_STRUCTP(r->out.rids); + ZERO_STRUCTP(r->out.types); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (r->in.num_names == 0) { + return NT_STATUS_OK; + } + + r->out.rids->ids = talloc_array(mem_ctx, uint32_t, r->in.num_names); + r->out.types->ids = talloc_array(mem_ctx, uint32_t, r->in.num_names); + if (!r->out.rids->ids || !r->out.types->ids) { + return NT_STATUS_NO_MEMORY; + } + r->out.rids->count = r->in.num_names; + r->out.types->count = r->in.num_names; + + num_mapped = 0; + + for (i=0;i<r->in.num_names;i++) { + struct ldb_message **res; + struct dom_sid *sid; + uint32_t atype, rtype; + + r->out.rids->ids[i] = 0; + r->out.types->ids[i] = SID_NAME_UNKNOWN; + + count = gendb_search(d_state->sam_ctx, mem_ctx, d_state->domain_dn, &res, attrs, + "sAMAccountName=%s", + ldb_binary_encode_string(mem_ctx, r->in.names[i].string)); + if (count != 1) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + sid = samdb_result_dom_sid(mem_ctx, res[0], "objectSid"); + if (sid == NULL) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + atype = ldb_msg_find_attr_as_uint(res[0], "sAMAccountType", 0); + if (atype == 0) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + rtype = ds_atype_map(atype); + + if (rtype == SID_NAME_UNKNOWN) { + status = STATUS_SOME_UNMAPPED; + continue; + } + + r->out.rids->ids[i] = sid->sub_auths[sid->num_auths-1]; + r->out.types->ids[i] = rtype; + num_mapped++; + } + + if (num_mapped == 0) { + return NT_STATUS_NONE_MAPPED; + } + return status; +} + + +/* + samr_LookupRids +*/ +static NTSTATUS dcesrv_samr_LookupRids(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_LookupRids *r) +{ + NTSTATUS status; + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + const char **names; + struct lsa_String *lsa_names; + enum lsa_SidType *ids; + + ZERO_STRUCTP(r->out.names); + ZERO_STRUCTP(r->out.types); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + if (r->in.num_rids == 0) + return NT_STATUS_OK; + + lsa_names = talloc_zero_array(mem_ctx, struct lsa_String, r->in.num_rids); + names = talloc_zero_array(mem_ctx, const char *, r->in.num_rids); + ids = talloc_zero_array(mem_ctx, enum lsa_SidType, r->in.num_rids); + + if ((lsa_names == NULL) || (names == NULL) || (ids == NULL)) + return NT_STATUS_NO_MEMORY; + + r->out.names->names = lsa_names; + r->out.names->count = r->in.num_rids; + + r->out.types->ids = (uint32_t *) ids; + r->out.types->count = r->in.num_rids; + + status = dsdb_lookup_rids(d_state->sam_ctx, mem_ctx, d_state->domain_sid, + r->in.num_rids, r->in.rids, names, ids); + if (NT_STATUS_IS_OK(status) || NT_STATUS_EQUAL(status, NT_STATUS_NONE_MAPPED) || NT_STATUS_EQUAL(status, STATUS_SOME_UNMAPPED)) { + uint32_t i; + for (i = 0; i < r->in.num_rids; i++) { + lsa_names[i].string = names[i]; + } + } + return status; +} + + +/* + samr_OpenGroup +*/ +static NTSTATUS dcesrv_samr_OpenGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenGroup *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *groupname; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *g_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.group_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the group SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the group record */ + if (d_state->builtin) { + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectClass=group)" + "(groupType=%d))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP); + } else { + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectClass=group)" + "(|(groupType=%d)(groupType=%d)))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + } + if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + groupname = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (groupname == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, groupname); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_GROUP); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.group_handle = g_handle->wire_handle; + + return NT_STATUS_OK; +} + +/* + samr_QueryGroupInfo +*/ +static NTSTATUS dcesrv_samr_QueryGroupInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryGroupInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + const char * const attrs[4] = { "sAMAccountName", "description", + "numMembers", NULL }; + int ret; + union samr_GroupInfo *info; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + + /* pull all the group attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_GROUP; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_GroupInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* Fill in the level */ + switch (r->in.level) { + case GROUPINFOALL: + QUERY_STRING(msg, all.name, "sAMAccountName"); + info->all.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + QUERY_UINT (msg, all.num_members, "numMembers") + QUERY_STRING(msg, all.description, "description"); + break; + case GROUPINFONAME: + QUERY_STRING(msg, name, "sAMAccountName"); + break; + case GROUPINFOATTRIBUTES: + info->attributes.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + break; + case GROUPINFODESCRIPTION: + QUERY_STRING(msg, description, "description"); + break; + case GROUPINFOALL2: + QUERY_STRING(msg, all2.name, "sAMAccountName"); + info->all.attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; /* Do like w2k3 */ + QUERY_UINT (msg, all2.num_members, "numMembers") + QUERY_STRING(msg, all2.description, "description"); + break; + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetGroupInfo +*/ +static NTSTATUS dcesrv_samr_SetGroupInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetGroupInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *g_state; + struct ldb_message *msg; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + g_state = h->data; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, g_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case GROUPINFODESCRIPTION: + SET_STRING(msg, description, "description"); + break; + case GROUPINFONAME: + /* On W2k3 this does not change the name, it changes the + * sAMAccountName attribute */ + SET_STRING(msg, name, "sAMAccountName"); + break; + case GROUPINFOATTRIBUTES: + /* This does not do anything obviously visible in W2k3 LDAP */ + return NT_STATUS_OK; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(g_state->sam_ctx, msg); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + + +/* + samr_AddGroupMember +*/ +static NTSTATUS dcesrv_samr_AddGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct dom_sid *membersid; + const char *memberdn; + struct ldb_result *res; + const char * const attrs[] = { NULL }; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + membersid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (membersid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* according to MS-SAMR 3.1.5.8.2 all type of accounts are accepted */ + ret = ldb_search(d_state->sam_ctx, mem_ctx, &res, + d_state->domain_dn, LDB_SCOPE_SUBTREE, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, membersid)); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (res->count == 0) { + return NT_STATUS_NO_SUCH_USER; + } + + if (res->count > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberdn = ldb_dn_alloc_linearized(mem_ctx, res->msgs[0]->dn); + + if (memberdn == NULL) + return NT_STATUS_NO_MEMORY; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_addval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_MEMBER_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_DeleteDomainGroup +*/ +static NTSTATUS dcesrv_samr_DeleteDomainGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteDomainGroup *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.group_handle = *r->in.group_handle; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.group_handle); + + return NT_STATUS_OK; +} + + +/* + samr_DeleteGroupMember +*/ +static NTSTATUS dcesrv_samr_DeleteGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct dom_sid *membersid; + const char *memberdn; + struct ldb_result *res; + const char * const attrs[] = { NULL }; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + membersid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (membersid == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* according to MS-SAMR 3.1.5.8.2 all type of accounts are accepted */ + ret = ldb_search(d_state->sam_ctx, mem_ctx, &res, + d_state->domain_dn, LDB_SCOPE_SUBTREE, attrs, + "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, membersid)); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (res->count == 0) { + return NT_STATUS_NO_SUCH_USER; + } + + if (res->count > 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberdn = ldb_dn_alloc_linearized(mem_ctx, res->msgs[0]->dn); + + if (memberdn == NULL) + return NT_STATUS_NO_MEMORY; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_UNWILLING_TO_PERFORM: + return NT_STATUS_MEMBER_NOT_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_QueryGroupMember +*/ +static NTSTATUS dcesrv_samr_QueryGroupMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryGroupMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct samr_RidAttrArray *array; + unsigned int i, num_members; + struct dom_sid *members; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.group_handle, SAMR_HANDLE_GROUP); + + a_state = h->data; + d_state = a_state->domain_state; + + status = dsdb_enum_group_mem(d_state->sam_ctx, mem_ctx, + a_state->account_dn, &members, + &num_members); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + array = talloc_zero(mem_ctx, struct samr_RidAttrArray); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (num_members == 0) { + *r->out.rids = array; + + return NT_STATUS_OK; + } + + array->rids = talloc_array(array, uint32_t, num_members); + if (array->rids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->attributes = talloc_array(array, uint32_t, num_members); + if (array->attributes == NULL) { + return NT_STATUS_NO_MEMORY; + } + + array->count = 0; + for (i=0; i<num_members; i++) { + if (!dom_sid_in_domain(d_state->domain_sid, &members[i])) { + continue; + } + + status = dom_sid_split_rid(NULL, &members[i], NULL, + &array->rids[array->count]); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + array->attributes[array->count] = SE_GROUP_MANDATORY | + SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + array->count++; + } + + *r->out.rids = array; + + return NT_STATUS_OK; +} + + +/* + samr_SetMemberAttributesOfGroup +*/ +static NTSTATUS dcesrv_samr_SetMemberAttributesOfGroup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetMemberAttributesOfGroup *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_OpenAlias +*/ +static NTSTATUS dcesrv_samr_OpenAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenAlias *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *alias_name; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *g_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.alias_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the alias SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (sid == NULL) + return NT_STATUS_NO_MEMORY; + + /* search for the group record */ + ret = gendb_search(d_state->sam_ctx, mem_ctx, NULL, &msgs, attrs, + "(&(objectSid=%s)(objectclass=group)" + "(|(grouptype=%d)(grouptype=%d)))", + ldap_encode_ndr_dom_sid(mem_ctx, sid), + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + if (ret == 0) { + return NT_STATUS_NO_SUCH_ALIAS; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + alias_name = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (alias_name == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, alias_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + g_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_ALIAS); + if (!g_handle) { + return NT_STATUS_NO_MEMORY; + } + + g_handle->data = talloc_steal(g_handle, a_state); + + *r->out.alias_handle = g_handle->wire_handle; + + return NT_STATUS_OK; +} + + +/* + samr_QueryAliasInfo +*/ +static NTSTATUS dcesrv_samr_QueryAliasInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryAliasInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + const char * const attrs[4] = { "sAMAccountName", "description", + "numMembers", NULL }; + int ret; + union samr_AliasInfo *info; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + /* pull all the alias attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_ALIAS; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_AliasInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + switch(r->in.level) { + case ALIASINFOALL: + QUERY_STRING(msg, all.name, "sAMAccountName"); + QUERY_UINT (msg, all.num_members, "numMembers"); + QUERY_STRING(msg, all.description, "description"); + break; + case ALIASINFONAME: + QUERY_STRING(msg, name, "sAMAccountName"); + break; + case ALIASINFODESCRIPTION: + QUERY_STRING(msg, description, "description"); + break; + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetAliasInfo +*/ +static NTSTATUS dcesrv_samr_SetAliasInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetAliasInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = ldb_dn_copy(mem_ctx, a_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + switch (r->in.level) { + case ALIASINFODESCRIPTION: + SET_STRING(msg, description, "description"); + break; + case ALIASINFONAME: + /* On W2k3 this does not change the name, it changes the + * sAMAccountName attribute */ + SET_STRING(msg, name, "sAMAccountName"); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* modify the samdb record */ + ret = ldb_modify(a_state->sam_ctx, msg); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + return NT_STATUS_OK; +} + + +/* + samr_DeleteDomAlias +*/ +static NTSTATUS dcesrv_samr_DeleteDomAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteDomAlias *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.alias_handle = *r->in.alias_handle; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.alias_handle); + + return NT_STATUS_OK; +} + + +/* + samr_AddAliasMember +*/ +static NTSTATUS dcesrv_samr_AddAliasMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddAliasMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + struct ldb_message **msgs; + const char * const attrs[] = { NULL }; + struct ldb_dn *memberdn = NULL; + int ret; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + ret = gendb_search(d_state->sam_ctx, mem_ctx, NULL, + &msgs, attrs, "(objectsid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + + if (ret == 1) { + memberdn = msgs[0]->dn; + } else if (ret == 0) { + status = samdb_create_foreign_security_principal( + d_state->sam_ctx, mem_ctx, r->in.sid, &memberdn); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + DEBUG(0,("Found %d records matching sid %s\n", + ret, dom_sid_string(mem_ctx, r->in.sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + if (memberdn == NULL) { + DEBUG(0, ("Could not find memberdn\n")); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_addval(d_state->sam_ctx, mem_ctx, mod, "member", + ldb_dn_alloc_linearized(mem_ctx, memberdn)); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_ENTRY_ALREADY_EXISTS: + return NT_STATUS_MEMBER_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_DeleteAliasMember +*/ +static NTSTATUS dcesrv_samr_DeleteAliasMember(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteAliasMember *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_message *mod; + const char *memberdn; + int ret; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + memberdn = samdb_search_string(d_state->sam_ctx, mem_ctx, NULL, + "distinguishedName", "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + if (memberdn == NULL) { + return NT_STATUS_OBJECT_NAME_NOT_FOUND; + } + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = talloc_reference(mem_ctx, a_state->account_dn); + + ret = samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, "member", + memberdn); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + + ret = ldb_modify(a_state->sam_ctx, mod); + switch (ret) { + case LDB_SUCCESS: + return NT_STATUS_OK; + case LDB_ERR_UNWILLING_TO_PERFORM: + return NT_STATUS_MEMBER_NOT_IN_GROUP; + case LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS: + return NT_STATUS_ACCESS_DENIED; + default: + return dsdb_ldb_err_to_ntstatus(ret); + } +} + + +/* + samr_GetMembersInAlias +*/ +static NTSTATUS dcesrv_samr_GetMembersInAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetMembersInAlias *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct lsa_SidPtr *array; + unsigned int i, num_members; + struct dom_sid *members; + NTSTATUS status; + + DCESRV_PULL_HANDLE(h, r->in.alias_handle, SAMR_HANDLE_ALIAS); + + a_state = h->data; + d_state = a_state->domain_state; + + status = dsdb_enum_group_mem(d_state->sam_ctx, mem_ctx, + a_state->account_dn, &members, + &num_members); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (num_members == 0) { + r->out.sids->sids = NULL; + + return NT_STATUS_OK; + } + + array = talloc_array(mem_ctx, struct lsa_SidPtr, num_members); + if (array == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i=0; i<num_members; i++) { + array[i].sid = &members[i]; + } + + r->out.sids->num_sids = num_members; + r->out.sids->sids = array; + + return NT_STATUS_OK; +} + +/* + samr_OpenUser +*/ +static NTSTATUS dcesrv_samr_OpenUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_OpenUser *r) +{ + struct samr_domain_state *d_state; + struct samr_account_state *a_state; + struct dcesrv_handle *h; + const char *account_name; + struct dom_sid *sid; + struct ldb_message **msgs; + struct dcesrv_handle *u_handle; + const char * const attrs[2] = { "sAMAccountName", NULL }; + int ret; + + ZERO_STRUCTP(r->out.user_handle); + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the users SID */ + sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!sid) { + return NT_STATUS_NO_MEMORY; + } + + /* search for the user record */ + ret = gendb_search(d_state->sam_ctx, + mem_ctx, d_state->domain_dn, &msgs, attrs, + "(&(objectSid=%s)(objectclass=user))", + ldap_encode_ndr_dom_sid(mem_ctx, sid)); + if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } + if (ret != 1) { + DEBUG(0,("Found %d records matching sid %s\n", ret, + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + account_name = ldb_msg_find_attr_as_string(msgs[0], "sAMAccountName", NULL); + if (account_name == NULL) { + DEBUG(0,("sAMAccountName field missing for sid %s\n", + dom_sid_string(mem_ctx, sid))); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + a_state = talloc(mem_ctx, struct samr_account_state); + if (!a_state) { + return NT_STATUS_NO_MEMORY; + } + a_state->sam_ctx = d_state->sam_ctx; + a_state->access_mask = r->in.access_mask; + a_state->domain_state = talloc_reference(a_state, d_state); + a_state->account_dn = talloc_steal(a_state, msgs[0]->dn); + a_state->account_sid = talloc_steal(a_state, sid); + a_state->account_name = talloc_strdup(a_state, account_name); + if (!a_state->account_name) { + return NT_STATUS_NO_MEMORY; + } + + /* create the policy handle */ + u_handle = dcesrv_handle_create(dce_call, SAMR_HANDLE_USER); + if (!u_handle) { + return NT_STATUS_NO_MEMORY; + } + + u_handle->data = talloc_steal(u_handle, a_state); + + *r->out.user_handle = u_handle->wire_handle; + + return NT_STATUS_OK; + +} + + +/* + samr_DeleteUser +*/ +static NTSTATUS dcesrv_samr_DeleteUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_DeleteUser *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + int ret; + + *r->out.user_handle = *r->in.user_handle; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + + ret = ldb_delete(a_state->sam_ctx, a_state->account_dn); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to delete user: %s: %s\n", + ldb_dn_get_linearized(a_state->account_dn), + ldb_errstring(a_state->sam_ctx))); + return dsdb_ldb_err_to_ntstatus(ret); + } + + talloc_free(h); + ZERO_STRUCTP(r->out.user_handle); + + return NT_STATUS_OK; +} + + +/* + samr_QueryUserInfo +*/ +static NTSTATUS dcesrv_samr_QueryUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryUserInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg, **res; + int ret; + struct ldb_context *sam_ctx; + + const char * const *attrs = NULL; + union samr_UserInfo *info; + + NTSTATUS status; + + *r->out.info = NULL; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + sam_ctx = a_state->sam_ctx; + + /* fill in the reply */ + switch (r->in.level) { + case 1: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "primaryGroupID", + "description", + "comment", + NULL}; + attrs = attrs2; + break; + } + case 2: + { + static const char * const attrs2[] = {"comment", + "countryCode", + "codePage", + NULL}; + attrs = attrs2; + break; + } + case 3: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "objectSid", + "primaryGroupID", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "userWorkstations", + "lastLogon", + "lastLogoff", + "pwdLastSet", + "msDS-UserPasswordExpiryTimeComputed", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "userAccountControl", + "msDS-User-Account-Control-Computed", + NULL}; + attrs = attrs2; + break; + } + case 4: + { + static const char * const attrs2[] = {"logonHours", + NULL}; + attrs = attrs2; + break; + } + case 5: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + "objectSid", + "primaryGroupID", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "description", + "userWorkstations", + "lastLogon", + "lastLogoff", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "pwdLastSet", + "msDS-ResultantPSO", + "msDS-UserPasswordExpiryTimeComputed", + "accountExpires", + "userAccountControl", + "msDS-User-Account-Control-Computed", + NULL}; + attrs = attrs2; + break; + } + case 6: + { + static const char * const attrs2[] = {"sAMAccountName", + "displayName", + NULL}; + attrs = attrs2; + break; + } + case 7: + { + static const char * const attrs2[] = {"sAMAccountName", + NULL}; + attrs = attrs2; + break; + } + case 8: + { + static const char * const attrs2[] = {"displayName", + NULL}; + attrs = attrs2; + break; + } + case 9: + { + static const char * const attrs2[] = {"primaryGroupID", + NULL}; + attrs = attrs2; + break; + } + case 10: + { + static const char * const attrs2[] = {"homeDirectory", + "homeDrive", + NULL}; + attrs = attrs2; + break; + } + case 11: + { + static const char * const attrs2[] = {"scriptPath", + NULL}; + attrs = attrs2; + break; + } + case 12: + { + static const char * const attrs2[] = {"profilePath", + NULL}; + attrs = attrs2; + break; + } + case 13: + { + static const char * const attrs2[] = {"description", + NULL}; + attrs = attrs2; + break; + } + case 14: + { + static const char * const attrs2[] = {"userWorkstations", + NULL}; + attrs = attrs2; + break; + } + case 16: + { + static const char * const attrs2[] = {"userAccountControl", + "msDS-User-Account-Control-Computed", + "pwdLastSet", + "msDS-UserPasswordExpiryTimeComputed", + NULL}; + attrs = attrs2; + break; + } + case 17: + { + static const char * const attrs2[] = {"accountExpires", + NULL}; + attrs = attrs2; + break; + } + case 18: + { + return NT_STATUS_NOT_SUPPORTED; + } + case 20: + { + static const char * const attrs2[] = {"userParameters", + NULL}; + attrs = attrs2; + break; + } + case 21: + { + static const char * const attrs2[] = {"lastLogon", + "lastLogoff", + "pwdLastSet", + "msDS-ResultantPSO", + "msDS-UserPasswordExpiryTimeComputed", + "accountExpires", + "sAMAccountName", + "displayName", + "homeDirectory", + "homeDrive", + "scriptPath", + "profilePath", + "description", + "userWorkstations", + "comment", + "userParameters", + "objectSid", + "primaryGroupID", + "userAccountControl", + "msDS-User-Account-Control-Computed", + "logonHours", + "badPwdCount", + "badPasswordTime", + "logonCount", + "countryCode", + "codePage", + NULL}; + attrs = attrs2; + break; + } + case 23: + case 24: + case 25: + case 26: + { + return NT_STATUS_NOT_SUPPORTED; + } + default: + { + return NT_STATUS_INVALID_INFO_CLASS; + } + } + + /* pull all the user attributes */ + ret = gendb_search_dn(a_state->sam_ctx, mem_ctx, + a_state->account_dn, &res, attrs); + if (ret == 0) { + return NT_STATUS_NO_SUCH_USER; + } + if (ret != 1) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + msg = res[0]; + + /* allocate the info structure */ + info = talloc_zero(mem_ctx, union samr_UserInfo); + if (info == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* fill in the reply */ + switch (r->in.level) { + case 1: + QUERY_STRING(msg, info1.account_name, "sAMAccountName"); + QUERY_STRING(msg, info1.full_name, "displayName"); + QUERY_UINT (msg, info1.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info1.description, "description"); + QUERY_STRING(msg, info1.comment, "comment"); + break; + + case 2: + QUERY_STRING(msg, info2.comment, "comment"); + QUERY_UINT (msg, info2.country_code, "countryCode"); + QUERY_UINT (msg, info2.code_page, "codePage"); + break; + + case 3: + QUERY_STRING(msg, info3.account_name, "sAMAccountName"); + QUERY_STRING(msg, info3.full_name, "displayName"); + QUERY_RID (msg, info3.rid, "objectSid"); + QUERY_UINT (msg, info3.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info3.home_directory, "homeDirectory"); + QUERY_STRING(msg, info3.home_drive, "homeDrive"); + QUERY_STRING(msg, info3.logon_script, "scriptPath"); + QUERY_STRING(msg, info3.profile_path, "profilePath"); + QUERY_STRING(msg, info3.workstations, "userWorkstations"); + QUERY_UINT64(msg, info3.last_logon, "lastLogon"); + QUERY_UINT64(msg, info3.last_logoff, "lastLogoff"); + QUERY_UINT64(msg, info3.last_password_change, "pwdLastSet"); + QUERY_APASSC(msg, info3.allow_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info3.force_password_change, "msDS-UserPasswordExpiryTimeComputed"); + QUERY_LHOURS(msg, info3.logon_hours, "logonHours"); + /* level 3 gives the raw badPwdCount value */ + QUERY_UINT (msg, info3.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info3.logon_count, "logonCount"); + QUERY_AFLAGS(msg, info3.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 4: + QUERY_LHOURS(msg, info4.logon_hours, "logonHours"); + break; + + case 5: + QUERY_STRING(msg, info5.account_name, "sAMAccountName"); + QUERY_STRING(msg, info5.full_name, "displayName"); + QUERY_RID (msg, info5.rid, "objectSid"); + QUERY_UINT (msg, info5.primary_gid, "primaryGroupID"); + QUERY_STRING(msg, info5.home_directory, "homeDirectory"); + QUERY_STRING(msg, info5.home_drive, "homeDrive"); + QUERY_STRING(msg, info5.logon_script, "scriptPath"); + QUERY_STRING(msg, info5.profile_path, "profilePath"); + QUERY_STRING(msg, info5.description, "description"); + QUERY_STRING(msg, info5.workstations, "userWorkstations"); + QUERY_UINT64(msg, info5.last_logon, "lastLogon"); + QUERY_UINT64(msg, info5.last_logoff, "lastLogoff"); + QUERY_LHOURS(msg, info5.logon_hours, "logonHours"); + QUERY_BPWDCT(msg, info5.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info5.logon_count, "logonCount"); + QUERY_UINT64(msg, info5.last_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info5.acct_expiry, "accountExpires"); + QUERY_AFLAGS(msg, info5.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 6: + QUERY_STRING(msg, info6.account_name, "sAMAccountName"); + QUERY_STRING(msg, info6.full_name, "displayName"); + break; + + case 7: + QUERY_STRING(msg, info7.account_name, "sAMAccountName"); + break; + + case 8: + QUERY_STRING(msg, info8.full_name, "displayName"); + break; + + case 9: + QUERY_UINT (msg, info9.primary_gid, "primaryGroupID"); + break; + + case 10: + QUERY_STRING(msg, info10.home_directory,"homeDirectory"); + QUERY_STRING(msg, info10.home_drive, "homeDrive"); + break; + + case 11: + QUERY_STRING(msg, info11.logon_script, "scriptPath"); + break; + + case 12: + QUERY_STRING(msg, info12.profile_path, "profilePath"); + break; + + case 13: + QUERY_STRING(msg, info13.description, "description"); + break; + + case 14: + QUERY_STRING(msg, info14.workstations, "userWorkstations"); + break; + + case 16: + QUERY_AFLAGS(msg, info16.acct_flags, "msDS-User-Account-Control-Computed"); + break; + + case 17: + QUERY_UINT64(msg, info17.acct_expiry, "accountExpires"); + break; + + case 20: + status = samdb_result_parameters(mem_ctx, msg, "userParameters", &info->info20.parameters); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(info); + return status; + } + break; + + case 21: + QUERY_UINT64(msg, info21.last_logon, "lastLogon"); + QUERY_UINT64(msg, info21.last_logoff, "lastLogoff"); + QUERY_UINT64(msg, info21.last_password_change, "pwdLastSet"); + QUERY_UINT64(msg, info21.acct_expiry, "accountExpires"); + QUERY_APASSC(msg, info21.allow_password_change,"pwdLastSet"); + QUERY_UINT64(msg, info21.force_password_change, "msDS-UserPasswordExpiryTimeComputed"); + QUERY_STRING(msg, info21.account_name, "sAMAccountName"); + QUERY_STRING(msg, info21.full_name, "displayName"); + QUERY_STRING(msg, info21.home_directory, "homeDirectory"); + QUERY_STRING(msg, info21.home_drive, "homeDrive"); + QUERY_STRING(msg, info21.logon_script, "scriptPath"); + QUERY_STRING(msg, info21.profile_path, "profilePath"); + QUERY_STRING(msg, info21.description, "description"); + QUERY_STRING(msg, info21.workstations, "userWorkstations"); + QUERY_STRING(msg, info21.comment, "comment"); + status = samdb_result_parameters(mem_ctx, msg, "userParameters", &info->info21.parameters); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(info); + return status; + } + + QUERY_RID (msg, info21.rid, "objectSid"); + QUERY_UINT (msg, info21.primary_gid, "primaryGroupID"); + QUERY_AFLAGS(msg, info21.acct_flags, "msDS-User-Account-Control-Computed"); + info->info21.fields_present = 0x08FFFFFF; + QUERY_LHOURS(msg, info21.logon_hours, "logonHours"); + QUERY_BPWDCT(msg, info21.bad_password_count, "badPwdCount"); + QUERY_UINT (msg, info21.logon_count, "logonCount"); + if ((info->info21.acct_flags & ACB_PW_EXPIRED) != 0) { + info->info21.password_expired = PASS_MUST_CHANGE_AT_NEXT_LOGON; + } else { + info->info21.password_expired = PASS_DONT_CHANGE_AT_NEXT_LOGON; + } + QUERY_UINT (msg, info21.country_code, "countryCode"); + QUERY_UINT (msg, info21.code_page, "codePage"); + break; + + + default: + talloc_free(info); + return NT_STATUS_INVALID_INFO_CLASS; + } + + *r->out.info = info; + + return NT_STATUS_OK; +} + + +/* + samr_SetUserInfo +*/ +static NTSTATUS dcesrv_samr_SetUserInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetUserInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct ldb_message *msg; + int ret; + NTSTATUS status = NT_STATUS_OK; + struct ldb_context *sam_ctx; + DATA_BLOB session_key = data_blob_null; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + sam_ctx = a_state->sam_ctx; + + msg = ldb_msg_new(mem_ctx); + if (msg == NULL) { + return NT_STATUS_NO_MEMORY; + } + + msg->dn = talloc_reference(mem_ctx, a_state->account_dn); + if (!msg->dn) { + return NT_STATUS_NO_MEMORY; + } + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to start a transaction: %s\n", + ldb_errstring(sam_ctx)); + return NT_STATUS_LOCK_NOT_GRANTED; + } + + switch (r->in.level) { + case 2: + SET_STRING(msg, info2.comment, "comment"); + SET_UINT (msg, info2.country_code, "countryCode"); + SET_UINT (msg, info2.code_page, "codePage"); + break; + + case 4: + SET_LHOURS(msg, info4.logon_hours, "logonHours"); + break; + + case 6: + SET_STRING(msg, info6.account_name, "samAccountName"); + SET_STRING(msg, info6.full_name, "displayName"); + break; + + case 7: + SET_STRING(msg, info7.account_name, "samAccountName"); + break; + + case 8: + SET_STRING(msg, info8.full_name, "displayName"); + break; + + case 9: + SET_UINT(msg, info9.primary_gid, "primaryGroupID"); + break; + + case 10: + SET_STRING(msg, info10.home_directory, "homeDirectory"); + SET_STRING(msg, info10.home_drive, "homeDrive"); + break; + + case 11: + SET_STRING(msg, info11.logon_script, "scriptPath"); + break; + + case 12: + SET_STRING(msg, info12.profile_path, "profilePath"); + break; + + case 13: + SET_STRING(msg, info13.description, "description"); + break; + + case 14: + SET_STRING(msg, info14.workstations, "userWorkstations"); + break; + + case 16: + SET_AFLAGS(msg, info16.acct_flags, "userAccountControl"); + break; + + case 17: + SET_UINT64(msg, info17.acct_expiry, "accountExpires"); + break; + + case 18: + status = samr_set_password_buffers(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + r->in.info->info18.lm_pwd_active ? r->in.info->info18.lm_pwd.hash : NULL, + r->in.info->info18.nt_pwd_active ? r->in.info->info18.nt_pwd.hash : NULL); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info18.password_expired > 0) { + struct ldb_message_element *set_el; + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, "pwdLastSet", 0) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 20: + SET_PARAMETERS(msg, info20.parameters, "userParameters"); + break; + + case 21: + if (r->in.info->info21.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info21.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info21.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info21.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info21.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info21.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info21.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info21.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info21.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info21.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info21.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info21.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info21.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info21.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info21.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info21.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info21.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info21.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info21.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info21.logon_count, "logonCount"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info21.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info21.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET((SAMR_FIELD_LM_PASSWORD_PRESENT + | SAMR_FIELD_NT_PASSWORD_PRESENT)) { + uint8_t *lm_pwd_hash = NULL, *nt_pwd_hash = NULL; + + if (r->in.info->info21.lm_password_set) { + if ((r->in.info->info21.lm_owf_password.length != 16) + || (r->in.info->info21.lm_owf_password.size != 16)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + lm_pwd_hash = (uint8_t *) r->in.info->info21.lm_owf_password.array; + } + if (r->in.info->info21.nt_password_set) { + if ((r->in.info->info21.nt_owf_password.length != 16) + || (r->in.info->info21.nt_owf_password.size != 16)) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + nt_pwd_hash = (uint8_t *) r->in.info->info21.nt_owf_password.array; + } + status = samr_set_password_buffers(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + lm_pwd_hash, + nt_pwd_hash); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + } + + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info21.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + case 23: + if (r->in.info->info23.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info23.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info23.info.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info23.info.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info23.info.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info23.info.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info23.info.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info23.info.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info23.info.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info23.info.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info23.info.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info23.info.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info23.info.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info23.info.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info23.info.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info23.info.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info23.info.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info23.info.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info23.info.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info23.info.logon_count, "logonCount"); + + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info23.info.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info23.info.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) { + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info23.password); + } else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) { + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info23.password); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info23.info.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + /* the set password levels are handled separately */ + case 24: + status = samr_set_password(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info24.password); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info24.password_expired > 0) { + struct ldb_message_element *set_el; + if (samdb_msg_add_uint64(sam_ctx, mem_ctx, msg, "pwdLastSet", 0) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 25: + if (r->in.info->info25.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info25.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + SET_UINT64(msg, info25.info.last_logon, "lastLogon"); + IFSET(SAMR_FIELD_LAST_LOGOFF) + SET_UINT64(msg, info25.info.last_logoff, "lastLogoff"); + IFSET(SAMR_FIELD_ACCT_EXPIRY) + SET_UINT64(msg, info25.info.acct_expiry, "accountExpires"); + IFSET(SAMR_FIELD_ACCOUNT_NAME) + SET_STRING(msg, info25.info.account_name, "samAccountName"); + IFSET(SAMR_FIELD_FULL_NAME) + SET_STRING(msg, info25.info.full_name, "displayName"); + IFSET(SAMR_FIELD_HOME_DIRECTORY) + SET_STRING(msg, info25.info.home_directory, "homeDirectory"); + IFSET(SAMR_FIELD_HOME_DRIVE) + SET_STRING(msg, info25.info.home_drive, "homeDrive"); + IFSET(SAMR_FIELD_LOGON_SCRIPT) + SET_STRING(msg, info25.info.logon_script, "scriptPath"); + IFSET(SAMR_FIELD_PROFILE_PATH) + SET_STRING(msg, info25.info.profile_path, "profilePath"); + IFSET(SAMR_FIELD_DESCRIPTION) + SET_STRING(msg, info25.info.description, "description"); + IFSET(SAMR_FIELD_WORKSTATIONS) + SET_STRING(msg, info25.info.workstations, "userWorkstations"); + IFSET(SAMR_FIELD_COMMENT) + SET_STRING(msg, info25.info.comment, "comment"); + IFSET(SAMR_FIELD_PARAMETERS) + SET_PARAMETERS(msg, info25.info.parameters, "userParameters"); + IFSET(SAMR_FIELD_PRIMARY_GID) + SET_UINT(msg, info25.info.primary_gid, "primaryGroupID"); + IFSET(SAMR_FIELD_ACCT_FLAGS) + SET_AFLAGS(msg, info25.info.acct_flags, "userAccountControl"); + IFSET(SAMR_FIELD_LOGON_HOURS) + SET_LHOURS(msg, info25.info.logon_hours, "logonHours"); + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + SET_UINT (msg, info25.info.bad_password_count, "badPwdCount"); + IFSET(SAMR_FIELD_NUM_LOGONS) + SET_UINT (msg, info25.info.logon_count, "logonCount"); + IFSET(SAMR_FIELD_COUNTRY_CODE) + SET_UINT (msg, info25.info.country_code, "countryCode"); + IFSET(SAMR_FIELD_CODE_PAGE) + SET_UINT (msg, info25.info.code_page, "codePage"); + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) { + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info25.password); + } else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) { + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info25.password); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info25.info.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + break; + + /* the set password levels are handled separately */ + case 26: + status = samr_set_password_ex(dce_call, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + mem_ctx, + &r->in.info->info26.password); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info26.password_expired > 0) { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info26.password_expired + == PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + break; + + case 31: + status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(status)); + goto done; + } + + status = samr_set_password_aes(dce_call, + mem_ctx, + &session_key, + sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info31.password, + DSDB_PASSWORD_RESET); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (r->in.info->info31.password_expired > 0) { + const char *t = "0"; + struct ldb_message_element *set_el = NULL; + + if (r->in.info->info31.password_expired == + PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + + ret = ldb_msg_add_string(msg, "pwdLastSet", t); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } + + break; + case 32: + status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(status)); + goto done; + } + + if (r->in.info->info32.info.fields_present == 0) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + +#define IFSET(bit) if (bit & r->in.info->info32.info.fields_present) + IFSET(SAMR_FIELD_LAST_LOGON) + { + SET_UINT64(msg, info32.info.last_logon, "lastLogon"); + } + IFSET(SAMR_FIELD_LAST_LOGOFF) + { + SET_UINT64(msg, info32.info.last_logoff, "lastLogoff"); + } + IFSET(SAMR_FIELD_ACCT_EXPIRY) + { + SET_UINT64(msg, + info32.info.acct_expiry, + "accountExpires"); + } + IFSET(SAMR_FIELD_ACCOUNT_NAME) + { + SET_STRING(msg, + info32.info.account_name, + "samAccountName"); + } + IFSET(SAMR_FIELD_FULL_NAME) + { + SET_STRING(msg, info32.info.full_name, "displayName"); + } + IFSET(SAMR_FIELD_HOME_DIRECTORY) + { + SET_STRING(msg, + info32.info.home_directory, + "homeDirectory"); + } + IFSET(SAMR_FIELD_HOME_DRIVE) + { + SET_STRING(msg, info32.info.home_drive, "homeDrive"); + } + IFSET(SAMR_FIELD_LOGON_SCRIPT) + { + SET_STRING(msg, info32.info.logon_script, "scriptPath"); + } + IFSET(SAMR_FIELD_PROFILE_PATH) + { + SET_STRING(msg, + info32.info.profile_path, + "profilePath"); + } + IFSET(SAMR_FIELD_DESCRIPTION) + { + SET_STRING(msg, info32.info.description, "description"); + } + IFSET(SAMR_FIELD_WORKSTATIONS) + { + SET_STRING(msg, + info32.info.workstations, + "userWorkstations"); + } + IFSET(SAMR_FIELD_COMMENT) + { + SET_STRING(msg, info32.info.comment, "comment"); + } + IFSET(SAMR_FIELD_PARAMETERS) + { + SET_PARAMETERS(msg, + info32.info.parameters, + "userParameters"); + } + IFSET(SAMR_FIELD_PRIMARY_GID) + { + SET_UINT(msg, + info32.info.primary_gid, + "primaryGroupID"); + } + IFSET(SAMR_FIELD_ACCT_FLAGS) + { + SET_AFLAGS(msg, + info32.info.acct_flags, + "userAccountControl"); + } + IFSET(SAMR_FIELD_LOGON_HOURS) + { + SET_LHOURS(msg, info32.info.logon_hours, "logonHours"); + } + IFSET(SAMR_FIELD_BAD_PWD_COUNT) + { + SET_UINT(msg, + info32.info.bad_password_count, + "badPwdCount"); + } + IFSET(SAMR_FIELD_NUM_LOGONS) + { + SET_UINT(msg, info32.info.logon_count, "logonCount"); + } + IFSET(SAMR_FIELD_COUNTRY_CODE) + { + SET_UINT(msg, info32.info.country_code, "countryCode"); + } + IFSET(SAMR_FIELD_CODE_PAGE) + { + SET_UINT(msg, info32.info.code_page, "codePage"); + } + + /* password change fields */ + IFSET(SAMR_FIELD_LAST_PWD_CHANGE) + { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + IFSET(SAMR_FIELD_NT_PASSWORD_PRESENT) + { + status = samr_set_password_aes( + dce_call, + mem_ctx, + &session_key, + a_state->sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info32.password, + DSDB_PASSWORD_RESET); + } + else IFSET(SAMR_FIELD_LM_PASSWORD_PRESENT) + { + status = samr_set_password_aes( + dce_call, + mem_ctx, + &session_key, + a_state->sam_ctx, + a_state->account_dn, + a_state->domain_state->domain_dn, + &r->in.info->info32.password, + DSDB_PASSWORD_RESET); + } + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + IFSET(SAMR_FIELD_EXPIRED_FLAG) + { + const char *t = "0"; + struct ldb_message_element *set_el; + if (r->in.info->info32.info.password_expired == + PASS_DONT_CHANGE_AT_NEXT_LOGON) { + t = "-1"; + } + if (ldb_msg_add_string(msg, "pwdLastSet", t) != + LDB_SUCCESS) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + set_el = ldb_msg_find_element(msg, "pwdLastSet"); + set_el->flags = LDB_FLAG_MOD_REPLACE; + } +#undef IFSET + + break; + default: + /* many info classes are not valid for SetUserInfo */ + status = NT_STATUS_INVALID_INFO_CLASS; + goto done; + } + + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + /* modify the samdb record */ + if (msg->num_elements > 0) { + ret = ldb_modify(sam_ctx, msg); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to modify record %s: %s\n", + ldb_dn_get_linearized(a_state->account_dn), + ldb_errstring(sam_ctx))); + + status = dsdb_ldb_err_to_ntstatus(ret); + goto done; + } + } + + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + DBG_ERR("Failed to commit transaction modifying account record " + "%s: %s\n", + ldb_dn_get_linearized(msg->dn), + ldb_errstring(sam_ctx)); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + status = NT_STATUS_OK; +done: + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + } + + return status; +} + + +/* + samr_GetGroupsForUser +*/ +static NTSTATUS dcesrv_samr_GetGroupsForUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetGroupsForUser *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + struct samr_domain_state *d_state; + struct ldb_result *res, *res_memberof; + const char * const attrs[] = { "primaryGroupID", + "memberOf", + NULL }; + const char * const group_attrs[] = { "objectSid", + NULL }; + + struct samr_RidWithAttributeArray *array; + struct ldb_message_element *memberof_el; + int i, ret, count = 0; + uint32_t primary_group_id; + char *filter; + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + d_state = a_state->domain_state; + + ret = dsdb_search_dn(a_state->sam_ctx, mem_ctx, + &res, + a_state->account_dn, + attrs, DSDB_SEARCH_SHOW_EXTENDED_DN); + + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + return NT_STATUS_NO_SUCH_USER; + } else if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } else if (res->count != 1) { + return NT_STATUS_NO_SUCH_USER; + } + + primary_group_id = ldb_msg_find_attr_as_uint(res->msgs[0], "primaryGroupID", + 0); + + filter = talloc_asprintf(mem_ctx, + "(&(|(grouptype=%d)(grouptype=%d))" + "(objectclass=group)(|", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + + memberof_el = ldb_msg_find_element(res->msgs[0], "memberOf"); + if (memberof_el != NULL) { + for (i = 0; i < memberof_el->num_values; i++) { + const struct ldb_val *memberof_sid_binary; + char *memberof_sid_escaped; + struct ldb_dn *memberof_dn + = ldb_dn_from_ldb_val(mem_ctx, + a_state->sam_ctx, + &memberof_el->values[i]); + if (memberof_dn == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberof_sid_binary + = ldb_dn_get_extended_component(memberof_dn, + "SID"); + if (memberof_sid_binary == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + memberof_sid_escaped = ldb_binary_encode(mem_ctx, + *memberof_sid_binary); + if (memberof_sid_escaped == NULL) { + return NT_STATUS_NO_MEMORY; + } + filter = talloc_asprintf_append(filter, "(objectSID=%s)", + memberof_sid_escaped); + if (filter == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + ret = dsdb_search(a_state->sam_ctx, mem_ctx, + &res_memberof, + d_state->domain_dn, + LDB_SCOPE_SUBTREE, + group_attrs, 0, + "%s))", filter); + + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + count = res_memberof->count; + } + + array = talloc(mem_ctx, struct samr_RidWithAttributeArray); + if (array == NULL) + return NT_STATUS_NO_MEMORY; + + array->count = 0; + array->rids = NULL; + + array->rids = talloc_array(mem_ctx, struct samr_RidWithAttribute, + count + 1); + if (array->rids == NULL) + return NT_STATUS_NO_MEMORY; + + /* Adds the primary group */ + + array->rids[0].rid = primary_group_id; + array->rids[0].attributes = SE_GROUP_MANDATORY + | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + array->count += 1; + + /* Adds the additional groups */ + for (i = 0; i < count; i++) { + struct dom_sid *group_sid; + + group_sid = samdb_result_dom_sid(mem_ctx, + res_memberof->msgs[i], + "objectSid"); + if (group_sid == NULL) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + array->rids[i + 1].rid = + group_sid->sub_auths[group_sid->num_auths-1]; + array->rids[i + 1].attributes = SE_GROUP_MANDATORY + | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + array->count += 1; + } + + *r->out.rids = array; + + return NT_STATUS_OK; +} + +/* + * samr_QueryDisplayInfo + * + * A cache of the GUID's matching the last query is maintained + * in the SAMR_QUERY_DISPLAY_INFO_CACHE guid_cache maintained o + * n the dcesrv_handle. + */ +static NTSTATUS dcesrv_samr_QueryDisplayInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + struct ldb_result *res; + uint32_t i; + uint32_t results = 0; + uint32_t count = 0; + const char *const cache_attrs[] = {"objectGUID", NULL}; + const char *const attrs[] = { + "objectSID", "sAMAccountName", "displayName", "description", NULL}; + struct samr_DispEntryFull *entriesFull = NULL; + struct samr_DispEntryFullGroup *entriesFullGroup = NULL; + struct samr_DispEntryAscii *entriesAscii = NULL; + struct samr_DispEntryGeneral *entriesGeneral = NULL; + const char *filter; + int ret; + NTSTATUS status; + struct samr_guid_cache *cache = NULL; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + cache = &d_state->guid_caches[SAMR_QUERY_DISPLAY_INFO_CACHE]; + /* + * Can the cached results be used? + * The cache is discarded if the start index is zero, or the requested + * level is different from that in the cache. + */ + if ((r->in.start_idx == 0) || (r->in.level != cache->handle)) { + /* + * The cached results can not be used, so will need to query + * the database. + */ + + /* + * Get the search filter for the current level + */ + switch (r->in.level) { + case 1: + case 4: + filter = talloc_asprintf(mem_ctx, + "(&(objectclass=user)" + "(sAMAccountType=%d))", + ATYPE_NORMAL_ACCOUNT); + break; + case 2: + filter = talloc_asprintf(mem_ctx, + "(&(objectclass=user)" + "(sAMAccountType=%d))", + ATYPE_WORKSTATION_TRUST); + break; + case 3: + case 5: + filter = + talloc_asprintf(mem_ctx, + "(&(|(groupType=%d)(groupType=%d))" + "(objectClass=group))", + GTYPE_SECURITY_UNIVERSAL_GROUP, + GTYPE_SECURITY_GLOBAL_GROUP); + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + } + clear_guid_cache(cache); + + /* + * search for all requested objects in all domains. + */ + ret = dsdb_search(d_state->sam_ctx, + mem_ctx, + &res, + ldb_get_default_basedn(d_state->sam_ctx), + LDB_SCOPE_SUBTREE, + cache_attrs, + 0, + "%s", + filter); + if (ret != LDB_SUCCESS) { + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + if ((res->count == 0) || (r->in.max_entries == 0)) { + return NT_STATUS_OK; + } + + status = load_guid_cache(cache, d_state, res->count, res->msgs); + TALLOC_FREE(res); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + cache->handle = r->in.level; + } + *r->out.total_size = cache->size; + + /* + * if there are no entries or the requested start index is greater + * than the number of entries, we return an empty response. + */ + if (r->in.start_idx >= cache->size) { + *r->out.returned_size = 0; + switch(r->in.level) { + case 1: + r->out.info->info1.count = *r->out.returned_size; + r->out.info->info1.entries = NULL; + break; + case 2: + r->out.info->info2.count = *r->out.returned_size; + r->out.info->info2.entries = NULL; + break; + case 3: + r->out.info->info3.count = *r->out.returned_size; + r->out.info->info3.entries = NULL; + break; + case 4: + r->out.info->info4.count = *r->out.returned_size; + r->out.info->info4.entries = NULL; + break; + case 5: + r->out.info->info5.count = *r->out.returned_size; + r->out.info->info5.entries = NULL; + break; + } + return NT_STATUS_OK; + } + + /* + * Allocate an array of the appropriate result structures for the + * current query level. + * + * r->in.start_idx is always < cache->size due to the check above + */ + results = MIN((cache->size - r->in.start_idx), r->in.max_entries); + switch (r->in.level) { + case 1: + entriesGeneral = talloc_array( + mem_ctx, struct samr_DispEntryGeneral, results); + break; + case 2: + entriesFull = + talloc_array(mem_ctx, struct samr_DispEntryFull, results); + break; + case 3: + entriesFullGroup = talloc_array( + mem_ctx, struct samr_DispEntryFullGroup, results); + break; + case 4: + case 5: + entriesAscii = + talloc_array(mem_ctx, struct samr_DispEntryAscii, results); + break; + } + + if ((entriesGeneral == NULL) && (entriesFull == NULL) && + (entriesAscii == NULL) && (entriesFullGroup == NULL)) + return NT_STATUS_NO_MEMORY; + + /* + * Process the list of result GUID's. + * Read the details of each object and populate the result structure + * for the current level. + */ + count = 0; + for (i = 0; i < results; i++) { + struct dom_sid *objectsid; + struct ldb_result *rec; + const uint32_t idx = r->in.start_idx + i; + uint32_t rid; + + /* + * Read an object from disk using the GUID as the key + * + * If the object can not be read, or it does not have a SID + * it is ignored. In this case the number of entries returned + * will be less than the requested size, there will also be + * a gap in the idx numbers in the returned elements e.g. if + * there are 3 GUIDs a, b, c in the cache and b is deleted from + * disk then details for a, and c will be returned with + * idx values of 1 and 3 respectively. + * + */ + ret = dsdb_search_by_dn_guid(d_state->sam_ctx, + mem_ctx, + &rec, + &cache->entries[idx], + attrs, + 0); + if (ret == LDB_ERR_NO_SUCH_OBJECT) { + struct GUID_txt_buf guid_buf; + char *guid_str = + GUID_buf_string(&cache->entries[idx], + &guid_buf); + DBG_WARNING("GUID [%s] not found\n", guid_str); + continue; + } else if (ret != LDB_SUCCESS) { + clear_guid_cache(cache); + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + objectsid = samdb_result_dom_sid(mem_ctx, + rec->msgs[0], + "objectSID"); + if (objectsid == NULL) { + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID for GUID [%s] not found\n", + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + status = dom_sid_split_rid(NULL, + objectsid, + NULL, + &rid); + if (!NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + struct GUID_txt_buf guid_buf; + DBG_WARNING( + "objectSID [%s] for GUID [%s] invalid\n", + dom_sid_str_buf(objectsid, &sid_buf), + GUID_buf_string(&cache->entries[idx], &guid_buf)); + continue; + } + + /* + * Populate the result structure for the current object + */ + switch(r->in.level) { + case 1: + + entriesGeneral[count].idx = idx + 1; + entriesGeneral[count].rid = rid; + + entriesGeneral[count].acct_flags = + samdb_result_acct_flags(rec->msgs[0], NULL); + entriesGeneral[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesGeneral[count].full_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "displayName", ""); + entriesGeneral[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 2: + entriesFull[count].idx = idx + 1; + entriesFull[count].rid = rid; + + /* + * No idea why we need to or in ACB_NORMAL here, + * but this is what Win2k3 seems to do... + */ + entriesFull[count].acct_flags = + samdb_result_acct_flags(rec->msgs[0], NULL) | + ACB_NORMAL; + entriesFull[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesFull[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 3: + entriesFullGroup[count].idx = idx + 1; + entriesFullGroup[count].rid = rid; + + /* + * We get a "7" here for groups + */ + entriesFullGroup[count].acct_flags = + SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + entriesFullGroup[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + entriesFullGroup[count].description.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "description", ""); + break; + case 4: + case 5: + entriesAscii[count].idx = idx + 1; + entriesAscii[count].account_name.string = + ldb_msg_find_attr_as_string( + rec->msgs[0], "sAMAccountName", ""); + break; + } + count++; + } + + /* + * Build the response based on the request level. + */ + *r->out.returned_size = count; + switch(r->in.level) { + case 1: + r->out.info->info1.count = count; + r->out.info->info1.entries = entriesGeneral; + break; + case 2: + r->out.info->info2.count = count; + r->out.info->info2.entries = entriesFull; + break; + case 3: + r->out.info->info3.count = count; + r->out.info->info3.entries = entriesFullGroup; + break; + case 4: + r->out.info->info4.count = count; + r->out.info->info4.entries = entriesAscii; + break; + case 5: + r->out.info->info5.count = count; + r->out.info->info5.entries = entriesAscii; + break; + } + + return ((r->in.start_idx + results) < cache->size) + ? STATUS_MORE_ENTRIES + : NT_STATUS_OK; +} + + +/* + samr_GetDisplayEnumerationIndex +*/ +static NTSTATUS dcesrv_samr_GetDisplayEnumerationIndex(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDisplayEnumerationIndex *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_TestPrivateFunctionsDomain +*/ +static NTSTATUS dcesrv_samr_TestPrivateFunctionsDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_TestPrivateFunctionsDomain *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + samr_TestPrivateFunctionsUser +*/ +static NTSTATUS dcesrv_samr_TestPrivateFunctionsUser(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_TestPrivateFunctionsUser *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + + +/* + samr_GetUserPwInfo +*/ +static NTSTATUS dcesrv_samr_GetUserPwInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetUserPwInfo *r) +{ + struct dcesrv_handle *h; + struct samr_account_state *a_state; + + ZERO_STRUCTP(r->out.info); + + DCESRV_PULL_HANDLE(h, r->in.user_handle, SAMR_HANDLE_USER); + + a_state = h->data; + + r->out.info->min_password_length = samdb_search_uint(a_state->sam_ctx, + mem_ctx, 0, a_state->domain_state->domain_dn, "minPwdLength", + NULL); + r->out.info->password_properties = samdb_search_uint(a_state->sam_ctx, + mem_ctx, 0, a_state->account_dn, "pwdProperties", NULL); + + return NT_STATUS_OK; +} + + +/* + samr_RemoveMemberFromForeignDomain +*/ +static NTSTATUS dcesrv_samr_RemoveMemberFromForeignDomain(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_RemoveMemberFromForeignDomain *r) +{ + struct dcesrv_handle *h; + struct samr_domain_state *d_state; + const char *memberdn; + struct ldb_message **res; + const char *no_attrs[] = { NULL }; + int i, count; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + memberdn = samdb_search_string(d_state->sam_ctx, mem_ctx, NULL, + "distinguishedName", "(objectSid=%s)", + ldap_encode_ndr_dom_sid(mem_ctx, r->in.sid)); + /* Nothing to do */ + if (memberdn == NULL) { + return NT_STATUS_OK; + } + + count = samdb_search_domain(d_state->sam_ctx, mem_ctx, + d_state->domain_dn, &res, no_attrs, + d_state->domain_sid, + "(&(member=%s)(objectClass=group)" + "(|(groupType=%d)(groupType=%d)))", + memberdn, + GTYPE_SECURITY_BUILTIN_LOCAL_GROUP, + GTYPE_SECURITY_DOMAIN_LOCAL_GROUP); + + if (count < 0) + return NT_STATUS_INTERNAL_DB_CORRUPTION; + + for (i=0; i<count; i++) { + struct ldb_message *mod; + int ret; + + mod = ldb_msg_new(mem_ctx); + if (mod == NULL) { + return NT_STATUS_NO_MEMORY; + } + + mod->dn = res[i]->dn; + + if (samdb_msg_add_delval(d_state->sam_ctx, mem_ctx, mod, + "member", memberdn) != LDB_SUCCESS) + return NT_STATUS_NO_MEMORY; + + ret = ldb_modify(d_state->sam_ctx, mod); + talloc_free(mod); + if (ret != LDB_SUCCESS) { + return dsdb_ldb_err_to_ntstatus(ret); + } + } + + return NT_STATUS_OK; +} + + +/* + samr_QueryDomainInfo2 + + just an alias for samr_QueryDomainInfo +*/ +static NTSTATUS dcesrv_samr_QueryDomainInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDomainInfo2 *r) +{ + struct samr_QueryDomainInfo r1; + NTSTATUS status; + + r1 = (struct samr_QueryDomainInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .out.info = r->out.info, + }; + + status = dcesrv_samr_QueryDomainInfo(dce_call, mem_ctx, &r1); + + return status; +} + + +/* + samr_QueryUserInfo2 + + just an alias for samr_QueryUserInfo +*/ +static NTSTATUS dcesrv_samr_QueryUserInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryUserInfo2 *r) +{ + struct samr_QueryUserInfo r1; + NTSTATUS status; + + r1 = (struct samr_QueryUserInfo) { + .in.user_handle = r->in.user_handle, + .in.level = r->in.level, + .out.info = r->out.info + }; + + status = dcesrv_samr_QueryUserInfo(dce_call, mem_ctx, &r1); + + return status; +} + + +/* + samr_QueryDisplayInfo2 +*/ +static NTSTATUS dcesrv_samr_QueryDisplayInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo2 *r) +{ + struct samr_QueryDisplayInfo q; + NTSTATUS result; + + q = (struct samr_QueryDisplayInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .in.start_idx = r->in.start_idx, + .in.max_entries = r->in.max_entries, + .in.buf_size = r->in.buf_size, + .out.total_size = r->out.total_size, + .out.returned_size = r->out.returned_size, + .out.info = r->out.info, + }; + + result = dcesrv_samr_QueryDisplayInfo(dce_call, mem_ctx, &q); + + return result; +} + + +/* + samr_GetDisplayEnumerationIndex2 +*/ +static NTSTATUS dcesrv_samr_GetDisplayEnumerationIndex2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDisplayEnumerationIndex2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_QueryDisplayInfo3 +*/ +static NTSTATUS dcesrv_samr_QueryDisplayInfo3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_QueryDisplayInfo3 *r) +{ + struct samr_QueryDisplayInfo q; + NTSTATUS result; + + q = (struct samr_QueryDisplayInfo) { + .in.domain_handle = r->in.domain_handle, + .in.level = r->in.level, + .in.start_idx = r->in.start_idx, + .in.max_entries = r->in.max_entries, + .in.buf_size = r->in.buf_size, + .out.total_size = r->out.total_size, + .out.returned_size = r->out.returned_size, + .out.info = r->out.info, + }; + + result = dcesrv_samr_QueryDisplayInfo(dce_call, mem_ctx, &q); + + return result; +} + + +/* + samr_AddMultipleMembersToAlias +*/ +static NTSTATUS dcesrv_samr_AddMultipleMembersToAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_AddMultipleMembersToAlias *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_RemoveMultipleMembersFromAlias +*/ +static NTSTATUS dcesrv_samr_RemoveMultipleMembersFromAlias(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_RemoveMultipleMembersFromAlias *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_GetDomPwInfo + + this fetches the default password properties for a domain + + note that w2k3 completely ignores the domain name in this call, and + always returns the information for the servers primary domain +*/ +static NTSTATUS dcesrv_samr_GetDomPwInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetDomPwInfo *r) +{ + struct ldb_message **msgs; + int ret; + const char * const attrs[] = {"minPwdLength", "pwdProperties", NULL }; + struct ldb_context *sam_ctx; + + ZERO_STRUCTP(r->out.info); + + sam_ctx = dcesrv_samdb_connect_as_user(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + /* The domain name in this call is ignored */ + ret = gendb_search_dn(sam_ctx, + mem_ctx, NULL, &msgs, attrs); + if (ret <= 0) { + talloc_free(sam_ctx); + + return NT_STATUS_NO_SUCH_DOMAIN; + } + if (ret > 1) { + talloc_free(msgs); + talloc_free(sam_ctx); + + return NT_STATUS_INTERNAL_DB_CORRUPTION; + } + + r->out.info->min_password_length = ldb_msg_find_attr_as_uint(msgs[0], + "minPwdLength", 0); + r->out.info->password_properties = ldb_msg_find_attr_as_uint(msgs[0], + "pwdProperties", 1); + + talloc_free(msgs); + talloc_unlink(mem_ctx, sam_ctx); + + return NT_STATUS_OK; +} + + +/* + samr_Connect2 +*/ +static NTSTATUS dcesrv_samr_Connect2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect2 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_SetUserInfo2 + + just an alias for samr_SetUserInfo +*/ +static NTSTATUS dcesrv_samr_SetUserInfo2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetUserInfo2 *r) +{ + struct samr_SetUserInfo r2; + + r2 = (struct samr_SetUserInfo) { + .in.user_handle = r->in.user_handle, + .in.level = r->in.level, + .in.info = r->in.info, + }; + + return dcesrv_samr_SetUserInfo(dce_call, mem_ctx, &r2); +} + + +/* + samr_SetBootKeyInformation +*/ +static NTSTATUS dcesrv_samr_SetBootKeyInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetBootKeyInformation *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_GetBootKeyInformation +*/ +static NTSTATUS dcesrv_samr_GetBootKeyInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_GetBootKeyInformation *r) +{ + /* Windows Server 2008 returns this */ + return NT_STATUS_NOT_SUPPORTED; +} + + +/* + samr_Connect3 +*/ +static NTSTATUS dcesrv_samr_Connect3(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect3 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_Connect4 +*/ +static NTSTATUS dcesrv_samr_Connect4(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect4 *r) +{ + struct samr_Connect c; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + return dcesrv_samr_Connect(dce_call, mem_ctx, &c); +} + + +/* + samr_Connect5 +*/ +static NTSTATUS dcesrv_samr_Connect5(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_Connect5 *r) +{ + struct samr_Connect c; + NTSTATUS status; + + c = (struct samr_Connect) { + .in.system_name = NULL, + .in.access_mask = r->in.access_mask, + .out.connect_handle = r->out.connect_handle, + }; + + status = dcesrv_samr_Connect(dce_call, mem_ctx, &c); + + r->out.info_out->info1.client_version = SAMR_CONNECT_AFTER_W2K; + r->out.info_out->info1.supported_features = 0; + *r->out.level_out = r->in.level_in; + + return status; +} + + +/* + samr_RidToSid +*/ +static NTSTATUS dcesrv_samr_RidToSid(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_RidToSid *r) +{ + struct samr_domain_state *d_state; + struct dcesrv_handle *h; + + DCESRV_PULL_HANDLE(h, r->in.domain_handle, SAMR_HANDLE_DOMAIN); + + d_state = h->data; + + /* form the users SID */ + *r->out.sid = dom_sid_add_rid(mem_ctx, d_state->domain_sid, r->in.rid); + if (!*r->out.sid) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + + +/* + samr_SetDsrmPassword +*/ +static NTSTATUS dcesrv_samr_SetDsrmPassword(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct samr_SetDsrmPassword *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + samr_ValidatePassword + + For now the call checks the password complexity (if active) and the minimum + password length on level 2 and 3. Level 1 is ignored for now. +*/ +static NTSTATUS dcesrv_samr_ValidatePassword(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ValidatePassword *r) +{ + struct samr_GetDomPwInfo r2; + struct samr_PwInfo pwInfo; + const char *account = NULL; + DATA_BLOB password; + enum samr_ValidationStatus res; + NTSTATUS status; + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(dce_call->conn->endpoint->ep_description); + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + + if (transport != NCACN_IP_TCP && transport != NCALRPC) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + dcesrv_call_auth_info(dce_call, NULL, &auth_level); + if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) { + DCESRV_FAULT(DCERPC_FAULT_ACCESS_DENIED); + } + + (*r->out.rep) = talloc_zero(mem_ctx, union samr_ValidatePasswordRep); + + r2 = (struct samr_GetDomPwInfo) { + .in.domain_name = NULL, + .out.info = &pwInfo, + }; + + status = dcesrv_samr_GetDomPwInfo(dce_call, mem_ctx, &r2); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + switch (r->in.level) { + case NetValidateAuthentication: + /* we don't support this yet */ + return NT_STATUS_NOT_SUPPORTED; + break; + case NetValidatePasswordChange: + account = r->in.req->req2.account.string; + password = data_blob_const(r->in.req->req2.password.string, + r->in.req->req2.password.length); + res = samdb_check_password(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + account, + NULL, /* userPrincipalName */ + NULL, /* displayName/full_name */ + &password, + pwInfo.password_properties, + pwInfo.min_password_length); + (*r->out.rep)->ctr2.status = res; + break; + case NetValidatePasswordReset: + account = r->in.req->req3.account.string; + password = data_blob_const(r->in.req->req3.password.string, + r->in.req->req3.password.length); + res = samdb_check_password(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + account, + NULL, /* userPrincipalName */ + NULL, /* displayName/full_name */ + &password, + pwInfo.password_properties, + pwInfo.min_password_length); + (*r->out.rep)->ctr3.status = res; + break; + default: + return NT_STATUS_INVALID_INFO_CLASS; + break; + } + + return NT_STATUS_OK; +} + +static void dcesrv_samr_Opnum68NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum68NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum69NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum69NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum70NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum70NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum71NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum71NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +static void dcesrv_samr_Opnum72NotUsedOnWire(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_Opnum72NotUsedOnWire *r) +{ + DCESRV_FAULT_VOID(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_samr_s.c" diff --git a/source4/rpc_server/samr/dcesrv_samr.h b/source4/rpc_server/samr/dcesrv_samr.h new file mode 100644 index 0000000..66038ed --- /dev/null +++ b/source4/rpc_server/samr/dcesrv_samr.h @@ -0,0 +1,88 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the samr pipe - definitions + + 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "param/param.h" +#include "libds/common/roles.h" + +/* + this type allows us to distinguish handle types +*/ +enum samr_handle { + SAMR_HANDLE_CONNECT, + SAMR_HANDLE_DOMAIN, + SAMR_HANDLE_USER, + SAMR_HANDLE_GROUP, + SAMR_HANDLE_ALIAS +}; + + +/* + state asscoiated with a samr_Connect*() operation +*/ +struct samr_connect_state { + struct ldb_context *sam_ctx; + uint32_t access_mask; +}; + +/* + * Cache of object GUIDS + */ +struct samr_guid_cache { + unsigned handle; + unsigned size; + struct GUID *entries; +}; + +enum samr_guid_cache_id { + SAMR_QUERY_DISPLAY_INFO_CACHE, + SAMR_ENUM_DOMAIN_GROUPS_CACHE, + SAMR_ENUM_DOMAIN_USERS_CACHE, + SAMR_LAST_CACHE +}; + +/* + state associated with a samr_OpenDomain() operation +*/ +struct samr_domain_state { + struct samr_connect_state *connect_state; + void *sam_ctx; + uint32_t access_mask; + struct dom_sid *domain_sid; + const char *domain_name; + struct ldb_dn *domain_dn; + enum server_role role; + bool builtin; + struct loadparm_context *lp_ctx; + struct samr_guid_cache guid_caches[SAMR_LAST_CACHE]; + struct samr_SamEntry *domain_users_cached; +}; + +/* + state associated with a open account handle +*/ +struct samr_account_state { + struct samr_domain_state *domain_state; + void *sam_ctx; + uint32_t access_mask; + struct dom_sid *account_sid; + const char *account_name; + struct ldb_dn *account_dn; +}; diff --git a/source4/rpc_server/samr/samr_password.c b/source4/rpc_server/samr/samr_password.c new file mode 100644 index 0000000..b581be6 --- /dev/null +++ b/source4/rpc_server/samr/samr_password.c @@ -0,0 +1,833 @@ +/* + Unix SMB/CIFS implementation. + + samr server password set/change handling + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/common/common.h" +#include "rpc_server/samr/dcesrv_samr.h" +#include "system/time.h" +#include "lib/crypto/md4.h" +#include "dsdb/common/util.h" +#include "dsdb/samdb/samdb.h" +#include "auth/auth.h" +#include "libcli/auth/libcli_auth.h" +#include "../lib/util/util_ldb.h" +#include "rpc_server/samr/proto.h" +#include "auth/auth_sam.h" +#include "lib/param/loadparm.h" +#include "librpc/rpc/dcerpc_helper.h" +#include "librpc/rpc/dcerpc_samr.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static void log_password_change_event(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote_client_address, + const struct tsocket_address *local_server_address, + const char *auth_description, + const char *password_type, + const char *original_client_name, + const char *account_name_from_db, + NTSTATUS status, + struct dom_sid *sid) +{ + /* + * Forcing this via the NTLM auth structure is not ideal, but + * it is the most practical option right now, and ensures the + * logs are consistent, even if some elements are always NULL. + */ + struct auth_usersupplied_info ui = { + .was_mapped = true, + .client = { + .account_name = original_client_name, + .domain_name = lpcfg_sam_name(lp_ctx), + }, + .mapped = { + .account_name = account_name_from_db, + .domain_name = lpcfg_sam_name(lp_ctx), + }, + .remote_host = remote_client_address, + .local_host = local_server_address, + .service_description = "SAMR Password Change", + .auth_description = auth_description, + .password_type = password_type, + }; + + log_authentication_event(msg_ctx, + lp_ctx, + NULL, + &ui, + status, + ui.mapped.domain_name, + ui.mapped.account_name, + sid); +} +/* + samr_ChangePasswordUser + + So old it is just not worth implementing + because it does not supply a plaintext and so we can't do password + complexity checking and cannot update all the other password hashes. + +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + samr_OemChangePasswordUser2 + + No longer implemented as it requires the LM hash +*/ +NTSTATUS dcesrv_samr_OemChangePasswordUser2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_OemChangePasswordUser2 *r) +{ + return NT_STATUS_NOT_IMPLEMENTED; +} + +/* + samr_ChangePasswordUser4 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser4(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser4 *r) +{ +#ifdef HAVE_GNUTLS_PBKDF2 + struct ldb_context *sam_ctx = NULL; + struct ldb_message *msg = NULL; + struct ldb_dn *dn = NULL; + const char *samAccountName = NULL; + struct dom_sid *objectSid = NULL; + struct samr_Password *nt_pwd = NULL; + gnutls_datum_t nt_key; + gnutls_datum_t salt = { + .data = r->in.password->salt, + .size = sizeof(r->in.password->salt), + }; + uint8_t cdk_data[16] = {0}; + DATA_BLOB cdk = { + .data = cdk_data, + .length = sizeof(cdk_data), + }; + struct auth_session_info *call_session_info = NULL; + struct auth_session_info *old_session_info = NULL; + NTSTATUS status = NT_STATUS_WRONG_PASSWORD; + int rc; + + r->out.result = NT_STATUS_WRONG_PASSWORD; + + if (r->in.password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (r->in.password->PBKDF2Iterations < 5000 || + r->in.password->PBKDF2Iterations > 1000000) { + return NT_STATUS_INVALID_PARAMETER; + } + /* + * Connect to a SAMDB with system privileges for fetching the old + * password hashes. + */ + sam_ctx = samdb_connect(mem_ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + system_session(dce_call->conn->dce_ctx->lp_ctx), + dce_call->conn->remote_address, + 0); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + rc = ldb_transaction_start(sam_ctx); + if (rc != LDB_SUCCESS) { + DBG_WARNING("Failed to start transaction: %s\n", + ldb_errstring(sam_ctx)); + return NT_STATUS_TRANSACTION_ABORTED; + } + + /* + * We use authsam_search_account() to be consistent with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + dn = msg->dn; + samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + objectSid = samdb_result_dom_sid(msg, msg, "objectSid"); + + status = samdb_result_passwords(mem_ctx, + dce_call->conn->dce_ctx->lp_ctx, + msg, + &nt_pwd); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + if (nt_pwd == NULL) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_WRONG_PASSWORD; + goto done; + } + + nt_key = (gnutls_datum_t){ + .data = nt_pwd->hash, + .size = sizeof(nt_pwd->hash), + }; + + rc = gnutls_pbkdf2(GNUTLS_MAC_SHA512, + &nt_key, + &salt, + r->in.password->PBKDF2Iterations, + cdk.data, + cdk.length); + if (rc < 0) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_WRONG_PASSWORD; + goto done; + } + + /* Drop to user privileges for the password change */ + + old_session_info = ldb_get_opaque(sam_ctx, DSDB_SESSION_INFO); + call_session_info = dcesrv_call_session_info(dce_call); + + rc = ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, call_session_info); + if (rc != LDB_SUCCESS) { + ldb_transaction_cancel(sam_ctx); + status = NT_STATUS_INVALID_SYSTEM_SERVICE; + goto done; + } + + status = samr_set_password_aes(dce_call, + mem_ctx, + &cdk, + sam_ctx, + dn, + NULL, + r->in.password, + DSDB_PASSWORD_CHECKED_AND_CORRECT); + BURN_DATA(cdk_data); + + /* Restore our privileges to system level */ + if (old_session_info != NULL) { + ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, old_session_info); + } + + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto done; + } + + /* And this confirms it in a transaction commit */ + rc = ldb_transaction_commit(sam_ctx); + if (rc != LDB_SUCCESS) { + DBG_WARNING("Failed to commit transaction to change password " + "on %s: %s\n", + ldb_dn_get_linearized(dn), + ldb_errstring(sam_ctx)); + status = NT_STATUS_TRANSACTION_ABORTED; + goto done; + } + + status = NT_STATUS_OK; +done: + { + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + + log_password_change_event(imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + dce_call->conn->remote_address, + dce_call->conn->local_address, + "samr_ChangePasswordUser4", + "AES using NTLM-hash", + r->in.account->string, + samAccountName, + status, + objectSid); + } + + /* Only update the badPwdCount if we found the user */ + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { + authsam_update_bad_pwd_count(sam_ctx, + msg, + ldb_get_default_basedn(sam_ctx)); + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + /* + * Don't give the game away: (don't allow anonymous users to + * prove the existence of usernames) + */ + status = NT_STATUS_WRONG_PASSWORD; + } + + return status; +#else /* HAVE_GNUTLS_PBKDF2 */ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +#endif /* HAVE_GNUTLS_PBKDF2 */ +} + +/* + samr_ChangePasswordUser3 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser3(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser3 *r) +{ + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + NTSTATUS status = NT_STATUS_WRONG_PASSWORD; + DATA_BLOB new_password; + struct ldb_context *sam_ctx = NULL; + struct ldb_dn *user_dn = NULL; + int ret; + struct ldb_message *msg = NULL; + struct samr_Password *nt_pwd; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + enum samPwdChangeReason reason = SAM_PWD_CHANGE_NO_ERROR; + uint8_t new_nt_hash[16]; + struct samr_Password nt_verifier; + const char *user_samAccountName = NULL; + struct dom_sid *user_objectSid = NULL; + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + enum ntlm_auth_level ntlm_auth_level + = lpcfg_ntlm_auth(lp_ctx); + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t nt_session_key; + struct auth_session_info *call_session_info = NULL; + struct auth_session_info *old_session_info = NULL; + int rc; + + *r->out.dominfo = NULL; + *r->out.reject = NULL; + + /* this call should be disabled without NTLM auth */ + if (ntlm_auth_level == NTLM_AUTH_DISABLED) { + DBG_WARNING("NTLM password changes not" + "permitted by configuration.\n"); + return NT_STATUS_NTLM_BLOCKED; + } + + if (r->in.nt_password == NULL || + r->in.nt_verifier == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* Connect to a SAMDB with system privileges for fetching the old pw + * hashes. */ + sam_ctx = dcesrv_samdb_connect_as_system(mem_ctx, dce_call); + if (sam_ctx == NULL) { + return NT_STATUS_INVALID_SYSTEM_SERVICE; + } + + ret = ldb_transaction_start(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(1, ("Failed to start transaction: %s\n", ldb_errstring(sam_ctx))); + return NT_STATUS_TRANSACTION_ABORTED; + } + + /* + * We use authsam_search_account() to be consistent with the + * other callers in the bad password and audit log handling + * systems. It ensures we get DSDB_SEARCH_SHOW_EXTENDED_DN. + */ + status = authsam_search_account(mem_ctx, + sam_ctx, + r->in.account->string, + ldb_get_default_basedn(sam_ctx), + &msg); + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + user_dn = msg->dn; + user_samAccountName = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + user_objectSid = samdb_result_dom_sid(mem_ctx, msg, "objectSid"); + + status = samdb_result_passwords(mem_ctx, lp_ctx, + msg, &nt_pwd); + if (!NT_STATUS_IS_OK(status) ) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (!nt_pwd) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* decrypt the password we have been given */ + nt_session_key = (gnutls_datum_t) { + .data = nt_pwd->hash, + .size = sizeof(nt_pwd->hash), + }; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &nt_session_key, + NULL); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + r->in.nt_password->data, + 516); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (!extract_pw_from_buffer(mem_ctx, r->in.nt_password->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + if (r->in.nt_verifier == NULL) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* check NT verifier */ + mdfour(new_nt_hash, new_password.data, new_password.length); + + rc = E_old_pw_hash(new_nt_hash, nt_pwd->hash, nt_verifier.hash); + if (rc != 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + ldb_transaction_cancel(sam_ctx); + goto failed; + } + if (!mem_equal_const_time(nt_verifier.hash, r->in.nt_verifier->hash, 16)) { + status = NT_STATUS_WRONG_PASSWORD; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* Drop to user privileges for the password change */ + + old_session_info = ldb_get_opaque(sam_ctx, DSDB_SESSION_INFO); + call_session_info = dcesrv_call_session_info(dce_call); + + ret = ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, call_session_info); + if (ret != LDB_SUCCESS) { + status = NT_STATUS_INVALID_SYSTEM_SERVICE; + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* Performs the password modification. We pass the old hashes read out + * from the database since they were already checked against the user- + * provided ones. */ + status = samdb_set_password(sam_ctx, mem_ctx, + user_dn, NULL, + &new_password, + NULL, + DSDB_PASSWORD_CHECKED_AND_CORRECT, + &reason, + &dominfo); + + /* Restore our privileges to system level */ + if (old_session_info != NULL) { + ldb_set_opaque(sam_ctx, DSDB_SESSION_INFO, old_session_info); + } + + if (!NT_STATUS_IS_OK(status)) { + ldb_transaction_cancel(sam_ctx); + goto failed; + } + + /* And this confirms it in a transaction commit */ + ret = ldb_transaction_commit(sam_ctx); + if (ret != LDB_SUCCESS) { + DEBUG(1,("Failed to commit transaction to change password on %s: %s\n", + ldb_dn_get_linearized(user_dn), + ldb_errstring(sam_ctx))); + status = NT_STATUS_TRANSACTION_ABORTED; + goto failed; + } + + status = NT_STATUS_OK; + +failed: + + log_password_change_event(imsg_ctx, + lp_ctx, + dce_call->conn->remote_address, + dce_call->conn->local_address, + "samr_ChangePasswordUser3", + "RC4/DES using NTLM-hash", + r->in.account->string, + user_samAccountName, + status, + user_objectSid); + if (NT_STATUS_IS_OK(status)) { + return NT_STATUS_OK; + } + + /* Only update the badPwdCount if we found the user */ + if (NT_STATUS_EQUAL(status, NT_STATUS_WRONG_PASSWORD)) { + NTSTATUS bad_pwd_status; + + bad_pwd_status = authsam_update_bad_pwd_count( + sam_ctx, msg, ldb_get_default_basedn(sam_ctx)); + if (NT_STATUS_EQUAL(bad_pwd_status, NT_STATUS_ACCOUNT_LOCKED_OUT)) { + status = bad_pwd_status; + } + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_USER)) { + /* Don't give the game away: (don't allow anonymous users to prove the existence of usernames) */ + status = NT_STATUS_WRONG_PASSWORD; + } + + reject = talloc_zero(mem_ctx, struct userPwdChangeFailureInformation); + if (reject != NULL) { + reject->extendedFailureReason = reason; + + *r->out.reject = reject; + } + + *r->out.dominfo = dominfo; + + return status; +} + +/* + samr_ChangePasswordUser2 + + easy - just a subset of samr_ChangePasswordUser3 +*/ +NTSTATUS dcesrv_samr_ChangePasswordUser2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct samr_ChangePasswordUser2 *r) +{ + struct samr_ChangePasswordUser3 r2; + struct samr_DomInfo1 *dominfo = NULL; + struct userPwdChangeFailureInformation *reject = NULL; + + r2.in.server = r->in.server; + r2.in.account = r->in.account; + r2.in.nt_password = r->in.nt_password; + r2.in.nt_verifier = r->in.nt_verifier; + r2.in.lm_change = r->in.lm_change; + r2.in.lm_password = r->in.lm_password; + r2.in.lm_verifier = r->in.lm_verifier; + r2.in.password3 = NULL; + r2.out.dominfo = &dominfo; + r2.out.reject = &reject; + + return dcesrv_samr_ChangePasswordUser3(dce_call, mem_ctx, &r2); +} + + +/* + set password via a samr_CryptPassword buffer +*/ +NTSTATUS samr_set_password(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + struct samr_CryptPassword *pwbuf) +{ + NTSTATUS nt_status; + DATA_BLOB new_password; + DATA_BLOB session_key = data_blob(NULL, 0); + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t _session_key; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + int rc; + bool encrypted; + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + DBG_NOTICE("samr: failed to get session key: %s\n", + nt_errstr(nt_status)); + return nt_status; + } + + _session_key = (gnutls_datum_t) { + .data = session_key.data, + .size = session_key.length, + }; + + /* + * This is safe to support as we only have a session key + * over a SMB connection which we force to be encrypted. + */ + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &_session_key, + NULL); + if (rc < 0) { + GNUTLS_FIPS140_SET_STRICT_MODE(); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + rc = gnutls_cipher_decrypt(cipher_hnd, + pwbuf->data, + 516); + gnutls_cipher_deinit(cipher_hnd); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + goto out; + } + + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + return NT_STATUS_WRONG_PASSWORD; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + DSDB_PASSWORD_RESET, + NULL, + NULL); +out: + return nt_status; +} + + +/* + set password via a samr_CryptPasswordEx buffer +*/ +NTSTATUS samr_set_password_ex(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + struct samr_CryptPasswordEx *pwbuf) +{ + struct loadparm_context *lp_ctx = dce_call->conn->dce_ctx->lp_ctx; + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + NTSTATUS nt_status; + DATA_BLOB new_password; + + /* The confounder is in the last 16 bytes of the buffer */ + DATA_BLOB confounder = data_blob_const(&pwbuf->data[516], 16); + DATA_BLOB pw_data = data_blob_const(pwbuf->data, 516); + DATA_BLOB session_key = data_blob(NULL, 0); + int rc; + bool encrypted; + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(3,("samr: failed to get session key: %s " + "=> NT_STATUS_WRONG_PASSWORD\n", + nt_errstr(nt_status))); + return NT_STATUS_WRONG_PASSWORD; + } + + encrypted = dcerpc_is_transport_encrypted(session_info); + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED && + !encrypted) { + return NT_STATUS_ACCESS_DENIED; + } + + GNUTLS_FIPS140_SET_LAX_MODE(); + rc = samba_gnutls_arcfour_confounded_md5(&confounder, + &session_key, + &pw_data, + SAMBA_GNUTLS_DECRYPT); + GNUTLS_FIPS140_SET_STRICT_MODE(); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + goto out; + } + + if (!extract_pw_from_buffer(mem_ctx, pwbuf->data, &new_password)) { + DEBUG(3,("samr: failed to decode password buffer\n")); + nt_status = NT_STATUS_WRONG_PASSWORD; + goto out; + } + + /* set the password - samdb needs to know both the domain and user DNs, + so the domain password policy can be used */ + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + DSDB_PASSWORD_RESET, + NULL, + NULL); + ZERO_ARRAY_LEN(new_password.data, + new_password.length); + +out: + return nt_status; +} + +/* + set password via encrypted NT and LM hash buffers +*/ +NTSTATUS samr_set_password_buffers(struct dcesrv_call_state *dce_call, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + TALLOC_CTX *mem_ctx, + const uint8_t *lm_pwd_hash, + const uint8_t *nt_pwd_hash) +{ + struct samr_Password *d_lm_pwd_hash = NULL, *d_nt_pwd_hash = NULL; + uint8_t random_session_key[16] = { 0, }; + DATA_BLOB session_key = data_blob(NULL, 0); + DATA_BLOB in, out; + NTSTATUS nt_status = NT_STATUS_OK; + int rc; + + nt_status = dcesrv_transport_session_key(dce_call, &session_key); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_USER_SESSION_KEY)) { + DEBUG(3,("samr: failed to get session key: %s " + "=> use a random session key\n", + nt_errstr(nt_status))); + + /* + * Windows just uses a random key + */ + generate_random_buffer(random_session_key, + sizeof(random_session_key)); + session_key = data_blob_const(random_session_key, + sizeof(random_session_key)); + nt_status = NT_STATUS_OK; + } + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (nt_pwd_hash != NULL) { + in = data_blob_const(nt_pwd_hash, 16); + out = data_blob_talloc_zero(mem_ctx, 16); + + rc = sess_crypt_blob(&out, &in, &session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + d_nt_pwd_hash = (struct samr_Password *) out.data; + } + + if ((d_lm_pwd_hash != NULL) || (d_nt_pwd_hash != NULL)) { + nt_status = samdb_set_password(sam_ctx, mem_ctx, account_dn, + domain_dn, NULL, + d_nt_pwd_hash, + DSDB_PASSWORD_RESET, + NULL, NULL); + } + + return nt_status; +} + +NTSTATUS samr_set_password_aes(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *cdk, + struct ldb_context *sam_ctx, + struct ldb_dn *account_dn, + struct ldb_dn *domain_dn, + struct samr_EncryptedPasswordAES *pwbuf, + enum dsdb_password_checked old_password_checked) +{ + DATA_BLOB pw_data = data_blob_null; + DATA_BLOB new_password = data_blob_null; + const DATA_BLOB ciphertext = + data_blob_const(pwbuf->cipher, pwbuf->cipher_len); + DATA_BLOB iv = data_blob_const(pwbuf->salt, sizeof(pwbuf->salt)); + NTSTATUS nt_status = NT_STATUS_OK; + bool ok; + + nt_status = samba_gnutls_aead_aes_256_cbc_hmac_sha512_decrypt( + mem_ctx, + &ciphertext, + cdk, + &samr_aes256_enc_key_salt, + &samr_aes256_mac_key_salt, + &iv, + pwbuf->auth_data, + &pw_data); + if (!NT_STATUS_IS_OK(nt_status)) { + return NT_STATUS_WRONG_PASSWORD; + } + + ok = extract_pwd_blob_from_buffer514(mem_ctx, + pw_data.data, + &new_password); + TALLOC_FREE(pw_data.data); + if (!ok) { + DBG_NOTICE("samr: failed to decode password buffer\n"); + return NT_STATUS_WRONG_PASSWORD; + } + + nt_status = samdb_set_password(sam_ctx, + mem_ctx, + account_dn, + domain_dn, + &new_password, + NULL, + old_password_checked, + NULL, + NULL); + TALLOC_FREE(new_password.data); + + return nt_status; +} diff --git a/source4/rpc_server/service_rpc.c b/source4/rpc_server/service_rpc.c new file mode 100644 index 0000000..8c6b751 --- /dev/null +++ b/source4/rpc_server/service_rpc.c @@ -0,0 +1,220 @@ +/* + Unix SMB/CIFS implementation. + + smbd-specific dcerpc server code + + Copyright (C) Andrew Tridgell 2003-2005 + Copyright (C) Stefan (metze) Metzmacher 2004-2005 + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2004,2007 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "auth/auth.h" +#include "../lib/util/dlinklist.h" +#include "rpc_server/dcerpc_server.h" +#include "rpc_server/dcerpc_server_proto.h" +#include "librpc/rpc/dcerpc.h" +#include "system/filesys.h" +#include "lib/messaging/irpc.h" +#include "system/network.h" +#include "lib/socket/netif.h" +#include "param/param.h" +#include "../lib/tsocket/tsocket.h" +#include "librpc/rpc/dcerpc_proto.h" +#include "../lib/util/tevent_ntstatus.h" +#include "libcli/raw/smb.h" +#include "../libcli/named_pipe_auth/npa_tstream.h" +#include "samba/process_model.h" + +static void skip_become_root(void) +{ +} + +static void skip_unbecome_root(void) +{ +} + +static struct dcesrv_context_callbacks srv_callbacks = { + .log.successful_authz = log_successful_dcesrv_authz_event, + .auth.gensec_prepare = dcesrv_gensec_prepare, + .auth.become_root = skip_become_root, + .auth.unbecome_root = skip_unbecome_root, + .assoc_group.find = dcesrv_assoc_group_find_s4, +}; + +/* + * Need to run the majority of the RPC endpoints in a single process to allow + * for shared handles, and the sharing of ldb contexts. + * + * However other endpoints are capable of being run in multiple processes + * e.g. NETLOGON. + * + * To support this the process model is manipulated to force those end points + * not supporting multiple processes into the single process model. The code + * responsible for this is in dcesrv_init_endpoints + * + */ +NTSTATUS server_service_rpc_init(TALLOC_CTX *); + +/* + * Initialise the rpc endpoints. + */ +static NTSTATUS dcesrv_init_endpoints(struct task_server *task, + struct dcesrv_context *dce_ctx, + bool use_single_process) +{ + + struct dcesrv_endpoint *e; + const struct model_ops *model_ops = NULL; + + /* + * For those RPC services that run with shared context we need to + * ensure that they don't fork a new process on accept (standard_model). + * And as there is only one process handling these requests we need + * to handle accept errors in a similar manner to the single process + * model. + * + * To do this we override the process model operations with the single + * process operations. This is not the most elegant solution, but it is + * the least ugly, and is confined to the next block of code. + */ + if (use_single_process) { + model_ops = process_model_startup("single"); + if (model_ops == NULL) { + DBG_ERR("Unable to load single process model"); + return NT_STATUS_INTERNAL_ERROR; + } + } else { + model_ops = task->model_ops; + } + + for (e = dce_ctx->endpoint_list; e; e = e->next) { + + enum dcerpc_transport_t transport = + dcerpc_binding_get_transport(e->ep_description); + + if (transport == NCACN_HTTP) { + /* + * We don't support ncacn_http yet + */ + continue; + } + if (e->use_single_process == use_single_process) { + NTSTATUS status; + status = dcesrv_add_ep(dce_ctx, + task->lp_ctx, + e, + task->event_ctx, + model_ops, + task->process_context); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + return NT_STATUS_OK; +} + +/* + * Initialise the RPC service. + * And those end points that can be serviced by multiple processes. + * The endpoints that need to be run in a single process are setup in the + * post_fork hook. +*/ +static NTSTATUS dcesrv_task_init(struct task_server *task) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct dcesrv_context *dce_ctx; + const char **ep_servers = NULL; + + dcerpc_server_init(task->lp_ctx); + + task_server_set_title(task, "task[dcesrv]"); + + status = dcesrv_init_context(task->event_ctx, + task->lp_ctx, + &srv_callbacks, + &dce_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ep_servers = lpcfg_dcerpc_endpoint_servers(task->lp_ctx); + status = dcesrv_init_ep_servers(dce_ctx, ep_servers); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* Make sure the directory for NCALRPC exists */ + if (!directory_exist(lpcfg_ncalrpc_dir(task->lp_ctx))) { + mkdir(lpcfg_ncalrpc_dir(task->lp_ctx), 0755); + } + status = dcesrv_init_endpoints(task, dce_ctx, false); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + task->private_data = dce_ctx; + return NT_STATUS_OK; +} + +/* + * Initialise the endpoints that need to run in a single process fork. + * The endpoint registration is only done for the first process instance. + * + */ +static void dcesrv_post_fork(struct task_server *task, + struct process_details *pd) +{ + + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + struct dcesrv_context *dce_ctx; + + if (task->private_data == NULL) { + task_server_terminate(task, "dcerpc: No dcesrv_context", true); + return; + } + dce_ctx = + talloc_get_type_abort(task->private_data, struct dcesrv_context); + + /* + * Ensure the single process endpoints are only available to the + * first instance. + */ + if (pd->instances == 0) { + status = dcesrv_init_endpoints(task, dce_ctx, true); + if (!NT_STATUS_IS_OK(status)) { + task_server_terminate( + task, + "dcerpc: Failed to initialise end points", + true); + return; + } + } + + irpc_add_name(task->msg_ctx, "rpc_server"); +} + +NTSTATUS server_service_rpc_init(TALLOC_CTX *ctx) +{ + static const struct service_details details = { + .inhibit_fork_on_accept = false, + .inhibit_pre_fork = false, + .task_init = dcesrv_task_init, + .post_fork = dcesrv_post_fork}; + return register_server_service(ctx, "rpc", &details); +} diff --git a/source4/rpc_server/srvsvc/dcesrv_srvsvc.c b/source4/rpc_server/srvsvc/dcesrv_srvsvc.c new file mode 100644 index 0000000..d7a2262 --- /dev/null +++ b/source4/rpc_server/srvsvc/dcesrv_srvsvc.c @@ -0,0 +1,2300 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the srvsvc pipe + + Copyright (C) Stefan (metze) Metzmacher 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "ntvfs/ntvfs.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_srvsvc.h" +#include "rpc_server/common/common.h" +#include "rpc_server/common/share.h" +#include "auth/auth.h" +#include "libcli/security/security.h" +#include "system/time.h" +#include "rpc_server/srvsvc/proto.h" +#include "param/param.h" + +#undef strcasecmp +#undef strncasecmp + +#define SRVSVC_CHECK_ADMIN_ACCESS do { \ + struct auth_session_info *si = dcesrv_call_session_info(dce_call); \ + struct security_token *t = si->security_token; \ + if (!security_token_has_builtin_administrators(t) && \ + !security_token_has_sid(t, &global_sid_Builtin_Server_Operators)) { \ + return WERR_ACCESS_DENIED; \ + } \ +} while (0) + +/* + srvsvc_NetCharDevEnum +*/ +static WERROR dcesrv_srvsvc_NetCharDevEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevEnum *r) +{ + *r->out.totalentries = 0; + + switch (r->in.info_ctr->level) { + case 0: + r->out.info_ctr->ctr.ctr0 = talloc(mem_ctx, struct srvsvc_NetCharDevCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr0); + + r->out.info_ctr->ctr.ctr0->count = 0; + r->out.info_ctr->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + + case 1: + r->out.info_ctr->ctr.ctr1 = talloc(mem_ctx, struct srvsvc_NetCharDevCtr1); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr1); + + r->out.info_ctr->ctr.ctr1->count = 0; + r->out.info_ctr->ctr.ctr1->array = NULL; + + return WERR_NOT_SUPPORTED; + + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetCharDevGetInfo +*/ +static WERROR dcesrv_srvsvc_NetCharDevGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevGetInfo *r) +{ + ZERO_STRUCTP(r->out.info); + + switch (r->in.level) { + case 0: + { + return WERR_NOT_SUPPORTED; + } + case 1: + { + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetCharDevControl +*/ +static WERROR dcesrv_srvsvc_NetCharDevControl(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevControl *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetCharDevQEnum +*/ +static WERROR dcesrv_srvsvc_NetCharDevQEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevQEnum *r) +{ + *r->out.totalentries = 0; + + switch (r->in.info_ctr->level) { + case 0: + { + r->out.info_ctr->ctr.ctr0 = talloc(mem_ctx, struct srvsvc_NetCharDevQCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr0); + + r->out.info_ctr->ctr.ctr0->count = 0; + r->out.info_ctr->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 1: + { + r->out.info_ctr->ctr.ctr1 = talloc(mem_ctx, struct srvsvc_NetCharDevQCtr1); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr1); + + r->out.info_ctr->ctr.ctr1->count = 0; + r->out.info_ctr->ctr.ctr1->array = NULL; + + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetCharDevQGetInfo +*/ +static WERROR dcesrv_srvsvc_NetCharDevQGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevQGetInfo *r) +{ + ZERO_STRUCTP(r->out.info); + + switch (r->in.level) { + case 0: + { + return WERR_NOT_SUPPORTED; + } + case 1: + { + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetCharDevQSetInfo +*/ +static WERROR dcesrv_srvsvc_NetCharDevQSetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevQSetInfo *r) +{ + switch (r->in.level) { + case 0: + { + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + return WERR_NOT_SUPPORTED; + } + case 1: + { + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetCharDevQPurge +*/ +static WERROR dcesrv_srvsvc_NetCharDevQPurge(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevQPurge *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetCharDevQPurgeSelf +*/ +static WERROR dcesrv_srvsvc_NetCharDevQPurgeSelf(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetCharDevQPurgeSelf *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetConnEnum +*/ +static WERROR dcesrv_srvsvc_NetConnEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetConnEnum *r) +{ + *r->out.totalentries = 0; + + switch (r->in.info_ctr->level) { + case 0: + { + r->out.info_ctr->ctr.ctr0 = talloc(mem_ctx, struct srvsvc_NetConnCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr0); + + r->out.info_ctr->ctr.ctr0->count = 0; + r->out.info_ctr->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 1: + { + r->out.info_ctr->ctr.ctr1 = talloc(mem_ctx, struct srvsvc_NetConnCtr1); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr1); + + r->out.info_ctr->ctr.ctr1->count = 0; + r->out.info_ctr->ctr.ctr1->array = NULL; + + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetFileEnum +*/ +static WERROR dcesrv_srvsvc_NetFileEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetFileEnum *r) +{ + *r->out.totalentries = 0; + + switch (r->in.info_ctr->level) { + case 2: + { + r->out.info_ctr->ctr.ctr2 = talloc(mem_ctx, struct srvsvc_NetFileCtr2); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr2); + + r->out.info_ctr->ctr.ctr2->count = 0; + r->out.info_ctr->ctr.ctr2->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 3: + { + r->out.info_ctr->ctr.ctr3 = talloc(mem_ctx, struct srvsvc_NetFileCtr3); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr3); + + r->out.info_ctr->ctr.ctr3->count = 0; + r->out.info_ctr->ctr.ctr3->array = NULL; + + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetFileGetInfo +*/ +static WERROR dcesrv_srvsvc_NetFileGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetFileGetInfo *r) +{ + ZERO_STRUCTP(r->out.info); + + switch (r->in.level) { + case 2: + { + return WERR_NOT_SUPPORTED; + } + case 3: + { + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetFileClose +*/ +static WERROR dcesrv_srvsvc_NetFileClose(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetFileClose *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetSessEnum +*/ +static WERROR dcesrv_srvsvc_NetSessEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSessEnum *r) +{ + *r->out.totalentries = 0; + + switch (r->in.info_ctr->level) { + case 0: + { + r->out.info_ctr->ctr.ctr0 = talloc(mem_ctx, struct srvsvc_NetSessCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr0); + + r->out.info_ctr->ctr.ctr0->count = 0; + r->out.info_ctr->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 1: + { + r->out.info_ctr->ctr.ctr1 = talloc(mem_ctx, struct srvsvc_NetSessCtr1); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr1); + + r->out.info_ctr->ctr.ctr1->count = 0; + r->out.info_ctr->ctr.ctr1->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 2: + { + r->out.info_ctr->ctr.ctr2 = talloc(mem_ctx, struct srvsvc_NetSessCtr2); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr2); + + r->out.info_ctr->ctr.ctr2->count = 0; + r->out.info_ctr->ctr.ctr2->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 10: + { + r->out.info_ctr->ctr.ctr10 = talloc(mem_ctx, struct srvsvc_NetSessCtr10); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr10); + + r->out.info_ctr->ctr.ctr10->count = 0; + r->out.info_ctr->ctr.ctr10->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 502: + { + r->out.info_ctr->ctr.ctr502 = talloc(mem_ctx, struct srvsvc_NetSessCtr502); + W_ERROR_HAVE_NO_MEMORY(r->out.info_ctr->ctr.ctr502); + + r->out.info_ctr->ctr.ctr502->count = 0; + r->out.info_ctr->ctr.ctr502->array = NULL; + + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetSessDel +*/ +static WERROR dcesrv_srvsvc_NetSessDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSessDel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetShareAdd +*/ +static WERROR dcesrv_srvsvc_NetShareAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareAdd *r) +{ + switch (r->in.level) { + case 0: + { + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + return WERR_NOT_SUPPORTED; + } + case 1: + { + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + return WERR_NOT_SUPPORTED; + } + case 2: + { + NTSTATUS nterr; + struct share_info *info; + struct share_context *sctx; + unsigned int count = 8; + unsigned int i; + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + /* there are no more than 8 options in struct srvsvc_NetShareInfo2 */ + info = talloc_array(mem_ctx, struct share_info, count); + W_ERROR_HAVE_NO_MEMORY(info); + + i = 0; + + info[i].name = SHARE_TYPE; + info[i].type = SHARE_INFO_STRING; + switch (r->in.info->info2->type) { + case STYPE_DISKTREE: + info[i].value = talloc_strdup(info, "DISK"); + break; + case STYPE_PRINTQ: + info[i].value = talloc_strdup(info, "PRINTER"); + break; + case STYPE_IPC: + info[i].value = talloc_strdup(info, "IPC"); + break; + default: + return WERR_INVALID_PARAMETER; + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + i++; + + if (r->in.info->info2->path && r->in.info->info2->path[0]) { + info[i].name = SHARE_PATH; + info[i].type = SHARE_INFO_STRING; + + /* Windows will send a path in a form of C:\example\path */ + if (r->in.info->info2->path[1] == ':') { + info[i].value = talloc_strdup(info, &r->in.info->info2->path[2]); + } else { + /* very strange let's try to set as is */ + info[i].value = talloc_strdup(info, r->in.info->info2->path); + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + all_string_sub((char *)info[i].value, "\\", "/", 0); + + i++; + } + + if (r->in.info->info2->comment && r->in.info->info2->comment[0]) { + info[i].name = SHARE_COMMENT; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, r->in.info->info2->comment); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + if (r->in.info->info2->password && r->in.info->info2->password[0]) { + info[i].name = SHARE_PASSWORD; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, r->in.info->info2->password); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + info[i].name = SHARE_MAX_CONNECTIONS; + info[i].type = SHARE_INFO_INT; + info[i].value = talloc(info, int); + *((int *)info[i].value) = r->in.info->info2->max_users; + i++; + + /* TODO: security descriptor */ + + nterr = share_create(sctx, r->in.info->info2->name, info, i); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + + return WERR_OK; + } + case 501: + { + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + return WERR_NOT_SUPPORTED; + } + case 502: + { + NTSTATUS nterr; + struct share_info *info; + struct share_context *sctx; + unsigned int count = 10; + unsigned int i; + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + /* there are no more than 10 options in struct srvsvc_NetShareInfo502 */ + info = talloc_array(mem_ctx, struct share_info, count); + W_ERROR_HAVE_NO_MEMORY(info); + + i = 0; + + info[i].name = SHARE_TYPE; + info[i].type = SHARE_INFO_STRING; + switch (r->in.info->info502->type) { + case 0x00: + info[i].value = talloc_strdup(info, "DISK"); + break; + case 0x01: + info[i].value = talloc_strdup(info, "PRINTER"); + break; + case 0x03: + info[i].value = talloc_strdup(info, "IPC"); + break; + default: + return WERR_INVALID_PARAMETER; + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + i++; + + if (r->in.info->info502->path && r->in.info->info502->path[0]) { + info[i].name = SHARE_PATH; + info[i].type = SHARE_INFO_STRING; + + /* Windows will send a path in a form of C:\example\path */ + if (r->in.info->info502->path[1] == ':') { + info[i].value = talloc_strdup(info, &r->in.info->info502->path[2]); + } else { + /* very strange let's try to set as is */ + info[i].value = talloc_strdup(info, r->in.info->info502->path); + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + all_string_sub((char *)info[i].value, "\\", "/", 0); + + i++; + } + + if (r->in.info->info502->comment && r->in.info->info502->comment[0]) { + info[i].name = SHARE_COMMENT; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, r->in.info->info502->comment); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + if (r->in.info->info502->password && r->in.info->info502->password[0]) { + info[i].name = SHARE_PASSWORD; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, r->in.info->info502->password); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + info[i].name = SHARE_MAX_CONNECTIONS; + info[i].type = SHARE_INFO_INT; + info[i].value = talloc(info, int); + *((int *)info[i].value) = r->in.info->info502->max_users; + i++; + + /* TODO: security descriptor */ + + nterr = share_create(sctx, r->in.info->info502->name, info, i); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + +static WERROR dcesrv_srvsvc_fiel_ShareInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct share_config *scfg, uint32_t level, + union srvsvc_NetShareInfo *info) +{ + struct dcesrv_context *dce_ctx = dce_call->conn->dce_ctx; + + switch (level) { + case 0: + { + info->info0->name = talloc_strdup(mem_ctx, scfg->name); + W_ERROR_HAVE_NO_MEMORY(info->info0->name); + + return WERR_OK; + } + case 1: + { + info->info1->name = talloc_strdup(mem_ctx, scfg->name); + W_ERROR_HAVE_NO_MEMORY(info->info1->name); + info->info1->type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + info->info1->comment = share_string_option(mem_ctx, scfg, SHARE_COMMENT, ""); + W_ERROR_HAVE_NO_MEMORY(info->info1->comment); + + return WERR_OK; + } + case 2: + { + info->info2->name = talloc_strdup(mem_ctx, scfg->name); + W_ERROR_HAVE_NO_MEMORY(info->info2->name); + info->info2->type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + info->info2->comment = share_string_option(mem_ctx, scfg, SHARE_COMMENT, ""); + W_ERROR_HAVE_NO_MEMORY(info->info2->comment); + info->info2->permissions = dcesrv_common_get_share_permissions(mem_ctx, dce_ctx, scfg); + info->info2->max_users = share_int_option(scfg, SHARE_MAX_CONNECTIONS, SHARE_MAX_CONNECTIONS_DEFAULT); + info->info2->current_users = dcesrv_common_get_share_current_users(mem_ctx, dce_ctx, scfg); + info->info2->path = dcesrv_common_get_share_path(mem_ctx, dce_ctx, scfg); + W_ERROR_HAVE_NO_MEMORY(info->info2->path); + info->info2->password = share_string_option(mem_ctx, scfg, SHARE_PASSWORD, NULL); + + return WERR_OK; + } + case 501: + { + info->info501->name = talloc_strdup(mem_ctx, scfg->name); + W_ERROR_HAVE_NO_MEMORY(info->info501->name); + info->info501->type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + info->info501->comment = share_string_option(mem_ctx, scfg, SHARE_COMMENT, ""); + W_ERROR_HAVE_NO_MEMORY(info->info501->comment); + info->info501->csc_policy = share_int_option(scfg, SHARE_CSC_POLICY, SHARE_CSC_POLICY_DEFAULT); + + return WERR_OK; + } + case 502: + { + info->info502->name = talloc_strdup(mem_ctx, scfg->name); + W_ERROR_HAVE_NO_MEMORY(info->info502->name); + info->info502->type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + info->info502->comment = share_string_option(mem_ctx, scfg, SHARE_COMMENT, ""); + W_ERROR_HAVE_NO_MEMORY(info->info502->comment); + info->info502->permissions = dcesrv_common_get_share_permissions(mem_ctx, dce_ctx, scfg); + info->info502->max_users = share_int_option(scfg, SHARE_MAX_CONNECTIONS, SHARE_MAX_CONNECTIONS_DEFAULT); + info->info502->current_users = dcesrv_common_get_share_current_users(mem_ctx, dce_ctx, scfg); + info->info502->path = dcesrv_common_get_share_path(mem_ctx, dce_ctx, scfg); + W_ERROR_HAVE_NO_MEMORY(info->info502->path); + info->info502->password = share_string_option(mem_ctx, scfg, SHARE_PASSWORD, NULL); + info->info502->sd_buf.sd = dcesrv_common_get_security_descriptor(mem_ctx, dce_ctx, scfg); + + return WERR_OK; + } + case 1005: + { + info->info1005->dfs_flags = dcesrv_common_get_share_dfs_flags(mem_ctx, dce_ctx, scfg); + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + +/* + srvsvc_NetShareEnumAll +*/ +static WERROR dcesrv_srvsvc_NetShareEnumAll(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareEnumAll *r) +{ + NTSTATUS nterr; + int numshares = 0; + const char **snames; + struct share_context *sctx; + struct share_config *scfg; + + *r->out.totalentries = 0; + + /* TODO: - paging of results + */ + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + nterr = share_list_all(mem_ctx, sctx, &numshares, &snames); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + switch (r->in.info_ctr->level) { + case 0: + { + unsigned int i; + struct srvsvc_NetShareCtr0 *ctr0; + + ctr0 = talloc(mem_ctx, struct srvsvc_NetShareCtr0); + W_ERROR_HAVE_NO_MEMORY(ctr0); + + ctr0->count = numshares; + ctr0->array = NULL; + + if (ctr0->count == 0) { + r->out.info_ctr->ctr.ctr0 = ctr0; + return WERR_OK; + } + + ctr0->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo0, ctr0->count); + W_ERROR_HAVE_NO_MEMORY(ctr0->array); + + for (i = 0; i < ctr0->count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + info.info0 = &ctr0->array[i]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + talloc_free(scfg); + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr0 = ctr0; + *r->out.totalentries = r->out.info_ctr->ctr.ctr0->count; + return WERR_OK; + } + case 1: + { + unsigned int i; + struct srvsvc_NetShareCtr1 *ctr1; + + ctr1 = talloc(mem_ctx, struct srvsvc_NetShareCtr1); + W_ERROR_HAVE_NO_MEMORY(ctr1); + + ctr1->count = numshares; + ctr1->array = NULL; + + if (ctr1->count == 0) { + r->out.info_ctr->ctr.ctr1 = ctr1; + return WERR_OK; + } + + ctr1->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo1, ctr1->count); + W_ERROR_HAVE_NO_MEMORY(ctr1->array); + + for (i=0; i < ctr1->count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + info.info1 = &ctr1->array[i]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + talloc_free(scfg); + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr1 = ctr1; + *r->out.totalentries = r->out.info_ctr->ctr.ctr1->count; + + return WERR_OK; + } + case 2: + { + unsigned int i; + struct srvsvc_NetShareCtr2 *ctr2; + + SRVSVC_CHECK_ADMIN_ACCESS; + + ctr2 = talloc(mem_ctx, struct srvsvc_NetShareCtr2); + W_ERROR_HAVE_NO_MEMORY(ctr2); + + ctr2->count = numshares; + ctr2->array = NULL; + + if (ctr2->count == 0) { + r->out.info_ctr->ctr.ctr2 = ctr2; + return WERR_OK; + } + + ctr2->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo2, ctr2->count); + W_ERROR_HAVE_NO_MEMORY(ctr2->array); + + for (i=0; i < ctr2->count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + info.info2 = &ctr2->array[i]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + talloc_free(scfg); + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr2 = ctr2; + *r->out.totalentries = r->out.info_ctr->ctr.ctr2->count; + + return WERR_OK; + } + case 501: + { + unsigned int i; + struct srvsvc_NetShareCtr501 *ctr501; + + SRVSVC_CHECK_ADMIN_ACCESS; + + ctr501 = talloc(mem_ctx, struct srvsvc_NetShareCtr501); + W_ERROR_HAVE_NO_MEMORY(ctr501); + + ctr501->count = numshares; + ctr501->array = NULL; + + if (ctr501->count == 0) { + r->out.info_ctr->ctr.ctr501 = ctr501; + return WERR_OK; + } + + ctr501->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo501, ctr501->count); + W_ERROR_HAVE_NO_MEMORY(ctr501->array); + + for (i=0; i < ctr501->count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + info.info501 = &ctr501->array[i]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + talloc_free(scfg); + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr501 = ctr501; + *r->out.totalentries = r->out.info_ctr->ctr.ctr501->count; + + return WERR_OK; + } + case 502: + { + unsigned int i; + struct srvsvc_NetShareCtr502 *ctr502; + + SRVSVC_CHECK_ADMIN_ACCESS; + + ctr502 = talloc(mem_ctx, struct srvsvc_NetShareCtr502); + W_ERROR_HAVE_NO_MEMORY(ctr502); + + ctr502->count = numshares; + ctr502->array = NULL; + + if (ctr502->count == 0) { + r->out.info_ctr->ctr.ctr502 = ctr502; + return WERR_OK; + } + + ctr502->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo502, ctr502->count); + W_ERROR_HAVE_NO_MEMORY(ctr502->array); + + for (i=0; i < ctr502->count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + info.info502 = &ctr502->array[i]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + talloc_free(scfg); + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr502 = ctr502; + *r->out.totalentries = r->out.info_ctr->ctr.ctr502->count; + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetShareGetInfo +*/ +static WERROR dcesrv_srvsvc_NetShareGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareGetInfo *r) +{ + NTSTATUS nterr; + struct share_context *sctx = NULL; + struct share_config *scfg = NULL; + + ZERO_STRUCTP(r->out.info); + + /* TODO: - access check + */ + + if (strcmp("", r->in.share_name) == 0) { + return WERR_INVALID_PARAMETER; + } + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + nterr = share_get_config(mem_ctx, sctx, r->in.share_name, &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + switch (r->in.level) { + case 0: + { + WERROR status; + union srvsvc_NetShareInfo info; + + info.info0 = talloc(mem_ctx, struct srvsvc_NetShareInfo0); + W_ERROR_HAVE_NO_MEMORY(info.info0); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info0 = info.info0; + return WERR_OK; + } + case 1: + { + WERROR status; + union srvsvc_NetShareInfo info; + + info.info1 = talloc(mem_ctx, struct srvsvc_NetShareInfo1); + W_ERROR_HAVE_NO_MEMORY(info.info1); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info1 = info.info1; + return WERR_OK; + } + case 2: + { + WERROR status; + union srvsvc_NetShareInfo info; + + SRVSVC_CHECK_ADMIN_ACCESS; + + info.info2 = talloc(mem_ctx, struct srvsvc_NetShareInfo2); + W_ERROR_HAVE_NO_MEMORY(info.info2); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info2 = info.info2; + return WERR_OK; + } + case 501: + { + WERROR status; + union srvsvc_NetShareInfo info; + + info.info501 = talloc(mem_ctx, struct srvsvc_NetShareInfo501); + W_ERROR_HAVE_NO_MEMORY(info.info501); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info501 = info.info501; + return WERR_OK; + } + case 502: + { + WERROR status; + union srvsvc_NetShareInfo info; + + SRVSVC_CHECK_ADMIN_ACCESS; + + info.info502 = talloc(mem_ctx, struct srvsvc_NetShareInfo502); + W_ERROR_HAVE_NO_MEMORY(info.info502); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info502 = info.info502; + return WERR_OK; + } + case 1005: + { + WERROR status; + union srvsvc_NetShareInfo info; + + info.info1005 = talloc(mem_ctx, struct srvsvc_NetShareInfo1005); + W_ERROR_HAVE_NO_MEMORY(info.info1005); + + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.level, &info); + if (!W_ERROR_IS_OK(status)) { + return status; + } + + r->out.info->info1005 = info.info1005; + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + +static WERROR dcesrv_srvsvc_fill_share_info(struct share_info *info, int *count, + const char *share_name, int level, + const char *name, + const char *path, + const char *comment, + const char *password, + enum srvsvc_ShareType type, + int32_t max_users, + uint32_t csc_policy, + struct security_descriptor *sd) +{ + unsigned int i = 0; + + if (level == 501) { + info[i].name = SHARE_CSC_POLICY; + info[i].type = SHARE_INFO_INT; + info[i].value = talloc(info, int); + *((int *)info[i].value) = csc_policy; + i++; + } + + switch(level) { + + case 502: + /* TODO: check if unknown is csc_policy */ + + /* TODO: security descriptor */ + + case 2: + if (path && path[0]) { + info[i].name = SHARE_PATH; + info[i].type = SHARE_INFO_STRING; + + /* Windows will send a path in a form of C:\example\path */ + if (path[1] == ':') { + info[i].value = talloc_strdup(info, &path[2]); + } else { + /* very strange let's try to set as is */ + info[i].value = talloc_strdup(info, path); + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + all_string_sub((char *)info[i].value, "\\", "/", 0); + + i++; + } + + if (password && password[0]) { + info[i].name = SHARE_PASSWORD; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, password); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + info[i].name = SHARE_MAX_CONNECTIONS; + info[i].type = SHARE_INFO_INT; + info[i].value = talloc(info, int); + *((int *)info[i].value) = max_users; + i++; + + FALL_THROUGH; + case 501: + case 1: + info[i].name = SHARE_TYPE; + info[i].type = SHARE_INFO_STRING; + switch (type) { + case 0x00: + info[i].value = talloc_strdup(info, "DISK"); + break; + case 0x01: + info[i].value = talloc_strdup(info, "PRINTER"); + break; + case 0x03: + info[i].value = talloc_strdup(info, "IPC"); + break; + default: + return WERR_INVALID_PARAMETER; + } + W_ERROR_HAVE_NO_MEMORY(info[i].value); + i++; + + FALL_THROUGH; + case 1004: + if (comment) { + info[i].name = SHARE_COMMENT; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, comment); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + + i++; + } + + FALL_THROUGH; + case 0: + if (name && + strcasecmp(share_name, name) != 0) { + info[i].name = SHARE_NAME; + info[i].type = SHARE_INFO_STRING; + info[i].value = talloc_strdup(info, name); + W_ERROR_HAVE_NO_MEMORY(info[i].value); + i++; + } + + break; + + default: + return WERR_INVALID_LEVEL; + } + + *count = i; + + return WERR_OK; +} + +/* + srvsvc_NetShareSetInfo +*/ +static WERROR dcesrv_srvsvc_NetShareSetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareSetInfo *r) +{ + NTSTATUS nterr; + WERROR status; + struct share_context *sctx = NULL; + struct share_info *info; + int count; + + /* TODO: - access check + */ + + /* there are no more than 10 options in all struct srvsvc_NetShareInfoXXX */ + info = talloc_array(mem_ctx, struct share_info, 10); + W_ERROR_HAVE_NO_MEMORY(info); + + if (strcmp("", r->in.share_name) == 0) { + return WERR_INVALID_PARAMETER; + } + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + switch (r->in.level) { + case 0: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + r->in.info->info0->name, + NULL, + NULL, + NULL, + 0, + 0, + 0, + NULL); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 1: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + r->in.info->info1->name, + NULL, + r->in.info->info1->comment, + NULL, + r->in.info->info1->type, + 0, + 0, + NULL); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 2: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + r->in.info->info2->name, + r->in.info->info2->path, + r->in.info->info2->comment, + r->in.info->info2->password, + r->in.info->info2->type, + r->in.info->info2->max_users, + 0, + NULL); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 501: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + r->in.info->info501->name, + NULL, + r->in.info->info501->comment, + NULL, + r->in.info->info501->type, + 0, + r->in.info->info501->csc_policy, + NULL); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 502: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + r->in.info->info502->name, + r->in.info->info502->path, + r->in.info->info502->comment, + r->in.info->info502->password, + r->in.info->info502->type, + r->in.info->info502->max_users, + 0, + r->in.info->info502->sd_buf.sd); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 1004: + { + status = dcesrv_srvsvc_fill_share_info(info, &count, + r->in.share_name, r->in.level, + NULL, + NULL, + r->in.info->info1004->comment, + NULL, + 0, + 0, + 0, + NULL); + if (!W_ERROR_EQUAL(status, WERR_OK)) { + return status; + } + break; + } + case 1005: + { + /* r->in.info.dfs_flags; */ + + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } + + nterr = share_set(sctx, r->in.share_name, info, count); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + if (r->in.parm_error) { + r->out.parm_error = r->in.parm_error; + } + + return WERR_OK; +} + + +/* + srvsvc_NetShareDelSticky +*/ +static WERROR dcesrv_srvsvc_NetShareDelSticky(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareDelSticky *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetShareCheck +*/ +static WERROR dcesrv_srvsvc_NetShareCheck(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareCheck *r) +{ + NTSTATUS nterr; + struct share_context *sctx = NULL; + struct share_config *scfg = NULL; + char *device; + const char **names; + int count; + int i; + + *r->out.type = 0; + + /* TODO: - access check + */ + + if (strcmp("", r->in.device_name) == 0) { + *r->out.type = STYPE_IPC; + return WERR_OK; + } + + /* copy the path skipping C:\ */ + if (strncasecmp(r->in.device_name, "C:", 2) == 0) { + device = talloc_strdup(mem_ctx, &r->in.device_name[2]); + } else { + /* no chance we have a share that doesn't start with C:\ */ + return WERR_NERR_DEVICENOTSHARED; + } + all_string_sub(device, "\\", "/", 0); + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + nterr = share_list_all(mem_ctx, sctx, &count, &names); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + for (i = 0; i < count; i++) { + const char *path; + const char *type; + + nterr = share_get_config(mem_ctx, sctx, names[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + path = share_string_option(mem_ctx, scfg, SHARE_PATH, NULL); + if (!path) continue; + + if (strcmp(device, path) == 0) { + type = share_string_option(mem_ctx, scfg, SHARE_TYPE, NULL); + if (!type) continue; + + if (strcmp(type, "DISK") == 0) { + *r->out.type = STYPE_DISKTREE; + return WERR_OK; + } + + if (strcmp(type, "IPC") == 0) { + *r->out.type = STYPE_IPC; + return WERR_OK; + } + + if (strcmp(type, "PRINTER") == 0) { + *r->out.type = STYPE_PRINTQ; + return WERR_OK; + } + } + } + + return WERR_NERR_DEVICENOTSHARED; +} + + +/* + srvsvc_NetSrvGetInfo +*/ +static WERROR dcesrv_srvsvc_NetSrvGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSrvGetInfo *r) +{ + struct dcesrv_context *dce_ctx = dce_call->conn->dce_ctx; + struct dcerpc_server_info *server_info = lpcfg_dcerpc_server_info(mem_ctx, dce_ctx->lp_ctx); + const struct loadparm_substitution *lp_sub = + lpcfg_noop_substitution(); + + ZERO_STRUCTP(r->out.info); + + switch (r->in.level) { + case 100: + { + struct srvsvc_NetSrvInfo100 *info100; + + info100 = talloc(mem_ctx, struct srvsvc_NetSrvInfo100); + W_ERROR_HAVE_NO_MEMORY(info100); + + info100->platform_id = dcesrv_common_get_platform_id(mem_ctx, dce_ctx); + info100->server_name = dcesrv_common_get_server_name(mem_ctx, dce_ctx, r->in.server_unc); + W_ERROR_HAVE_NO_MEMORY(info100->server_name); + + r->out.info->info100 = info100; + return WERR_OK; + } + case 101: + { + struct srvsvc_NetSrvInfo101 *info101; + + info101 = talloc(mem_ctx, struct srvsvc_NetSrvInfo101); + W_ERROR_HAVE_NO_MEMORY(info101); + + info101->platform_id = dcesrv_common_get_platform_id(mem_ctx, dce_ctx); + info101->server_name = dcesrv_common_get_server_name(mem_ctx, dce_ctx, r->in.server_unc); + W_ERROR_HAVE_NO_MEMORY(info101->server_name); + + info101->version_major = server_info->version_major; + info101->version_minor = server_info->version_minor; + info101->server_type = dcesrv_common_get_server_type(mem_ctx, dce_call->event_ctx, dce_ctx); + info101->comment = lpcfg_server_string(dce_ctx->lp_ctx, lp_sub, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(info101->comment); + + r->out.info->info101 = info101; + return WERR_OK; + } + case 102: + { + struct srvsvc_NetSrvInfo102 *info102; + + info102 = talloc(mem_ctx, struct srvsvc_NetSrvInfo102); + W_ERROR_HAVE_NO_MEMORY(info102); + + info102->platform_id = dcesrv_common_get_platform_id(mem_ctx, dce_ctx); + info102->server_name = dcesrv_common_get_server_name(mem_ctx, dce_ctx, r->in.server_unc); + W_ERROR_HAVE_NO_MEMORY(info102->server_name); + + info102->version_major = server_info->version_major; + info102->version_minor = server_info->version_minor; + info102->server_type = dcesrv_common_get_server_type(mem_ctx, dce_call->event_ctx, dce_ctx); + info102->comment = lpcfg_server_string(dce_ctx->lp_ctx, lp_sub, mem_ctx); + W_ERROR_HAVE_NO_MEMORY(info102->comment); + + info102->users = dcesrv_common_get_users(mem_ctx, dce_ctx); + info102->disc = dcesrv_common_get_disc(mem_ctx, dce_ctx); + info102->hidden = dcesrv_common_get_hidden(mem_ctx, dce_ctx); + info102->announce = dcesrv_common_get_announce(mem_ctx, dce_ctx); + info102->anndelta = dcesrv_common_get_anndelta(mem_ctx, dce_ctx); + info102->licenses = dcesrv_common_get_licenses(mem_ctx, dce_ctx); + info102->userpath = dcesrv_common_get_userpath(mem_ctx, dce_ctx); + W_ERROR_HAVE_NO_MEMORY(info102->userpath); + + r->out.info->info102 = info102; + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetSrvSetInfo +*/ +static WERROR dcesrv_srvsvc_NetSrvSetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSrvSetInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetDiskEnum +*/ +static WERROR dcesrv_srvsvc_NetDiskEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetDiskEnum *r) +{ + r->out.info->disks = NULL; + r->out.info->count = 0; + *r->out.totalentries = 0; + + switch (r->in.level) { + case 0: + { + /* we can safely hardcode the reply and report we have only one disk (C:) */ + /* for some reason Windows wants 2 entries with the second being empty */ + r->out.info->disks = talloc_array(mem_ctx, struct srvsvc_NetDiskInfo0, 2); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks); + r->out.info->count = 2; + + r->out.info->disks[0].disk = talloc_strdup(mem_ctx, "C:"); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[0].disk); + + r->out.info->disks[1].disk = talloc_strdup(mem_ctx, ""); + W_ERROR_HAVE_NO_MEMORY(r->out.info->disks[1].disk); + + *r->out.totalentries = 1; + r->out.resume_handle = r->in.resume_handle; + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetServerStatisticsGet +*/ +static WERROR dcesrv_srvsvc_NetServerStatisticsGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetServerStatisticsGet *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetTransportAdd +*/ +static WERROR dcesrv_srvsvc_NetTransportAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetTransportAdd *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetTransportEnum +*/ +static WERROR dcesrv_srvsvc_NetTransportEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetTransportEnum *r) +{ + r->out.transports->level = r->in.transports->level; + *r->out.totalentries = 0; + if (r->out.resume_handle) { + *r->out.resume_handle = 0; + } + + switch (r->in.transports->level) { + case 0: + { + r->out.transports->ctr.ctr0 = talloc(mem_ctx, struct srvsvc_NetTransportCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.transports->ctr.ctr0); + + r->out.transports->ctr.ctr0->count = 0; + r->out.transports->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 1: + { + r->out.transports->ctr.ctr1 = talloc(mem_ctx, struct srvsvc_NetTransportCtr1); + W_ERROR_HAVE_NO_MEMORY(r->out.transports->ctr.ctr1); + + r->out.transports->ctr.ctr1->count = 0; + r->out.transports->ctr.ctr1->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 2: + { + r->out.transports->ctr.ctr2 = talloc(mem_ctx, struct srvsvc_NetTransportCtr2); + W_ERROR_HAVE_NO_MEMORY(r->out.transports->ctr.ctr2); + + r->out.transports->ctr.ctr2->count = 0; + r->out.transports->ctr.ctr2->array = NULL; + + return WERR_NOT_SUPPORTED; + } + case 3: + { + r->out.transports->ctr.ctr3 = talloc(mem_ctx, struct srvsvc_NetTransportCtr3); + W_ERROR_HAVE_NO_MEMORY(r->out.transports->ctr.ctr3); + + r->out.transports->ctr.ctr3->count = 0; + r->out.transports->ctr.ctr3->array = NULL; + + return WERR_NOT_SUPPORTED; + } + default: + return WERR_INVALID_LEVEL; + } +} + +/* + srvsvc_NetTransportDel +*/ +static WERROR dcesrv_srvsvc_NetTransportDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetTransportDel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetRemoteTOD +*/ +static WERROR dcesrv_srvsvc_NetRemoteTOD(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetRemoteTOD *r) +{ + struct timeval tval; + time_t t; + struct tm tm; + struct srvsvc_NetRemoteTODInfo *info; + + info = talloc(mem_ctx, struct srvsvc_NetRemoteTODInfo); + W_ERROR_HAVE_NO_MEMORY(info); + + GetTimeOfDay(&tval); + t = tval.tv_sec; + + gmtime_r(&t, &tm); + + info->elapsed = t; + /* TODO: fake the uptime: just return the milliseconds till 0:00:00 today */ + info->msecs = (tm.tm_hour*60*60*1000) + + (tm.tm_min*60*1000) + + (tm.tm_sec*1000) + + (tval.tv_usec/1000); + info->hours = tm.tm_hour; + info->mins = tm.tm_min; + info->secs = tm.tm_sec; + info->hunds = tval.tv_usec/10000; + info->timezone = get_time_zone(t)/60; + info->tinterval = 310; /* just return the same as windows */ + info->day = tm.tm_mday; + info->month = tm.tm_mon + 1; + info->year = tm.tm_year + 1900; + info->weekday = tm.tm_wday; + + *r->out.info = info; + + return WERR_OK; +} + +/* + srvsvc_NetPathType +*/ +static WERROR dcesrv_srvsvc_NetPathType(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetPathType *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetPathCanonicalize +*/ +static WERROR dcesrv_srvsvc_NetPathCanonicalize(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetPathCanonicalize *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetPathCompare +*/ +static WERROR dcesrv_srvsvc_NetPathCompare(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetPathCompare *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetNameValidate +*/ +static WERROR dcesrv_srvsvc_NetNameValidate(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetNameValidate *r) +{ + int len; + + if ((r->in.flags != 0x0) && (r->in.flags != 0x80000000)) { + return WERR_INVALID_NAME; + } + + switch (r->in.name_type) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + return WERR_NOT_SUPPORTED; + + case 9: /* validate share name */ + + len = strlen_m(r->in.name); + if ((r->in.flags == 0x0) && (len > 81)) { + return WERR_INVALID_NAME; + } + if ((r->in.flags == 0x80000000) && (len > 13)) { + return WERR_INVALID_NAME; + } + if (! dcesrv_common_validate_share_name(mem_ctx, r->in.name)) { + return WERR_INVALID_NAME; + } + return WERR_OK; + + case 10: + case 11: + case 12: + case 13: + return WERR_NOT_SUPPORTED; + default: + return WERR_INVALID_PARAMETER; + } +} + + +/* + srvsvc_NetPRNameCompare +*/ +static WERROR dcesrv_srvsvc_NetPRNameCompare(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetPRNameCompare *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetShareEnum +*/ +static WERROR dcesrv_srvsvc_NetShareEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareEnum *r) +{ + NTSTATUS nterr; + int numshares = 0; + const char **snames; + struct share_context *sctx; + struct share_config *scfg; + struct dcesrv_context *dce_ctx = dce_call->conn->dce_ctx; + + *r->out.totalentries = 0; + + /* TODO: - paging of results + */ + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + nterr = share_list_all(mem_ctx, sctx, &numshares, &snames); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + switch (r->in.info_ctr->level) { + case 0: + { + unsigned int i, y = 0; + unsigned int count; + struct srvsvc_NetShareCtr0 *ctr0; + + ctr0 = talloc(mem_ctx, struct srvsvc_NetShareCtr0); + W_ERROR_HAVE_NO_MEMORY(ctr0); + + count = numshares; + ctr0->count = count; + ctr0->array = NULL; + + if (ctr0->count == 0) { + r->out.info_ctr->ctr.ctr0 = ctr0; + return WERR_OK; + } + + ctr0->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo0, count); + W_ERROR_HAVE_NO_MEMORY(ctr0->array); + + for (i=0; i < count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + enum srvsvc_ShareType type; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + + type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + if (type & STYPE_HIDDEN) { + ctr0->count--; + talloc_free(scfg); + continue; + } + + info.info0 = &ctr0->array[y]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + W_ERROR_NOT_OK_RETURN(status); + talloc_free(scfg); + y++; + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr0 = ctr0; + *r->out.totalentries = r->out.info_ctr->ctr.ctr0->count; + + return WERR_OK; + } + case 1: + { + unsigned int i, y = 0; + unsigned int count; + struct srvsvc_NetShareCtr1 *ctr1; + + ctr1 = talloc(mem_ctx, struct srvsvc_NetShareCtr1); + W_ERROR_HAVE_NO_MEMORY(ctr1); + + count = numshares; + ctr1->count = count; + ctr1->array = NULL; + + if (ctr1->count == 0) { + r->out.info_ctr->ctr.ctr1 = ctr1; + return WERR_OK; + } + + ctr1->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo1, count); + W_ERROR_HAVE_NO_MEMORY(ctr1->array); + + for (i=0; i < count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + enum srvsvc_ShareType type; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + + type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + if (type & STYPE_HIDDEN) { + ctr1->count--; + talloc_free(scfg); + continue; + } + + info.info1 = &ctr1->array[y]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + W_ERROR_NOT_OK_RETURN(status); + talloc_free(scfg); + y++; + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr1 = ctr1; + *r->out.totalentries = r->out.info_ctr->ctr.ctr1->count; + + return WERR_OK; + } + case 2: + { + unsigned int i, y = 0; + unsigned int count; + struct srvsvc_NetShareCtr2 *ctr2; + + SRVSVC_CHECK_ADMIN_ACCESS; + + ctr2 = talloc(mem_ctx, struct srvsvc_NetShareCtr2); + W_ERROR_HAVE_NO_MEMORY(ctr2); + + count = numshares; + ctr2->count = count; + ctr2->array = NULL; + + if (ctr2->count == 0) { + r->out.info_ctr->ctr.ctr2 = ctr2; + return WERR_OK; + } + + ctr2->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo2, count); + W_ERROR_HAVE_NO_MEMORY(ctr2->array); + + for (i=0; i < count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + enum srvsvc_ShareType type; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + + type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + if (type & STYPE_HIDDEN) { + ctr2->count--; + talloc_free(scfg); + continue; + } + + info.info2 = &ctr2->array[y]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + W_ERROR_NOT_OK_RETURN(status); + talloc_free(scfg); + y++; + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr2 = ctr2; + *r->out.totalentries = r->out.info_ctr->ctr.ctr2->count; + + return WERR_OK; + } + case 502: + { + unsigned int i, y = 0; + unsigned int count; + struct srvsvc_NetShareCtr502 *ctr502; + + SRVSVC_CHECK_ADMIN_ACCESS; + + ctr502 = talloc(mem_ctx, struct srvsvc_NetShareCtr502); + W_ERROR_HAVE_NO_MEMORY(ctr502); + + count = numshares; + ctr502->count = count; + ctr502->array = NULL; + + if (ctr502->count == 0) { + r->out.info_ctr->ctr.ctr502 = ctr502; + return WERR_OK; + } + + ctr502->array = talloc_array(mem_ctx, struct srvsvc_NetShareInfo502, count); + W_ERROR_HAVE_NO_MEMORY(ctr502->array); + + for (i=0; i < count; i++) { + WERROR status; + union srvsvc_NetShareInfo info; + enum srvsvc_ShareType type; + + nterr = share_get_config(mem_ctx, sctx, snames[i], &scfg); + if (!NT_STATUS_IS_OK(nterr)) { + DEBUG(1, ("ERROR: Service [%s] disappeared after enumeration", snames[i])); + return WERR_GEN_FAILURE; + } + + type = dcesrv_common_get_share_type(mem_ctx, dce_ctx, scfg); + if (type & STYPE_HIDDEN) { + ctr502->count--; + talloc_free(scfg); + continue; + } + + info.info502 = &ctr502->array[y]; + status = dcesrv_srvsvc_fiel_ShareInfo(dce_call, mem_ctx, scfg, r->in.info_ctr->level, &info); + W_ERROR_NOT_OK_RETURN(status); + talloc_free(scfg); + y++; + } + talloc_free(snames); + + r->out.info_ctr->ctr.ctr502 = ctr502; + *r->out.totalentries = r->out.info_ctr->ctr.ctr502->count; + + return WERR_OK; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + srvsvc_NetShareDelStart +*/ +static WERROR dcesrv_srvsvc_NetShareDelStart(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareDelStart *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetShareDelCommit +*/ +static WERROR dcesrv_srvsvc_NetShareDelCommit(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareDelCommit *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetGetFileSecurity +*/ +static WERROR dcesrv_srvsvc_NetGetFileSecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetGetFileSecurity *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct sec_desc_buf *sd_buf; + struct ntvfs_context *ntvfs_ctx = NULL; + struct ntvfs_request *ntvfs_req; + union smb_fileinfo *io; + NTSTATUS nt_status; + + nt_status = srvsvc_create_ntvfs_context(dce_call, mem_ctx, r->in.share, &ntvfs_ctx); + if (!NT_STATUS_IS_OK(nt_status)) return ntstatus_to_werror(nt_status); + + ntvfs_req = ntvfs_request_create(ntvfs_ctx, mem_ctx, + session_info, + 0, + dce_call->time, + NULL, NULL, 0); + W_ERROR_HAVE_NO_MEMORY(ntvfs_req); + + sd_buf = talloc(mem_ctx, struct sec_desc_buf); + W_ERROR_HAVE_NO_MEMORY(sd_buf); + + io = talloc(mem_ctx, union smb_fileinfo); + W_ERROR_HAVE_NO_MEMORY(io); + + io->query_secdesc.level = RAW_FILEINFO_SEC_DESC; + io->query_secdesc.in.file.path = r->in.file; + io->query_secdesc.in.secinfo_flags = r->in.securityinformation; + + nt_status = ntvfs_qpathinfo(ntvfs_req, io); + if (!NT_STATUS_IS_OK(nt_status)) return ntstatus_to_werror(nt_status); + + sd_buf->sd = io->query_secdesc.out.sd; + + *r->out.sd_buf = sd_buf; + return WERR_OK; +} + + +/* + srvsvc_NetSetFileSecurity +*/ +static WERROR dcesrv_srvsvc_NetSetFileSecurity(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSetFileSecurity *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct ntvfs_context *ntvfs_ctx; + struct ntvfs_request *ntvfs_req; + union smb_setfileinfo *io; + NTSTATUS nt_status; + + nt_status = srvsvc_create_ntvfs_context(dce_call, mem_ctx, r->in.share, &ntvfs_ctx); + if (!NT_STATUS_IS_OK(nt_status)) return ntstatus_to_werror(nt_status); + + ntvfs_req = ntvfs_request_create(ntvfs_ctx, mem_ctx, + session_info, + 0, + dce_call->time, + NULL, NULL, 0); + W_ERROR_HAVE_NO_MEMORY(ntvfs_req); + + io = talloc(mem_ctx, union smb_setfileinfo); + W_ERROR_HAVE_NO_MEMORY(io); + + io->set_secdesc.level = RAW_SFILEINFO_SEC_DESC; + io->set_secdesc.in.file.path = r->in.file; + io->set_secdesc.in.secinfo_flags = r->in.securityinformation; + io->set_secdesc.in.sd = r->in.sd_buf->sd; + + nt_status = ntvfs_setpathinfo(ntvfs_req, io); + if (!NT_STATUS_IS_OK(nt_status)) return ntstatus_to_werror(nt_status); + + return WERR_OK; +} + + +/* + srvsvc_NetServerTransportAddEx +*/ +static WERROR dcesrv_srvsvc_NetServerTransportAddEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetServerTransportAddEx *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NetServerSetServiceBitsEx +*/ +static WERROR dcesrv_srvsvc_NetServerSetServiceBitsEx(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetServerSetServiceBitsEx *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSGETVERSION +*/ +static WERROR dcesrv_srvsvc_NETRDFSGETVERSION(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSGETVERSION *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSCREATELOCALPARTITION +*/ +static WERROR dcesrv_srvsvc_NETRDFSCREATELOCALPARTITION(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSCREATELOCALPARTITION *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSDELETELOCALPARTITION +*/ +static WERROR dcesrv_srvsvc_NETRDFSDELETELOCALPARTITION(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSDELETELOCALPARTITION *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSSETLOCALVOLUMESTATE +*/ +static WERROR dcesrv_srvsvc_NETRDFSSETLOCALVOLUMESTATE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSSETLOCALVOLUMESTATE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSSETSERVERINFO +*/ +static WERROR dcesrv_srvsvc_NETRDFSSETSERVERINFO(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSSETSERVERINFO *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSCREATEEXITPOINT +*/ +static WERROR dcesrv_srvsvc_NETRDFSCREATEEXITPOINT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSCREATEEXITPOINT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSDELETEEXITPOINT +*/ +static WERROR dcesrv_srvsvc_NETRDFSDELETEEXITPOINT(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSDELETEEXITPOINT *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSMODIFYPREFIX +*/ +static WERROR dcesrv_srvsvc_NETRDFSMODIFYPREFIX(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSMODIFYPREFIX *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSFIXLOCALVOLUME +*/ +static WERROR dcesrv_srvsvc_NETRDFSFIXLOCALVOLUME(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSFIXLOCALVOLUME *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRDFSMANAGERREPORTSITEINFO +*/ +static WERROR dcesrv_srvsvc_NETRDFSMANAGERREPORTSITEINFO(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRDFSMANAGERREPORTSITEINFO *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + srvsvc_NETRSERVERTRANSPORTDELEX +*/ +static WERROR dcesrv_srvsvc_NETRSERVERTRANSPORTDELEX(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRSERVERTRANSPORTDELEX *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* + srvsvc_NetShareDel +*/ +static WERROR dcesrv_srvsvc_NetShareDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetShareDel *r) +{ + NTSTATUS nterr; + struct share_context *sctx; + + nterr = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + nterr = share_remove(sctx, r->in.share_name); + if (!NT_STATUS_IS_OK(nterr)) { + return ntstatus_to_werror(nterr); + } + + return WERR_OK; +} + +/* + srvsvc_NetSetServiceBits +*/ +static WERROR dcesrv_srvsvc_NetSetServiceBits(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NetSetServiceBits *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* + srvsvc_NETRPRNAMECANONICALIZE +*/ +static WERROR dcesrv_srvsvc_NETRPRNAMECANONICALIZE(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct srvsvc_NETRPRNAMECANONICALIZE *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_srvsvc_s.c" diff --git a/source4/rpc_server/srvsvc/srvsvc_ntvfs.c b/source4/rpc_server/srvsvc/srvsvc_ntvfs.c new file mode 100644 index 0000000..cbd0eb3 --- /dev/null +++ b/source4/rpc_server/srvsvc/srvsvc_ntvfs.c @@ -0,0 +1,139 @@ +/* + Unix SMB/CIFS implementation. + + srvsvc pipe ntvfs helper functions + + Copyright (C) Stefan (metze) Metzmacher 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 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#include "includes.h" +#include "ntvfs/ntvfs.h" +#include "rpc_server/dcerpc_server.h" +#include "param/param.h" +#include "rpc_server/srvsvc/proto.h" + +struct srvsvc_ntvfs_ctx { + struct ntvfs_context *ntvfs; +}; + +static int srvsvc_ntvfs_ctx_destructor(struct srvsvc_ntvfs_ctx *c) +{ + ntvfs_disconnect(c->ntvfs); + return 0; +} + +NTSTATUS srvsvc_create_ntvfs_context(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + const char *share, + struct ntvfs_context **_ntvfs) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct imessaging_context *imsg_ctx = + dcesrv_imessaging_context(dce_call->conn); + struct server_id server_id = dcesrv_server_id(dce_call->conn); + NTSTATUS status; + struct srvsvc_ntvfs_ctx *c; + struct ntvfs_request *ntvfs_req; + enum ntvfs_type type; + struct share_context *sctx; + struct share_config *scfg; + char *sharetype; + union smb_tcon tcon; + const struct tsocket_address *local_address; + const struct tsocket_address *remote_address; + + status = share_get_context(mem_ctx, dce_call->conn->dce_ctx->lp_ctx, &sctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = share_get_config(mem_ctx, sctx, share, &scfg); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("srvsvc_create_ntvfs_context: couldn't find service %s\n", share)); + return status; + } + +#if 0 /* TODO: fix access cecking */ + if (!socket_check_access(dce_call->connection->socket, + scfg->name, + share_string_list_option(scfg, SHARE_HOSTS_ALLOW), + share_string_list_option(scfg, SHARE_HOSTS_DENY))) { + return NT_STATUS_ACCESS_DENIED; + } +#endif + + /* work out what sort of connection this is */ + sharetype = share_string_option(mem_ctx, scfg, SHARE_TYPE, SHARE_TYPE_DEFAULT); + if (sharetype && strcmp(sharetype, "IPC") == 0) { + type = NTVFS_IPC; + } else if (sharetype && strcmp(sharetype, "PRINTER")) { + type = NTVFS_PRINT; + } else { + type = NTVFS_DISK; + } + + TALLOC_FREE(sharetype); + + c = talloc(mem_ctx, struct srvsvc_ntvfs_ctx); + NT_STATUS_HAVE_NO_MEMORY(c); + + /* init ntvfs function pointers */ + status = ntvfs_init_connection(c, scfg, type, + PROTOCOL_NT1, + 0,/* ntvfs_client_caps */ + dce_call->event_ctx, + imsg_ctx, + dce_call->conn->dce_ctx->lp_ctx, + server_id, + &c->ntvfs); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("srvsvc_create_ntvfs_context: ntvfs_init_connection failed for service %s\n", + scfg->name)); + return status; + } + talloc_set_destructor(c, srvsvc_ntvfs_ctx_destructor); + + /* + * NOTE: we only set the addr callbacks as we're not interesseted in oplocks or in getting file handles + */ + local_address = dcesrv_connection_get_local_address(dce_call->conn); + remote_address = dcesrv_connection_get_remote_address(dce_call->conn); + status = ntvfs_set_addresses(c->ntvfs, local_address, remote_address); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("srvsvc_create_ntvfs_context: NTVFS failed to set the addr callbacks!\n")); + return status; + } + + ntvfs_req = ntvfs_request_create(c->ntvfs, mem_ctx, + session_info, + 0, /* TODO: fill in PID */ + dce_call->time, + NULL, NULL, 0); + NT_STATUS_HAVE_NO_MEMORY(ntvfs_req); + + /* Invoke NTVFS connection hook */ + tcon.tcon.level = RAW_TCON_TCON; + ZERO_STRUCT(tcon.tcon.in); + tcon.tcon.in.service = scfg->name; + status = ntvfs_connect(ntvfs_req, &tcon); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("srvsvc_create_ntvfs_context: NTVFS ntvfs_connect() failed!\n")); + return status; + } + + *_ntvfs = c->ntvfs; + return NT_STATUS_OK; +} diff --git a/source4/rpc_server/tests/rpc_dns_server_dnsutils_test.c b/source4/rpc_server/tests/rpc_dns_server_dnsutils_test.c new file mode 100644 index 0000000..cdb245e --- /dev/null +++ b/source4/rpc_server/tests/rpc_dns_server_dnsutils_test.c @@ -0,0 +1,304 @@ +/* + * Unit tests for source4/rpc_server/dnsserver/dnsutils.c + * + * Copyright (C) Catalyst.NET Ltd 2018 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include <stdarg.h> + * #include <stddef.h> + * #include <setjmp.h> + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +#include <stdarg.h> +#include <stddef.h> +#include <setjmp.h> +#include <cmocka.h> + + +#include "../dnsserver/dnsutils.c" + + +/* + * Test setting of an empty ZONE_MASTER_SERVERS property + */ +static void test_dnsserver_init_zoneinfo_master_servers_empty(void **state) +{ + struct dnsserver_zone *zone = NULL; + struct dnsserver_serverinfo *serverinfo = NULL; + struct dnsserver_zoneinfo *zoneinfo = NULL; + struct dnsp_DnsProperty *property = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Setup the zone data + */ + zone = talloc_zero(ctx, struct dnsserver_zone); + assert_non_null(zone); + zone->name = "test"; + + /* + * Set up an empty ZONE_MASTER_SERVERS property + */ + property = talloc_zero_array(ctx, struct dnsp_DnsProperty, 1); + assert_non_null(property); + property->id = DSPROPERTY_ZONE_MASTER_SERVERS; + property->data.master_servers.addrCount = 0; + property->data.master_servers.addrArray = NULL; + + zone->tmp_props = property; + zone->num_props = 1; + + + /* + * Setup the server info + */ + serverinfo = talloc_zero(ctx, struct dnsserver_serverinfo); + assert_non_null(serverinfo); + + /* + * call dnsserver_init_zoneinfo + */ + zoneinfo = dnsserver_init_zoneinfo(zone, serverinfo); + + /* + * Check results + */ + assert_non_null(zoneinfo); + assert_non_null(zoneinfo->aipLocalMasters); + assert_int_equal(zoneinfo->aipLocalMasters->AddrCount, 0); + assert_null(zoneinfo->aipLocalMasters->AddrArray); + + TALLOC_FREE(ctx); +} + +/* + * Test setting of a non empty ZONE_MASTER_SERVERS property + */ +static void test_dnsserver_init_zoneinfo_master_servers(void **state) +{ + struct dnsserver_zone *zone = NULL; + struct dnsserver_serverinfo *serverinfo = NULL; + struct dnsserver_zoneinfo *zoneinfo = NULL; + struct dnsp_DnsProperty *property = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Setup the zone data + */ + zone = talloc_zero(ctx, struct dnsserver_zone); + assert_non_null(zone); + zone->name = "test"; + + /* + * Set up an empty ZONE_MASTER_SERVERS property + */ + property = talloc_zero_array(ctx, struct dnsp_DnsProperty, 1); + assert_non_null(property); + property->id = DSPROPERTY_ZONE_MASTER_SERVERS; + property->data.master_servers.addrCount = 4; + property->data.master_servers.addrArray = + talloc_zero_array(ctx, uint32_t, 4); + property->data.master_servers.addrArray[0] = 1000; + property->data.master_servers.addrArray[1] = 1001; + property->data.master_servers.addrArray[2] = 1002; + property->data.master_servers.addrArray[3] = 1003; + + zone->tmp_props = property; + zone->num_props = 1; + + + /* + * Setup the server info + */ + serverinfo = talloc_zero(ctx, struct dnsserver_serverinfo); + assert_non_null(serverinfo); + + /* + * call dnsserver_init_zoneinfo + */ + zoneinfo = dnsserver_init_zoneinfo(zone, serverinfo); + + /* + * Check results + */ + assert_non_null(zoneinfo); + assert_non_null(zoneinfo->aipLocalMasters); + assert_int_equal(zoneinfo->aipLocalMasters->AddrCount, 4); + assert_non_null(zoneinfo->aipLocalMasters->AddrArray); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[0], 1000); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[1], 1001); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[2], 1002); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[3], 1003); + + /* + * Ensure that we're working with a copy of the property data + * and not a reference. + * The pointers should be different, and we should be able to change + * the values in the property without affecting the zoneinfo data + */ + assert_true(zoneinfo->aipLocalMasters->AddrArray != + property->data.master_servers.addrArray); + property->data.master_servers.addrArray[0] = 0; + property->data.master_servers.addrArray[1] = 1; + property->data.master_servers.addrArray[2] = 2; + property->data.master_servers.addrArray[3] = 3; + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[0], 1000); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[1], 1001); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[2], 1002); + assert_int_equal(zoneinfo->aipLocalMasters->AddrArray[3], 1003); + + TALLOC_FREE(ctx); +} + +/* + * Test setting of an empty ZONE_SCAVENGING_SERVERS property + */ +static void test_dnsserver_init_zoneinfo_scavenging_servers_empty(void **state) +{ + struct dnsserver_zone *zone = NULL; + struct dnsserver_serverinfo *serverinfo = NULL; + struct dnsserver_zoneinfo *zoneinfo = NULL; + struct dnsp_DnsProperty *property = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Setup the zone data + */ + zone = talloc_zero(ctx, struct dnsserver_zone); + assert_non_null(zone); + zone->name = "test"; + + property = talloc_zero_array(ctx, struct dnsp_DnsProperty, 1); + assert_non_null(property); + property->id = DSPROPERTY_ZONE_SCAVENGING_SERVERS; + property->data.servers.addrCount = 0; + property->data.servers.addrArray = NULL; + + zone->tmp_props = property; + zone->num_props = 1; + + + serverinfo = talloc_zero(ctx, struct dnsserver_serverinfo); + assert_non_null(serverinfo); + + zoneinfo = dnsserver_init_zoneinfo(zone, serverinfo); + + assert_non_null(zoneinfo); + assert_non_null(zoneinfo->aipScavengeServers); + assert_int_equal(zoneinfo->aipScavengeServers->AddrCount, 0); + assert_null(zoneinfo->aipScavengeServers->AddrArray); + + TALLOC_FREE(ctx); +} + +/* + * Test setting of a non empty ZONE_SCAVENGING_SERVERS property + */ +static void test_dnsserver_init_zoneinfo_scavenging_servers(void **state) +{ + struct dnsserver_zone *zone = NULL; + struct dnsserver_serverinfo *serverinfo = NULL; + struct dnsserver_zoneinfo *zoneinfo = NULL; + struct dnsp_DnsProperty *property = NULL; + + TALLOC_CTX *ctx = talloc_new(NULL); + + /* + * Setup the zone data + */ + zone = talloc_zero(ctx, struct dnsserver_zone); + assert_non_null(zone); + zone->name = "test"; + + property = talloc_zero_array(ctx, struct dnsp_DnsProperty, 1); + assert_non_null(property); + property->id = DSPROPERTY_ZONE_SCAVENGING_SERVERS; + property->data.servers.addrCount = 4; + property->data.servers.addrArray = talloc_zero_array(ctx, uint32_t, 4); + property->data.servers.addrArray[0] = 1000; + property->data.servers.addrArray[1] = 1001; + property->data.servers.addrArray[2] = 1002; + property->data.servers.addrArray[3] = 1003; + + zone->tmp_props = property; + zone->num_props = 1; + + + serverinfo = talloc_zero(ctx, struct dnsserver_serverinfo); + assert_non_null(serverinfo); + + zoneinfo = dnsserver_init_zoneinfo(zone, serverinfo); + + assert_non_null(zoneinfo); + assert_non_null(zoneinfo->aipScavengeServers); + assert_int_equal(zoneinfo->aipScavengeServers->AddrCount, 4); + assert_non_null(zoneinfo->aipScavengeServers->AddrArray); + assert_non_null(zoneinfo->aipScavengeServers->AddrArray); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[0], 1000); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[1], 1001); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[2], 1002); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[3], 1003); + + /* + * Ensure that we're working with a copy of the property data + * and not a reference. + * The pointers should be different, and we should be able to change + * the values in the property without affecting the zoneinfo data + */ + assert_true(zoneinfo->aipScavengeServers->AddrArray != + property->data.servers.addrArray); + property->data.servers.addrArray[0] = 0; + property->data.servers.addrArray[1] = 1; + property->data.servers.addrArray[2] = 2; + property->data.servers.addrArray[3] = 3; + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[0], 1000); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[1], 1001); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[2], 1002); + assert_int_equal(zoneinfo->aipScavengeServers->AddrArray[3], 1003); + + + TALLOC_FREE(ctx); +} +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test( + test_dnsserver_init_zoneinfo_master_servers_empty), + cmocka_unit_test( + test_dnsserver_init_zoneinfo_master_servers), + cmocka_unit_test( + test_dnsserver_init_zoneinfo_scavenging_servers_empty), + cmocka_unit_test( + test_dnsserver_init_zoneinfo_scavenging_servers), + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/source4/rpc_server/unixinfo/dcesrv_unixinfo.c b/source4/rpc_server/unixinfo/dcesrv_unixinfo.c new file mode 100644 index 0000000..848c11f --- /dev/null +++ b/source4/rpc_server/unixinfo/dcesrv_unixinfo.c @@ -0,0 +1,191 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the unixinfo pipe + + Copyright (C) Volker Lendecke 2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_unixinfo.h" +#include "libcli/wbclient/wbclient.h" +#include "system/passwd.h" + +static NTSTATUS dcesrv_unixinfo_SidToUid(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct unixinfo_SidToUid *r) +{ + NTSTATUS status; + struct id_map *ids; + + DEBUG(5, ("dcesrv_unixinfo_SidToUid called\n")); + + ids = talloc(mem_ctx, struct id_map); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids->sid = &r->in.sid; + ids->status = ID_UNKNOWN; + ZERO_STRUCT(ids->xid); + status = wbc_sids_to_xids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + if (ids->xid.type == ID_TYPE_BOTH || + ids->xid.type == ID_TYPE_UID) { + *r->out.uid = ids->xid.id; + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_SID; + } +} + +static NTSTATUS dcesrv_unixinfo_UidToSid(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct unixinfo_UidToSid *r) +{ + struct id_map *ids; + uint32_t uid; + NTSTATUS status; + + DEBUG(5, ("dcesrv_unixinfo_UidToSid called\n")); + + uid = r->in.uid; /* This cuts uid to 32 bit */ + if ((uint64_t)uid != r->in.uid) { + DEBUG(10, ("uid out of range\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + ids = talloc(mem_ctx, struct id_map); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids->sid = NULL; + ids->status = ID_UNKNOWN; + + ids->xid.id = uid; + ids->xid.type = ID_TYPE_UID; + + status = wbc_xids_to_sids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + r->out.sid = ids->sid; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_unixinfo_SidToGid(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct unixinfo_SidToGid *r) +{ + NTSTATUS status; + struct id_map *ids; + + DEBUG(5, ("dcesrv_unixinfo_SidToGid called\n")); + + ids = talloc(mem_ctx, struct id_map); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids->sid = &r->in.sid; + ids->status = ID_UNKNOWN; + ZERO_STRUCT(ids->xid); + status = wbc_sids_to_xids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + if (ids->xid.type == ID_TYPE_BOTH || + ids->xid.type == ID_TYPE_GID) { + *r->out.gid = ids->xid.id; + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_SID; + } +} + +static NTSTATUS dcesrv_unixinfo_GidToSid(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct unixinfo_GidToSid *r) +{ + struct id_map *ids; + uint32_t gid; + NTSTATUS status; + + DEBUG(5, ("dcesrv_unixinfo_GidToSid called\n")); + + gid = r->in.gid; /* This cuts gid to 32 bit */ + if ((uint64_t)gid != r->in.gid) { + DEBUG(10, ("gid out of range\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + ids = talloc(mem_ctx, struct id_map); + NT_STATUS_HAVE_NO_MEMORY(ids); + + ids->sid = NULL; + ids->status = ID_UNKNOWN; + + ids->xid.id = gid; + ids->xid.type = ID_TYPE_GID; + + status = wbc_xids_to_sids(ids, 1); + NT_STATUS_NOT_OK_RETURN(status); + + r->out.sid = ids->sid; + return NT_STATUS_OK; +} + +static NTSTATUS dcesrv_unixinfo_GetPWUid(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct unixinfo_GetPWUid *r) +{ + unsigned int i; + + *r->out.count = 0; + + r->out.infos = talloc_zero_array(mem_ctx, struct unixinfo_GetPWUidInfo, + *r->in.count); + NT_STATUS_HAVE_NO_MEMORY(r->out.infos); + *r->out.count = *r->in.count; + + for (i=0; i < *r->in.count; i++) { + uid_t uid; + struct passwd *pwd; + + uid = r->in.uids[i]; + pwd = getpwuid(uid); + if (pwd == NULL) { + DEBUG(10, ("uid %d not found\n", uid)); + r->out.infos[i].homedir = ""; + r->out.infos[i].shell = ""; + r->out.infos[i].status = NT_STATUS_NO_SUCH_USER; + continue; + } + + r->out.infos[i].homedir = talloc_strdup(mem_ctx, pwd->pw_dir); + r->out.infos[i].shell = talloc_strdup(mem_ctx, pwd->pw_shell); + + if ((r->out.infos[i].homedir == NULL) || + (r->out.infos[i].shell == NULL)) { + r->out.infos[i].homedir = ""; + r->out.infos[i].shell = ""; + r->out.infos[i].status = NT_STATUS_NO_MEMORY; + continue; + } + + r->out.infos[i].status = NT_STATUS_OK; + } + + return NT_STATUS_OK; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_unixinfo_s.c" diff --git a/source4/rpc_server/winreg/README b/source4/rpc_server/winreg/README new file mode 100644 index 0000000..03a81e7 --- /dev/null +++ b/source4/rpc_server/winreg/README @@ -0,0 +1,3 @@ +This is the RPC server for the registry for Samba. It is basically a +front-end for the registry library in lib/registry. See +lib/registry/README for more details. diff --git a/source4/rpc_server/winreg/rpc_winreg.c b/source4/rpc_server/winreg/rpc_winreg.c new file mode 100644 index 0000000..3adaafe --- /dev/null +++ b/source4/rpc_server/winreg/rpc_winreg.c @@ -0,0 +1,742 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the winreg pipe + + Copyright (C) 2004 Jelmer Vernooij, jelmer@samba.org + Copyright (C) 2008 Matthias Dieter Wallnöfer, mwallnoefer@yahoo.de + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "lib/registry/registry.h" +#include "librpc/gen_ndr/ndr_winreg.h" +#include "librpc/gen_ndr/ndr_security.h" +#include "libcli/security/session.h" + +enum handle_types { HTYPE_REGVAL, HTYPE_REGKEY }; + +static WERROR dcesrv_winreg_openhive(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, uint32_t hkey, + struct policy_handle **outh) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct registry_context *ctx = NULL; + struct dcesrv_handle *h; + WERROR result; + + h = dcesrv_handle_create(dce_call, HTYPE_REGKEY); + W_ERROR_HAVE_NO_MEMORY(h); + + result = reg_open_samba(h, &ctx, + dce_call->event_ctx, + dce_call->conn->dce_ctx->lp_ctx, + session_info, + NULL); + if (!W_ERROR_IS_OK(result)) { + DEBUG(0, ("Error opening registry: %s\n", win_errstr(result))); + return result; + } + + result = reg_get_predefined_key(ctx, hkey, + (struct registry_key **)&h->data); + if (!W_ERROR_IS_OK(result)) { + return result; + } + *outh = &h->wire_handle; + + return result; +} + +#define func_winreg_OpenHive(k,n) static WERROR dcesrv_winreg_Open ## k (struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, struct winreg_Open ## k *r) \ +{ \ + return dcesrv_winreg_openhive (dce_call, mem_ctx, n, &r->out.handle);\ +} + +func_winreg_OpenHive(HKCR,HKEY_CLASSES_ROOT) +func_winreg_OpenHive(HKCU,HKEY_CURRENT_USER) +func_winreg_OpenHive(HKLM,HKEY_LOCAL_MACHINE) +func_winreg_OpenHive(HKPD,HKEY_PERFORMANCE_DATA) +func_winreg_OpenHive(HKU,HKEY_USERS) +func_winreg_OpenHive(HKCC,HKEY_CURRENT_CONFIG) +func_winreg_OpenHive(HKDD,HKEY_DYN_DATA) +func_winreg_OpenHive(HKPT,HKEY_PERFORMANCE_TEXT) +func_winreg_OpenHive(HKPN,HKEY_PERFORMANCE_NLSTEXT) + +/* + winreg_CloseKey +*/ +static WERROR dcesrv_winreg_CloseKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_CloseKey *r) +{ + struct dcesrv_handle *h; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + + talloc_unlink(dce_call->context, h); + + ZERO_STRUCTP(r->out.handle); + + return WERR_OK; +} + +/* + winreg_CreateKey +*/ +static WERROR dcesrv_winreg_CreateKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_CreateKey *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h, *newh; + struct security_descriptor sd; + struct registry_key *key; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + newh = dcesrv_handle_create(dce_call, HTYPE_REGKEY); + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + /* we support only non volatile keys */ + if (r->in.options != REG_OPTION_NON_VOLATILE) { + return WERR_NOT_SUPPORTED; + } + + /* the security descriptor is optional */ + if (r->in.secdesc != NULL) { + DATA_BLOB sdblob; + enum ndr_err_code ndr_err; + sdblob.data = r->in.secdesc->sd.data; + sdblob.length = r->in.secdesc->sd.len; + if (sdblob.data == NULL) { + return WERR_INVALID_PARAMETER; + } + ndr_err = ndr_pull_struct_blob_all(&sdblob, mem_ctx, &sd, + (ndr_pull_flags_fn_t)ndr_pull_security_descriptor); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return WERR_INVALID_PARAMETER; + } + } + + result = reg_key_add_name(newh, key, r->in.name.name, NULL, + r->in.secdesc?&sd:NULL, (struct registry_key **)&newh->data); + + r->out.action_taken = talloc(mem_ctx, enum winreg_CreateAction); + if (r->out.action_taken == NULL) { + talloc_free(newh); + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.action_taken = REG_ACTION_NONE; + + if (W_ERROR_IS_OK(result)) { + r->out.new_handle = &newh->wire_handle; + *r->out.action_taken = REG_CREATED_NEW_KEY; + } else { + talloc_free(newh); + } + + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_DeleteKey +*/ +static WERROR dcesrv_winreg_DeleteKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_DeleteKey *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + result = reg_key_del(mem_ctx, key, r->in.key.name); + talloc_unlink(dce_call->context, h); + + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_DeleteValue +*/ +static WERROR dcesrv_winreg_DeleteValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_DeleteValue *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + return reg_del_value(mem_ctx, key, r->in.value.name); + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_EnumKey +*/ +static WERROR dcesrv_winreg_EnumKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_EnumKey *r) +{ + struct dcesrv_handle *h; + struct registry_key *key; + const char *name, *classname; + NTTIME last_mod; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + result = reg_key_get_subkey_by_index(mem_ctx, + key, r->in.enum_index, &name, &classname, &last_mod); + + if (2*strlen_m_term(name) > r->in.name->size) { + return WERR_MORE_DATA; + } + + if (name != NULL) { + r->out.name->name = name; + r->out.name->length = 2*strlen_m_term(name); + } else { + r->out.name->name = r->in.name->name; + r->out.name->length = r->in.name->length; + } + r->out.name->size = r->in.name->size; + + r->out.keyclass = r->in.keyclass; + if (classname != NULL) { + r->out.keyclass->name = classname; + r->out.keyclass->length = 2*strlen_m_term(classname); + } else { + r->out.keyclass->name = r->in.keyclass->name; + r->out.keyclass->length = r->in.keyclass->length; + } + r->out.keyclass->size = r->in.keyclass->size; + + if (r->in.last_changed_time != NULL) + r->out.last_changed_time = &last_mod; + + return result; +} + + +/* + winreg_EnumValue +*/ +static WERROR dcesrv_winreg_EnumValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_EnumValue *r) +{ + struct dcesrv_handle *h; + struct registry_key *key; + const char *data_name; + uint32_t data_type; + DATA_BLOB data; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + result = reg_key_get_value_by_index(mem_ctx, key, + r->in.enum_index, &data_name, &data_type, &data); + + if (!W_ERROR_IS_OK(result)) { + /* if the lookup wasn't successful, send client query back */ + data_name = r->in.name->name; + data_type = *r->in.type; + data.data = r->in.value; + data.length = *r->in.length; + } + + /* "data_name" is NULL when we query the default attribute */ + if (data_name != NULL) { + r->out.name->name = data_name; + r->out.name->length = 2*strlen_m_term(data_name); + } else { + r->out.name->name = r->in.name->name; + r->out.name->length = r->in.name->length; + } + r->out.name->size = r->in.name->size; + + r->out.type = talloc(mem_ctx, enum winreg_Type); + if (!r->out.type) { + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.type = (enum winreg_Type) data_type; + + /* check the client has enough room for the value */ + if (r->in.value != NULL && + r->in.size != NULL && + data.length > *r->in.size) { + return WERR_MORE_DATA; + } + + if (r->in.value != NULL) { + r->out.value = data.data; + } + + if (r->in.size != NULL) { + r->out.size = talloc(mem_ctx, uint32_t); + *r->out.size = data.length; + r->out.length = r->out.size; + } + + return result; +} + + +/* + winreg_FlushKey +*/ +static WERROR dcesrv_winreg_FlushKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_FlushKey *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + return reg_key_flush(key); + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_GetKeySecurity +*/ +static WERROR dcesrv_winreg_GetKeySecurity(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_GetKeySecurity *r) +{ + struct dcesrv_handle *h; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_LoadKey +*/ +static WERROR dcesrv_winreg_LoadKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_LoadKey *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_NotifyChangeKeyValue +*/ +static WERROR dcesrv_winreg_NotifyChangeKeyValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_NotifyChangeKeyValue *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_OpenKey +*/ +static WERROR dcesrv_winreg_OpenKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_OpenKey *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h, *newh; + struct registry_key *key; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.parent_handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + case SECURITY_USER: + if (r->in.keyname.name && strcmp(r->in.keyname.name, "") == 0) { + newh = talloc_reference(dce_call->context, h); + result = WERR_OK; + } else { + newh = dcesrv_handle_create(dce_call, HTYPE_REGKEY); + result = reg_open_key(newh, key, r->in.keyname.name, + (struct registry_key **)&newh->data); + } + + if (W_ERROR_IS_OK(result)) { + r->out.handle = &newh->wire_handle; + } else { + talloc_free(newh); + } + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_QueryInfoKey +*/ +static WERROR dcesrv_winreg_QueryInfoKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_QueryInfoKey *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + const char *classname = NULL; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + case SECURITY_USER: + result = reg_key_get_info(mem_ctx, key, &classname, + r->out.num_subkeys, r->out.num_values, + r->out.last_changed_time, r->out.max_subkeylen, + r->out.max_valnamelen, r->out.max_valbufsize); + + if (r->out.max_subkeylen != NULL) { + /* for UTF16 encoding */ + *r->out.max_subkeylen *= 2; + } + if (r->out.max_valnamelen != NULL) { + /* for UTF16 encoding */ + *r->out.max_valnamelen *= 2; + } + + if (classname != NULL) { + r->out.classname->name = classname; + r->out.classname->name_len = 2*strlen_m_term(classname); + } else { + r->out.classname->name = r->in.classname->name; + r->out.classname->name_len = r->in.classname->name_len; + } + r->out.classname->name_size = r->in.classname->name_size; + + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_QueryValue +*/ +static WERROR dcesrv_winreg_QueryValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_QueryValue *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + uint32_t value_type; + DATA_BLOB value_data; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + case SECURITY_USER: + if ((r->in.type == NULL) || (r->in.data_length == NULL) || + (r->in.data_size == NULL)) { + return WERR_INVALID_PARAMETER; + } + + result = reg_key_get_value_by_name(mem_ctx, key, + r->in.value_name->name, &value_type, &value_data); + + if (!W_ERROR_IS_OK(result)) { + /* if the lookup wasn't successful, send client query back */ + value_type = *r->in.type; + value_data.data = r->in.data; + value_data.length = *r->in.data_length; + } else { + if ((r->in.data != NULL) + && (*r->in.data_size < value_data.length)) { + result = WERR_MORE_DATA; + } + } + + r->out.type = talloc(mem_ctx, enum winreg_Type); + if (!r->out.type) { + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.type = (enum winreg_Type) value_type; + r->out.data_length = talloc(mem_ctx, uint32_t); + if (!r->out.data_length) { + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.data_length = value_data.length; + r->out.data_size = talloc(mem_ctx, uint32_t); + if (!r->out.data_size) { + return WERR_NOT_ENOUGH_MEMORY; + } + *r->out.data_size = value_data.length; + r->out.data = value_data.data; + + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_ReplaceKey +*/ +static WERROR dcesrv_winreg_ReplaceKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_ReplaceKey *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_RestoreKey +*/ +static WERROR dcesrv_winreg_RestoreKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_RestoreKey *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_SaveKey +*/ +static WERROR dcesrv_winreg_SaveKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_SaveKey *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_SetKeySecurity +*/ +static WERROR dcesrv_winreg_SetKeySecurity(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_SetKeySecurity *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_SetValue +*/ +static WERROR dcesrv_winreg_SetValue(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_SetValue *r) +{ + struct auth_session_info *session_info = + dcesrv_call_session_info(dce_call); + struct dcesrv_handle *h; + struct registry_key *key; + DATA_BLOB data; + WERROR result; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + key = h->data; + + switch (security_session_user_level(session_info, NULL)) + { + case SECURITY_SYSTEM: + case SECURITY_ADMINISTRATOR: + data.data = r->in.data; + data.length = r->in.size; + result = reg_val_set(key, r->in.name.name, r->in.type, data); + return result; + default: + return WERR_ACCESS_DENIED; + } +} + + +/* + winreg_UnLoadKey +*/ +static WERROR dcesrv_winreg_UnLoadKey(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_UnLoadKey *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_InitiateSystemShutdown +*/ +static WERROR dcesrv_winreg_InitiateSystemShutdown(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_InitiateSystemShutdown *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_AbortSystemShutdown +*/ +static WERROR dcesrv_winreg_AbortSystemShutdown(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_AbortSystemShutdown *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_GetVersion +*/ +static WERROR dcesrv_winreg_GetVersion(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_GetVersion *r) +{ + struct dcesrv_handle *h; + + DCESRV_PULL_HANDLE_FAULT(h, r->in.handle, HTYPE_REGKEY); + + r->out.version = talloc(mem_ctx, uint32_t); + W_ERROR_HAVE_NO_MEMORY(r->out.version); + + *r->out.version = 5; + + return WERR_OK; +} + + +/* + winreg_QueryMultipleValues +*/ +static WERROR dcesrv_winreg_QueryMultipleValues(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_QueryMultipleValues *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_InitiateSystemShutdownEx +*/ +static WERROR dcesrv_winreg_InitiateSystemShutdownEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_InitiateSystemShutdownEx *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_SaveKeyEx +*/ +static WERROR dcesrv_winreg_SaveKeyEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_SaveKeyEx *r) +{ + return WERR_NOT_SUPPORTED; +} + + +/* + winreg_QueryMultipleValues2 +*/ +static WERROR dcesrv_winreg_QueryMultipleValues2(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_QueryMultipleValues2 *r) +{ + return WERR_NOT_SUPPORTED; +} + +/* + winreg_DeleteKeyEx +*/ +static WERROR dcesrv_winreg_DeleteKeyEx(struct dcesrv_call_state *dce_call, + TALLOC_CTX *mem_ctx, + struct winreg_DeleteKeyEx *r) +{ + return WERR_NOT_SUPPORTED; +} + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_winreg_s.c" diff --git a/source4/rpc_server/wkssvc/dcesrv_wkssvc.c b/source4/rpc_server/wkssvc/dcesrv_wkssvc.c new file mode 100644 index 0000000..2c0ba81 --- /dev/null +++ b/source4/rpc_server/wkssvc/dcesrv_wkssvc.c @@ -0,0 +1,403 @@ +/* + Unix SMB/CIFS implementation. + + endpoint server for the wkssvc pipe + + Copyright (C) Stefan (metze) Metzmacher 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "rpc_server/dcerpc_server.h" +#include "librpc/gen_ndr/ndr_wkssvc.h" +#include "librpc/gen_ndr/ndr_srvsvc.h" +#include "rpc_server/common/common.h" +#include "param/param.h" + +/* + wkssvc_NetWkstaGetInfo +*/ +static WERROR dcesrv_wkssvc_NetWkstaGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetWkstaGetInfo *r) +{ + struct dcesrv_context *dce_ctx = dce_call->conn->dce_ctx; + struct dcerpc_server_info *server_info = lpcfg_dcerpc_server_info(mem_ctx, dce_ctx->lp_ctx); + + /* NOTE: win2k3 ignores r->in.server_name completly so we do --metze */ + + switch(r->in.level) { + case 100: + { + struct wkssvc_NetWkstaInfo100 *info100; + + info100 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo100); + W_ERROR_HAVE_NO_MEMORY(info100); + + info100->platform_id = dcesrv_common_get_platform_id(mem_ctx, dce_ctx); + info100->server_name = dcesrv_common_get_server_name(mem_ctx, dce_ctx, NULL); + W_ERROR_HAVE_NO_MEMORY(info100->server_name); + info100->domain_name = server_info->domain_name; + info100->version_major = server_info->version_major; + info100->version_minor = server_info->version_minor; + + r->out.info->info100 = info100; + return WERR_OK; + } + case 101: + { + struct wkssvc_NetWkstaInfo101 *info101; + + info101 = talloc(mem_ctx, struct wkssvc_NetWkstaInfo101); + W_ERROR_HAVE_NO_MEMORY(info101); + + info101->platform_id = dcesrv_common_get_platform_id(mem_ctx, dce_ctx); + info101->server_name = dcesrv_common_get_server_name(mem_ctx, dce_ctx, NULL); + W_ERROR_HAVE_NO_MEMORY(info101->server_name); + info101->domain_name = server_info->domain_name; + info101->version_major = server_info->version_major; + info101->version_minor = server_info->version_minor; + info101->lan_root = dcesrv_common_get_lan_root(mem_ctx, dce_ctx); + + r->out.info->info101 = info101; + return WERR_OK; + } + case 102: + { + return WERR_ACCESS_DENIED; + } + case 502: + { + return WERR_ACCESS_DENIED; + } + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + wkssvc_NetWkstaSetInfo +*/ +static WERROR dcesrv_wkssvc_NetWkstaSetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetWkstaSetInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetWkstaEnumUsers +*/ +static WERROR dcesrv_wkssvc_NetWkstaEnumUsers(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetWkstaEnumUsers *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrWkstaUserGetInfo +*/ +static WERROR dcesrv_wkssvc_NetrWkstaUserGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrWkstaUserGetInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrWkstaUserSetInfo +*/ +static WERROR dcesrv_wkssvc_NetrWkstaUserSetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrWkstaUserSetInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetWkstaTransportEnum +*/ +static WERROR dcesrv_wkssvc_NetWkstaTransportEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetWkstaTransportEnum *r) +{ + switch (r->in.info->level) { + case 0: + r->out.info->ctr.ctr0 = talloc(mem_ctx, struct wkssvc_NetWkstaTransportCtr0); + W_ERROR_HAVE_NO_MEMORY(r->out.info->ctr.ctr0); + + r->out.info->ctr.ctr0->count = 0; + r->out.info->ctr.ctr0->array = NULL; + + return WERR_NOT_SUPPORTED; + + default: + return WERR_INVALID_LEVEL; + } +} + + +/* + wkssvc_NetrWkstaTransportAdd +*/ +static WERROR dcesrv_wkssvc_NetrWkstaTransportAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrWkstaTransportAdd *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrWkstaTransportDel +*/ +static WERROR dcesrv_wkssvc_NetrWkstaTransportDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrWkstaTransportDel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrUseAdd +*/ +static WERROR dcesrv_wkssvc_NetrUseAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUseAdd *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrUseGetInfo +*/ +static WERROR dcesrv_wkssvc_NetrUseGetInfo(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUseGetInfo *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrUseDel +*/ +static WERROR dcesrv_wkssvc_NetrUseDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUseDel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrUseEnum +*/ +static WERROR dcesrv_wkssvc_NetrUseEnum(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUseEnum *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrMessageBufferSend +*/ +static WERROR dcesrv_wkssvc_NetrMessageBufferSend(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrMessageBufferSend *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrWorkstationStatisticsGet +*/ +static WERROR dcesrv_wkssvc_NetrWorkstationStatisticsGet(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrWorkstationStatisticsGet *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrLogonDomainNameAdd +*/ +static WERROR dcesrv_wkssvc_NetrLogonDomainNameAdd(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrLogonDomainNameAdd *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrLogonDomainNameDel +*/ +static WERROR dcesrv_wkssvc_NetrLogonDomainNameDel(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrLogonDomainNameDel *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrJoinDomain +*/ +static WERROR dcesrv_wkssvc_NetrJoinDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrJoinDomain *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrUnjoinDomain +*/ +static WERROR dcesrv_wkssvc_NetrUnjoinDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUnjoinDomain *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrRenameMachineInDomain +*/ +static WERROR dcesrv_wkssvc_NetrRenameMachineInDomain(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrRenameMachineInDomain *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrValidateName +*/ +static WERROR dcesrv_wkssvc_NetrValidateName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrValidateName *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrGetJoinInformation +*/ +static WERROR dcesrv_wkssvc_NetrGetJoinInformation(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrGetJoinInformation *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrGetJoinableOus +*/ +static WERROR dcesrv_wkssvc_NetrGetJoinableOus(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrGetJoinableOus *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + WKSSVC_NETRJOINDOMAIN2 +*/ +static WERROR dcesrv_wkssvc_NetrJoinDomain2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrJoinDomain2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + WKSSVC_NETRUNJOINDOMAIN2 +*/ +static WERROR dcesrv_wkssvc_NetrUnjoinDomain2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrUnjoinDomain2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrRenameMachineInDomain2 +*/ +static WERROR dcesrv_wkssvc_NetrRenameMachineInDomain2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrRenameMachineInDomain2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrValidateName2 +*/ +static WERROR dcesrv_wkssvc_NetrValidateName2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrValidateName2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrGetJoinableOus2 +*/ +static WERROR dcesrv_wkssvc_NetrGetJoinableOus2(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrGetJoinableOus2 *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrAddAlternateComputername +*/ +static WERROR dcesrv_wkssvc_NetrAddAlternateComputerName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrAddAlternateComputerName *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrRemoveAlternateComputername +*/ +static WERROR dcesrv_wkssvc_NetrRemoveAlternateComputerName(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrRemoveAlternateComputerName *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrSetPrimaryComputername +*/ +static WERROR dcesrv_wkssvc_NetrSetPrimaryComputername(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrSetPrimaryComputername *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* + wkssvc_NetrEnumerateComputerNames +*/ +static WERROR dcesrv_wkssvc_NetrEnumerateComputerNames(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, + struct wkssvc_NetrEnumerateComputerNames *r) +{ + DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR); +} + + +/* include the generated boilerplate */ +#include "librpc/gen_ndr/ndr_wkssvc_s.c" diff --git a/source4/rpc_server/wscript_build b/source4/rpc_server/wscript_build new file mode 100644 index 0000000..31ec4f6 --- /dev/null +++ b/source4/rpc_server/wscript_build @@ -0,0 +1,221 @@ +#!/usr/bin/env python + +bld.SAMBA_SUBSYSTEM('DCERPC_SHARE', + source='common/share_info.c', + autoproto='common/share.h', + deps='ldb share', + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER'), + ) + +bld.SAMBA_SUBSYSTEM('DCERPC_COMMON', + source=''' + common/server_info.c + common/forward.c + common/loadparm.c + ''', + autoproto='common/proto.h', + deps='ldb DCERPC_SHARE', + enabled=bld.AD_DC_BUILD_IS_ENABLED() + ) + +bld.SAMBA_LIBRARY('dcerpc_server', + source='dcerpc_server.c', + pc_files='dcerpc_server.pc', + deps='LIBCLI_AUTH ndr samba_server_gensec service auth', + public_deps='dcerpc dcerpc-server-core', + autoproto='dcerpc_server_proto.h', + public_headers='dcerpc_server.h', + vnum='0.0.1', + enabled=bld.AD_DC_BUILD_IS_ENABLED() + ) + +bld.SAMBA_MODULE('dcerpc_rpcecho', + source='echo/rpc_echo.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_rpcecho_init', + deps='ndr-standard events', + enabled=bld.CONFIG_GET('ENABLE_SELFTEST') + ) + + +bld.SAMBA_MODULE('dcerpc_epmapper', + source='epmapper/rpc_epmapper.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_epmapper_init', + deps='NDR_EPMAPPER' + ) + + +bld.SAMBA_MODULE('dcerpc_remote', + source='remote/dcesrv_remote.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_remote_init', + deps='LIBCLI_SMB ndr-table' + ) + + +bld.SAMBA_MODULE('dcerpc_srvsvc', + source='srvsvc/dcesrv_srvsvc.c srvsvc/srvsvc_ntvfs.c', + autoproto='srvsvc/proto.h', + subsystem='dcerpc_server', + init_function='dcerpc_server_srvsvc_init', + deps='DCERPC_COMMON NDR_SRVSVC share ntvfs', + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + + +bld.SAMBA_MODULE('dcerpc_wkssvc', + source='wkssvc/dcesrv_wkssvc.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_wkssvc_init', + deps='DCERPC_COMMON ndr-standard' + ) + + +bld.SAMBA_MODULE('dcerpc_unixinfo', + source='unixinfo/dcesrv_unixinfo.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_unixinfo_init', + deps='DCERPC_COMMON samdb NDR_UNIXINFO LIBWBCLIENT_OLD' + ) + + +bld.SAMBA_MODULE('dcesrv_samr', + source='samr/dcesrv_samr.c samr/samr_password.c', + autoproto='samr/proto.h', + subsystem='dcerpc_server', + init_function='dcerpc_server_samr_init', + deps=''' + samdb + DCERPC_COMMON + ndr-standard + auth4_sam + GNUTLS_HELPERS + DCERPC_HELPER + ''' + ) + + +bld.SAMBA_MODULE('dcerpc_winreg', + source='winreg/rpc_winreg.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_winreg_init', + deps='registry ndr-standard', + internal_module=True, + enabled=bld.CONFIG_SET('WITH_NTVFS_FILESERVER') + ) + + +bld.SAMBA_MODULE('dcerpc_netlogon', + source='netlogon/dcerpc_netlogon.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_netlogon_init', + deps=''' + DCERPC_COMMON + RPC_NDR_IRPC + COMMON_SCHANNEL + ndr-standard + auth4_sam + samba-hostconfig + DSDB_MODULE_HELPERS + util_str_escape + DCERPC_SERVER_NETLOGON + ''' + ) + +bld.SAMBA_MODULE('dcerpc_lsarpc', + source='lsa/dcesrv_lsa.c lsa/lsa_init.c lsa/lsa_lookup.c', + autoproto='lsa/proto.h', + subsystem='dcerpc_server', + init_function='dcerpc_server_lsa_init', + deps=''' + samdb + DCERPC_COMMON + ndr-standard + LIBCLI_AUTH + NDR_DSSETUP + com_err + samba-security + UTIL_LSARPC + ''' + ) + + +bld.SAMBA_MODULE('dcerpc_backupkey', + source='backupkey/dcesrv_backupkey.c ', + autoproto='backupkey/proto.h', + subsystem='dcerpc_server', + init_function='dcerpc_server_backupkey_init', + deps=''' + samdb + DCERPC_COMMON + NDR_BACKUPKEY + RPC_NDR_BACKUPKEY + gnutls + GNUTLS_HELPERS + ''', + ) + + +bld.SAMBA_MODULE('dcerpc_drsuapi', + source=''' + drsuapi/dcesrv_drsuapi.c + drsuapi/updaterefs.c + drsuapi/getncchanges.c + drsuapi/addentry.c + drsuapi/writespn.c + drsuapi/drsutil.c + ''', + subsystem='dcerpc_server', + init_function='dcerpc_server_drsuapi_init', + deps='samdb DCERPC_COMMON NDR_DRSUAPI samba-security' + ) + + +bld.SAMBA_MODULE('dcerpc_browser', + source='browser/dcesrv_browser.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_browser_init', + deps='DCERPC_COMMON NDR_BROWSER' + ) + +bld.SAMBA_MODULE('dcerpc_eventlog', + source='eventlog/dcesrv_eventlog6.c', + subsystem='dcerpc_server', + init_function='dcerpc_server_eventlog6_init', + deps='DCERPC_COMMON' + ) + +bld.SAMBA_MODULE('dcerpc_dnsserver', + source=''' + dnsserver/dcerpc_dnsserver.c + dnsserver/dnsutils.c + dnsserver/dnsdata.c + dnsserver/dnsdb.c + ''', + subsystem='dcerpc_server', + init_function='dcerpc_server_dnsserver_init', + deps='DCERPC_COMMON dnsserver_common netif' + ) + + +bld.SAMBA_MODULE('service_dcerpc', + source='service_rpc.c', + autoproto='service_rpc.h', + subsystem='service', + init_function='server_service_rpc_init', + internal_module=False, + deps='dcerpc_server' + ) + +bld.SAMBA_BINARY('test_rpc_dns_server_dnsutils', + source='tests/rpc_dns_server_dnsutils_test.c', + deps=''' + dnsserver_common + dcerpc_server + cmocka + talloc + ''', + for_selftest=True, + enabled=bld.AD_DC_BUILD_IS_ENABLED() + ) |